57/F2: Placement leaves the settings layers, held beside the assignment (issue 651) #316

Merged
mesh-admin merged 6 commits from pkg/57-f2-placement-facts into feat/57-config-and-provisioning 2026-10-14 01:09:56 +00:00
Contributor

Work package F2 of hq design 57, issue 651, ADR 0348 clause 11 (placement is not config).

What changed

  • Migration 0097 adds the table placement (one row per layer scope: a machine's, or the whole mesh's with node null, exactly as the settings layer it came from) and the column settings_history.why. It copies every settings layer that holds places, accesses or networks whole into settings_history (replaced_by moved, why moved to placement, ADR 0348 clause 11), moves those three keys into placement, and removes them from the layer. The layer keeps its other keys, its set_at and set_by; a layer that held only placement stays as an empty layer.
  • The composer reads placement only from there. catalogue.Layer gains Placement; Places, AccessPlaces and KeptNetworks read layer.Placement and never layer.Values. inventory.SettingsFor fills Placement from the placement table beside each layer.
  • How a person sets placement afterwards: the same settings verb, routed. SetSettings/SetSettingsBy/KeepSettings split a layer they are given (catalogue.SplitPlacement/LayerOf) and write the settings row and the placement row in one transaction; ClearSettings clears both; Layer, the settings history and the facts snapshot read a layer whole (config values plus placement). So everything the operator does today — set, propose (places and accesses stay trusted keys), replace, clear, history, the merge gate's facts — keeps working unchanged.
  • Today's form is kept. A mergeable file that names no keys (ADR 0326 §3) still receives every key of the layer as it is said, placement included, and the line-end judgement still covers placement paths. ADR 0348 clause 11 lets a module that has not moved keep today's form, and without this the files of such modules (for example home-assistant on ace, whose layer holds places) would change.

Not done, and why: Settle is unchanged

The issue asks that Settle stop letting a settings layer replace a provider's served keys, and to stop and report if a live layer relies on it. One does: influxdb on ace has the layer {"org": "zurag", "bucket": "zurag", ...} while its manifest serves influxdb-api with the literal org: mesh, bucket: default; grafana on ace consumes influxdb-api and writes dbName: ${bound:influxdb-api:bucket} into its data source file. Removing the override would change grafana's file on ace from zurag to default. (mailu's domain and keycloak's issuer are served as ${setting:<same key>}, so they would not change.) So Settle is left as it is; the coordinator decides how to move influxdb (for example serving ${setting:org}/${setting:bucket} from the catalogue) before the override is removed.

