diff --git a/04-ISSUES/275-a-machine-waiting-for-its-push-was-said-urgent/00-report.md b/04-ISSUES/275-a-machine-waiting-for-its-push-was-said-urgent/00-report.md new file mode 100644 index 0000000..179863d --- /dev/null +++ b/04-ISSUES/275-a-machine-waiting-for-its-push-was-said-urgent/00-report.md @@ -0,0 +1,92 @@ +--- +status: located +opened: 2026-10-06 +located-in: [mesh-controller] +fixed-by: novox/mesh-controller PR #89 +amended-design: +--- + +# 275. A machine waiting for its push was said to be urgent + +## Symptom + +On 2026-10-06 the operator assigned the backup module to the workstation and, a minute later, pushed +it. Between the two, the self-check's D1 (to-be 45 §4) raised an **urgent** condition on the +workstation, and the operator was notified on the desktop: + +> nothing can be sent to the workstation: its declaration does not compose — restic needs a secret +> called "repository" and none was made for it + +The push that followed made the secret, sent the declaration, and the machine applied it. Nothing had +been wrong. In the same window D3 raised that the workstation's `node-backup` holder was silent, and +D13 that the data declared there was unmeasured — about a holder that had not been sent yet. + +Three conditions, one of them urgent, for the ordinary gap between `assign` and `push`. A condition +that fires on normal work teaches the operator to ignore conditions, which is the failure to-be 45 +exists to prevent. + +## Cause + +**D1 asked a question the push answers.** A module's own secrets — a repository password, a database +superuser — are made by the push, on the send path, and only there: a question that wrote would +contend with the machine it was asking about (that is why compositions are `Reading` everywhere but +the send). D1 composes every machine with a `Reading` composition. For a module assigned since the +last push, that composition has no value for its own secret, and the composer refuses a secret it is +given no value for. D1 read every refusal as "this machine's declaration does not compose". + +The refusal was right for what it guards — a declaration must never be sent with a credential missing +— and wrong as a finding: the push would have made the secret and composed. D1 could not tell "the +push makes this" from "the push fails on this", because the refusal was one untyped error. + +**D3 and D13 read the assignment, not the delivery.** Both expected a holder on every machine its +module is assigned to. Assigned is not there: the holder exists once the machine was sent a +declaration carrying it and applied it. + +## Fix + +The fix is novox/mesh-controller PR #89. The rule: + +**A finding about a machine is about what it was sent, or what the next push would send it — never +about the gap between an assignment and its push.** + +- **D1 composes as the push would, making nothing.** An own secret not made yet is judged by the + push's own rules. One the push *makes* is composed with a stand-in, and named. One the push is + *refused* on — a bus credential nobody issued + ([issue 203](../203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md)), a + machine with no sealing key, a given secret sealed to a key the machine no longer has — is refused + in the push's words. Everything else that does not compose, or that the node-engine's validator + refuses, is still found, and is still urgent: the stand-in hides nothing. +- **A machine that composes only with stand-ins is waiting for a push, not broken.** Nothing is said + for thirty minutes from when it began waiting — the oldest assignment since its last send, or that + send. Past that, a **warning**, `awaiting-push`, names what the next push will make and the verb + that sends it. Never urgent: nothing is wrong that a push does not fix. +- **The composer's refusal is typed.** A secret given no value is its own error, carrying the module + and the name; no caller reads the words. +- **D3 and D13 expect a holder once it was delivered.** A holder is expected to answer on a machine + when the machine's last send carried its module and the machine has reported acting on that + declaration, or has had ten minutes to. A machine sent nothing is expected to hold nothing the mesh + put there. A send recorded before the mesh kept what it carried is read as before. + +## How it is checked + +Tests in the controller run through its real stores: + +- **The window.** A machine is pushed, then a module with an own secret is assigned to it. The read + composition refuses with the typed error; the composition asked ahead of the push composes, names + the secret, and makes nothing. D1 says nothing within the bound, a warning past it, and nothing once + the machine is pushed. Without the fix this test fails with the urgent text seen live. +- **A real failure stays urgent.** A module whose bus credential was never issued, a given secret + under an old key, and a machine whose set does not resolve beside a waiting secret are each still + urgent `uncomposable`. +- **When a holder is expected.** Never sent, sent without it, sent and not yet reported, reported, + and sent long ago without a report each give the expected answer, read from the send and report + records. + +## Left open + +- **To-be 45's D1 row** still states only "every machine's declaration composes". It should say that + a composition waiting only on what the next push makes is not a failure, and name the + `awaiting-push` warning. An amendment via playbook 02, not made here. +- **Ports.** A module's machine port is also chosen only on the send path. A composition that reads + composes an unchosen port at the module's own number; that has raised nothing so far, but it is the + same gap, and the stand-in rule would apply to it if a port ever made D1 refuse.