What.cmd/mesh-controller/replay239_test.go, a test-only replay of hq issue 239 (2026-10-04: photos registered from the app's own repository was rebuilt from the catalogue's modules/photos stub, the controller recorded the catalogue as its source, and the next push removed two sites and five routes). Listed in testdata/store-tests.
Why. The fix already landed: #174 (merge eb7a158, hq ADR 0266 §7) refuses an outcome built from a repository other than the one the module is registered from, unless asked at the controller's terminal. Nothing replayed the incident.
How. The replay uses only code from before the fix (aMesh, RegisterModule, SourceOf, RecordBuildRequest, takeIn): it registers photos from novox/photos and another module from novox/mesh-catalog, then takes in a verb-asked build of photos from the catalogue. It asserts the take-in is refused naming both sources, and photos keeps its source and version.
Tested. With a throwaway store (MESH_TEST_POSTGRES): fails on eb7a158^1 (registered; source becomes the catalogue; version becomes the stub), passes on eb7a158 and on main. sh merge-check.sh passes with the store.
Fixes issue 239 (replay; mesh-lab register entry in its own PR).
**What.** `cmd/mesh-controller/replay239_test.go`, a test-only replay of hq issue 239 (2026-10-04: `photos` registered from the app's own repository was rebuilt from the catalogue's `modules/photos` stub, the controller recorded the catalogue as its source, and the next push removed two sites and five routes). Listed in `testdata/store-tests`.
**Why.** The fix already landed: #174 (merge `eb7a158`, hq ADR 0266 §7) refuses an outcome built from a repository other than the one the module is registered from, unless asked at the controller's terminal. Nothing replayed the incident.
**How.** The replay uses only code from before the fix (aMesh, RegisterModule, SourceOf, RecordBuildRequest, takeIn): it registers `photos` from `novox/photos` and another module from `novox/mesh-catalog`, then takes in a verb-asked build of `photos` from the catalogue. It asserts the take-in is refused naming both sources, and `photos` keeps its source and version.
**Tested.** With a throwaway store (MESH_TEST_POSTGRES): fails on `eb7a158^1` (registered; source becomes the catalogue; version becomes the stub), passes on `eb7a158` and on main. `sh merge-check.sh` passes with the store.
Fixes issue 239 (replay; mesh-lab register entry in its own PR).
The fix (#174, hq ADR 0266 §7) refuses an outcome built from a repository other than the module's
registered one; nothing replayed the photo app's outage that showed why. The replay uses only what
the controller had before the fix, so the prover can show it fails there and passes on the fix.
composed together: pass — the change touches no module of the mesh's graph
Transitions
2026-10-13 05:02 (new) → proposed (announced): novox/mesh-controller#294's head announced
2026-10-13 05:28 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-13 05:28 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-13 05:37 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 05:37 published → delivered (done): nothing for a walk to move
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-controller@95556b8def3f` — **delivered** since 2026-10-13T05:37:39Z
**Delivery plan** — builds nothing: the change touches no module of the mesh's graph
**Group** `fix/239-replay`, in order: novox/mesh-controller@95556b8def3f → novox/mesh-lab@3b4ba29becc8
- composed together: pass — the change touches no module of the mesh's graph
**Transitions**
- 2026-10-13 05:02 (new) → proposed (announced): novox/mesh-controller#294's head announced
- 2026-10-13 05:28 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-13 05:28 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-13 05:37 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 05:37 published → delivered (done): nothing for a walk to move
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Merge check at 95556b8d
Judged: head 95556b8def3f merged into trunk cd6757f1e3c8; read beside it: mesh-catalog 53e51864969f, mesh-controller-main cd6757f1e3c8, 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, route-proxy)
mesh/repo-check: PASS — its merge-check.sh passed
Delivery plan — builds build-agent, route-proxy → ace, g14, novox, shanks; no bus step
tier 0: build-agent
tier 1: route-proxy
ace: receives build-agent, route-proxy
g14: receives build-agent
novox: receives build-agent, route-proxy
shanks: receives build-agent
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy 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, route-proxy is judged on its first machine
Run by the build seat on shanks as build-1791867759485278251 (builds --log build-1791867759485278251).
--- the gate: build-agent, route-proxy
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T05:01:44Z 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-13T05:01:44Z
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, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.039s
ok github.com/novox/mesh-controller/cmd/mesh-controller 174.838s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.053s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.060s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.042s
ok github.com/novox/mesh-controller/examples/route-proxy 1.206s
ok github.com/novox/mesh-controller/internal/artifacts 1.094s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.436s
ok github.com/novox/mesh-controller/internal/builder 44.578s
ok github.com/novox/mesh-controller/internal/catalogue 8.513s
ok github.com/novox/mesh-controller/internal/conditions 2.665s
ok github.com/novox/mesh-controller/internal/envfile 1.056s
ok github.com/novox/mesh-controller/internal/facts 1.055s
ok github.com/novox/mesh-controller/internal/identity 4.943s
ok github.com/novox/mesh-controller/internal/inventory 36.035s
ok github.com/novox/mesh-controller/internal/lease 17.721s
ok github.com/novox/mesh-controller/internal/licences 4.109s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.053s
ok github.com/novox/mesh-controller/internal/link 60.484s
ok github.com/novox/mesh-controller/internal/lint 2.510s
ok github.com/novox/mesh-controller/internal/outward 1.096s
ok github.com/novox/mesh-controller/internal/overlay 1.079s
ok github.com/novox/mesh-controller/internal/secrets 1.530s
ok github.com/novox/mesh-controller/internal/store 3.473s
ok github.com/novox/mesh-controller/internal/testbus 1.244s
ok github.com/novox/mesh-controller/internal/token 1.017s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.22s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.23s)
./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.24s)
./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.22s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.26s)
./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.21s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.24s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.28s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.28s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.17s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.15s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.16s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.28s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.23s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.28s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.24s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.20s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.20s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.18s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.15s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.08s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.08s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.10s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.08s)
./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)
Out of date when it landed: out of date: judged against trunk cd6757f1e3, the trunk is now 8f1aec0832b1; judged again on a recheck or when it is merged
**Merge check** at `95556b8d`
Judged: head `95556b8def3f` merged into trunk `cd6757f1e3c8`; read beside it: mesh-catalog `53e51864969f`, mesh-controller-main `cd6757f1e3c8`, 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, route-proxy)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: build-agent
- tier 1: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy 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, route-proxy is judged on its first machine
Run by the build seat on shanks as `build-1791867759485278251` (`builds --log build-1791867759485278251`).
```
--- the gate: build-agent, route-proxy
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T05:01:44Z 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-13T05:01:44Z
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, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.039s
ok github.com/novox/mesh-controller/cmd/mesh-controller 174.838s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.053s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.060s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.042s
ok github.com/novox/mesh-controller/examples/route-proxy 1.206s
ok github.com/novox/mesh-controller/internal/artifacts 1.094s
? github.com/novox/mesh-controller/internal/beside [no test files]
ok github.com/novox/mesh-controller/internal/broker 4.436s
ok github.com/novox/mesh-controller/internal/builder 44.578s
ok github.com/novox/mesh-controller/internal/catalogue 8.513s
ok github.com/novox/mesh-controller/internal/conditions 2.665s
ok github.com/novox/mesh-controller/internal/envfile 1.056s
ok github.com/novox/mesh-controller/internal/facts 1.055s
ok github.com/novox/mesh-controller/internal/identity 4.943s
ok github.com/novox/mesh-controller/internal/inventory 36.035s
ok github.com/novox/mesh-controller/internal/lease 17.721s
ok github.com/novox/mesh-controller/internal/licences 4.109s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.053s
ok github.com/novox/mesh-controller/internal/link 60.484s
ok github.com/novox/mesh-controller/internal/lint 2.510s
ok github.com/novox/mesh-controller/internal/outward 1.096s
ok github.com/novox/mesh-controller/internal/overlay 1.079s
ok github.com/novox/mesh-controller/internal/secrets 1.530s
ok github.com/novox/mesh-controller/internal/store 3.473s
ok github.com/novox/mesh-controller/internal/testbus 1.244s
ok github.com/novox/mesh-controller/internal/token 1.017s
store tests, each run against the store:
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeWalksTheOthersAgain (0.22s)
./cmd/mesh-controller --- PASS: TestAStopWithoutAMergeEndsTheMergesThatContainIt (0.23s)
./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.24s)
./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.22s)
./cmd/mesh-controller --- PASS: TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain (0.26s)
./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.21s)
./cmd/mesh-controller --- PASS: TestADirectoryOwnersKeyIsRefusedThroughAVerb (0.24s)
./cmd/mesh-controller --- PASS: TestTheSendMakesNoValueForAnOutsideSecretNobodyGave (0.28s)
./cmd/mesh-controller --- PASS: TestAQuestionComposesWithoutAnOutsideSecretNobodyGave (0.28s)
./cmd/mesh-controller --- PASS: TestReplay201 (0.17s)
./cmd/mesh-controller --- PASS: TestARollbackWhoseSendWasRefusedIsCompletedByTheNextSend (0.15s)
./cmd/mesh-controller --- PASS: TestAGateJudgesTheOtherMovesBesideAKeptCore (0.16s)
./cmd/mesh-controller --- PASS: TestReplay401 (0.28s)
./cmd/mesh-controller --- PASS: TestReplay201ThePlansSend (0.23s)
./cmd/mesh-controller --- PASS: TestPlanAndNodeListAnOptionalRequirementLeftUnbound (0.28s)
./cmd/mesh-controller --- PASS: TestReplay340 (0.24s)
./cmd/mesh-controller --- PASS: TestOnlyATrustedFilesNamedKeyIsRefusedThroughAVerb (0.20s)
./cmd/mesh-controller --- PASS: TestReplay349 (0.20s)
./cmd/mesh-controller --- PASS: TestReplay348AFaultFromBeforeTheSendFailsNoGate (0.18s)
./cmd/mesh-controller --- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository (0.15s)
./internal/inventory --- PASS: TestTheLastSendSaysWhatTheMachineAnswered (0.08s)
./internal/inventory --- PASS: TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend (0.08s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain (0.10s)
./internal/inventory --- PASS: TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance (0.08s)
./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)
```
**Out of date when it landed**: out of date: judged against trunk cd6757f1e3c8, the trunk is now 8f1aec0832b1; judged again on a recheck or when it is merged
The test uses only symbols that existed before the fix (eb7a158086): aMesh, catalogue.ParseManifest, inventory.RegisterModule, inventory.Source, inv.RecordBuildRequest, inventory.BuildRequest, takeIn, link.BuildResult, link.SourceOnSeat, inv.SourceOf, inv.Catalogue and inv.BuildByID. None of these are new in the fix; the fix only adds an internal call to the new mayRegisterFrom inside takeIn, which the test exercises indirectly through takeIn's changed behaviour rather than by naming mayRegisterFrom itself.
I raised a throwaway postgres 17 container and ran the replay with MESH_TEST_POSTGRES against it at three points. On eb7a158^1 it fails: "--- FAIL: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository", with photos recorded as built from "novox/mesh-catalog" and version "stub". On eb7a158 it passes: "--- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository". On this PR's head (which stands in for main, since main already carries eb7a158) it also passes. I then ran the full sh merge-check.sh on this branch with the same store: it exited 0, and every test including TestReplay239AModuleNameIsNotTakenOverByAnotherRepository reported PASS.
The replay faithfully reproduces the issue 239 incident: it registers photos from novox/photos (the app's own repository, matching the real source) and a second module from novox/mesh-catalog so the catalogue counts as a repository the mesh builds from, then asks a build of photos from novox/mesh-catalog's modules/photos through the build verb (RecordBuildRequest + takeIn, never the terminal) carrying a stub manifest — mirroring the real "rebuild to main" that pulled in the catalogue's stub instead of the app. It asserts the take-in is refused naming both novox/photos and novox/mesh-catalog, that photos keeps source novox/photos and version "app" (not "stub"), and that the refused build request is still recorded by BuildByID, matching ADR 0266 §7's rule that a refusal is still recorded, never silently dropped.
I grepped the controller's verbs. RegisterModule is called from exactly three places: the terminal's module add (cmd/mesh-controller/modules.go), takeIn (cmd/mesh-controller/build.go, now gated by mayRegisterFrom), and raiseFromFacts (cmd/mesh-controller/merge_gate.go), which only rebuilds a synthetic throwaway store for the merge-gate's own compose check and never touches the live inventory a verb could reach. The ControllerVerbs table has no "module" verb at all, only "modules" (read-only); the generic "command" verb's commandReadForms map restricts "module" to its "list" subcommand only. So the claim holds: no verb lets a caller register a module from a repository other than the one it is already registered from; only the terminal's module add, or a build asked at the terminal, can move a module's recorded source.
No concerns found.
Verdict: approve at 95556b8def3f3f21e3d037617d73f97606e6ea1f
The test uses only symbols that existed before the fix (eb7a15808641f2075d9fd1660fa0e949b9cd7230): aMesh, catalogue.ParseManifest, inventory.RegisterModule, inventory.Source, inv.RecordBuildRequest, inventory.BuildRequest, takeIn, link.BuildResult, link.SourceOnSeat, inv.SourceOf, inv.Catalogue and inv.BuildByID. None of these are new in the fix; the fix only adds an internal call to the new mayRegisterFrom inside takeIn, which the test exercises indirectly through takeIn's changed behaviour rather than by naming mayRegisterFrom itself.
I raised a throwaway postgres 17 container and ran the replay with MESH_TEST_POSTGRES against it at three points. On eb7a158^1 it fails: "--- FAIL: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository", with photos recorded as built from "novox/mesh-catalog" and version "stub". On eb7a158 it passes: "--- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository". On this PR's head (which stands in for main, since main already carries eb7a158) it also passes. I then ran the full `sh merge-check.sh` on this branch with the same store: it exited 0, and every test including TestReplay239AModuleNameIsNotTakenOverByAnotherRepository reported PASS.
The replay faithfully reproduces the issue 239 incident: it registers photos from novox/photos (the app's own repository, matching the real source) and a second module from novox/mesh-catalog so the catalogue counts as a repository the mesh builds from, then asks a build of photos from novox/mesh-catalog's modules/photos through the build verb (RecordBuildRequest + takeIn, never the terminal) carrying a stub manifest — mirroring the real "rebuild to main" that pulled in the catalogue's stub instead of the app. It asserts the take-in is refused naming both novox/photos and novox/mesh-catalog, that photos keeps source novox/photos and version "app" (not "stub"), and that the refused build request is still recorded by BuildByID, matching ADR 0266 §7's rule that a refusal is still recorded, never silently dropped.
I grepped the controller's verbs. RegisterModule is called from exactly three places: the terminal's `module add` (cmd/mesh-controller/modules.go), takeIn (cmd/mesh-controller/build.go, now gated by mayRegisterFrom), and raiseFromFacts (cmd/mesh-controller/merge_gate.go), which only rebuilds a synthetic throwaway store for the merge-gate's own compose check and never touches the live inventory a verb could reach. The ControllerVerbs table has no "module" verb at all, only "modules" (read-only); the generic "command" verb's commandReadForms map restricts "module" to its "list" subcommand only. So the claim holds: no verb lets a caller register a module from a repository other than the one it is already registered from; only the terminal's module add, or a build asked at the terminal, can move a module's recorded source.
No concerns found.
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.
cmd/mesh-controller/replay239_test.go, a test-only replay of hq issue 239 (2026-10-04:photosregistered from the app's own repository was rebuilt from the catalogue'smodules/photosstub, the controller recorded the catalogue as its source, and the next push removed two sites and five routes). Listed intestdata/store-tests.Why. The fix already landed: #174 (merge
eb7a158, hq ADR 0266 §7) refuses an outcome built from a repository other than the one the module is registered from, unless asked at the controller's terminal. Nothing replayed the incident.How. The replay uses only code from before the fix (aMesh, RegisterModule, SourceOf, RecordBuildRequest, takeIn): it registers
photosfromnovox/photosand another module fromnovox/mesh-catalog, then takes in a verb-asked build ofphotosfrom the catalogue. It asserts the take-in is refused naming both sources, andphotoskeeps its source and version.Tested. With a throwaway store (MESH_TEST_POSTGRES): fails on
eb7a158^1(registered; source becomes the catalogue; version becomes the stub), passes oneb7a158and on main.sh merge-check.shpasses with the store.Fixes issue 239 (replay; mesh-lab register entry in its own PR).
Delivery
novox/mesh-controller@95556b8def3f— delivered since 2026-10-13T05:37:39ZDelivery plan — builds nothing: the change touches no module of the mesh's graph
Group
fix/239-replay, in order: novox/mesh-controller@95556b8def → novox/mesh-lab@3b4ba29becTransitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
95556b8dJudged: head
95556b8def3fmerged into trunkcd6757f1e3c8; read beside it: mesh-catalog53e51864969f, mesh-controller-maincd6757f1e3c8, mesh-host7a41e120635b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791867759485278251(builds --log build-1791867759485278251).Out of date when it landed: out of date: judged against trunk
cd6757f1e3, the trunk is now 8f1aec0832b1; judged again on a recheck or when it is mergedVerdict: approve at
95556b8defThe test uses only symbols that existed before the fix (
eb7a158086): aMesh, catalogue.ParseManifest, inventory.RegisterModule, inventory.Source, inv.RecordBuildRequest, inventory.BuildRequest, takeIn, link.BuildResult, link.SourceOnSeat, inv.SourceOf, inv.Catalogue and inv.BuildByID. None of these are new in the fix; the fix only adds an internal call to the new mayRegisterFrom inside takeIn, which the test exercises indirectly through takeIn's changed behaviour rather than by naming mayRegisterFrom itself.I raised a throwaway postgres 17 container and ran the replay with MESH_TEST_POSTGRES against it at three points. On eb7a158^1 it fails: "--- FAIL: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository", with photos recorded as built from "novox/mesh-catalog" and version "stub". On
eb7a158it passes: "--- PASS: TestReplay239AModuleNameIsNotTakenOverByAnotherRepository". On this PR's head (which stands in for main, since main already carrieseb7a158) it also passes. I then ran the fullsh merge-check.shon this branch with the same store: it exited 0, and every test including TestReplay239AModuleNameIsNotTakenOverByAnotherRepository reported PASS.The replay faithfully reproduces the issue 239 incident: it registers photos from novox/photos (the app's own repository, matching the real source) and a second module from novox/mesh-catalog so the catalogue counts as a repository the mesh builds from, then asks a build of photos from novox/mesh-catalog's modules/photos through the build verb (RecordBuildRequest + takeIn, never the terminal) carrying a stub manifest — mirroring the real "rebuild to main" that pulled in the catalogue's stub instead of the app. It asserts the take-in is refused naming both novox/photos and novox/mesh-catalog, that photos keeps source novox/photos and version "app" (not "stub"), and that the refused build request is still recorded by BuildByID, matching ADR 0266 §7's rule that a refusal is still recorded, never silently dropped.
I grepped the controller's verbs. RegisterModule is called from exactly three places: the terminal's
module add(cmd/mesh-controller/modules.go), takeIn (cmd/mesh-controller/build.go, now gated by mayRegisterFrom), and raiseFromFacts (cmd/mesh-controller/merge_gate.go), which only rebuilds a synthetic throwaway store for the merge-gate's own compose check and never touches the live inventory a verb could reach. The ControllerVerbs table has no "module" verb at all, only "modules" (read-only); the generic "command" verb's commandReadForms map restricts "module" to its "list" subcommand only. So the claim holds: no verb lets a caller register a module from a repository other than the one it is already registered from; only the terminal's module add, or a build asked at the terminal, can move a module's recorded source.No concerns found.