How it is proven

  • TestADeclarationIsTheSameAfterPlacementMoves (cmd, store test): a mesh seeded as the store holds it before 0097 (mesh-wide accesses, a machine's places with owner, accesses and a kept network on an adopted machine, a layer of placement only, a mergeable file of today's form) is migrated and every machine's declaration composed; the resources of the seeded modules are byte for byte the golden files written by the integration branch's controller at bfc79c6 from the same seed. Checked that it fails when today's form is dropped.
  • TestPlacementMovesOutOfTheSettingsLayers (inventory, store test): after 0097 no settings row holds a placement key; every layer reads back as before (Layer, SettingsFor whole); set_at/set_by unchanged; the moved layers are in the history with why; the moved layers still judge as composable.
  • TestASettingRoutesPlacementBesideTheLayer (inventory, store test): the route (set, read back whole, history whole, KeepSettings, clear) and the refusals (an undeclared directory, the machine's own path, a kept network on a converged machine, a mesh-wide kept network), with nothing written on a refusal.
  • TestPlacementIsReadFromThePlacementOnly (catalogue): a placement key among config values places nothing; LayerOf splits; today's mergeable file composes the same.
  • Existing tests: catalogue tests that built a layer with placement among Values now build it with LayerOf (same values, same assertions); nothing else changed.

Run: gofmt -l cmd internal examples (empty), go vet ./..., go test -race ./... and MESH_TEST_POSTGRES=… sh merge-check.sh against a throwaway PostgreSQL 18: exit 0, every listed store test PASS (the three new ones added to testdata/store-tests).

Left for other packages

  • K5 moves the remaining settings layers into mesh-config; placement stays here.
  • Whether a mesh-wide placement scope should exist at all (ADR 0348 clause 3 removes the mesh-wide module value for config, and says nothing of placement): kept as it is today so nothing changes.
  • Design 57 says the data moves run as verbs after the delivery; this package was briefed as a numbered migration, which runs at the controller's start in the same delivery. Composition is identical before and after, so the timing does not change what any machine is told.
  • Files touched that other agents also touch: cmd/mesh-controller/modules.go (4 lines in the settings history output) and internal/inventory/catalogue.go (settings functions only); T1 (#313) touches other parts of both. No change in internal/catalogue/manifest.go, internal/builder or internal/broker.

Reviewer's checklist

  1. Does what the issue's Expected and design 57's F2 say, nothing outside it: yes, except Settle, stopped as briefed because influxdb on ace relies on it (above).
  2. Done-when proof as tests that fail before and pass now: the three store tests need migration 0097 and fail without it; the byte-identical test was checked to fail when composition changes.
  3. Byte-identical composition: TestADeclarationIsTheSameAfterPlacementMoves against golden files from bfc79c6.
  4. No secret anywhere; no hand-written database change; the schema change is migration 0097. Test fixtures write rows only to their own throwaway store.
  5. Strict parsers: no new manifest or declaration field; the declaration is unchanged.
  6. gofmt, go vet, go test ./... and merge-check.sh pass (commands above).
  7. Words: placement, config value, controller, walk; issue and ADR numbers said with what they are.
Work package F2 of hq design 57, issue 651, ADR 0348 clause 11 (placement is not config). ## What changed - **Migration 0097** adds the table `placement` (one row per layer scope: a machine's, or the whole mesh's with node null, exactly as the settings layer it came from) and the column `settings_history.why`. It copies every settings layer that holds `places`, `accesses` or `networks` whole into `settings_history` (replaced_by `moved`, why `moved to placement, ADR 0348 clause 11`), moves those three keys into `placement`, and removes them from the layer. The layer keeps its other keys, its `set_at` and `set_by`; a layer that held only placement stays as an empty layer. - **The composer reads placement only from there.** `catalogue.Layer` gains `Placement`; `Places`, `AccessPlaces` and `KeptNetworks` read `layer.Placement` and never `layer.Values`. `inventory.SettingsFor` fills `Placement` from the placement table beside each layer. - **How a person sets placement afterwards: the same `settings` verb, routed.** `SetSettings`/`SetSettingsBy`/`KeepSettings` split a layer they are given (`catalogue.SplitPlacement`/`LayerOf`) and write the settings row and the placement row in one transaction; `ClearSettings` clears both; `Layer`, the settings history and the facts snapshot read a layer whole (config values plus placement). So everything the operator does today — set, propose (places and accesses stay trusted keys), replace, clear, history, the merge gate's facts — keeps working unchanged. - **Today's form is kept.** A mergeable file that names no keys (ADR 0326 §3) still receives every key of the layer as it is said, placement included, and the line-end judgement still covers placement paths. ADR 0348 clause 11 lets a module that has not moved keep today's form, and without this the files of such modules (for example home-assistant on ace, whose layer holds `places`) would change. ## Not done, and why: `Settle` is unchanged The issue asks that `Settle` stop letting a settings layer replace a provider's served keys, and to stop and report if a live layer relies on it. One does: **influxdb on ace** has the layer `{"org": "zurag", "bucket": "zurag", ...}` while its manifest serves `influxdb-api` with the literal `org: mesh`, `bucket: default`; grafana on ace consumes `influxdb-api` and writes `dbName: ${bound:influxdb-api:bucket}` into its data source file. Removing the override would change grafana's file on ace from `zurag` to `default`. (mailu's `domain` and keycloak's `issuer` are served as `${setting:<same key>}`, so they would not change.) So `Settle` is left as it is; the coordinator decides how to move influxdb (for example serving `${setting:org}`/`${setting:bucket}` from the catalogue) before the override is removed. ## How it is proven - `TestADeclarationIsTheSameAfterPlacementMoves` (cmd, store test): a mesh seeded as the store holds it before 0097 (mesh-wide accesses, a machine's places with owner, accesses and a kept network on an adopted machine, a layer of placement only, a mergeable file of today's form) is migrated and every machine's declaration composed; the resources of the seeded modules are byte for byte the golden files written by the integration branch's controller at bfc79c6 from the same seed. Checked that it fails when today's form is dropped. - `TestPlacementMovesOutOfTheSettingsLayers` (inventory, store test): after 0097 no settings row holds a placement key; every layer reads back as before (`Layer`, `SettingsFor` whole); `set_at`/`set_by` unchanged; the moved layers are in the history with why; the moved layers still judge as composable. - `TestASettingRoutesPlacementBesideTheLayer` (inventory, store test): the route (set, read back whole, history whole, `KeepSettings`, clear) and the refusals (an undeclared directory, the machine's own path, a kept network on a converged machine, a mesh-wide kept network), with nothing written on a refusal. - `TestPlacementIsReadFromThePlacementOnly` (catalogue): a placement key among config values places nothing; `LayerOf` splits; today's mergeable file composes the same. - Existing tests: catalogue tests that built a layer with placement among `Values` now build it with `LayerOf` (same values, same assertions); nothing else changed. Run: `gofmt -l cmd internal examples` (empty), `go vet ./...`, `go test -race ./...` and `MESH_TEST_POSTGRES=… sh merge-check.sh` against a throwaway PostgreSQL 18: exit 0, every listed store test PASS (the three new ones added to `testdata/store-tests`). ## Left for other packages - K5 moves the remaining settings layers into mesh-config; placement stays here. - Whether a mesh-wide placement scope should exist at all (ADR 0348 clause 3 removes the mesh-wide module value for config, and says nothing of placement): kept as it is today so nothing changes. - Design 57 says the data moves run as verbs after the delivery; this package was briefed as a numbered migration, which runs at the controller's start in the same delivery. Composition is identical before and after, so the timing does not change what any machine is told. - Files touched that other agents also touch: `cmd/mesh-controller/modules.go` (4 lines in the settings history output) and `internal/inventory/catalogue.go` (settings functions only); T1 (#313) touches other parts of both. No change in `internal/catalogue/manifest.go`, `internal/builder` or `internal/broker`. ## Reviewer's checklist 1. Does what the issue's Expected and design 57's F2 say, nothing outside it: yes, except `Settle`, stopped as briefed because influxdb on ace relies on it (above). 2. Done-when proof as tests that fail before and pass now: the three store tests need migration 0097 and fail without it; the byte-identical test was checked to fail when composition changes. 3. Byte-identical composition: `TestADeclarationIsTheSameAfterPlacementMoves` against golden files from bfc79c6. 4. No secret anywhere; no hand-written database change; the schema change is migration 0097. Test fixtures write rows only to their own throwaway store. 5. Strict parsers: no new manifest or declaration field; the declaration is unchanged. 6. gofmt, go vet, go test ./... and merge-check.sh pass (commands above). 7. Words: placement, config value, controller, walk; issue and ADR numbers said with what they are.
mesh-admin added 6 commits 2026-10-14 00:46:30 +00:00
ADR 0348 clause 11: places, accesses and kept networks are placement, not config.
Today's mergeable files still take them as said, so nothing composes differently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings verb's path splits a layer it is given into its config values and
its placement, so what a person types still works; a layer reads back and goes
into the history whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The facts snapshot, the take preview and the settings history read a layer as it
was set; tests that place give the layer through LayerOf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Written by the integration branch's controller at bfc79c6 from the seed of
TestADeclarationIsTheSameAfterPlacementMoves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move every layer's placement out with migration 0097, and prove no machine is told anything new (issue 651)
mesh/delivery delivered
mesh/merge-gate head 912d76df25f8 merged into trunk 617881c3093d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 912d76df25f8 merged into trunk 617881c3093d — pass: its merge-check.sh passed
912d76df25
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Contributor

Delivery novox/mesh-controller@912d76df25f8 — delivered since 2026-10-14T01:10:27Z

Delivery plan — builds nothing: the change touches no module of the mesh's graph

Transitions

  • 2026-10-14 00:46 (new) → proposed (announced): novox/mesh-controller#316's head announced
  • 2026-10-14 00:50 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
  • 2026-10-14 00:50 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
  • 2026-10-14 01:01 ready → proposed (trunk moved): out of date: judged against trunk bfc79c64d9, the trunk is now 617881c3093d; judged again on a recheck or when it is merged
  • 2026-10-14 01:07 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
  • 2026-10-14 01:07 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
  • 2026-10-14 01:10 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
  • 2026-10-14 01:10 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@912d76df25f8` — **delivered** since 2026-10-14T01:10:27Z **Delivery plan** — builds nothing: the change touches no module of the mesh's graph **Transitions** - 2026-10-14 00:46 (new) → proposed (announced): novox/mesh-controller#316's head announced - 2026-10-14 00:50 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit - 2026-10-14 00:50 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail - 2026-10-14 01:01 ready → proposed (trunk moved): out of date: judged against trunk bfc79c64d989, the trunk is now 617881c3093d; judged again on a recheck or when it is merged - 2026-10-14 01:07 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit - 2026-10-14 01:07 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail - 2026-10-14 01:10 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move - 2026-10-14 01:10 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-admin left a comment
Author
Contributor

Verdict: APPROVE at 912d76df25

Independent review of package F2 (issue 651) against design 57 and ADR 0348 clause 11 / ADR 0349 clause 17.

What I ran in my own worktree of 912d76d, against a throwaway PostgreSQL over TCP (stopped afterwards):

  • gofmt -l cmd internal examples: empty. go vet ./...: clean.
  • MESH_TEST_POSTGRES=… sh merge-check.sh: exit 0, store-checked; TestADeclarationIsTheSameAfterPlacementMoves, TestPlacementMovesOutOfTheSettingsLayers and TestASettingRoutesPlacementBesideTheLayer PASS.
  • I regenerated the golden files myself: a worktree of bfc79c6 with only the test and its two helpers copied in, run with MESH_WRITE_GOLDEN=1. The result is identical to testdata/placement-moved/*.json.
  • Revert proof: at 912d76d with the non-test code put back to bfc79c6 (migration 0097 kept), the byte-identical test fails ("anchor leaves out … accesses "series"").
  • 912d76d merged with T1 (#313, def3cce): no conflict. It builds, go vet is clean, and the tests of internal/catalogue, internal/inventory and cmd/mesh-controller pass with the store.

What I checked and found right:

  • Migration 0097: the SQL is correct. The history copy is written before the move, the keys are moved and then deleted, set_at/set_by are kept, and an emptied layer is kept. The runner applies it in one transaction with its migration row, so a re-run is skipped and a failure keeps nothing. The number is free: feat/57 and main end at 0096, and no open pull request (#315, #314, #313, #297, #203, #182) adds a migration above 0095.
  • The settings verb still takes the three keys and routes them. KeepSettings (the merge gate's facts) splits a layer the same way. Every refusal goes through JudgeSettings with LayerOf. A placement row cannot exist without its settings row: set, clear and the cascades always touch both.
  • Today's form (asSaid in ApplySettings): I confirmed that home-assistant's layer on ace holds places and that its runtime-config names no keys. ADR 0349 clause 17 (the old form stays until a module moves) supports keeping it.
  • Settle: I confirmed that influxdb's layer on ace is org/bucket zurag, that the catalogue serves the literal mesh/default, and that grafana's data source reads ${bound:influxdb-api:bucket}. No provider serves a placement key and no file uses ${setting:places|accesses|networks}, so leaving overridden on Values only changes nothing. The split is issue 696.

Findings (none blocks this pull request; they can follow):

  1. cmd/mesh-controller/placement_moved_test.go:41-48: the seed has no mesh-wide places layer under a machine's (how precedence between scopes works after the move), and no module whose mergeable file names its keys (takes-settings, as keycloak's and searxng's do in the catalogue). Adding both to the seed (and regenerating at bfc79c6) would make the byte-identical proof cover them.
  2. internal/inventory/migrations/0097-placement-is-not-config.sql:12 and internal/catalogue/placement.go:37: ADR 0348 clause 11 also counts ports, expose, reach and endpoints as placement, and they stay in the settings layer. "The composer reads placement from here only" therefore says more than is built. Please say that only the three keys move, and name the issue that moves the rest.
  3. testdata/store-tests: this conflicts with F3 (#315), because both append a line (issue 622). Whichever merges second needs a one-line rebase.
Verdict: APPROVE at 912d76df25f858f52902d734cc9559eb65de75b1 Independent review of package F2 (issue 651) against design 57 and ADR 0348 clause 11 / ADR 0349 clause 17. What I ran in my own worktree of 912d76d, against a throwaway PostgreSQL over TCP (stopped afterwards): - `gofmt -l cmd internal examples`: empty. `go vet ./...`: clean. - `MESH_TEST_POSTGRES=… sh merge-check.sh`: exit 0, store-checked; TestADeclarationIsTheSameAfterPlacementMoves, TestPlacementMovesOutOfTheSettingsLayers and TestASettingRoutesPlacementBesideTheLayer PASS. - I regenerated the golden files myself: a worktree of bfc79c6 with only the test and its two helpers copied in, run with MESH_WRITE_GOLDEN=1. The result is identical to `testdata/placement-moved/*.json`. - Revert proof: at 912d76d with the non-test code put back to bfc79c6 (migration 0097 kept), the byte-identical test fails ("anchor leaves out … accesses \"series\""). - 912d76d merged with T1 (#313, def3cce): no conflict. It builds, `go vet` is clean, and the tests of internal/catalogue, internal/inventory and cmd/mesh-controller pass with the store. What I checked and found right: - Migration 0097: the SQL is correct. The history copy is written before the move, the keys are moved and then deleted, `set_at`/`set_by` are kept, and an emptied layer is kept. The runner applies it in one transaction with its `migration` row, so a re-run is skipped and a failure keeps nothing. The number is free: feat/57 and main end at 0096, and no open pull request (#315, #314, #313, #297, #203, #182) adds a migration above 0095. - The settings verb still takes the three keys and routes them. KeepSettings (the merge gate's facts) splits a layer the same way. Every refusal goes through JudgeSettings with LayerOf. A placement row cannot exist without its settings row: set, clear and the cascades always touch both. - Today's form (`asSaid` in ApplySettings): I confirmed that home-assistant's layer on ace holds `places` and that its `runtime-config` names no keys. ADR 0349 clause 17 (the old form stays until a module moves) supports keeping it. - Settle: I confirmed that influxdb's layer on ace is `org`/`bucket` `zurag`, that the catalogue serves the literal `mesh`/`default`, and that grafana's data source reads `${bound:influxdb-api:bucket}`. No provider serves a placement key and no file uses `${setting:places|accesses|networks}`, so leaving `overridden` on Values only changes nothing. The split is issue 696. Findings (none blocks this pull request; they can follow): 1. cmd/mesh-controller/placement_moved_test.go:41-48: the seed has no mesh-wide `places` layer under a machine's (how precedence between scopes works after the move), and no module whose mergeable file names its keys (`takes-settings`, as keycloak's and searxng's do in the catalogue). Adding both to the seed (and regenerating at bfc79c6) would make the byte-identical proof cover them. 2. internal/inventory/migrations/0097-placement-is-not-config.sql:12 and internal/catalogue/placement.go:37: ADR 0348 clause 11 also counts `ports`, `expose`, `reach` and `endpoints` as placement, and they stay in the settings layer. "The composer reads placement from here only" therefore says more than is built. Please say that only the three keys move, and name the issue that moves the rest. 3. testdata/store-tests: this conflicts with F3 (#315), because both append a line (issue 622). Whichever merges second needs a one-line rebase.
mesh-admin merged commit 71362c196d into feat/57-config-and-provisioning 2026-10-14 01:09:56 +00:00
mesh-admin deleted branch pkg/57-f2-placement-facts 2026-10-14 01:09:56 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-controller#316