Multiple fixes: status vocabulary aligned, 065/026/070/007 resolved, 066/069/049/046 located, 064 diagnosed #64

Merged
jschoubben merged 10 commits from feat/multiple-fixes into main 2026-09-21 17:23:35 +00:00
9 changed files with 183 additions and 8 deletions
Showing only changes of commit 16442ecd23 - Show all commits
@@ -0,0 +1,68 @@
---
topic: what runs on it
status: accepted
date: 2026-09-21
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0051-shared-data-is-the-operators.md
---
# 91. A mount is declared, and there are three things it can be
## Context
A container's bind mount whose source does not exist is created by the container runtime, as
root, with whatever mode it picks. So `owner` and `mode` — which exist so a module can say who
its data belongs to — never reach the directories that hold data, and the rule that keeps a
directory when a module goes away ([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md))
does not cover them, because the mesh has never heard of them
([issue 026](../04-ISSUES/026-the-data-directories-are-not-declared/00-report.md)). Fourteen
such mounts were declared by hand; a check that every mount is declared was then written and
withdrawn, because it refused the builder: the builder mounts the container runtime's socket,
which is not its data, already exists, and belongs to the machine. Declaring it as the module's
own directory would be a lie the host would act on.
## Considered Options
1. **Declare the socket as a directory anyway.** Rejected: the host would create, own and
protect a path that is the machine's.
2. **A new manifest field** naming machine paths a module may mount. Rejected: the manifest
already says the module needs the container runtime, and a second field would say the same
thing in paths.
3. **Three declarations, one for each kind of path a mount can be.** Adopted.
## Decision
A container may not mount a path the module never declared, and a path is declared in one of
three ways, which are the three things a path can be:
- **the module's own** — a directory or file resource, or where a secret, a grant or a
contribution lands. Created and owned by the mesh for this module, kept when the module goes;
- **the operator's** — an `accesses` entry ([ADR 0051](0051-shared-data-is-the-operators.md)):
pre-existing, shared, granted for use, never owned;
- **the machine's** — a facility a declared capability grants. `container-runtime` grants its
socket. The path exists, the machine owns it, and the capability is the declaration.
A mount under a declared directory is declared. The check runs where the manifest is parsed,
and names the path and the three remedies.
## Consequences
Every directory that holds a module's data is one the mesh created with the module's owner and
mode, and one ADR 0030 protects. What got harder: a manifest borrowed from a compose file no
longer passes on the strength of its volume lines; each must say what kind of path it mounts.
The table of what a capability grants is small and in the catalogue's parser; a new capability
that grants a path adds a row.
## How it is checked
Manifest tests refuse an undeclared mount, accept one under a declared directory, accept one
the module accesses, and accept the runtime's socket with the capability and refuse it without.
A test parses every manifest in the catalogue beside the checkout and fails on any that breaks
the rule, so the catalogue cannot drift back.
## References
- [issue 026](../04-ISSUES/026-the-data-directories-are-not-declared/00-report.md)
- [ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md), [ADR 0051](0051-shared-data-is-the-operators.md)
- [`03-DESIGN/01-to-be/18-building-a-module.md`](../03-DESIGN/01-to-be/18-building-a-module.md)
@@ -0,0 +1,61 @@
---
topic: the tiers
status: accepted
date: 2026-09-21
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0085-a-secret-is-a-provision.md
---
# 92. An operator delivers a pair credential, and the mesh never replaces it
## Context
[ADR 0085](0085-a-secret-is-a-provision.md) names three species of secret and gives the vault
two of them: a module's own secret, which the mesh mints, and an operator-delivered secret — a
credential for something outside the mesh, which only a person can supply. Under 0085 a secret
from the vault is a pair credential between the consumer and the vault. The controller's one
command that takes a value from a person wrote only a module's own secret, sealed to one node.
Nothing could put a value into a pair, so the third species had no entry
([issue 070](../04-ISSUES/070-an-operator-cannot-deliver-a-pair-credential/00-report.md)), and
an operator's credential could be held only as an own secret — un-audited, un-rotatable, the
gap 0085 opened to close.
## Considered Options
1. **A new verb on the pair.** Rejected: `secret accept` already means "a value a person
supplied, sealed on the way in, plaintext discarded"; a second verb would mean the same.
2. **`secret accept` grows a provider end.** Adopted.
## Decision
`secret accept <node> <module> <name> --provider <node>` seals the supplied value to the
consumer's node, to the provider's node, and to the operator's key when the mesh has one, and
records the pair as `accepted`. Every pair credential now says where it came from: `made` or
`accepted`.
An accepted pair is never replaced by a made one. When a sealing key at either end changes, the
mesh cannot re-seal a value it does not hold, so the read is refused and names the remedy —
accept it again. `rotate` refuses an accepted pair for the same reason: the mesh cannot make its
replacement, and deleting it would have the next read mint one, delivered and reported as
applied while failing to authenticate somewhere else entirely. Rotating an accepted credential
is accepting a new value.
## Consequences
A credential for something outside the mesh lives in the vault's ledger with the others, sealed
to both ends and recoverable by the operator. What got harder: a mesh whose node keys change
cannot heal an accepted pair by itself; a person is asked. That is the honest shape — the value
was never the mesh's to make.
## How it is checked
An inventory test accepts a value into a pair, reads it back twice unchanged with origin
`accepted`, asserts `rotate` refuses it naming the remedy while a made pair still rotates;
another changes a node's key and asserts the read is refused, then accepts again and reads.
## References
- [issue 070](../04-ISSUES/070-an-operator-cannot-deliver-a-pair-credential/00-report.md), [issue 069](../04-ISSUES/069-one-secret-provision-yields-one-value/00-report.md)
- [ADR 0085](0085-a-secret-is-a-provision.md)
- [`03-DESIGN/01-to-be/24-the-secrets-vault.md`](../03-DESIGN/01-to-be/24-the-secrets-vault.md)
+2
View File
@@ -112,6 +112,7 @@ python3 00-META/checks/index.py fail if stale
- **0075** — [An artifact store is a provision; a package registry is a different one](0075-two-stores-and-which-provides-what.md)
- **0078** — [The store and the broker are ordinary modules](0078-the-store-and-broker-are-modules.md)
- **0079** — [The foundation seats are named after their servers](0079-the-foundation-seats-are-named-after-their-servers.md)
- **0092** — [An operator delivers a pair credential, and the mesh never replaces it](0092-an-operator-delivers-a-pair-credential.md)
### What runs on them, and how it gets there
@@ -141,6 +142,7 @@ python3 00-META/checks/index.py fail if stale
- **0084** — [Which provider serves a consumer, when the mesh runs more than one](0084-which-provider-serves-a-consumer.md)
- **0085** — [A secret is a provision, and the vault is the module that provides it](0085-a-secret-is-a-provision.md)
- **0087** — [A seeded file is created once, and what grows in it is not the mesh's](0087-a-seeded-file-is-created-once.md)
- **0091** — [A mount is declared, and there are three things it can be](0091-a-mount-is-declared-three-ways.md)
### How it is built
@@ -7,6 +7,7 @@ code:
- mesh-catalog modules/builder
updated: 2026-09-21
decisions:
- 02-DECISIONS/0091-a-mount-is-declared-three-ways.md
- 02-DECISIONS/0087-a-seeded-file-is-created-once.md
- 02-DECISIONS/0040-what-a-module-is.md
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
@@ -182,6 +183,16 @@ disagrees with it.
| `computed` | marks a module the controller generates rather than an author writing |
| `build.artifacts` | what it produces |
**A container mounts only what the manifest declares**
([ADR 0091](../../02-DECISIONS/0091-a-mount-is-declared-three-ways.md)). A bind mount the module
never declared is created by the container runtime as root, so the module's owner and mode never
reach its data and the rule that keeps data when a module goes away does not cover it. A path is
declared in one of three ways, for the three things a path can be: the module's own (a directory
or file resource, or where a secret, grant or contribution lands), the operator's (an `accesses`
entry), or the machine's (a facility a declared capability grants — `container-runtime` grants its
socket). *How it is checked:* the parser refuses an undeclared mount naming the path and the three
remedies, and a test parses every manifest in the catalogue beside the checkout.
### What it builds
| kind | is |
+7 -1
View File
@@ -4,6 +4,7 @@ status: implemented
code: [mesh-catalog, mesh-controller, mesh-host]
updated: 2026-09-21
decisions:
- 02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md
- 02-DECISIONS/0085-a-secret-is-a-provision.md
- 02-DECISIONS/0031-the-control-plane-authenticates-nobody.md
- 02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md
@@ -45,7 +46,12 @@ whose job it is to give it one."*
A module that needs a secret for its own use requires a `secret` provision, exactly as it requires
a database from the store. The vault generates the value — or takes custody of one an operator
delivered — and the credential belongs to the consumer↔vault pair. Because it is an ordinary pair
delivered, through `secret accept … --provider`, which seals it to both ends and records the pair
as accepted ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)) — and
the credential belongs to the consumer↔vault pair. An accepted pair is the one exception to what
follows: the mesh cannot make its replacement, so it is neither remade when a key changes nor
rotated; both are refused aloud, and accepting a new value is the rotation. *How it is checked:*
an inventory test accepts, reads back unchanged, and asserts the two refusals name the remedy. Because it is an ordinary pair
credential, **everything already built for pair credentials applies to it unchanged**: it rotates
with the one command that discards a credential and delivers both ends together, it is one secret
per holder so rotating one touches nothing else, and *who holds this* is a query rather than an
@@ -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,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).