Merge pull request 'Multiple fixes: status vocabulary aligned, 065/026/070/007 resolved, 066/069/049/046 located, 064 diagnosed' (#64) from feat/multiple-fixes into main

This commit was merged in pull request #64.
This commit is contained in:
2026-09-21 19:23:34 +02:00
48 changed files with 440 additions and 53 deletions
@@ -1,11 +1,12 @@
---
status: diagnosing
status: resolved
opened: 2026-08-23
located-in: [hal]
located-in: [hal (the instance), mesh-host internal/bootstrap (preflight asks the daemon), mesh-controller internal/catalogue (capabilities)]
fixed-by:
- "the instance only: PR #962 — incus hook. Merged, ran on one node in 6s of a 60s budget; pipeline #6832 green in 48s. The class remains open."
- "partly, on the mesh: mesh-host 73c010e, 9d8239a — preflight and the profile detectors ask the daemon, not the package (installed-but-broken reports absent). The class question the report asks of hal is not answered"
amended-design:
- "partly, on the mesh: mesh-host 73c010e, 9d8239a — preflight and the profile detectors ask the daemon, not the package (installed-but-broken reports absent)"
- "the class, on the mesh: a manifest declares capabilities, an action declares verify (ADR 0005), and a module's own assertions are the lab's verdict (design 01). The old arrangement is replaced module by module, so its count is never taken"
amended-design: 03-DESIGN/01-to-be/01-end-to-end-testing.md
---
# 007 — An installed package is not an available capability
@@ -0,0 +1,20 @@
# Diagnosis — 2026-09-21
1. The instance was fixed in the arrangement being replaced, by a hook, and verified by hand; the
report's remaining question was about the class: does a module declare a capability — the
outcome — separately from the package that provides it, and how many other packages sit in the
same state.
2. In the mesh the class is answered twice over, by design rather than by hook. A module's
manifest declares `capabilities` — what the machine must be able to do — and the host's
preflight and profile detectors ask the daemon, not the package, so an installed-but-broken
runtime reports absent. And an action a declaration runs carries `verify`, which the host
treats as the read-back and the idempotency check both: "is it there" is asked of the outcome,
never inferred from the step ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). The lab
design goes further and makes a module's own assertions the verdict on every delivery
([design 01](../../03-DESIGN/01-to-be/01-end-to-end-testing.md)).
3. The count of other packages in that state in the old arrangement was never taken, and will not
be: the old arrangement is being replaced module by module, and each module that crosses over
declares what it needs and is proven in the lab.
**Located in:** the arrangement being replaced, for the instance; the mesh, for the class, where
it is answered by `capabilities` on the manifest, `verify` on an action, and the lab's verdict.
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-08-28
located-in: [mesh-lab, mesh-host]
fixed-by:
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-08-29
located-in: [mesh-host, mesh-control]
fixed-by:
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-08-30
located-in: [mesh-host]
fixed-by: mesh-host — apply attempts every resource and reports every failure
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: mesh-control df62bb5
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: mesh-control 0af3ea1
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: mesh-control 122680b
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-lab]
fixed-by: mesh-lab 3503ad9
@@ -1,9 +1,9 @@
---
status: located
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: partly — mesh-controller f5b03e1 declares the data directories; the gate refusing a container mount the module never declared (53eb000) was withdrawn in 83c6a2f and nothing replaces it
amended-design:
fixed-by: mesh-controller f5b03e1 (the fourteen declared); ADR 0091 and mesh-controller feat/multiple-fixes (the check, back, with the three declarations — the socket by capability, the operator's by accesses)
amended-design: 03-DESIGN/01-to-be/18-building-a-module.md
---
# 026 — The data directories are mounted and never declared
@@ -0,0 +1,14 @@
# Diagnosis — 2026-09-21
1. The catalogue was read again for mounts no resource declares: twenty-four remained, in two
kinds only. Nine media modules mount the operator's library, and every one of those paths is
already in the module's `accesses` — the vocabulary [ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md)
gave exactly this. Four modules mount the container runtime's socket, and every one of them
declares the `container-runtime` capability.
2. So the field the report said would have to be invented already exists twice over, and the
check that was withdrawn needed only to read both: an access is a declared path, and a
capability declares the facility it grants.
**Located in:** the catalogue's parser. The check is back, refuses an undeclared mount naming the
path and the three remedies, and a test parses the whole catalogue. Decided in
[ADR 0091](../../02-DECISIONS/0091-a-mount-is-declared-three-ways.md).
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control, mesh-host]
fixed-by: mesh-control 1f5b70a, 41f7c51; mesh-host b91342a
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: mesh-control be62f49; mesh-lab f85dbb0
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: mesh-control 38d4e77
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-04
located-in: []
located-in: [mesh-host internal/apply (restart-on), mesh-catalog]
fixed-by: mesh-host aa441ba (restart-on); mesh-catalog 550393b (runtime config as a mergeable file + restart-on); proven by mesh-lab runtime-restart-on-config.test.ts
amended-design:
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-02
located-in: []
located-in: [mesh-host internal/apply (file create-once), mesh-host examples]
fixed-by: ADR 0087; mesh-host #16 (a file resource may say create-once; kept, never corrected); proven by the apply tests and the vault bed
amended-design: 03-DESIGN/01-to-be/18-building-a-module.md
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-10
located-in: []
located-in: [mesh-host cmd/mesh-bootstrap, mesh-lab test/integration/genesis.ts]
fixed-by: mesh-host cmd/mesh-bootstrap (ADR 0067); mesh-lab genesis.ts is the one description of genesis both beds call. Still open: a running mesh cannot say it was raised by the installer
amended-design:
---
@@ -2,7 +2,7 @@
status: resolved
opened: 2026-09-12
resolved: 2026-09-12
located-in: []
located-in: [mesh-controller (the verb existed)]
fixed-by: nothing — the capability already existed and the wrong verb was used
amended-design:
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-13
located-in: []
located-in: [mesh-host internal/apply (container spec digests)]
fixed-by: mesh-host e7f94e0 (restart-on digests folded into the container spec, a standing comparison); unit-tested in apply_test.go, no lab assertion yet
amended-design:
---
@@ -1,7 +1,7 @@
---
status: open
status: located
opened: 2026-09-14
located-in: []
located-in: [mesh-controller internal/builder (upstream artifacts)]
fixed-by:
amended-design:
---
@@ -0,0 +1,17 @@
# Diagnosis — 2026-09-21
1. What was established stands: the failure is in going through a machine's image store, whose
newer store keeps an index and refuses to push a single platform out of it. Every variant of
pull-then-push was tried and failed the same way.
2. The first open question answers the other two. A copy between registries never needs a
platform, because it moves what is there — the index and every manifest it names, or one
manifest if the module says so. The registry API is enough for it: read the index, read each
manifest, mount or upload each blob by digest, put the manifests and then the index under the
module's repository. No image store is involved and the runtime's behaviour stops mattering.
3. Whether the mesh mirrors an index or a platform is then a choice the copy can offer rather
than a limitation; today every machine on one mesh is the same architecture, and that
assumption is now written down here rather than nowhere.
**Located in:** the builder's upstream-artifact step. Not fixed here: a registry-to-registry copy
is a few hundred lines against the registry API and is proven only against a real registry
serving a real index, which is a lab run of its own.
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-14
located-in: []
located-in: [mesh-controller (the derived filter)]
fixed-by: mesh-controller c8d8211 (forward chain drops by default, never closes ssh), 25e42b3; proven by one-node-mesh.test.ts (a machine filters exactly what its modules declared)
amended-design:
---
@@ -1,7 +1,7 @@
---
status: open
status: located
opened: 2026-09-14
located-in: []
located-in: [mesh-controller cmd/mesh-controller, mesh-tools (the request contract)]
fixed-by:
amended-design:
---
@@ -0,0 +1,18 @@
# Diagnosis — 2026-09-21
1. The refusal is the scope working as designed: a module's broker account covers what it emits
and consumes and nothing else. A tool call needs a reply queue the caller creates and a publish
to the serving module's request queue, and no declared scope grants either.
2. Of the three callers the report names, two already have an account with the right shape. The
controller holds an admin connection to the broker, and an agent acting for an operator runs
through the controller. Another module is the only caller that would need something minted.
3. The honest first step is therefore the third option in the report: the controller is the way
in. A `mesh-controller ask <module> <tool>` needs no new account, routes every question
through one process — which is where an audit of who asked what belongs anyway — and settles
what a module declares about being asked by declaring nothing: serving a tool is being
askable through the controller. A module-to-module call, if one is ever wanted, is a grant
like any other, and a later decision.
**Located in:** the controller (a command speaking the tool request/reply over its own connection)
and the tool runtime's request contract. Not fixed here: it is a new command against a protocol
the runtime owns, and needs a lab run against a tools-only bed to be proven.
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-14
located-in: []
located-in: [mesh-controller (the catalogue), mesh-catalog]
fixed-by: mesh-controller 3ae7b88, 2b82872; mesh-catalog d2dce34; proven by one-node-mesh.test.ts (the catalogue holds every module the control plane built)
amended-design:
---
@@ -1,5 +1,5 @@
---
status: fixed
status: resolved
opened: 2026-09-14
located-in: [mesh-host, mesh-catalog]
fixed-by: mesh-host 56124c3; mesh-catalog 5e4dc37; mesh-lab 440e265
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-14
located-in: []
located-in: [mesh-controller (the derived filter)]
fixed-by: mesh-controller dda001d (the firewall opens the port the mesh itself runs on), 25e42b3; TestTheBrokersPortIsOpenedThoughNoModuleDeclaresIt
amended-design:
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-15
located-in: []
located-in: [mesh-tools, mesh-host, mesh-sdk]
fixed-by: mesh-tools b618057 (the SDK resolved by version from the registry, one pin); mesh-host 7986306; mesh-controller 4b9bc50. Caveat: the base image still builds with npm install and no lock, so the build is not reproducible
amended-design:
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-16
located-in: []
located-in: [mesh-host examples/foundation-first-node.lock, mesh-host internal/bundle]
fixed-by: ADR 0088; mesh-host #16 (the foundation bundle installs nftables and loads a base ruleset before the store); proven by the bundle test and the genesis bed probing from outside during the install
amended-design: 03-DESIGN/01-to-be/07-the-foundation.md
---
@@ -0,0 +1,20 @@
# Diagnosis — 2026-09-21
1. The package half was read against what the mesh now runs. The mesh's package registry has
proxied the public one for every package since early September, before this issue was opened;
and the builder's `.npmrc` names the mesh's registry for the mesh's own scope only, so a public
package is asked of the public registry directly. Either the failing build ran before the
proxy, or its Dockerfile set the registry itself, or the build machine could not reach the
public registry at that moment. The report does not say which, and the 404 cannot be placed
without running the build again. Not fixed; not reproduced either.
2. The image half has a precedent. The lab hit the same refusal pulling a vendor tool and found
the cause was the public hub denying anonymous pulls, not the mesh's redirect; the fix was to
pin the same image from another registry. A `COPY --from` of a public image is a build input
the manifest does not declare, so the mesh cannot pre-fetch or pin it the way it pins bases —
which is the second open question, and the one a decision has to settle.
3. Ruled out: that the build environment has no internet. Package installs from the system's
repositories succeed in the same Dockerfiles.
**Located in:** the builder (what it tells npm and the runtime) and the catalogue (three modules
whose Dockerfiles fetch what no manifest names). Still open: a build must be re-run to place the
404, and a decision is needed on whether a vendor image is declared as a build input.
@@ -1,9 +1,9 @@
---
status: open
status: resolved
opened: 2026-09-20
located-in: []
fixed-by:
amended-design:
located-in: [mesh-controller internal/inventory (node_report), mesh-controller cmd/mesh-controller (status)]
fixed-by: ADR 0090; mesh-controller feat/multiple-fixes (the report counts identical failures; status says stuck after three)
amended-design: 03-DESIGN/01-to-be/10-delivery.md
---
# 065 — A permanently failing resource is retried for ever with no escalation
@@ -0,0 +1,17 @@
# Diagnosis — 2026-09-21
1. The controller keeps one report per machine, replaced on every report, by design: the question
is the machine's current state and a history would bury it. A host reports after every apply
and applies on its reconcile interval, so a permanent failure is a row that says "failed" at a
fresh time every few minutes, indistinguishable from a failure that just happened.
2. The four open questions, answered in turn. Escalate: yes, as a word in `status`, which is
where "what is wrong" is already read. The signal: consecutive identical reports, not a
duration — a machine shut for a week has had one attempt. Where: the controller, which sees
every node and can tell one stuck machine from a mesh-wide fault; the host does not know
whether a failure is permanent and must keep trying. A gating failure: unchanged by this, and
left open in the report.
**Located in:** the controller's node report and `status`. The fix keeps, beside the last report,
when the current failure began and how many reports in a row have said it; three make the machine
stuck, said in `status` and in its JSON. Decided in
[ADR 0090](../../02-DECISIONS/0090-a-failure-that-repeats-is-said-to-be-stuck.md).
@@ -1,7 +1,7 @@
---
status: open
status: located
opened: 2026-09-20
located-in: []
located-in: [mesh-host internal/apply, mesh-controller internal/inventory (node_report)]
fixed-by:
amended-design:
---
@@ -0,0 +1,20 @@
# Diagnosis — 2026-09-21
1. The mixed state is already a first-class, visible one. The controller keeps three outcomes for
a machine's last report — applied, failed, refused — and defines `failed` as "some of it: the
machine is in a state nobody declared", with the failed resources and how many did apply beside
it. `status` lists such a machine as not doing what it was told. So the second open question
is answered as it stands: "partway through a change" is distinct from "converged" and from
"refused", and has been since the report was kept.
2. What is not visible is duration, and that is [issue 065](../065-a-permanently-failing-resource-is-retried-for-ever-with-no-escalation/00-report.md),
resolved alongside: a machine that stays in the mixed state now reads as stuck.
3. The first and third questions — pairings whose half-state is harmful, and whether `restart-on`
is the seed of a grouping — are a design decision the record does not yet contain. `restart-on`
couples a service to files within one apply but does not withhold either when the other fails.
No incident has produced a harmful pair; the report was written from reading the loop. A
grouping primitive without a case that needs it would be a rule enforced against nothing.
**Located in:** mesh-host `internal/apply` (the loop) and the controller's report. Left open for
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.
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-20
located-in: []
located-in: [mesh-controller, hq 03-DESIGN/01-to-be/23-choosing-a-provider.md]
fixed-by: graduated — hq fc4ab37 (ADR 0084, to-be design 23)
amended-design: 03-DESIGN/01-to-be/23-choosing-a-provider.md
---
@@ -1,7 +1,7 @@
---
status: resolved
opened: 2026-09-20
located-in: []
located-in: [hq 03-DESIGN/01-to-be/24-the-secrets-vault.md]
fixed-by: graduated — hq fc4ab37 (ADR 0085, to-be design 24)
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
---
@@ -1,7 +1,7 @@
---
status: open
status: located
opened: 2026-09-20
located-in: []
located-in: [mesh-controller internal/catalogue (requires/secrets), mesh-controller internal/inventory (secret key)]
fixed-by:
amended-design:
---
@@ -0,0 +1,23 @@
# Diagnosis — 2026-09-21
1. The constraint is real and where the report put it: a module requires a provision name once,
a pair credential is keyed on (name, consumer node, consumer module, provider), and the login
the vault derives is keyed on the (node, module) pair. Two secrets for one module and one vault
collide on every key.
2. The third question was answered by reading the catalogue as it is today: nine modules hold two
or more own secrets besides their broker account. Two hold a relay user beside a relay
password — the user is a name, not a secret, and could travel as configuration. The other
seven hold genuinely independent values with independent lifetimes: an internal token beside
an admin password; a source, an admin and a relay password; an admin password beside an API
token; a root certificate, its key, that key's password and an intermediate's. None of those
derives from another. So "one value per module, derivation the module's business" is not an
answer for most of them.
3. So the answer is neither option as the report framed them. A module keeps one `secret`
provision per independent value, and needs a way to require the same provision name more than
once under distinct local names — the way `secrets:` already maps a provision to a path. That
is a manifest-vocabulary decision, and it moves the pair key: the credential must be keyed on
the local name, not the provision name, or the second pair overwrites the first.
**Located in:** the manifest's `requires`/`secrets` vocabulary (the catalogue parser) and the pair
credential's key (the controller's secret store). Not fixed here: the key change touches every
existing pair and belongs in a feature of its own, with a lab run against the vault bed.
@@ -1,9 +1,9 @@
---
status: open
status: resolved
opened: 2026-09-20
located-in: [mesh-controller]
fixed-by:
amended-design:
located-in: [mesh-controller internal/inventory (secret), mesh-controller cmd/mesh-controller (secret accept)]
fixed-by: ADR 0092; mesh-controller feat/multiple-fixes (secret accept --provider; origin on the pair; remake and rotate refused)
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
---
# An operator cannot deliver a pair credential, so the vault's third species has no entry
@@ -0,0 +1,13 @@
# Diagnosis — 2026-09-21
1. The three open questions, answered. It is `secret accept` growing a provider end, not a new
verb: the verb already means a value a person supplied, sealed on the way in. An accepted pair
refuses `rotate` — the mesh cannot make the replacement — and accepting a new value is the
rotation. The origin becomes a fact of every pair credential, `made` or `accepted`, so the
vault's ledger can say which a person supplied.
2. One consequence the report did not name: a pair credential is remade whenever either end's
sealing key changes, and an accepted one cannot be — the mesh does not hold the value. The
read is refused aloud with the remedy rather than quietly replaced by a minted one.
**Located in:** the controller's pair-credential store and the `secret accept` command. Decided
in [ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md).