Commit Graph
100 Commits
Author SHA1 Message Date
jschoubben ae4b86e333 route-proxy's proof skips aloud when mesh-networking is not beside, since the running checker places none for the node-engine until this release is delivered
mesh/delivery merged: source.main moved to novox/mesh-host@4c7056de4241
mesh/delivery-group group feat/sdk1 merged: every member is merged
mesh/check checking ae4b86e33300 on 98fbc490a4c9 | novox/mesh-host#198 in group feat/sdk1
With mesh-networking beside, or with nothing beside and the captured copy read, it still judges the manifest.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 20:39:56 +01:00
jschoubben 8f1e5f2547 Stop judging the controller's manifest at genesis's step 9: the controller's repository is emptied
mesh/delivery-group group feat/sdk1 checking: 6 of 7 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: meshbuildgo/cmd/mesh-build-go.TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus: NOT CHECKED: the node-engine's composer c…
The test read mesh-controller/module.json beside the check, which no longer exists after the
controller's removal (issue 1126, ADR 0593 §11), so it would fail every check of the node-engine.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 20:23:02 +01:00
jschoubben 60518f532d Pin mesh-sdk at f02f142, the sdk1 head whose tests no longer leak package globals, so the group's check builds against it
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 20:14:16 +01:00
jschoubben cdd17eaae8 Pin mesh-sdk at the sdk1 group's head 1c7356c, so the group's check builds every module against the narrowed SDK it was written for
mesh/delivery-group group feat/sdk1 checking: 7 of 7 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: meshbuildgo/cmd/mesh-build-go.TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus: NOT CHECKED: the node-engine's composer c…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:29:53 +01:00
jschoubben 3293f6ddcd Take the environment's rendering integration tests with the code they test
ADR 0593 §7: the environment's rendering left mesh-sdk for the node-engine,
so its two integration tests (the POSIX environment sourced twice, and
the service manager reading the systemd rendering) move with it.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:21:10 +01:00
jschoubben 1ada760a64 Hold the proofs that a manifest composes, which left the judge's and route-proxy's tests
ADR 0593 §7: the composition is the node-engine's, so its tests hold
what the indexer's judge tests and route-proxy's tests proved by
composing: a bundle's words per machine, a Go bundle's delivery, the
credential form in a handed plan, settings refusals, placeholder and
vault refusals, the session's files, a field set aside composed whole,
route-proxy's manifest composing for a machine, a route endpoint's
machine port in six cases and the plan's route entry end to end. Two
table tests, one subtest per item; route-proxy's manifest is captured
beside the catalogue.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:19:49 +01:00
jschoubben d68e241724 Take the catalogue's composition rules into the node-engine
ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk
carries no composition rule. The catalogue functions only the
node-engine's composition reaches (Resolve, Resolution's Compose,
Declaration, DataItems, LeftOut, Rules and the rest, the settings,
placeholder and template refusals, the rendering helpers) are copied
with their tests into internal/compose/resolution; a method of a
mesh-sdk shape becomes a function taking it first. The composition
calls its own copies, and the captured catalogue its tests read lives
here now, shared by mesh-compose's tests.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:15:55 +01:00
jschoubben be0226b624 Stop verifying composition signatures: the node applies only its own
ADR 0593 §6: a composition read from composed-<node> is a record never
applied, and the engine applies only what it composes itself, so no
composition carries a signature and nothing is verified. The agent's
trusted signers, its signed-composition test and the unused signing
helper of the integration bed go, so mesh-sdk can drop SignComposed,
VerifyComposed and Composed.Signature.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:01:12 +01:00
jschoubben acb720d2bf Merge five moved overlay tests into two, offsetting the tests this release adds
The test budget: mesh-host is over 500 unit tests, so the three tests
added for ADR 0593 §6 and §7 are paid for by folding the private
network's hub refusals, its MTU cases and its no-self-peer check into
table-driven tests, each case kept.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:39:51 +01:00
jschoubben f6bda12a32 The node whole publishes its compositions and answers a candidate (H6)
ADR 0593 §6, §7, §9: the integration test runs the node agent against
the real bus under the grant the grants part composes for a node: it
publishes web's composition in composed-<node> with the digest its have
names, and answers compose-candidate for the indexer's candidate,
changing nothing. A node that cannot compose now answers unready, so
the checker counts it as no answer by the verdict and never by words.
The golden grant reads the indexer's candidates and verdicts as it
reads the builder's builds.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:39:12 +01:00
jschoubben 4b8b5c7329 The composition verb reads the revisions the engine publishes (H5)
ADR 0593 §6: composed-<node> is written again, by the node's engine,
each composition with its plan. The verb composition finds a published
revision by the digest the have and want name, its resources', so a plan
on the record does not hide it. The golden node grant also reads the
indexer's verdicts, which compose-candidate takes for a candidate's
manifests.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:35:00 +01:00
jschoubben 3fa41dfe1a Serve node-engine.compose-candidate from the node's shadow (H4)
ADR 0593 §7: a check's composition is the checker's, made from each
placed node's answer, since only a node can compose with its own sealed
values. The node composes the indexer's candidate in shadow on a copy of
its shadow's inputs, the candidate's modules laid over the indexer's
with their manifest verdicts, and answers per module composed (changed
or not), refused or held, and its own composition's verdict, within the
answer bound; it applies, writes and hands nothing. With input it also
answers the composition input, for the sandbox path.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:33:58 +01:00
jschoubben 4f51393b72 Publish every composition the node makes in composed-<node> (H3)
ADR 0593 §6: the provisioner, the assigner and the view read compositions
instead of composing, so the node's engine writes each one it makes to
its composition state: the wanted builds, every placed module at its
newest done build (so the plan exists before the want moves), each with
its provisioning plan, and a module refused alone with its refusal. A
key is written only when it differs, and kept while among the module's
five newest or named by a want. A placed module's trunk build landing
now makes a pass.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:29:20 +01:00
jschoubben 9a12f2851f The node-engine owns the composition it alone runs (H1, H2)
ADR 0593 §1 and §7: compose, its bus reader and runtime reader, takein,
validate, the overlay generator, the composer's run and mesh-compose, and
the judgement of a move leave mesh-sdk for the node-engine's internal
packages, so no other module composes and a change to them rebuilds only
the node-engine. The shapes others read (the composer's request and
verdict, the overlay's names, compositions and answers) stay in mesh-sdk.
The node's grant is now a golden file of what the grants part composes,
since the composer of the bus's user list leaves mesh-sdk too.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:24:05 +01:00
jschoubben 049c3b8c11 Pin mesh-sdk to the group head of issue 1126
mesh/delivery merged: source.main moved to novox/mesh-host@98fbc490a4c9
mesh/delivery-group group feat/1126-removal merged: every member is merged
mesh/check checking 049c3b8c11c9 on 84737f4cb26d | novox/mesh-host#197 in group feat/1126-removal
Every member builds against mesh-sdk abf5eac, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:27:34 +01:00
jschoubben 813f3cae87 Pin mesh-sdk to the group head of issue 1126
Every member builds against mesh-sdk f569924, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:20:14 +01:00
jschoubben 77c1b50019 Pin mesh-sdk to the group head of issue 1126
Every member builds against mesh-sdk 729a86b, the head of feat/1126-removal, so the group is checked as one contract.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:56:55 +01:00
jschoubben 6bcc4eab24 Say in the comments that the enrolment seat and the packages key stand alone now
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:37:11 +01:00
jschoubben 692b0f0160 Prove a rolled-back build is not placed again on the node tools, the one process judged by default
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:35:33 +01:00
jschoubben 897a5efe7c Name no controller signer or composer the SDK no longer holds
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:34:53 +01:00
jschoubben 98f4ff6163 Read the bus's user-list file's place from what the node runs, not from the controller's compositions
The bus module on the bus node runs from what its agent holds (its own composition, or the declaration kept before the
switch: every novox have read 2026-11-02 names its own composition or none, and nats names a build alone), while
composed-<node> was written by the controller, which ended (issue 1126). The follower now looks at the agent's record
of what runs whenever it changed, writes again only when the file it names moved, and trusts no composition signer.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:34:15 +01:00
jschoubben ec4fe14457 Remove what only served the controller's compositions: the own-secret resync, the shadow's wants watch and the vault key's follower
Every node composes its own since group A (issue 1126), so the resync that handed the vault an own secret's value
while a node applied the controller's compositions never runs, the shadow's answers read no want, and nothing follows
the heard vault key any more.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:29:47 +01:00
jschoubben 75eb2bb534 Say in the usage that overlay take is refused
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:28:36 +01:00
jschoubben 18521ba7af Remove the mesh-cli forward to the controller
nox now calls the seats' verbs through the console itself (mesh-catalog #593), and the controller the engine forwarded
its lines to ended (issue 1126), so the socket, its forward and the subject it asked on go. The kernel's word on a
peer moves into the filled-file socket, its one remaining reader.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:28:35 +01:00
jschoubben a9a0f7b4e0 Say the node's facts in its haves alone, and enrol through the seat alone
The CONTROL stream the facts, the enrolment and the overlay rekey were published into was the controller's, which ended
by its unassignment (issue 1126) and whose stream is removed after group B's delivery: a publish there would fail every
facts event before its haves are written. The facts go into the haves as they did after the report, the enrolment goes
through mesh-enrolment alone, and overlay take is refused because no seat takes a rekey now.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:27:28 +01:00
jschoubben f5ccbb2700 Open every link with no signer instead of following the controller's key
The controller that signed the hand-over, search, judgement, access and move asks ended (issue 1126), so a link
believes none of them by having no signer, and the per-ask key it was told to follow goes with the lease.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:24:05 +01:00
jschoubben 0d1bbe967d Answer the node seat's packages from the engine's own key alone
The controller's declared-packages mirror ended with the controller (issue 1126) and is deleted after group B's
delivery, so the verb no longer reads or compares it.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:22:54 +01:00
jschoubben ff4dc71ce4 Witness the node tools alone: the controller's lease names no build any more
The controller ended by its unassignment (issue 1126, ADR 0590), so no build is judged by the lease: the lease
witness, its contract and the direct get the witness asked it with are removed, and its tests now prove each rollback
path on the node tools' PING.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:22:21 +01:00
jschoubben 38d666f223 Apply only the node's own compositions, never the controller's
mesh/delivery merged: source.main moved to novox/mesh-host@84737f4cb26d
mesh/check being carried to 38d666f22390 on 84737f4cb26d | from 14a12cc7b6f9
The controller ended by its unassignment, so a node that falls back to the
compositions in its bucket follows what nobody writes (issue 1126, A7). The
node agent now always runs its own composer: without a placement key it
composes nothing and keeps what runs, a placement it cannot watch stops the
agent and is said, and a candidate taking its placement away is refused
instead of answered as a return to the controller. The start-time placement
read, the end-when-placed watchers and the returning answer go, with their
tests and the three integration tests of the controller-composed path.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 10:40:50 +01:00
jschoubben 5cfc4ff4a7 Trust the vault's key alone and write the grants part's user list alone
The controller ended by its unassignment and its lease is never handed, so
the engine's lease gate kept trusting the controller's key and the bus node
kept the controller's list as a fallback nobody writes (issue 1126, A6).
The engine now trusts no controller signature, its hand-over line names the
node seat's hand-over, and the user-list follower writes only the grants
part's list: a key unreadable or deleted keeps the list loaded, and with no
key heard nothing is written. Compositions in the node's bucket name the
file's place alone. The guard no longer asks for the controller's user.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 10:37:27 +01:00
jschoubben 44db32c31b Pin mesh-sdk at dff0ce2, the enrolment group's rebased head, so every member builds the same contract (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@14a12cc7b6f9
mesh/delivery-group group feat/l3-enrol merged: every member is merged
mesh/check failed (2 tests): integration: mesh-mail-reader/cmd/mesh-mail-reader.TestAcceptanceOneMatteringMailSendsExactlyOneTellNamingIt: timed out w…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:39 +01:00
jschoubben 12168ba4da The engine carries its login fall back in its health report, and says the enrolment fallback is the pre-lease path
A fall back to the old login during a rotation is now delivery.NodeHealth's Fallback (the node, the login fallen back from,
why, when), written with the node's health report, from which the watchman raises node-account-fallback; it is said no more
once the vault's key of the node moves on (blocker 7, ADR 0583 §6). mesh-host enrol's fall back to the controller's
enrolment subject says it is the path before the lease write, which the bus refuses once the lease is written. Tests: rows
in the existing rotation test; none added.

