From bc373797c7ec4a4abfc243f8987199263baef69d Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:16:15 +0200 Subject: [PATCH 1/5] Issue 076 opened: a served fact made at first start cannot be served; ADR 0097's refusal of an undeclared base is live --- ...-vendor-image-is-a-declared-build-input.md | 9 ++-- 03-DESIGN/01-to-be/18-building-a-module.md | 4 +- .../01-diagnosis.md | 3 ++ .../00-report.md | 42 +++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md diff --git a/02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md b/02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md index 7b0131c..2237aea 100644 --- a/02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md +++ b/02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md @@ -36,10 +36,11 @@ image the manifest did not declare is refused before the build, naming the image its own stages, declared arguments and `scratch` are not fetches. An unpinned vendor image is refused: a tag is what somebody else can move. -A recipe whose `FROM` names an undeclared base is **said, not yet refused**: the mesh's own images -— the control plane's, the builder's, the tool runtime's — start from a public base and declare -none, and refusing those refuses genesis. They declare their bases next; until then every build -names the undeclared base and the remedy. +A recipe whose `FROM` names an undeclared base was at first said, not refused: the mesh's own +images — the control plane's, the tool runtime's, the route proxy's — started from a public base +and declared none, and refusing those refuses genesis. *Amended the same day:* those three declare +their bases now, and an undeclared `FROM` is refused like an undeclared copy. The builder's own +image and the examples are built by `make`, not by the mesh, and take arguments with defaults. The package half of the issue is not decided here: the mesh's package registry already proxies the public one, and the failure the report saw has to be run again to be placed. diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index 752e14c..0076a36 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -221,8 +221,8 @@ and nothing uploaded on a second copy. entry is a module's artifact or an image published elsewhere, pinned by digest, read from one build argument; the image is copied into the mesh's registry before the build and the recipe is handed the copy. A recipe whose `COPY --from` names a registry image the manifest did not declare -is refused before the build, naming it and the remedy; an undeclared `FROM` is said, not yet -refused, because the mesh's own images start from a public base and declare none. *How it is +is refused before the build, naming it and the remedy, and so is an undeclared `FROM`: the mesh's +own images declare the bases they start from. *How it is checked:* builder tests on a declared and an unpinned vendor image, and a recipe test on what counts as a copy and what as a base. diff --git a/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md b/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md index 8621a08..7d1437f 100644 --- a/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md +++ b/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md @@ -27,3 +27,6 @@ sidecar beds proved a sidecar alone, which the module beds prove whole; the mini grant beds proved a grant with a second store beside the foundation's, which the grant bed and the vault bed prove against the catalogue. Retired, with their scenarios. Two remain declared: route-forwarding, which needs the certificate authority beside the proxy, and the large mesh test. + +*Route-forwarding's conversion is blocked:* the catalogue's authority cannot be raised as written +([issue 076](../076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md)). 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 new file mode 100644 index 0000000..4494234 --- /dev/null +++ b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md @@ -0,0 +1,42 @@ +--- +status: located +opened: 2026-09-21 +located-in: [mesh-catalog modules/step-ca, mesh-controller internal/catalogue (serves)] +fixed-by: +amended-design: +--- + +# A served fact made at first start cannot be served, so the catalogue's authority cannot start + +## Symptom, as observed + +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. + +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 +consumer of `acme-ca` is given `${bound:acme-ca:root}` from the provider's `serves`, which is +written in the manifest before anything runs. + +## Why it matters beyond this instance + +- **Two kinds of secret the vocabulary does not distinguish.** A value the mesh may invent (a + password) and a value only the module can produce (a key pair, a certificate) are both + "own secrets", and the mesh invents both. +- **A served fact that exists only after first start** has no way into a binding. Anything a + module generates and its consumers must trust — a root, a public key, a fingerprint — is in + the same position. +- Every consumer of `acme-ca`, which today is the route proxy, is blocked with it. + +## What would close it + +Either a module may say a secret is *made by the module* — the mesh reserves the name, the +module writes the value once, the mesh takes custody of it and delivers it where it is bound — +or a served fact may be *contributed at run time* by the provider's runtime rather than written +in its manifest. The first is the smaller change and covers the root certificate; the second is +what a fingerprint or a public key wants. Decided, then the authority raised in the lab beside +the proxy, which is the route-forwarding bed's conversion. From 252c6042e8a72eca8204f8abe24500feaf9720d9 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:27:32 +0200 Subject: [PATCH 2/5] ADR 0098: a fact a provider makes at first start is fetched from it; issue 076 resolved; design 08 amended; 074 down to one bed --- ...makes-at-first-start-is-fetched-from-it.md | 58 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/08-connectivity.md | 12 +++- .../01-diagnosis.md | 3 + .../00-report.md | 8 +-- .../01-diagnosis.md | 11 ++++ 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md create mode 100644 04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md 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 new file mode 100644 index 0000000..6d0b729 --- /dev/null +++ b/02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md @@ -0,0 +1,58 @@ +--- +topic: the tiers +status: accepted +date: 2026-09-21 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0085-a-secret-is-a-provision.md +--- + +# 98. A fact a provider makes at first start is fetched from it, not carried in its manifest + +## Context + +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 +([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`, +which is written in the manifest before anything runs. + +## Considered Options + +1. **A secret the module makes**, with the mesh taking custody once the file exists. Rejected + for now: a node would have to send a value up to the mesh, which no channel does today, and + a root key is the one thing the mesh has no reason to hold. +2. **A served fact the provider contributes at run time.** Rejected for now: the same new + channel, for a fact that is not secret at all. +3. **The consumer fetches it from the provider**, over the mesh network, through a gate before + the thing that needs it starts. Adopted. + +## Decision + +A provider's `serves` names where a fact made at first start can be fetched — the authority +serves its root at a path beside its ACME directory — and a consumer fetches it in a `run-once` +step declared before the resource that needs it, from the provider's bound address. The mesh +network is where the fetch happens, which is what makes fetching without a prior trust +acceptable: it is the network the mesh itself authenticates. The mesh mints only what it can +make: the authority's password. The root key stays where it was made. + +## Consequences + +The catalogue's authority starts, and the proxy that requires it trusts what it fetched. What +got harder: a consumer of such a fact carries one more resource, the gate that fetches it, and +a fact that changes after first start is refetched only when the declaration changes. + +## 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. + +## References + +- [issue 076](../04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md) +- [ADR 0053](0053-a-step-that-runs-on-a-schedule.md), [ADR 0085](0085-a-secret-is-a-provision.md) +- [`03-DESIGN/01-to-be/08-connectivity.md`](../03-DESIGN/01-to-be/08-connectivity.md) diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 3b098db..63d6133 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -115,6 +115,7 @@ python3 00-META/checks/index.py fail if stale - **0092** — [An operator delivers a pair credential, and the mesh never replaces it](0092-an-operator-delivers-a-pair-credential.md) - **0094** — [A module may hold several secrets from one provider, each a pair of its own](0094-a-module-may-hold-several-secrets-from-one-provider.md) - **0095** — [The control plane is the way to ask a module](0095-the-control-plane-is-the-way-to-ask-a-module.md) +- **0098** — [A fact a provider makes at first start is fetched from it, not carried in its manifest](0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md) ### What runs on them, and how it gets there diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index e5871da..9badb7e 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -7,8 +7,9 @@ code: - mesh-controller internal/identity/authority.go - mesh-host internal/identity/serving.go - mesh-host internal/apply (the service that reflects a rule set) -updated: 2026-09-09 +updated: 2026-09-21 decisions: + - 02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md - 02-DECISIONS/0005-the-node-host.md - 02-DECISIONS/0004-a-node-and-how-it-joins.md - 02-DECISIONS/0007-connectivity.md @@ -557,6 +558,15 @@ fingerprint in its token ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-j so nothing needs the CA before membership. It certifies internal names afterwards, and that is all it does. +**The internal authority makes its own root at first start, and a consumer fetches it** +([ADR 0098](../../02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md)). +The mesh mints the authority's password and nothing else of its: a root certificate and its key +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. + ### What was built *2026-08-31.* diff --git a/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md b/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md index 7d1437f..f2313f7 100644 --- a/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md +++ b/04-ISSUES/074-a-mesh-test-wears-a-catalogue-modules-name/01-diagnosis.md @@ -30,3 +30,6 @@ route-forwarding, which needs the certificate authority beside the proxy, and th *Route-forwarding's conversion is blocked:* the catalogue's authority cannot be raised as written ([issue 076](../076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md)). + +*Later the same day.* Route-forwarding converted, with the authority beside the proxy +(ADR 0098). One bed remains declared: the large mesh test, with its three fixtures. 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 4494234..a39ae2b 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 @@ -1,9 +1,9 @@ --- -status: located +status: resolved opened: 2026-09-21 -located-in: [mesh-catalog modules/step-ca, mesh-controller internal/catalogue (serves)] -fixed-by: -amended-design: +located-in: [mesh-catalog modules/step-ca, mesh-catalog modules/route-proxy] +fixed-by: ADR 0098; mesh-catalog multiple-fixes (the authority makes its own root and serves it; the proxy fetches it through a gate); proven by the route-forwarding bed +amended-design: 03-DESIGN/01-to-be/08-connectivity.md --- # A served fact made at first start cannot be served, so the catalogue's authority cannot start 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 new file mode 100644 index 0000000..b066938 --- /dev/null +++ b/04-ISSUES/076-a-served-fact-made-at-first-start-cannot-be-served/01-diagnosis.md @@ -0,0 +1,11 @@ +# Diagnosis — 2026-09-21 + +1. The certificate bed already raised the same authority image with no root supplied, and it made + its own root and issued within a second. The manifest's three minted "secrets" were not needed + by the authority; they were needed by the consumer, which was handed the root as a served fact. +2. Of the three ways to get a fact made at first start to a consumer, two need a channel from a + node up to the mesh that does not exist. The third needs nothing new: the provider serves the + fact at a path, and the consumer fetches it over the mesh network in a gate before it starts. + +**Located in:** the two manifests. Decided in +[ADR 0098](../../02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md). From 75af72ca27f8e87ca33ed556d186e6d257bb4dcd Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:32:03 +0200 Subject: [PATCH 3/5] ADR 0096: the copy is proven against the public hub by the genesis bed --- .../0096-an-upstream-image-is-copied-between-registries.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md b/02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md index ac6a913..69265d6 100644 --- a/02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md +++ b/02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md @@ -54,7 +54,10 @@ A test raises a fake upstream registry serving an index over two platforms behin challenge, and a fake mesh registry that records what arrives: every blob of both platforms arrives once, two manifests and the index are put under their digests, the reference returned pins the index under the module's repository, and a second copy uploads nothing. A reference -test reads names the way a runtime does. +test reads names the way a runtime does. *Proven against the real thing the same day:* the genesis +bed built the tool runtime through the mesh's builder with its node base copied out of the public +hub into the mesh's registry by this code — after one finding the fake could not give: the builder +ran on the default bridge, where loopback is not the machine, and now runs on the host network. ## References From 6d3cb609492d29361f28334a9b2e87ead4db1583 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:44:24 +0200 Subject: [PATCH 4/5] Issue 076: the route-forwarding bed proves ADR 0098; what the run taught about the overlay --- .../01-diagnosis.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 b066938..518a7b8 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 @@ -9,3 +9,17 @@ **Located in:** the two manifests. Decided in [ADR 0098](../../02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md). + +**Proven** the same day: the route-forwarding bed raises the authority, the proxy and a consumer +from the catalogue on one node and serves a public name through the proxy. Two things the run +taught, both about the bed rather than the decision: + +- The authority certifies itself for the machine's private-network address, which is what a + consumer on any node dials. A machine raised from the foundation bundle has no such address + until it is placed on the overlay, so a bed must place it first — as a hub of one, the way a + 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. From 6bc9df4b49e9cb72c6018f203aa2f0156d5e9e58 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 21 Sep 2026 22:55:11 +0200 Subject: [PATCH 5/5] Review corrections: 076 and ADR 0098 say what the authority could and could not do; issues 077 (a fetched fact is fetched once) and 078 (secret accept takes any name) opened --- ...makes-at-first-start-is-fetched-from-it.md | 11 +++--- 03-DESIGN/01-to-be/08-connectivity.md | 7 ++-- .../00-report.md | 11 +++--- .../01-diagnosis.md | 10 ++++-- .../00-report.md | 35 +++++++++++++++++++ .../00-report.md | 32 +++++++++++++++++ 6 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 04-ISSUES/077-a-fact-fetched-at-first-start-is-fetched-once/00-report.md create mode 100644 04-ISSUES/078-a-delivered-secret-is-accepted-under-any-name/00-report.md 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.