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 9bed17e6..6f69461d 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-catalog modules/zsh] +code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog modules/zsh, mesh-catalog modules/gitea] decisions: - "ADR 0286" - "ADR 0259" @@ -35,8 +35,8 @@ gate in G"). **Where.** mesh-sdk (the manifest field, the seat definition's field, the per-machine state's keys), mesh-contracts (the login shell's seat definition), mesh-delivery (the indexer's manifest validator and catalog graph entry, the grants -part's deny), mesh-tools (the tool runner's gate, its state, its tools) and the mesh MCP server's `mesh_describe` in -it. Nothing is built in a controller: no controller issues memberships today, bus permissions are the grants part's +part's deny), mesh-tools (the tool runner's gate, its state, its tools, and the mesh MCP server's `mesh_describe` in +it) and mesh-catalog (the zsh module, which holds the login shell, and the forge module, §21). Nothing is built in a controller: no controller issues memberships today, bus permissions are the grants part's (seat `mesh-grants`), the hand-act log is the seat `mesh-hand-acts`, and the asker's logic is operator-channel's client in mesh-contracts (ADR 0627 §1). @@ -46,13 +46,16 @@ digest is Go alone (§8), the claim is kept in the runner's bus state and not a and not a retry on a timer (§16), and a fourth open ask is offered as G makes it (§17). The controller's own verbs (ADR 0286 §43) are not built: no controller serves verbs. -**The bound of the gate.** The gate holds for every call that reaches the tool runner's handler. It does not hold for -a process that reads the tool runner's bus credential (the file `broker` in its state directory, `own-secrets`) and -speaks to the bus itself: its account may subscribe a marked subject on a machine whose runner honours the mark. Who -may read that file is therefore the bound of the operator's choice; the prepare reads its mode on every machine with -`/node-reads.stat` and writes it in the milestone file, and a mode readable beyond the operator's account is a -design question for the operator before develop. The same limit as design 47 G §385 (one tool-runner account per -machine). +**The bound of the gate, a known limit.** The gate holds for every call that reaches the tool runner's handler. It does +not hold for a process that reads the tool runner's bus credential (the file `broker` in its state directory, +`own-secrets`) and speaks to the bus itself: that account may subscribe a marked subject, a restricted stream's among +them, on a machine whose runner honours the mark. Read on the workstation on 2026-11-11 with `node-reads.stat`, the +file is mode 0600, owned by the operator's account, in a directory of mode 0755: every process running as the +operator's account, every module on the machine's shared account and every agent session among them, can read it and +hear a restricted stream without a warrant. This is the same known limit as design 47 G §385 (modules running as the +machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it. The +prepare reads the file's mode and owner on every node with `/node-reads.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 mark @@ -70,14 +73,19 @@ machine). 4. **The manifest validator refuses a mark it cannot honour.** The indexer's manifest validator (`judge/rules.go`) refuses, as the checker runs it, and sets aside to wait, as the indexer runs it, a manifest that: marks a tool its `tools` does not list, or a verb its `claims` do not serve (ADR 0286 §4); marks a tool or a verb, or holds a seat - verb its seat marks, without `operator-channel` in `uses` (§7); marks a tool or a verb and declares a resource of - type `process`, whose tools its own program answers and no tool runner gates; or names in `needs-approval` one name - that is both one of its tools and one of its events. Each refusal names the entry and the rule. *Why:* a mark + verb its seat marks, without `operator-channel` in `uses` (§7); marks a tool or a verb while every artifact of kind + `bundle` it builds is run by one of its `process` resources (`artifact`), so its own program answers its tools and + no tool runner loads or gates them (a module such as the forge's, whose tools bundle no process runs, is not + refused); or names in `needs-approval` one name that is both one of its tools and one of its events. A tool mark + is checked against `tools` where the manifest lists its tools; where only its bundle names them, the tool runner + checks each marked tool against the tools the bundle registers at load, and a mark naming none is its finding + `..mark-unserved` (said as §16's), cleared when the bundle serves it or the mark goes. Each refusal names the entry and the rule. *Why:* a mark nothing enforces would be a call that runs unasked while saying it is guarded; ADR 0286 §43 had the controller enforce its own verbs, and no other own-process server has a gate, so such a mark is refused rather than ignored; and an entry that names two things is read one way by one reader and another way by the next (the design's choices - under ADR 0286 §4, §40). *How it is checked:* unit, one row per refusal in `judge/rules_test.go`, and a row that - accepts the zsh module's manifest of §21. + under ADR 0286 §4, §40, the refusal kept by lead-47 on 2026-11-11). *How it is checked:* unit, one row per refusal + in `judge/rules_test.go`, rows that accept the zsh and forge manifests of §21, and a row of §22's mark table for + `mark-unserved`. ## Where the marks are read, and where they are withheld @@ -156,9 +164,9 @@ machine). run, asked; not yet taken by the router (ask , fingerprint …); make the same call again later." The ask stays in the seat's stream of accepts, a work queue the router takes it from when it runs, and the `act.` key stays, so the next call reads the router's record of the same ask and asks nothing new. A mark added since the caller last - called is named in the answer ("marked since "). *Why:* ADR 0286 §14, §37. The caller calls again: the - held call the operator asked for on 2026-10-16 (issue 417), which runs on the Approve and returns its outcome, is - with the operator; this line is written for "call again" until the operator answers. *How it is checked:* unit, + 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. ## The runner's record, and the same call again @@ -249,7 +257,17 @@ machine). the seat, gains `operator-channel` in `uses`. The control-node keeps withholding `execute`, and whether it serves it again under the mark stays the operator's setting. *Why:* ADR 0286 §44, §45: the reads it carried have their tools on every node (the seat `node-reads`, ADR 0310; issue 418 resolved 2026-10-12, answering on every node since - 2026-10-16). No other tool or verb is marked in this milestone. *How it is checked:* live, as the acceptance says. + 2026-10-16). *How it is checked:* live, as the acceptance says. + - **The forge's two acts on a trunk's protection are marked.** The forge module (mesh-catalog `modules/gitea`) + gains a tool `gitea_branch_protection_loosen`, marked in its `needs-approval`, which alone performs the two acts + `gitea_branch_protection_set_full` refuses today: removing a trunk's protection, and allowing a force-push onto + a trunk; `gitea_branch_protection_set_full` keeps refusing both and its refusal names the new tool. The manifest + gains `operator-channel` in `uses`. No other tool or verb is marked in this milestone. *Why:* the forge's own + refusal says these two acts are the operator's and waited for this mark (issue 417, 2026-10-28); a mark is on a + whole tool (ADR 0286 §1), so the acts move to a tool of their own, as ADR 0286 §48 splits a tool into an unmarked + and a marked form, and tightening a protection stays unasked; marked now pending the operator's answer of + 2026-11-11. *How it is checked:* the forge module's tool test: `set_full` refuses both acts naming the new + tool; the new tool performs each; and live, a loosening asked on the phone runs once on the Approve. ## Tests, delivery and going back