Fixes issue 164. Decision: novox/hq ADR 0337 (accepted 2026-10-13 on the operator's Approve).
What changes
Parser (internal/catalogue): an offer may say "credential": {"issued-by": "outside"} and must then name no own secret and no tool; any issuer other than software or outside is refused (ADR 0337 §1, widening ADR 0305 §1). Manifest.IssuedOutsideOf, OffersIssuedOutside.
Inventory: SecretFor never makes a value for such a provision. Its one mint path asks the catalogue and answers NotGivenError instead (§2). GivenPairSecret reads such a pair as given only when its row is accepted and sealed to the current keys of both ends. It never writes; a value the mesh made before the rule, or one sealed under an old key, is not given (§2, §8). remakeShared refuses, and SharedSecretFor answers not given, for a provider's own secret issued outside (ADR 0228). RotateSecret refuses such a provision, naming the accept line (§9). AcceptSecretForProvision seals one value to every consumer named, to the provider and to the operator, each row accepted under one generation, and says which consumers have no sealing key (§3). GivenPairsOf lists the provisions a consumer holds a given value for.
Plan: a need whose provider's offer says outside is read with GivenPairSecret. While it is not given, the consumer receives no credential file and its machine is not refused (§4). The plan reads a NotGivenError from SharedSecretFor the same way.
secret accept <provider's node> <provider module> <provision> gives such a credential once for its provider. It resolves every machine to find each consumer that binds the provision from that provider, seals the value to them all, and sends the holding machines (§3, issue 165). The per-pair form (--provider) is refused for such a provision, naming this line. The value is read from the terminal or --from, never through a verb, and is never printed.
Waits: link.Wait and inventory.Wait carry provision, so a wait names exactly one secret, one setting or one provision (§5). The controller excuses a provision wait only when the module requires the provision, a provider offers it issued outside, and no given value is held for the pair under the current keys (§6). The consumer's needs-operator condition says "give ", and its summary names the one secret accept line for the provider (§7).
Not in this PR (follow-up issues)
mesh-sdk go/health and the node-engine must carry provision in a wait end to end.
The catalogue's sonarr, radarr, lidarr, jackett, nzbget and qbittorrent manifests must declare issued-by: outside, and their consumers' probes must go (§11, issue 167). Until they declare it, nothing changes on any machine.
Tests (fail on main: the new identifiers do not exist there; the replay fails behaviourally)
internal/catalogue/issued_outside_test.go: the offer is read, and every bad shape is refused.
internal/inventory/outside_test.go (store tests, listed in testdata/store-tests):
nothing is made for a pair with no row or after a key change;
a made row from before is not given, and rotate is refused naming the accept;
one accept is sealed to every consumer, the provider and the operator under one generation, and a keyless consumer is said;
a shared own secret issued outside is never made.
cmd/mesh-controller/outside_wait_test.go: the wait is excused only under §6; the condition is in plain words and names the accept line; one accept reaches the consumers its provider answers.
cmd/mesh-controller/outside_accept_test.go (store test): a made value is placed before the rule and not after; the per-pair form is refused naming the provider's form; one accept is placed for both consumers.
cmd/mesh-controller/replay164_test.go, TestReplay164: the replay, written with only pre-fix code. go run ./cmd/prove R164 in mesh-lab says "PROVED: fails before its fix, passes on its fix" (register entry: mesh-lab fix/164-replay).
Checks run locally
A full merge-check.sh against a throwaway store passed everything except two failures:
the empty-on-error lint, now fixed;
TestATierPutsABrokenModuleBackAtOnce, which failed under a load of 25 or more and passes 3 of 3 alone.
After the lint fix: gofmt, vet, lint, catalogue, and the targeted inventory and cmd tests pass (niced, no race run, because g14 is overloaded).
Do not merge without an independent review.
Fixes issue 164. Decision: novox/hq ADR 0337 (accepted 2026-10-13 on the operator's Approve).
**What changes**
- Parser (`internal/catalogue`): an offer may say `"credential": {"issued-by": "outside"}` and must then name no own secret and no tool; any issuer other than `software` or `outside` is refused (ADR 0337 §1, widening ADR 0305 §1). `Manifest.IssuedOutsideOf`, `OffersIssuedOutside`.
- Inventory: `SecretFor` never makes a value for such a provision. Its one mint path asks the catalogue and answers `NotGivenError` instead (§2). `GivenPairSecret` reads such a pair as given only when its row is `accepted` and sealed to the current keys of both ends. It never writes; a value the mesh made before the rule, or one sealed under an old key, is not given (§2, §8). `remakeShared` refuses, and `SharedSecretFor` answers not given, for a provider's own secret issued outside (ADR 0228). `RotateSecret` refuses such a provision, naming the accept line (§9). `AcceptSecretForProvision` seals one value to every consumer named, to the provider and to the operator, each row `accepted` under one generation, and says which consumers have no sealing key (§3). `GivenPairsOf` lists the provisions a consumer holds a given value for.
- Plan: a need whose provider's offer says `outside` is read with `GivenPairSecret`. While it is not given, the consumer receives no credential file and its machine is **not** refused (§4). The plan reads a `NotGivenError` from `SharedSecretFor` the same way.
- `secret accept <provider's node> <provider module> <provision>` gives such a credential once for its provider. It resolves every machine to find each consumer that binds the provision from that provider, seals the value to them all, and sends the holding machines (§3, issue 165). The per-pair form (`--provider`) is refused for such a provision, naming this line. The value is read from the terminal or `--from`, never through a verb, and is never printed.
- Waits: `link.Wait` and `inventory.Wait` carry `provision`, so a wait names exactly one secret, one setting or one provision (§5). The controller excuses a provision wait only when the module requires the provision, a provider offers it issued outside, and no given value is held for the pair under the current keys (§6). The consumer's `needs-operator` condition says "give <what>", and its summary names the one `secret accept` line for the provider (§7).
**Not in this PR (follow-up issues)**
- mesh-sdk `go/health` and the node-engine must carry `provision` in a wait end to end.
- The catalogue's sonarr, radarr, lidarr, jackett, nzbget and qbittorrent manifests must declare `issued-by: outside`, and their consumers' probes must go (§11, issue 167). Until they declare it, nothing changes on any machine.
**Tests (fail on main: the new identifiers do not exist there; the replay fails behaviourally)**
- `internal/catalogue/issued_outside_test.go`: the offer is read, and every bad shape is refused.
- `internal/inventory/outside_test.go` (store tests, listed in `testdata/store-tests`):
- nothing is made for a pair with no row or after a key change;
- a made row from before is not given, and rotate is refused naming the accept;
- one accept is sealed to every consumer, the provider and the operator under one generation, and a keyless consumer is said;
- a shared own secret issued outside is never made.
- `cmd/mesh-controller/outside_wait_test.go`: the wait is excused only under §6; the condition is in plain words and names the accept line; one accept reaches the consumers its provider answers.
- `cmd/mesh-controller/outside_accept_test.go` (store test): a made value is placed before the rule and not after; the per-pair form is refused naming the provider's form; one accept is placed for both consumers.
- `cmd/mesh-controller/replay164_test.go`, **TestReplay164**: the replay, written with only pre-fix code. `go run ./cmd/prove R164` in mesh-lab says "PROVED: fails before its fix, passes on its fix" (register entry: mesh-lab fix/164-replay).
**Checks run locally**
- A full `merge-check.sh` against a throwaway store passed everything except two failures:
- the empty-on-error lint, now fixed;
- `TestATierPutsABrokenModuleBackAtOnce`, which failed under a load of 25 or more and passes 3 of 3 alone.
- After the lint fix: gofmt, vet, lint, catalogue, and the targeted inventory and cmd tests pass (niced, no race run, because g14 is overloaded).
Do not merge without an independent review.
Deliverynovox/mesh-controller@c9dd07f919a2 — delivered since 2026-10-13T09:58:59Z
Delivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
tier 0: mesh-controller
tier 1: build-agent
ace: receives build-agent
g14: receives build-agent
novox: receives mesh-controller, build-agent
shanks: receives build-agent
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
Machines
mesh-controller on novox: passed — healthy 3 times over 3m37s
mesh-controller on the rest running it: sent
build-agent on ace: passed — healthy 1 times over 21s
build-agent on the rest running it: sent
Transitions
2026-10-13 09:38 (new) → proposed (announced): novox/mesh-controller#298's head announced
2026-10-13 09:48 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-13 09:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-13 09:52 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-13 09:52 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-13 09:58 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-controller@c9dd07f919a2` — **delivered** since 2026-10-13T09:58:59Z
**Delivery plan** — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- ace: receives build-agent
- g14: receives build-agent
- novox: receives mesh-controller, build-agent
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
**Machines**
- mesh-controller on novox: passed — healthy 3 times over 3m37s
- mesh-controller on the rest running it: sent
- build-agent on ace: passed — healthy 1 times over 21s
- build-agent on the rest running it: sent
**Transitions**
- 2026-10-13 09:38 (new) → proposed (announced): novox/mesh-controller#298's head announced
- 2026-10-13 09:48 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-13 09:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-13 09:52 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-13 09:52 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-13 09:58 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`.
Merge check at cfe63a47
Judged: head cfe63a4738ae merged into trunk 1826b68a22b5; read beside it: mesh-catalog f35b06750452, mesh-controller-main 1826b68a22b5, mesh-host 7a41e120635b, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller)
mesh/repo-check: PASS — its merge-check.sh passed
Delivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
tier 0: mesh-controller
tier 1: build-agent
ace: receives build-agent
g14: receives build-agent
novox: receives mesh-controller, build-agent
shanks: receives build-agent
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
read by no module's build: cmd/mesh-controller/operator_wait_test.go, cmd/mesh-controller/outside_accept_test.go, cmd/mesh-controller/outside_wait_test.go, cmd/mesh-controller/replay164_test.go, internal/catalogue/issued_credential_test.go, internal/catalogue/issued_outside_test.go, internal/inventory/outside_test.go, testdata/store-tests
Run by the build seat on ace as build-1791882924477134278 (builds --log build-1791882924477134278).
--- the gate: build-agent, mesh-controller
mesh-controller: ok, 58 tool(s); WARNING: merge-window ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T09:09:10Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T09:09:10Z
modules the change moves:
- mesh-controller (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 2 module(s) in 2 tier(s): build-agent, mesh-controller
notes:
- cmd/mesh-controller/operator_wait_test.go, cmd/mesh-controller/outside_accept_test.go, cmd/mesh-controller/outside_wait_test.go, cmd/mesh-controller/replay164_test.go, internal/catalogue/issued_credential_test.go, internal/catalogue/issued_outside_test.go, internal/inventory/outside_test.go and testdata/store-tests read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.042s
ok github.com/novox/mesh-controller/cmd/mesh-controller 156.093s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.037s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.049s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.039s
ok github.com/novox/mesh-controller/examples/route-proxy 1.138s
ok github.com/novox/mesh-controller/internal/artifacts 1.080s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.034s
ok github.com/novox/mesh-controller/internal/builder 38.235s
ok github.com/novox/mesh-controller/internal/catalogue 7.907s
ok github.com/novox/mesh-controller/internal/conditions 2.569s
ok github.com/novox/mesh-controller/internal/envfile 1.042s
ok github.com/novox/mesh-controller/internal/facts 1.048s
ok github.com/novox/mesh-controller/internal/identity 4.324s
ok github.com/novox/mesh-controller/internal/inventory 32.568s
ok github.com/novox/mesh-controller/internal/lease 17.709s
ok github.com/novox/mesh-controller/internal/licences 3.704s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.043s
ok github.com/novox/mesh-controller/internal/link 58.285s
ok github.com/novox/mesh-controller/internal/lint 2.274s
ok github.com/novox/mesh-controller/internal/outward 1.070s
ok github.com/novox/mesh-controller/internal/overlay 1.061s
ok github.com/novox/mesh-controller/internal/secrets 1.429s
ok github.com/novox/mesh-controller/internal/store 3.303s
ok github.com/novox/mesh-controller/internal/testbus 1.221s
ok github.com/novox/mesh-controller/internal/token 1.019s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.20s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.21s)
./cmd/mesh-controller --- PASS: TestAStopWalksTheOthersAgainInTheOpenBatch (0.20s)
./cmd/mesh-controller --- PASS: TestAStopNamesWhatStaysOnTheMachinesAndWhatStillCarriesIt (0.19s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeLeavesItOutOfABatch (0.18s)
./cmd/mesh-controller --- PASS: TestSavingPlansAndMovingMergesIsOneAct (0.15s)
./cmd/mesh-controller --- PASS: TestAPlanFollowedFromTheStoreAnswersWhenItMoves (1.95s)
./cmd/mesh-controller --- PASS: TestNodeShowAndStatusReadTheLastSendFromTheStore (0.22s)
./cmd/mesh-controller --- PASS: TestTheConditionsVerbReadsTheLooksTheBucketNoLongerHolds (0.13s)
./cmd/mesh-controller --- PASS: TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded (0.23s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped (0.22s)
./cmd/mesh-controller --- PASS: TestAWalkRetriedByAPersonIsSaidStartedAgain (0.19s)
./cmd/mesh-controller --- PASS: TestABacklogReleasedByAPersonIsSaidStarted (0.19s)
./cmd/mesh-controller --- PASS: TestAWalkEndedByCancelOrClearIsSaidEnded (0.24s)
./cmd/mesh-controller --- PASS: TestAWalkRestartedByARebuildIsSaidStarted (0.19s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.23s)
./cmd/mesh-controller --- PASS: TestReplay237 (0.19s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer (0.18s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt (0.19s)
./cmd/mesh-controller --- PASS: TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal (0.18s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.21s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.26s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.25s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.14s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.13s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.14s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.26s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.21s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.26s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.23s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.18s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.18s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.17s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.14s)
./cmd/mesh-controller --- PASS: TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns (0.17s)
./cmd/mesh-controller --- PASS: TestOneAcceptForAProviderIsPlacedForEveryConsumer (0.43s)
./cmd/mesh-controller --- PASS: TestReplay164 (0.25s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.07s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.07s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.09s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.08s)
./internal/inventory --- PASS: TestAConditionsLooksAreKeptBesideTheBucket (0.06s)
./internal/inventory --- PASS: TestAPlanIdHoldingAQuoteIsReadAsAPlainId (0.06s)
./internal/inventory --- PASS: TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid (0.06s)
./internal/inventory --- PASS: TestAWalksRestIsReadFromEachMachinesOwnSend (0.07s)
./internal/inventory --- PASS: TestAStoredBuildWithASilentMergeableFileIsStillReadAndComposed (0.06s)
./internal/inventory --- PASS: TestACredentialIssuedOutsideIsNeverMade (0.07s)
./internal/inventory --- PASS: TestOneAcceptForAProviderIsSealedToEveryConsumer (0.08s)
./internal/inventory --- PASS: TestASharedOwnSecretIssuedOutsideIsNeverMade (0.07s)
**Merge check** at `cfe63a47`
Judged: head `cfe63a4738ae` merged into trunk `1826b68a22b5`; read beside it: mesh-catalog `f35b06750452`, mesh-controller-main `1826b68a22b5`, mesh-host `7a41e120635b`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- ace: receives build-agent
- g14: receives build-agent
- novox: receives mesh-controller, build-agent
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
- read by no module's build: cmd/mesh-controller/operator_wait_test.go, cmd/mesh-controller/outside_accept_test.go, cmd/mesh-controller/outside_wait_test.go, cmd/mesh-controller/replay164_test.go, internal/catalogue/issued_credential_test.go, internal/catalogue/issued_outside_test.go, internal/inventory/outside_test.go, testdata/store-tests
Run by the build seat on ace as `build-1791882924477134278` (`builds --log build-1791882924477134278`).
```
--- the gate: build-agent, mesh-controller
mesh-controller: ok, 58 tool(s); WARNING: merge-window ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T09:09:10Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T09:09:10Z
modules the change moves:
- mesh-controller (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 2 module(s) in 2 tier(s): build-agent, mesh-controller
notes:
- cmd/mesh-controller/operator_wait_test.go, cmd/mesh-controller/outside_accept_test.go, cmd/mesh-controller/outside_wait_test.go, cmd/mesh-controller/replay164_test.go, internal/catalogue/issued_credential_test.go, internal/catalogue/issued_outside_test.go, internal/inventory/outside_test.go and testdata/store-tests read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.042s
ok github.com/novox/mesh-controller/cmd/mesh-controller 156.093s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.037s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.049s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.039s
ok github.com/novox/mesh-controller/examples/route-proxy 1.138s
ok github.com/novox/mesh-controller/internal/artifacts 1.080s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.034s
ok github.com/novox/mesh-controller/internal/builder 38.235s
ok github.com/novox/mesh-controller/internal/catalogue 7.907s
ok github.com/novox/mesh-controller/internal/conditions 2.569s
ok github.com/novox/mesh-controller/internal/envfile 1.042s
ok github.com/novox/mesh-controller/internal/facts 1.048s
ok github.com/novox/mesh-controller/internal/identity 4.324s
ok github.com/novox/mesh-controller/internal/inventory 32.568s
ok github.com/novox/mesh-controller/internal/lease 17.709s
ok github.com/novox/mesh-controller/internal/licences 3.704s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.043s
ok github.com/novox/mesh-controller/internal/link 58.285s
ok github.com/novox/mesh-controller/internal/lint 2.274s
ok github.com/novox/mesh-controller/internal/outward 1.070s
ok github.com/novox/mesh-controller/internal/overlay 1.061s
ok github.com/novox/mesh-controller/internal/secrets 1.429s
ok github.com/novox/mesh-controller/internal/store 3.303s
ok github.com/novox/mesh-controller/internal/testbus 1.221s
ok github.com/novox/mesh-controller/internal/token 1.019s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.20s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.21s)
./cmd/mesh-controller --- PASS: TestAStopWalksTheOthersAgainInTheOpenBatch (0.20s)
./cmd/mesh-controller --- PASS: TestAStopNamesWhatStaysOnTheMachinesAndWhatStillCarriesIt (0.19s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeLeavesItOutOfABatch (0.18s)
./cmd/mesh-controller --- PASS: TestSavingPlansAndMovingMergesIsOneAct (0.15s)
./cmd/mesh-controller --- PASS: TestAPlanFollowedFromTheStoreAnswersWhenItMoves (1.95s)
./cmd/mesh-controller --- PASS: TestNodeShowAndStatusReadTheLastSendFromTheStore (0.22s)
./cmd/mesh-controller --- PASS: TestTheConditionsVerbReadsTheLooksTheBucketNoLongerHolds (0.13s)
./cmd/mesh-controller --- PASS: TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded (0.23s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped (0.22s)
./cmd/mesh-controller --- PASS: TestAWalkRetriedByAPersonIsSaidStartedAgain (0.19s)
./cmd/mesh-controller --- PASS: TestABacklogReleasedByAPersonIsSaidStarted (0.19s)
./cmd/mesh-controller --- PASS: TestAWalkEndedByCancelOrClearIsSaidEnded (0.24s)
./cmd/mesh-controller --- PASS: TestAWalkRestartedByARebuildIsSaidStarted (0.19s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.23s)
./cmd/mesh-controller --- PASS: TestReplay237 (0.19s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer (0.18s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt (0.19s)
./cmd/mesh-controller --- PASS: TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal (0.18s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.21s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.26s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.25s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.14s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.13s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.14s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.26s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.21s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.26s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.23s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.18s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.18s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.17s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.14s)
./cmd/mesh-controller --- PASS: TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns (0.17s)
./cmd/mesh-controller --- PASS: TestOneAcceptForAProviderIsPlacedForEveryConsumer (0.43s)
./cmd/mesh-controller --- PASS: TestReplay164 (0.25s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.07s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.07s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.09s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.08s)
./internal/inventory --- PASS: TestAConditionsLooksAreKeptBesideTheBucket (0.06s)
./internal/inventory --- PASS: TestAPlanIdHoldingAQuoteIsReadAsAPlainId (0.06s)
./internal/inventory --- PASS: TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid (0.06s)
./internal/inventory --- PASS: TestAWalksRestIsReadFromEachMachinesOwnSend (0.07s)
./internal/inventory --- PASS: TestAStoredBuildWithASilentMergeableFileIsStillReadAndComposed (0.06s)
./internal/inventory --- PASS: TestACredentialIssuedOutsideIsNeverMade (0.07s)
./internal/inventory --- PASS: TestOneAcceptForAProviderIsSealedToEveryConsumer (0.08s)
./internal/inventory --- PASS: TestASharedOwnSecretIssuedOutsideIsNeverMade (0.07s)
```
Independent review for issue 164, against ADR 0337 (accepted, version 8). I read the whole diff (18 files), the callers of every changed function, and the media catalogue's current manifests of sonarr, qbittorrent and nzbget.
WHAT I CHECKED, AND WHAT I SAW
Nothing is minted for issued-by outside. SecretFor's one mint path now asks the catalogue first and answers NotGivenError for such a provision, after the held-row read and before MakeWithOperator, so no caller of SecretFor can have a value made. The plan does not call SecretFor for such a need at all: issuedOutsideBy picks GivenPairSecret by the need's own provider module, and GivenPairSecret only reads, never writes. remakeShared refuses a provider's own secret that says issued-by outside, and SharedSecretFor answers NotGivenError for it before it would call remakeShared. RotateSecret refuses such a provision and names the accept line. The store tests TestACredentialIssuedOutsideIsNeverMade and TestASharedOwnSecretIssuedOutsideIsNeverMade show no row and no value for a pair with no row, for a newly bound consumer and after a key change.
A software-issued offer is unchanged (ADR 0305 §7). The parser's software branch is the same code under a new else-if; the plan's SharedOwn and SecretFor branches are untouched for offers that do not say outside. The ADR 0305 tests pass. The one edit to them (internal/catalogue/issued_credential_test.go) changes only the expected words of the refusal of a foreign issuer, from "the one issuer an offer may name" to "the issuers an offer may name"; ADR 0337 §12 asks for those tests unchanged, so this is a small deviation in letter, not in what is checked.
A stored row from before the change is safe. A made row sealed to the current keys is answered by GivenPairSecret as not given ("the value held was made by the mesh"), and the plan places no file for it; the store test covers this. A rollback to main is safe: there is no migration, the rows AcceptSecretForProvision writes use the existing columns with origin accepted and a generation, which main's SecretFor reads as held, and the stored catalogue is read with json.Unmarshal rather than the parser, so main still reads a manifest that says issued-by outside. Main would then mint again for such an offer, which is the pre-fix behaviour, not a failure.
The waiting consumer gets the needs-operator condition naming the accept. checkWait now requires exactly one of secret, setting or provision; checkProvisionWait excuses a provision wait only under §6's three conditions; checkWaiting fills Act with "secret accept ", and the condition's words say "give " with the line in the summary. TestAWaitForAProvisionIsExcusedOnlyWhenTheStoreBearsItOut and TestAWaitForAProvisionNamesTheOneAcceptForItsProvider cover it.
The tests fail on main, and the replay uses only pre-fix code. On main 1826b68 the new catalogue, inventory and cmd tests do not compile (IssuedOutsideOf, OffersIssuedOutside, IsNotGiven, Wait.Provision and consumersFrom do not exist there). TestReplay164, laid over main alone, fails on behaviour: "the send placed a credential for tv-api (requests.secret-tv-api), which only a person can give and nobody gave" and "the mesh holds a value for tv-api that nobody gave (origin made)". It uses register, assign, planFor, declarationFor, AcceptSecretForPair and SecretsOf, all on main. mesh-lab #102's go run ./cmd/prove R164 says PROVED.
Runs, niced on the laptop against a throwaway store: with -race, the new and neighbouring tests of internal/catalogue, internal/inventory and cmd/mesh-controller (Outside, OneAcceptForAProvider, Replay164, Wait, Secret, Rotate, Issued, Replay401, Replay201) all pass. Without -race, the whole of internal/inventory, internal/catalogue and internal/link pass. go vet is clean and gofmt lists no changed file. The whole of cmd/mesh-controller also passes without -race (328 s).
FINDINGS, NONE BLOCKING
A. The provider half still carries a pre-rule made value. grantsFor reads SecretsFrom, which returns every pair row with this provider, and puts its for_provider value on the grant as Sealed, without asking whether the offer says outside or whether the row was made. So a row the mesh made before this change is still sent to the provider's machine. ADR 0337 §2 says such a value "is never delivered again". Nothing is affected today, because no manifest says outside yet, but it should be closed before issue 618 makes the six offers say outside: leave made rows of an outside offer out of SecretsFrom (or out of the grant's Sealed), with a store test.
B. Four places judge "issued outside" by the provision's name across the whole catalogue, not by the provider the pair binds: SecretFor and RotateSecret (through issuedOutside), and readWaitFacts and checkProvisionWait (through OffersIssuedOutside); GivenPairsOf also ignores which provider a given row came from. The plan itself uses the bound provider. Today every provision name in both catalogues is offered by one module (the only shared name is route), so nothing changes, but if two modules ever offered one name with different issuers, the mesh would stop making the credential of the one that is not outside. A follow-up should pass the provider module through.
C. When the provider module is assigned to more than one machine, the accept line names "" instead of the machine the consumer binds. ADR 0337 §7 wants the one line for the provider; the consumer's bound provider is known from its resolution. Minor.
D. A design point for issue 618, not for this pull request. The current manifests of sonarr, qbittorrent and nzbget already offer their credential as their own secret (ADR 0158: "credential": {"own": "api-key"} or {"own": "password"}), and each writes the mesh's value into the app before it starts (sonarr's api-key-init writes ApiKey into config.xml; qbittorrent's webui-login-init writes the WebUI password hash; nzbget reads FILE__NZBGET_PASS). So for these providers the mesh's value does work today, and ADR 0337 §11, which moves them to issued-by outside, would replace a working credential with one accept per provider. Issue 618 should be judged against the manifests as they are before anyone builds it.
STATUSES AT THIS HEAD (read 11:36)
mesh/merge-gate: "pending — out of date: judged against trunk 1826b68a22, the trunk is now 404b4409e70e; judged again on a recheck or when it is merged". mesh/repo-check: the same words. mesh/delivery: "pending — checking". Neither required check has a verdict on this head against the current main, and I ran the tests on the base 1826b68, not on 404b4409. The approval is of the code; ask mesh-delivery.recheck and merge only once both required checks are green and read.
Verdict: approve at cfe63a4738ae57b0fe82518c50eeb541f990c157
Independent review for issue 164, against ADR 0337 (accepted, version 8). I read the whole diff (18 files), the callers of every changed function, and the media catalogue's current manifests of sonarr, qbittorrent and nzbget.
WHAT I CHECKED, AND WHAT I SAW
1. Nothing is minted for issued-by outside. SecretFor's one mint path now asks the catalogue first and answers NotGivenError for such a provision, after the held-row read and before MakeWithOperator, so no caller of SecretFor can have a value made. The plan does not call SecretFor for such a need at all: issuedOutsideBy picks GivenPairSecret by the need's own provider module, and GivenPairSecret only reads, never writes. remakeShared refuses a provider's own secret that says issued-by outside, and SharedSecretFor answers NotGivenError for it before it would call remakeShared. RotateSecret refuses such a provision and names the accept line. The store tests TestACredentialIssuedOutsideIsNeverMade and TestASharedOwnSecretIssuedOutsideIsNeverMade show no row and no value for a pair with no row, for a newly bound consumer and after a key change.
2. A software-issued offer is unchanged (ADR 0305 §7). The parser's software branch is the same code under a new else-if; the plan's SharedOwn and SecretFor branches are untouched for offers that do not say outside. The ADR 0305 tests pass. The one edit to them (internal/catalogue/issued_credential_test.go) changes only the expected words of the refusal of a foreign issuer, from "the one issuer an offer may name" to "the issuers an offer may name"; ADR 0337 §12 asks for those tests unchanged, so this is a small deviation in letter, not in what is checked.
3. A stored row from before the change is safe. A made row sealed to the current keys is answered by GivenPairSecret as not given ("the value held was made by the mesh"), and the plan places no file for it; the store test covers this. A rollback to main is safe: there is no migration, the rows AcceptSecretForProvision writes use the existing columns with origin accepted and a generation, which main's SecretFor reads as held, and the stored catalogue is read with json.Unmarshal rather than the parser, so main still reads a manifest that says issued-by outside. Main would then mint again for such an offer, which is the pre-fix behaviour, not a failure.
4. The waiting consumer gets the needs-operator condition naming the accept. checkWait now requires exactly one of secret, setting or provision; checkProvisionWait excuses a provision wait only under §6's three conditions; checkWaiting fills Act with "secret accept <machine> <module> <provision>", and the condition's words say "give <what>" with the line in the summary. TestAWaitForAProvisionIsExcusedOnlyWhenTheStoreBearsItOut and TestAWaitForAProvisionNamesTheOneAcceptForItsProvider cover it.
5. The tests fail on main, and the replay uses only pre-fix code. On main 1826b68 the new catalogue, inventory and cmd tests do not compile (IssuedOutsideOf, OffersIssuedOutside, IsNotGiven, Wait.Provision and consumersFrom do not exist there). TestReplay164, laid over main alone, fails on behaviour: "the send placed a credential for tv-api (requests.secret-tv-api), which only a person can give and nobody gave" and "the mesh holds a value for tv-api that nobody gave (origin made)". It uses register, assign, planFor, declarationFor, AcceptSecretForPair and SecretsOf, all on main. mesh-lab #102's `go run ./cmd/prove R164` says PROVED.
Runs, niced on the laptop against a throwaway store: with -race, the new and neighbouring tests of internal/catalogue, internal/inventory and cmd/mesh-controller (Outside, OneAcceptForAProvider, Replay164, Wait, Secret, Rotate, Issued, Replay401, Replay201) all pass. Without -race, the whole of internal/inventory, internal/catalogue and internal/link pass. go vet is clean and gofmt lists no changed file. The whole of cmd/mesh-controller also passes without -race (328 s).
FINDINGS, NONE BLOCKING
A. The provider half still carries a pre-rule made value. grantsFor reads SecretsFrom, which returns every pair row with this provider, and puts its for_provider value on the grant as Sealed, without asking whether the offer says outside or whether the row was made. So a row the mesh made before this change is still sent to the provider's machine. ADR 0337 §2 says such a value "is never delivered again". Nothing is affected today, because no manifest says outside yet, but it should be closed before issue 618 makes the six offers say outside: leave made rows of an outside offer out of SecretsFrom (or out of the grant's Sealed), with a store test.
B. Four places judge "issued outside" by the provision's name across the whole catalogue, not by the provider the pair binds: SecretFor and RotateSecret (through issuedOutside), and readWaitFacts and checkProvisionWait (through OffersIssuedOutside); GivenPairsOf also ignores which provider a given row came from. The plan itself uses the bound provider. Today every provision name in both catalogues is offered by one module (the only shared name is route), so nothing changes, but if two modules ever offered one name with different issuers, the mesh would stop making the credential of the one that is not outside. A follow-up should pass the provider module through.
C. When the provider module is assigned to more than one machine, the accept line names "<its machine>" instead of the machine the consumer binds. ADR 0337 §7 wants the one line for the provider; the consumer's bound provider is known from its resolution. Minor.
D. A design point for issue 618, not for this pull request. The current manifests of sonarr, qbittorrent and nzbget already offer their credential as their own secret (ADR 0158: "credential": {"own": "api-key"} or {"own": "password"}), and each writes the mesh's value into the app before it starts (sonarr's api-key-init writes ApiKey into config.xml; qbittorrent's webui-login-init writes the WebUI password hash; nzbget reads FILE__NZBGET_PASS). So for these providers the mesh's value does work today, and ADR 0337 §11, which moves them to issued-by outside, would replace a working credential with one accept per provider. Issue 618 should be judged against the manifests as they are before anyone builds it.
STATUSES AT THIS HEAD (read 11:36)
mesh/merge-gate: "pending — out of date: judged against trunk 1826b68a22b5, the trunk is now 404b4409e70e; judged again on a recheck or when it is merged". mesh/repo-check: the same words. mesh/delivery: "pending — checking". Neither required check has a verdict on this head against the current main, and I ran the tests on the base 1826b68, not on 404b4409. The approval is of the code; ask mesh-delivery.recheck and merge only once both required checks are green and read.
Merge check at cfe63a47
Judged: head cfe63a4738ae merged into trunk 404b4409e70e
mesh/merge-gate: FAIL — head cfe63a4738 does not merge cleanly into trunk 404b4409e7: in conflict testdata/store-tests (modules: build-agent, mesh-controller)
mesh/repo-check: FAIL — head cfe63a4738 does not merge cleanly into trunk 404b4409e7: in conflict testdata/store-tests
Delivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
tier 0: mesh-controller
tier 1: build-agent
ace: receives build-agent
g14: receives build-agent
novox: receives mesh-controller, build-agent
shanks: receives build-agent
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
Run by the build seat on shanks as build-1791884212717223933 (builds --log build-1791884212717223933).
head cfe63a4738ae does not merge cleanly into trunk 404b4409e70e: in conflict testdata/store-tests
**Merge check** at `cfe63a47`
Judged: head `cfe63a4738ae` merged into trunk `404b4409e70e`
- `mesh/merge-gate`: **FAIL** — head cfe63a4738ae does not merge cleanly into trunk 404b4409e70e: in conflict testdata/store-tests (modules: build-agent, mesh-controller)
- `mesh/repo-check`: **FAIL** — head cfe63a4738ae does not merge cleanly into trunk 404b4409e70e: in conflict testdata/store-tests
**Delivery plan** — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- ace: receives build-agent
- g14: receives build-agent
- novox: receives mesh-controller, build-agent
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
Run by the build seat on shanks as `build-1791884212717223933` (`builds --log build-1791884212717223933`).
```
head cfe63a4738ae does not merge cleanly into trunk 404b4409e70e: in conflict testdata/store-tests
```
mesh/merge-gate head c9dd07f919a2 merged into trunk 404b4409e70e — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
A Servarr app's API key, an indexer's key and a downloader's password are values the provider's
software holds; the pair path made a random value for every consumer of them, sealed it to both ends
and reported success, and the provider refused it. An offer may now say `"credential": {"issued-by":
"outside"}`: SecretFor never makes a value for it, the plan reads such a pair as given or not given
and sends the consumer no credential file until a person gives it, without refusing its machine;
remakeShared and SharedSecretFor never make a provider's own secret issued outside either.
`secret accept <provider's node> <provider module> <provision>` seals one value to every consumer that
binds the provision from that provider, the provider and the operator, under one generation; the
per-pair form is refused for such a provision, and rotate names the accept. A consumer may wait for a
provision's credential: the controller excuses the wait only when the store bears it out, and its
needs-operator condition names the one accept line.
Adds the replay of issue 164 (TestReplay164), written with only what the controller had before.
This re-reviews the rebase of the head I approved in review 187 (cfe63a4738) onto main 404b4409e7.
The rebase changes only testdata/store-tests. git range-diff 1826b68a22b5..cfe63a47 404b4409e70e..c9dd07f9 shows one commit, and its only difference is the context of testdata/store-tests. The file at the new head keeps #296's line "./cmd/mesh-controller TestReplay614ANewSDKPlansTheToolchainAndThenItsBundles" and is followed by this pull request's five lines unchanged: the three internal/inventory store tests, TestOneAcceptForAProviderIsPlacedForEveryConsumer and TestReplay164. Leaving that file aside, the added and removed lines of the pull request's diff are identical before and after the rebase (18 files, 1207 insertions, 26 deletions). The new head's parent is 404b4409e7.
I ran these niced on the laptop against a throwaway store. go vet of internal/inventory, internal/catalogue and cmd/mesh-controller is clean. go test ./internal/inventory passes in full (25 s). With -race, the three new inventory store tests pass. TestReplay164, TestOneAcceptForAProviderIsPlacedForEveryConsumer and the provision-wait tests in cmd/mesh-controller pass on the new base. The findings A to D of review 187 still stand, and none of them blocks the merge.
The statuses at this head, read at 11:40, are: mesh/merge-gate "pending — building on shanks since 11:38" (after "waiting in the build queue since 11:38") and mesh/delivery "pending — checking". No mesh/repo-check status is posted yet. Merge only once both required checks are green and read.
Verdict: approve at c9dd07f919a27868dba0c77b4d97467c92b82610
This re-reviews the rebase of the head I approved in review 187 (cfe63a4738ae57b0fe82518c50eeb541f990c157) onto main 404b4409e70e.
The rebase changes only testdata/store-tests. `git range-diff 1826b68a22b5..cfe63a47 404b4409e70e..c9dd07f9` shows one commit, and its only difference is the context of testdata/store-tests. The file at the new head keeps #296's line "./cmd/mesh-controller TestReplay614ANewSDKPlansTheToolchainAndThenItsBundles" and is followed by this pull request's five lines unchanged: the three internal/inventory store tests, TestOneAcceptForAProviderIsPlacedForEveryConsumer and TestReplay164. Leaving that file aside, the added and removed lines of the pull request's diff are identical before and after the rebase (18 files, 1207 insertions, 26 deletions). The new head's parent is 404b4409e70e.
I ran these niced on the laptop against a throwaway store. `go vet` of internal/inventory, internal/catalogue and cmd/mesh-controller is clean. `go test ./internal/inventory` passes in full (25 s). With -race, the three new inventory store tests pass. TestReplay164, TestOneAcceptForAProviderIsPlacedForEveryConsumer and the provision-wait tests in cmd/mesh-controller pass on the new base. The findings A to D of review 187 still stand, and none of them blocks the merge.
The statuses at this head, read at 11:40, are: mesh/merge-gate "pending — building on shanks since 11:38" (after "waiting in the build queue since 11:38") and mesh/delivery "pending — checking". No mesh/repo-check status is posted yet. Merge only once both required checks are green and read.
Merge check at c9dd07f9
Judged: head c9dd07f919a2 merged into trunk 404b4409e70e; read beside it: mesh-catalog 45d01e77c26d, mesh-controller-main 404b4409e70e, mesh-host 7a41e120635b, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (run 2) (modules: build-agent, mesh-controller)
mesh/repo-check: PASS — its merge-check.sh passed (run 2)
Delivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
tier 0: mesh-controller
tier 1: build-agent
ace: receives build-agent
g14: receives build-agent
novox: receives mesh-controller, build-agent
shanks: receives build-agent
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
Run by the build seat on shanks as build-1791884327303169448 (builds --log build-1791884327303169448).
restarted: handed out 2 times; an earlier hand-out did not finish here, so this run started from the beginning
--- the gate: build-agent, mesh-controller
mesh-controller: ok, 58 tool(s); WARNING: merge-window ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T09:37:20Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T09:37:20Z
modules the change moves:
- mesh-controller (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 2 module(s) in 2 tier(s): build-agent, mesh-controller
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.043s
ok github.com/novox/mesh-controller/cmd/mesh-controller 181.980s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.041s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.047s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.035s
ok github.com/novox/mesh-controller/examples/route-proxy 1.235s
ok github.com/novox/mesh-controller/internal/artifacts 1.091s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.521s
ok github.com/novox/mesh-controller/internal/builder 46.567s
ok github.com/novox/mesh-controller/internal/catalogue 8.331s
ok github.com/novox/mesh-controller/internal/conditions 2.710s
ok github.com/novox/mesh-controller/internal/envfile 1.063s
ok github.com/novox/mesh-controller/internal/facts 1.074s
ok github.com/novox/mesh-controller/internal/identity 5.027s
ok github.com/novox/mesh-controller/internal/inventory 43.426s
ok github.com/novox/mesh-controller/internal/lease 17.715s
ok github.com/novox/mesh-controller/internal/licences 4.126s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.052s
ok github.com/novox/mesh-controller/internal/link 64.225s
ok github.com/novox/mesh-controller/internal/lint 2.515s
ok github.com/novox/mesh-controller/internal/outward 1.088s
ok github.com/novox/mesh-controller/internal/overlay 1.074s
ok github.com/novox/mesh-controller/internal/secrets 1.543s
ok github.com/novox/mesh-controller/internal/store 3.482s
ok github.com/novox/mesh-controller/internal/testbus 1.196s
ok github.com/novox/mesh-controller/internal/token 1.015s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.23s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.24s)
./cmd/mesh-controller --- PASS: TestAStopWalksTheOthersAgainInTheOpenBatch (0.22s)
./cmd/mesh-controller --- PASS: TestAStopNamesWhatStaysOnTheMachinesAndWhatStillCarriesIt (0.21s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeLeavesItOutOfABatch (0.20s)
./cmd/mesh-controller --- PASS: TestSavingPlansAndMovingMergesIsOneAct (0.17s)
./cmd/mesh-controller --- PASS: TestAPlanFollowedFromTheStoreAnswersWhenItMoves (1.97s)
./cmd/mesh-controller --- PASS: TestNodeShowAndStatusReadTheLastSendFromTheStore (0.25s)
./cmd/mesh-controller --- PASS: TestTheConditionsVerbReadsTheLooksTheBucketNoLongerHolds (0.14s)
./cmd/mesh-controller --- PASS: TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded (0.25s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped (0.25s)
./cmd/mesh-controller --- PASS: TestAWalkRetriedByAPersonIsSaidStartedAgain (0.21s)
./cmd/mesh-controller --- PASS: TestABacklogReleasedByAPersonIsSaidStarted (0.21s)
./cmd/mesh-controller --- PASS: TestAWalkEndedByCancelOrClearIsSaidEnded (0.27s)
./cmd/mesh-controller --- PASS: TestAWalkRestartedByARebuildIsSaidStarted (0.21s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.25s)
./cmd/mesh-controller --- PASS: TestReplay237 (0.20s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer (0.20s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt (0.21s)
./cmd/mesh-controller --- PASS: TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal (0.20s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.23s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.30s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.27s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.16s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.16s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.16s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.30s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.23s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.28s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.25s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.21s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.20s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.18s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.16s)
./cmd/mesh-controller --- PASS: TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns (0.19s)
./cmd/mesh-controller --- PASS: TestReplay614ANewSDKPlansTheToolchainAndThenItsBundles (0.08s)
./cmd/mesh-controller --- PASS: TestOneAcceptForAProviderIsPlacedForEveryConsumer (0.48s)
./cmd/mesh-controller --- PASS: TestReplay164 (0.28s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.08s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.08s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.10s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.09s)
./internal/inventory --- PASS: TestAConditionsLooksAreKeptBesideTheBucket (0.07s)
./internal/inventory --- PASS: TestAPlanIdHoldingAQuoteIsReadAsAPlainId (0.07s)
./internal/inventory --- PASS: TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid (0.07s)
./internal/inventory --- PASS: TestAWalksRestIsReadFromEachMachinesOwnSend (0.07s)
./internal/inventory --- PASS: TestAStoredBuildWithASilentMergeableFileIsStillReadAndComposed (0.07s)
./internal/inventory --- PASS: TestACredentialIssuedOutsideIsNeverMade (0.08s)
./internal/inventory --- PASS: TestOneAcceptForAProviderIsSealedToEveryConsumer (0.09s)
./internal/inventory --- PASS: TestASharedOwnSecretIssuedOutsideIsNeverMade (0.07s)
**Merge check** at `c9dd07f9`
Judged: head `c9dd07f919a2` merged into trunk `404b4409e70e`; read beside it: mesh-catalog `45d01e77c26d`, mesh-controller-main `404b4409e70e`, mesh-host `7a41e120635b`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (run 2) (modules: build-agent, mesh-controller)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed (run 2)
**Delivery plan** — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- ace: receives build-agent
- g14: receives build-agent
- novox: receives mesh-controller, build-agent
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before build-agent is judged on its first machine
Run by the build seat on shanks as `build-1791884327303169448` (`builds --log build-1791884327303169448`).
```
restarted: handed out 2 times; an earlier hand-out did not finish here, so this run started from the beginning
--- the gate: build-agent, mesh-controller
mesh-controller: ok, 58 tool(s); WARNING: merge-window ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T09:37:20Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T09:37:20Z
modules the change moves:
- mesh-controller (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 2 module(s) in 2 tier(s): build-agent, mesh-controller
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.043s
ok github.com/novox/mesh-controller/cmd/mesh-controller 181.980s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.041s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.047s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.035s
ok github.com/novox/mesh-controller/examples/route-proxy 1.235s
ok github.com/novox/mesh-controller/internal/artifacts 1.091s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.521s
ok github.com/novox/mesh-controller/internal/builder 46.567s
ok github.com/novox/mesh-controller/internal/catalogue 8.331s
ok github.com/novox/mesh-controller/internal/conditions 2.710s
ok github.com/novox/mesh-controller/internal/envfile 1.063s
ok github.com/novox/mesh-controller/internal/facts 1.074s
ok github.com/novox/mesh-controller/internal/identity 5.027s
ok github.com/novox/mesh-controller/internal/inventory 43.426s
ok github.com/novox/mesh-controller/internal/lease 17.715s
ok github.com/novox/mesh-controller/internal/licences 4.126s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.052s
ok github.com/novox/mesh-controller/internal/link 64.225s
ok github.com/novox/mesh-controller/internal/lint 2.515s
ok github.com/novox/mesh-controller/internal/outward 1.088s
ok github.com/novox/mesh-controller/internal/overlay 1.074s
ok github.com/novox/mesh-controller/internal/secrets 1.543s
ok github.com/novox/mesh-controller/internal/store 3.482s
ok github.com/novox/mesh-controller/internal/testbus 1.196s
ok github.com/novox/mesh-controller/internal/token 1.015s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.23s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.24s)
./cmd/mesh-controller --- PASS: TestAStopWalksTheOthersAgainInTheOpenBatch (0.22s)
./cmd/mesh-controller --- PASS: TestAStopNamesWhatStaysOnTheMachinesAndWhatStillCarriesIt (0.21s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeLeavesItOutOfABatch (0.20s)
./cmd/mesh-controller --- PASS: TestSavingPlansAndMovingMergesIsOneAct (0.17s)
./cmd/mesh-controller --- PASS: TestAPlanFollowedFromTheStoreAnswersWhenItMoves (1.97s)
./cmd/mesh-controller --- PASS: TestNodeShowAndStatusReadTheLastSendFromTheStore (0.25s)
./cmd/mesh-controller --- PASS: TestTheConditionsVerbReadsTheLooksTheBucketNoLongerHolds (0.14s)
./cmd/mesh-controller --- PASS: TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded (0.25s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped (0.25s)
./cmd/mesh-controller --- PASS: TestAWalkRetriedByAPersonIsSaidStartedAgain (0.21s)
./cmd/mesh-controller --- PASS: TestABacklogReleasedByAPersonIsSaidStarted (0.21s)
./cmd/mesh-controller --- PASS: TestAWalkEndedByCancelOrClearIsSaidEnded (0.27s)
./cmd/mesh-controller --- PASS: TestAWalkRestartedByARebuildIsSaidStarted (0.21s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.25s)
./cmd/mesh-controller --- PASS: TestReplay237 (0.20s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer (0.20s)
./cmd/mesh-controller --- PASS: TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt (0.21s)
./cmd/mesh-controller --- PASS: TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal (0.20s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.23s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.30s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.27s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.16s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.16s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.16s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.30s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.23s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.28s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.25s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.21s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.20s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.18s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.16s)
./cmd/mesh-controller --- PASS: TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns (0.19s)
./cmd/mesh-controller --- PASS: TestReplay614ANewSDKPlansTheToolchainAndThenItsBundles (0.08s)
./cmd/mesh-controller --- PASS: TestOneAcceptForAProviderIsPlacedForEveryConsumer (0.48s)
./cmd/mesh-controller --- PASS: TestReplay164 (0.28s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.08s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.08s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.10s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.09s)
./internal/inventory --- PASS: TestAConditionsLooksAreKeptBesideTheBucket (0.07s)
./internal/inventory --- PASS: TestAPlanIdHoldingAQuoteIsReadAsAPlainId (0.07s)
./internal/inventory --- PASS: TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid (0.07s)
./internal/inventory --- PASS: TestAWalksRestIsReadFromEachMachinesOwnSend (0.07s)
./internal/inventory --- PASS: TestAStoredBuildWithASilentMergeableFileIsStillReadAndComposed (0.07s)
./internal/inventory --- PASS: TestACredentialIssuedOutsideIsNeverMade (0.08s)
./internal/inventory --- PASS: TestOneAcceptForAProviderIsSealedToEveryConsumer (0.09s)
./internal/inventory --- PASS: TestASharedOwnSecretIssuedOutsideIsNeverMade (0.07s)
```
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.
Fixes issue 164. Decision: novox/hq ADR 0337 (accepted 2026-10-13 on the operator's Approve).
What changes
internal/catalogue): an offer may say"credential": {"issued-by": "outside"}and must then name no own secret and no tool; any issuer other thansoftwareoroutsideis refused (ADR 0337 §1, widening ADR 0305 §1).Manifest.IssuedOutsideOf,OffersIssuedOutside.SecretFornever makes a value for such a provision. Its one mint path asks the catalogue and answersNotGivenErrorinstead (§2).GivenPairSecretreads such a pair as given only when its row isacceptedand sealed to the current keys of both ends. It never writes; a value the mesh made before the rule, or one sealed under an old key, is not given (§2, §8).remakeSharedrefuses, andSharedSecretForanswers not given, for a provider's own secret issued outside (ADR 0228).RotateSecretrefuses such a provision, naming the accept line (§9).AcceptSecretForProvisionseals one value to every consumer named, to the provider and to the operator, each rowacceptedunder one generation, and says which consumers have no sealing key (§3).GivenPairsOflists the provisions a consumer holds a given value for.outsideis read withGivenPairSecret. While it is not given, the consumer receives no credential file and its machine is not refused (§4). The plan reads aNotGivenErrorfromSharedSecretForthe same way.secret accept <provider's node> <provider module> <provision>gives such a credential once for its provider. It resolves every machine to find each consumer that binds the provision from that provider, seals the value to them all, and sends the holding machines (§3, issue 165). The per-pair form (--provider) is refused for such a provision, naming this line. The value is read from the terminal or--from, never through a verb, and is never printed.link.Waitandinventory.Waitcarryprovision, so a wait names exactly one secret, one setting or one provision (§5). The controller excuses a provision wait only when the module requires the provision, a provider offers it issued outside, and no given value is held for the pair under the current keys (§6). The consumer'sneeds-operatorcondition says "give ", and its summary names the onesecret acceptline for the provider (§7).Not in this PR (follow-up issues)
go/healthand the node-engine must carryprovisionin a wait end to end.issued-by: outside, and their consumers' probes must go (§11, issue 167). Until they declare it, nothing changes on any machine.Tests (fail on main: the new identifiers do not exist there; the replay fails behaviourally)
internal/catalogue/issued_outside_test.go: the offer is read, and every bad shape is refused.internal/inventory/outside_test.go(store tests, listed intestdata/store-tests):cmd/mesh-controller/outside_wait_test.go: the wait is excused only under §6; the condition is in plain words and names the accept line; one accept reaches the consumers its provider answers.cmd/mesh-controller/outside_accept_test.go(store test): a made value is placed before the rule and not after; the per-pair form is refused naming the provider's form; one accept is placed for both consumers.cmd/mesh-controller/replay164_test.go, TestReplay164: the replay, written with only pre-fix code.go run ./cmd/prove R164in mesh-lab says "PROVED: fails before its fix, passes on its fix" (register entry: mesh-lab fix/164-replay).Checks run locally
merge-check.shagainst a throwaway store passed everything except two failures:TestATierPutsABrokenModuleBackAtOnce, which failed under a load of 25 or more and passes 3 of 3 alone.Do not merge without an independent review.
Delivery
novox/mesh-controller@c9dd07f919a2— delivered since 2026-10-13T09:58:59ZDelivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
cfe63a47Judged: head
cfe63a4738aemerged into trunk1826b68a22b5; read beside it: mesh-catalogf35b06750452, mesh-controller-main1826b68a22b5, mesh-host7a41e120635b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Run by the build seat on ace as
build-1791882924477134278(builds --log build-1791882924477134278).Verdict: approve at
cfe63a4738Independent review for issue 164, against ADR 0337 (accepted, version 8). I read the whole diff (18 files), the callers of every changed function, and the media catalogue's current manifests of sonarr, qbittorrent and nzbget.
WHAT I CHECKED, AND WHAT I SAW
Nothing is minted for issued-by outside. SecretFor's one mint path now asks the catalogue first and answers NotGivenError for such a provision, after the held-row read and before MakeWithOperator, so no caller of SecretFor can have a value made. The plan does not call SecretFor for such a need at all: issuedOutsideBy picks GivenPairSecret by the need's own provider module, and GivenPairSecret only reads, never writes. remakeShared refuses a provider's own secret that says issued-by outside, and SharedSecretFor answers NotGivenError for it before it would call remakeShared. RotateSecret refuses such a provision and names the accept line. The store tests TestACredentialIssuedOutsideIsNeverMade and TestASharedOwnSecretIssuedOutsideIsNeverMade show no row and no value for a pair with no row, for a newly bound consumer and after a key change.
A software-issued offer is unchanged (ADR 0305 §7). The parser's software branch is the same code under a new else-if; the plan's SharedOwn and SecretFor branches are untouched for offers that do not say outside. The ADR 0305 tests pass. The one edit to them (internal/catalogue/issued_credential_test.go) changes only the expected words of the refusal of a foreign issuer, from "the one issuer an offer may name" to "the issuers an offer may name"; ADR 0337 §12 asks for those tests unchanged, so this is a small deviation in letter, not in what is checked.
A stored row from before the change is safe. A made row sealed to the current keys is answered by GivenPairSecret as not given ("the value held was made by the mesh"), and the plan places no file for it; the store test covers this. A rollback to main is safe: there is no migration, the rows AcceptSecretForProvision writes use the existing columns with origin accepted and a generation, which main's SecretFor reads as held, and the stored catalogue is read with json.Unmarshal rather than the parser, so main still reads a manifest that says issued-by outside. Main would then mint again for such an offer, which is the pre-fix behaviour, not a failure.
The waiting consumer gets the needs-operator condition naming the accept. checkWait now requires exactly one of secret, setting or provision; checkProvisionWait excuses a provision wait only under §6's three conditions; checkWaiting fills Act with "secret accept ", and the condition's words say "give " with the line in the summary. TestAWaitForAProvisionIsExcusedOnlyWhenTheStoreBearsItOut and TestAWaitForAProvisionNamesTheOneAcceptForItsProvider cover it.
The tests fail on main, and the replay uses only pre-fix code. On main
1826b68the new catalogue, inventory and cmd tests do not compile (IssuedOutsideOf, OffersIssuedOutside, IsNotGiven, Wait.Provision and consumersFrom do not exist there). TestReplay164, laid over main alone, fails on behaviour: "the send placed a credential for tv-api (requests.secret-tv-api), which only a person can give and nobody gave" and "the mesh holds a value for tv-api that nobody gave (origin made)". It uses register, assign, planFor, declarationFor, AcceptSecretForPair and SecretsOf, all on main. mesh-lab #102'sgo run ./cmd/prove R164says PROVED.Runs, niced on the laptop against a throwaway store: with -race, the new and neighbouring tests of internal/catalogue, internal/inventory and cmd/mesh-controller (Outside, OneAcceptForAProvider, Replay164, Wait, Secret, Rotate, Issued, Replay401, Replay201) all pass. Without -race, the whole of internal/inventory, internal/catalogue and internal/link pass. go vet is clean and gofmt lists no changed file. The whole of cmd/mesh-controller also passes without -race (328 s).
FINDINGS, NONE BLOCKING
A. The provider half still carries a pre-rule made value. grantsFor reads SecretsFrom, which returns every pair row with this provider, and puts its for_provider value on the grant as Sealed, without asking whether the offer says outside or whether the row was made. So a row the mesh made before this change is still sent to the provider's machine. ADR 0337 §2 says such a value "is never delivered again". Nothing is affected today, because no manifest says outside yet, but it should be closed before issue 618 makes the six offers say outside: leave made rows of an outside offer out of SecretsFrom (or out of the grant's Sealed), with a store test.
B. Four places judge "issued outside" by the provision's name across the whole catalogue, not by the provider the pair binds: SecretFor and RotateSecret (through issuedOutside), and readWaitFacts and checkProvisionWait (through OffersIssuedOutside); GivenPairsOf also ignores which provider a given row came from. The plan itself uses the bound provider. Today every provision name in both catalogues is offered by one module (the only shared name is route), so nothing changes, but if two modules ever offered one name with different issuers, the mesh would stop making the credential of the one that is not outside. A follow-up should pass the provider module through.
C. When the provider module is assigned to more than one machine, the accept line names "" instead of the machine the consumer binds. ADR 0337 §7 wants the one line for the provider; the consumer's bound provider is known from its resolution. Minor.
D. A design point for issue 618, not for this pull request. The current manifests of sonarr, qbittorrent and nzbget already offer their credential as their own secret (ADR 0158: "credential": {"own": "api-key"} or {"own": "password"}), and each writes the mesh's value into the app before it starts (sonarr's api-key-init writes ApiKey into config.xml; qbittorrent's webui-login-init writes the WebUI password hash; nzbget reads FILE__NZBGET_PASS). So for these providers the mesh's value does work today, and ADR 0337 §11, which moves them to issued-by outside, would replace a working credential with one accept per provider. Issue 618 should be judged against the manifests as they are before anyone builds it.
STATUSES AT THIS HEAD (read 11:36)
mesh/merge-gate: "pending — out of date: judged against trunk
1826b68a22, the trunk is now 404b4409e70e; judged again on a recheck or when it is merged". mesh/repo-check: the same words. mesh/delivery: "pending — checking". Neither required check has a verdict on this head against the current main, and I ran the tests on the base1826b68, not on404b4409. The approval is of the code; ask mesh-delivery.recheck and merge only once both required checks are green and read.Merge check at
cfe63a47Judged: head
cfe63a4738aemerged into trunk404b4409e70emesh/merge-gate: FAIL — headcfe63a4738does not merge cleanly into trunk404b4409e7: in conflict testdata/store-tests (modules: build-agent, mesh-controller)mesh/repo-check: FAIL — headcfe63a4738does not merge cleanly into trunk404b4409e7: in conflict testdata/store-testsDelivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791884212717223933(builds --log build-1791884212717223933).A Servarr app's API key, an indexer's key and a downloader's password are values the provider's software holds; the pair path made a random value for every consumer of them, sealed it to both ends and reported success, and the provider refused it. An offer may now say `"credential": {"issued-by": "outside"}`: SecretFor never makes a value for it, the plan reads such a pair as given or not given and sends the consumer no credential file until a person gives it, without refusing its machine; remakeShared and SharedSecretFor never make a provider's own secret issued outside either. `secret accept <provider's node> <provider module> <provision>` seals one value to every consumer that binds the provision from that provider, the provider and the operator, under one generation; the per-pair form is refused for such a provision, and rotate names the accept. A consumer may wait for a provision's credential: the controller excuses the wait only when the store bears it out, and its needs-operator condition names the one accept line. Adds the replay of issue 164 (TestReplay164), written with only what the controller had before.cfe63a4738toc9dd07f919Verdict: approve at
c9dd07f919This re-reviews the rebase of the head I approved in review 187 (
cfe63a4738) onto main404b4409e7.The rebase changes only testdata/store-tests.
git range-diff 1826b68a22b5..cfe63a47 404b4409e70e..c9dd07f9shows one commit, and its only difference is the context of testdata/store-tests. The file at the new head keeps #296's line "./cmd/mesh-controller TestReplay614ANewSDKPlansTheToolchainAndThenItsBundles" and is followed by this pull request's five lines unchanged: the three internal/inventory store tests, TestOneAcceptForAProviderIsPlacedForEveryConsumer and TestReplay164. Leaving that file aside, the added and removed lines of the pull request's diff are identical before and after the rebase (18 files, 1207 insertions, 26 deletions). The new head's parent is404b4409e7.I ran these niced on the laptop against a throwaway store.
go vetof internal/inventory, internal/catalogue and cmd/mesh-controller is clean.go test ./internal/inventorypasses in full (25 s). With -race, the three new inventory store tests pass. TestReplay164, TestOneAcceptForAProviderIsPlacedForEveryConsumer and the provision-wait tests in cmd/mesh-controller pass on the new base. The findings A to D of review 187 still stand, and none of them blocks the merge.The statuses at this head, read at 11:40, are: mesh/merge-gate "pending — building on shanks since 11:38" (after "waiting in the build queue since 11:38") and mesh/delivery "pending — checking". No mesh/repo-check status is posted yet. Merge only once both required checks are green and read.
Merge check at
c9dd07f9Judged: head
c9dd07f919a2merged into trunk404b4409e70e; read beside it: mesh-catalog45d01e77c26d, mesh-controller-main404b4409e70e, mesh-host7a41e120635b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (run 2) (modules: build-agent, mesh-controller)mesh/repo-check: PASS — its merge-check.sh passed (run 2)Delivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791884327303169448(builds --log build-1791884327303169448).