diff --git a/02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md b/02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md index 6d0b729..bad2871 100644 --- a/02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md +++ b/02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md @@ -13,8 +13,8 @@ extends: 02-DECISIONS/0085-a-secret-is-a-provision.md The catalogue's certificate authority declared its root certificate, its root key and that key's password as its own secrets, and told the container to initialise from them. The mesh mints an -own secret as random bytes, and random bytes are not a certificate: as written the authority -could not start, and no bed had raised it +own secret nobody delivers as random bytes, and random bytes are not a certificate: issued, the +authority could not start; only an operator hand-making its root could raise it ([issue 076](../04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md)). The authority can make its own root at first start. What it could not do then was tell the mesh what that root is: a consumer was given `${bound:acme-ca:root}` from the provider's `serves`, @@ -48,8 +48,11 @@ a fact that changes after first start is refetched only when the declaration cha ## How it is checked The route-forwarding bed installs the authority, the proxy and a consumer from the catalogue and -asserts a routed name is served through the proxy; the proxy cannot start without the root its -gate fetched. The catalogue-wide manifest test parses both manifests. +asserts a routed name is served through the proxy. The proxy refuses to start on a bundle that is +not a certificate, so the name being served proves the gate fetched one; the gate itself refuses +a body that is not a certificate. That the proxy obtains a certificate from this authority through +that root is the certificate bed's proof, against the same authority with the same proxy. The +catalogue-wide manifest test parses both manifests. ## References diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index 9badb7e..8aa3640 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -564,8 +564,11 @@ The mesh mints the authority's password and nothing else of its: a root certific are things only the authority can make, and a served fact written in a manifest cannot carry what does not exist until the authority has run. So the authority serves its root at a path beside its ACME directory, and the proxy that requires it fetches that root over the mesh network in a -run-once step before it starts. *How it is checked:* the route-forwarding bed installs the -authority, the proxy and a consumer from the catalogue and asserts the routed name is served. +run-once step before it starts. The step is run once per declaration: a root that changes +after first start is fetched again only when the declaration changes +([issue 077](../../04-ISSUES/077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md)). +*How it is checked:* the route-forwarding bed installs the authority, the proxy and a consumer +from the catalogue and asserts the routed name is served. ### What was built diff --git a/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md index a39ae2b..2460cff 100644 --- a/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md +++ b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md @@ -12,10 +12,13 @@ amended-design: 03-DESIGN/01-to-be/08-connectivity.md The catalogue's certificate authority module declares its root certificate, its root key and that key's password as its own secrets, and writes each into a file the container is told to -initialise from. The mesh mints an own secret as random bytes. Random bytes are not a -certificate: as written, the authority cannot initialise, and no bed has ever raised it — the -whole-mesh bed that names it has not run since it was converted. Found while converting the -route-forwarding bed to the catalogue's proxy, which requires the authority beside it. +initialise from. An own secret nobody delivers is minted by the mesh as random bytes, and random +bytes are not a certificate: issued that way, the authority cannot initialise. It could be +raised by an operator making a root with openssl and delivering all three through `secret +accept` — the whole-mesh bed did exactly that, and has not run since it was converted — but a +module that only starts once a person has hand-made its key material is not a module a mesh +can raise. Found while converting the route-forwarding bed to the catalogue's proxy, which +requires the authority beside it. The authority can make its own root at first start — the certificate bed raises it that way and it issues within a second. What it cannot do then is tell the mesh what that root is: a diff --git a/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md index 518a7b8..376a09a 100644 --- a/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md +++ b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md @@ -20,6 +20,10 @@ taught, both about the bed rather than the decision: real first node is. - With the overlay's networking and the three modules in **one** push, the proxy's fetch of the roots timed out at the private-network address; with the overlay converged first and the - modules pushed after, it passes. Whether that was the order of application within a push or - the filter closing the interface until it was derived was not isolated. A consumer whose first - start dials a provider assumes the provider's network is already there; the bed makes it so. + modules pushed after, it passes. The order between modules is not the cause: the controller + applies a node's providers before its consumers. The overlay interface and the filter that + admits it were not there yet, and the gate, as first written, tried once with no timeout — a + fetch that hangs holds the node's whole apply. The gate now retries with a timeout and refuses + a body that is not a certificate. A consumer whose first start dials a provider still assumes + the provider's network exists; a fact fetched once per declaration is + [issue 077](../077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md). diff --git a/04-ISSUES/077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md b/04-ISSUES/077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md new file mode 100644 index 0000000..727ad05 --- /dev/null +++ b/04-ISSUES/077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md @@ -0,0 +1,35 @@ +--- +status: open +opened: 2026-09-21 +located-in: [mesh-host internal/apply (run-once marker), mesh-catalog modules/route-proxy] +--- + +# 077 — A fact fetched at first start is fetched once per declaration + +## Symptom + +A consumer fetches a fact its provider made at first start through a run-once step +([ADR 0098](../../02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md)): +the route proxy fetches the certificate authority's root before it starts. The host runs a +run-once step once per declaration digest. When the authority is re-initialised — its state +wiped, or the module moved to another node, where it makes a new root — the proxy's declaration +is unchanged, so the step does not run again. The proxy keeps the old root, refuses the new +authority's certificates, and its own healing path, keyed on the root it holds, never fires. + +Observed by reading the apply loop and the proxy, not from an incident. No bed re-keys an +authority. + +## Why it matters beyond the instance + +Any fact a provider makes at first start has the same shape: the consumer's declaration does not +change when the provider's fact does. A run-once step cannot say "again when the provider +changed", and a restart trigger is not allowed on a run-once step (ADR 0053), so there is no +declarative remedy today. + +## What would close it + +Either the run-once marker includes something of the provider's — the provider's declaration +digest, or an epoch the mesh raises when a provider is re-issued or moved — or the gate is not +run-once but a validator that runs before every start of the service and is cheap when nothing +changed. Decided, then proven by a bed that re-keys the authority and watches the proxy trust the +new root. diff --git a/04-ISSUES/078-a-delivered-secret-is-accepted-under-any-name/00-report.md b/04-ISSUES/078-a-delivered-secret-is-accepted-under-any-name/00-report.md new file mode 100644 index 0000000..6377996 --- /dev/null +++ b/04-ISSUES/078-a-delivered-secret-is-accepted-under-any-name/00-report.md @@ -0,0 +1,32 @@ +--- +status: open +opened: 2026-09-21 +located-in: [mesh-controller internal/inventory (secrets), mesh-controller cmd (secret accept)] +--- + +# 078 — A delivered secret is accepted under any name + +## Symptom + +`secret accept ` stores a value for a module under a name it does not +check against the module's manifest. A name the manifest no longer declares — an own secret that +became a requirement kept in the vault, or a name that never existed — is stored silently. The +row is dead: nothing reads it, the vault mints a value instead, and the operator believes they +delivered a secret the module is not using. + +Found by review, not by a run: the whole-mesh bed delivered four such names after their modules +moved to the several-secrets vocabulary ([ADR 0094](../../02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md)), +and nothing said so. + +## Why it matters beyond the instance + +A silent acceptance is the shape of failure the mesh is built to refuse: an operator's action +that changes nothing and reports success. It hides every stale delivery, in beds and in operation +alike. + +## What would close it + +Acceptance is refused for a name the module's current manifest does not declare as an own +secret, with the names it does declare in the refusal. A unit test delivers under an undeclared +name and expects the refusal; the whole-mesh bed then fails loudly if a delivery goes stale +again.