(issue 774)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00
jschoubben 72aca552e1 The engine's rotation against a real bus whose user list is reloaded step by step
A stand-in vault reloads a nats-server's users as the grants part's list is loaded, and the engine's own rotation code runs
against it with real test-dials and a real confirmation request: at every step at least one login authenticates; the engine
switches only once the list holds both logins and confirms in the new login's name; at rotation-done the old login is
retired and its value dropped; with the list never loaded the test-dial is refused and the engine never switches; and an
abandon after the switch brings the engine back to the old login before the new value goes (ADR 0583 §2-§6). One integration
test; mesh-host holds seven of fifty. The real vault and the real engine together are proven live on g14 first.

(issue 774, 774/S65)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00
jschoubben 9ae30a7bbb mesh-host enrol redeems through the seat mesh-enrolment and refuses an answer the token's key did not sign
The engine dials as enrol.<node> on the pinned certificate, calls redeem with the public halves of its keys and a proof over
them (the SDK's RedeemProof, which binds the node and the keys and never the token's secret), asks again while the holder
says to, and verifies the vault's answer against the key its token carries before it keeps anything: an answer signed by
another key, naming another signer or node, or altered is refused and nothing is kept. A signed one is kept (membership,
login, keys), then the node dials as the login it was given, reads its key of node-logins, and calls confirm; nothing is
applied until its placement names a module. When the bus does not serve the seat to the token (the controller still holds its
lease) the controller's enrolment subject is used as before. The token's shape is mesh-sdk's now: identity keeps its names as
aliases. Tests: one added, paid by merging three identity token tests into one (net two fewer).

