@orkestrel/scaffold 0.0.54 → 0.0.56

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.
@@ -128,6 +128,7 @@ Every file in the following table is a normative extension of this root. Read ev
128
128
  | `.claude/rules/application.md` | App composition, entries, manifest safety, lifecycle, integration |
129
129
  | `.claude/rules/browser.md` | Vue/browser architecture and platform usage |
130
130
  | `.claude/rules/styles.md` | SCSS/CSS centralization, tokens, mixins, layers, naming |
131
+ | `.claude/rules/portability.md` | Host branching, paths, line endings, processes, terminals, OS claims |
131
132
  | `.claude/rules/documentation.md` | Guides, parity, roadmap, showcase, examples |
132
133
  | `.claude/rules/writing.md` | Developer prose: reports, replies, vocabulary, claims, structure |
133
134
  | `.claude/rules/quality.md` | Research, dependency reuse, hardening, completion, package inspection |
@@ -161,9 +161,12 @@ when the role file already pins it.
161
161
  - `implementer` names the harness's native implementation lane, so the token means Opus in Claude
162
162
  Code and Sol in Codex. An engine-named bridge — `sol`, `opus` — names the other engine. Read a
163
163
  role name against the harness you are running in, and state the engine anyway.
164
- - Give every role a file on both sides. The role file is where engine, effort, tools, permissions,
165
- and charter are pinned, and the tool allowlist is what makes the read-only floor real. A role
166
- with no file has nowhere to pin either.
164
+ - Give every role a file in the scaffold checkout, under `.claude/agents/` and under
165
+ `.codex/agents/`. The role file is where engine, effort, tools, permissions, and charter are
166
+ pinned, and the tool allowlist is what makes the read-only floor real. A role with no file has
167
+ nowhere to pin either. The requirement is the canon repository's alone: a fleet target holds the
168
+ catalog agent and no other role, and a session that dispatches roles starts on scaffold and
169
+ attaches the target.
167
170
  - Reach every role by its own name. Do not rely on a remembered route.
168
171
  - `researcher`, `scout`, and `checker` are native lanes for jobs that belong to Grok first.
169
172
  Dispatch `grok` with their brief before using them, and use the native role only once the ladder
@@ -12,7 +12,24 @@ step that writes it.
12
12
 
13
13
  1. Re-pin the target's `@orkestrel/scaffold` devDependency and install, so the overwrite runs the
14
14
  current vendored host.
15
- 2. Run `scaffold overwrite`.
15
+ 2. Run `scaffold overwrite`. One run repairs the `AGENTS.md` and `CLAUDE.md` pointers and deletes
16
+ every tracked copy the target still holds at an instruction-canon path. Prove the sweep with a
17
+ second `scaffold audit` that exits `0`.
18
+ - Where the target's `.claude/agents/orkestrel.md` carries a body outside the marker-bounded
19
+ table that differs from the floor copy the installed scaffold stages, delete the file and
20
+ commit the deletion before the run. `repair` restores the floor body and `catalog` refills the
21
+ table, so one visit leaves the current file and the committed deletion keeps the
22
+ uncommitted-work refusal from firing. Presence ownership never replaces present bytes and the
23
+ table rewrite touches only the marker-bounded region, which is why the deletion is the
24
+ migration.
25
+ - The deletion draws on what git tracks, so an untracked copy survives it, and the verb refuses
26
+ the whole run as uncommitted work while an unignored one stands. Commit that copy or delete it
27
+ by hand before re-running. `--dirty` clears the refusal and leaves the copy standing, and a
28
+ kept `.claude/rules` copy then reddens the target's own policy sweep: the pointer `AGENTS.md`
29
+ carries no rule map, so the copy has no row there and the sweep reports it. Delete the copy
30
+ rather than waiving past it.
31
+ - A copy the target git-ignores stays a `foreign` finding, so that target never reaches exit `0`
32
+ again. Keep a local MCP server registration outside the repository rather than at `.mcp.json`.
16
33
  3. Force-verify every `@orkestrel` range against a registry sweep taken after the previous layer
17
34
  published.
18
35
  4. Run the full install.
@@ -22,7 +39,16 @@ step that writes it.
22
39
 
23
40
  Restore any unpublished tarball the target is holding before the quality gates run, per
