Commit Graph
495 Commits
Author SHA1 Message Date
jschoubben c957f1ec5e Run a failed run-once process again on every apply until it passes (novox/hq issue 467)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
A failure recorded nothing, so the record of the step's last success stayed and
the next apply skipped it, opening ADR 0136's gate on what it had just refused.
The record is kept, for removal, with what it was run as cleared.
2026-10-11 11:07:17 +02:00
mesh-admin 85c51ebb59 Merge pull request 'A refused line's request is read before it is answered, so a busy line reads busy (issue 434)' (#80) from fix/434-a-busy-line-is-answered-busy-whatever-the-load into main 2026-10-11 00:55:47 +00:00
jschoubben f9805b2113 Bound what a refusal costs the engine, and let the busy tests fail instead of hang
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
A refusal waiting for its request parsed it into a 64 KiB reader for up to 5 s,
on a socket every account may open. It now drains at most 4 KiB through a small
buffer for at most 1 s and never past the accept-time deadline, only 32 wait at
once, and an unknown account is refused before it takes a slot. The busy tests
wait for the first line with a timeout and say what came of it (novox/hq issue 434).
2026-10-11 02:47:11 +02:00
jschoubben 70cb613614 Read a refused line's request before answering it, so a busy line is read as busy
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
The engine answered busy and closed before the client's request had landed;
the client's write then broke and it never read the refusal waiting for it.
Under the whole suite's load this failed the busy test (novox/hq issue 434).
2026-10-11 02:35:20 +02:00
mesh-admin bd5cc69804 Merge pull request 'The setuid walk paces itself by the machine's I/O pressure (issue 433)' (#79) from fix/433-the-setuid-walk-yields-to-a-busy-disk into main 2026-10-11 00:03:46 +00:00
jschoubben f311fa36dc The setuid walk is paced before each file too, so a large directory is not read blind (review of issue 433)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
A file's own details (lstat) may be read from the disk as a directory is,
so a directory of tens of thousands of files was read without a look and
the header's promise that no burst is longer than PaceLook did not hold.
The walk now asks the pacer before each file as well; the 250 ms gate
makes that a clock read per file. A test with one directory of twenty
files fails without it. The clock is read once after a run of pauses,
and the constants test says that it guards the constants only.
2026-10-11 01:55:39 +02:00
jschoubben 30242197fa The paced walk keeps a tenth of the time, and reads the pressure since its last look (review of issue 433)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
The review of #79 found that pacing alone brought back issue 361's
fault. The control-node's own I/O pressure sits near the 10 % bound with
no walk running, so a walk that paused 30 s for every 250 ms it read
would read 0.8 % of the time: a 16-minute walk would take some 32 hours,
outlive the age a search judges for, and be begun again at an engine
restart, leaving the machine unjudged.

A run of pauses is now at most nine times as long as the walk read since
the one before, so the walk keeps at least a tenth of the time whatever
the pressure, and takes at most ten times as long as unpaced: about
2.7 hours for the 16-minute walk, inside the 12 hours the controller
waits quietly. A test ties the bound to rootsearch.Quiet.

The pressure is the share of the time since the last look in which some
task waited on I/O, from PSI's cumulative counter, not its ten-second
average, which the kernel moves only every two seconds. A pause is
500 ms, then the pressure is looked at again.

The engine's walker is built in one place, engineWalker, and a test
fails if it is not paced by /proc/pressure/io. What the walk cannot read
is said through the search's own channel (the cache's Say), as the rest
of the search is, with what it means for the disk.
2026-10-11 01:53:25 +02:00
jschoubben 8f7db549ae The setuid walk paces itself by the machine's I/O pressure (issue 433)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
The walk for setuid programs is put in the idle I/O class so it yields
to everything else, but only a scheduler that schedules by class (BFQ)
honours that. The control-node's disks run mq-deadline, which does not,
so the walk read at full priority. On 2026-10-11 the walk started at
01:27, right after the engine applied a new controller, and ran sixteen
minutes; it kept two spinning disks saturated, the bus whose store
shares them stalled for half a minute twice, and the controller lost
its lease and restarted each time.

