What. A new Go package go/outward: the content rule the operator channel applies (messenger's content.go), with the secret class split out as mesh-catalog #178 (issue 383) splits it:
Check(text): what may leave on a channel (roles and words; no address, path or secret's shape).
CheckSecret(text): a secret's shape alone; a path is judged piece by piece between its slashes.
Refusal{Class, What}: never the text itself.
Why. ADR 0310 §4: the operator channel and every node-reads answer apply one rule, kept in the SDK (ADR 0039 §1). mesh-reads (mesh-catalog, same branch name) uses CheckSecret on every line it answers.
Not in this group. The messenger still has its own copy. Switching it to this package changes modules/messenger/cmd/messenger/content.go, which mesh-catalog #178 (issue 383) also changes. I left it out so as not to cut into that work. It is a follow-up once #178 has merged, and I am asking for an issue for it.
Tests.outward_test.go carries messenger's cases unchanged (roles and words pass; every address, path and secret class is refused as before), plus TestCheckSecretRefusesASecretsShapeAndNothingElse. sh go/merge-check.sh passes (gofmt, vet, every Go package's tests).
Order. Merge this first. mesh-catalog's mesh-reads pins it as v0.1.14-0.20261012115257-4dc5f29d3e23.
Part of issue 418 (with mesh-controller and mesh-catalog on fix/418-node-reads). Not merged.
**What.** A new Go package `go/outward`: the content rule the operator channel applies (messenger's `content.go`), with the secret class split out as mesh-catalog #178 (issue 383) splits it:
- `Check(text)`: what may leave on a channel (roles and words; no address, path or secret's shape).
- `CheckSecret(text)`: a secret's shape alone; a path is judged piece by piece between its slashes.
- `Refusal{Class, What}`: never the text itself.
**Why.** ADR 0310 §4: the operator channel and every `node-reads` answer apply one rule, kept in the SDK (ADR 0039 §1). `mesh-reads` (mesh-catalog, same branch name) uses `CheckSecret` on every line it answers.
**Not in this group.** The messenger still has its own copy. Switching it to this package changes `modules/messenger/cmd/messenger/content.go`, which mesh-catalog #178 (issue 383) also changes. I left it out so as not to cut into that work. It is a follow-up once #178 has merged, and I am asking for an issue for it.
**Tests.** `outward_test.go` carries messenger's cases unchanged (roles and words pass; every address, path and secret class is refused as before), plus `TestCheckSecretRefusesASecretsShapeAndNothingElse`. `sh go/merge-check.sh` passes (gofmt, vet, every Go package's tests).
**Order.** Merge this first. mesh-catalog's `mesh-reads` pins it as `v0.1.14-0.20261012115257-4dc5f29d3e23`.
Part of issue 418 (with mesh-controller and mesh-catalog on `fix/418-node-reads`). Not merged.
The operator channel's rule (messenger's content.go, with the secret class
split out as in issue 383's branch) moves here as package outward: Check for
what may leave on a channel, CheckSecret for a secret's shape alone, which is
what a node-reads answer is held to. The messenger switches to it once
mesh-catalog #178, which changes that file, has merged.
composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
Machines
mesh-issues on novox: passed — healthy 1 times over 21s
mesh-sdk on every machine running it: sent
mesh-issues on the rest running it: sent
Transitions
2026-10-12 12:27 (new) → proposed (announced): novox/mesh-sdk#23's head announced
2026-10-12 12:30 proposed → checked (checked): the verdict names this commit
2026-10-12 12:30 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-12 13:27 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-12 13:27 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
2026-10-12 13:29 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-sdk@67bfa22132f2` — **delivered** since 2026-10-12T13:29:20Z
**Delivery plan** — builds mesh-sdk, sent nowhere; no bus step
- tier 0: mesh-sdk
**Group** `fix/418-node-reads`, in order: novox/mesh-controller@63a99d43237f → novox/mesh-sdk@67bfa22132f2
- novox/mesh-controller@63a99d43237f before novox/mesh-sdk@67bfa22132f2: built by
- composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
**Machines**
- mesh-issues on novox: passed — healthy 1 times over 21s
- mesh-sdk on every machine running it: sent
- mesh-issues on the rest running it: sent
**Transitions**
- 2026-10-12 12:27 (new) → proposed (announced): novox/mesh-sdk#23's head announced
- 2026-10-12 12:30 proposed → checked (checked): the verdict names this commit
- 2026-10-12 12:30 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-12 13:27 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-12 13:27 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
- 2026-10-12 13:29 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)
Delivery plan — builds mesh-sdk, sent nowhere; no bus step
tier 0: mesh-sdk
Run by the build seat on ace as build-1791806709621577837 (builds --log build-1791806709621577837).
duration_ms: 64.89474
type: 'test'
...
# Subtest: delete refuses an active consumer and deletes only the named retired one
ok 12 - delete refuses an active consumer and deletes only the named retired one
---
duration_ms: 58.886762
type: 'test'
...
# Subtest: an addition acts on the first pass
ok 13 - an addition acts on the first pass
---
duration_ms: 11.936628
type: 'test'
...
# Subtest: what the backend holds at start is retired by the same rules
ok 14 - what the backend holds at start is retired by the same rules
---
duration_ms: 46.0531
type: 'test'
...
# Subtest: the four tools are registered for the module and answer
ok 15 - the four tools are registered for the module and answer
---
duration_ms: 0.862041
type: 'test'
...
# [tools] broken: contributor failed, exposing none: Error: no token
# Subtest: semver orders releases
ok 16 - semver orders releases
---
duration_ms: 0.885165
type: 'test'
...
# Subtest: module tools register and collect, a thrower is skipped not fatal
ok 17 - module tools register and collect, a thrower is skipped not fatal
---
duration_ms: 0.589719
type: 'test'
...
# [provisioner:analytics] webapp-anchor: no longer in /tmp/prov-0GMZtV/analytics.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so webapp-anchor KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:08:19.759Z
# [provisioner:analytics] webapp-anchor: DELETING, asked by someone: gone for good
# Subtest: provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
ok 18 - provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
---
duration_ms: 216.896339
type: 'test'
...
# [provisioner:s3-bucket] mesh_anchor_ncloud: no longer in /tmp/prov-derived-LxT7J8/store.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_ncloud KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:08:19.993Z
# [provisioner:s3-bucket] mesh_anchor_ncloud: DELETING, asked by someone: unassigned
# Subtest: provisioner hands the adapter what the mesh derived, and still has it at withdrawal
ok 19 - provisioner hands the adapter what the mesh derived, and still has it at withdrawal
---
duration_ms: 134.075528
type: 'test'
...
# Subtest: provisioner stops calling an unreadable secret a race, and says what to look at
ok 20 - provisioner stops calling an unreadable secret a race, and says what to look at
---
duration_ms: 72.008027
type: 'test'
...
# [provisioner:postgres-database] contributions file unreadable (/tmp/prov-WegL2H/postgres-database.json): ENOENT: no such file or directory, open '/tmp/prov-WegL2H/postgres-database.json'; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-WegL2H/postgres-database.json): SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3); nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-WegL2H/postgres-database.json is for s3-bucket, not postgres-database; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-WegL2H/postgres-database.json has no given list; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-WegL2H/postgres-database.json): SyntaxError: Unexpected end of JSON input; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] RETIREMENT WAITS FOR A PERSON: the mesh no longer asks for 2 of the 2 consumer(s) this provider holds (mesh_anchor_forge, mesh_anchor_mail), more than 3, or more than 50% of the 2 held. Nothing is retired until `retire approve` or `retire reject` (novox/hq ADR 0230)
# [provisioner:postgres-database] retirement of mesh_anchor_forge, mesh_anchor_mail approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_forge: no longer in /tmp/prov-WegL2H/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_forge KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_mail: no longer in /tmp/prov-WegL2H/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_mail KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# Subtest: provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
ok 21 - provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
---
duration_ms: 1306.684282
type: 'test'
...
# [provisioner:cache] webapp-anchor: the backend no longer holds it; applying again
# Subtest: provisioner applies again what the backend no longer holds, and trusts memory without holds
ok 22 - provisioner applies again what the backend no longer holds, and trusts memory without holds
---
duration_ms: 212.539567
type: 'test'
...
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# Subtest: provisioner keeps a consumer applied when the backend cannot be asked
ok 23 - provisioner keeps a consumer applied when the backend cannot be asked
---
duration_ms: 71.4734
type: 'test'
...
# Subtest: provisioner backs off when create does not satisfy holds, and says so
ok 24 - provisioner backs off when create does not satisfy holds, and says so
---
duration_ms: 402.256375
type: 'test'
...
# Subtest: provisioner treats a failed or hung holds as could-not-ask, and never logs the password
ok 25 - provisioner treats a failed or hung holds as could-not-ask, and never logs the password
---
duration_ms: 403.027537
type: 'test'
...
# Subtest: provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
ok 26 - provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
---
duration_ms: 302.029577
type: 'test'
...
# Subtest: modules can SERVE: a real async tool, loaded and invoked over the broker
ok 27 - modules can SERVE: a real async tool, loaded and invoked over the broker
---
duration_ms: 26.554996
type: 'test'
...
# Subtest: serving refuses one module exposing two tools of the same name
ok 28 - serving refuses one module exposing two tools of the same name
---
duration_ms: 0.252024
type: 'test'
...
# Subtest: events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
ok 29 - events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
---
duration_ms: 0.567147
type: 'test'
...
# Subtest: each registration is given the environment asked for it, and sees no other's (ADR 0192)
ok 30 - each registration is given the environment asked for it, and sees no other's (ADR 0192)
---
duration_ms: 0.193815
type: 'test'
...
# Subtest: a bundle on stdio lists its tools and answers a call, a seat's verb among them
ok 31 - a bundle on stdio lists its tools and answers a call, a seat's verb among them
---
duration_ms: 124.336998
type: 'test'
...
# Subtest: a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
ok 32 - a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
---
duration_ms: 82.953138
type: 'test'
...
# Subtest: a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
ok 33 - a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
---
duration_ms: 84.670397
type: 'test'
...
# Subtest: a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
ok 34 - a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
---
duration_ms: 3198.443269
type: 'test'
...
# Subtest: a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
ok 35 - a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
---
duration_ms: 83.059367
type: 'test'
...
# Subtest: a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
ok 36 - a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
---
duration_ms: 283.915073
type: 'test'
...
# Subtest: topics match as the runtime's do: * one segment, \# and ** any number
ok 37 - topics match as the runtime's do: * one segment, \# and ** any number
---
duration_ms: 2.346133
type: 'test'
...
1..37
# tests 37
# suites 0
# pass 37
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 3934.417378
NOT TESTED HERE: go/ (the Go binding) — the Go toolchain runs it from go/merge-check.sh
NOT TESTED HERE: python/, rust/, c/ — no toolchain of theirs runs a merge check
--- its go/merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-sdk/go 0.121s
ok git.novox.be/novox/mesh-sdk/go/asker 0.002s
ok git.novox.be/novox/mesh-sdk/go/asks 0.002s
ok git.novox.be/novox/mesh-sdk/go/cli 1.035s
? git.novox.be/novox/mesh-sdk/go/example [no test files]
ok git.novox.be/novox/mesh-sdk/go/health 0.002s
ok git.novox.be/novox/mesh-sdk/go/outward 0.003s
**Merge check** at `4dc5f29d`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)
**Delivery plan** — builds mesh-sdk, sent nowhere; no bus step
- tier 0: mesh-sdk
Run by the build seat on ace as `build-1791806709621577837` (`builds --log build-1791806709621577837`).
```
duration_ms: 64.89474
type: 'test'
...
# Subtest: delete refuses an active consumer and deletes only the named retired one
ok 12 - delete refuses an active consumer and deletes only the named retired one
---
duration_ms: 58.886762
type: 'test'
...
# Subtest: an addition acts on the first pass
ok 13 - an addition acts on the first pass
---
duration_ms: 11.936628
type: 'test'
...
# Subtest: what the backend holds at start is retired by the same rules
ok 14 - what the backend holds at start is retired by the same rules
---
duration_ms: 46.0531
type: 'test'
...
# Subtest: the four tools are registered for the module and answer
ok 15 - the four tools are registered for the module and answer
---
duration_ms: 0.862041
type: 'test'
...
# [tools] broken: contributor failed, exposing none: Error: no token
# Subtest: semver orders releases
ok 16 - semver orders releases
---
duration_ms: 0.885165
type: 'test'
...
# Subtest: module tools register and collect, a thrower is skipped not fatal
ok 17 - module tools register and collect, a thrower is skipped not fatal
---
duration_ms: 0.589719
type: 'test'
...
# [provisioner:analytics] webapp-anchor: no longer in /tmp/prov-0GMZtV/analytics.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so webapp-anchor KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:08:19.759Z
# [provisioner:analytics] webapp-anchor: DELETING, asked by someone: gone for good
# Subtest: provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
ok 18 - provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
---
duration_ms: 216.896339
type: 'test'
...
# [provisioner:s3-bucket] mesh_anchor_ncloud: no longer in /tmp/prov-derived-LxT7J8/store.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_ncloud KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:08:19.993Z
# [provisioner:s3-bucket] mesh_anchor_ncloud: DELETING, asked by someone: unassigned
# Subtest: provisioner hands the adapter what the mesh derived, and still has it at withdrawal
ok 19 - provisioner hands the adapter what the mesh derived, and still has it at withdrawal
---
duration_ms: 134.075528
type: 'test'
...
# Subtest: provisioner stops calling an unreadable secret a race, and says what to look at
ok 20 - provisioner stops calling an unreadable secret a race, and says what to look at
---
duration_ms: 72.008027
type: 'test'
...
# [provisioner:postgres-database] contributions file unreadable (/tmp/prov-WegL2H/postgres-database.json): ENOENT: no such file or directory, open '/tmp/prov-WegL2H/postgres-database.json'; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-WegL2H/postgres-database.json): SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3); nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-WegL2H/postgres-database.json is for s3-bucket, not postgres-database; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-WegL2H/postgres-database.json has no given list; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-WegL2H/postgres-database.json): SyntaxError: Unexpected end of JSON input; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] RETIREMENT WAITS FOR A PERSON: the mesh no longer asks for 2 of the 2 consumer(s) this provider holds (mesh_anchor_forge, mesh_anchor_mail), more than 3, or more than 50% of the 2 held. Nothing is retired until `retire approve` or `retire reject` (novox/hq ADR 0230)
# [provisioner:postgres-database] retirement of mesh_anchor_forge, mesh_anchor_mail approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_forge: no longer in /tmp/prov-WegL2H/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_forge KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_mail: no longer in /tmp/prov-WegL2H/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_mail KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# Subtest: provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
ok 21 - provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
---
duration_ms: 1306.684282
type: 'test'
...
# [provisioner:cache] webapp-anchor: the backend no longer holds it; applying again
# Subtest: provisioner applies again what the backend no longer holds, and trusts memory without holds
ok 22 - provisioner applies again what the backend no longer holds, and trusts memory without holds
---
duration_ms: 212.539567
type: 'test'
...
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# Subtest: provisioner keeps a consumer applied when the backend cannot be asked
ok 23 - provisioner keeps a consumer applied when the backend cannot be asked
---
duration_ms: 71.4734
type: 'test'
...
# Subtest: provisioner backs off when create does not satisfy holds, and says so
ok 24 - provisioner backs off when create does not satisfy holds, and says so
---
duration_ms: 402.256375
type: 'test'
...
# Subtest: provisioner treats a failed or hung holds as could-not-ask, and never logs the password
ok 25 - provisioner treats a failed or hung holds as could-not-ask, and never logs the password
---
duration_ms: 403.027537
type: 'test'
...
# Subtest: provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
ok 26 - provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
---
duration_ms: 302.029577
type: 'test'
...
# Subtest: modules can SERVE: a real async tool, loaded and invoked over the broker
ok 27 - modules can SERVE: a real async tool, loaded and invoked over the broker
---
duration_ms: 26.554996
type: 'test'
...
# Subtest: serving refuses one module exposing two tools of the same name
ok 28 - serving refuses one module exposing two tools of the same name
---
duration_ms: 0.252024
type: 'test'
...
# Subtest: events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
ok 29 - events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
---
duration_ms: 0.567147
type: 'test'
...
# Subtest: each registration is given the environment asked for it, and sees no other's (ADR 0192)
ok 30 - each registration is given the environment asked for it, and sees no other's (ADR 0192)
---
duration_ms: 0.193815
type: 'test'
...
# Subtest: a bundle on stdio lists its tools and answers a call, a seat's verb among them
ok 31 - a bundle on stdio lists its tools and answers a call, a seat's verb among them
---
duration_ms: 124.336998
type: 'test'
...
# Subtest: a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
ok 32 - a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
---
duration_ms: 82.953138
type: 'test'
...
# Subtest: a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
ok 33 - a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
---
duration_ms: 84.670397
type: 'test'
...
# Subtest: a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
ok 34 - a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
---
duration_ms: 3198.443269
type: 'test'
...
# Subtest: a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
ok 35 - a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
---
duration_ms: 83.059367
type: 'test'
...
# Subtest: a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
ok 36 - a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
---
duration_ms: 283.915073
type: 'test'
...
# Subtest: topics match as the runtime's do: * one segment, \# and ** any number
ok 37 - topics match as the runtime's do: * one segment, \# and ** any number
---
duration_ms: 2.346133
type: 'test'
...
1..37
# tests 37
# suites 0
# pass 37
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 3934.417378
NOT TESTED HERE: go/ (the Go binding) — the Go toolchain runs it from go/merge-check.sh
NOT TESTED HERE: python/, rust/, c/ — no toolchain of theirs runs a merge check
--- its go/merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-sdk/go 0.121s
ok git.novox.be/novox/mesh-sdk/go/asker 0.002s
ok git.novox.be/novox/mesh-sdk/go/asks 0.002s
ok git.novox.be/novox/mesh-sdk/go/cli 1.035s
? git.novox.be/novox/mesh-sdk/go/example [no test files]
ok git.novox.be/novox/mesh-sdk/go/health 0.002s
ok git.novox.be/novox/mesh-sdk/go/outward 0.003s
```
Independent review for issue 418 (ADR 0310 §4). go vet ./... is clean and go test -race ./... passes in go/. The package is new and additive, so nothing that imports the SDK today changes. Check keeps the operator channel's cases.
Required: CheckSecret misses the shapes that node-reads will meet most often. node-reads applies CheckSecret to every line of env files, unit files, configs and history files. I fed it shapes from those files, and these pass as clean:
A credential inside a URL: nats://user:pa55word@host:4222. Any scheme://user:pass@ should be refused by its shape, whatever the password looks like.
An environment-style name that ends in a secret word: MESH_TOKEN=s3cr3t-v4lue, SUDO_PASSWORD hunter2. The \b in reAssigned does not fire after _, so DB_PASS=, *_TOKEN=, *_SECRET= and *_PASSWORD= are only caught when the value happens to look random.
A name and a value with spaces around =: aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY. The spaces break the run, and the slashes split the value into pieces shorter than 20 characters.
Base64 values: of 1000 random 32-byte base64 values, 24 pass, and of 1000 random 16-byte base64url values, 246 pass. Splitting a run at / (needed for paths) lets base64 through when the slashes fall evenly.
Suggested fix:
Add a named shape for URL userinfo ([a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@).
Let reAssigned match a secret word at the end of a name with _ or - before it ((?i)(^|[^a-z0-9])[a-z0-9_]*(password|passwd|pass|pwd|secret|token|api_?key|private_?key|psk|credential)s?\s*[=: ]\s*\S, tuned against the existing cases).
Split a run at / only when the run starts with /, ~ or ./, or contains //, so base64 is judged whole.
Add each of the shapes above as a test case.
Follow-up, not blocking here: mesh-controller still keeps its own copy in internal/outward ("mirrored, not imported"). Once the messenger moves onto this package, the controller should import it too, so ADR 0310 §4's "one rule" holds. The PR already names the messenger move as a follow-up under issue 418.
Merge order for the group: this PR first (after the fix), then mesh-controller #257, then the catalogue module.
Verdict: request changes at 4dc5f29d3e23a06d58fd1dcc7e2c7ea96a16917b
Independent review for issue 418 (ADR 0310 §4). `go vet ./...` is clean and `go test -race ./...` passes in `go/`. The package is new and additive, so nothing that imports the SDK today changes. `Check` keeps the operator channel's cases.
**Required: `CheckSecret` misses the shapes that node-reads will meet most often.** node-reads applies `CheckSecret` to every line of env files, unit files, configs and history files. I fed it shapes from those files, and these pass as clean:
- A credential inside a URL: `nats://user:pa55word@host:4222`. Any `scheme://user:pass@` should be refused by its shape, whatever the password looks like.
- An environment-style name that ends in a secret word: `MESH_TOKEN=s3cr3t-v4lue`, `SUDO_PASSWORD hunter2`. The `\b` in `reAssigned` does not fire after `_`, so `DB_PASS=`, `*_TOKEN=`, `*_SECRET=` and `*_PASSWORD=` are only caught when the value happens to look random.
- A name and a value with spaces around `=`: `aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. The spaces break the run, and the slashes split the value into pieces shorter than 20 characters.
- Base64 values: of 1000 random 32-byte base64 values, 24 pass, and of 1000 random 16-byte base64url values, 246 pass. Splitting a run at `/` (needed for paths) lets base64 through when the slashes fall evenly.
Suggested fix:
- Add a named shape for URL userinfo (`[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@`).
- Let `reAssigned` match a secret word at the end of a name with `_` or `-` before it (`(?i)(^|[^a-z0-9])[a-z0-9_]*(password|passwd|pass|pwd|secret|token|api_?key|private_?key|psk|credential)s?\s*[=: ]\s*\S`, tuned against the existing cases).
- Split a run at `/` only when the run starts with `/`, `~` or `./`, or contains `//`, so base64 is judged whole.
- Add each of the shapes above as a test case.
**Follow-up, not blocking here:** mesh-controller still keeps its own copy in `internal/outward` ("mirrored, not imported"). Once the messenger moves onto this package, the controller should import it too, so ADR 0310 §4's "one rule" holds. The PR already names the messenger move as a follow-up under issue 418.
Merge order for the group: this PR first (after the fix), then mesh-controller #257, then the catalogue module.
node-reads applies CheckSecret to env, unit and configuration files, where
user:pass@ in a URL, DB_PASS=, MESH_TOKEN= and name = value with spaces passed,
and a random base64 value passed when its slashes or dashes cut it into short
pieces. A run is now judged in pieces only where it is a path, base64 padding
marks a value, and letters alone are random when the case changes as a random
string's does, so a CamelCase name still passes.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)
Delivery plan — builds mesh-sdk, sent nowhere; no bus step
tier 0: mesh-sdk
Run by the build seat on g14 as build-1791808075767291231 (builds --log build-1791808075767291231).
---
duration_ms: 68.105048
type: 'test'
...
# Subtest: delete refuses an active consumer and deletes only the named retired one
ok 12 - delete refuses an active consumer and deletes only the named retired one
---
duration_ms: 60.240992
type: 'test'
...
# Subtest: an addition acts on the first pass
ok 13 - an addition acts on the first pass
---
duration_ms: 16.95916
type: 'test'
...
# Subtest: what the backend holds at start is retired by the same rules
ok 14 - what the backend holds at start is retired by the same rules
---
duration_ms: 47.675033
type: 'test'
...
# Subtest: the four tools are registered for the module and answer
ok 15 - the four tools are registered for the module and answer
---
duration_ms: 1.389165
type: 'test'
...
# [tools] broken: contributor failed, exposing none: Error: no token
# Subtest: semver orders releases
ok 16 - semver orders releases
---
duration_ms: 0.787072
type: 'test'
...
# Subtest: module tools register and collect, a thrower is skipped not fatal
ok 17 - module tools register and collect, a thrower is skipped not fatal
---
duration_ms: 0.497968
type: 'test'
...
# [provisioner:analytics] webapp-anchor: no longer in /tmp/prov-Kp26WK/analytics.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so webapp-anchor KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:30:23.919Z
# [provisioner:analytics] webapp-anchor: DELETING, asked by someone: gone for good
# Subtest: provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
ok 18 - provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
---
duration_ms: 211.145413
type: 'test'
...
# [provisioner:s3-bucket] mesh_anchor_ncloud: no longer in /tmp/prov-derived-SBNKyO/store.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_ncloud KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:30:24.146Z
# [provisioner:s3-bucket] mesh_anchor_ncloud: DELETING, asked by someone: unassigned
# Subtest: provisioner hands the adapter what the mesh derived, and still has it at withdrawal
ok 19 - provisioner hands the adapter what the mesh derived, and still has it at withdrawal
---
duration_ms: 125.530489
type: 'test'
...
# Subtest: provisioner stops calling an unreadable secret a race, and says what to look at
ok 20 - provisioner stops calling an unreadable secret a race, and says what to look at
---
duration_ms: 71.217959
type: 'test'
...
# [provisioner:postgres-database] contributions file unreadable (/tmp/prov-PXxpRn/postgres-database.json): ENOENT: no such file or directory, open '/tmp/prov-PXxpRn/postgres-database.json'; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-PXxpRn/postgres-database.json): SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3); nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-PXxpRn/postgres-database.json is for s3-bucket, not postgres-database; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-PXxpRn/postgres-database.json has no given list; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] RETIREMENT WAITS FOR A PERSON: the mesh no longer asks for 2 of the 2 consumer(s) this provider holds (mesh_anchor_forge, mesh_anchor_mail), more than 3, or more than 50% of the 2 held. Nothing is retired until `retire approve` or `retire reject` (novox/hq ADR 0230)
# [provisioner:postgres-database] retirement of mesh_anchor_forge, mesh_anchor_mail approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_forge: no longer in /tmp/prov-PXxpRn/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_forge KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_mail: no longer in /tmp/prov-PXxpRn/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_mail KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# Subtest: provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
ok 21 - provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
---
duration_ms: 1308.584045
type: 'test'
...
# [provisioner:cache] webapp-anchor: the backend no longer holds it; applying again
# Subtest: provisioner applies again what the backend no longer holds, and trusts memory without holds
ok 22 - provisioner applies again what the backend no longer holds, and trusts memory without holds
---
duration_ms: 212.495164
type: 'test'
...
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# Subtest: provisioner keeps a consumer applied when the backend cannot be asked
ok 23 - provisioner keeps a consumer applied when the backend cannot be asked
---
duration_ms: 71.523213
type: 'test'
...
# Subtest: provisioner backs off when create does not satisfy holds, and says so
ok 24 - provisioner backs off when create does not satisfy holds, and says so
---
duration_ms: 404.301677
type: 'test'
...
# Subtest: provisioner treats a failed or hung holds as could-not-ask, and never logs the password
ok 25 - provisioner treats a failed or hung holds as could-not-ask, and never logs the password
---
duration_ms: 402.253872
type: 'test'
...
# Subtest: provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
ok 26 - provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
---
duration_ms: 302.519432
type: 'test'
...
# Subtest: modules can SERVE: a real async tool, loaded and invoked over the broker
ok 27 - modules can SERVE: a real async tool, loaded and invoked over the broker
---
duration_ms: 60.207829
type: 'test'
...
# Subtest: serving refuses one module exposing two tools of the same name
ok 28 - serving refuses one module exposing two tools of the same name
---
duration_ms: 0.252866
type: 'test'
...
# Subtest: events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
ok 29 - events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
---
duration_ms: 0.570144
type: 'test'
...
# Subtest: each registration is given the environment asked for it, and sees no other's (ADR 0192)
ok 30 - each registration is given the environment asked for it, and sees no other's (ADR 0192)
---
duration_ms: 0.165211
type: 'test'
...
# Subtest: a bundle on stdio lists its tools and answers a call, a seat's verb among them
ok 31 - a bundle on stdio lists its tools and answers a call, a seat's verb among them
---
duration_ms: 125.877461
type: 'test'
...
# Subtest: a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
ok 32 - a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
---
duration_ms: 83.441353
type: 'test'
...
# Subtest: a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
ok 33 - a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
---
duration_ms: 84.991932
type: 'test'
...
# Subtest: a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
ok 34 - a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
---
duration_ms: 3221.341964
type: 'test'
...
# Subtest: a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
ok 35 - a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
---
duration_ms: 82.868584
type: 'test'
...
# Subtest: a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
ok 36 - a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
---
duration_ms: 271.001279
type: 'test'
...
# Subtest: topics match as the runtime's do: * one segment, \# and ** any number
ok 37 - topics match as the runtime's do: * one segment, \# and ** any number
---
duration_ms: 2.368209
type: 'test'
...
1..37
# tests 37
# suites 0
# pass 37
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 3963.915328
NOT TESTED HERE: go/ (the Go binding) — the Go toolchain runs it from go/merge-check.sh
NOT TESTED HERE: python/, rust/, c/ — no toolchain of theirs runs a merge check
--- its go/merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-sdk/go 0.182s
ok git.novox.be/novox/mesh-sdk/go/asker 0.009s
ok git.novox.be/novox/mesh-sdk/go/asks 0.008s
ok git.novox.be/novox/mesh-sdk/go/cli 1.377s
? git.novox.be/novox/mesh-sdk/go/example [no test files]
ok git.novox.be/novox/mesh-sdk/go/health 0.009s
ok git.novox.be/novox/mesh-sdk/go/outward 0.114s
**Merge check** at `67bfa221`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)
**Delivery plan** — builds mesh-sdk, sent nowhere; no bus step
- tier 0: mesh-sdk
Run by the build seat on g14 as `build-1791808075767291231` (`builds --log build-1791808075767291231`).
```
---
duration_ms: 68.105048
type: 'test'
...
# Subtest: delete refuses an active consumer and deletes only the named retired one
ok 12 - delete refuses an active consumer and deletes only the named retired one
---
duration_ms: 60.240992
type: 'test'
...
# Subtest: an addition acts on the first pass
ok 13 - an addition acts on the first pass
---
duration_ms: 16.95916
type: 'test'
...
# Subtest: what the backend holds at start is retired by the same rules
ok 14 - what the backend holds at start is retired by the same rules
---
duration_ms: 47.675033
type: 'test'
...
# Subtest: the four tools are registered for the module and answer
ok 15 - the four tools are registered for the module and answer
---
duration_ms: 1.389165
type: 'test'
...
# [tools] broken: contributor failed, exposing none: Error: no token
# Subtest: semver orders releases
ok 16 - semver orders releases
---
duration_ms: 0.787072
type: 'test'
...
# Subtest: module tools register and collect, a thrower is skipped not fatal
ok 17 - module tools register and collect, a thrower is skipped not fatal
---
duration_ms: 0.497968
type: 'test'
...
# [provisioner:analytics] webapp-anchor: no longer in /tmp/prov-Kp26WK/analytics.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so webapp-anchor KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:30:23.919Z
# [provisioner:analytics] webapp-anchor: DELETING, asked by someone: gone for good
# Subtest: provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
ok 18 - provisioner creates each consumer with the mesh's login and password; remove waits for a person's delete
---
duration_ms: 211.145413
type: 'test'
...
# [provisioner:s3-bucket] mesh_anchor_ncloud: no longer in /tmp/prov-derived-SBNKyO/store.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_ncloud KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): the mesh stopped asking for it: the same answer in 5 passes over 0s, from 2026-10-12T12:30:24.146Z
# [provisioner:s3-bucket] mesh_anchor_ncloud: DELETING, asked by someone: unassigned
# Subtest: provisioner hands the adapter what the mesh derived, and still has it at withdrawal
ok 19 - provisioner hands the adapter what the mesh derived, and still has it at withdrawal
---
duration_ms: 125.530489
type: 'test'
...
# Subtest: provisioner stops calling an unreadable secret a race, and says what to look at
ok 20 - provisioner stops calling an unreadable secret a race, and says what to look at
---
duration_ms: 71.217959
type: 'test'
...
# [provisioner:postgres-database] contributions file unreadable (/tmp/prov-PXxpRn/postgres-database.json): ENOENT: no such file or directory, open '/tmp/prov-PXxpRn/postgres-database.json'; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] contributions file is not JSON (/tmp/prov-PXxpRn/postgres-database.json): SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3); nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-PXxpRn/postgres-database.json is for s3-bucket, not postgres-database; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] /tmp/prov-PXxpRn/postgres-database.json has no given list; nothing applied or removed until it can be read
# [provisioner:postgres-database] contributions file readable again
# [provisioner:postgres-database] RETIREMENT WAITS FOR A PERSON: the mesh no longer asks for 2 of the 2 consumer(s) this provider holds (mesh_anchor_forge, mesh_anchor_mail), more than 3, or more than 50% of the 2 held. Nothing is retired until `retire approve` or `retire reject` (novox/hq ADR 0230)
# [provisioner:postgres-database] retirement of mesh_anchor_forge, mesh_anchor_mail approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_forge: no longer in /tmp/prov-PXxpRn/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_forge KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# [provisioner:postgres-database] mesh_anchor_mail: no longer in /tmp/prov-PXxpRn/postgres-database.json; RETIRED, MARK ONLY — this provider cannot disable a consumer (its adapter has no retire), so mesh_anchor_mail KEEPS ITS ACCESS and its data until a person deletes it with `cleanup delete`. Nothing was changed in the backend (novox/hq ADR 0230): approved by test: both unassigned on purpose
# Subtest: provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
ok 21 - provisioner removes nothing while its contributions file cannot be used (novox/hq issue 241)
---
duration_ms: 1308.584045
type: 'test'
...
# [provisioner:cache] webapp-anchor: the backend no longer holds it; applying again
# Subtest: provisioner applies again what the backend no longer holds, and trusts memory without holds
ok 22 - provisioner applies again what the backend no longer holds, and trusts memory without holds
---
duration_ms: 212.495164
type: 'test'
...
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# [provisioner:cache] webapp-anchor: could not check the backend, will ask again: Error: connection refused
# Subtest: provisioner keeps a consumer applied when the backend cannot be asked
ok 23 - provisioner keeps a consumer applied when the backend cannot be asked
---
duration_ms: 71.523213
type: 'test'
...
# Subtest: provisioner backs off when create does not satisfy holds, and says so
ok 24 - provisioner backs off when create does not satisfy holds, and says so
---
duration_ms: 404.301677
type: 'test'
...
# Subtest: provisioner treats a failed or hung holds as could-not-ask, and never logs the password
ok 25 - provisioner treats a failed or hung holds as could-not-ask, and never logs the password
---
duration_ms: 402.253872
type: 'test'
...
# Subtest: provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
ok 26 - provisioner stops a verify pass at a timeout, and retries a failed re-apply at the next check
---
duration_ms: 302.519432
type: 'test'
...
# Subtest: modules can SERVE: a real async tool, loaded and invoked over the broker
ok 27 - modules can SERVE: a real async tool, loaded and invoked over the broker
---
duration_ms: 60.207829
type: 'test'
...
# Subtest: serving refuses one module exposing two tools of the same name
ok 28 - serving refuses one module exposing two tools of the same name
---
duration_ms: 0.252866
type: 'test'
...
# Subtest: events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
ok 29 - events: a module emits, a listener and the audit sink (\#) both receive it, with metadata
---
duration_ms: 0.570144
type: 'test'
...
# Subtest: each registration is given the environment asked for it, and sees no other's (ADR 0192)
ok 30 - each registration is given the environment asked for it, and sees no other's (ADR 0192)
---
duration_ms: 0.165211
type: 'test'
...
# Subtest: a bundle on stdio lists its tools and answers a call, a seat's verb among them
ok 31 - a bundle on stdio lists its tools and answers a call, a seat's verb among them
---
duration_ms: 125.877461
type: 'test'
...
# Subtest: a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
ok 32 - a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)
---
duration_ms: 83.441353
type: 'test'
...
# Subtest: a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
ok 33 - a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)
---
duration_ms: 84.991932
type: 'test'
...
# Subtest: a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
ok 34 - a launched bundle subscribes as it is imported, handles events the runtime hands it, and asks through it (ADR 0198)
---
duration_ms: 3221.341964
type: 'test'
...
# Subtest: a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
ok 35 - a subscribe the runtime refused leaves no handler behind, so one event is handled once (novox/hq issue 276)
---
duration_ms: 82.868584
type: 'test'
...
# Subtest: a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
ok 36 - a launched bundle watches its state as it is imported, has the current values before it goes on, and keeps state through the runtime (ADR 0201)
---
duration_ms: 271.001279
type: 'test'
...
# Subtest: topics match as the runtime's do: * one segment, \# and ** any number
ok 37 - topics match as the runtime's do: * one segment, \# and ** any number
---
duration_ms: 2.368209
type: 'test'
...
1..37
# tests 37
# suites 0
# pass 37
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 3963.915328
NOT TESTED HERE: go/ (the Go binding) — the Go toolchain runs it from go/merge-check.sh
NOT TESTED HERE: python/, rust/, c/ — no toolchain of theirs runs a merge check
--- its go/merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-sdk/go 0.182s
ok git.novox.be/novox/mesh-sdk/go/asker 0.009s
ok git.novox.be/novox/mesh-sdk/go/asks 0.008s
ok git.novox.be/novox/mesh-sdk/go/cli 1.377s
? git.novox.be/novox/mesh-sdk/go/example [no test files]
ok git.novox.be/novox/mesh-sdk/go/health 0.009s
ok git.novox.be/novox/mesh-sdk/go/outward 0.114s
```
Re-review against review 53. go vet ./... is clean, go test -race -count=1 ./... passes, and the forge's mesh/merge-gate (4 of 4 compose) and mesh/repo-check (go and typescript) are green at this head.
I reran my probes against CheckSecret. Every shape I named in review 53 is now refused:
nats://user:pa55word@host:4222 and postgres://app:x@db/app
/proc/mdstat lines, a passwd line, ExecStart= and Include lines, docker overlay mount options and an https URL
Not blocking, for the record:
Two harmless lines are withheld: an ssh public key line (ssh-ed25519 AAAA…) and a /dev/disk/by-id/nvme-…_S6B0NL0W123456A-part1 path, whose serial number looks random. This loses some usefulness and is no risk.
Three shapes still pass, because no name or shape marks them: password "hunter2" (a quoted value after a space), mysql -pHunter2Hunter2 and Authorization: Bearer <short>. File modes and mesh-reads' name refusals are the bounds there.
One base64 line of a PEM private key still passes this rule on its own. mesh-catalog #247 now withholds the whole BEGIN…END block, which covers it.
Merge order: this PR first, then mesh-controller #257, then mesh-catalog #247 once #257's rollout says done.
Verdict: approve at 67bfa22132f264c0d844d2034e5ae605a4c5c4b9
Re-review against review 53. `go vet ./...` is clean, `go test -race -count=1 ./...` passes, and the forge's `mesh/merge-gate` (4 of 4 compose) and `mesh/repo-check` (go and typescript) are green at this head.
I reran my probes against `CheckSecret`. Every shape I named in review 53 is now refused:
- `nats://user:pa55word@host:4222` and `postgres://app:x@db/app`
- `MESH_TOKEN=s3cr3t-v4lue`, `DB_PASS=correcthorse`, `SUDO_PASSWORD hunter2`, `export GITHUB_TOKEN=abc`, `client_secret: abc`
- `aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`
Random base64 values passing, out of 2000 each, standard / url-safe: 16 bytes 1 / 1 (was 246 of 1000 for url-safe), 24 bytes 3 / 0, 32 bytes 0 / 0 (was 24 of 1000), 48 bytes 0 / 0.
Ordinary config lines still pass:
- sshd's `PasswordAuthentication no` and `ChallengeResponseAuthentication no`
- `Environment=MESH_BACKUP_PASSWORD_FILE=/var/lib/restic/repository.secret`, `token_file = /etc/app/token`, `password_file: /run/secrets/db`, `auth_token_lifetime = 3600`
- fstab `UUID=…` and `PARTUUID=…`
- `/proc/mdstat` lines, a passwd line, `ExecStart=` and `Include` lines, docker overlay mount options and an https URL
Not blocking, for the record:
- Two harmless lines are withheld: an ssh public key line (`ssh-ed25519 AAAA…`) and a `/dev/disk/by-id/nvme-…_S6B0NL0W123456A-part1` path, whose serial number looks random. This loses some usefulness and is no risk.
- Three shapes still pass, because no name or shape marks them: `password "hunter2"` (a quoted value after a space), `mysql -pHunter2Hunter2` and `Authorization: Bearer <short>`. File modes and mesh-reads' name refusals are the bounds there.
- One base64 line of a PEM private key still passes this rule on its own. mesh-catalog #247 now withholds the whole BEGIN…END block, which covers it.
Merge order: this PR first, then mesh-controller #257, then mesh-catalog #247 once #257's rollout says done.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What. A new Go package
go/outward: the content rule the operator channel applies (messenger'scontent.go), with the secret class split out as mesh-catalog #178 (issue 383) splits it:Check(text): what may leave on a channel (roles and words; no address, path or secret's shape).CheckSecret(text): a secret's shape alone; a path is judged piece by piece between its slashes.Refusal{Class, What}: never the text itself.Why. ADR 0310 §4: the operator channel and every
node-readsanswer apply one rule, kept in the SDK (ADR 0039 §1).mesh-reads(mesh-catalog, same branch name) usesCheckSecreton every line it answers.Not in this group. The messenger still has its own copy. Switching it to this package changes
modules/messenger/cmd/messenger/content.go, which mesh-catalog #178 (issue 383) also changes. I left it out so as not to cut into that work. It is a follow-up once #178 has merged, and I am asking for an issue for it.Tests.
outward_test.gocarries messenger's cases unchanged (roles and words pass; every address, path and secret class is refused as before), plusTestCheckSecretRefusesASecretsShapeAndNothingElse.sh go/merge-check.shpasses (gofmt, vet, every Go package's tests).Order. Merge this first. mesh-catalog's
mesh-readspins it asv0.1.14-0.20261012115257-4dc5f29d3e23.Part of issue 418 (with mesh-controller and mesh-catalog on
fix/418-node-reads). Not merged.Delivery
novox/mesh-sdk@67bfa22132f2— delivered since 2026-10-12T13:29:20ZDelivery plan — builds mesh-sdk, sent nowhere; no bus step
Group
fix/418-node-reads, in order: novox/mesh-controller@63a99d4323 → novox/mesh-sdk@67bfa22132Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
4dc5f29dmesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)Delivery plan — builds mesh-sdk, sent nowhere; no bus step
Run by the build seat on ace as
build-1791806709621577837(builds --log build-1791806709621577837).Verdict: request changes at
4dc5f29d3eIndependent review for issue 418 (ADR 0310 §4).
go vet ./...is clean andgo test -race ./...passes ingo/. The package is new and additive, so nothing that imports the SDK today changes.Checkkeeps the operator channel's cases.Required:
CheckSecretmisses the shapes that node-reads will meet most often. node-reads appliesCheckSecretto every line of env files, unit files, configs and history files. I fed it shapes from those files, and these pass as clean:nats://user:pa55word@host:4222. Anyscheme://user:pass@should be refused by its shape, whatever the password looks like.MESH_TOKEN=s3cr3t-v4lue,SUDO_PASSWORD hunter2. The\binreAssigneddoes not fire after_, soDB_PASS=,*_TOKEN=,*_SECRET=and*_PASSWORD=are only caught when the value happens to look random.=:aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY. The spaces break the run, and the slashes split the value into pieces shorter than 20 characters./(needed for paths) lets base64 through when the slashes fall evenly.Suggested fix:
[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@).reAssignedmatch a secret word at the end of a name with_or-before it ((?i)(^|[^a-z0-9])[a-z0-9_]*(password|passwd|pass|pwd|secret|token|api_?key|private_?key|psk|credential)s?\s*[=: ]\s*\S, tuned against the existing cases)./only when the run starts with/,~or./, or contains//, so base64 is judged whole.Follow-up, not blocking here: mesh-controller still keeps its own copy in
internal/outward("mirrored, not imported"). Once the messenger moves onto this package, the controller should import it too, so ADR 0310 §4's "one rule" holds. The PR already names the messenger move as a follow-up under issue 418.Merge order for the group: this PR first (after the fix), then mesh-controller #257, then the catalogue module.
Merge check at
67bfa221mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-sdk)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)Delivery plan — builds mesh-sdk, sent nowhere; no bus step
Run by the build seat on g14 as
build-1791808075767291231(builds --log build-1791808075767291231).Verdict: approve at
67bfa22132Re-review against review 53.
go vet ./...is clean,go test -race -count=1 ./...passes, and the forge'smesh/merge-gate(4 of 4 compose) andmesh/repo-check(go and typescript) are green at this head.I reran my probes against
CheckSecret. Every shape I named in review 53 is now refused:nats://user:pa55word@host:4222andpostgres://app:x@db/appMESH_TOKEN=s3cr3t-v4lue,DB_PASS=correcthorse,SUDO_PASSWORD hunter2,export GITHUB_TOKEN=abc,client_secret: abcaws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYRandom base64 values passing, out of 2000 each, standard / url-safe: 16 bytes 1 / 1 (was 246 of 1000 for url-safe), 24 bytes 3 / 0, 32 bytes 0 / 0 (was 24 of 1000), 48 bytes 0 / 0.
Ordinary config lines still pass:
PasswordAuthentication noandChallengeResponseAuthentication noEnvironment=MESH_BACKUP_PASSWORD_FILE=/var/lib/restic/repository.secret,token_file = /etc/app/token,password_file: /run/secrets/db,auth_token_lifetime = 3600UUID=…andPARTUUID=…/proc/mdstatlines, a passwd line,ExecStart=andIncludelines, docker overlay mount options and an https URLNot blocking, for the record:
ssh-ed25519 AAAA…) and a/dev/disk/by-id/nvme-…_S6B0NL0W123456A-part1path, whose serial number looks random. This loses some usefulness and is no risk.password "hunter2"(a quoted value after a space),mysql -pHunter2Hunter2andAuthorization: Bearer <short>. File modes and mesh-reads' name refusals are the bounds there.Merge order: this PR first, then mesh-controller #257, then mesh-catalog #247 once #257's rollout says done.