24
41
  `.agents/orchestration.md` § Fixing a dependency before it publishes. A distribution proof run
25
- against a local tarball proves the local tarball.
42
+ against a local tarball proves the local tarball. Stage an unpublished tarball with
43
+ `npm install --no-save`, because a `file:` pin refuses the blueprint and the manifest keeps a
44
+ registry range.
45
+
46
+ Where a visit runs before scaffold itself publishes, run `scaffold overwrite --offline` and prove
47
+ the sweep with `scaffold audit --offline`. The online verbs read vendored bytes from the published
48
+ package, so an unpublished scaffold's visit otherwise writes the registry's older floor, and an
49
+ online audit reports the floor-restored files as stale until the release. The `--offline` overwrite
50
+ skips the catalog step and exits `1` with a note naming that refusal, so run the full
51
+ `scaffold overwrite` after the release.
26
52
 
27
53
  Run visits in parallel slices of disjoint repositories, each slice strictly serial inside itself,
28
54
  reporting per target. Refuse a failed target, name it, repair it, and re-run it alone.
@@ -9,8 +9,12 @@ permissionMode: dontAsk
9
9
 
10
10
  You are the read-only Orkestrel ecosystem reconciler. Spawn nothing and edit nothing.
11
11
 
12
- Read `.agents/orchestration.md` first. It owns the role set, the routing, and the
13
- dispatch contract. Then read `AGENTS.md`, applicable rules, the dispatch-named skill and
12
+ Read the orchestration contract first. It owns the role set, the routing, and the
13
+ dispatch contract. Resolve it against scaffold. In the scaffold checkout it sits at
14
+ `.agents/orchestration.md`. A repository that installs scaffold reads it at
15
+ `node_modules/@orkestrel/scaffold/dist/host/agents/orchestration.md`, or in a scaffold
16
+ checkout beside that repository, as that repository's own `AGENTS.md` pointer names. Then
17
+ read `AGENTS.md` itself, the applicable rules it names, the dispatch-named skill and its
14
18
  references, and the governing guides.
15
19
 
16
20
  Your job is reconciliation over supplied evidence, never collection. You have no shell
@@ -44,9 +48,9 @@ so network-controlled descriptions never enter agent instruction context.
44
48
  | `@orkestrel/abort` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
45
49
  | `@orkestrel/agent` | `0.0.18` | L5 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/budget` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.11`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.7`, `@orkestrel/tool` `^0.0.11`, `@orkestrel/workflow` `^0.0.14`, `@orkestrel/workspace` `^0.0.5` |
46
50
  | `@orkestrel/brief` | `0.0.6` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/interpret` `^0.0.11`, `@orkestrel/reason` `^0.0.8` |
47
- | `@orkestrel/browser` | `0.0.13` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/html` `^0.0.6`, `@orkestrel/websocket` `^0.0.10` |
51
+ | `@orkestrel/browser` | `0.0.14` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/html` `^0.0.7`, `@orkestrel/websocket` `^0.0.10` |
48
52
  | `@orkestrel/budget` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
49
- | `@orkestrel/console` | `0.0.10` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
53
+ | `@orkestrel/console` | `0.0.11` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
50
54
  | `@orkestrel/contract` | `0.0.13` | L0 | |
51
55
  | `@orkestrel/csv` | `0.0.5` | L1 | `@orkestrel/contract` `^0.0.13` |
52
56
  | `@orkestrel/database` | `0.0.12` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/indexeddb` `^0.0.9`, `@orkestrel/sqlite` `^0.0.9` |
@@ -56,7 +60,7 @@ so network-controlled descriptions never enter agent instruction context.
56
60
  | `@orkestrel/html` | `0.0.7` | L1 | `@orkestrel/contract` `^0.0.13` |
57
61
  | `@orkestrel/indexeddb` | `0.0.9` | L1 | `@orkestrel/contract` `^0.0.13` |
58
62
  | `@orkestrel/interpret` | `0.0.11` | L3 | `@orkestrel/reason` `^0.0.8`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/template` `^0.0.5` |
