ADR 0583 §1 (insight 2026-11-01) and §2: the grants part reads the vault's node-logins as its named reader and
composes a node user per login, both while the node rotates and the new one alone after rotation-done; each node's
engine reads its own node's key alone (composed by mesh-sdk while the vault serves rotate-node). The part follows the
state by watch, so a rotation begun or ended composes again at once. A second login is judged its node's when the
list is compared (Placed), and a hash changing by a rotation node-logins names is not counted as a writer changing
several hashes at once (sealed.go). The state not on the bus yet is a standing gap: every node logs in as node.<n>,
as today. Tested as a subtest of TestGrants, no new test function. Built against the group's mesh-sdk head 5000774
through a go.work outside the worktree; the pin is the lead's.
(issue 774, 774/S53)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
ADR 0587 §4: the grants part composes enrol.<node> from a key at live and the vault's hash, granted only the seat's
redeem and its own inbox; §5: when the key ends the user goes. The part reads mesh-enrolment.enrolments as its named
reader and follows it by watch: a key reaching live composes the user, and its end (by the holder, or by the bus at
its expiry) is a change of the key that takes it out, so no consume of the seat's ending events is added beside the
watch (the subtask named one; the watch already hears every end, the bus's expiry included). From the lease write the
user loses the controller's enrolment subject (§6) and the standing gap of the controller's tokens is dropped; before
it the gap stands. Tested as a subtest of TestGrants, no new test function. Built against the group's mesh-sdk head
5000774 through a go.work outside the worktree; the pin is the lead's.
(issue 774, 774/S52)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
ADR 0549 §5, ADR 0564 §9, ADR 0571 §10: the part no longer reads mesh-controller.assignments (the union with
placement goes; placement not read is a failed read that places nothing) nor consumes the controller's
condition-raised and condition-cleared, the keeper's being the one source. ADR 0571 §13's test for this
repository replaces the guard of ADR 0549 §6 and its list ../assignments-readers, now empty and deleted: at
phase write no module.json here depends on mesh-controller beyond the lease and the three kept to the write
(catalogue.ManifestRefusals).
Tests: in TestGrants the fixture places through the seat's placement (assign, unassign), the subtest of the
stale assignments becomes 774/S29/AC1 (placement alone gives the fixture's three memberships with no read,
consume or call of the controller; the fixture is the suite's own, whose assignments named the same modules,
not a copy of live memberships), and the assignments-readers subtest becomes 774/S24/AC1. No new test function.
(issue 774, 774/S29, 774/S24)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
ADR 0571 §13: the judge moved to mesh-sdk (catalogue.Dependencies, Judge, KeepList; issue 774, 774/S4) so the
manifest tests of three repositories and grants_lease_check give one judgement; mesh-grants re-exports it by
its names. The keep list there holds the three kept to the write alone (774/S30), superseding the keep of
pacman's read at 4a84370. grants_lease_check no longer counts the controller's standing assignments, which
nothing reads any more.
Tests: TestTheLeaseCheckJudgesEveryDependencyOnTheController keeps the tool's own rows (what it read, the
lease, the streams); its dependency rows moved to mesh-sdk's TestTheLeaseWritesCheckJudgesEveryDependencyOnTheController
(774/S24's replacement of the moved rows), and TestTheKeepListNamesItsRecordsAndThreeToTheWrite went with them.
(issue 774, 774/S30, 774/S24)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
ADR 0582: lease-write is refused, naming each check, until the lease check at phase write and every condition
of clause 2 read green (design 47's earlier issues resolved, every node trusting the vault's key, mesh-config's
compare empty from the indexer, the upgrade compare empty, the keeper's take-over done, the vault minting with
nothing held back, the bus module listing its expiry and removal tools). It asks the operator once; the yes
binds the act, the holder, the ask and the checks' digest, and the checks are read again on the warrant (one
that reads otherwise refuses it, named). Then, each step read back: node-service-manager.stop on the
controller's node, the list composed with Records.LeaseCut and waited on until the bus node's have says it
loaded it, the expiry removed through nats_bucket_expiry, the key holder written (with the ask and the time),
lease-handed said, the act recorded through mesh-hand-acts.warranted, and removal-due written with a day to
live. lease-altered is said once when the key or the expiry differs; lease-hand-back reverses it for a day
and is refused once the removal ran; removal-due's marker alone starts the removal of the controller's
consumer and the stream ASSIGNMENTS, recorded as hand acts, and a failure says removal-failed and writes the
key again with a day (grants_removal_due is the recovery for a missed marker). From the write memberships are
written to the state alone (ADR 0571 clause 10), the user-list key says lease-handed, and the user list's
hand-back answers gone (ADR 0547 §11). Every step waits on an event, never a timer.
Bent rule: S14-S21 are one commit, not eight: the steps are one state machine in leasewrite.go, and splitting
it by hunks would review the same file eight times for nothing it protects.
Tests: TestTheLeaseWrite (new, one function with a subtest per subtask), paid by the deletion of
TestTheKeepListNamesItsRecordsAndThreeToTheWrite (moved to mesh-sdk, S4/S30) and the assigner's two deletions
(S25). Fixtures shaped from each writer's code: systemd's Manager.Status, nats-tools' BucketExpirySet,
ConsumerRemoved and StreamRemoved, mesh-issues' Brief, mesh-delivery-view's TrustsAnswer, mesh-config's
Comparison, the assigner's upgrade compare, mesh-vault's vault_status.
(issue 774, 774/S14, 774/S15, 774/S16, 774/S17, 774/S18, 774/S19, 774/S20, 774/S21)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
pacman still falls back to the controller's declared-packages for a node whose engine has
not written its own packages (ADR 0565 §6), so the check named that read as ended and would
refuse the lease write. It is now a kept read, dropped with the others at the write's own
delivery (issue 774, design 47 L3).
Quick fix chosen over removing pacman's fallback: it matches the record as it stands, and
the read goes with the other kept reads at the write's delivery.
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
Issue 1585, S12: grants, builder and checker read with catalogue.Decode and grants holds a module the indexer
refused; the checker asks the Go build seat to run mesh-judge, built at mesh-delivery's trunk or at the group's
mesh-delivery head when it changes the judge, over the catalogue as the change leaves it, and fails on a refusal.
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
The holder writes a node's changed module keys, then its node key as the commit by compare-and-set at
the revision the proposal was laid over, then the placement.<node> mirror for readers not yet delivered.
Every reader assembles through mesh-sdk's PlacementKeys, which reads both forms.
mesh/check being carried to 2dd5239726ca on e559798972b4 | from d409bdf20f48
Review of 09791f5: with the indexer's modules bucket readable but empty, nothing was read, so
nothing was found and both phases passed; the operator could have handed the lease away while an
unkept read still stood. Judge now refuses when no manifest was read, or fewer of the indexer's
modules than it holds (a provided manifest does not count for a missing one), saying why; a
missing or refused manifest is named as before, now under test.
The controller's grants leave the keep list: ADR 0571 §9 has no manifest read them from L7's
delivery, changing ADR 0567 §10 and ADR 0547 §5's shadow compare, so the assigner's and the grants
part's reads are refused, and a refusal names the record that ended a declaration where one does.
Quick fix: the counts in the grants part's Judge, as here. Thorough fix: the same coverage rule
in the shared mesh-sdk derivation once the keep list moves there; the quick one stands until then.
The operator asked to remove the controller by finishing its hand-over; no tool said who holds
the lease, which manifests still read, invoke or consume mesh-controller, or whether the lease
write's check (ADR 0571 §13) would pass. grants_lease_check answers all three from bus state on
the grants part, which already reads the lease, every manifest at the indexer's trunk and the
bus's streams: the holder and what follows from it, every dependency with the record on the
named keep list that keeps it, the controller's streams and buckets someone consumes, and the
check run dry at the write (the three kept to it alone) and before its delivery, with reasons.
Quick fix: the judgement and the keep list in the grants part, as here.
Thorough fix: the keep list and the dependency derivation in mesh-sdk, shared by this tool and
the §13 manifest tests in mesh-catalog, mesh-delivery and mesh-tools. Chosen quick: the tests do
not exist yet and an sdk move needs a group; the move is named for the lead.
The grants part repeated the composer's entry lines to keep a refused principal's grants of the list in force;
mesh-sdk 61b67d6 exports them as buscompose.AccountsEntry, so the copy goes. The byte-for-byte test of the kept
entry still passes. Needs the mesh-sdk pin at 61b67d6 or later.
mesh-wireguard is provided by the mesh itself (mesh-sdk delivery.ProvidedByMesh): no repository builds it, so the
indexer holds no fingerprint of it and the assigner never wants it by one; the controller composes it with the
node's own. The grants part named it "no fingerprint of the new form: the indexer holds no value of it" on every
pass, a warning nothing could clear (sweep 2026-10-30 item 6).
Quick fix: drop mesh-wireguard by name from the missing list.
Thorough fix, chosen as cheap and covering every module the mesh may provide later: MissingGrants passes over any
module ProvidedByMesh names. The controller says the same of it in mesh-control (its grants and switch lines).
Met twice on 2026-10-30 (the engine's acts, then mesh-host_health): the composer refused one principal as a
second writer, and that refusal ended the whole composition, so the list in force stood for every principal
while changes piled up; the next list that composed moved 52 users at once (16:37:28).
Quick fix: leave the refused principal out and write the rest (locks that principal out of the bus).
Thorough fix, chosen because the kind was met twice and it hides every other change: ComposeEach composes each
principal on its own; a refused one keeps its grants of the list in force (the part's sealed grants), written as
the composer writes an entry; one the list in force does not carry stays out; a list that would drop one the
list in force carries, whose grants were not kept, is held, said. The refusal is raised as the warning
mesh-grants.principal-refused, by name and why, cleared once it composes, and listed in the pass
(principals_refused). The entry renderer mirrors buscompose.ComposeAccounts's lines and is pinned to them by the
test (the entry the bus loaded is written back byte for byte); it belongs in mesh-sdk as an exported entry writer.
The sealed-grants value held every user of the last two lists in one value; at 61 users it was refused with
"message size exceeds maximum" (JetStream 10054, 16:37 and 16:55 on 2026-10-30), so the permissions of the
list in force were never kept and a grant judged against them waited.
Quick fix: keep only the newest list in the one value (halves it; it still grows with the mesh).
Thorough fix, chosen because the value grows with every placed module and the kind was met twice: one key per
user and list, sealed-grants.<digest hex>.<user>, and an index sealed-lists naming the kept lists and their users,
written only once every user's key is there; the third list's keys are taken away, and the old one-value key read
as a fallback and removed. mesh-sdk's kvlimit guard (7b02f63) refuses an oversized value before the put through
process.State.Put, which the part's own store (cmd/mesh-grants ownStore.Put) writes through once pinned.
The state memberships held the part's wrapper ({node, module, digest, membership, published, retired}) while every
reader takes the bare membership the assignments stream carries: novox's tool runner decoded an empty one and served
no tool. The part now writes membership.EncodeGrantsMembership of a membership published or unchanged, never one held
back. The pass record moves to the pass the part already keeps (user-list at pass): published or why not was there, the
retired seat verbs are added as Result.Retired; grants_membership reads both, so no new state is declared.
Issue 774 (the bus login list, L3). Quick fix: marshal m.Membership here. Thorough, chosen: one codec in mesh-sdk the
writer and the tool runner share, because a hand-built fixture agreed with the mistake. Pins mesh-sdk at the head of
its fix/memberships-shape.
Why: after the lease write the assignments are frozen, so the part's union kept a module the catalogue takes off a node
with its account, grant, membership and user-list entry. The lease is read as the assigner reads it
(buscompose.LeaseHandedAway); before it, or with no placement on the bus, the union stands. The read stays declared until
the first change after the lease write, named in modules/assignments-readers with the assigner; a TestGrants row refuses
any other module.json that reads mesh-controller.assignments, as mesh-catalog's checks/placement does for the catalogue.
Why: the controller's grant writer skipped link2pay, snake, f1-circuits and txt-game in silence (it read the fingerprint by
their unchanged commit, a build of the old form) and the take-in said 'its grant written'. The part already reads the
fingerprint from the indexer's value, so those four are granted at their src2 done key's fingerprint; a row proves it.
What a pass leaves without a grant (no fingerprint of the new form with the indexer's none2, a refused manifest, no
hashed account) is now said rather than passed over. Quick: log it. Thorough (chosen): the pass's Missing, shown by the
grants tool, and a warning condition raised and cleared on change, as consumer-differs is.
Why: the assigner switches its grants source when the controller's lease is handed away; until then the written keys are compared with the controller's, so the switch rests on a measured equality. Needs the group's mesh-sdk pin (PartGrantKey) before it builds.
Live since the take-over the shadow list was written every 3 s (revision 99270
to 99357 in 90 s, nothing differing): the sealed key read back says [] for the
nil left-out a pass composes, so every pass wrote the key again; each write
moved the bus node's report of what it loaded, whose revision and time were
inputs of the shadow list, and woke the next pass. The bus itself reloads only
when the list's bytes change (its log: two configurations alternating with the
placement, f8567865 and 473f9ad3), so this loop was load, not the reload.
Review round 1 of issue 1945's fix found two ways the guard could let a loss
through or hide one from the operator.
A publish deny was never compared: grants.User carried no deny, so a widened
allow that brings a new deny (a lost publish right) passed as an added right.
The user now carries its deny; a deny the shadow adds is a loss, and while
nats_users answers no deny for the loaded list, a user whose grants differ
and whose shadow grant has a deny refuses, named as a deny the loaded list
cannot be compared on. None of today's 41 differing users carries a deny
(only the four node-tools users do, and they are the same in both lists).
The ask said "takes none away" whenever rights were added, whatever else
differed. Its words now name the added rights only when nothing else refuses
(Refusals: unread list, Removing, a missing hash), and otherwise say what the
yes would be refused for, naming the users; Decide uses the same Refusals.
issue 1945
The operator's yes to take-over was refused twice (2026-10-26 13:48 and
2026-10-27 21:19 UTC) because 41 users differ, every difference a right the
grants part's list adds. The controller's list is not changed (ADR 0547 §1),
so ADR 0547 §8's equality could never hold again. ADR 0562 changes it:
take-over is allowed when no user is added or dropped, no loaded user loses a
publish, subscribe or responses right and every hash is held; the ask names
the added rights by user and kind, and a yes after they changed is refused.
Tests: rows from the captured live comparison of 21:19 (testdata, no
hashes) in the compare table, and rows in the take-over table for a right
removed, added rights changed since the ask, and only added rights recorded.
issue 1945
A node composing a consumer reads the served keys of the trunk's manifest of a
provider elsewhere, which may serve a setting the build it runs does not.
mesh-grants declares each module's served settings (catalogue.ServedSettings), so
the user list grants every other node's agent those keys of mesh-config_resolved
alone: shanks then settles what novox's mailu serves. The assigner's check of
every node reads no build, archive or served value: it composes as before.
mesh/check being carried to ec0b7e2dcc06 on da514816204b | from 24cb379434f5
A dropped verified-sender comes from a root-free word that failed for one
pass, not from a release, and losing it cut the operator's approvals on
2026-10-27. Holdings.CapabilitiesLost holds such a membership with its own
reason while the holder is assigned and its manifest still asks for it; a
dropped tool or field and a moved seat stay published.