diff --git a/03-DESIGN/01-to-be/20-writing-a-module.md b/03-DESIGN/01-to-be/20-writing-a-module.md index d8af1b9..f103d43 100644 --- a/03-DESIGN/01-to-be/20-writing-a-module.md +++ b/03-DESIGN/01-to-be/20-writing-a-module.md @@ -5,8 +5,9 @@ code: - mesh-catalog modules/showcase - mesh-controller internal/builder - mesh-sdk src -updated: 2026-09-15 +updated: 2026-09-21 decisions: + - 02-DECISIONS/0053-a-step-that-runs-on-a-schedule.md - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md - 02-DECISIONS/0040-what-a-module-is.md - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md @@ -178,3 +179,22 @@ knows. Putting the Plex client anywhere else is the shared-library disease with the builder records that as the build edge — but *a mesh that can rebuild a commit and get a different library* is a real change from how everything else here works, and it should be a decision rather than a consequence. + +## A file and the thing that reads it are guarded by a gate + +*Written 2026-09-21, from resolving [04-ISSUES/066](../../04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/00-report.md).* + +The apply is not a transaction: every resource is attempted, every failure reported, and only a +failed gate stops what follows it ([ADR 0053](../../02-DECISIONS/0053-a-step-that-runs-on-a-schedule.md)). +So a push can half-happen, and the pairing that matters is a configuration file beside the +service that reads it. A module keeps that pair correct by declaring them in this order: the file; +a `run-once` container that validates it; the service, with `restart-on` naming the file. A file +the validator refuses reaches the disk and nothing else — the gate fails, the service after it is +left as it was, and the machine reports the push failed. The service never serves what the +validator refused. That is the grouping, made from what the manifest already has; no primitive +withholds a file from the disk, and a service that reads its file live rather than at start is the +one shape this does not protect, and should not be written. + +*How it is checked:* the lab's coupled-pair spike declares exactly this pair, pushes a refused +file, and asserts the file on disk is the new one, the service serves the old one, and the machine +reports the push failed. diff --git a/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/00-report.md b/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/00-report.md index 81e2d7a..d27ca3d 100644 --- a/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/00-report.md +++ b/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/00-report.md @@ -1,9 +1,9 @@ --- -status: located +status: resolved opened: 2026-09-20 located-in: [mesh-host internal/apply, mesh-controller internal/inventory (node_report)] -fixed-by: -amended-design: +fixed-by: nothing new — a run-once validator before a service that restarts on the file is the grouping (ADR 0053); the lab's coupled-pair spike proves the service never serves what the validator refused; the visible half was issue 065 +amended-design: 03-DESIGN/01-to-be/20-writing-a-module.md --- # 066 — A partly applied declaration leaves a mixed state, with no rollback diff --git a/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/01-diagnosis.md b/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/01-diagnosis.md index c9c4adf..58455e6 100644 --- a/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/01-diagnosis.md +++ b/04-ISSUES/066-a-partly-applied-declaration-leaves-a-mixed-state-with-no-rollback/01-diagnosis.md @@ -18,3 +18,12 @@ the grouping question alone; it closes when a coupled pair that must not be half-applied is found in a module, and the declaration gains a way to say so — or when enough modules have run that the absence is evidence. The visibility half is done. + +*2026-09-21, later.* The pairing was made on purpose, in a lab spike: a config file, a run-once +validator, a service that reads the file once at start and restarts on it. The second push changed +the file and made the validator refuse it. Observed: the file on disk was the new one, the service +served the old one, and the machine reported the push failed. The half-state exists — and it is +harmless, because the gate held: the refused configuration never reached the service. The grouping +the report asked for is this order of three resources, made from what a manifest already has. The +one shape it does not protect is a service that reads its file live, which a module should not +write. Resolved; the pattern is in design 20 with the spike as its check.