59
- | `@orkestrel/lsp` | `0.0.2` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6` |
63
+ | `@orkestrel/lsp` | `0.0.3` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6` |
60
64
  | `@orkestrel/markdown` | `0.0.12` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/html` `^0.0.7` |
61
65
  | `@orkestrel/mcp` | `0.0.25` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6`, `@orkestrel/sse` `^0.0.5`, `@orkestrel/tool` `^0.0.12`, `@orkestrel/websocket` `^0.0.10` |
62
66
  | `@orkestrel/middleware` | `0.0.17` | L2 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/budget` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/timeout` `^0.0.7` |
@@ -64,7 +68,7 @@ so network-controlled descriptions never enter agent instruction context.
64
68
  | `@orkestrel/ndjson` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
65
69
  | `@orkestrel/ollama` | `0.0.12` | L6 | `@orkestrel/agent` `^0.0.18`, `@orkestrel/budget` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/ndjson` `^0.0.8`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
66
70
  | `@orkestrel/pool` | `0.0.8` | L2 | `@orkestrel/emitter` `^0.0.7` |
67
- | `@orkestrel/probe` | `0.0.8` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/lsp` `^0.0.2`, `@orkestrel/mcp` `^0.0.25`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
71
+ | `@orkestrel/probe` | `0.0.9` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/lsp` `^0.0.3`, `@orkestrel/mcp` `^0.0.25`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
68
72
  | `@orkestrel/process` | `0.0.6` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7` |
69
73
  | `@orkestrel/program` | `0.0.10` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/qualifier` `^0.0.11`, `@orkestrel/rater` `^0.0.11`, `@orkestrel/reason` `^0.0.7` |
70
74
  | `@orkestrel/qualifier` | `0.0.11` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/reason` `^0.0.7` |
@@ -73,7 +77,7 @@ so network-controlled descriptions never enter agent instruction context.
73
77
  | `@orkestrel/reason` | `0.0.8` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
74
78
  | `@orkestrel/relation` | `0.0.10` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8` |
75
79
  | `@orkestrel/router` | `0.0.11` | L2 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7` |
76
- | `@orkestrel/scaffold` | `0.0.53` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/markdown` `^0.0.11`, `@orkestrel/process` `^0.0.6`, `@orkestrel/template` `^0.0.5` |
80
+ | `@orkestrel/scaffold` | `0.0.54` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/markdown` `^0.0.12`, `@orkestrel/process` `^0.0.6`, `@orkestrel/template` `^0.0.5` |
77
81
  | `@orkestrel/sea` | `0.0.11` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6` |
78
82
  | `@orkestrel/server` | `0.0.15` | L3 | `@orkestrel/abort` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/router` `^0.0.11`, `@orkestrel/timeout` `^0.0.8` |
79
83
  | `@orkestrel/sqlite` | `0.0.9` | L1 | `@orkestrel/contract` `^0.0.13` |
@@ -81,7 +85,7 @@ so network-controlled descriptions never enter agent instruction context.
81
85
  | `@orkestrel/supervisor` | `0.0.1` | L5 | `@orkestrel/contract` `^0.0.11`, `@orkestrel/database` `^0.0.9`, `@orkestrel/emitter` `^0.0.6`, `@orkestrel/workflow` `^0.0.12` |
82
86
  | `@orkestrel/table` | `0.0.3` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
83
87
  | `@orkestrel/template` | `0.0.5` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
84
- | `@orkestrel/terminal` | `0.0.12` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/form` `^0.0.3`, `@orkestrel/sse` `^0.0.5` |
88
+ | `@orkestrel/terminal` | `0.0.13` | L3 | `@orkestrel/console` `^0.0.11`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/form` `^0.0.3`, `@orkestrel/sse` `^0.0.5` |
85
89
  | `@orkestrel/test` | `0.0.11` | L0 | |
86
90
  | `@orkestrel/timeout` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
87
91
  | `@orkestrel/tool` | `0.0.12` | L1 | `@orkestrel/contract` `^0.0.13` |
@@ -106,6 +106,17 @@ kind. It reads declaration syntax and file name, never meaning.
106
106
  no bare collection literal.
107
107
  - It proves that no source, test, config, or script file carries an `eslint-disable` or
108
108
  `oxlint-disable` directive.
