From 8e9116469a0aab2fb70b8a076a004c924257e82b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 03:26:27 +0100 Subject: [PATCH] Answer step 0b's review of the approval mark and marked waits The served version comes from what the bundle runs and a reading from the runner that made it, so no placement or clock lifts a deny; the runner's state takes the old form the bus module can create; kept calls stay open on their key; and the closed directory's mode is read by node-files. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../01-to-be/34-milestones/every-event.md | 13 +- .../01-to-be/46-milestones/7-approval-mark.md | 158 +++++++++++------- 2 files changed, 106 insertions(+), 65 deletions(-) diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md index 26cb3010..12027e82 100644 --- a/03-DESIGN/01-to-be/34-milestones/every-event.md +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -124,7 +124,8 @@ that holds its mark reach the machines together. 11. **Its manifest's changes, every one.** `node-tools/module.json` drops the key `waits` and its five entries (ADR 0670 §2); gains `operator-channel` in `uses`, for the asks of marked waits (§35; design 46 7-approval-mark §7); gains - in `requires` the state `approvals` of design 46 7-approval-mark §13; and gains the stream `node-tools-findings` and + in its old `state` section the per-machine state `approvals` of design 46 7-approval-mark §13 (the tool runner is a + foundation part, ADR 0619 §2, §3); and gains the stream `node-tools-findings` and the `consumes` of `mesh-conditions.findings-asked` that design 47 G §387 writes. Its `version` stays `2`, a label no reader judges. Nothing else in it changes. 12. **The publication time is kept.** `bus.Heard` gains `Published`, read from the event's header `x-time`, which @@ -183,7 +184,8 @@ that holds its mark reach the machines together. answered after the timeout with §16's words; `TestMeshWaitWithNoFamiliesIssuedWaitsOnNothing` is deleted. In `bus/wait_test.go`, `TestTheBusRefusesAWaitOutsideTheGrant` stays as §16's refusal test, with a timeout of a fraction of a second, its comment citing ADR 0670 §1. The manifest test also holds each change of §11: no `waits`, - `operator-channel` in `uses`, the state `approvals` in `requires` with its options, and the finding stream. New + `operator-channel` in `uses`, the state `approvals` in the old `state` section, per machine with its time to live, + and none in `requires`, and the finding stream. New test functions are allowed within the module's budget of 500 unit and 50 integration tests (Q-TB). ## The claude-code module's Stop hook @@ -317,8 +319,8 @@ approves, for a marked event or a restricted stream. `from: seat` is never marked. 35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as design 46 7-approval-mark §8 makes one: the address `mesh_wait`, the node, the module `node-tools` and the tool `mesh_wait`, - its build (the tool runner's own fingerprint, from its catalog entry of the version served; design 46 7-approval-mark - §8, Q-BD), and every + its build (the tool runner's own fingerprint, as the node-engine's `running..node-tools` last said it, which the + tool runner hears, design 47 G §309; Q-BD; while it is unknown, a marked wait is refused, saying so), and every argument given (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the bound. The tool runner refuses, asks, judges and claims it as that part's §9, §10, §11 and §13 to §17 do for a marked tool, asking on the seat `operator-channel` under its own module's name, `node-tools`, whose manifest gains @@ -350,7 +352,8 @@ approves, for a marked event or a restricted stream. so the restricted stream's event is heard only after the warrant; with a warrant for other names, another filter or another timeout it is refused; the same names `from: seat` ask nothing; a wait named `..` under a marked `.` is marked; and before the catalog half is read, a marked wait asks nothing, is - judged when the first view comes within its timeout, and is refused at its timeout when it does not. The warrant is + judged when the first view comes within its timeout, and is refused at its timeout when it does not. A marked wait while + the tool runner's own fingerprint is unknown is refused, saying so. The warrant is written on the test bus as the router's record. `Overlaps` gains rows in §1's table (`mesh.stream.mail.event.mail.>` overlaps `mesh.stream.mail.event.mail.*.message`; `mesh.mod.mail.event.x` does not overlap `mesh.stream.mail.event.x`), and §32's refusal a row in the indexer's `judge/rules_test.go`. diff --git a/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md b/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md index 6bd8676c..1e706344 100644 --- a/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md +++ b/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md @@ -3,7 +3,7 @@ layer: to-be design: 46 milestone: 7-approval-mark updated: 2026-11-11 -code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-host, mesh-catalog modules/zsh, mesh-catalog modules/gitea] +code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-host, mesh-catalog modules/zsh, mesh-catalog modules/gitea, mesh-catalog modules/mesh-files] decisions: - "ADR 0286" - "ADR 0259" @@ -14,6 +14,8 @@ decisions: - "ADR 0595" - "ADR 0597" - "ADR 0310" + - "ADR 0394" + - "ADR 0619" - "ADR 0268" - "ADR 0564" --- @@ -57,10 +59,10 @@ hear a restricted stream without a warrant. This is the same known limit as desi machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it: that a restricted stream is heard only on a warrant holds against every caller once that fix runs modules and sessions on accounts that cannot read the file, and until then against callers that go through the tool runner. The -prepare reads the file's mode and owner on every node with `/node-reads.stat` and writes them in the milestone +prepare reads the file's mode and owner on every node with `/node-files.stat` and writes them in the milestone file; a node where the file is readable beyond the operator's account is a finding of the prepare. The forge module's -own credential (§21) lies in a directory of mode 0700, owned by the forge's account, which `node-reads` cannot enter: -no tool answers that file's own mode, a missing read tool, said here; the directory's mode is what the prepare read. +own credential (§21) lies in a directory of mode 0700, owned by the forge's account, which the reading account cannot +enter; `node-files.stat` answers such a file's mode from this milestone (§21a), and the prepare reads it then. ## The mark @@ -114,45 +116,56 @@ no tool answers that file's own mode, a missing read tool, said here; the direct definitions of the catalog half carry the verb marks (§2). The tool runner, which from G takes its first view of the catalog half by `mesh-catalog.revision` and follows `catalog-revision` (design 47 G §196), derives from it, for each module it serves, its marked tools and verbs, in a package of its own, `node-tools/internal/approval`, again on - every revision it hears. No module's name is written in its code. The marks follow the running version: the runner - applies the marks of the module's version it serves, read from the assignment half's entry for that module on its - node (design 47 G §175; Q-VS), and, while the newest revision's entry for that module - differs, the union of both, so a mark is never lost before or after the module moves. A mark added while a call - waits applies from the next call, which the answer of §12 says. *Why:* ADR 0286 §5 had the controller issue the mark - in a membership so that a bundle could not drop it; no controller issues memberships now, and the judged catalog - half is the same guarantee: the bundle never carries the mark (ADR 0656 §6, one source for which modules exist). - The union keeps ADR 0286 §3's "marks add up" across a version change. *How it is checked:* unit, a table test in - `approval` derives the marks from fixture catalog halves, §3's row, a version change and the union among them. + every revision it hears. No module's name is written in its code. The marks follow the running version: a bundle's + served version is the fingerprint its composed environment carries (design 47 G §424), whose entry the runner reads + by `mesh-catalog.entry` (design 47 G §443) when it loads the bundle; it applies that entry's marks and, while the + newest revision's entry for that module differs, the union of both, so a mark is never lost before or after the + module moves, and a delivery that drops a mark while the old bundle still runs is still asked. When the served + entry cannot be read, every marked call on that runtime is refused (§19). A mark added while a call waits applies + from the next call, which the answer of §12 says. *Why:* ADR 0286 §5 had the controller issue the mark in a + membership so that a bundle could not drop it; no controller issues memberships now, and the judged catalog half is + the same guarantee: the bundle never carries the mark (ADR 0656 §6, one source for which modules exist). The union + keeps ADR 0286 §3's "marks add up" across a version change. The served version is read from what the bundle runs, + not from a placement, which can change before the bundle does (step 0b). *How it is checked:* unit, a table test in + `approval` derives the marks from fixture catalog halves and served entries, §3's row, a version change and the + union among them, and a served entry that cannot be read. 6. **A machine whose tool runner does not honour the mark serves no marked tool or verb.** The tool runner states - `approval-mark/1` among its call capabilities (`runtime.CallCapabilities`), which its announcement carries and its - node's node-engine takes into the node's record; the assignment half carries, for each node, its capability readings - and the time each was taken (`at`), which the assigner keeps from the node-engine's reading (design 47 G §194; - Q-CR). The grants part, composing a runtime's account, reads that runtime's node's readings: where the - tool runner does not state `approval-mark/1`, where the node has no readings, or where its readings are older than - the tool runner's version on that node (failing closed as §19), it composes a subscribe deny of every marked - subject of every module the runtime carries, the marks read from the catalog half as §5 has them: - `mesh.mod..tool.` and the same followed by `.*`; the - caller-named subject mesh-sdk's `process.Call` sends on first, `mesh.mod..call..>`; and for a marked - verb of a seat held per node `mesh.seat..tool.`, the same followed by `.`, and - `mesh.seat..call...>`; for a marked verb of a seat held once for the mesh - `mesh.seat..tool.` and `mesh.seat..call..>`, since its subjects name no node (Q-DL). - The bus's deny outranks its allow of `mesh.mod..tool.>` for each. The tool runner's version on a node is read from the - same assignment half revision, its entry for `node-tools` on that node (the module version placed there, design 47 - G §175), against the version the node-engine's `running..node-tools` last said running (design 47 G §309); - readings whose `at` is before that version ran are older. Going back to an older tool runner on a - node leaves the window until its readings update, in which the old deny or none stands; that window is accepted. `Permissions` gains `SubscribeDeny`, written into the user list as the server's - subscribe deny, as `PublishDeny` is for publishing; the user list's `User` gains `subscribe_deny` beside - `publish_deny`, in its comparison and in the differences the grants part says (Q-SD). The withheld tools and verbs are named in the grants part's read - of that account (`mesh-grants.grants_user_list`, a field `withheld` with each subject and "its tool runner does not - state approval-mark/1"), and in the node-engine's `composition` of the module, which reads them through a read verb - `withheld` that the seat `mesh-grants`'s definition gains (input `node`; answer each module's withheld subjects and - why), never from the grants part's state (Q-NE). *Why:* ADR 0286 §7, §8, §9: the bus + `approval-mark/1` among its call capabilities (`runtime.CallCapabilities`), which its announcement carries with + the tool runner's own fingerprint, and its node's node-engine takes both into the node's record; the assignment + half carries, for each node, its capability readings, each with the fingerprint of the runner that announced it + (design 47 G §194; Q-CR). The grants part, composing a runtime's account, reads that runtime's node's readings and + the fingerprint the node-engine's `running..node-tools` last said running (design 47 G §309): where the tool + runner does not state `approval-mark/1`, where the node has no readings, or where the readings' fingerprint differs + from the running one or is absent (an older reading, failing closed as §19; no time is compared), it composes a + subscribe deny of every marked subject of every module the runtime carries, the marks read from the catalog half + as §5 has them: `mesh.mod..tool.` and the same followed by `.*`; the caller-named subject mesh-sdk's + `process.Call` sends on first, `mesh.mod..call..>`; for a marked verb of a seat held per node + `mesh.seat..tool.`, the same followed by `.`, and `mesh.seat..call...>`; and for + a marked verb of a seat held once for the mesh `mesh.seat..tool.` and `mesh.seat..call..>`, + since its subjects name no node (Q-DL). The bus's deny outranks its allow of `mesh.mod..tool.>` for each. + Going back to an older tool runner on a node leaves the window until its readings update, in which the old deny or + none stands; that window is accepted. `Permissions` gains `SubscribeDeny`, written into the user list as the + server's subscribe deny, as `PublishDeny` is for publishing; the user list's `User` gains `subscribe_deny` beside + `publish_deny`, in its comparison and in the differences the grants part says (Q-SD). An added `subscribe_deny` is a + narrowing: the user-list guard lets it pass at once, on the first composition after the release, and counts it as + no right lost. The withheld tools and verbs are named in the grants part's read of that account + (`mesh-grants.grants_user_list`, a field `withheld` with each subject and "its tool runner does not state + approval-mark/1"), and in the node-engine's `composition` of the module, which reads them through a verb `withheld` + that the seat `mesh-grants`'s definition gains: class `read`, callers the node-engine, persons and the operator, + input `node`, answer each module's withheld subjects and why, with a fixture in the definition; the node-engine's + manifest gains `mesh-grants` in `uses`; when the verb does not answer, the composition says "withheld unknown: the + grants part does not answer"; never read from the grants part's state (Q-NE). *Why:* ADR 0286 §7, §8, §9: the bus enforces the mark; the runner's status of ADR 0286 §7 is read from the grants part's account read, since a runner - that does not honour the mark is a version that cannot say so. A call that reaches no responder because its + that does not honour the mark is a version that cannot say so. A reading is tied to the runner that made it, not to + a time, so a clock or a late reading cannot lift the deny (step 0b). A call that reaches no responder because its subject is withheld is answered by the mesh MCP server naming `withheld` and the grants part's read. *How it is - checked:* unit, the grants part's composition table: readings lacking the capability, no readings, and readings - older than the runner each compose the deny of every subject above, the caller-named ones and a marked seat verb's - `.` form among them, each named in `withheld`; readings with the capability compose no deny; integration, the + checked:* unit, the grants part's composition table: readings lacking the capability, no readings, readings with + another runner's fingerprint and readings with none each compose the deny of every subject above, the caller-named + ones, a marked seat verb's `.` form and a mesh-wide seat's marked verb among them, each named in `withheld`; + readings with the capability and the running fingerprint compose no deny; the release's first composition passes + the user-list guard with the deny added; a node gaining `approval-mark/1`, where only its readings change, has the + deny removed; unit, the assigner's table: a reading keeps the announcing runner's fingerprint; whole (node-engine), + `composition` shows the withheld subjects, and with the grants part down says "withheld unknown"; integration, the grants part's real-bus test: an account composed with the deny is refused delivery of a call on a marked subject while it holds `mesh.mod..tool.>`, and so is a call sent through `process.Call` on the caller-named subject. @@ -168,7 +181,7 @@ no tool answers that file's own mode, a missing read tool, said here; the direct 8. **The act is digested with operator-channel's client.** For a call of a marked tool or verb, the tool runner builds an `operatorchannelclient.Act` with `address` (as called), `node` (the node that serves it), `module` and - `tool`, or `seat` and `verb`, `build` (the module's fingerprint, from its catalog entry of the version served; Q-BD), and `arg.` for each + `tool`, or `seat` and `verb`, `build` (the fingerprint the bundle's environment carries, the version it serves, §5; Q-BD), and `arg.` for each argument given, a string as itself and any other value as its JSON in RFC 8785's canonical form; an argument left out is not in the act. Its digest is `operatorchannelclient.ActDigest`. *Why:* ADR 0286 §10, §11; the digest is in mesh-contracts since ADR 0627 §1. The TypeScript twin ADR 0286 named is not built: the tool runner is Go, and no @@ -210,13 +223,18 @@ no tool answers that file's own mode, a missing read tool, said here; the direct called is named in the answer ("marked since "). *Why:* ADR 0286 §14, §37; the caller calls again, as the operator chose on 2026-11-11 ("Call again (as designed)") over a held call that runs on the Approve and returns its outcome (issue 417, 2026-10-16). *How it is checked:* unit, - rows of §22's table hold both answers; a row with no router holds that a second call asks nothing new. + rows of §22's table hold both answers; a row with no router holds that a second call asks nothing new; a row holds + a headline whose names are cut with "…" to 60 characters (Q-HL). ## The runner's record, and the same call again -13. **The runner keeps its asks and claims in a state of its own, per machine.** The tool runner's manifest asks in - `requires` for a state `approvals`, template `state`, `per-machine`, `ttl-seconds` 172,800 (48 hours), and no cap of - its own: the `state` template's fixed size holds (ADR 0595 §2; Q-CAP). +13. **The runner keeps its asks and claims in a state of its own, per machine.** The tool runner, a foundation part, + declares a state `approvals` in its manifest's old `state` section, per machine, with a time to live of 172,800 + seconds (48 hours), which the grants part creates as it creates a foundation part's states (ADR 0619 §2, §3; the bus + module refuses a per-machine state in the new form today). Its size is the old form's fixed one, 64 MiB + (`StateMaxBytes`), which §18 reads from the bucket, and a value is at most 256 KiB: an ask whose kept call would pass + it fails closed, naming the cause (ADR 0595 §3; Q-CAP). A runner whose membership binds no `approvals` serves every + unmarked call as before and refuses only marked ones (§19). Its keys: `.ask.`, the asked call as the client keeps it (the ask, the act, the whole arguments, when asked, whether acted on, and `recorded` once `warranted` answered, §16; the client's `Kept` gains `act`, `arguments` and `recorded` for it, Q-KP), and `.act.`, the id of the @@ -227,7 +245,7 @@ no tool answers that file's own mode, a missing read tool, said here; the direct per-machine state on each account, and every module's per-machine keys keep their form; mesh-sdk's rebuild of it rides in G's group. That size is the bucket's, shared by every machine's keys. A write the bus refuses (the size reached, or any other) fails closed under §19 and is said in the answer; `mesh_approvals` reports the bucket's fill - against the template's size, saying that both are every machine's together. A run still unrecorded (§16) an hour before its key's 48 hours end is written in the runner's log + against its size read from the bucket, saying that both are every machine's together. A run still unrecorded (§16) an hour before its key's 48 hours end is written in the runner's log as an error naming the ask; this is written only while the runner runs, so a machine asleep through that hour keeps only the `call-unanswered` history of §16, which is accepted. *Why:* ADR 0286 §24, §30 kept the waiting call in the runner's record and claimed a warrant in the node's own file, @@ -236,7 +254,9 @@ no tool answers that file's own mode, a missing read tool, said here; the direct writer per machine, so the file is not built. The keys below the machine's own depart from ADR 0260 §16 (a per-machine state reaches only its machine's key), the design's choice following the operator's answers of 2026-11-11 (the gate built in G); the key form is the design's choice too. *How it is - checked:* unit, the manifest test of `node-tools` holds the entry; the grants part's composition table holds the + checked:* unit, the manifest test of `node-tools` holds the old-form entry (per machine, its time to live) and no + `approvals` in `requires`; a row of §22's table: an ask over 256 KiB refused naming the cause; another: with no + binding, an unmarked call runs and a marked one is refused; the grants part's composition table holds the `.>` reach and no other node's; integration, §22's whole test reads the keys. 14. **The same call again is judged on the router's record.** When a call of a marked tool comes and `.act.` names an ask, the runner reads the router's own record of that ask under the module's name (`Client.Where`, the @@ -256,15 +276,17 @@ no tool answers that file's own mode, a missing read tool, said here; the direct 16. **The call runs, and its warrant is recorded, across a restart too.** Once the checks pass, the runner runs the call and answers with the tool's own answer and a line naming the approval ("run on the operator's approval via at