The walk now also paces itself by PSI, which reads the same whatever
the scheduler: before it reads a directory, while some task has waited
on I/O for more than 10 % of the last ten seconds, it pauses two
seconds at a time. It looks at most every 250 ms, pauses at most 30 s
in a row and then reads one more directory, so a machine that is never
quiet is walked slowly but still judged. Its own reads count in the
pressure, so on an otherwise idle machine it holds the disk below the
bound. Pressure it cannot read pauses nothing, and is said once.

A paced walk finds what an unpaced one finds; a walk ended while it
pauses is an incomplete walk, never one that found nothing.
2026-10-11 01:47:43 +02:00
mesh-admin 5226d7bcd0 Merge pull request 'Node-engine: state a tool check that waits for the operator as waiting (issue 386, ADR 0283)' (#78) from feat/386-a-wait-for-the-operator into main 2026-10-10 20:03:27 +00:00
jochen 9545121ec9 Clear an old pass on a wait, and say a change of waits (review of #78)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
A resource that passed, then waited, then failed once after its grace was
still stated healthy from the old pass; and a change of what it waits for
alone raised no change event, so the controller could miss it.
2026-10-10 21:14:14 +02:00
jochen 3abb07f1e2 State a tool check that waits for the operator as waiting (issue 386, ADR 0283)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-host → 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
A module whose only fault was a secret the operator had not given yet was
stated unhealthy, failed its first-node gate and was put back. The engine
now reads the tool's answer through mesh-sdk go/health and states such a
resource waiting, with what it waits for, on one look; a malformed wait
stays unhealthy.
2026-10-10 21:11:02 +02:00
mesh-admin 9d8aa9d6f0 Merge pull request 'Carry the controller's grant to ask mesh-delivery for its delivery times in the installer's first user list (hq ADR 0282, issue 382)' (#77) from feat/382-carry-times-grant into main 2026-10-10 18:39:44 +00:00
jochen 212a3cc113 Carry the controller's grant to ask the delivery's owner for its delivery times in the installer's first user list (hq ADR 0282)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
2026-10-10 20:32:16 +02:00
mesh-admin cc0b8554b1 Merge pull request 'Show how far the setuid search got, and start a fresh one when the terminal asks (hq issue 361)' (#76) from fix/361-the-setuid-search-shows-its-progress into main 2026-10-10 00:21:51 +00:00
jochen 0b4c86e3be Count a setuid find only where the account can execute it, read at every judgement (hq issue 361)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
The two finds on the control-node sit in the operator's home of mode 0700, which the agent account cannot
search. The search keeps every find; each judgement reads the modes on the way again, since an account can
open its own home without root. A find in a home is said by count, its path in the journal alone. Also
from review: a checkpoint racing SearchNow forgets the walk it just kept, and a walk kept by an older engine
says its counts as lower bounds.
2026-10-10 02:00:10 +02:00
jochen 8e79403c21 Show how far the setuid search got, and start a fresh one when the terminal asks (hq issue 361)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestNatsAnEnrolmentWithNoReplyAddressIsNotAnswered (0.00s)
mesh/delivery superseded: a newer head of the same pull request
The walk kept its progress every minute and nobody could read it: root-free said only that a search ran.
The account verdict now carries the progress (counts, times, mount points; never a path) as fields and in
its reason, outside what makes a verdict changed. A signed ask from the controller's terminal throws the last
search away and starts a full one, after the operator removed by hand what it found.
2026-10-10 01:55:58 +02:00
mesh-admin b8c8546118 Merge pull request 'Let the setuid search run to completion, resumably, and judge from the last complete one (hq issue 361)' (#75) from fix/361-the-setuid-search-runs-to-completion into main 2026-10-09 23:18:44 +00:00
jochen a7270f00cb Keep the walk's paths as bytes, forget a walk root superseded, and leave other mounts unread (hq issue 361 review)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
mesh/delivery-group group fix/361-the-setuid-search-runs-to-completion delivered: every member is delivered
The review found a cursor of a name that is not UTF-8 came back changed
from JSON, so a resumed walk skipped what it stood before; an ask to
search again was lost to a restart, which carried the older walk on; a
fresh search judged a filesystem it never walked; a mount point root
cannot read failed or hung the walk; a found file gone before the package
manager was asked failed every resume; and skipped or kept outcomes
started a search on every reconcile, back to back with a long walk.
2026-10-10 01:10:56 +02:00
jochen 2dbbda51c6 Walk for setuid programs in the engine and carry an interrupted walk on (hq issue 361)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery-group group fix/361-the-setuid-search-runs-to-completion ready: every member ready, and composed together they pass
mesh/delivery superseded: a newer head of the same pull request
The control-node's walk takes far longer than the engine runs between
updates, so a search that starts over at every restart never completes.
The walk is the engine's own, in lexical order, so its progress is one
cursor per filesystem, kept every minute beside the engine's state and
carried on after a restart while younger than rootsearch.FreshFor. It runs
on a thread of its own at nice 19 and the idle I/O class.
2026-10-10 01:00:47 +02:00
jochen 2410796869 Let the setuid search run to completion and judge from the last complete one (hq issue 361)
A machine with millions of files never finished the walk within fifteen
minutes, so its agent account was never judged and the router refused to
link. Only root can make a setuid-root file, so a complete search stays
sound until root acts: it now runs at idle priority with no bound, is kept
beside the engine's state, judges for rootsearch.FreshFor from its start,
runs again daily and after an apply that moves accounts, packages, actions
or sudo rules, and an incomplete or stale search is never free.
2026-10-10 00:53:05 +02:00
mesh-admin 090b9082eb Merge pull request 'Renew the engine placed by hand to the proved build, and take a hand-over from the controller's terminal (hq issue 356)' (#74) from fix/356-hand-over-by-nox-and-the-engine-on-path-renewed into main 2026-10-09 18:07:21 +00:00
jochen 8fc1d72cd5 Keep the setuid search's time on the caller's clock
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery failed: its walk failed: a gate on a first machine (what it carried put back), a build, a machine
SetuidCache compared the caller's now with a time read from the wall clock when the search finished. When the caller's clock ran behind the wall clock, a search was never due again: TestTheSetuidSearchRunsInTheBackgroundAndIsKeptForItsInterval, whose clock is a fixed date, has failed on main and every branch since 2026-10-09 18:00 UTC. A search's time is now the now it was started at; a result reads at most one search older than it is, which errs towards searching again.
2026-10-09 20:04:11 +02:00
jochen 9a807de659 Give a handed-over directory through no link on every apply, not only the first (hq issue 356, review)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheSetuidSearchRunsInTheBackgroundAndIsKeptForItsInterval (0.00s)
mesh/delivery superseded: a newer head of the same pull request
After the first apply the record is no longer used as found, so the directory read as the mesh's own and later applies set its mode and owner by path: a parent swapped for a link after the hand-over took carried root's chmod and chown to its target. The hand-over is now checked before that shortcut, so a handed-over directory always goes through applyHandedOver. The test swaps the parent and the grandparent after an apply already gave the directory its mode; both cases fail with the old order.
2026-10-09 19:58:27 +02:00
jochen fc6c5a92e6 Believe a hand-over only signed by the mesh, and give it through no link (hq issue 356, review)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
Two holes the review proved. The ask's subject proved nothing — a responder can reach it through a reply — so the engine now verifies the mesh's signature over a fixed context with the key it verifies declarations with, before reading anything, and refuses an ask for another node, expired, good too long, without a nonce, or replayed. And the apply followed a link the hand-over had refused: a component swapped for a link after the record carried root's chmod and chown to its target. A handed-over directory must now still resolve to itself, and is changed only through one descriptor opened from / with no link followed (fchmod, fchown). Tests for the reply guard, the run loop's hook, the fallback renewal on proof and a non-regular fallback; 27 checks each removed in turn, each failing a test.
2026-10-09 19:45:00 +02:00
jochen e74ee82a76 Renew the engine placed by hand to the proved build, and take a hand-over from the controller's terminal (hq issue 356)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
The file on PATH was the engine a person copied at installation, and every delivered engine ran from the versions directory: the operator typed hand-over and a build from before it answered "unexpected argument". A proved engine now copies itself over that file when the bytes differ (never a link). And the hand-over of a directory used as found (issue 339) gets its channel: the controller's terminal asks this node's engine on mesh.node.<node>.ask.hand-over; the engine judges every value — the ask's size and shape, who asked, the path as it states it, a directory and no link, through no link — and records it or refuses and records nothing. Every text the engine addresses to the operator names the nox line (ADR 0272); the engine's own subcommands are install-time and rescue, and its usage says so.
2026-10-09 18:34:10 +02:00
mesh-admin b0d03be7a4 Merge pull request 'mesh-cli: forward the line's standard input to the controller, with the line alone (hq ADR 0259 §10, ADR 0272)' (#73) from feat/a-terminal-line-takes-standard-input into main 2026-10-09 15:58:48 +00:00
jschoubben 4ea6a2f04e mesh-cli: forward the line's standard input to the controller, with the line alone
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
mesh/delivery-group group feat/a-terminal-line-takes-standard-input stopped: a member was stopped
A secret given at the controller's terminal through mesh-cli (secret accept … --from -) never reached the
line: the engine forwarded the words only. It now forwards what mesh-cli's standard input held (at most
64 KiB, the SDK's bound) on the same core request to the controller, says it nowhere, and sends none
with a follow of the same call. mesh-sdk moves to f047d0a.
2026-10-09 16:34:45 +02:00
mesh-admin d8ff1540df Merge pull request 'Installer's first user list: the controller's grant to ask the desk for a secret (hq ADR 0259 §10)' (#68) from feat/a-secret-given-at-the-desk into main 2026-10-09 14:17:41 +00:00
mesh-admin cdc2af7b43 Merge pull request 'Installer's first user list: the controller's grants to ask the operator (hq ADR 0259)' (#59) from feat/the-controller-asks-the-operator into main 2026-10-09 14:17:38 +00:00
mesh-admin 7a2ffc0a4c Merge pull request 'Read the session record strictly, and make its tests hold (hq ADR 0272)' (#72) from fix/272-the-session-record-read-strictly into main 2026-10-09 12:12:11 +00:00
jochen b1e7b6d26e Read the session record strictly, and make its tests hold (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
- UID, CLASS and SCOPE are read at their first occurrence: logind writes
  each once, and a later line saying otherwise is not the record's word.
- A comment says /run/systemd/sessions is systemd's private format, and
  that a format change fails closed: no session, so nobody is the terminal.
- Tests that hold, each proved by the mutation it guards failing:
  - a record naming UID=1001 for session-8.scope under user-1000.slice,
    asked as uid 1000 (the record's UID check alone);
  - session-a.b.scope with a passing record (the id's characters alone);
  - session-9.scope/x.scope (the scope being the slice's direct child);
  - a key said twice, either way round (first occurrence);
  - the client sends its line only after the session was read, so reading
    the session in the forward fails the test.
2026-10-09 14:05:53 +02:00
mesh-admin 512b0edb02 Merge pull request 'A login session is the account's own, of class user, read at accept (hq ADR 0272)' (#71) from feat/272-the-session-is-a-users-read-at-accept into main 2026-10-09 12:04:18 +00:00
jschoubben 4ba6d0864e Carry the controller's grant to ask the desk for a secret in the installer's first user list (hq ADR 0259 §10)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
mesh/delivery-group group feat/a-secret-given-at-the-desk delivered: every member is delivered
mesh-controller #156 (give) grants the controller mesh.seat.node-launcher.tool.secret.*; the
controller's test holds the installer's list equal to what it composes, and failed without it.
2026-10-09 14:04:01 +02:00
jochen 73e0916968 Carry the controller's grants to ask the operator and act on a warrant in the installer's first user list, so a mesh raised from it is not refused them (hq ADR 0259)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-host@4ba6d0864e3d (merged as d8ff1540 into main, walk plan-17915554870866…
mesh/delivery-group group feat/the-controller-asks-the-operator delivered: every member is delivered
2026-10-09 14:03:58 +02:00
jochen bf3f45e769 A login session is the account's own, of class user, read at accept (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
The cgroup names a scope; only logind's record of it says whose session it
is. The engine now takes a session only when /run/systemd/sessions/<id>
says it is this account's (UID), of class user (not a greeter's or the
user manager's), and of this very scope. And it reads the session the
moment the connection is accepted, with the namespaces, before the line,
so the peer is judged as it was when it connected.

Tests: session-9.scope with a user record is taken; x.scope, a different
uid, another account's session, a greeter's, a record naming another
scope, no record, and a path with dots are not; the session is read
before the line is forwarded.
2026-10-09 13:55:45 +02:00
mesh-admin fd13834c0d Merge pull request 'Say the login session a mesh-cli line comes from, and pin the SDK at go/v0.1.9 (hq ADR 0272)' (#70) from feat/272-a-login-session-and-the-tagged-sdk into main 2026-10-09 11:54:31 +00:00
jochen 4a5dcb0209 Say the login session a mesh-cli line comes from, and pin the SDK at go/v0.1.9 (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
A service running as the operator — the tool runner, a unit of the
account's user manager — shares the operator's user id, and was as much the
terminal on the control-node as a person typing. The engine now reads the
asking process's cgroup and sends its login session's scope
(session-<id>.scope under the account's own user-<uid>.slice), or nothing;
the controller makes nothing else the terminal. The SDK is pinned at its
tag now that go/v0.1.9 exists.
2026-10-09 13:40:17 +02:00
mesh-admin 1ac7741c63 Merge pull request 'Vouch for the account that runs mesh-cli, and ask the controller its line (hq ADR 0272)' (#69) from feat/272-the-engine-vouches-for-mesh-cli into main 2026-10-09 11:38:39 +00:00
jochen f684967c3b Merge main, and close what the confirmation review found (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
- Any failure of a request the link may have delivered now says the line
  may have run; "nothing ran" is said only of a request the link knows it
  never asked (no link open, nobody listening: link.ErrNotAsked).
- A test holds that a connection saying nothing is answered within its
  deadline, set at accept, and gives its slot back.
- The README says the socket refuses a process outside the machine's own
  namespaces, so flatpak, toolbox and distrobox shells are refused.
2026-10-09 13:09:48 +02:00
jochen 655f515aa7 Merge remote-tracking branch 'origin/main' into feat/272-the-engine-vouches-for-mesh-cli 2026-10-09 13:09:16 +02:00
mesh-admin 8f763ee911 Merge pull request 'Judge whether an account declared never to become root can (hq ADR 0266)' (#61) from feat/an-agent-account-never-becomes-root into main 2026-10-09 10:56:00 +00:00
jochen 564948c2f2 mesh-cli: follow a line to its answer, bound each account, refuse other namespaces (review of hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
- A request that timed out was said as "the controller was not reached,
  nothing ran", though the line may have run. The engine now follows a line
  the controller answers as running, by its call, until it ends; when no
  answer comes in time it says the line may have run and names the call.
  "Nothing ran" is said only of a request no controller took.
- One account could hold the socket: accepting waited for a free slot, and
  a connection could stall before its deadline was set. The deadline is set
  at accept, before any lookup; each user id has two lines at once and the
  machine eight, and one over is answered busy at once.
- A container running with the operator's user id shares the number, not
  the machine: a peer whose mount or user namespace is not the engine's is
  refused, and nothing is asked.

SDK pinned at the go/cli branch's newest commit until go/v0.1.9 is tagged.
2026-10-09 12:48:20 +02:00
jochen 1c61b72f35 Read the homes in one order, so their test does not depend on a map's
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery delivered
The homes come from a map of the user database, and the test compared them in
the order it happened to iterate: it failed one run in four, the repo check
of 2026-10-09 among them.
2026-10-09 12:36:56 +02:00
jochen 2673e7a2c9 Let the setuid search run to its 15-minute bound, ask pacman once, and call a walk with errors incomplete
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestHomesAreAPersonsOrAnAgentsNotAServicesOrRoots (0.00s)
mesh/delivery superseded: a newer head of the same pull request
The search kept a two-minute limit of its own inside its 15-minute bound, so
a long walk always ended incomplete; pacman was asked once per path; and find
exiting with an error but some output was read as a complete walk. The bound
and the not-judged reasons move to the public rootsearch package so the
controller reads the same values (the review of 2026-10-09).
2026-10-09 12:11:57 +02:00
jochen 021d860ca4 Vouch for the account that runs mesh-cli, and ask the controller its line (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
The records send the operator to "the controller's terminal", and nothing
reaches it (hq issue 343). mesh-cli holds no credential: it asks the engine
on its own machine over a socket. The engine reads the asking account from
the kernel (SO_PEERCRED), never from the program, and asks the controller on
mesh.control.<node>.cli, its own node's subject, so the bus server says which
node asked. The controller decides what the line may do; nothing received
on the socket is run or applied here, and the socket has no network address
(ADR 0004 stands).

The local protocol is the SDK's go/cli package, at a pseudo-version of its
branch until that merges and is tagged go/v0.1.8.
2026-10-09 12:05:53 +02:00
jochen b28d7bbcbf Search for setuid programs in the background, and judge each polkit rule alone
mesh/merge-gate pass: builds mesh-host → 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
mesh/delivery superseded: a newer head of the same pull request
A search over a large disk ran inside the look, holding the judge's lock and
stalling the health statement; it now runs apart, serving its last result,
backing off after a failure, and saying not judged until it has one. And a
rule naming a user, or a comment, no longer hides another rule's
unconditional yes (hq ADR 0266).
2026-10-09 10:10:05 +02:00
jochen e2e8be72fa Keep main's placement guard, and refuse only an archive or a written-into file below an agent's home
Issue 339's guard (#63/#64/#65) now refuses the machine's own trees, another account's
directory below a home, and uses a found directory as found; this branch's own copy of
those rules goes. What stays is the one rule only an account that never becomes root
needs: below its home it owns every parent and could swap a link in between the
engine's check and an unpack or a write-into (hq ADR 0266 §7). One reader of the user
database serves both.
2026-10-09 10:10:05 +02:00
jochen d4af8bba1b Judge an opened file's kind and links below a home, and more ways to root
A hard link swapped in for ~/.claude would have had root chown another
account's file; fstat on the descriptor now refuses a second link, a fifo or
an unexpected kind before anything is changed (hq ADR 0266, the re-review).
The judge also finds polkit rules for every account, a runtime's API on TCP,
setgid-to-root programs whoever owns them, setuid programs on every suid
filesystem, and unprotected links; the rest is listed as not judged.
2026-10-09 10:07:52 +02:00
jochen 02a1c5f753 Refuse to take over a system account as one that never becomes root
The controller cannot read a machine's user database, so a name it is given
for the agents' account (hq ADR 0266) may be a service's own; taking it over
would hand agents that service's files. Refuse it, touching nothing.
2026-10-09 10:07:52 +02:00
jochen b89b143fe7 Follow no link below a home as root, and judge more ways to root
An account could replace ~/.claude with a link to /etc and have the
node-engine chown, chmod or write through it on the next apply. Below a
person's or an agent's home every component is now opened without
following a link, and a link refuses the resource in words.

The root judge also reads doas and polkit rules, the container runtimes'
sockets with their ACLs, ACLs on the secrets, and setuid-root programs no
package owns, in the C locale; Judged and NotJudged write down exactly
what it covers (hq ADR 0266 review).
2026-10-09 10:07:52 +02:00