Commit Graph
100 Commits
Author SHA1 Message Date
jschoubben ec00e13e22 Hold a decision record to the words rules only from 2026-10-11 (review of #206)
mesh/merge-gate pass: builds new: modules/mesh-knowledge, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
hq's words check never read a decision record, so a correction to any decision
of October 7 to 10 was refused for words it was allowed to write. The links an
accepted record holds are kept by its correction without being judged again.
2026-10-11 05:14:37 +02:00
jschoubben 10ab959d0f Tighten who writes and reviews a record, date the rules, bound history (review of #206)
mesh/merge-gate pass: builds new: modules/mesh-knowledge, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A revision came from anyone and a review was refused only to the first author,
so a co-writer could approve, and a record proposed before callers are proven
would later be reviewable by its own unproven author. Now a revision is taken
only from the proven author, a reserved number only from whoever reserved it,
and a review is refused from any writer of any version or for any record with
an unproven writer. The rules held records from no date, so a correction to an
older record was refused by rules hq never held it to; each dated rule now holds
from hq's own start date, and a new record cannot be dated before its
reservation. history and the question to ask are bounded like the other answers.
2026-10-11 05:10:02 +02:00
jschoubben ba7b455719 Add mesh-knowledge, the knowledge base's first slice (hq issue 425, ADR 0290)
mesh/merge-gate pass: builds new: modules/mesh-knowledge, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
An agent learns what a decision says only by reading whole records. This module
holds every decision record and research effort whole in a database in the store,
with clauses that carry their own status and source, typed links read from both
ends, and every version. It answers ask, get and holds within a stated bound, and
takes a record through reserve, propose (refused on any of the 29 per-record
rules), review by someone other than the author, and acceptance, on the
operator's proven warrant when the record needs it. The 4 whole-set rules make
the module unhealthy when they fail. The import reads hq at a commit through the
records module, never overwrites, and changes nothing when run again. Prose
decisions get drafted clauses that a second judgement must confirm before they
are served.
2026-10-11 04:58:35 +02:00
jschoubben 15a24fb63e mesh-delivery: share a carrier's walk only when it delivers the whole superseded member
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
A turn given on one carrier's waiting walk let the next member go while
another module of the member before it waited on a walk that could still
fail, and the stop then took the shared walk down. The turn is now shared
only when that one walk carries every module not yet carried, each by a
merge the forge said contains the member's; an unknown answer is asked and
waited for (novox/hq issue 445, re-review of #203).
2026-10-11 03:46:32 +02:00
jschoubben f7ba413c10 mesh-delivery: a member on its carrier's walk takes its turn, and a waiting member says for whom
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Under ADR 0276 the merge that carries a superseded member is often folded
into the next member's own walk, which waits for that member's turn: waiting
there waited for itself until the carrier's bound. The members on that walk
now take their turn, as members sharing one walk do. A member that waits says
which merge it waits for and until when, and `stalled` uses that bound; a
carrier still in a batch is bounded by the window and one walk; a member every
module of which was carried is reached, never stopped with no reason
(novox/hq issue 445, review of #203).
2026-10-11 03:41:52 +02:00
jschoubben 51a85c5998 mesh-delivery: a member after a superseded one waits while a later merge can carry it
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Superseded is no failure: the later merge that took its walk over may still
deliver its change, and a covered member counts as delivered (ADR 0270). So the
members after it wait while such a merge is open within its own bound, take
their turn once its delivered walk covers the member, and are stopped, saying
why, only when none can carry it (novox/hq issue 445).
2026-10-11 03:34:04 +02:00
jschoubben 1cf56688c1 Make the gitea module the one writer of mesh/merge-gate, so a verdict always lands last (hq issue 446)
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1791682027628755615 (novox/mesh-catalog at 87b0d3a5 into main,…
mesh-delivery read the gate as pending and then wrote "building on", and the gitea module could write the verdict in
between: the forge keeps the newest status, so a pass read pending and a failure hid under "building". The gitea
module now hears the build seat's started itself and puts every write of a head's gate on one chain, marking a
verdict the moment it is heard; a start from before the pending words were last set is not the current check's.
2026-10-11 03:23:52 +02:00
jschoubben 4254d17322 Say where a pending merge check is: queued since when, then building on which machine (hq issue 446)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
The forge's holder set mesh/merge-gate pending as "the mesh is mapping this head onto its module graph" and nothing
changed it until the verdict, so a ten-minute controller check read as a stalled forge (issue 423). The holder now
says the head waits in the build queue since the local time it was announced; mesh-delivery follows the build
seat's started event and says the machine building it and since when, never over a verdict the forge holds.
2026-10-11 03:14:06 +02:00
jschoubben 30788c46a1 mesh-delivery: give an unanswered line's checks as data, so the controller says their times in the operator's zone (novox/hq issue 443)
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/delivery-group group fix/443-a-stalled-lines-times-are-local delivered: every member is delivered
2026-10-11 03:10:22 +02:00
jschoubben 9bd8903ed2 mesh-delivery: test that an unanswered line clears when its delivery moves on or its pull request closes (issue 441)
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1791682028101285377 (novox/mesh-catalog at a5b4a757 into main,…
The review asked for the clearing paths to be pinned: a recheck or a merge
clears the line before the forge is read again, and a pull request gone from
the open list is no longer said.
2026-10-11 03:03:43 +02:00
jschoubben 5855c41f9d mesh-delivery: say a ready or rejected delivery whose check has not answered on the forge (issue 441)
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Ready and rejected have no bound, and the watch for unchecked heads skipped
every head with a delivery, so a lost or lagging status write left a pull
request unable to merge with nothing said. Such a head is now said on issue
438's rule: an unanswered line past the table's bound for a check asked and
not answered.
2026-10-11 03:00:23 +02:00
jschoubben 4870c9171e mesh-cli: build on the SDK that reads the engine's refusal after a failed write, so a caller hears busy, not broken pipe (novox/hq issue 444)
mesh/merge-gate pass: builds mesh-cli → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
2026-10-11 02:56:55 +02:00
jschoubben e3f29e6bb4 mesh-delivery: say where GroupState and groupTurns still differ, keep the stopped line whole
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
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.
2026-10-11 02:45:43 +02:00
jschoubben 036544ee0f mesh-delivery: a closed group whose members all ended ends, never delivering
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
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).
2026-10-11 02:39:45 +02:00
jschoubben 450892498d mesh-delivery: an unanswered line names its checks in a field of their own (issue 438)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/delivery-group group fix/438-a-check-that-never-answered-is-said delivered: every member is delivered
The controller's words for the line (mesh-controller #213) name the checks waited
on; a field spares them reading the sentence.
2026-10-11 02:25:48 +02:00
jschoubben 1f48a29495 mesh-delivery: a check that answers keeps the hour running, and a started head is unanswered (issue 438)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Review of #195: the clock restarted on any change in what a head waits on, so a
check answering pushed the line back by the time it took; it now restarts only on
a newer ask. A head whose check started was announced, so its line no longer says
unannounced; pending times are given in UTC like the module's other lines.
2026-10-11 02:22:16 +02:00
jschoubben 520763e98d mounts: list says an armed automount waits, not mounted, as the look already judges it (issue 437)
mesh/merge-gate pass: builds mounts → shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
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.
2026-10-11 02:17:47 +02:00
jschoubben 9d766b675d mesh-delivery: say a head whose merge check started and never answered (issue 438)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
The watch for unchecked heads counted any merge-check status as checked, pending
included, so a head left with a pending mesh/merge-gate, or never given
mesh/repo-check, waited unable to merge with nothing saying so. gitea_open_pulls
now gives each head status with its state and time, and the watch says a head
whose required check stays pending or unset past the delivery table's bound for
a check asked and not answered.
2026-10-11 02:16:34 +02:00
jschoubben 99df6b999b mounts: pass unit names to systemctl show after --, so the root mount's -.mount is not read as an option
mesh/merge-gate pass: builds mounts → shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
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.
2026-10-11 02:02:18 +02:00
jschoubben c87a4421b9 gitea: a read-back that fails after the merge says the merge happened (review of issue 381)
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
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.
2026-10-11 01:35:43 +02:00
jschoubben 9ef810d9c3 gitea: twelve characters name a head, and what landed is read from the merge commit (review of issue 381)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
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.
2026-10-11 01:33:17 +02:00
jschoubben c32eb6237d gitea: a merge names the head it was reviewed at (issue 381)
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
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.
2026-10-11 01:28:52 +02:00
jschoubben 15546f9d83 messenger: count the store's writes, and name an ask's record when it is the only one held (review of issue 422)
mesh/merge-gate pass: builds messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
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.
2026-10-11 00:49:38 +02:00
jschoubben d00cc8baf4 messenger: the preview is the sending path's own walk (review of issue 422)
mesh/merge-gate pass: builds messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
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.
2026-10-11 00:43:40 +02:00
jschoubben b7b34ddad7 messenger: read a condition's message before it is sent (issue 422)
mesh/merge-gate pass: builds messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
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.
2026-10-11 00:30:32 +02:00
jschoubben f5158ce5bf telegram, desk-channel: move to mesh-sdk go/v0.1.10, the tag of mesh-sdk #12's merge
mesh/delivery delivered
mesh/merge-gate pass: builds dunst, messenger, sudo, new: modules/desk-channel, modules/telegram → ace, g14, novox, shanks; no bus step; every machine comp…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
2026-10-09 17:08:17 +02:00
jschoubben f5cbfd850c rofi: write the hidden prompt from names and say the controller asks, never with the caller's words
mesh/merge-gate pass: builds rofi → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
The review of 2026-10-09 (M4): the prompt and its message were the caller's free text, so whoever could
ask the verb could put any words before the operator. The verb now takes the module, the secret and the
machine, checked as names, and writes the prompt here, saying the mesh's controller asks — which the bus
makes true, denying the verb to every other principal (mesh-controller #156). prompt and message are refused.
2026-10-09 17:08:17 +02:00
jschoubben 41b9e2d613 telegram, desk-channel: move to mesh-sdk 16984aa, rebased on its main 2026-10-09 17:08:17 +02:00
jschoubben 723e5f8cce telegram: say the desktop client's gap is a known one the router does not enforce (hq issue 344) 2026-10-09 17:08:17 +02:00
jschoubben 0284204989 desk-channel: run as its own account, and show a link's code only where a notifier heard unpaused will show it, kept in no history
The review of 2026-10-09 (H4). The desk ran in the operator's runtime, where agents run; it now runs as
the account desk-channel, as telegram does. A link's code is shown only on a named machine whose notifier
said in the last five minutes that it is not paused, and is sent secret, so the notifier keeps it out of
the history every caller of the verbs reads; while no such machine exists the desk says it cannot send,
and the router starts no link.
2026-10-09 17:08:17 +02:00
jschoubben c161521e49 dunst: keep what is shown once out of the history, and say whether notifications are paused
The review of 2026-10-09 (H4): a link's code shown on the desk stayed in dunst's history, which
node-notifier.history serves to every caller of the mesh's verbs, agents among them; and a code sent to a
paused notifier is held and shown later, to whoever is then at the screen. send now takes secret (transient,
category mesh.secret) and transient; the dunstrc's rules keep both out of the history, and history leaves out
any that got there. The pause, read from dunst itself, is said as the event paused at start, on change and
every two minutes, for the desk to read.
2026-10-09 17:08:17 +02:00
jschoubben 711e674ab1 telegram: take a tap only on a message showing the words this channel put there, and say where an approval from Telegram means something
The review of 2026-10-09 (M4): another holder of the token can change a message's words and leave its
buttons, so the operator taps Release under words that say something else. Each message's words are now
kept as a digest, per chat and message, and a tap on a message whose words differ, or that this channel
never showed, is refused on the phone and never said to the router.

The README says, as the operator directed (H5), that an approval from Telegram is meaningful only where
agents run under their own account without sudo, without the operator's X display, and with Telegram
Desktop's data readable by the operator alone; until then, approve from the phone.
2026-10-09 17:08:17 +02:00
jschoubben d991ef6b55 telegram, desk-channel: move to mesh-sdk 7690299, whose digests are over named fields (the review of 2026-10-09) 2026-10-09 17:08:17 +02:00
jschoubben 76db269c83 telegram, desk-channel: move to mesh-sdk d4077b4, where a warrant names the ask's digest (hq ADR 0259 §6) 2026-10-09 17:08:17 +02:00
jschoubben 8754de747d messenger: judge the break-glass file's owner by a faked owner both ways, so the test holds under root
mesh/merge-gate pass: builds messenger, sudo → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery stopped: the pull request closed unmerged
The build seat runs the suite as root, where every file the test makes is root's, so "a file not made by
root" was root's and unlinked (the forge's repo-check of 2026-10-09). The owner is now faked as another
account for the refusal and as root for the act; the test fails as before under unshare -r without this.
2026-10-09 17:08:17 +02:00
jschoubben ca07c5ab3b messenger: move to mesh-sdk go/v0.1.10, the tag of mesh-sdk #12's merge 2026-10-09 17:08:17 +02:00
jschoubben 3826741f62 messenger: ask the root-free word fresh before taking an ask
A word kept from a moment ago, not free, refused an ask taken just after root became free again, and
the asker keeps a refusal until something changes. Taking an ask asks the controller fresh, still
before the work is taken (the lab's proof found it).
2026-10-09 17:08:17 +02:00
jschoubben d136806745 messenger: an early desk unlink bars nothing, root lifts any bar, no root-free ask inside the work, no button without its effect (the confirmation review of 2026-10-09)
N1-router: another account's /start offered the desk an unlink of the operator's account in its first
hour, and a desk click, which a program on the desk machine can forge, unlinked it and barred it for a
day, again and again. An unlink at the desk in the first hour now bars nothing: any account, the
operator's too, links again at once with a code from the desk; the day's bar stays for an account
removed by its own approval or by root, and root's allow-link lifts any bar. The words say which.

Lows: the controller's root-free word is asked before the work is taken (it waited up to 10 s inside
it) and the answer is judged with that fresh word; inside the work only a recent word is read, and a
failed ask drops the old one. An answer whose effect the content rule withholds is not offered.
mesh-sdk moves to 16984aa. Each test failed before its fix.
2026-10-09 17:08:17 +02:00
jschoubben ff79defc2b messenger: approve only on the controller's recent word that root is free, and close the review's gaps (hq ADR 0259)
The review of 2026-10-09: the router read the absence of an agent-root condition as a pass, and its minute
hung on a break-glass file, taking the work it already held. An approval now rests on the controller's
verb root-free, asked live for the router's machine and the channel's, failing closed on every other
answer, and a stale word on the kinds promises no verified sender. Break-glass runs in the minute, says
on every channel what root did, and ends the router's own open questions. An ask offering an approval is
answered only where it could be approved; a message says who asks, what each answer does and what expiry
does. The router's own asks bind the account they are about; a link is checked against one per kind as it
is made; an unlinked account does not come back by a code within a day; a fresh link is unlinked from the
desk; no link begins while the desk cannot show its code, and a code is marked secret on every path. Links
are bounded per account in the router's state; a warrant's message id cannot be foreseen. Moves to
mesh-sdk 76902998 (canonical digests).
2026-10-09 17:08:17 +02:00
jschoubben f30089c288 sudo: count any sudo rule as root, fail on what cannot be judged, and commit no binary
The review of 2026-10-09: an unknown account and sudo that could not be asked were answered not root, and
only NOPASSWD on ALL counted, though NOPASSWD on systemctl, tee, bash or pacman reaches root as surely,
and a password the person typed is kept by sudo for minutes. Any rule now counts, as the node-engine's
own judge of an agent account does (ADR 0266); what cannot be judged fails the answer. The bundle built
in the module's directory is removed and ignored.
2026-10-09 17:08:17 +02:00
jschoubben c1d299336b messenger: send an ask's message alone even when only one answer can be given there
The lab's proof showed an ask whose approval was held back (an agent could become root where the
channel runs) folded into a burst's digest with a condition, and its one remaining answer, Decline,
lost with it. A message carrying an ask's answer now always goes alone.
2026-10-09 17:08:17 +02:00
jschoubben 1fc88b9915 messenger: tell the operator, by name and number, when another account answers a question meant for them
The lab's proof showed a stranger's refused tap said as "That answer was not taken" on the operator's
own chat (the router sends to the linked account), where it reads as the operator's own answer refused.
It now says which account tried, that nothing was done, and what it may mean.
2026-10-09 17:08:17 +02:00
jschoubben 8b00ffbfe9 messenger: name the ask's digest in every warrant, and bind the router's own asks to their act (hq ADR 0259 §6)
A warrant now carries the digest of the ask exactly as the router took it, so its asker acts only for
the ask the person was shown, with what each option binds. The router refuses an ask whose approving
option binds nothing (the SDK's Check). Its own unlink and relink asks bind the act they perform, and
it performs that act only on a warrant for that ask. mesh-sdk moves to d4077b4.
2026-10-09 17:08:17 +02:00
jschoubben 4e14ef9835 mesh-delivery: cover a held merge whose module a backlog walk carried to every machine (hq issue 351)
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
A build the controller held back for a person reaches its machines by a backlog walk on the
operator's word, which credits the delivery that published the build one step per machine and
never the walk's own "every machine running it". Its last record keeps no gate, only the machines
it is done on, so the credited steps stayed judging for ever, and `covered` read only the aggregate
steps, so the held merge had no end but a person's wrong act (the catalogue's #125, held 28 hours
after hq ADR 0270 went live).

A finished backlog walk now passes the steps it carried on the machines it is done on; `carries`
counts a module as sent everywhere when a passed step stands on every machine the held delivery's
plan sends it to; and a walk that carried a step still judging is asked for by its id, since one
that ended before the holder's restart may be older than the ended walks the controller lists
back. Three tests fail before and pass after.
2026-10-09 16:52:37 +02:00
jschoubben f3dcb9dc5a mesh-cli: carry what a pipe or a file gives standard input with the line, at most 64 KiB
mesh/merge-gate pass: builds mesh-cli → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
So a secret reaches the controller's terminal without standing on a command line:
mesh-cli secret accept <node> <module> <name> --from - < file. A terminal is never read, more than the
bound is refused before anything is asked, and nothing carried is printed. mesh-sdk moves to f047d0a.
2026-10-09 16:50:46 +02:00
jschoubben a6a9a818d2 Answer the review of hq issue 348's catalogue change
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/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
- The dnsmasq watch restarts only a unit that runs, without waiting for
  its start; a unit stopped, starting or failed is held and said (C2).
- dnsmasq_resolve asks only an address the resolver listens on (C3).
- Tests for the watch loop keeping its budget, a watch that wrote nothing
  or stopped looking, and a unit not running (C4).
- A restart is remembered as said only once emitted, restarts made before
  the bundle started are said, and whether one helped is said (C5).
- docker_events reads the runtime's whole buffer: complete when it is not
  full or reaches past the window, never a quiet machine read as lost (C6).
- netcheck: refused read with errors.Is; tests for addresses and ports,
  and for a container named as docker knows it.
2026-10-09 15:25:45 +02:00
jschoubben a0049f3e4e dnsmasq: bind each address once, at start, after it exists (hq issue 348)
bind-dynamic closes every listener whose interface a re-read of the
machine's addresses did not find, and a re-read during a bridge teardown
fails (dnsmasq 2.93 network.c:832-875, reproduced in a network namespace:
53 releases in 120 parallel teardowns). bind-interfaces never closes a
listener; a drop-in makes the unit wait for the private address and retry
a start that found it missing.
2026-10-09 15:04:45 +02:00
jschoubben 2b2397abe0 dnsmasq: ask the resolver a real question, and restart it when it stops answering (hq issue 348)
On 2026-10-09 the control node's dnsmasq stayed active and running, its
journal silent, and refused every question on its private address for
twenty minutes; nothing in the module looked past the unit.

The module is now Go (its TypeScript tools were never served): a root
watch asks the machine's own name on every listen address over UDP and
TCP every 15s, restarts the unit after two failing looks (2 min apart,
3 an hour), and says whether that helped. dnsmasq_health is the watch
process's declared check, so a restart that did not help becomes the
module's unhealthy condition; the service is judged by its unit.
2026-10-09 15:04:45 +02:00
jschoubben a588eb5983 xdg: keep the folders file trusted, so only the terminal sets the folders (hq issue 346)
mesh/merge-gate pass: builds xdg → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
user-dirs.dirs is shell assignments meant to be sourced by a shell, and a setting is pasted into it as
it is, so a folder of $HOME/x"; <command>; X=" set through the verb would add a command of the
caller's. With the file trusted again, the three folders are set at the controller's terminal alone
until settings are typed and quoted at render.
2026-10-09 12:40:04 +02:00
jschoubben 2d2c694306 betterbird: know a composer by its instance alone, and bound a restart's worst case (review of #155)
mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
A composer window whose class is not Betterbird's own was not seen, so a restart could lose its message.
Each step of a restart now has a bound short enough that all of them together, taken in full, stay
under the runtime's 30 s: 3 s for the window tree, 8 s to quit, 3 and 5 s to start, 4 s to see it.
2026-10-09 12:34:06 +02:00
jschoubben ca1bd18de6 picom: keep the configuration trusted, so only the terminal sets the backend (review of #157)
mesh/merge-gate pass: builds picom → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
A setting is pasted into picom.conf as it is, so a backend of xrender"; write-pid-path = "… adds an
option of the caller's: picom would then overwrite any file the operator can write. With the file
trusted, both settings are set at the controller's terminal alone until settings are typed and quoted
(hq issue 346).
2026-10-09 12:33:41 +02:00
jschoubben 73f2962bab picom: the backend and damage tracking are each machine's settings (hq issue 345)
mesh/merge-gate pass: builds picom → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A window mapped but left unpainted, or stale after a workspace switch, is a known failure of picom's glx
backend with damage tracking on some drivers, and the first suspect for the empty calendar reminder on
the workstation. Research 037 already found the backend to depend on the machine's driver. Both become
preferences whose defaults are today's (glx, use-damage true), so nothing changes until a machine is
given xrender or use-damage false to test it.
2026-10-09 12:25:08 +02:00
jschoubben 4fe8958dda xorg: refuse a window that is not on the screen, and keep an inline screenshot within the bus (review of #154)
mesh/merge-gate pass: builds xorg → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
A window that is unmapped or on a hidden workspace was cut from the screen anyway, which shows what is
there instead of it. An inline image of up to 4 MiB could not cross the bus, whose largest message is
1 MiB; it is now held to 700 KiB and halved in size until it fits, saying so.
2026-10-09 12:23:51 +02:00
jschoubben 3629089ced betterbird: never lose an unsent message, never answer an address, and keep the test apart (review of #155)
mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A restart ended Betterbird with whatever a composer held; now it refuses while a Msgcompose window is
open unless discard_unsent says the operator agreed, and answers within the runtime's 30 s. A
preference's value could carry an address, a user%40domain@host URI, a host or the home; those are
masked, recent-folder, account and cloud-file preferences and every add-on preference but the theme's
id are withheld, and a request for everything is refused. A log kept the token after Bearer or Basic.
The reminder test's Betterbird counted as the operator's and left its profile behind when it ended on
its own; it is now left out everywhere and its unit removes the profile when it stops.
2026-10-09 12:22:47 +02:00
jschoubben 52a4827983 xorg: list the server's windows, and cut a window's screenshot from the screen (hq issue 345)
mesh/merge-gate pass: builds xorg → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A reminder window that showed as transparent and empty could not be looked at: the window manager's
list leaves dialogs out and says nothing a compositor goes by, and xwd -id refused a window of a
32-bit visual with BadColor (X_QueryColors). xorg_windows answers every window with its class, and
on request its depth, type, state, role and own opacity; a window's screenshot is now cut from the
whole screen as the compositor shows it, and inline answers the image for a session that cannot
read the machine's files. Declares xorg-xwininfo, which both now use.
2026-10-09 12:07:29 +02:00
jschoubben a7953b969d betterbird: a module for the mail and calendar client, so its windows and preferences can be seen and declared (hq issue 345)
mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A calendar reminder showed as a transparent, empty window on shanks and no tool could say which
window it was or which preferences Betterbird ran with. The module owns one file, the user.js its
setting prefs fills, which its tools place whole into the profile Betterbird uses and never over an
operator's own; it reads named preferences without credentials or accounts, restarts Betterbird under
the user manager so its output reaches the journal, checks what it promises, and shows a reminder on
demand from a throw-away profile so a fix can be proved without the operator's calendar. No package:
betterbird-bin is the AUR's.
2026-10-09 12:07:29 +02:00
jschoubben 49f5ba377e The derived-value record is ADR 0202 (was 0201)
mesh/merge-gate pass: builds minio, sent nowhere; no bus step; 1 wait(s) for a person; every machine composes with the change as it did without (4 of 4 com…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
(cherry picked from commit ed10d96048)
2026-10-08 01:42:16 +02:00
jschoubben 69d6b9066f The mesh's one resolver, what every node asks, and a node's hosts file (hq ADR 0194, 0196, 0199)
- dnsmasq holds mesh-dns-resolver: provides wildcard-resolution mesh-wide, forwards every declared
  zone (zones fact), listens on the private address and loopback only, reads no hosts file and no
  operator's files, and no longer writes the container runtime's dns.
- resolv-conf names the mesh's resolver by address, then 1.1.1.1, timeout 1, one attempt; it now
  holds the runtime's live-restore, which dnsmasq held and every node needs.
- resolved-split-dns routes the suffix to the mesh's resolver by address, not 127.0.0.1.
- hosts: new module holding node-hosts-file — the machine's own lines in its block of /etc/hosts,
  the operator's lines kept, changed by entries/add/remove through sudo -n.
2026-10-05 20:37:59 +02:00
jschoubben 1a03caf8a5 The store collects nightly again (hq ADR 0189)
Withdrawn 2026-10-04 to unblock novox: while-stopped named the module-local
id and the host refuses a declaration naming a container it does not have,
whole. mesh-controller#259 fixed the namespacing and it has been live since,
so the window composes as distribution.store and the host will take it.

Unchanged from before: plain garbage-collect at 03:30 with the server held
still. The store is at 40G and nothing reclaims it until this runs.
2026-10-05 14:33:14 +02:00
jschoubben 83bd2afe66 Merge pull request 'restic: restore a single kept file, not only a directory' (#56) from fix/restore-a-file into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 12:22:17 +00:00
jschoubben 46e3a5a6b7 restic: restore a single kept file, not only a directory
The first restore of an arr app refused its settings file with "not a directory": restic restores
a snapshot's subfolder, not a file. A file is restored with its full path into a scratch directory
beside the target, moved into place, and the scratch removed.
2026-10-05 14:05:52 +02:00
jschoubben d67e786bf9 Merge pull request 'restic: declare sqlite, the tool SQLite stores are copied with' (#55) from feat/arr-backups into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 11:49:35 +00:00
jschoubben 7786507d45 restic: declare sqlite, the tool SQLite stores are copied with
The arr apps keep SQLite databases; a consistent copy of a live one is sqlite3's .backup. Declared
once, by the holder that runs the copies, rather than by each app.
2026-10-05 13:39:55 +02:00
jschoubben a70227a40a Merge pull request 'restic: ask as root whether a directory is there' (#54) from fix/backup-sees-as-root into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 10:38:25 +00:00
jschoubben 08c7a79f79 restic: ask as root whether a directory is there
The first run on the control node called postgres's dumps missing: the holder looked as the
runtime's account, which cannot see inside a store's 0700 data directory. Every other act already
runs as root; the existence checks do too now.
2026-10-05 12:31:07 +02:00
jschoubben d9120c6848 Merge pull request 'fail2ban: its tools in Go' (#53) from feat/fail2ban-in-go into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 10:24:17 +00:00
jschoubben 96ee0d7ad6 Merge pull request 'Back up every store: the restic module and the stores' contributions (hq ADR 0214, to-be 43)' (#49) from feat/node-backup into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 10:13:48 +00:00
jschoubben d43de93e49 fail2ban: its tools in Go
Go is the default for module code. One binary, fail2ban-tools, serving the node-intrusion-prevention
seat's four verbs and fail2ban_settings over the SDK, with the same parsing and the same tests; read
back against the control node's live daemon.
2026-10-05 12:01:25 +02:00
jschoubben 3a88bca21a restic: the holder in Go
Go is the default for new module code; the holder is one binary like the licence manager, serving
the seat's verbs over the SDK and running the nights beside them. Same behaviour and tests.
2026-10-05 11:58:18 +02:00
jschoubben 32cd92baeb Back up every store: the restic module holds node-backup, the stores contribute their dumps
ADR 0214 / to-be 43. restic keeps one repository per machine and takes a nightly snapshot per
module — 14 daily, 8 weekly, 6 monthly — and restores beside the live data, never over it. postgres,
mssql and mongodb contribute a consistent dump; minio, influxdb, the vault, mailu, gitea and
nextcloud the directories that hold their data.
2026-10-05 11:47:59 +02:00
jschoubben 316576f1da Merge pull request 'A withdrawn consumer keeps its data, in every provider that holds some (hq issue 241)' (#44) from fix/a-withdrawn-consumer-keeps-its-data into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-04 23:45:16 +00:00
jschoubben 1fb7ca3d72 A withdrawn consumer keeps its data, in every provider that holds some (hq issue 241)
mssql disables the login, mongodb takes the user's roles, minio revokes the key and keeps the bucket,
mailu disables the mailbox, gitea prohibits the login instead of purging the user and their
repositories, umami keeps the website. Each provider's create already enables what this locks.
2026-10-05 00:34:40 +02:00
jschoubben 190d711a2a postgres: a withdrawn consumer keeps its database; retiring renames, never drops (hq issue 241) 2026-10-05 00:33:12 +02:00
jschoubben 7aaf784bb5 Merge pull request 'Remove the catalogue's photos: the app's own repository defines it' (#278) from fix/photos-lives-in-its-own-repository into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-04 15:05:31 +00:00
jschoubben 4004dee825 Remove the catalogue's photos: the app's own repository defines it
Two definitions held one module name. Rebuilt to this catalogue's main on 2026-10-04, the mesh took
this stub — a server and an admin client — over the app's definition in photos.git, and five of its
six sites lost their routes. The app's repository is the source, as de-spiegel's and link2pay's are.
2026-10-04 16:35:28 +02:00
jschoubben 5d59b35cf7 photos authenticates against the database its user lives in (hq issue 232)
The provider creates each consumer's user in that consumer's own database.
photos asked for admin, where no such user exists; invoicing, against the
same provider, already asked for the name the mesh gave it and worked.

Invisible until hq 225 was fixed: while the provisioner could not read its
secrets, no user existed anywhere, so 'UserNotFound for db admin' was a true
and complete account of that fault. A fault that explains the symptom is not
evidence there is only one.
2026-10-04 12:33:56 +02:00
jschoubben 1bc6daf31b The photo clients publish the endpoint they declare (hq issue 227)
Each declares a web endpoint — 4001, 4012, 4013 — and published a bare 80,
which the mesh has nothing to assign for, so 80 reached the machine and
collided with the reverse proxy. Written the long way, the software's 80 is
published at the port the module declares and the mesh rewrites the outer
half to whatever it assigned.

photos is the one that failed on the control node; the other two are the same
fault waiting for a machine that runs a proxy.
2026-10-04 12:25:28 +02:00
jschoubben 525c639041 The store does not collect until every controller composes the window (hq ADR 0189)
mesh-controller#259 fixes while-stopped to name the container as the machine
knows it — `distribution.store`, not `store`. Until that controller is the
one composing, novox refuses its whole declaration and takes nothing at all.

The step comes out; deletion stays on, already applied and harmless on its
own. A collect step without its window would be worse than none: garbage
collection against a live registry can sweep a blob a build is pushing.

Put back once the fixed controller is deployed and stays.
2026-10-04 04:26:38 +02:00
jschoubben 159ed53103 Rebased onto main: ADR 0188 renumbered to 0201, and minio takes the sdk at 0.1.7
The bundles refactor took ADR 0188 on main, so minio's comments cite 0201.
The sdk is 0.1.7 after the same rebase, and minio needs the `derived` field
it carries.
2026-10-04 02:45:13 +02:00
jschoubben 723e676b75 The store enables deletion and collects nightly (hq ADR 0189)
REGISTRY_STORAGE_DELETE_ENABLED on the server — the door already accepts a
push — and a scheduled step running the registry's own collector over the
volume at 03:30 with the server held still. Plain garbage-collect: what the
mesh keeps is still a manifest, so --delete-untagged is not needed and would
delete images machines are running.
2026-10-04 02:34:06 +02:00
jschoubben 661114370f minio declares the bucket it derives; its consumers stop transcribing it (hq ADR 0188)
serves.s3-bucket.bucket is ${consumer:as:dns}; the provisioner uses what it
is given. nextcloud, invoicing and photos ask for ${bound:s3-bucket:bucket}
instead of naming mesh-novox-* literals, which also named this node.
bucketFor and the long-dead accessKeyFor are gone.
2026-10-04 02:34:06 +02:00
jschoubben 0e8ad3a8e1 Merge pull request 'dnsmasq: restart on the mesh hosts region, which it reads only at start' (#241) from fix/dnsmasq-rereads-the-names-region into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-03 13:29:29 +00:00
jschoubben b43e405947 dnsmasq: restart on the mesh's hosts region, which it reads only at start
The names region of /etc/hosts is written by mesh-wireguard and dnsmasq answers from it, but read
it once at start: after the mesh stopped publishing public names (hq ADR 0191) every machine's
hosts file was right and every resolver still answered mail.novox.be with a tunnel address. The
config comment that says so changes the file, which restarts dnsmasq once everywhere.
2026-10-03 15:29:28 +02:00
jschoubben 38be3ba7f0 Merge pull request 'ssh-client: own ~/.ssh, not the openssh package' (#238) from fix/ssh-client-owns-no-package into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-03 10:43:46 +00:00
jschoubben a19638d112 ssh-client: own ~/.ssh, not the openssh package
A package has one owning module, and sshd already owns openssh on every node — assigning
ssh-client was refused for both declaring it, and the refusal left every node unresolvable
until it was unassigned. Owning it was wrong besides: unassigning ssh-client would have
removed the package sshd serves from. The client binary ships in the package sshd holds.
2026-10-03 12:43:35 +02:00
jschoubben 1cbddeb104 Merge pull request 'ssh-client module: the mesh owns ~/.ssh, config from the hub (to-be 29)' (#237) from feat/ssh-client-module into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-03 10:40:13 +00:00
jschoubben 301aeda5f3 ssh-client module: the mesh owns ~/.ssh, config from the hub (to-be 29)
Requires openssh; creates ~/.ssh (0700, owned by the operator account via
${machine:account}); writes every other node's Host block (HostName + User
<account>) into a marked region of ~/.ssh/config (home-scoped, into:block), so
`ssh <node>` reaches each peer as the right account and the operator's own
config is kept. Universal-tier: assigned wherever a person logs in; a node with
no account gets no config.
2026-10-03 11:58:05 +02:00
jschoubben 85af10ebad Merge pull request 'step-ca: stop offering acme-ca, so public names are certified by public-acme' (#234) from fix/step-ca-is-not-the-public-acme-ca into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-03 08:57:25 +00:00
jschoubben 0778f8f0ae step-ca: stop offering acme-ca, so public names are certified by public-acme
step-ca and public-acme both offered acme-ca on novox, and route-proxy's pin names a node, not
a module — so which one certified the public names depended on provider order. After the
controller restart on 2026-10-03 it came out as step-ca, and every public site served a
certificate no browser trusts. step-ca's own names are already certified through
internal-acme-ca; acme-ca is the public authority's alone.
2026-10-03 10:54:43 +02:00
jschoubben a309deb479 Remove portainer: deprecated, and unassigned everywhere
It held the docker socket behind a public name. Nothing depends on it;
it is off both machines that ran it, with its data.
2026-10-02 23:25:45 +02:00
jschoubben 2e6cc71f7a gitea: the jail also bans what gitea's sshd refuses
The jail read gitea's container journal, which carries its sshd's lines,
but matched only the web login. 167 ssh attempts an hour from the
internet went unbanned. Two patterns, one per attempt: an unknown user,
and a user sshd refuses; tested against a day of the real log, 946
matches and none on an accepted login.
2026-10-02 23:23:35 +02:00
jschoubben 304044da40 A ban list never holds a neighbour (hq ADR 0186)
The home server banned the house's own router within an hour of the first public jail: the router
reflects local traffic, so every client in the building arrives as the gateway's address. Every
private range joins the mesh's own in the never-ban list.
2026-10-02 18:42:16 +02:00
jschoubben 23112b111c The proxy's jail reads both refusals, each pattern naming the host once
fail2ban expands <HOST> to a named group, so two in one pattern is a duplicate group name and
the daemon refuses to start at all -- every jail on the machine, not just this one. Two patterns,
one <HOST> each: the certificate refused for an unserved name, and the request refused for one.
Caught live on the control node (hq ADR 0179).
2026-10-02 17:23:42 +02:00
jschoubben b547308e05 The proxy's jail reads a refused name as well as a refused certificate
The pattern ended at the line's end, which only the certificate refusal does; a request for
an unserved name carries trailing text and never matched. Caught against the live lines
before the jail counted anything (hq ADR 0179).
2026-10-02 17:20:54 +02:00
jschoubben 1601d5a335 fail2ban holds the intrusion seat's verbs and composes the jails; mail, forge and proxy declare theirs (hq ADR 0179, to-be 31)
The module gains a runtime carrying only the fail2ban client with the daemon's socket shared in,
serving status/banned/ban/unban and its own fail2ban_settings. It declares jailing, so the
controller's composition lands in jail.d/mesh.conf and filter.d; mailu, route-proxy and gitea log to
the journal and declare a jail reading it by container name. The base is strict: three in a day for
a day, twice banned in two weeks for four; the mesh's range stays never banned.
2026-10-02 17:02:49 +02:00
jschoubben 3dfbad6f03 nftables declares the ufw front end absent once its filter is loaded (hq ADR 0175) 2026-10-02 16:27:34 +02:00
jschoubben 6dfd2401c9 lab: the image carries what the builds and the lab call: python3, file, iproute2, sudo 2026-10-02 15:23:52 +02:00
jschoubben 8cd4f199f1 lab: a run resolves the @novox scope from the forge's package registry 2026-10-02 15:13:22 +02:00
jschoubben bce7b3a551 lab: compile from the module's root, so the runtime finds its tools
Both sources sit in tools/, so tsc took tools/ as the root and wrote
dist/index.js, while the runtime loads dist/tools/index.js: the module
started and served nothing.
2026-10-02 15:08:01 +02:00
jschoubben b77582f6a6 Cite hq ADR 0170, not 0169: the firewall seat's record was renumbered after a collision on hq main 2026-10-02 14:52:26 +02:00