(issue 774, 774/S64)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00
jschoubben e362c800a4 The engine test-dials, switches, confirms, drops and falls back across a rotation of its login
On node-logins' ready the engine fetches the new value through the vault's own hand-out (own/<node>/mesh-host/<login>, opened
with its sealing key), dials a second connection as the new login on the pinned certificate, and switches only if that
authenticates, then calls mesh-vault.node-account-confirm over the link as the new login (the vault is asked in the name of
the login the open link authenticated as, and only once that link is open). It keeps the old value until the key says the
rotation is done and drops it then, never before; an abandon, after the switch before or after the confirmation, switches
back before the new value is dropped; repeated refusals of the new login's credentials before rotation-done bring it back
to the old one, said as the finding node-account-fallback (in the journal: no route for an engine's finding to the watchman
is decided), and the same rotation is not tried again. Tests: two added, paid by merging two identity sealing tests into one;
more to be paid by deleting the identity token tests that move to mesh-sdk with the token (774/S64).

(issue 774, 774/S62, 774/S63)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00
jschoubben c7468656af The engine dials as the login the vault's node-logins names
A membership may name its Logins (ADR 0583 §1): the login in use and the password of each login held, kept beside the
identity, so a restart dials as the login in use. The engine follows its own key of node-logins as a watch on every link
and dials as the login it names when it holds its value, with the node's one inbox; with no key, or a key naming the first
login, it dials node.<n> with the membership's password as before. A change of the login in use drops the link so it is
opened again as the new one, and the vault is asked in the name of the login dialled (the subject names the caller).
Tests: one added, paid by merging the pinned-broker test with the malformed-pin test (two into one).

(issue 774, 774/S61)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00
jschoubben c27fad5c6e Pin mesh-sdk at c21d8fd, the removal group's head, so every member builds the same contract (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@598b0d8193a5
mesh/delivery-group group feat/l3-removal merged: every member is merged
mesh/check failed: mesh-build-go: the check of novox/mesh-catalog#590: the ask mvyhumdm860c7f's step on shanks did not end within 30m0s of its start |…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 00:53:42 +01:00
jschoubben 85826c633d The bus node's follower never falls back to the controller's user list once the grants part's key says the lease is handed (ADR 0571 §8, ADR 0547 §11)
A key that later cannot be read or is deleted is said, the key last heard stands and the list loaded stays. A key without
the flag still falls back, as before.
Tests: three rows added to the existing table test of the follower; no new test function. Built against the group's
mesh-sdk through a go.work outside the tree; go.mod is not re-pinned here.

(issue 774, 774/S31)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 00:23:11 +01:00
jschoubben 74ecda8ea2 Pin the group's mesh-sdk head b2a27ed5 (issue 1953)
mesh/delivery merged: source.main moved to novox/mesh-host@9f7e898ecfeb
mesh/delivery-group group feat/node-packages merged: every member is merged
mesh/check checking 74ecda8ea2cf on bc960c51dbf0 | novox/mesh-host#193 in group feat/node-packages
The delivery group feat/node-packages pins one mesh-sdk head in every member: it carries DeclaredPackagesOf and the engine's read of the mirror this change uses.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 16:24:04 +01:00
jschoubben e61141f596 Keep the packages this node's compositions declare in the engine's own key, and answer them on the node seat (ADR 0565 clauses 6 and 15, issue 1953)
pacman still reads the controller's mirror because no engine wrote its key: the engine
now keeps each handed composition's package resources and, after a pass that handed
something new, writes mesh-host_packages.<node> only when the value differs, owed after
the growing bound when refused. The manifest declares the state so the grants part
creates it. The verb packages answers the key beside the mirror's key for the node and
their difference, and with write writes the key again from what was composed. H8's
integration test proves the write under the grant the grants part composes.

