Write ADR 0666's asks into design 46 and its versions into design 51

ADR 0666 §6 has the operator-channel offer a fourth ask instead of refusing
it, never holding the newest back; design 46 now says which ask waits and
how it is offered again. ADR 0666 §9 numbers a package when it is
published and has that publication rebuild its dependents; design 51 now
says how the builder numbers it, says it on the seat mesh-builds, and who
asks a tagged version once the checker goes.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
This commit is contained in:
2026-11-10 02:36:26 +01:00
parent 471fd8791d
commit 2fa15a1c8d
2 changed files with 56 additions and 10 deletions
@@ -2,8 +2,10 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog]
updated: 2026-11-06
updated: 2026-11-10
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0347"
- "ADR 0345"
- "ADR 0344"
@@ -221,7 +223,20 @@ when the title and the note alone do not fit, and says that too.
### Limits, batching, history
- An asker holds **at most three open asks**; a fourth is refused in words.
- An asker holds **at most three open asks**; a fourth is refused in words. *Amended 2026-11-10, ADR 0666 §6, from the
delivery of design 47's milestone G:* **no ask is hidden, and the newest is never held back.** An asker still has at
most three asks offered at once, but a fourth is offered, not refused: the oldest of its offered asks is held back
instead, kept open with its answer tickets withdrawn, its message edited to say it waits behind newer asks of the
same asker, and offered again, with new tickets, as soon as one of the three ends. An ask with the same `about` as an
open one of the same asker replaces it (§10), so a held-back ask is never one a newer ask already stands for. `asks`
lists each asker's held-back asks with since when, and the router's tool `ask-again` offers one now, holding back the
oldest offered one in its place. A Decline ends an ask like any answer, and its asker may ask again at once: the
pipeline runner's verb `ask-again` asks a declined `approval` step so (design 47, *Approval is a step*). Why the
oldest: the newest is what the asker needs now, and the oldest has had the operator's eye longest; holding it back
edits its message rather than removing it, so the operator still sees it waits. *How it is checked:* the router's
whole-module test offers a fourth ask and holds back the oldest, offers the held-back one again when one ends,
replaces an open ask by a newer one with the same `about`, and offers an ask after a Decline; live, a fourth ask and
a declined approval each reach the operator (design 47, test 71).
- Asks count against the router's hourly cap, as messages do; answers do not.
- Asks to one channel within the burst window go out together under a heading ("3 questions waiting"),
each as its own message, answerable and editable alone.
@@ -424,7 +439,8 @@ what it does in plain words, its assurance level (`acknowledge`, `approve`, `des
answer (`operator`); an expiry; what happens on expiry, in words; and optionally what it is about (a
condition's key), so a newer ask about it replaces the older. It is refused at once, on its asker's
`decided` subject, when its words break a rule, when no ready channel can carry one of its options' levels,
or when the asker already has three open.
or, until the delivery of design 47's milestone G, when the asker already has three open; from that delivery a fourth
is offered and the asker's oldest held back (§6, *Limits, batching, history*; ADR 0666 §6).
**A warrant** carries the ask's id and asker, the option, `by` ("the operator, via telegram, user id
verified"), the channel module, the proofs, the level met and when. No secret. An ask that ends without one
@@ -577,7 +593,8 @@ runner's bus permissions.
refused and said: waiting, declined (not asked again for 10 minutes), expired, already run, or the router's
refusal.
- **Fail closed:** a secret's shape in an argument or more than 16 KiB of arguments, no operator channel, a
fourth open ask, an unpublished ask, an unreadable router record, node file or mark: nothing runs.
fourth open ask (until design 47's milestone G is delivered, after which a fourth is offered and the oldest held
back, §6), an unpublished ask, an unreadable router record, node file or mark: nothing runs.
- **The controller** enforces the same mark on its own verbs, asking as `mesh-controller`.
- **Marked first:** `node-login-shell.execute` on every node once the reads it carried have tools (issue 418).
`mesh-delivery.release` waits until a warrant the controller holds passes the mark of a verb it calls (issue
@@ -702,6 +719,7 @@ Each phase ends at its own *done when*. Owning repositories from [`repos.md`](..
| **5 — Authorise, with TOTP** | `authorises` in the verb table; the router's checks for the code; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the router's state, enrolled at a terminal with ten recovery codes; `factor recover`, `factor status`, the local-only break-glass enrolment; no P2 tier before recovery codes exist; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code; a recovery drill re-enrols with a recovery code, and is announced |
| **6 — Telegram live** | the `telegram` module (built in 5a) with replies and forced-reply codes, under its own account on the control node; the operator's two bots configured; the watcher assigned off the control node; the dead-man ping | mesh-catalog | the self-check says the away channel carries every tier; rehearsals: an approve and a destroy answered on the phone, with the hand-acts read |
| **7 — A call that needs approval** (ADR 0286) | `needs-approval` in the manifest and the seat list, issued in the membership, withheld from a runner without `approval-mark/1` in its bus permissions (controller); the act's digest in TypeScript beside Go, with shared vectors (shared library); the check, the ask, the runner's `approvals`, the node's used file and `warranted` (tool runner); the controller's own verbs; `execute` marked (the controller's seat list) once issue 418's reads exist | mesh-controller, mesh-sdk, mesh-tools, mesh-catalog | a marked call on a node is asked on the phone, does nothing, runs once when made again after Approve, is refused the third time and asked again, and `hand-acts` shows the warrant |
| **8 — No ask hidden** (ADR 0666 §6), delivered with design 47's milestone G | a fourth ask offered and the asker's oldest held back, offered again when one ends; `asks` lists the held-back asks; the router's tool `ask-again`; the pipeline runner's `approval` asks and their `about` | mesh-catalog (the router) | a fourth ask and a declined approval each reach the operator; `asks` shows the held-back one |
**Switching phase 5a on: the operator's steps, in order** (amended 2026-10-09, ADR 0259). Each line is typed with
`nox` (`mesh-cli`) on the control-node, from the operator's own account, which is the controller's terminal
@@ -6,13 +6,15 @@ code:
- mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its program, holding the two image seats)
- mesh-catalog modules/gitea (package retention; the two package seats' verbs in its Go bundle)
- mesh-catalog modules/mesh-build-go, modules/mesh-build-node, modules/mesh-build-image (publishing through the seats)
- mesh-delivery modules/mesh-delivery-builder (versions, tag asks), the forge bridge, the indexer, the checker, the assigner
- mesh-delivery modules/mesh-delivery-builder (versions, tag asks; from milestone G a package numbered at its publication, the seat `mesh-builds`), the forge bridge, the indexer, the checker, the assigner, modules/mesh-delivery-pipeline (`tag-wanted` from milestone G)
- mesh-sdk (the bus helper for sending in parts)
- mesh-catalog modules/docker (image pruning)
- mesh-host internal/apply (every new image of a module pulled before any of its containers is replaced; a failed pull applied again on the status view's `settled` for the store's holder, and at the node-engine's start), internal/compose (each routed image reference written with its seat and that seat's holder)
- mesh-delivery modules/mesh-grants (what a node-engine hears: the status view's `settled`)
updated: 2026-11-04
updated: 2026-11-10
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0605"
- "ADR 0612"
- "ADR 0614"
@@ -160,7 +162,8 @@ the verb.
entry turns `published` and the builder writes the done key then, so a late store is never lost.
- **What is published when**: a package or an image from a trunk build under its version, or as a tagged version; a
bundle or an archive from every done key by its digest alone, a candidate's included, and at the trunk's claim the
build module calls `publish` with the version and the held digest, which stores nothing. An upstream image is copied
build module calls `publish` with the version and the held digest, which stores nothing. From the delivery of design
47's milestone G a package's version is given at its publication instead (*The versions*). An upstream image is copied
by `mirror` with its pinned digest.
- **The done key names the reference the seat answered**, in ADR 0605 §9's form from this milestone on:
`<registry>/<module>/<artifact>@sha256:<digest>` for an image, bundle, archive or upstream copy and
@@ -263,7 +266,26 @@ the verb.
### The versions
- **The builder numbers every trunk build** of a package, image, bundle or archive at its claim: the package series
- **From the delivery of design 47's milestone G, a package is numbered when it is published** (ADR 0666 §9). The
builder numbers a package's version at its publication, never only when its build is claimed: a package's trunk
build is claimed with no number and built by its digest, and once its build is done the builder gives it the next
count of its series, under compare-and-set, and says `numbered`, on which the build module calls `publish` with
the version and the held digest, as below for a floor not yet known. A build of an older source revision of the
same watched branch than one already numbered for that name is not numbered and publishes nothing, so a version never
names older content than the one below it. When the holder says `published`, the builder says the publication as the
event `package-published` of its seat `mesh-builds` (the name, the version, the fingerprint, the commit, the watched
branch). On it every artifact with a "published before it builds" edge to the package is rebuilt against the new
version, and the pipeline runner, which consumes the event, starts its pipeline (design 47, *A publish rebuilds*);
nothing else is rebuilt, and a change to a package's tests alone publishes nothing. This removes the claim's wait
for earlier merges' numbers below, which held a group on 2026-11-09 (research 086, Findings). Why at publication: a
number given at the claim must wait for every earlier merge's number, and a build that never publishes keeps a
number nothing holds. An image, bundle or archive keeps the rule below. How it is checked: in the builder's
whole-module test a package's trunk build is numbered only once built, an older revision's build after a newer one
publishes nothing, `package-published` is said once per publication, and only the importing artifacts rebuild
(design 47, test 80).
- **A build that cannot read the published packages refuses**, naming the unread state, never building against a guess
(ADR 0666 §9; design 47, test 70).
- **Until that delivery, the builder numbers every trunk build** of a package, image, bundle or archive at its claim: the package series
its manifest declares (design 32; `0.1` when it declares none) and the next count for that name, under
compare-and-set. Numbers follow the order of the forge bridge's merge events: a claim of a later merge waits until
every earlier merge of the same name whose fingerprint the indexer still holds is numbered (a fingerprint replaced
@@ -318,7 +340,11 @@ the verb.
- **Asked by an event, never by a push** (ADR 0610 §1). The checker emits `tag-wanted` (repository, branch, head,
tag), on a family of its own, `checker.asks`, kept seven days, apart from its verdicts, so a waiter on a verdict does
not wake on it, for each head of a delivery group member that declares a package; a person asks through `builder_tag`. The
not wake on it, for each head of a delivery group member that declares a package; a person asks through `builder_tag`.
From the delivery of design 47's milestone G the checker goes, and the pipeline runner says `tag-wanted` instead, on
its own seat `mesh-pipeline-runner`, for each member that declares a package of a run on request of a group not yet
merged (ADR 0666 §11): a group merged together is one source revision whose packages are published before its
dependents build, so only a run on request needs a tagged version. The
builder consumes the event on a durable consumer and keeps the ask in its own state `tag-asks`, removed when its
branch is merged or deleted, or its pull request closed. An ask the builder has not answered within five minutes is narrated in the build log
with what it waits on.
@@ -326,7 +352,9 @@ the verb.
and Go's `@latest` skips it. A package new in a group is numbered from its floor (`0.1.0-feat-x.1`).
- **A group's check builds after `published`**: Go members by the tagged version, TypeScript members with their lock
naming it and the registry's integrity; nothing is packed for a check, and a lone pull request's check publishes
nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package.
nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package. From the
delivery of design 47's milestone G the same holds for a group's run on request, whose pipeline steps build after
`published`, and a step that gets no `published` within the bound ends failed, naming the package.
- **Removed with its branch**: on the forge bridge's `merged` event, its pull `closed`, or its branch-deleted event,
heard on a durable consumer, the builder
calls each seat's `remove`. It acknowledges the event only once every seat emitted `removed`; until then it retries