109
+ - It proves that every `.claude/rules/*.md` file has a rule-map row in `AGENTS.md` and that every
110
+ row resolves to a file.
111
+ - It proves the host portability rules that are path- or text-shaped over the populations
112
+ `POLICY_PORTABILITY_GLOB` and `POLICY_PORTABILITY_SOURCE_GLOB` name: no path segment carries a
113
+ Windows reserved device name, a character Windows refuses, a trailing dot or space, or a sibling
114
+ differing only by case; no `package.json` script names a `.sh` file; and no source in the parsed
115
+ population trims a payload before splitting it on `'\n'`, reads `os.EOL`, or imports `EOL` from
116
+ `node:os`.
117
+ - It cannot write a filename the host refuses. A Windows host rejects `<`, folds a case collision
118
+ into one file, and turns `:` into an alternate data stream, so those boundaries are proven from a
119
+ path population rather than from written files.
109
120
  - It does not prove a collection is frozen. It reads the declaration, never the value a call
110
121
  returns, so `Object.freeze([…])` and any other call initializer are one syntax to it. The freeze
111
122
  obligation in the earlier kind-purity rules binds regardless; only the bare literal is mechanical.
@@ -0,0 +1,98 @@
1
+ ---
2
+ paths:
3
+ - 'src/**/*'
4
+ - 'app/**/*'
5
+ - 'configs/**/*'
6
+ - 'tests/**/*'
7
+ - 'scripts/**/*'
8
+ - 'guides/**/*'
9
+ - 'package.json'
10
+ - '.gitattributes'
11
+ - '.github/workflows/*'
12
+ ---
13
+
14
+ # Host portability rules
15
+
16
+ Published source runs on Windows and Linux. Write one implementation that is correct on each host,
17
+ or gate the host-specific branch on the host that needs it.
18
+
19
+ Take the test half from `.claude/rules/tests.md`: it governs a host-varying property inside a test
20
+ and the form of a conditional skip.
21
+
22
+ ## Host branching
23
+
24
+ - Branch on a capability a runtime probe answers — a returned value, a thrown code, a present
25
+ member — wherever the difference is detectable.
26
+ - Branch on `process.platform` only where the behavior is platform-defined, and name that fact in
27
+ the symbol's `@remarks`.
28
+ - Put a platform-literal table — install root, executable suffix, toolchain name — inside a
29
+ `process.platform` branch, and read it on that platform alone.
30
+ - Never flatten a correctly-gated host branch into a single path, and never relax a fail-closed
31
+ check to make one host pass.
32
+
33
+ ## Line endings
34
+
35
+ - Split arrived text on `/\r\n|\n/`. Never split on `\n` alone, and never split on a bare `\r`.
36
+ - Split first, then trim each line; trimming the whole payload leaves a `\r` on every line but the
37
+ last.
38
+ - Frame a named protocol — HTTP, LSP, RFC 6455, NDJSON — with the terminator that protocol fixes,
39
+ and leave that terminator alone.
40
+ - Emit `\n` from code this package owns. Never emit `os.EOL`.
41
+
42
+ ## Paths
43
+
44
+ - Normalize each operand of a path comparison with the same normalizer before comparing.
45
+ - Never case-fold a containment check. Fold a lookup key only by the file-name case sensitivity the
46
+ consumer declares, and fold the whole key when you fold at all.
47
+ - Compose, split, and relativize a host path with `node:path`. Never concatenate a separator
48
+ literal.
49
+ - Keep a storage key, an archive key, and a URI path slash-separated, and refuse `\` and a drive
50
+ prefix inside one.
51
+ - Refuse the Windows reserved device names and the filename characters Windows rejects, on every
52
+ host.
53
+ - Read the temporary directory from `os.tmpdir()`. Never write a `/tmp` literal in source.
54
+ - Build a `file:` URI with `pathToFileURL`. Never format one from a path string.
55
+
56
+ ## Processes and executables
57
+
58
+ - Resolve, spawn, and terminate a child through `@orkestrel/process` where the package declares it.
59
+ - Where it is not declared, spawn `process.execPath` with a JavaScript entry. Never spawn a `.bin`
60
+ shim, and never add `shell: true` to reach one.
61
+ - Take a resolver's first match only after splitting its output into lines and trimming each one.
62
+ `where` prints a match per line and can name a file that is not an executable.
63
+ - Treat `fs.constants.X_OK` as an existence check on Windows, where `accessSync` passes on a plain
64
+ file. Confirm an executable by its name and its file type instead.
65
+ - Read an `fs.constants` member before putting it in a flag word. Windows leaves `O_NOFOLLOW`
66
+ undefined, so `O_WRONLY | O_NOFOLLOW` refuses nothing there. Never polyfill the missing member:
67
+ compensate with a check the host supports, or record the limit.
68
+ - Read `PROGRAMFILES`, `LOCALAPPDATA`, and `APPDATA` inside the win32 branch and `HOME` inside the
69
+ POSIX branch. Never read one host's root on the other.
70
+ - Terminate a Windows child by its process tree. A `SIGTERM` handler never runs there, so gate every
71
+ cooperative-shutdown path on POSIX.
72
+ - Treat a permission bit as advisory on Windows: a written mode does not round-trip
73
+ (`chmodSync(dir, 0o500)` reads back `0444` there), so assert modes only where a probe reports they
74
+ round-trip.
75
+ - Retry an `EBUSY`, `ENOTEMPTY`, or `EPERM` removal inside a bounded budget, and report the failure
76
+ when the budget ends.
77
+
78
+ ## Terminals
79
+
80
+ - Gate styling and interactivity on `isTTY`, `NO_COLOR`, and `FORCE_COLOR`, never on the platform.
81
+ - Decode `\r`, `\n`, and `\r\n` from a key stream as the same key.
82
+ - Never call a Windows console-mode API. Node exposes none, so record the unreached behavior as a
83
+ limit.
84
+
85
+ ## Scripts and packaging
86
+
87
+ - Write every `package.json` script as a portable command: a Node invocation or an installed binary.
88
+ Never name a `.sh` file there.
89
+ - Keep `#!/usr/bin/env node` on an npm bin. npm writes the Windows shim from it.
90
+ - Keep an agent hook under `scripts/` in bash, and keep `.gitattributes` `eol=lf`.
91
+
92
+ ## Claims
93
+
94
+ - Never describe the suite, the host, or CI as POSIX. Name the host and the date behind a reading.
95
+ - Run the gate chain on each host a change claims, and record a reading that could not run as a
96
+ limit naming the host it needs.
97
+ - Keep a POSIX-shaped `file:///` literal in a guide only as an opaque fictional identifier. Build
98
+ any example a reader executes with `pathToFileURL`.
@@ -60,6 +60,7 @@ A review that reads a diff finds what the diff shows. A review that tries to bre
60
60
 