Needs mesh-sdk feat/node-packages (0ef7c0c) for DeclaredPackagesOf and the engine's read of the
mirror (go.mod pin left for the lead: the SDK commit is not pushed).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 16:09:13 +01:00
jschoubben 07066fe7a0 Pin the group's mesh-sdk head after the gofmt fix
mesh/delivery merged: source.main moved to novox/mesh-host@bc960c51dbf0
mesh/delivery-group group feat/judge-once merged: every member is merged
mesh/check checking 07066fe7a0a8 on 233bb23bbcb1 | novox/mesh-host#192 in group feat/judge-once
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:27:07 +01:00
jschoubben 974177a074 Pin mesh-sdk after the first review round of the judge-once work (ADR 0578)
mesh/delivery-group group feat/judge-once checking: 6 of 6 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 974177a07478 on 233bb23bbcb1 | novox/mesh-host#192 in group feat/judge-once
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:13:14 +01:00
jschoubben 0422893873 Pin mesh-sdk at the final head of the judge-once work (ADR 0578)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:46:15 +01:00
jschoubben 1d3029fa1e Decode manifests in the packaging test, since the SDK's judge is deleted (ADR 0578)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:39:51 +01:00
jschoubben af66f72931 Take the indexer's verdict on a manifest instead of judging it, and name a held module (ADR 0578)
Issue 1585, S17: the engine decodes manifests leniently; a module whose manifest the indexer refused is held and named,
its data key stands, and every other module's items are still published. Policy, HoldsData and the certificate check no
longer parse with the judge.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:04:34 +01:00
jschoubben e031758e4a Forget a kept build when its key is written again, and retry data items on the composer's bound (review round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@233bb23bbcb1
mesh/delivery-group group feat/data-items merged: every member is merged
mesh/check pass e031758e4a37 on 6c1f7cc7f50f | unit 4510 run, 4259 kept, 49 skipped | integration 4589 run, 4361 kept, 51 skipped | over bound: unit, …
The builder claims a done key again as a new revision, so a build kept as done is dropped on any
write of its key. Data items that could not be worked out or written stay due and owe a pass after
the growing bound, so a first write refused before the state or grant exists is tried again
without waiting for an unrelated composition.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 00:27:29 +01:00
jschoubben 122f4dd727 Read a done build once, and work out the data items in the scope the pass composed in (ADR 0577)
The data items after a pass re-read each placed module's build over the bus (about 55 per pass on
ace, g14 and shanks); a done build never changes, so the composer keeps it. The items are now
worked out with the pass's own kept builds and modules run inside the node's own, as composed.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 00:18:30 +01:00
jschoubben e0261cdfc1 Keep each placed module's data items in the engine's state data, so disk-load measures without composing (ADR 0577)
After a pass that handed a new composition, the node's composer works out its modules' data
items from the inputs it composed from and writes each module's key <node>.<module> of
mesh-host_data only when it differs, deleting the keys of modules that left. Pins mesh-sdk
feat/data-items for the contract and the engine's grant. Two tool-answer tests are merged so
the unit count stays where it was.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 00:09:25 +01:00
jschoubben 71afdf3ad2 Keep a have held by an interrupted backup and naming nothing out of the node's own
mesh/delivery merged: source.main moved to novox/mesh-host@6c1f7cc7f50f
mesh/check being carried to 71afdf3ad234 on 6c1f7cc7f50f | from ffa63d25a066
novox's postgres stayed held after mesh-host #189: its have, written before that fix,
names no build, so the node's own composition still took it in and was refused for its
secret reader before the start could back it up again. Only that exact record leaves
the node's own; every other have naming nothing keeps main's rule. Completes #189 for a
have written before it (issue 1289).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-10-31 20:19:21 +01:00
jschoubben 76c7ddfc52 Keep main's rule for which haves run inside the node's own composition
mesh/delivery merged: source.main moved to novox/mesh-host@ffa63d25a066
mesh/check being carried to 76c7ddfc5227 on ffa63d25a066 | from a4713e2d28d7
Review round 1: a have naming no build in a phase but running is also written for a
module that runs inside the node's own (waiting for a yes), so taking it out would
drop its resources before the yes. The fp the held have now keeps already fixes
postgres (issue 1289); a test drives an interrupted backup end to end.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-10-31 16:38:05 +01:00
jschoubben 39365c62c9 Resume a backup the node agent's own stop interrupted, and keep what a held module runs
On novox (2026-10-31) postgres's repeat backup was interrupted by the node-engine's
restart: its held have named no build, so the node's own composition took postgres in
and was refused for its secret reader, and nothing ran the backup again. Issue 1289
(R4c, its delivery held).

- A held have names what runs: read from the module's have before backing-up when
  the agent has no running entry, as the not-composed mark does.
- A have in any phase but running that names nothing is not run inside the node's own.
- A backup interrupted by the agent's stop is no failure: the next start backs the
  module up again and applies while the want asks a backup. A real failure stays
  held for the operator's answer.

Quick fix: a new backup ask by hand. Thorough, chosen: the fault fixed where it lives
in the node agent, so every data module's interrupted backup resumes by itself.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-10-31 16:33:49 +01:00
jschoubben 8bdfddab4a Hand a module exactly when the vault's own answer judged it handed, so the node never composes a secret the vault leaves to the provisioner (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@a4713e2d28d7
mesh/delivery-group group feat/072-moves merged: every member is merged
mesh/check failed: shanks: the check of novox/mesh-controller#487 failed: go mod download in modules/route-proxy: go: git.novox.be/novox/mesh-controll…
2026-10-31 09:49:14 +01:00
jschoubben a4c0cee251 Read this node's placement from its node and module keys, or its placement.<node> before the holder converts it, so a write of the holder's is composed only once its node key lands (issue 774, research 072 row 37) 2026-10-31 09:37:23 +01:00
jschoubben da0f8f1cfd The node composes again only when what it composes from changed, so a want, placement, module or value written again asks the vault nothing (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@bb8383b065a1
mesh/check being carried to da0f8f1cfdb4 on bb8383b065a1 | from 62b211bb6040
Every put under want.<node>.>, every module's take-in and every placement write
made a pass, and each pass asked the vault for the node's own secrets. The
shadow's change check moves onto the composer, gains the want (by its
fingerprint, the only part composed) and serves both loops.
2026-10-31 01:59:58 +01:00
jschoubben 1d8d726758 Leave a record the node's own carried for a module that composes its own, and let that module's apply remove it, so the node's own is not refused for ever (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@62b211bb6040
mesh/check checking 1d8d7267584e on 62b211bb6040
On the bus node restic's manifest replaced its process measure by awake (mesh-catalog 35ff67bd). The record of restic.measure was the node's, made while restic was folded, so restic's own apply never saw it, and the guard against removing a placed module's resources refused the node's own apply whole every two minutes from 2026-10-30 19:42: nothing of the node's own applied.

