Review of #197: a carried superseded member is reached in GroupState but
still stops the members after it in groupTurns (hq issue 445); cite ADR
0239 decision 4, which GroupState extends; shorten the stopped reason so
the member's id survives the forge's 140-character status line. Tests
pin one forge ask per pair and CoveredBy/ContainedIn across a read-back.
A superseded member counts as reached when a later delivered walk on its
trunk carried every module it moves (ADR 0270's covered test); otherwise
the group reads stopped, as groupTurns already treats it. Five groups
read "delivering, 1 of 2 delivered" for days with nothing in progress
(novox/hq issue 442).
The autofs line an armed automount leaves in mountinfo made List say
"mounted" while nothing was mounted. One judgement, Mounted.IsAutomount,
now serves List, the look and the checks before a mount.
systemctl refused the whole call on -.mount, and list answered every mount
without its unit state (novox/hq issue 374). The test fake now refuses a
dash-led argument as systemctl does, so it no longer agrees with the mistake.
The second review found the gap it was asked to look for: everything
after the forge's merge could throw — a forge 5xx, a timeout, a commit
named without parents — and the caller got a bare error that never said
the merge had happened.
From the merge on, a read-back that fails or cannot be done is said
beside merged: true, in read_back; only a proven other head is an error,
and that error says the merge happened. A parent named without its sha
is a failed read-back, never an empty head said to have landed.
A fast-forward's commit is taken as the head only when the forge names
it so. That it does is assumed, not seen on this forge, so another sha
is said in read_back rather than raised as another head having landed.
The method's description lists all five methods and says which two can
be read back. The fast-forward test now gives the merge commit a
different second parent, so reading it as a merge would fail there.
The review of #193 approved it with five nits, all taken.
Seven characters of a sha can be matched by a commit made to match them
in seconds, so an author who force-pushes could have a review merge a
commit nobody read. A head is now named by at least 12 characters, up to
64 for a sha-256 repository.
What landed was read from the forge's record of the pull request, which
is the very thing a race leaves behind. It is now read from the merge
commit: its second parent for a merge, the commit itself for a
fast-forward. A squash or a rebase makes new commits that name no head,
and the answer says it cannot be read back instead of staying silent.
A merge of another head than the one handed over was reported as a
field beside merged: true, which a caller reading only that would miss.
It is now an error that says the merge happened and what landed.
The refusal no longer asserts that the head moved after the review,
which it cannot know; it says the head is not the one named.
mesh-controller#197 was reviewed at 065cfa0d, force-pushed to 3ced96f
at 12:14 UTC, checked green there at 12:22, and merged at 3ced96f at
12:26. The merge tool kept its promise that only a checked head is
merged, but it took no argument for the head the review approved, so it
merged whatever the head was and a commit nobody reviewed landed.
gitea_merge_pull_request now takes `head`, the commit the caller
reviewed (a sha, or at least its first 7 characters), and refuses with
nothing sent when it is missing, is not a commit, or is not the pull
request's head — naming the head it has now. The forge is handed the
same head as head_commit_id, so a push between the read and the merge
is the forge's to refuse. The answer reads back what was merged and says
so loudly if the forge merged another head anyway.
`head` is required, not optional: an optional guard is the shape that
let this happen. A caller that leaves it out is told what to name.
The review's two nits. A write of bytes identical to what is stored was
invisible to a comparison of content, so the test store counts its
writes and the preview's tests read that count: a persist from inside
Preview now fails five of them instead of one. And a router holding an
ask's record and no condition said it held none, rather than saying what
that key is; the branches are the other way round, with a test that
holds nothing else.
The review of #192 found three faults, all verified by running.
The tests did not prove the claim the change leads with: a probe that
sent, queued, recorded and wrote from inside Preview still passed,
because the condition they used was a warning needing nothing, which is
held rather than sent. They now hold an urgent condition, snapshot what
a send would move — the channels, the queue, the history, the refusals,
the stored record and the record held — assert none of it moved, and
tick once more. Every one of those probes now fails the tests.
`cleared` and `silenced` said wording no send would carry: an open
record has no clearing time, so a condition open four hours cleared
"after a moment", and no silence, so a silence read "until Mon 1 Jan
00:17". A clearing is now composed as if it cleared now, which the
answer says; a silence is refused unless the condition is under one.
The preview composed only its own words, while a send reduces a message
whose words the content rule refuses. The escalation walk is now one
function, allowedWords, which `words` calls for a send and the preview
calls alone — so the words read are the words that would leave, and
reading them records no refusal and states nothing on the bus.
withTracking hands back the line it put in, so the preview reports that
line instead of deciding the rule a second time; and a record that is
not a condition, or one carrying an open ask, is refused or says that a
channel's message carries more.
The words a condition reaches the operator with are composed in the
router, and nothing kept them: a send's history row holds a key and an
outcome, never text, and a notification still on the desk is not in the
notifier's history. So a wording could only be proven by waiting for a
send and reading the operator's screen, which is why proving issue 400's
tracking line stalled.
messenger_preview composes what a send would carry for a condition the
router holds open — the same words, the same tracking line, the same
verdict — and says whether the content rule would let them out. It
sends, queues and records nothing.
The operator could not tell from a notification whether its cause was
known, tracked and being fixed, or whether they had to act. The issue
tracker gains the verb tracking, answering for every condition key an
issue links which issues link it and where each stands; the operator
channel's router asks it outside the work and says one line from it in
every condition's message, per hq ADR 0285.
A password holding '=' and a space or comma splits the entry, and the tail
read as name=... was said as "the source <tail>", carrying part of the
secret into the health answer and the operator's conditions (review of
mesh-catalog#190, issue 386).
A password typed into an entry by mistake, in another scheme or split by a
space or a comma, was quoted back in the problem and so reached the status
and the health answer. An entry is now named by its source's name or its
position only; a user with a control character or a second @ is refused
(review of mesh-catalog#190, issue 386).
The live module check refused smb-users: a key naming a user is the
operator's value and may have no default, and a setting without one would
refuse to compose mounts on every node until set. The username now goes in
the source's URL (smb://<user>@<host>/<share>), percent-decoded; a
password there is refused unsaid, and a source without a user waits for
sources (hq ADR 0283, issue 386).
mesh/merge-gate fail: builds mounts → shanks; no bus step; a manifest the change touches fails the module check: modules/mounts/module.json: this manifest …
A configuration without the passwords directory would have read a path
relative to the process's working directory; it is now refused as a fault.
Review of mesh-catalog#190 (issue 386, ADR 0283).
mesh/merge-gate fail: builds mounts → shanks; no bus step; a manifest the change touches fails the module check: modules/mounts/module.json: this manifest …
The one INI secret smb-credentials could not be given through the desk
prompt, which takes one hidden line, and a missing login was said as a
fault: the first-node gate put the build back and the operator was told
nothing was theirs to do. The username is now the setting smb-users and
each password the one-line secret smb-password-<source>; a source missing
either answers waits through mesh-sdk's health answer (hq ADR 0283,
issue 386, issue 379).
The note's by is who acted as the runtime proves it (ADR 0275 §5); the
note's text names both workers. Tests for a resolved issue corrected, link
and set keeping the worker current, and a take-over reason with a secret's
shape. The ready-to-verify note says an issue resolves from fixing.
Two agents could start on one issue without seeing each other, and an issue
could be resolved without anyone having said they started its fix. A move to
diagnosing or fixing now names by, the worker as who@node; get and list show
the worker with since and stale; another worker is refused while it is
current unless take-over says why (noted); a worker untouched for
worker-stale-after (24h) may be taken over from without a reason. Resolved is
reached only from fixing. The moved event carries by, since and caller.
Fixing counts as not closed for the triage and the duplicate check.
hq ADR 0283.
mesh/merge-gate fail: builds mesh-delivery → novox; no bus step; a manifest the change touches fails the module check: mesh-delivery claims mesh-delivery a…
mesh/merge-gate fail: builds mesh-delivery → novox; no bus step; a manifest the change touches fails the module check: mesh-delivery claims mesh-delivery a…
The operator's budget (a leaf module running everywhere within five minutes
of its merge, a core module within ten) cannot be held to without measuring
it. Each delivery now keeps its delivery time, class and phases from the
walk the controller says; 'times' answers the week's median and worst per
class, the deliveries over budget and their longest phase, and one
delivery's breakdown; 'deliveries' is headed by the same line. A phase the
walk could not measure stays unknown, never a zero. Measurement only.
keep wrote the whole delivery on every call, and Reconcile passes every walk
the controller keeps through settleAndKeep every 30 seconds, so the
deliveries bucket was rewritten about once per walk per pass with nothing
changed: a key's revision no longer meant the delivery moved, and a watcher
of the bucket would receive a stream of non-changes.
keep now remembers each delivery as it last wrote it, encoded, and skips the
write when the delivery encodes exactly the same and is not waiting to be
written again. Anything a delivery holds that changes - its state, steps,
walk record, owed effects and their tries - changes its encoding, so every
change is still written; a failed write stays dirty and Flush writes it
again. Events, notes, views and forge statuses are owed and done as before.
A retry of an owed effect still writes its new try count: it is a change.
The test fails before (five passes over unmoved walks wrote five times) and
passes after; a walk that moves is still written and said.
The operator approved measuring from the delivery's own events whether each fix an
issue names reached the machines; delivered is not resolved, and a merged fix that
does not arrive is said on the issue and the operator's channel instead of found.
The operator asked for the judgement that already reads the backlog to fill what an
issuer cannot know, and for dependencies between issues, an issue being workable
only once what it waits for is on the machines. The triage fills only empty values,
adds links and removes none; readiness follows mesh-delivery's own events, said once
per spell, through a duplicate to its original, and loud when a delivery cannot be asked.
mesh/merge-gate pass: builds dnsmasq, docker, netcheck → ace, g14, novox, shanks; no bus step; 2 wait(s) for a person; every machine composes with the chan…
mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
An independent review found a pass could starve new issues' checks, two writers
could apply one verdict twice, a judged issue resolved by a person could still be
linked, and the dry run's answer raced its pass.
mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
The operator asked for duplicate issues to be consolidated as they are opened.
The tracker consumes its own opened event and asks the agent on its node, through
claude-code's new claude_code_judge, whether an older issue not resolved asks the
same question; it closes only the newer issue of a certain duplicate, keeping its
report on the older one, and fails loudly when it cannot judge. groom runs the
same judgement over the backlog as a dry run, applied on confirm.
dunst.started says the notifier's bundle started again: the notifications stay on screen and only the
answers they waited for are lost. Forgetting their ids made the router's message a second popup beside a
dead one. The desk now keeps the ids and says answers-lost-at; the router's message replaces each
notification in place.
Whether a message is said again is now its own (last said before the answers were lost), so a router
that started after the loss still says it and a repeated standing says nothing. It is sent to the desk
directly, not through the outbox, whose failure path moves a warning to Telegram; a failed send is tried
again at the next standing.
On 2026-10-10 the desk's notifier started again on two machines and the desk showed, from its own
memory, nine old popups: resolved, silenced and cancelled ones among them. Its memory held every handle
ever shown with buttons, and the router's edits (silenced, resolved) had reached only the newest handle
of each problem, so the rest stood as if open.
A channel's memory serves only to replace or edit a message in place. A notifier that started again lost
its screen: the desk forgets that machine's notification ids and says in its standing since when the
screen is new (shows-since). The router, which alone knows what is still open and unanswered, says that
again on that kind: one message per problem under its handle with the words and answers of now, a
silenced one edited to say so with no answers and no urgency, nothing that ended. Each move of
shows-since is acted on once, and waits while the desk cannot send rather than going to Telegram.
Telegram re-shows nothing on a restart (its messages stay in the chat) and says no shows-since.
An edit that offers nothing and reaches no screen its message is on is taken with nothing to change;
one that offers answers is said as failed, so the router says it anew.
The secret smb-credentials was an INI file alone, which the desk's one-line
prompt (secret ask, hq ADR 0277) cannot type: on the desktop the Pi's source
said "line 1 is not key=value inside a [section]". A login is now also one line,
`[recalbox] username=root password=…`, password last and to the end of the
line so it may hold spaces and `=`; the file form still reads, and a file may
mix both. A line the module cannot read is refused with the form it expected,
never with what was typed, and a login without a username is not answered. The
source's "login is not given" says the desk verb and the line to type.
The controller's secret-ask verb opens the launcher's hidden prompt from anywhere on the mesh, so the
operator must know whose ask they are typing into. The prompt takes an optional asked_by, the
controller's word on the bus's caller held to a name's characters and spaces, and says it; a sentence,
markup or a line break is refused, so the prompt's words stay the launcher's.
mesh/merge-gate pass: builds desk-channel, messenger, telegram → novox; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/delivery superseded: its walk was superseded and names no walk that took it over: a walk that took it over answers its merge, and it follows that wa…
Seen at 14:40: machine.shanks.units was silenced for 7 days with a reason,
and its critical popup stood on the desk as "Still open" while the
router logged nothing. A silence now edits the message where it is shown
to "Silenced until <day>: <headline>" with the reason, no answers and no
urgency, so the desk's popup times out; nothing more is said while it
lasts. When the silence ends and the condition is still open, the message
is edited back to its open words. Resolved while silenced, it is said only
where an edit notifies nobody. The silence is taken from the controller's
event and from the 10-min reading of the open set alike.
mesh/merge-gate pass: builds desk-channel, messenger, telegram → novox; no bus step; every machine composes with the change as it did without (4 of 4 compo…
- A comeback before the clearing was said rings when it is urgent now, and
says the reminder or escalation that was waiting when it cleared.
- Comebacks never said are counted apart, so "again, Nth time" counts only
what the operator was told; an urgent comeback after a said clearing rings
again in place of its message.
- telegram sends the new message before deleting the earlier one, so a
failed send leaves the operator a message.
- desk-channel keeps a machine's notification on record when its replace
fails, and says a message shown nowhere as failed.
- A store that cannot be read sends a new message instead of holding it.
- Tests for each, and for a reminder in a burst and a clearing the channel
could not take.
The operator stopped believing the mesh's notifications: one problem stood
twice on the desk (a reminder beside the message it followed, an escalation
then a reminder 31 s later), a unit failing again at each apply was said
again 22 times, and a question's buttons outlived it on a reminder the
ask's end never reached.
- messenger: a reminder or escalation is said in place of the problem's
message (Replace under its handle), alone, never as a digest line; a
problem first said in a digest gets its own message next time. A
reminder counts from when it was last said. A clearing is said once it
stays cleared 2 min (10 min once it came back); back before that, the
message continues without a word. Cleared before ever said and back:
said. An ask's expiry is said with its day.
- desk-channel: a send or show under a handle already shown replaces its
notification by the notifier's replace id.
- telegram: the same deletes the earlier message and sends the new one,
ringing as its kind rings; one it cannot delete loses its buttons.
The review found that a double tap while the phone's spinner turned would be
answered 'That answer was not taken', reading as if the approval failed; it is
now 'You already chose Approve at 13:30: recorded.' and does nothing more. The
fallback toast no longer promises a change that may not come (a stranger's
tap), the wait outlasts the router's 10 s wait for the controller's root word,
the channel's own refusals stay in the chat too, and the store's error words
stay in the journal.
At 13:30 on 2026-10-10 the operator approved two questions on Telegram and saw
nothing happen: the channel acknowledged each tap with no words, before the
router had judged it, and the edit that removed the buttons kept the question's
title, adding only a line under it that rings nothing.
The router now answers a chosen option in reply to the tap, once the ask's end
is recorded and before the question is edited: "You chose Approve: recorded,
and claude-code was told." (or, when the bus did not take the warrant, that it
is told when it does). A refusal already replied with its reason. Telegram
holds a tap's acknowledgement for the router's word and shows it as the tap's
own toast; a word with a body (a refusal's reason, Details) also stays in the
chat. Without a word in 8 seconds the toast claims nothing, and the late word
goes to the chat. A secret is never a toast.
An ask's own message, once answered, carries its outcome as its title:
"You chose Approve at 13:30 on Telegram · <headline>", then whether the asker
was told. The asks protocol carries no result back from the asker, so nothing
more is claimed of what it did.
The review found that each try at an unready channel composed its held messages and so made a Details
ticket for each, and enough tries pushed out the tickets of messages already shown.