61
61
  - An instrument is not evidence until it has failed. Pair every probe, comparison, or matrix with a negative control that must report failure, run under the same conditions. An identity check whose control reports "same" has measured nothing.
62
62
  - When a question about a TypeScript edit can supply a workspace project, a case of workspace files with a test, and a negative control naming its files, its test, the stage it must fail at, and why, call the `prove` tool the `probe` MCP server registers before relying on the answer. When the question supplies no project, no case, or no control, follow `.claude/rules/tests.md` § Probes and report the fallback instrument's own control and coverage.
63
+ - When no `probe` server is registered in the session, register one outside the repository, in the harness's own local or user MCP scope — in Claude Code, `claude mcp add` outside project scope — naming the installed `node_modules/@orkestrel/probe/dist/bin/main.js` entry, and start it in the repository whose projects the question names, because the server fixes its workspace from its own working directory. The registration cannot live in the tree: a scaffold target holds no `.mcp.json` file, that path is instruction canon, and a copy at it reports as foreign drift on every `scaffold audit` run. Where the harness registers no server at all, treat the question as supplying no project and take the preceding rule's fallback.
63
64
  - Quote the closing line of the `prove` answer verbatim in every report, brief, and audit verdict that rests on the claim: the `receipt probe:<digest>:…` line when the case ran clean and the control broke exactly where the claim declared it would, and the `no receipt` line otherwise. A `no receipt` line leaves the claim unproved — report it with the stage that refused.
64
65
  - Read a receipt as evidence about its claim, never as a gate result. The gate chain still runs, and `verifier` still owns its result.
65
66
  - Draw the negative control from outside the population the instrument covers. Name the instrument's membership rule first, then pick a control that rule excludes. A control sampled from constructs the instrument already handles proves only that it discriminates among those constructs, and says nothing about the class it silently cannot reach.