Quick fix considered: forget the one record by hand (a hand act on the node; refused). Thorough fix (this): the node's own apply keeps such a record and says so, never refusing or removing it; a module's own apply owns a node record whose id names it, so it alone removes it once neither composition declares it.
2026-10-31 01:13:11 +01:00
jschoubben b0d6684d37 Retry only a failed apply that is not held, answer a repeated compose from the last pass, and say the try the refusal named (issue 774, review round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@3d1465606bed
mesh/delivery-group group fix/compose-retries-transient merged: every member is merged
mesh/check being carried to b0d6684d3731 on 91fb323ce0cf | from mesh-sdk dec3bfe7a8cb | novox/mesh-host#185 in group fix/compose-retries-transient
The verb compose has no warrant (ADR 0563 §2): Agent.Retry released a module held for a failed backup and re-applied
one already running its want. RetryFailed tries again only a failed apply not held, and refuses in words otherwise.
A hand ask no longer restarts the growing bound, and a pass less than askedWithin old is the answer, so a caller in a
loop drives no passes against the vault. A composer whose loop ended refuses at once. The retry line said try N+1.
The test's first shadow now ends before slowAfter is set, a race the race detector found under the new timing.
2026-10-31 01:08:57 +01:00
jschoubben 56eb0b792c Pin mesh-sdk dfd5d82, so the node-engine announces the verb compose (issue 774) 2026-10-31 00:57:53 +01:00
jschoubben 9c15610f70 Say when the node composes again after a seat that did not answer, say what a long pass waits on, and serve the verb compose (issue 774)
On 2026-10-31 ace's route-proxy said only that the vault could not be asked, and ace said nothing for five minutes
while a pass waited after the vault restarted; the retry that already existed was invisible. A refusal for a seat or
a read that did not answer now says when the node composes again ("the node composes again at HH:MM:SS (try N)"), a
retry pass says it starts, a pass that runs on says every minute what it waits on, and the node-engine's verb
compose makes a pass now and says how it went, so a person recovers without a restart.
2026-10-31 00:56:47 +01:00
jschoubben 8da56ead81 Pin mesh-sdk 8ed3b87, so a held module's ungranted contribution never puts a stand-in in its provider (issue 774, review round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@91fb323ce0cf
mesh/delivery-group group fix/held-keeps-contributions merged: every member is merged
mesh/check checking 8da56ead8199 on 91fb323ce0cf | novox/mesh-host#184 in group fix/held-keeps-contributions
2026-10-31 00:36:17 +01:00
jschoubben 7bbba2e39e Keep a have's sealed values only when they are of what runs, when a waiting-yes have says running again (issue 774, review round 1) 2026-10-31 00:35:22 +01:00
jschoubben 08e9d15817 Serve the node-engine verb composition: a module's composition beside what runs, its judgement and its diff (issue 774)
Four providers waited for a yes on 2026-10-31 and nobody could read what waited: the have said only
"where its data is kept". The read verb composition answers it from the engine: the latest revision of the
want's build or the one asked by digest, every revision the bucket keeps, what runs and where that is known from,
the want and the have, the move's judgement and whether it waits for a yes, and with diff the resources that
differ, a received file naming its consumers. revisionNamed now reads the same revisions.
2026-10-31 00:26:57 +01:00
jschoubben 1b624c3ec7 Pin mesh-sdk 6bce1fb, so a held module keeps its contributions and the seat's contract names the verb composition (issue 774) 2026-10-31 00:24:56 +01:00
jschoubben 7e2d947f5f Say running again when a provider's composition goes back to what runs while it waited for a yes (issue 774)
waitsForYes returned at once for a composition equal to what runs, and settleOne then found nothing to apply, so
the have kept phase waiting-yes for a move that no longer existed (ace.postgres, novox.postgres, novox.keycloak and
novox.mongodb on 2026-10-31). The have is now rewritten to running.

Quick fix: rewrite the have where the equal composition is met. Thorough fix (not here): one place that writes a
module's have from the settled state after every settle, so no early return can leave a stale phase.
2026-10-31 00:20:43 +01:00
jschoubben 62cfb45789 Pin mesh-sdk eff81f1, so a module held for a missing secret never refuses the node's whole composition (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@1e833feb3940
mesh/delivery-group group fix/held-module-and-assigner-contract merged: every member is merged
mesh/check checking 62cfb45789c1 on 1e833feb3940 | novox/mesh-host#183 in group fix/held-module-and-assigner-contract
2026-10-30 21:21:19 +01:00
jschoubben 15361c661f Pin mesh-sdk 4e9621ed, so a proposal foresees a bus account the Approve will mint and a held module's first reason leads a refusal (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@16b8f591ed6d
mesh/delivery-group group fix/kept-read-and-foreseen-account merged: every member is merged
mesh/check pass 15361c661f51 on b133a8949dcb | unit 2719 run, 602 kept, 36 skipped | integration 2731 run, 645 kept, 29 skipped | over bound: unit, in…
2026-10-30 20:34:12 +01:00
jschoubben cc3f7e8e01 Retire the kept read though mesh-wireguard has no fingerprint, since the node's own composes it from the manifest the mesh provides (issue 774)
The review of d5e8458 found that mesh-wireguard, provided by the mesh and never wanted at a fingerprint, kept the kept
declaration read on every node for ever, against ADR 0550 §2 once the lease is written. A selfcompose row shows the
node's own composes it from delivery.ProvidedManifest with nothing running, so the kept read holds nothing of it.

Quick fix: name mesh-wireguard in allHave. Thorough fix: allHave counts every module delivery.ProvidedByMesh says the
mesh provides as covered. Chose thorough: one list of provided modules, mesh-sdk's.
2026-10-30 20:30:37 +01:00
jschoubben d5e8458340 Keep reading the kept declaration while a placed module runs at a build of the old form, so the node's own never drops it (issue 774)
At 19:43:01 on 2026-10-30 shanks' new engine said "the kept declaration is no longer read: each of the 77 modules
placed here has a have of its own", and from 19:43:57 every apply of the node's own was refused: it would remove
systemd-resolved's resources (novox the same for nats and restic). systemd-resolved's have names only a controller
build (build-1791404191702719760), no fingerprint, so the assigner wants it nowhere and it runs folded in the node's
own, of which the kept declaration is the one record; retired, the folded module was composed as nothing.

Quick fix: never retire the kept declaration. Thorough fix: allHave counts only a have naming a fingerprint of the new
form (or removed); a module at a build of the old form keeps the read, named with why, until it has one. Chose
thorough: the read retires by itself once every placed module is wanted.
2026-10-30 20:27:12 +01:00
jschoubben b133a8949d Merge pull request 'The node-engine composes again when a build it waits on is done (issue 774)' (#181) from fix/build-not-done-holds-one-module into main
Reviewed-on: #181
2026-10-30 18:53:08 +00:00
jschoubben 33131dcbfe Compose again when a wanted build lands, so a node that waited on a build is not left refused until another input moves (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@b133a8949dcb
mesh/delivery-group group fix/build-not-done-holds-one-module merged: every member is merged
mesh/check checking 33131dcbfe77 on 2ae7c53376e8 | novox/mesh-host#181 in group fix/build-not-done-holds-one-module
At 19:30 on 2026-10-30 node-tools' want moved to a build not yet done; once it landed, ace, g14 and shanks stayed
refused until some other input moved (g14 and shanks at 19:40, ace after a restart), since the composer watched no
build.

Quick fix: compose again on a timer while anything is held. Thorough fix: the composer watches the builder's builds
(within the node's compose reads, NodeComposeReads) and makes a pass when a done build of a module the node wants, at
its want's or the trunk's build, lands; nothing else moves it. With mesh-sdk 9685990a (pinned here) a running module
whose build is not done is held, keeping what runs, so the pass that waited composes it as soon as it lands. Chose
thorough: event-driven, no poll.
2026-10-30 19:49:08 +01:00
jschoubben 2ae7c53376 Merge pull request 'Pin mesh-sdk 754edfa, so one refused manifest holds one module on this node (issue 774)' (#180) from fix/one-bad-item-holds-one-module into main
Reviewed-on: #180
2026-10-30 18:30:57 +00:00
jschoubben 4055e0094b Pin mesh-sdk 754edfae, so a manifest the mesh refuses holds that module alone and the node composes the rest (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@2ae7c53376e8
mesh/delivery-group group fix/one-bad-item-holds-one-module merged: every member is merged
mesh/check checking 4055e0094b48 on ee71ba4dea81 | novox/mesh-host#180 in group fix/one-bad-item-holds-one-module
At 19:13 on 2026-10-30 power's bar item (mesh-catalog ca8d6f33) made frombus.Compose refuse the whole node, and the
composer marked every module not composed (32 of 81 failed on shanks). The composer already holds what Compose names
held (NotComposedEach, issue 1126); the SDK now names a refused manifest's module held instead of refusing the node.

Quick fix: none in mesh-host; thorough fix is the SDK's input (one rule for Compose, Answer and PlansHeld). The test
replays the captured power manifest and fails on the old pin with the live words.
2026-10-30 19:25:37 +01:00
jschoubben ee71ba4dea Merge pull request 'The node-engine asks again a subscription the bus refused, so its seat answers; backups and health follow what runs (issue 774)' (#179) from fix/sweep-2026-10-30 into main
Reviewed-on: #179
2026-10-30 18:13:06 +00:00
jschoubben b9c24a7b42 Read the vault's structured move-in refusal and take a value it holds another of as superseded, so the move is not asked again for ever (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@ee71ba4dea81
mesh/delivery-group group fix/sweep-2026-10-30 merged: every member is merged
mesh/check checking b9c24a7b42cb on 737bce9c9448 | novox/mesh-host#179 in group fix/sweep-2026-10-30
mesh-vault (mesh-catalog f3b13de2) answers a move it refuses with `refused: {reason, held}` and `why`, and
no read-back. The engine read only the read-back, so every such row failed "answered with no read-back"
and the controller asked it again on every pass.

Quick fix: treat any answer without a read-back as done. Thorough fix (chosen): read `refused`. For
holds-another-value the vault's value is the live one: the move answers done, superseded, at the held
generation with the held record in its words, so the controller records the row and stops asking; the
journal says it once per secret. Any other reason fails in the vault's own words.
2026-10-30 18:36:38 +01:00
jschoubben 744bfec4a0 Hand the health loop's judges what runs once a module settles, so a resource its new composition drops is no longer watched (issue 774)
restic's composition dropped its process restic.measure on g14 and shanks at 16:14; the engine removed it
and said it down on every look after. The judges were set only when facts were said, and the facts said
inside an apply read the module's composition from before it (the agent records the new one only after
the apply returns), so the removed resource was put back. Judge.Set itself prunes what is not declared.

Quick fix: set the judges after the restic apply. Thorough fix (chosen): one function hands every judge
(liveness, resolver, units, accounts, libraries) what is declared; it runs from the facts as before and
now also whenever a module settles (applied or removed) or is taken as running, so every removed health
entry of every module is pruned at recompose.
2026-10-30 18:12:56 +01:00
jschoubben 8bff438e5e Ask a data module's backup again while nothing on the machine answers it, within three minutes, so a tool-runner restart no longer holds the module silently (issue 774)
On 2026-10-30 ace's redis, postgres and restic (14:03), step-ca (13:55) and mesh-vault on novox (15:22)
were held on a single backup ask that met no responder while the tool runner restarted.

Quick fix: ask node-backup.now twice. Thorough fix (chosen): every node-backup ask (backed-up and now)
is asked again with the engine's backoff while it is known never to have been delivered (ErrNotAsked),
bounded at three minutes; an ask that may have been delivered (a timeout, a refusal) is never asked
twice, so no backup starts twice. Past the bound the backup fails saying how often and how long it was
asked, the module is held and its have carries those words for the assigner's ask and condition. This
was the engine's only one-shot seat ask.
2026-10-30 18:09:16 +01:00
jschoubben 7ecf7ba356 Ask a refused subscription again on a new link and announce the node seat as discovery reads it, so node-engine.root-free is answered again (issue 774)
Every engine's node-seat subscriptions were refused at 12:10 by a user list composed without them. nats
asks a refused subscription again only on a new connection, and a user-list reload makes none, so the
seat answered nobody all afternoon and the messenger held every Telegram ask. The endpoints also lacked
the kind and scope discovery reads, so the console said nothing in the mesh is called node-engine.

Quick fix: restart the engines. Thorough fix (chosen, the second time a stale user list left an engine
deaf): a refused subscription of any kind is said and the link is opened again after a pause (1 min,
doubling to 30 min), so every subscription the engine makes is asked again; the seat's endpoints carry
kind, seat, scope, module, description and schema like every tool runner's.
2026-10-30 18:06:52 +01:00
jschoubben 737bce9c94 Merge pull request 'Leave the engine's acts out of its declared state for now, so the bus's user list is composed again (issue 774)' (#178) from fix/774-engine-acts-not-carried into main
Reviewed-on: #178
2026-10-30 11:51:56 +00:00
jschoubben ec191247a0 Leave the engine's acts out of its declared state for now, so no node's tool runner carries a grant to the engine's own keys and the grants part composes the bus's user list again (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@737bce9c9448
mesh/check checking ec191247a06d on 737bce9c9448
2026-10-30 12:47:40 +01:00
jschoubben 015334020a Merge main (delivery 1, the parse-only sdk) into L3's re-land, and pin mesh-sdk 2da0de7, the release's final head (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@a9edd2951c46
mesh/delivery-group group feat/774-l3-reland merged: every member is merged
mesh/check checking 015334020ad2 on a3c098a5c618 | novox/mesh-host#177 in group feat/774-l3-reland
The conflicts were go.mod, go.sum and testdata/beside/CAPTURED alone, taken from the release. 2da0de7 is 6e4a301's code;
its conformance events are unchanged.
2026-10-30 01:13:38 +01:00
jschoubben 2fd165f5af Pin the parse-only mesh-sdk 78c9ec8 in place of 6e4a301, so main's engine reads the release's new words and means nothing new by them (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@a3c098a5c618
mesh/delivery-group group fix/774-new-words-first merged: every member is merged
mesh/check pass 2fd165f5af8e on 8c5af3e4a3a3 | unit 9174 run, 546 kept, 51 skipped | integration 9952 run, 659 kept, 60 skipped | over bound: unit, in…
78c9ec8 is sdk main 307b623, whose tree is main's pin dbcaa29, with one commit that parses the new words (a credential's
form, an artifact's bus, a log's max-age-days) and names the new seats; an offer with no form still means pair. No code
of ours changes. Unit and integration suites pass against a local bus and main's catalogue and controller beside: 1260
tests, 7 skipped for a container or a throwaway machine.
2026-10-30 00:39:20 +01:00
jschoubben db1151c773 Pin mesh-sdk 6e4a301 on main's engine, so the controller and the engine read the release's new manifest words before the release uses them (issue 774)
No code of ours changes. Whole unit and integration suites pass at main's code against a local bus: 1260 tests, 7
skipped for a container or a throwaway machine.
2026-10-30 00:25:10 +01:00
jschoubben 02f78c9949 Pin mesh-sdk 6e4a301, the release's final sdk (issue 774)
The whole integration suite passes at this head against a local bus (JetStream, and a bus with the witness grants) and
the release's mesh-catalog and mesh-controller beside it: 1260 tests, 7 skipped for a container or a throwaway machine.
2026-10-30 00:17:00 +01:00
jschoubben 192633edb8 Pin mesh-sdk 007b6fd, the release's final sdk (issue 774) 2026-10-30 00:07:07 +01:00
jschoubben e5e8ff5783 Say each account's root verdict and the engine's contract in the node's health report, so the self-check's DA and D-root judge without the controller (issue 1952, ADR 0266)
Every account resource marked root never goes into NodeHealth.Root; the statement's contract into Contract. Relogin,
root and found are judged each on its own, so an account may be both. Pins mesh-sdk f3677eb. Test: the report's row in
TestAHealthStatementIsSaidOnTheLinkOpenNow carries the agent account's verdict and the contract.
2026-10-29 23:49:36 +01:00
jschoubben c3ff18de4e Name the machines a failing network part points at in the node's health report, so the watchman holds it where the controller does (issue 774)
delivery.NetworkPart.Targets (mesh-sdk 6eae076) is filled where the report is made, by the controller's own rule:
- the hub's node for "hub";
- each resolver's node by its address on the private network;
- none when the part points at nothing, at this machine, or at an address no other mesh machine has.

The names come from the placements the node's own composer holds (Decided.Address, Decided.Hub). A spoke's tunnel names
only the hub, so the tunnel cannot name a resolver on another spoke. A node that does not compose its own names no
target.

Test: rows in TestAHealthStatementIsSaidOnTheLinkOpenNow (hub, two resolvers, a VPN's resolver, its own, a healthy
part).
2026-10-29 23:05:02 +01:00
jschoubben 3ed9abf658 Format the health report's aliases as gofmt does (issue 774) 2026-10-29 22:59:38 +01:00
jschoubben 552f97c919 Take the node's health report layout from mesh-sdk 94d0de5, so the engine and the watchman read one form (issue 774)
delivery.NodeHealth and its parts replace this package's copies. The message's network and units parts are aliases of
the same types, so its JSON is unchanged. The engine keeps the derivation from its statement and the write on a change.
Test: the key's bytes, decoded and encoded again by the sdk, are the same bytes.
2026-10-29 22:59:22 +01:00
jschoubben 65fbc1e6f8 Write each node's health report into its own key of mesh-host_health on a change, so the watchman raises the machine's alerts without the controller (issue 774, the operator's alert table)
Until now the six machine alerts came from a health message passing to the controller. The report is now kept in the
engine's declared state `health`, key `<node>`, created by the grants part as `acts` is. Only this node's engine
writes it, after comparing it on every look and only when what it says changed; it is read first after a restart.

It carries:
- the network parts (machine-network; network-rewritten through the resolver file part's writer and owner);
- the tunnel's peers, each seen within five minutes or not (network-unreachable);
- the failed units no module owns (machine-units);
- the accounts marked relogin needed (relogin-needed);
- the directories used as found (directory-used-as-found).

The streaks and the time of the look are left out, so a look alone writes nothing. A peer is reported seen or not,
never its handshake time, so a renewed handshake writes nothing.

The layout is internal/link's (NodeHealth) until it moves into mesh-sdk. Pins mesh-sdk 58a8324.

Tests:
- rows in TestOnTheHubAnyFreshPeerIsAHealthyTunnel: the peers, and a change only when one is seen or lost;
- a case in TestAHealthStatementIsSaidOnTheLinkOpenNow: what the report takes, and one write per change across a
  restart;
- the manifest's states in TestTheManifestConsumesTheBackupEvent.
2026-10-29 22:56:14 +01:00
jschoubben cbe8528f91 Resolve ${secret:<name>} through this node's provision keys, and check the reference by the vault's own name (issue 1954 L6-E1, E2; ADR 0570 clauses 1, 6; ADR 0094)
- A placeholder is `${secret:<name>}`, the name the module calls the credential (catalogue.SecretLocal): the local
  name where it keeps several values of one provision, else the provision. The `<provision>.<local>` form goes.
- The engine finds the one key under `<node>.<module>.` of mesh-provisioner's `provisions` going by that name. That is
  what node.<node> may read, by a watch to its caught-up mark; plans are not read.
- The reference's ID must be a credential name of that provision (provisions.VaultName). The vault's answer must carry
  exactly that name and generation. Anything else refuses the write, naming both.

Pins mesh-sdk 66ff33d. Tests: rows in TestAWriteAndAnApplyOfTheSameFileSerialise (a local name, a name no provision
goes by, a reference to no credential of the provision, a mismatched answer).
2026-10-29 22:41:44 +01:00
jschoubben 5497d362dd Refuse a provision's value unless the vault's credential name and generation are the ones its reference names (issue 1954 L6-E1; ADR 0570 clauses 1, 6)
The value written is the one the provision names: a1-47 makes Reference.ID the vault's credential name. A mismatch in
either refuses the write, naming both. Test: a row in TestAWriteAndAnApplyOfTheSameFileSerialise.
2026-10-29 22:37:44 +01:00
jschoubben 6b32e07164 Fill a provision's ${secret:} at the write from the vault's value sealed to this machine, so the provisioner's client never holds it (issue 1954 L6-E1, E2; ADR 0570 clause 6)
The provisioner's client fills a handed module's file but for each `${secret:<provision>}` (or a whole `secrets` file
that is the placeholder alone).
- The node-engine reads the provision's key `<node>.<consumer>.<provision>[.<local>]` in mesh-provisioner's
  `provisions`. The key must be `provided` and carry the vault's reference.
- It asks the vault's `own` for that pair credential sealed to this machine, at least at the reference's generation,
  and opens it with the node's sealing key.
- It writes the file as declared (its owner), 0600 when the declaration gives no mode.
- Anything short of that refuses the write, naming the provision and why.

A module the node composes itself already has its `secrets` files written so: its composition carries the vault's
value from `own`, written 0600 with the declared owner.

Pins mesh-sdk 4a96793 (go/provisions). Tests: rows in TestAWriteAndAnApplyOfTheSameFileSerialise.
2026-10-29 22:36:53 +01:00
jschoubben f7814853f9 Pin mesh-sdk 2e5cc4f, the re-land group's sdk head (issue 774) 2026-10-29 22:18:45 +01:00
jschoubben 58aacaad2d Pin mesh-sdk 7000bd5, the re-land group's sdk head: a tools bundle declares its own bus account (issue 1952 S5, issue 774) 2026-10-29 21:57:09 +01:00
jschoubben 81672b183d Keep the node's own as it runs from an engine's first start, and refuse the node's own apply removing a module no want removed (issue 774, review round 1)
The reviewer showed how 76e214a still lets the 07:54 outage happen at the re-landed engine's first start on novox.
applied/ is empty there, so loadApplied loads nothing. The kept declaration retires, because every placed module has a
have. Nothing then carries the node's own composition. The guard misses nats, which has no want there, so its
nats.conf is guessed as nats's and removed.

- At the start, when the store has no node's own composition, the node's own runs as the kept declaration has it, and
  the store keeps it now. The kept declaration retires only once the store holds `node`.
- What this engine applied runs until loadHad reads the composition a differing have names. The bucket no longer keeps
  the one novox's have names, so the stored one stays.
- In the node's own apply, a resource of another module goes only when that module's want says removed
  (Scope.RemovedByWant). A module the node's own composition carried has no want, so Placed never named it.
  Cost: the node's own apply cannot drop one of its own dotted resources without such a want. It is refused whole and
  said, never removed silently.

Tests, with no new test functions:
- A first-start case in the agent's Everything test (empty store, then a start with the declaration retired). It fails
  on 76e214a.
- Two rows in the guard's table: no want refuses; a want saying removed removes.
- A case for the store in the authority test.
2026-10-29 21:25:51 +01:00
jschoubben 76e214a8b9 Keep every composition the engine applied on the machine, so a module whose composition the bus no longer keeps runs as applied and is never composed as nothing (issue 774)
On novox on 2026-10-29 at 07:54 UTC the kept declaration was retired and the node's own composition was in no bucket.
The composer's first pass reads what runs before the agent reads any have (the agent waits for the composer's caught,
sent after that pass), so the node's own was composed with its folded modules running nothing, and the bus server and
four held modules were removed.

- Quick fix: read the node's own have in loadHad too. It still waits for the composer's first pass, and still finds
  nothing when the bucket no longer keeps the composition the have names.
- Thorough fix, chosen: the engine keeps each composition it applied (the node's own too) in `applied/` beside its
  state, 0600. The agent reads it at Run's start, before any watch, as what runs ahead of the bucket and the kept
  declaration. A have naming another running composition wins. An apply keeps; a removal forgets.

Test: a case in TestEverythingIsWhatTheAgentRunsAndWhatIsKeptForTheRest (no new test function). It fails on L3's
engine 4f9227a (0 compositions at the first own composition) and passes here.
2026-10-29 21:16:38 +01:00
jschoubben acf02d52ff Pin mesh-sdk 22c9ddd, so a carried or held module serves its tools from what runs and a data flag alone moves no data (issues 1126, 924) 2026-10-29 21:05:12 +01:00
jschoubben a7acee3eed Refuse an apply that would remove a resource of a module a want still places here, so a composition that lost what runs can never remove the bus server or a held module again (issue 774) 2026-10-29 21:04:17 +01:00
jschoubben d2645a27e9 Compose a file that runs as the node last wrote it, so a stale kept declaration's export no longer names a swept object (issue 924)
novox's mesh-vault has failed since 01:16 on 2026-10-29: its have says failed, so loadHad (fbea777) takes no
composition from it, and what runs falls back to the kept declaration, whose export (mesh-vault.kept) names
f099eaa1, an object the controller's sweep removed. The node wrote a newer export since. The composer carries that
file as it runs (mesh-sdk frombus keptOf), so what runs now names the digest and size of what is on disk, but only
where the node's record says it wrote exactly that content at that path (Applied.Wrote, Target). The apply then
finds the file in place and needs no object.

Quick fix weighed: have the controller put the object back. Thorough (chosen, in the node): what runs is what the node
wrote, never a guess from an older declaration. The rows join the existing whole-apply test; the test count stays.
2026-10-29 21:04:17 +01:00
jschoubben 7d39510450 Keep the content of every file given by digest on the node, so an object the controller's sweep removed never refuses a composition the node already had (issue 924)
On novox at 01:16 on 2026-10-29, after the node began composing its own modules, mesh-vault's apply failed while the
vault ran: its own composition carries the export as it runs (mesh-sdk frombus KeptRuns) by a digest whose object the
controller's sweep (mesh-controller cmd/mesh-controller/declaration_sweep.go, sweepNode and sweepDeclarationFiles)
had removed, since the sweep keeps only what the controller's compositions in composed-<node> and the wants name.
The node may only read its bucket (ADR 0486 §2), so the node keeps what it had: a file here that verifies, or an
object fetched, goes to <state dir>/objects under the object's name, and a kept one is used before the bus is asked.
An object unused for 14 days is let go. A fetch that still fails says the keep never held it, before its cause.

Quick fix weighed: re-put the object from the controller's store. Thorough (chosen): the node never depends on the
bucket for content it once had, for every module, since the sweep and a node composing its own can always disagree.
The whole-apply test gains three rows that fail on the old code; the test count stays.
2026-10-29 21:04:17 +01:00
jschoubben f38ac87ac9 What runs from the kept declaration says Data as the module's manifest does, and only a have that runs counts at the start (issue 924)
The kept-declaration fallback built a composition without Data, so after a restart a data module (mongodb, mssql
on novox) was judged to move its data against its first composition and the operator was asked a false yes. Now
every such composition takes Data from the manifest (agent.Env.HoldsData, the composer's DataItems), and a module
whose manifest cannot be read is said to be unjudgeable rather than taken to hold none. The composition a have
names comes before the kept declaration in the judgement too.

loadHad takes a have only in the phases baseline takes as running (running, held, a composer's mark): one backing
up, applying, checking, failed or waiting names a composition not applied (review of 031e106). Two rows added to
the same table test; the count stays.
2026-10-29 21:04:17 +01:00