@sabaiway/agent-workflow-kit 5.4.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -0
- package/README.md +1 -0
- package/SKILL.md +5 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +1 -1
- package/bridges/antigravity-cli-bridge/capability.json +1 -1
- package/bridges/codex-cli-bridge/SKILL.md +51 -4
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +616 -24
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +700 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +1 -1
- package/bridges/codex-cli-bridge/capability.json +15 -10
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/dispatch.md +29 -0
- package/references/modes/gates.md +6 -3
- package/references/modes/procedures.md +2 -0
- package/references/modes/receipt-deadline.md +3 -3
- package/references/modes/recommendations.md +1 -1
- package/references/modes/velocity.md +1 -0
- package/tools/commands.mjs +7 -0
- package/tools/core-evidence.mjs +37 -3
- package/tools/detect-backends.mjs +5 -4
- package/tools/dispatch-record.mjs +10 -3
- package/tools/dispatch-store.mjs +392 -0
- package/tools/dispatch.mjs +1779 -0
- package/tools/doc-parity.mjs +10 -2
- package/tools/exec-producer.mjs +483 -0
- package/tools/exec-receipt.mjs +263 -0
- package/tools/flow-check-cores.mjs +253 -0
- package/tools/flow-check-git-lane.mjs +56 -0
- package/tools/flow-check-rungs.mjs +330 -0
- package/tools/flow-check.mjs +23 -611
- package/tools/flow-store.mjs +111 -462
- package/tools/gates-declaration.mjs +13 -1
- package/tools/gates-init.mjs +134 -22
- package/tools/procedures.mjs +64 -5
- package/tools/receipt-deadline.mjs +25 -3
- package/tools/recommendations.mjs +108 -7
- package/tools/release-scan.mjs +33 -0
- package/tools/source-size-check.mjs +320 -0
- package/tools/source-size-config.mjs +244 -0
- package/tools/source-size-core.mjs +53 -0
- package/tools/source-size-gate-cmd.mjs +55 -0
- package/tools/source-size-judge.mjs +114 -0
- package/tools/source-size-refusal.mjs +70 -0
- package/tools/source-size-report.mjs +254 -0
- package/tools/source-size-scope.mjs +145 -0
- package/tools/store-append.mjs +444 -0
- package/tools/velocity-profile.mjs +24 -3
|
@@ -278,7 +278,7 @@ DEFAULT_CODEX_EFFORT="xhigh"
|
|
|
278
278
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
279
279
|
# version (drift-guarded by codex-review.test.mjs against capability.json).
|
|
280
280
|
AW_RECEIPT_BACKEND="codex"
|
|
281
|
-
AW_BRIDGE_VERSION="3.
|
|
281
|
+
AW_BRIDGE_VERSION="3.5.0" # aw-version-anchor
|
|
282
282
|
CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
|
|
283
283
|
CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
|
|
284
284
|
# Generous hard cap for a slow xhigh review (subscription latency varies).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "codex-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.5.0",
|
|
7
7
|
"posture": { "model": "gpt-5.6-sol", "effort": "xhigh", "tier": null },
|
|
8
8
|
"provides": ["execute", "review"],
|
|
9
9
|
"roles": {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
"output": "diff",
|
|
14
14
|
"contract": {
|
|
15
15
|
"invocations": [
|
|
16
|
-
"codex-exec <plan-file|->",
|
|
17
|
-
"codex-exec <plan-file|-> -- <extra codex flags...>"
|
|
16
|
+
"codex-exec [--nonce <n>] <plan-file|->",
|
|
17
|
+
"codex-exec [--nonce <n>] <plan-file|-> -- <extra codex flags...>"
|
|
18
18
|
],
|
|
19
19
|
"grounding": "automatic — the root AGENTS.md (Hard Constraints) is auto-merged into codex's context and the wrapper prepends the orchestrator execution contract; no grounding flags",
|
|
20
20
|
"continue": [
|
|
21
|
-
"codex-exec --resume-last <plan-file|->",
|
|
22
|
-
"codex-exec --resume <session-id> <plan-file|->"
|
|
21
|
+
"codex-exec --resume-last [--nonce <n>] <plan-file|->",
|
|
22
|
+
"codex-exec --resume <session-id> [--nonce <n>] <plan-file|->"
|
|
23
23
|
],
|
|
24
|
+
"receipt": "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, no node.",
|
|
24
25
|
"passthrough": {
|
|
25
26
|
"policy": "guarded",
|
|
26
27
|
"blocked": ["-c*", "--config*", "-s*", "--sandbox*", "--dangerously-bypass-approvals-and-sandbox", "--dangerously-bypass-hook-trust", "--full-auto", "--oss", "--local-provider*", "-p*", "--profile*", "-m*", "--model*", "-o*", "--output-last-message*", "--json*", "--color*", "--output-schema*", "--ephemeral*"],
|
|
@@ -75,14 +76,16 @@
|
|
|
75
76
|
{ "contractField": "invocations", "index": 1 }
|
|
76
77
|
],
|
|
77
78
|
"operands": [
|
|
78
|
-
{ "slot": "<
|
|
79
|
+
{ "slot": "[--nonce <n>]", "required": false, "description": "the delegation dispatch nonce — the plain-argument lane onto the AW_DISPATCH_NONCE seam (dispatch open records it; flag and a non-empty env must agree), recognised only before the prompt operand" },
|
|
80
|
+
{ "slot": "<plan-file|->", "required": true, "description": "the plan or instruction file, or - to read it from stdin (a nonced dispatch requires the FILE — contractDigest binds it)" },
|
|
79
81
|
{ "slot": "<extra codex flags...>", "required": false, "description": "extra codex flags, filtered by the guarded passthrough tiers" }
|
|
80
82
|
],
|
|
81
83
|
"guardrails": [
|
|
82
84
|
{ "value": "runs under codex's OWN OS sandbox (workspace-write)", "enforcement": "enforced", "condition": "it cannot nest inside another sandbox — the FS turns read-only; route it outside on the OBSERVED failure", "source": "capability.json roles.execute.contract.notes" },
|
|
83
85
|
{ "value": "the guarded passthrough blocks model / sandbox / approval overrides", "enforcement": "enforced", "source": "bin/codex-exec.sh" },
|
|
84
|
-
{ "value": "hard wall-clock cap CODEX_HARD_TIMEOUT (built-in default 3600s)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped", "source": "capability.json settings.CODEX_HARD_TIMEOUT" },
|
|
85
|
-
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=fresh|resume:<id>; timeout is banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" }
|
|
86
|
+
{ "value": "hard wall-clock cap CODEX_HARD_TIMEOUT (built-in default 3600s)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped, and a NONCED run refuses pre-spend", "source": "capability.json settings.CODEX_HARD_TIMEOUT" },
|
|
87
|
+
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=fresh|resume:<id>; timeout is banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" },
|
|
88
|
+
{ "value": "a NONCED run reserves its nonce before any spend and publishes a fail-closed terminal receipt at exit", "enforcement": "enforced", "condition": "nonce-supplied runs only — a nonce-less invocation writes no artifact at all", "source": "capability.json roles.execute.contract.receipt" }
|
|
86
89
|
],
|
|
87
90
|
"customHooks": ["CODEX_PROBE"]
|
|
88
91
|
},
|
|
@@ -95,7 +98,8 @@
|
|
|
95
98
|
"whenNotTo": ["a fresh task — a stale session carries stale assumptions"],
|
|
96
99
|
"invocationRefs": [{ "contractField": "continue", "index": 0 }],
|
|
97
100
|
"operands": [
|
|
98
|
-
{ "slot": "<
|
|
101
|
+
{ "slot": "[--nonce <n>]", "required": false, "description": "the delegation dispatch nonce — the plain-argument lane onto the AW_DISPATCH_NONCE seam, recognised only before the prompt operand" },
|
|
102
|
+
{ "slot": "<plan-file|->", "required": true, "description": "the follow-up instruction file, or - to read it from stdin (a nonced dispatch requires the FILE — contractDigest binds it)" }
|
|
99
103
|
],
|
|
100
104
|
"guardrails": [
|
|
101
105
|
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=resume:<id> only AFTER the sidecar id is resolved and validated; timeout is banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" }
|
|
@@ -111,7 +115,8 @@
|
|
|
111
115
|
"invocationRefs": [{ "contractField": "continue", "index": 1 }],
|
|
112
116
|
"operands": [
|
|
113
117
|
{ "slot": "<session-id>", "required": true, "description": "the session id the original run printed on stderr" },
|
|
114
|
-
{ "slot": "<
|
|
118
|
+
{ "slot": "[--nonce <n>]", "required": false, "description": "the delegation dispatch nonce — the plain-argument lane onto the AW_DISPATCH_NONCE seam, recognised only before the prompt operand" },
|
|
119
|
+
{ "slot": "<plan-file|->", "required": true, "description": "the follow-up instruction file, or - to read it from stdin (a nonced dispatch requires the FILE — contractDigest binds it)" }
|
|
115
120
|
],
|
|
116
121
|
"guardrails": [
|
|
117
122
|
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=resume:<id> only AFTER the explicit id is validated against the session-id grammar; timeout is banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" }
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### Mode: dispatch
|
|
2
|
+
|
|
3
|
+
<!-- opt-in-capability: none — the engine is invoked explicitly per sub-task or per report; it guards no repeatable surface and there is nothing a deployment could be missing without knowing it -->
|
|
4
|
+
|
|
5
|
+
The **delegation engine** (delegation Plans 1–2): the sub-task **contract check**, the records a human hand-writes into the delegation ledger, the four **writer verbs** that put a delegated thread on the record, the **arrival waiter**, and the L0 **acceptance report** over one wave. It exists because nothing today records `{dispatched → returned → folded}` for EXEC work, so "how much does delegating actually buy?" is unanswerable. The ledger answers it; this mode is the surface a number enters it through, and the surface it is read back from.
|
|
6
|
+
|
|
7
|
+
Run **`node ${CLAUDE_SKILL_DIR}/tools/dispatch.mjs <check|register|observe|open|await|return|fold|degrade|aggregate> …`**:
|
|
8
|
+
|
|
9
|
+
1. **`check <dispatch-file>`** — validates the ONE ```` ```aw-dispatch-contract ```` fenced block the dispatch file carries: exit `0` when its form is valid, `1` naming the FIRST violated field. The contract of the whole mode, in one sentence: the contract check is FORM-only — fields present, grammars respected, never boundedness, design-decidedness or acceptance adequacy — and `aggregate` REFUSES instead of computing acceptance for a wave with no pre-registration record, over an OPEN thread in scope, over a PRE-DISPATCH degrade that opens no thread, or across several waves with no `--wave`; the writer verbs add NO second legality door — the store's preflight is the only one, and its refusals travel verbatim — while `open` copies every mint-time field from the contract header and refuses a deadline below the wrapper cap plus the kill grace, `return` absorbs only a TERMINAL exec receipt whose backend, nonce and independently computed contractDigest match the dispatch it answers, and `fold` binds the CURRENT tree to the folded return's postTreeDigest, so a tree that moved between the two never folds, and `await` is satisfied ONLY by the TERMINAL exec receipt of its own dispatch's {backend, nonce} — never by a review receipt, a ledger line or a finding manifest — while an expiry names a supervision question and releases NO writer slot. Whether the sub-task is genuinely bounded, whether its design is decided, and whether its acceptance criteria are adequate stay **explicit orchestrator judgment** — a well-formed absurdity passes here by construction.
|
|
10
|
+
2. **`register`** — appends the wave's **pre-registration** record (`--wave --step-classes --pairing-key --min-per-class --mean-l-threshold --first-pass-num --first-pass-den`). Acceptance is pre-registered so thresholds can never be chosen after the observations that will be judged against them; a registration is **immutable per wave** and a second one refuses. `--pairing-key` accepts only a key the aggregator actually implements (today `stepClass`) — a wave registered under any other key would record a contract the computation never follows, and the aggregator re-checks the registration independently, since a store can be hand-written.
|
|
11
|
+
3. **`observe`** — appends ONE **observation** (`--wave --step-class --scope --plan --phase --provenance [--denominator-bytes]`). Provenance is `solo-construction` or `self-reported` **only**: delegated accounting is DERIVED from nonce threads and is never hand-appended. The numerator is the post-image bytes of the objects the scope names; a **solo** observation takes that same number as its denominator — `L = 1` by construction, so `--denominator-bytes` is refused — while a self-reported one states its own. One stated exception: a solo scope that measures **zero** bytes has no ratio at all, and is recorded INELIGIBLE by name (`zero-denominator`) rather than as `L = 1` — `0/0` is undefined before it is one, and a named ineligibility is what this family records instead of a silent number. **`--scope` is repeatable**, one repo-relative path per occurrence: no separator is safe inside a POSIX path, so a split list could not express `docs/my file.md` and would silently measure the wrong set wherever its fragments happened to exist. Paths are anchored at the **git top-level**, never at the caller's cwd, and the record carries a **JSON-encoded array of canonical repo-relative paths, carried in the schema's string field** (the record's `scope` is a string by D3, so the array travels encoded). An object's identity IS its canonical path, so two files with identical bytes are two objects and count twice, while one path reached twice (a repeated listing, an in-repo ancestor symlink) is one object and counts once. The run refuses by name outside a git work tree, for an absent or non-regular path, and for a path whose **real** location leaves the repository (an ancestor symlink is not a way out).
|
|
12
|
+
4. **`open`** — appends the **dispatch** record that opens a thread (`--contract <dispatch-file> --wave --backend --rationale --wrapper-cap-s --kill-grace-s [--retry-of <nonce>]`). Every mint-time field — `nonce`, `stepClass`, `vehicle`, `deadlineS`, `retry.index`, `retry.cap` — is **COPIED from the contract header** and bound by `contractDigest`, so a dispatch can never disagree with the header it claims to carry. It fingerprints the tree (`preTreeDigest`) and probes it for a CLEAN baseline; a **dirty** one is recorded as such, and the eventual return is then metric-INELIGIBLE by the name `dirty-baseline` — the uncommitted-state fingerprint is blind to the index↔worktree split, so a dirty baseline cannot attribute bytes to a dispatch. It **refuses a `deadlineS` below `--wrapper-cap-s` + `--kill-grace-s`** (both explicit operands: the kit never reads another package's default), because a dispatch that cannot be capped inside its own deadline could never honour the terminal-exit rule. Every retry rule — one successor per thread, the cap read off the chain's ORIGIN, wave and class equality, a changed contract after a `contract-refusal` — is the **store's**, and `open` surfaces its refusals verbatim.
|
|
13
|
+
5. **`await`** — watches for ONE dispatch to **answer**, and writes nothing at all (`--nonce [--timeout <s>]`). Satisfaction is the arrival of the **TERMINAL** exec receipt for the dispatch's own `{backend, nonce}`: a `reserved` artifact means the run holds the nonce and has published nothing about how it ended, so the wait continues — the same artifact that is a **supervision refusal** at `return`, because absorbing it would record an outcome nobody observed. Satisfaction is decided **positively**, by the exec-receipt reader itself, so an artifact of a neighbouring family standing at that path — a review receipt, a delegation ledger line, a finding manifest — **refuses** rather than satisfies: only an exec receipt answers an exec dispatch, which is the D10 rule the review waiter states from its own side. The bound is the **ABSOLUTE** deadline, measured from the DISPATCH record's timestamp rather than from whenever this wait began: `--timeout` defaults to the time **remaining**, and one reaching past the deadline is **refused** rather than silently clamped (a shortened wait would let a caller believe they waited longer than they did). An already-expired dispatch is **answered immediately**, never waited on. **Arrival is read first, before EVERY bound** — the two wait bounds and the `--timeout` admissibility check alike, so a receipt already on disk answers `ARRIVED` even when the supplied `--timeout` is inadmissible, and the check runs only once there genuinely IS a wait to bound. A receipt already on disk is a fact this verb reports; whether a *late* one may be absorbed is the absorb door's question, refused there by name — **lateness has exactly one decision site, and it is `return`**. The review waiter checks its deadline first for the opposite reason: a receipt *line* carries no dispatch-bound timestamp at all, so there the clock is the only evidence there is. Each pass consults the clock over the poll that has already happened, then sleeps and polls again, so a receipt landing during the last sleep is reported rather than lost to a cutoff a moment later. An unanswered wait exits **`3`** — its own status so a caller that **branches** on the code can tell it from a refusal, never a defence against one that discards failure wholesale — names whether the **deadline** or the `--timeout` ended it, and states that **no writer slot was released**: a wait that ended without an answer never authorizes the next dispatch. Every artifact read rides the store's own no-follow reader, so a symlinked, FIFO, unreadable or malformed artifact refuses **by class** instead of standing in for an answer.
|
|
14
|
+
6. **`return`** — absorbs the wrapper's exec receipt and appends the **return** record (`--nonce [--outcome <o>] [--no-receipt --exit-status <n>]`). The receipt and its report live **beside the ledger**, in the store's own directory. It reads the **TERMINAL** receipt — a `reserved` one is a **supervision question**, not a timeout, and refuses by saying so — then checks the receipt's independently computed `contractDigest` against the dispatch's (a run that executed a *different* contract would otherwise correlate cleanly), its `{backend, nonce}` body against the dispatch it answers (the filename alone is composed by this side), its `capS + killGraceS` against the recorded `deadlineS`, and its timestamp against the **ABSOLUTE** deadline (`dispatch.timestamp + deadlineS`) — a late return refuses by name. It re-verifies the report's digest and length against the receipt, refuses a change set **hidden from the plain `git diff`** the metric is computed over (assume-unchanged, skip-worktree, `diff.ignoreSubmodules` — the numerator and the denominator would both be blind to it), and then enumerates the returned objects and frames the canonical integration bundle, **bracketed by the tree fingerprint**: a tree that moves mid-computation refuses rather than mixing two change sets. `--outcome` records an orchestrator judgment under the **closed override form** — a wrapper outcome either stays itself or moves to `contract-refusal | partial-edit | acceptance-failure | stale-return | store-failure`, and `success` is recordable only from a receipt that already says success; the record vocabulary's own cross-field rules cut the remaining incompatible pairs. **`--no-receipt`** means "no TERMINAL receipt", never "no evidence": it absorbs a **reservation ONLY** — over a `terminal` artifact it refuses, because a hand-stated outcome there would discard an exit status, a session id and a report digest the run actually proved, and skip the report check that guards them. It builds the return from that reservation (`wrapperVersion` and `posture`, never hand-typed), requires `--exit-status` and an `--outcome` from `transport-failure | contract-refusal | store-failure | missing-identity` (so `partial-edit` and `stale-return` are inexpressible there by name), and reads the report **if present** — an absent one records length 0 and the metric is then ineligible **by name**: `empty-report` where there IS a diff, and `no-op-diff` where the tree is unchanged too, since the eligibility rule names the diff first. That keeps a failed REPORT write and a failed TERMINAL write two separately named lanes. With **no artifact at all** there is no honest return — `--no-receipt` reads the same path and would refuse the same way — so close the thread with `degrade`.
|
|
15
|
+
7. **`fold`** — appends the integration re-confirmation and CLOSES the thread (`--nonce --verdict <text>`). It runs the **same hidden-path guard** as `return`, and here for a sharper reason: `treeDigestAtFold` is computed from the very payload that is blind to those paths, so a change made behind an `assume-unchanged` / `skip-worktree` bit between the return and the fold leaves the digest **equal** and the fold would accept bytes nobody returned — the digest cannot catch what the payload cannot see. It then computes the **current** tree digest, which the store binds to the folded return's `postTreeDigest`: a tree that moved between the return and the fold refuses in the store's own words, and the recovery it names is a fresh dispatch, never a fold. Staging usually moves the tree too — every change set carrying an untracked path does — so the order is `return` → review (own eyes + the configured review recipe) → a plain gate run → `fold` → `git add`. **The one shape staging does not move** is a tracked-only change passing from the worktree into a **clean** index: the payload concatenates the staged and unstaged diffs, so those bytes are identical either side of `git add` (the uncommitted-state fingerprint's stated blindness to the index↔worktree split). The fold is still honest there — identical payload bytes mean the folded content IS the returned content — so the rule is "**the fold precedes staging**", never "staging refuses the fold by construction".
|
|
16
|
+
8. **`degrade`** — appends the recorded no-fold closure (`--wave --step-class --rationale [--nonce]`), **threaded** (with `--nonce`) or **pre-dispatch** (without). The pre-dispatch form opens no nonce thread, so once one is recorded `aggregate` **REFUSES the whole wave by name** until the wave's counting unit is decided — said here and on the line that writes it, because it is a live cost of the record, not a surprise to discover later.
|
|
17
|
+
9. **`aggregate [--wave <id>]`** — reports one wave: the registered thresholds, every observation (context — never part of the acceptance number), and per registered step class the delegated threads with the **D7 inclusion table** applied. A folded **success** with an eligible `wrapper-git` metric contributes its `L`; a folded success whose metric is ineligible or self-reported is EXCLUDED from the mean and from `n` while still counting in the first-pass rate; a failure-terminal thread, a degrade-closed one, and a folded **acceptance-failure** (the §7 fold-fix — the orchestrator paid for the same work twice, so its bytes are not leverage) are each in `n` at `L = 0`. The first-pass rate is per retry CHAIN: its denominator is the `retryIndex`-0 threads, so a retry never reports one piece of work twice. Below the registered minimum acceptance is **NOT computed** (insufficient); at or above it is computed and labeled **PILOT** evidence. The primary statistic is the per-class MEAN of `L`; the byte-weighted aggregate prints as SECONDARY only.
|
|
18
|
+
|
|
19
|
+
**Exit codes:** `0` success (a computed report and an insufficient one both exit 0 — go/no-go is judgment, never an exit code); `1` a refusal (a form violation, an unreadable dispatch file, a supervision question, any store STOP verbatim); `2` usage; `3` an `await` that ended with no terminal receipt — its own status so a caller that **branches** on the exit code can tell an unanswered wait from a refusal; a caller that discards every failure discards this one too.
|
|
20
|
+
|
|
21
|
+
**A concealing tree is refused at every door that measures or binds one** (`open`, `return`, `fold`) — the recorded baseline, the counted bytes and the folded identity are all claims about a tree that is telling the truth. Two arms, and the first refuses a **bit**, not its effect: any index entry whose `ls-files` tag is not `H` (assume-unchanged, skip-worktree, an unmerged index) stops the verb, because deleting a *materialized* skip-worktree file is invisible to `computeWorkingState`, to the plain diff, to the tree fingerprint **and** to the producer's enumeration — there is no pair of views whose difference could reveal it, and at `open` it is worse than invisible: the baseline records CLEAN and the delegate is later credited with a deletion it never made. The second arm compares the forced diff against the plain one **per side and on raw bytes** (a union lets a path visible on one side mask its hidden state on the other; decoded names let two distinct paths collapse into one), refusing what an `ignore-submodules` setting hides. Both name the path; the recovery is `git update-index --no-assume-unchanged / --no-skip-worktree`, or clearing the diff config.
|
|
22
|
+
|
|
23
|
+
**Honest limits of the exec lane, v1:** **gate output is never accounted** — the wrapper's EXIT trap removes its trace, so no `gate-output` component is emitted and the metric counts the returned change set only; a change set carrying a **binary, non-regular or submodule** object is **REFUSED** at `return` and `fold`. For the first two the payload carries no content at all — a binary by name alone (`untracked-binary:<path>`, and a tracked binary's diff is the line "Binary files … differ"), a non-regular path by name alone. The **submodule** arm is *conservative* rather than a claim about every submodule: a clean staged gitlink replacement does carry exact OIDs in the payload, but once a submodule is **dirty** nothing further about its nested state is recorded (probed: a second, different nested edit and a whole new nested file both leave the digest unchanged), and no cheap probe separates the two at this door. A **symlink whose target is not valid UTF-8** is refused for a narrower reason on the same principle: the payload reads a target as a *string*, so those bytes are folded to `U+FFFD` before the digest is taken (probed: targets `0xff` and `0xfe` produce one payload and one enumeration), and a later change of target would move nothing. Reading that target fails closed on every errno but `ENOENT` — a deleted link has nothing to lose, its bytes riding the diff, while `EINVAL` means the object is **no longer a symlink at all**: the producer labels an object `symlink` when *any* layer carries mode `120000`, so a committed symlink replaced by a binary regular file is a `symlink` entry that the content-blind refusal never sees either, and the type change would fall between the two guards.
|
|
24
|
+
|
|
25
|
+
**Which ledger the tree-binding verbs use.** For `open`, `return` and `fold` the resolved store must be the **canonical** one, exactly — `<git common dir>/agent-workflow-delegation.jsonl` — and every other store refuses. Three failures collapse into that one equality: a store placed inside the work tree is carried by the payload *and enumerated as an object*, so the metric would count its own bookkeeping as delegated work while the append that follows `postTreeDigest` moves the tree every later fold binds; a store belonging to another repository would measure this tree against a foreign thread, the frozen record schema binding no worktree identity; and a **second** ledger in the same git dir would share artifact names with the first, since those are a function of `{backend, nonce}` alone — so a return could absorb the neighbouring ledger's receipt. One repository, one delegation ledger, which is what resolving to the git *common* dir already meant. `register`, `observe`, `await` and `aggregate` keep the unrestricted override — they bind no tree. Path comparison canonicalizes through symlinks and **fails closed** on any error but `ENOENT` (a path this kit cannot resolve is not one it may approve); the check is repeated immediately before every append, which *narrows* the replacement window rather than closing it — a check-then-write pair is not atomic, and this module defends against a buggy or interrupted producer, never a racing adversary. `open` additionally refuses **pre-spend** when either exec artifact name for its `{backend, nonce}` is already taken: one nonce, one artifact pair. **Stated residual:** two *linked worktrees* of one repository share a common dir by design (the ledger is worktree-shared), so this rule cannot separate them — opening in one linked worktree and returning or folding in another measures the wrong tree, is **forbidden** under the D10 one-writer bar, and is named here rather than mechanized, because the frozen record schema carries no worktree identity to bind it with. Their bytes can therefore move under a digest that cannot follow them, while the numerator counts a size re-read at return time; documenting that would leave a promise knowingly false, so the capability is **subtracted** until the frozen shared payload (which the review lane binds too) can carry their content — queued as its own item.
|
|
26
|
+
|
|
27
|
+
**What the fold's binding IS, and its stated residuals.** The binding is **equality of the visible unframed payload** between the return and the fold — never an identity of the tree, and the difference is not academic. The payload is an unframed concatenation, so **two different trees can produce the same bytes**: probed live, a single file containing the line `untracked:two.txt` yields the same fingerprint as a tree of two files, and a symlink's target can imitate the marker that opens the next untracked entry. It also carries **no git-relevant mode**, so `chmod 0644 → 0755` on an untracked file moves neither the fingerprint nor the enumeration — a blind attribute, not a fourth unsupported content class. Regular-file content and symlink targets are deliberately **not** subtracted: they are present in the payload, only ambiguously framed, and refusing them would refuse `new` and `symlink`, which is every delegated change set there is. The real fix — a framed payload carrying mode bits — is queued against that frozen surface, since it revalues every stored fingerprint in the family. A **receipt is forgeable**, exactly like every record in this family, so what the absorb door defends against is a buggy or interrupted producer, not a hostile one; the numerator **over-counts a rename** (a delegate's rename arrives as a deletion plus a creation, and no identity matching is done — that machinery was built and deliberately subtracted), so the metric reads optimistic, except in the rename-then-recreate case, which is indeterminate and named in the producer's own header; and **D10 is a bar, not a mechanism** — at most ONE in-tree exec dispatch runs at a time, and nothing here refuses a second one.
|
|
28
|
+
|
|
29
|
+
**Invariants:** writer — appends only to the delegation ledger, `<git common dir>/agent-workflow-delegation.jsonl` (`AW_DELEGATION_STORE` overrides, absolute only), through the store's own lock-serialized append; the store's preflight is the single legality door and this mode adds no second validator · every verb that DERIVES anything from the ledger (`return`, `fold`, `degrade`, `aggregate`) replays the store's preflight over the whole file first and refuses at the first record the append path would have refused, naming its physical line — a verb never computes over a "legal prefix" · **never commits**, never runs a subscription CLI, spawns nothing but git READS · the ledger is separate from the review receipts and the flow store, and a line of one family never satisfies a reader of another.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- opt-in-capability: gates-declaration -->
|
|
4
4
|
<!-- opt-in-capability: gates-verification -->
|
|
5
|
+
<!-- opt-in-capability: source-size -->
|
|
5
6
|
|
|
6
7
|
The **generic project gate runner** — it batches the project's OWN declared verification commands into one run. The runner itself **writes nothing on a plain run, never commits, and never runs a subscription CLI**; what it EXECUTES is the project's own declaration, with the caller's privileges (trust posture: a batching convenience over commands the project already runs by hand — **not a sandbox**). Two modes write state: **`--final`** runs the FULL declared matrix as the D3(a) final verification run and mints the receipt the commit guard consumes (step 4), and an ARMED **`--pre-review`** records its subset attempt in the flow store (step 3; unarmed repos byte-unchanged).
|
|
7
8
|
|
|
@@ -20,10 +21,12 @@ Declared gates can also be **auto-approved** (no permission prompt on a byte-exa
|
|
|
20
21
|
|
|
21
22
|
**Candidate line — the review-receipt gate (opt-in, never auto-seeded; AD-021).** Projects that configure a reviewed/council `plan-execution.review` recipe can declare the review-state check as one more gate — the exact candidate `{ id, title, cmd }` line and its contract live under `${CLAUDE_SKILL_DIR}/references/modes/review-state.md` (step 3).
|
|
22
23
|
|
|
23
|
-
**
|
|
24
|
+
**Candidate line — the source-size gate (opt-in, never auto-seeded).** A project declaring the source-size practice (`docs/ai/source-size.json`) can declare its checker as one more gate, offered **only over a MINTED practice** — an absent config has nothing to offer, an authored-but-unminted one is withheld by name rather than declaring a gate that would refuse. Adoption is ONE consented verb, `node ${CLAUDE_SKILL_DIR}/tools/source-size-check.mjs --adopt --reason "<text>" [--cwd <project>]`: it mints the record and declares this gate and NOTHING else, is idempotent once adopted, and with no config refuses with the exact file to author (the practice's one manual step). The advisor surfaces it as the `source-size` item.
|
|
24
25
|
|
|
25
|
-
**
|
|
26
|
+
**Consent-gated filling — the init preview, not part of the runner (D9).** The template `gates.json` is seeded EMPTY; FILLING it is a consented preview at init (`node ${CLAUDE_SKILL_DIR}/tools/gates-init.mjs --cwd <project>`, dry-run by default — prints the derived entries and **writes NOTHING**; `--apply [--only <id>]…` writes exactly the consented entries on your explicit yes; add-only, id collisions refused). **Placement, not blind appending:** a consented entry lands at the END, except that a non-checker entry goes **BEFORE** a trailing canonical coverage checker — that checker must stay last, so appending after it would write a declaration the validator refuses, leaving a hand edit as the only lane on any final-capable declaration. Existing entries are never modified, removed or reordered. The offer derivation is **closed-world** (AD-052): only a terminating-class script NAME (test / lint / type-check / build — never dev/watch/serve, never a write-mode or release/publish/deploy variant) whose BODY is a member of the literal runner allowlist is offered — membership, never blocklist screening: the worst case is a legit command not offered, never a dangerous one offered. The offered cmd is the uniform hook-free **`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <allowlisted-body>`** — `exec` runs a command, not a named script, so no pre/post hook can fire (npm/pnpm/yarn alike; never `<pm> run <name>`, which re-exposes hooks), and the Corepack env prefix blocks a hostile `packageManager` pin from fetching the PM binary before exec. npm is pinned `--offline --script-shell /bin/sh`; pnpm/yarn refuse an absent runner without network (a user-installed cache/global/PATH runner executing is user machine state — part of the disclosed residual); a family without a verified fail-closed exec contract is WITHHELD loudly. A screened-out body is always named — and when nothing but kit checkers remains, the preview says so in plain words: the offer carries **no project-verification gate at all**. **Disclose before the yes** (the preview prints it): gates.json is a PRIVILEGED file — the wired hook auto-approves byte-exact declared commands — and a script gate runs project-controlled tooling the preview does not sandbox (safe-by-construction = the OFFER DERIVATION). At upgrade the only gates.json writer is the consented legacy migration (`${CLAUDE_SKILL_DIR}/references/modes/upgrade.md`).
|
|
27
|
+
|
|
28
|
+
**The coverage PRODUCER and the canonical checker are declared together or not at all.** `coverage-check` READS an lcov; something must WRITE it, and a checker with no producer PASSES (`skipped-no-lcov`) certifying nothing. A **producer** is a CLOSED set of full command forms, never a substring probe: the suite body `node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination="$AW_GIT_DIR/agent-workflow-lcov.info" --test-reporter=spec --test-reporter-destination=stdout` (optionally + the project's own test paths), bare or behind ONE verified per-PM `exec` wrapper. A cmd that merely MENTIONS the destination (in an `echo`, as a bare substring) or carries a PARTIAL flag set is **not** one. The tail must be path-shaped — path/glob/quoting bytes only, nothing that could sequence, redirect or substitute a command, no plain leading `-`. **Residual:** the screen judges SOURCE bytes — brace *sequence* expansion can produce others (`{Y..a}`), though bash never re-scans an expansion result as syntax, so such a byte is literal argument data, not an operator; the leading-`-` rule is first-order only (`'--flag'`, `{path,--flag}` pass); neither proves the lcov SURVIVES — "producer" means *configured with the reporters*, and a run producing none is caught at runtime as `skipped-no-lcov`. The destination rides `AW_GIT_DIR`, exported to every gate child on plain and `--final` runs alike (`AW_LCOV_FILE` is `--final`-only), so one cmd survives the unmet-producer preflight in both modes. On BOTH declaration paths: the fill preview WIRES those reporters onto a `node --test` body — the one allowlist member producing lcov unaided, every other body is emitted unchanged — WITHHOLDS the `coverage-check` candidate, with a named note, when neither the offer nor the declaration carries a producer; and `--apply` REFUSES a checker with no producer, a checker that is not LAST (an ORDERING refusal about entries that are ALREADY declared — reorder by hand; the fill places new entries before a trailing checker but never reorders what it did not write), or a SECOND canonical checker. The legacy migration likewise never ADDS the checker into a producer-less declaration, and reports an already-declared one as INERT. A producer declared AFTER the checker leaves it just as inert (it reads nothing, or stale bytes) — ORDER is the rule. An already-declared inert pair, and a matrix of nothing but kit checkers, are surfaced by the advisor's `gates-inert` item (`${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`); its cause-A remedy is the fill preview when the checker is LAST and the project's own scripts yield an offerable producer (the placement rule lands it before the checker), and HAND-APPLY otherwise.
|
|
26
29
|
|
|
27
30
|
**`coverage=` states what the run can honestly say about coverage.** A CLOSED four-value set, always present, DETAIL only — exit code, `status=`, the receipt status, `--final` acceptance and the commit-guard disposition are untouched. `coverage=certified` — the checker consumed an lcov and ISSUED a verdict, pass **or** fail (one listing uncovered lines still reads certified and still exits 1). `coverage=not-run` — the checker ran and issued NO verdict (no lcov bytes were read, or the run holds no attestation context). `coverage=none` — no canonical checker ran here (an `--only` subset, the `--pre-review` subset). `coverage=unknown` — the run ended before the gates produced a signal (missing / empty / malformed declaration, no bash, a pre-spend refusal), or that signal is unreadable: the checker could not spawn, or its two anchored lines are missing, duplicated, or CONTRADICTORY (`attested=yes` over `lcov-sha256=none` certifies nothing) — fail closed. The value is DERIVED by cross-reading both anchored machine lines, the bytes the `--final` receipt binds, so it can never disagree with what the checker printed; the checker's table row names a withheld verdict in the same words, and a `--final` receipt RECORDS the token (`none` never rides one — a final run always selects the checker).
|
|
28
31
|
|
|
29
|
-
**Invariants:** the runner writes nothing on a plain run; `--final`'s ONE evidence write rides the core-evidence sole writer (the runner never opens the store itself) · an ARMED `--pre-review`'s ONE flow write rides the flow store's locked append factory (unarmed: byte-unchanged) · never commits · never runs a subscription CLI · executes only the project's OWN declared commands (never a kit-invented one) · the bash contract fails loud, never reinterprets · gates-init is a separate consent-per-run preview —
|
|
32
|
+
**Invariants:** the runner writes nothing on a plain run; `--final`'s ONE evidence write rides the core-evidence sole writer (the runner never opens the store itself) · an ARMED `--pre-review`'s ONE flow write rides the flow store's locked append factory (unarmed: byte-unchanged) · never commits · never runs a subscription CLI · executes only the project's OWN declared commands (never a kit-invented one) · the bash contract fails loud, never reinterprets · gates-init is a separate consent-per-run preview — add-only (it never modifies, removes or reorders an existing entry), never pre-approved by any velocity tier.
|
|
@@ -17,6 +17,8 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slo
|
|
|
17
17
|
|
|
18
18
|
**Flow armed-halves block (session-start read side).** When the config carries a `flow` block, the advisor also renders `Flow (schema 1) — armed halves (config · chain · bookkeeping):` — the **config half** (preset · councilRounds · kitMinVersion), the **chain half** (a light read-only probe of the flow store on the checker's fixed path: ARMED at an adoption record, UNARMED for an absent or unadopted store, fail-closed BROKEN wording for a malformed one), and the **bookkeeping half** (each declared path: declared non-excluded — the tracked-file floor verifies on the `set-flow` arming path — vs loudly DECLARED-EXCLUDED). A config with no `flow` block renders byte-identically to before and pays no store probe.
|
|
19
19
|
|
|
20
|
+
**Declared source-size practice block (upfront knowledge, D-17 U1).** When the project declares a source-size practice in `docs/ai/source-size.json`, the advisor also renders `Declared source-size practice (docs/ai/source-size.json) — known BEFORE the code is written:` — the caps, what is recorded, the canonical WHY sentence and the plan-time rung (every Step that creates a file names the file and its single responsibility, and the planned layout fits the caps). `--json` carries the SAME lines under **`declaredPractice`** (an empty array when nothing is declared), so the human and structured renders never diverge. The four config states each read as themselves: **ABSENT** renders nothing at all (a project declaring no practice is handed no invented limits), **AUTHORED** and **INCOMPLETE** are pre-mint (the second names the half-written machine key), **MINTED** carries the recorded counts. A declaration that cannot be read (malformed, unknown-keyed, a dangling symlink) renders ONE loud `UNREADABLE` line and the advisor **still exits 0** — the practice's own checker owns the exit code for its config (exit 2 there) and its declared gate reds the gate matrix. Read through the practice's pure read core, so this advisor never reaches its writer half.
|
|
21
|
+
|
|
20
22
|
**Cap-soft-skip degradation (the feature's only AUTO route).** The activity procedures are auto-discoverable only through the one-line **`workflow:methodology`** pointer (this kit + the engine carry `disable-model-invocation:true`, so NL like "write a plan" does **not** auto-load this skill). On a deployment whose methodology pointer was cap-soft-skipped — or whose pre-existing customized pointer lacks the procedures clause — the procedures are still reachable by **explicitly** invoking `/agent-workflow-kit procedures`; surface that plainly rather than treating it as a gap.
|
|
21
23
|
|
|
22
24
|
**Invariants:** read-only · never writes · never commits · never runs a subscription CLI · the deterministic resolution is the kit's, the recipe execution is the orchestrator's.
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- opt-in-capability: none — a read-only per-dispatch waiter; it guards no repeatable surface (the review obligations gate is review-state, already covered) -->
|
|
4
4
|
|
|
5
|
-
The **per-dispatch receipt-ARRIVAL deadline runner** (flow-orchestration #41/#50): it waits for ONE dispatched review to **answer**, never for the review obligations to be satisfied — satisfaction is receipt ARRIVAL past the watermark — a strictly-newer parseable receipt line from the dispatched backend (or its nonce-matched finding manifest, preferred when present) — never obligation satisfaction. For "block until `--check` would PASS" use `review-state --await` instead; this runner answers the narrower per-dispatch question "did THIS dispatch come back at all?", which is what the round dispatch ledger's deadline discipline needs.
|
|
5
|
+
The **per-dispatch receipt-ARRIVAL deadline runner** (flow-orchestration #41/#50): it waits for ONE dispatched review to **answer**, never for the review obligations to be satisfied — satisfaction is receipt ARRIVAL past the watermark — a strictly-newer parseable REVIEW receipt line from the dispatched backend (or its nonce-matched finding manifest, preferred when present), never a delegation-ledger line that merely names the same backend — never obligation satisfaction. For "block until `--check` would PASS" use `review-state --await` instead; this runner answers the narrower per-dispatch question "did THIS dispatch come back at all?", which is what the round dispatch ledger's deadline discipline needs.
|
|
6
6
|
|
|
7
7
|
Run **`node ${CLAUDE_SKILL_DIR}/tools/receipt-deadline.mjs --backend <name> --watermark <bytes> [--nonce <nonce>] [--timeout <s>]`**:
|
|
8
8
|
|
|
9
9
|
1. **`--watermark`** is the receipts-file **byte length minted BEFORE the dispatch** (the round dispatch-ledger `receiptWatermark`). The runner additionally binds the file **prefix below that offset IN-PROCESS at start**: a shrunken file or a rewritten prefix refuses **loudly for the lifetime of the run** — a truncate-and-rewrite can never masquerade as arrival — and the watermark must sit **on a line boundary** (a positive offset whose preceding byte is not a newline refuses loudly at start: the pre-dispatch tail was unterminated, so an appended receipt would physically continue that malformed line). Honest limit: the prefix binding is a **runtime guard, never a persisted proof** (the persisted ledger watermark stays the plain integer).
|
|
10
|
-
2. **Arrival** = a newline-terminated, parseable receipt line **from that backend** starting at/after the watermark offset. A malformed line never satisfies (and never masks a later valid one); a foreign backend's line never satisfies; a partial (unterminated) append is not a receipt yet.
|
|
11
|
-
3. **`--nonce`** (the dispatch nonce under the safe grammar `[A-Za-z0-9._-]{1,64}`): when the `{backend, nonce}`-named finding manifest exists beside the receipts file, the runner **prefers that correlation** — the manifest is minted atomically BEFORE the receipt append, and it carries the dispatch identity, so it can never be another dispatch's receipt. A malformed or foreign-identity manifest refuses loudly.
|
|
10
|
+
2. **Arrival** = a newline-terminated, parseable **review receipt** line **from that backend** starting at/after the watermark offset. A malformed line never satisfies (and never masks a later valid one); a foreign backend's line never satisfies; a partial (unterminated) append is not a receipt yet. A line of the **delegation family** (the exec ledger — it carries a `backend` field too) is skipped on the same terms: a review waiter waits for a REVIEW answer, and an exec return is not one. An **exec receipt** placed in the receipts store is skipped on exactly the same terms: despite carrying both `backend` and `nonce`, it fails the review-receipt minimal core, so it never satisfies either a nonce-less or nonce-bound review wait. The two families are mutually inert **by construction**: this review waiter admits only a review receipt, while the exec waiter, `dispatch await`, states the mirror rule from its own side.
|
|
11
|
+
3. **`--nonce`** (the dispatch nonce under the safe grammar `[A-Za-z0-9._-]{1,64}`): when the `{backend, nonce}`-named finding manifest exists beside the receipts file, the runner **prefers that correlation** — the manifest is minted atomically BEFORE the receipt append, and it carries the dispatch identity, so it can never be another dispatch's receipt. A malformed or foreign-identity manifest refuses loudly. An exec receipt standing where the `{backend, nonce}`-named finding manifest belongs is **refused as malformed**, never read as arrival: the manifest grammar is the door, and a sibling family's artifact does not pass it.
|
|
12
12
|
4. **Timeout** (default 900s) fires ONLY when no receipt landed, and its wording **names the watermark**. An authoritative NEGATIVE verdict is not this tool's business — arrival is arrival, whatever the verdict says; the obligations verdict lives in `review-state`.
|
|
13
13
|
|
|
14
14
|
**Exit codes:** `0` arrived; `1` timeout or a loud refusal (shrunken/rewritten store, malformed manifest, no git tree); `2` usage (including an unsafe nonce).
|
|
@@ -27,7 +27,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
|
|
|
27
27
|
- `read-lane` — enabling the opt-in read-only compound lane auto-approves *compounds* (and singles) of the seeded read-only core that carry ZERO shell metaprogramming: an UNATTENDED trust extension, bounded by the audited read-only core (never a command outside it; prompt-bypass only, never a sandbox bypass) and applied regardless of which of those core commands you seeded as individual settings rules. It is a PROJECT-PERSISTENT declaration in `docs/ai/lanes.json` — every future session, subagents' Bash too where the host fires hooks on subagent Bash, and (committed) every checkout. The apply depends on state: when the lane is OFF, it is the `gate-hook --read-lane` preview (whose own currency check refuses a stale hook — a pre-1.48 hook never reads `lanes.json`); when the placed hook is STALE (an enabled lane over an old hook) or MISSING, the item instead surfaces a **delete-to-reseed** / re-place recovery (a destructive `rm` + `--apply`, an attention item — never the safe preview). Risk profile: a bounded read-only trust-posture extension — no write/exec exposure beyond the audited core.
|
|
28
28
|
- `worktrees-dir` — on a settings-native host that honors the key, the HAND-APPLY line widens the OS-sandbox WRITE surface to the whole worktrees parent dir: every sibling path under it (other repositories included) becomes agent-writable, and the widening persists for every later session. A harness-managed host may ignore that project setting; grant the narrow parent through host/session controls or use the provision terminal fallback instead. When that scope is wider than you want, narrow it FIRST: create a dedicated dir yourself (outside the agent's write surface), point `docs/ai/worktrees.json` `parentDir` at it, then re-run recommendations — the item re-renders with the narrowed dir. The kit never writes sandbox filesystem allowances itself; the line is always yours to paste. **Convergence** is two-path: a declared `sandbox.filesystem.allowWrite` entry covering the probed dir (either settings scope — `~` and `~/…` resolve against home, and coverage is path-segment-aware, so a grant on a sibling or on a child never counts), or — for a host that ignores that key — the neutral dir-bound acknowledgement recorded by this item's consent-gated apply one-liner (`ack-write --lane worktrees-dir` — a dry-run preview that prints the exact `--apply`; recorded as `worktreesDirAck` in the family-owned `docs/ai/acks.json`, never a security key), while the grant advice itself rides the `recipe:` line as the labeled FIRST step; against a trusted host NO the apply stays the HAND-APPLY grant advice and no ack is offered. Neither is proof of write CAPABILITY: the provision preflight's real create+delete probe stays the runtime truth, and the fingerprint is bound to the **resolved probe dir**, so the item re-fires only when that resolved dir changes (two absent `parentDir` values sharing an existing ancestor resolve to the same dir and keep the same ack). Risk profile: a real write-surface widening where honored — scope it deliberately.
|
|
29
29
|
|
|
30
|
-
- `gates-inert` — the gate matrix is DECLARED but verifies nothing, and the two causes have different remedies, so the item renders a different apply for each. **Cause A** — a canonical `coverage-check` gate with no producer gate declared BEFORE it (none at all, or one declared after it, which writes the lcov too late): the run certifies no coverage of its own — `coverage=not-run` when nothing wrote an lcov, or a verdict over STALE bytes an earlier run left in the git dir, which is worse because it reads as `coverage=certified`. This item is what surfaces that state at upgrade instead of leaving it to be noticed. The remedy needs the producer to run BEFORE the checker — declared or MOVED there — and the `gates-init`
|
|
30
|
+
- `gates-inert` — the gate matrix is DECLARED but verifies nothing, and the two causes have different remedies, so the item renders a different apply for each. **Cause A** — a canonical `coverage-check` gate with no producer gate declared BEFORE it (none at all, or one declared after it, which writes the lcov too late): the run certifies no coverage of its own — `coverage=not-run` when nothing wrote an lcov, or a verdict over STALE bytes an earlier run left in the git dir, which is worse because it reads as `coverage=certified`. This item is what surfaces that state at upgrade instead of leaving it to be noticed. The remedy needs the producer to run BEFORE the checker — declared or MOVED there — and what the fill can do about that is exactly what the apply says. When the checker is the LAST declared gate and the project's own scripts yield an offerable producer, the fill PLACES that producer before it, so the apply is the ordinary `gates-init` dry-run preview. Otherwise — no offerable producer, or a checker that is not last — this arm stays **HAND-APPLY**: the maintainer edits `docs/ai/gates.json` (the exact producer form lives in `${CLAUDE_SKILL_DIR}/references/modes/gates.md`), never you, never the kit, because the fill never reorders entries it did not write. **Cause B** — every declared gate is one of the kit's own canonical checkers, so the matrix runs no project-verification command at all: the apply is the `gates-init` dry-run preview, the same consent-gated seeder the `gates-declaration` item renders, and after the SAME confirmation you run the `--apply` line it prints. Neither cause changes any gate result, exit code or receipt — the item adds no enforcement, only the offer. Risk profile: `docs/ai/gates.json` is a PRIVILEGED file (a declared gate is auto-approvable by the wired hook), so both causes land in the same place — where the fill can help, it declares a project-controlled command on your explicit yes; where it cannot, the edit stays maintainer-only.
|
|
31
31
|
|
|
32
32
|
- `adr-store-migration` — other items write project files too; what is unique here is that the crossing **overwrites and deletes files the project already has**: it replaces the deployed enforcement scripts in `scripts/` (the directional subset — only basenames the project already has; a locally-edited copy is snapshotted first, never silently clobbered) and, where a retired archive file exists, DELETES it once conservation has been proven. That is why it is **HAND-APPLY** and why the command shown in the apply slot is a **`--dry-run`** — it writes nothing and prints the whole plan. `--apply` is a SEPARATE step, run only after that plan has been shown and **fresh consent** obtained for it; the consent flow executes only the apply slot, so an item that needs consent AFTER its preview cannot use that lane at all. Every write is idempotent and the run is re-runnable to completion after any interruption, so a re-run repairs rather than double-applies. It never commits. Risk profile: overwrite + delete of existing project files, gated on a preview you have actually read.
|
|
33
33
|
|
|
@@ -36,6 +36,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs [--dry-run | --apply] [
|
|
|
36
36
|
- `node ${CLAUDE_SKILL_DIR}/tools/repo-search.mjs --pattern <literal> --path <p>` (wildcard — the LITERAL search lane; for a pattern carrying a shell-significant byte use `--pattern-file <p>` instead, and for a TARGET PATH carrying one use `--paths-file <p>` — either way the bytes never enter the command string. one of the TWO tier tools that carry hook residual coverage — see the honesty note below)
|
|
37
37
|
- `node ${CLAUDE_SKILL_DIR}/tools/path-inventory.mjs --path <p>` (wildcard — the read-only inventory lane: exists / type / size / `wc -l` lines / one-level listing / small-file contents for N paths in ONE call, so small path questions never need a composed shell. `--paths-file <p>` is its out-of-band target lane; hook residual coverage as with repo-search)
|
|
38
38
|
- `node ${CLAUDE_SKILL_DIR}/tools/run-gates.mjs --cwd ${PROJECT_ROOT}` — **EXACT byte-string only**, and honestly **project-exec, not read-only**: it runs YOUR declared `docs/ai/gates.json` commands — the same trust boundary the opt-in hook grants byte-exact per-cmd. A wildcard would be BROADER than that boundary (`--cwd <dir>` executes another project's declared gates), so the bare cwd-defaulting form, any other `--cwd`, `--only`, and **`--final`** forms all still prompt (`--final` WRITES the final-run receipt into the core-evidence store — a recording run is never auto-approved).
|
|
39
|
+
- `node ${CLAUDE_SKILL_DIR}/tools/source-size-check.mjs --check` — **EXACT byte-string only**, and only this READ-ONLY mode: the tool's `--write-baseline` and `--adopt` modes WRITE (the recorded ratchet, and a gate declaration) and are deliberately uncovered — they still prompt, always. Every other form, `--cwd` included, keeps prompting. **What it covers, precisely:** the agent's own direct invocation. It is **not** the byte-string a DECLARED gate carries — the fill emits that path double-quoted (a kit path with a space must survive), and a seedable allow rule may carry no quotes at all, so the two spellings cannot be one string; a declared gate is auto-approved by the opt-in hook instead, byte-exact against `docs/ai/gates.json`.
|
|
39
40
|
- Writer previews, **exact arg-free dry-run byte-strings only** (the SEEDED tier byte-string is the arg-free preview of each): `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/gate-hook.mjs` — every `--apply`/`--write`/`--yes` still prompts, always. (`gate-hook` also has a **`--read-lane`** flagged preview — the opt-in read-only compound lane, `${CLAUDE_SKILL_DIR}/references/modes/hook.md`; that flagged form is NOT the seeded arg-free byte-string, so it may **prompt once** — it IS a consent flow, stated, no silent cap.)
|
|
40
41
|
|
|
41
42
|
Honesty notes: tier entries get **NO PreToolUse-hook residual coverage — with TWO deliberate exceptions, `repo-search.mjs` and `path-inventory.mjs`**, whose prefixes are in the hook's scanned list because they take caller-supplied argument bytes: a redirection or substitution on THEIR invocation raises the ask, whose reason names that tool's out-of-band lane (`--pattern-file` / `--paths-file`) — context for the HUMAN deciding, not a message to the caller. Neither is in the seeded core, so neither inherits the read-lane allow. Every other tier entry: the opt-in hook's residual ask-net guards only the seeded read-only CORE prefixes, so the tier rides the same settings-level residual posture as the core (redirection / command substitution are not inspectable at the settings layer; see the residual notice). A skill or project path that cannot survive UNQUOTED in a byte-exact rule (spaces, metacharacters, non-POSIX) **STOPs the tier up front with a clear error** — nothing is seeded. Anything you want covered beyond the tier — such paths, this repo's own relative-path spellings, other tools — stays a **BY-HAND add** to your settings, with the path your project actually reaches the kit by. Pre-existing `node …` allow entries that do NOT match the seeded tier byte-forms stay flagged by the advisory for hand review.
|
package/tools/commands.mjs
CHANGED
|
@@ -259,6 +259,13 @@ const CATALOG = [
|
|
|
259
259
|
kind: READ_ONLY,
|
|
260
260
|
oneLine: 'Check that the documented contract tokens still match the live code constants they describe — a read-only lint that fails closed on drift; --check turns it into a gate exit code.',
|
|
261
261
|
},
|
|
262
|
+
{
|
|
263
|
+
key: 'dispatch',
|
|
264
|
+
invocation: invocationOf('dispatch'),
|
|
265
|
+
group: 'Orchestrate',
|
|
266
|
+
kind: WRITER,
|
|
267
|
+
oneLine: 'Measure delegation: check a sub-task brief’s contract block (form only — never whether the task is genuinely bounded), pre-register an acceptance wave with its thresholds, record one observation, open a delegated thread from that brief, wait for that one dispatch to answer — a wait that ends without an answer says so and authorizes nothing — absorb the wrapper’s receipt back into the ledger, fold the returned work or close the thread with a recorded degrade, and print the per-class report of how much a delegated sub-task actually bought, derived from what was dispatched, returned and folded. Writes only its own ledger file beside the repo; never commits.',
|
|
268
|
+
},
|
|
262
269
|
{
|
|
263
270
|
// NEVER `guarded` — that kind promises dry-run-first, which these writers do not have; the
|
|
264
271
|
// honest strongest caution is `writer` with the destructive arm named in the line itself.
|
package/tools/core-evidence.mjs
CHANGED
|
@@ -56,7 +56,9 @@ const GIT_MAX_BUFFER = 256 * 1024 * 1024; // a full-tree diff / TAP stream can b
|
|
|
56
56
|
|
|
57
57
|
const gitRaw = (args, cwd) => spawnSync('git', args, { cwd, maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// Exported so a consumer computing over the SAME domain (the exec metric producer) reads git through
|
|
60
|
+
// this one runner instead of growing a second spawnSync wrapper with its own buffer bound.
|
|
61
|
+
export const gitBuf = (args, cwd) => {
|
|
60
62
|
const r = gitRaw(args, cwd);
|
|
61
63
|
if (r.error || r.status !== 0) return null;
|
|
62
64
|
return r.stdout;
|
|
@@ -1012,6 +1014,26 @@ export const runDegrade = ({ cwd = process.cwd(), env = process.env, backend, re
|
|
|
1012
1014
|
|
|
1013
1015
|
// ── the summary verb (D6): ONE stateless render — receipts + evidence store, no ledger ────────────
|
|
1014
1016
|
|
|
1017
|
+
// A red-proof binds the BYTES of the test file it was observed on, and the final-run checker refuses
|
|
1018
|
+
// when those bytes have moved (coverage-check.mjs, the hash-mismatch arm). This render used to print a
|
|
1019
|
+
// stale record byte-identically to a live one, so the only place staleness surfaced was that gate —
|
|
1020
|
+
// after both council dispatches, at the commit boundary, which is the most expensive discovery point
|
|
1021
|
+
// in the loop. The currency is therefore computed HERE, from the SAME two helpers the checker uses, so
|
|
1022
|
+
// the render and the gate can never disagree about what stale means.
|
|
1023
|
+
//
|
|
1024
|
+
// An unresolvable or unreadable file is STALE, not unknown: a proof nobody can re-check is not a proof
|
|
1025
|
+
// anyone should read as current.
|
|
1026
|
+
export const redProofCurrency = (rootTop, record) => {
|
|
1027
|
+
if (rootTop == null) return { state: 'stale', detail: 'the work-tree root is not resolvable' };
|
|
1028
|
+
const resolved = resolveTestFile(rootTop, record.file);
|
|
1029
|
+
if (!resolved.ok) return { state: 'stale', detail: resolved.reason };
|
|
1030
|
+
const current = hashFileBytes(resolved.abs);
|
|
1031
|
+
if (current == null) return { state: 'stale', detail: `cannot read "${record.file}"` };
|
|
1032
|
+
return current === record.fileHash
|
|
1033
|
+
? { state: 'current' }
|
|
1034
|
+
: { state: 'stale', detail: 'the bound test file changed after the mint' };
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1015
1037
|
export const buildSummaryState = ({ cwd = process.cwd(), env = process.env } = {}) => {
|
|
1016
1038
|
const fingerprint = computeTreeFingerprint(cwd);
|
|
1017
1039
|
const base = resolveBase(cwd);
|
|
@@ -1026,7 +1048,10 @@ export const buildSummaryState = ({ cwd = process.cwd(), env = process.env } = {
|
|
|
1026
1048
|
backend: b,
|
|
1027
1049
|
summary: summarizeReviewReceiptsForTree(receipts.filter((r) => r.backend === b), fingerprint),
|
|
1028
1050
|
}));
|
|
1029
|
-
const
|
|
1051
|
+
const rootTop = gitLine(['rev-parse', '--show-toplevel'], cwd);
|
|
1052
|
+
const redProofs = authoritativeOfKind(store.records, 'red-proof')
|
|
1053
|
+
.filter((r) => r.base === base)
|
|
1054
|
+
.map((r) => ({ ...r, currency: redProofCurrency(rootTop, r) }));
|
|
1030
1055
|
const degrades = authoritativeOfKind(store.records, 'degrade').filter((r) => r.fingerprint === fingerprint);
|
|
1031
1056
|
const finalRun = authoritativeOfKind(store.records, 'final').find((r) => r.fingerprintBefore === fingerprint) ?? null;
|
|
1032
1057
|
// A malformed/unreadable store makes the AUTHORITATIVE selection untrustworthy (a dropped later
|
|
@@ -1076,7 +1101,16 @@ export const renderSummary = (s) => {
|
|
|
1076
1101
|
? [` evidence sections WITHHELD — the store is unavailable (${s.storeMalformed} malformed line(s)${s.storeReadError ? `, read error: ${s.storeReadError}` : ''}); a dropped line could resurrect a superseded record — inspect ${s.storePath}`]
|
|
1077
1102
|
: [
|
|
1078
1103
|
` red-proof records (current base): ${s.redProofs.length ? '' : '(none)'}`,
|
|
1079
|
-
...s.redProofs.map((r) =>
|
|
1104
|
+
...s.redProofs.map((r) => {
|
|
1105
|
+
const stale = r.currency?.state === 'stale';
|
|
1106
|
+
const mark = stale ? `STALE (${r.currency.detail})` : 'CURRENT';
|
|
1107
|
+
return ` ${r.testId} — ${mark} · ${r.reds}/${r.runs} red, hash ${short(r.fileHash)}, pre-fix fingerprint ${short(r.fingerprint)}`;
|
|
1108
|
+
}),
|
|
1109
|
+
// The recovery, printed once and only when it is needed — it was written down nowhere, and
|
|
1110
|
+
// the loop paid for that three times.
|
|
1111
|
+
...(s.redProofs.some((r) => r.currency?.state === 'stale')
|
|
1112
|
+
? [' ↳ a STALE proof is refused by run-gates --final: park the fix so the bound test fails again, re-observe it with `core-evidence red-proof "<testId>"`, then restore the fix. Edit test files FIRST and re-observe ONCE per park — every proof bound to an edited file goes stale together.']
|
|
1113
|
+
: []),
|
|
1080
1114
|
` degrade records (current tree): ${s.degrades.length ? '' : '(none)'}`,
|
|
1081
1115
|
...s.degrades.map((d) => ` ${d.backend} — ${d.reason} (${d.timestamp})`),
|
|
1082
1116
|
];
|
|
@@ -67,14 +67,15 @@ const RAW_BACKENDS = [
|
|
|
67
67
|
roleContracts: {
|
|
68
68
|
execute: {
|
|
69
69
|
invocations: [
|
|
70
|
-
'codex-exec <plan-file|->',
|
|
71
|
-
'codex-exec <plan-file|-> -- <extra codex flags...>',
|
|
70
|
+
'codex-exec [--nonce <n>] <plan-file|->',
|
|
71
|
+
'codex-exec [--nonce <n>] <plan-file|-> -- <extra codex flags...>',
|
|
72
72
|
],
|
|
73
73
|
grounding: "automatic — the root AGENTS.md (Hard Constraints) is auto-merged into codex's context and the wrapper prepends the orchestrator execution contract; no grounding flags",
|
|
74
74
|
continue: [
|
|
75
|
-
'codex-exec --resume-last <plan-file|->',
|
|
76
|
-
'codex-exec --resume <session-id> <plan-file|->',
|
|
75
|
+
'codex-exec --resume-last [--nonce <n>] <plan-file|->',
|
|
76
|
+
'codex-exec --resume <session-id> [--nonce <n>] <plan-file|->',
|
|
77
77
|
],
|
|
78
|
+
receipt: "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, no node.",
|
|
78
79
|
passthrough: {
|
|
79
80
|
policy: 'guarded',
|
|
80
81
|
blocked: ['-c*', '--config*', '-s*', '--sandbox*', '--dangerously-bypass-approvals-and-sandbox', '--dangerously-bypass-hook-trust', '--full-auto', '--oss', '--local-provider*', '-p*', '--profile*', '-m*', '--model*', '-o*', '--output-last-message*', '--json*', '--color*', '--output-schema*', '--ephemeral*'],
|
|
@@ -324,7 +324,11 @@ const FIELD_CHECKS = {
|
|
|
324
324
|
firstPassNum: { ok: isByteCount, want: 'a non-negative integer first-pass numerator' },
|
|
325
325
|
firstPassDen: { ok: (v) => Number.isSafeInteger(v) && v >= 1, want: 'a positive integer first-pass denominator' },
|
|
326
326
|
vehicle: { ok: isPlainObject, want: 'the closed vehicle pair {requested, selected}' },
|
|
327
|
-
|
|
327
|
+
// The safe token grammar, not merely non-empty: the backend NAMES an artifact beside the store
|
|
328
|
+
// (the exec receipt and its report), so a name outside this grammar records a dispatch whose own
|
|
329
|
+
// receipt could never be written. One grammar on both sides, or the ledger accepts what the
|
|
330
|
+
// wrapper cannot mint.
|
|
331
|
+
backend: { ok: isSafeName, want: 'a backend name in the safe token grammar ([A-Za-z0-9._-]{1,64})' },
|
|
328
332
|
contractDigest: { ok: isHex64, want: 'the 64-hex digest of the canonical contract header' },
|
|
329
333
|
preTreeDigest: { ok: isHex64, want: 'the 64-hex uncommitted-state fingerprint at dispatch' },
|
|
330
334
|
postTreeDigest: { ok: isHex64, want: 'the 64-hex uncommitted-state fingerprint after the run' },
|
|
@@ -427,8 +431,11 @@ const validateReturnCrossFields = (r) => {
|
|
|
427
431
|
if (r.metric.eligible) {
|
|
428
432
|
return refuse(`return: this return's own fields make the metric INELIGIBLE (${local.ineligibleReason}) — an eligible metric is never claimed over them`);
|
|
429
433
|
}
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
// The locally PROVABLE name wins outright: "dirty-baseline" is admissible only where the return's
|
|
435
|
+
// own fields leave the metric eligible, so it can never stand in for a reason a reader could
|
|
436
|
+
// have checked. (The store then binds that ONE override to the dispatch's recorded baseline.)
|
|
437
|
+
if (r.metric.ineligibleReason !== local.ineligibleReason) {
|
|
438
|
+
return refuse(`return: metric.ineligibleReason "${r.metric.ineligibleReason}" contradicts this return's own fields (locally "${local.ineligibleReason}") — the provable reason is the one recorded, and "dirty-baseline" never replaces it`);
|
|
432
439
|
}
|
|
433
440
|
} else if (!r.metric.eligible && r.metric.ineligibleReason !== 'dirty-baseline') {
|
|
434
441
|
return refuse(`return: metric.ineligibleReason "${r.metric.ineligibleReason}" is not substantiated by this return's own fields — when the local evaluation finds the metric eligible, only "dirty-baseline" (the store-verified override) may be recorded`);
|