Issue 298 and ADR 0246: a seat's new verb is promised before it is required
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

Design 33 said a seat's verbs change additively, and section 3 made each
one a condition of holding, so a verb added in the controller and served
from the catalogue could land in neither order. It happened three times on
2026-10-07. Section 7 now says the three steps and how each is checked.
This commit is contained in:
jochen
2026-10-07 21:14:20 +02:00
parent 921ff3e6c4
commit e6a17b8bf6
6 changed files with 230 additions and 2 deletions
@@ -0,0 +1,83 @@
---
topic: the mesh
status: accepted
date: 2026-10-07
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
---
# 246. A seat's new verb is promised before it is required
## Context
[ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md) made serving a seat's verbs a
condition of holding it. [Design 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §7 says
those verbs change "additively within a version". The controller's claim check enforces two things: a
holder serves every verb the seat promises, and a claim names no verb the seat does not promise.
A mesh seat is defined in the controller, and its holders live in the catalogue. On 2026-10-07 three
verbs were added this way: `checks` on `mesh-delivery`, `failed` on `node-service-manager`, and
`resolvers` and `links` on `node-uplink`. Each time, neither repository could merge first
([issue 298](../04-ISSUES/298-a-new-seat-verb-deadlocks-across-two-repositories/00-report.md)). The new
controller refused the old holder for not serving the verb. The old controller refused the new holder for
claiming a verb it did not promise. Every catalogue check between the two failed on a module nobody had
touched. The controller was unblocked by marking the added verb optional (mesh-controller #117, kept
optional in a stored seat row by #114).
## Options
1. **Drop the check that refuses an unpromised verb.** The catalogue could then land first. But the check
catches a misspelt verb that would otherwise be served to nobody. The controller's change would still
refuse every holder not yet rebuilt, so only one order opens.
2. **A new seat version for every added verb.** §7 already allows a version for a change that would
break a caller. Running two versions side by side to add one verb costs a set of subjects, grants and
a retirement for nothing a caller would notice.
3. **Land both repositories at once.** Nothing joins two checked pull requests into one merge, and the
mesh would have to run both builds at the same moment on every machine.
4. **Promise first, require later.** The controller promises the verb as optional. Then the holder
serves it. Then the controller makes it required.
## Decision
Option 4. **A verb added to a seat whose holder lives in another repository lands in three steps, each
in its own pull request:**
1. **The controller promises it, optional.** A claim that serves it is accepted, and a holder that does
not serve it still holds the seat. The mark comes from the seat as compiled and survives a seat row
read back from the store.
2. **Every holder serves it.** Each holder is checked against a controller that already promises the
verb.
3. **The controller requires it.** The mark is removed, and serving the verb becomes a condition of
holding like every other verb of the seat.
A verb on a new seat, or on a seat that nothing holds yet, is required from the start. Removing a verb
or changing its arguments in a way that breaks a caller is still a new version (§7).
## Consequences
- Adding a verb to a held seat takes two controller changes and one in each holder's repository, where
design 33 implied one change. That is the price of §3 holding at every moment in between.
- Between steps 1 and 3, a caller can be told that a holder does not answer the verb. The caller finds
this out from the holder, not from the seat's protocol. Discovery lists the verb, because it is
promised.
- Nothing yet checks that step 3 happens. A verb left optional is a promise that no future holder has to
keep. Issue 298 is not resolved until a check covers it.
## How it is checked
- The controller's claim check refuses a claim that serves an unpromised verb, and a holder that misses a
required verb. It runs at registration, at handover, and in `module check`, which the catalogue's merge
check runs over every manifest with the controller the mesh runs.
- Tests in mesh-controller:
- `TestTheDeliverySeatPromisesChecks` (#117);
- `TestFailedIsAnOptionalVerbOfTheServiceManager`, `TestAnOptionalVerbStaysOptionalInASetReadFromTheStore`
and `TestASeatsVerbGainsTheArgumentsTheBinaryNames` (#114);
- `TestTheUplinkSeatPromisesItsVerbsAndRequiresNoneYet` and
`TestTheUplinkVerbsReadBackFromTheRowStayOptional` (#116).
## References
- [Issue 298](../04-ISSUES/298-a-new-seat-verb-deadlocks-across-two-repositories/00-report.md)
- [Design 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §3 and §7
- mesh-controller #114, #116 and #117; mesh-catalog #105, #107 and #109
+1
View File
@@ -208,6 +208,7 @@ python3 00-META/checks/index.py fail if stale
- **0237** — [A change is judged against the mesh that runs, before it merges, on the build seat](0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md)
- **0238** — [A commit is the build at hand: one commit, one change plan, checked off the trunk and published only on it](0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md)
- **0239** — [A delivery is owned by the mesh-delivery module and runs from commit to delivered](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md)
- **0246** — [A seat's new verb is promised before it is required](0246-a-seats-new-verb-is-promised-before-it-is-required.md)
### Its tiers, from the bottom up