Look at what a container mounts directly, accept a pre-upgrade label, and write the genesis secret without a newline

Review of the first cut found four things.

A directory mounted into a container is no longer looked inside, not even
for the files this host wrote there. The controller records every
provider's received and contributions file as a plain file under a mounted
directory, so folding those in would have recreated the route proxy — which
re-reads its routes live, by design — on every route change, and killed
every provisioner sidecar, which polls what it receives, mid-reconcile on
every grant. Whether a service reads a file under its directory once or
watches it is the service's; restart-on is how a module says "once", and it
stays the opt-in. Env-files and files mounted directly remain by content.

Genesis wrote the superuser secret as `value\n`; `secret accept` strips the
line ending by design, so the postgres module declared `value` — and with
a mounted file's content in the spec, phase three would have recreated the
store it meant to adopt in place, with the temporary control plane
connected to it. Genesis now writes the value alone. readCredentialFile
tolerated both endings already. Pinned with the bytes the genesis code
path writes, then the module's declaration of the same container: it must
reconcile.

A container carrying a label from before the host folded in what it reads
is accepted rather than recreated, when that label matches the spec as it
used to be computed: what it reads is recorded then, a change is caught
from that record from the next apply on, and the label is renewed at the
next genuine recreate. Recreating them all would have been a restart storm
across the mesh in declaration order, the store first. The trade-off is
stated in the code: a container already stale at upgrade time is not
caught, and could not have been either way.

The record of what a container read is looked up by its name when its
declared id has none — the bundle's `store` becomes `postgres.server` for
the same container — so a change on the day it is adopted still names the
file. The by-target lookup takes the most recently applied record, since
the bundle's record for the same target is never removed by the mesh's.

novox/hq 04-ISSUES/103
This commit is contained in:
2026-09-23 23:40:27 +02:00
parent c60228e719
commit 982b84310e
6 changed files with 329 additions and 63 deletions
+8 -7
View File
@@ -785,13 +785,14 @@ type Container struct {
// while every check passes because the file on disk is right. The host recreates the container
// when one of these resources changed this pass, even if the spec matches.
//
// What a running container reads at creation — its env-files, a file mounted into it, and the
// files the host wrote under a directory mounted into it — is part of its spec by content
// since novox/hq 04-ISSUES/103, and needs no naming here. RestartOn is for what the spec
// cannot see: a resource the container reflects without reading it directly, a step it
// consumes the result of. On a run-once step it means *run again*: a step that fetches a fact
// from a provider names the binding it reads, and is run again when the provider moved
// (novox/hq ADR 0099).
// What a running container reads at creation — its env-files, and a file mounted into it
// directly — is part of its spec by content since novox/hq 04-ISSUES/103, and needs no naming
// here. A directory mounted into it is NOT looked inside, not even for files the host wrote
// there: whether a service reads such a file once or watches it live is the service's, and
// RestartOn is how a module says "once, at start" — a config the host renders under the
// module's state directory, a step whose result it consumes. On a run-once step it means *run
// again*: a step that fetches a fact from a provider names the binding it reads, and is run
// again when the provider moved (novox/hq ADR 0099).
RestartOn []string `json:"restart-on,omitempty"`
// RunOnce marks a container the host runs to completion rather than leaves running: a step,