@walwal-harness/cli 7.1.56 → 7.1.57

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 CHANGED
@@ -31,6 +31,18 @@ docmeta:
31
31
 
32
32
  ## Unreleased
33
33
 
34
+ ## 7.1.57 — Reading is not reaching: corpus reachability, spec pins, the document is the record (2026-09-01)
35
+ Applies the field-trial revision of the change proposal. Its P1/P4/P5/P6/P9 shipped in 7.1.55–56 and the trial marks them **field-tested** — briefed verbatim into two agents that were not told they were observed, scored on instruments fixed in advance: correct ordering, 4 real citations, 0 fabrications, and a published not-read list. **Three proposals are new, and one claim was corrected.**
36
+
37
+ - **Reachability (new, the trial's own discovery).** The trial's agent followed the reading rule exactly and still missed the entry written for its exact bug — because that entry named `cto` in its own front matter and was linked from `cqo`'s index alone. Lazy loading tells a reader to consult `{shared, own-role}`; that is a **promise about reachability**, and where it is not kept the rule does not narrow the search, **it hides the entry**. Measured: 69 items, **10 unreachable role-routings, 7 invisible to a role the entry itself named** — every one written by a single role and filed only under that role's index. New `scripts/harness-corpus-reachability.sh` audits and (`--fix`) repairs this; `harness-gotcha-register.sh` gains `--roles` and auto-links at registration, so an entry cannot be filed under its author alone. **P1 makes agents read the index; this is what makes the index worth reading.**
38
+ - **Spec pins (new).** `scripts/harness-spec-pin.sh` records the version **and content hash** of every external spec a mission builds against in `{mission}/spec-pins.json`, and re-verifies before completion and archive. This was the trial's actual defect: a spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked **complete** — two revisions had landed silently. The symptom was not an error but an absence: a lookup key stopped matching and three overlays were dropped as `null`. **A category is complete against a spec version, never in the abstract.**
39
+ - **The document is the record (new).** A conclusion a session holds but has not written into its role document and the runtime state file is not held by the company; reconcile before reporting, strike and correct in place, never delete, and check role documents against peer documents. `harness-worker-evidence-validate.sh` now catches the measured case mechanically — a worker whose report says `COMPLETE` while `progress.json` still lists it running. Cost of the unreconciled version: an orchestration loop that went on trying to spawn a finished step **70 times**.
40
+ - **Instrument validity gains a statistics clause.** A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set and reads as a finding.
41
+ - **Zero new numbered rules.** The proposal's own closing question was whether nine more entries on a nineteen-item list is P1's failure mode wearing the shape of a fix. §8 stays at 22: reachability folds into Rule 11 (which becomes *lazy loading is a promise about reachability*), spec pins into Rule 4 (*nothing is complete in the abstract*), and the record clause into Rule 12 (now *the document is the record*). The enforcement lives in scripts, not in more prose.
42
+ - Gate ordering fixed while wiring this: the spec-pin check initially sat after the runtime transition, where a refusal would have refused nothing. All three completion gates now run **before** `progress.json` is touched, verified by asserting the runtime stays `running` on refusal.
43
+ - The audit found two unreachable routings in this package's own bundled corpus on first run, and read its own syntax documentation as data until the declaration was anchored to line start. Both fixed; the bundled corpus now audits clean at install.
44
+ - On P9, the trial **corrected** the earlier claim rather than confirming it: the same verbatim rule was obeyed by the second agent, so the honest figure is one of two — not "prose does not work" but "prose works at a rate you cannot predict per agent." Still the argument for a hook, since a gate has no compliance rate, but the earlier framing overstated it.
45
+
34
46
  ## 7.1.56 — Close the lessons-gate bypass at the terminal transition (2026-09-01)
35
47
  - **Fix: the Hard Rule 20 gate could be bypassed by firing the completion transition.** Found by an A/B sandbox test of 7.1.54 vs 7.1.55 running identical fixtures. 7.1.55 put the gate in `harness-stop.sh` only, but `harness-company-complete.sh` sets `conductor.state=completed`, and `harness-stop.sh` short-circuits on that state at the top of the file — so a mission could complete having never recorded what it read, simply by running the transition. That is precisely the failure the gate was written to prevent. `harness-company-complete.sh` now runs `harness-lessons-gate.sh` itself and refuses the transition (non-zero exit, runtime left in `running`, refusal logged to `progress.log`) when the active mission's role documents lack `## Lessons Preflight` / `## Lessons Tally`.
36
48
  - Deadlock-safe by construction, verified against every legitimate stop path: the gate is scoped to the latest **active** mission, so the Stop-hook auto-complete backstop — which fires only when no mission is active — always passes; and `harness-company-block.sh` (external-authority BLOCKED) is deliberately **not** gated, since a mission blocked on a missing credential must still be able to stop.
@@ -81,6 +81,12 @@ Required output sections:
81
81
 
82
82
  Every CDO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/cdo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
83
83
 
84
+ ## The Document Is The Record
85
+
86
+ A conclusion you hold but have not written into `cdo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
87
+
88
+ Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
89
+
84
90
  ## Worker Spawn Contract
85
91
 
86
92
  Two things are decided **before** the round starts, not after a worker dies.
@@ -113,6 +113,9 @@ The read is an **ordering constraint**, not a reading list. It happens before th
113
113
  - CEO must not accept CQO PASS for a runnable product unless OPS has supplied clean verification-watch evidence or an explicit not-applicable reason. Open OPS incidents, missing runtime mapping, missing required logs, service down, or health mismatch block Owner acceptance.
114
114
  - After launch, CEO treats OPS production incidents as company events. CEO convenes CTO/CQO/OPS when user-impacting production signals appear; CTO owns recovery, CQO owns regression confirmation, and OPS owns evidence and close criteria.
115
115
  - Every CEO and CXX mission document must include an English `## Implementation Notes` section with the required subsections below. CEO must reject CXX reports that omit it.
116
+ - A conclusion a CXX holds but has not written into its `{cxx}.md` **and** into `progress.json` is not held by the company. Before accepting any reported state change, CEO checks that it is reconciled in both; a report that contradicts its own document, or a peer's, is returned. Strike and correct in place — never delete a superseded line.
117
+ - Missions that build against an external spec carry `{mission}/spec-pins.json` (version + content hash). CEO requires CTO to pin before implementation and CQO to verify before PASS. Nothing is complete in the abstract.
118
+ - A registered convention/gotcha is linked from the index of **every role it names as an audience**. CEO treats an unreachable entry as an unregistered one.
116
119
  - Every CEO and CXX mission document and every worker report must carry `## Lessons Preflight` and a one-line `## Lessons Tally` (tally immediately before `## Implementation Notes`). `0 fired` is a valid tally; an omitted tally is not. CEO must reject reports that omit either section.
117
120
  - Every worker spawn declares its model explicitly — never the inherited CLI default. CEO requires the model in each CXX's Worker Task Briefs and Worker Evidence Manifest. **A silent or truncated worker is a rate limit until proven otherwise**: before treating a stalled round as a CXX failure, CEO asks for the usage-limit status and reset time, because a rate-limited worker looks exactly like a finished one from the outside.
118
121
  - Every worker report file is created **before the worker starts**, already carrying its required sections (seeded from `.harness/shared/templates/worker-report.md`). A worker killed mid-round must leave a valid partial report, never a stub. CEO treats a stub report as a seeding failure by the owning CXX, not as a worker failure.
@@ -94,6 +94,12 @@ Required output sections:
94
94
 
95
95
  Every COO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/coo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
96
96
 
97
+ ## The Document Is The Record
98
+
99
+ A conclusion you hold but have not written into `coo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
100
+
101
+ Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
102
+
97
103
  ## Worker Spawn Contract
98
104
 
99
105
  Two things are decided **before** the round starts, not after a worker dies.
@@ -33,7 +33,8 @@ Do not distill the corpus into a private checklist file and read that instead. A
33
33
  5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
34
34
  6. Define quality gates and delegate evidence collection to hired workers in fresh sessions.
35
35
  7. Monitor repeated issues and promote verified lessons to `.harness/conventions`, `.harness/gotchas`, `.harness/memories`, or `.harness/shared`.
36
- 8. Approve or reject archive based solely on worker-provided evidence and OPS runtime/watch evidence when the mission uses a runnable environment.
36
+ 8. Run `bash scripts/harness-spec-pin.sh . {goal-or-child-mission} verify` before any PASS and before archive. A category is complete **against a spec version**, never in the abstract; drift means the verified scope no longer matches what the work was built for, and the verdict is BLOCKED until CTO re-checks the affected work and re-pins.
37
+ 9. Approve or reject archive based solely on worker-provided evidence and OPS runtime/watch evidence when the mission uses a runnable environment.
37
38
 
38
39
  ## Worker Activity Telemetry
39
40
 
@@ -64,6 +65,14 @@ If changed-scope tests or changed-scope coverage fail, CQO returns FAIL or BLOCK
64
65
 
65
66
  CQO must report any out-of-scope full-suite failure or coverage deficit to CEO and CTO with command output, affected paths, and the classification above. CQO must not expand the mission into broad unrelated test-writing work unless CEO explicitly routes that as a new task.
66
67
 
68
+ ## Reachability, Not Just Reading
69
+
70
+ Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
71
+
72
+ **The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
73
+
74
+ Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
75
+
67
76
  ## Instrument Validity
68
77
 
69
78
  Evidence about what did **not** happen is worth exactly as much as the instrument that looked for it.
@@ -72,6 +81,7 @@ Evidence about what did **not** happen is worth exactly as much as the instrumen
72
81
  - Report the control next to the result: what was injected, that it was observed, and the negative result from the same run. A verdict resting on unproven negative evidence is **BLOCKED**, not PASS.
73
82
  - **Where an instrument is supplied by a dependency rather than written in-repo, its filtering behaviour is read from source and quoted** — package, version, file, line range — not inferred from observed output. A filter that lives upstream is invisible to every in-repo search, so its absence from the project's own code is not evidence of its absence.
74
83
  - When an instrument turns out to have been structurally null, the claims it produced are identifiable **by their shape** — every claim of that form, not just the one that happened to be noticed. Re-open them as a class and say so in Recurrence Notes.
84
+ - A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set, and reads as a finding.
75
85
  - An audit question that offers alternatives asserts that the alternatives are exhaustive. "Is it A or B?" cannot return "neither, it is upstream". When an audit stalls, re-ask the question without the menu.
76
86
 
77
87
  ## Hard Rules
@@ -103,7 +113,7 @@ Required output sections in `cqo.md`:
103
113
  4. Instrument Validity — for every negative claim: the instrument, its log level and filter (quoted from source when the instrument comes from a dependency), and the positive control that fired in the same run. Negative evidence with no control is BLOCKED, not PASS.
104
114
  5. OPS Watch Evidence — ops report path, monitored runtime mapping, incidents/warnings, and whether runtime evidence permits PASS.
105
115
  6. CQO Verdict — PASS, FAIL, or BLOCKED based only on worker evidence plus required OPS watch evidence. Must reference Worker Evidence Manifest entries.
106
- 7. Recurrence Notes — accepted gotchas, conventions, memories, or none.
116
+ 7. Recurrence Notes — accepted gotchas, conventions, memories, or none. Every entry registered here names **every role that should be able to find it** and is linked from each of those roles' indexes; `scripts/harness-corpus-reachability.sh` must pass.
107
117
  8. Lessons Tally — one line naming which preflight items actually fired. `0 fired` is valid and must be stated.
108
118
  9. Implementation Notes — in English, with `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`.
109
119
 
@@ -129,6 +139,12 @@ Every CQO evaluator/tester brief must require the worker to append this English
129
139
 
130
140
  The worker notes must cover risks, self-corrections, and chosen direction. Use `None` when a subsection has no entries. CQO must not accept evaluator output that omits this block.
131
141
 
142
+ ## The Document Is The Record
143
+
144
+ A conclusion you hold but have not written into `cqo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
145
+
146
+ Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
147
+
132
148
  ## Worker Spawn Contract
133
149
 
134
150
  Two things are decided **before** the round starts, not after a worker dies.
@@ -57,6 +57,28 @@ Every CTO worker brief that may use Playwright, browser automation, browser-base
57
57
 
58
58
  CTO must not accept worker plans or reports that omit this requirement when browser automation is in scope.
59
59
 
60
+ ## Reachability, Not Just Reading
61
+
62
+ Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
63
+
64
+ **The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
65
+
66
+ Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
67
+
68
+ ## Spec Version Pins
69
+
70
+ Nothing is complete in the abstract. **A category is complete against a spec version.**
71
+
72
+ When the mission builds against any external spec — an API contract, a schema, a partner document, a standard — record it before implementation starts:
73
+
74
+ ```
75
+ bash scripts/harness-spec-pin.sh . {goal-or-child-mission} add <name> <path> <version>
76
+ ```
77
+
78
+ This stores the version **and a content hash** in `{mission}/spec-pins.json`. Re-run `... verify` before handing off to CQO and before declaring any category done. The completion gate re-checks the pins, so drift blocks the mission from closing.
79
+
80
+ Measured: a spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked **complete**. Two later revisions had landed silently. The symptom was not an error — a lookup key stopped matching, and three overlays were dropped as `null`. **No error, no log, just an absence.** Nothing in the harness recorded which version the work had been for.
81
+
60
82
  ## Test Coverage Scope
61
83
 
62
84
  CTO must optimize engineering verification around the work actually changed in the mission. CTO worker briefs must require targeted tests, coverage checks, and regression commands for the changed files, modules, APIs, flows, and directly affected dependencies only.
@@ -116,6 +138,12 @@ Every CTO worker brief must require the worker to append this English block to t
116
138
 
117
139
  The worker notes must cover risks, self-corrections, and chosen direction. Use `None` when a subsection has no entries. CTO must not accept worker output that omits this block.
118
140
 
141
+ ## The Document Is The Record
142
+
143
+ A conclusion you hold but have not written into `cto.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
144
+
145
+ Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
146
+
119
147
  ## Worker Spawn Contract
120
148
 
121
149
  Two things are decided **before** the round starts, not after a worker dies.
@@ -79,12 +79,21 @@ After service launch, OPS continues the same monitoring duty against `runtime.pr
79
79
  - Repeated incidents must trigger recovery coordination through CEO -> CTO/CQO/OPS. OPS supplies evidence and recovery criteria; CTO owns fixes; CQO owns regression confirmation.
80
80
  - OPS may classify resolved events as close candidates only after the monitored endpoint is healthy and logs no longer show the triggering error pattern.
81
81
 
82
+ ## Reachability, Not Just Reading
83
+
84
+ Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
85
+
86
+ **The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
87
+
88
+ Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
89
+
82
90
  ## Instrument Validity
83
91
 
84
92
  OPS supplies most of the harness's negative evidence — "no crash", "no error in the log", "health stayed green" — so OPS owns proving the instrument could have seen the failure.
85
93
 
86
94
  - Every claim of the form "nothing bad happened" ships with a **positive control that fired in the same run** and varied the exact variable under suspicion. Without one, report the observation as unverified, not clean.
87
95
  - **Read dependency-supplied filtering from source and quote it** — package, version, file, line range — instead of inferring it from what appeared in the log. Log middleware, dev servers, proxies, and test runners routinely drop successful or sub-threshold requests at a log level nobody chose deliberately, and that rule appears nowhere in the project's own code.
96
+ - A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set, and reads as a finding.
88
97
  - Record the instrument in Environment Evidence: what tool observed the runtime, at what log level, with what filter, and what the control was. An unrecorded instrument makes every negative result from that run unusable.
89
98
 
90
99
  ## Port Policy
@@ -143,6 +152,12 @@ OPS must not accept worker plans or reports that omit this requirement when brow
143
152
 
144
153
  Every OPS worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/ops/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
145
154
 
155
+ ## The Document Is The Record
156
+
157
+ A conclusion you hold but have not written into `ops.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
158
+
159
+ Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
160
+
146
161
  ## Worker Spawn Contract
147
162
 
148
163
  Two things are decided **before** the round starts, not after a worker dies.
@@ -169,15 +169,15 @@ All Playwright usage by CEO, CXX, and hired workers must run with a visible real
169
169
  1. **No source edit without `{mission}/cto.md`** — CTO scope sign-off is required before any source file is modified.
170
170
  2. **No CXX impersonation** — The active model must not act as CEO/CTO/CQO inline. Use installed harness skills in fresh sessions.
171
171
  3. **No unnamed workers** — All specialist work routes through `harness-hiring` → `harness-resource-manager`.
172
- 4. **No archive without CQO verdict** — `{mission}/cqo.md` with explicit PASS must exist.
172
+ 4. **No archive without CQO verdict, and nothing is complete in the abstract** — `{mission}/cqo.md` with explicit PASS must exist. **A mission records the version and content hash of every external spec it builds against** (`{mission}/spec-pins.json`, managed by `scripts/harness-spec-pin.sh`), and the pins are re-checked against the current documents **before any category is marked complete and before any archive**. A category is complete *against a spec version*, never in the abstract: a spec can move under a category already marked done, changing a contract, with every later revision landing silently.
173
173
  5. **No gotcha skip** — Every hot-fix produces at least one `.harness/gotchas/` or `.harness/conventions/` entry.
174
174
  6. **This file is read-only during missions** — Raise a separate `/goal` to update AGENTS.md.
175
175
  7. **CEO routes only to CXX — never to workers** — CEO must not dispatch, hire, or brief specialist workers directly. Implementation workers are hired by CTO. Evaluator/tester workers are hired by CQO.
176
176
  8. **No CXX self-execution** — CXX agents coordinate and manage only. A CXX that produces deliverables without matching worker records has violated its scope. CEO must reject such reports.
177
177
  9. **No verdict without worker evidence** — CQO cannot issue ACCEPTED/REJECTED without a Worker Evidence Manifest referencing at least one evaluator worker. Self-inspection by CQO is not valid evidence.
178
178
  10. **Hierarchical worker ownership** — Hired workers are installed under `.claude/skills/{owning-cxx}/{worker}/` and `.codex/skills/{owning-cxx}/{worker}/`; mission worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`. Flat `{mission}/workers/` reports are legacy and signal an ownership violation unless explicitly migrated.
179
- 11. **Lazy convention/gotcha loading** — *What* to read. *When* to read it, and what to write about it, is Rule 20. CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX.
180
- 12. **Implementation Notes required** — `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
179
+ 11. **Lazy convention/gotcha loading is a promise about reachability** — *What* to read. *When* to read it, and what to write about it, is Rule 20. CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX. **An entry is linked from the index of every role it names as an audience, not only its author's.** Registration is incomplete until each declared audience can reach it by the path that role is told to use — where the promise is not kept, this rule does not narrow the search, **it hides the entry**. Declare the audience (`<!-- roles: cto, cqo -->` on a topic file, `- **Roles**: cto, cqo` on an index entry) and verify with `scripts/harness-corpus-reachability.sh`. The recurring failure is mundane: *the author files under itself*, and registration feels complete because the entry is indexed — just not where its declared readers look.
180
+ 12. **The document is the record** — A conclusion a session holds but has not written into its role document **is not held by the company**. Before reporting a state change to anyone, reconcile it in your own document and in the runtime state file; strike and correct in place, never delete. Role documents are checked against peer documents, not only against themselves. `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
181
181
  13. **Structured runtime state** — If the harness must parse it, record it in JSON or JSONL. CXX todo queues, event history, heartbeat timestamps, preemption/resume state, and completion evidence belong in `.harness/todos/*.json*` or `.harness/events.jsonl`, not in free-form Markdown tables. Markdown remains for instructions, conventions, gotchas, skills, and human-readable mission narrative.
182
182
  14. **Mission lifecycle is explicit** — Every goal, submission, and hot-fix directory must contain `mission-state.json` with `lifecycle` and `active`. Only one child mission under a goal may be active. Starting a newer submission/hot-fix closes, cancels, or supersedes the previous active child unless CEO records a deliberate TODO/resume plan.
183
183
  15. **Owner is final acceptance only** — Owner is not a tester or QA substitute. CEO/CXX must not report "done, please check" until worker-backed verification proves the goal can be completed. Use unit tests, E2E, Playwright, test accounts, seeded data, build/run checks, logs, and CQO evidence before requesting Owner acceptance.
@@ -18,3 +18,5 @@ Only company-level roles are provided by default:
18
18
  Topic-specific convention files may use descriptive names such as `i18n-locale.md`.
19
19
 
20
20
  CXX files such as `cto.md` and `cqo.md` act as lazy-loading indexes. Add links there when a topic file applies to that CXX.
21
+
22
+ **Declare the audience.** Every convention names each role that must be able to *find* it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and is linked from each of those roles' index files, not only its author's. Lazy loading is a promise about reachability: an entry filed only under its author is indexed but invisible to the readers it names. Verify with `bash scripts/harness-corpus-reachability.sh . text` (`--fix` adds the missing links). Entries in `shared.md` need no cross-linking; every role reads it.
@@ -24,6 +24,16 @@
24
24
  - CXX decisions use `{cxx}.md`.
25
25
  - Worker reports use `{owning-cxx}/workers/{worker-name}.md`.
26
26
 
27
+ ## Declared Audience
28
+
29
+ Every convention and gotcha declares which roles must be able to **find** it, and is linked from each of those roles' index files.
30
+
31
+ - Topic file: `<!-- roles: cto, cqo -->` near the top.
32
+ - Entry inside an index file: `- **Roles**: cto, cqo`.
33
+ - Registration is complete only when `bash scripts/harness-corpus-reachability.sh . text` passes. `--fix` adds the missing links.
34
+
35
+ Lazy loading tells a reader to consult `{shared, own-role}` and nothing else. That is a **promise about reachability**: where the promise is not kept, the rule stops narrowing the search and starts hiding the entry. An agent following the reading rule exactly will never see an item filed only under someone else's index.
36
+
27
37
  ## Section-Scoped Reading
28
38
 
29
39
  Any reader that scans a role document, worker report, or mission record for sections — a script, a hook, an agent following a protocol — matches:
package/gotchas/README.md CHANGED
@@ -18,3 +18,5 @@ Only company-level roles are provided by default:
18
18
  Topic-specific gotcha files may use descriptive names such as `i18n-locale-hotfix.md`.
19
19
 
20
20
  CXX files such as `cto.md` and `cqo.md` act as lazy-loading indexes. Add links there when a topic file applies to that CXX.
21
+
22
+ **Declare the audience.** Every gotcha names each role that must be able to *find* it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and is linked from each of those roles' index files, not only its author's. Lazy loading is a promise about reachability: an entry filed only under its author is indexed but invisible to the readers it names. Verify with `bash scripts/harness-corpus-reachability.sh . text` (`--fix` adds the missing links). Entries in `shared.md` need no cross-linking; every role reads it.
package/gotchas/cqo.md CHANGED
@@ -19,3 +19,14 @@ An instrument's filtering behaviour is read from source and quoted — package,
19
19
  ## Audit Questions That Offer Alternatives
20
20
 
21
21
  An audit question that offers alternatives asserts that the alternatives are exhaustive. "Is it a skip rule **or** a status-conditional format?" cannot return "neither, it is upstream" — both branches locate the rule inside our own code, so the true answer is unreachable from the question's grammar. When an audit stalls, re-ask the question without the menu.
22
+
23
+ ## A Range Is The Two Least Representative Points
24
+ <!-- roles: cqo, ops -->
25
+
26
+ Publish a summary statistic **with its `n`**, and characterise a spiky series by **percentiles, never min–max**. A range reports the two most extreme observations in the set and reads as a finding; on a spiky series it is almost always noise wearing the shape of a result.
27
+
28
+ ## Cross-role Links
29
+
30
+ Entries written under another role that name CQO as an audience.
31
+
32
+ - [Complete Against A Spec Version, Never In The Abstract](./cto.md) — verify `spec-pins.json` before PASS and before archive.
package/gotchas/cto.md CHANGED
@@ -11,3 +11,10 @@ Do not start implementation before domain, API, platform, account, and integrati
11
11
  ## Stub Report From A Worker Killed Mid-Round
12
12
 
13
13
  A report assembled at the end of a round becomes a stub when the round is cut short, and a stub halts the company. Create the worker report with every required section present **before** the worker starts, and require it to be filled in incrementally. Same failure, opposite outcome: an unseeded worker killed mid-round leaves a stub and costs a re-run; a seeded worker killed by the same limit leaves an intact partial report and costs nothing. The variable is a decision taken before the round.
14
+
15
+ ## Complete Against A Spec Version, Never In The Abstract
16
+ <!-- roles: cto, cqo -->
17
+
18
+ A category marked complete records *what it was complete against*. A spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked done — two later revisions landed silently and nothing in the harness recorded which version the work had been for.
19
+
20
+ The symptom was not an error. A lookup key stopped matching, and three map overlays were dropped as `null`: no exception, no log, just an absence. Pin version **and content hash** with `scripts/harness-spec-pin.sh` before implementation, and re-verify before completion and archive.
package/gotchas/ops.md CHANGED
@@ -19,3 +19,9 @@ Do not let CXX agents choose arbitrary ports. CEO must set `HARNESS_BASE_PORT` a
19
19
  ## Clean Log From An Unproven Instrument
20
20
 
21
21
  "Nothing bad appeared in the log" is worth nothing until a positive control proves the log could have shown it. Dev servers, log middleware, proxies, and test runners routinely drop successful or sub-threshold requests at a log level nobody chose deliberately, and that rule appears nowhere in the project's own code. Record the instrument — tool, log level, filter, control — in Environment Evidence, or report the observation as unverified rather than clean.
22
+
23
+ ## Cross-role Links
24
+
25
+ Entries written under another role that name OPS as an audience.
26
+
27
+ - [A Range Is The Two Least Representative Points](./cqo.md) — publish `n`, use percentiles, not min–max.
package/gotchas/shared.md CHANGED
@@ -29,3 +29,19 @@ A reader anchored on `^#` misses the same heading written as `> ## …`, which i
29
29
  ## Rules Stated One Layer Above The Executing Layer
30
30
 
31
31
  A requirement placed on a CXX that its workers must also satisfy does not reach the workers unless it is inserted **verbatim** into the worker brief. A rule stated one layer above the layer that executes it does not apply, and the layer below cannot infer a rule it was never given.
32
+
33
+ ## The Author Files Under Itself
34
+ <!-- roles: ceo, coo, cdo, cto, cqo, ops -->
35
+
36
+ The recurring way an indexed entry becomes unreachable: whoever wrote it filed it under their own role index and nowhere else. Registration *feels* complete — the entry exists, it is indexed, it is linked. It is just not where its declared readers are told to look.
37
+
38
+ Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named**. The clearest case was an entry whose own text called two others "the same family" — both siblings were already reachable from the index it was missing from. An inconsistency, not a decision.
39
+
40
+ Declare the audience at registration and let `scripts/harness-corpus-reachability.sh` link it. Reading is not reaching.
41
+
42
+ ## A Conclusion Not Written Is Not Held
43
+ <!-- roles: ceo, coo, cdo, cto, cqo, ops -->
44
+
45
+ A conclusion a session holds but has not written into its role document and the runtime state file is not held by the company. Reconcile before reporting; strike and correct in place, never delete.
46
+
47
+ The cheap version: a deliverable table that contradicts three messages already sent, and a line still requesting work a peer has already delivered. The expensive version was measured — a required step completed, reported, and accepted, that never reached the state file, so the orchestration loop went on trying to spawn the finished step **70 times**. "Write your conclusions down" reads as tidiness until it reads as seventy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.56",
3
+ "version": "7.1.57",
4
4
  "description": "Company-style AI agent harness for Claude and Codex. Installs commands, CXX agents, skills, HR-Resource hiring pool, and project-local .harness runtime state.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -16,6 +16,7 @@ REASON="${2:-mission-complete}"
16
16
 
17
17
  PROGRESS="$PROJECT_ROOT/.harness/progress.json"
18
18
  TODOS="$PROJECT_ROOT/.harness/todos/state.json"
19
+ DOCS="$PROJECT_ROOT/.harness/documents"
19
20
  [ -f "$PROGRESS" ] || {
20
21
  echo "[company-complete] not found: $PROGRESS" >&2
21
22
  exit 1
@@ -49,6 +50,20 @@ if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-les
49
50
  fi
50
51
  fi
51
52
 
53
+ # Corpus reachability (Hard Rule 11) and spec pins (Hard Rule 4) are re-checked
54
+ # at the same point, for the same reason: both are promises that decay silently
55
+ # between when they are made and when the mission claims to be done.
56
+ if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-corpus-reachability.sh" ]; then
57
+ if ! reach_out="$(bash "$SCRIPT_DIR/harness-corpus-reachability.sh" "$PROJECT_ROOT" text 2>/dev/null)"; then
58
+ {
59
+ echo "[company-complete] REFUSED: corpus entries are unreachable by roles they name as an audience."
60
+ echo "$reach_out"
61
+ } >&2
62
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | company-complete | refused | corpus-reachability (Hard Rule 11)" >> "$PROJECT_ROOT/.harness/progress.log" 2>/dev/null || true
63
+ exit 1
64
+ fi
65
+ fi
66
+
52
67
  state_mtime() {
53
68
  stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null || echo 0
54
69
  }
@@ -72,6 +87,24 @@ pick_transition_mission_state() {
72
87
  [ -n "$best" ] && printf '%s\n' "$best"
73
88
  }
74
89
 
90
+ # Spec pins are verified against the mission this transition would close. This
91
+ # sits BEFORE the runtime transition on purpose: a refusal that runs after
92
+ # progress.json is already `completed` has refused nothing.
93
+ if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-spec-pin.sh" ] && [ -d "$DOCS" ]; then
94
+ pin_target="$(pick_transition_mission_state)"
95
+ if [ -n "$pin_target" ]; then
96
+ mission_rel="${pin_target#"$DOCS"/}"; mission_rel="${mission_rel%/mission-state.json}"
97
+ if ! pin_out="$(bash "$SCRIPT_DIR/harness-spec-pin.sh" "$PROJECT_ROOT" "$mission_rel" verify text 2>/dev/null)"; then
98
+ {
99
+ echo "[company-complete] REFUSED: this mission was built against a spec that has since moved."
100
+ echo "$pin_out"
101
+ } >&2
102
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | company-complete | refused | spec-pin drift (Hard Rule 4)" >> "$PROJECT_ROOT/.harness/progress.log" 2>/dev/null || true
103
+ exit 1
104
+ fi
105
+ fi
106
+ fi
107
+
75
108
  if ! bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
76
109
  '.company_state.state = "idle" |
77
110
  .company_state.active_workers = 0 |
@@ -109,7 +142,6 @@ if [ -f "$TODOS" ]; then
109
142
  ' "$TODOS" > "$tmp" && mv "$tmp" "$TODOS"
110
143
  fi
111
144
 
112
- DOCS="$PROJECT_ROOT/.harness/documents"
113
145
  if [ -d "$DOCS" ]; then
114
146
  target_state="$(pick_transition_mission_state)"
115
147
  if [ -n "$target_state" ]; then
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+ # harness-corpus-reachability.sh — AGENTS.md Hard Rule 11 (reachability clause).
3
+ #
4
+ # Lazy loading tells a reader to consult only {shared, own-role}. That is a
5
+ # PROMISE ABOUT REACHABILITY. Where it is not kept, the rule does not narrow the
6
+ # search — it hides the entry: an item can name a role as its audience, be
7
+ # indexed, and still be invisible to that reader by the path the rule tells it
8
+ # to use. Measured on a live corpus: 69 items, 10 unreachable role-routings,
9
+ # 7 of them invisible to a role the item itself named.
10
+ #
11
+ # The mechanism is mundane and recurs on any install: THE AUTHOR FILES UNDER
12
+ # ITSELF. Registration feels complete because the entry is indexed — just not
13
+ # where its declared readers look.
14
+ #
15
+ # Declaring an audience (either form):
16
+ # file-level (topic files): <!-- roles: cto, cqo -->
17
+ # entry-level (index files): - **Roles**: cto, cqo
18
+ #
19
+ # Usage:
20
+ # harness-corpus-reachability.sh <project-root> [text|json] [--fix]
21
+ set -uo pipefail
22
+
23
+ PROJECT_ROOT="${1:-.}"
24
+ MODE="${2:-text}"
25
+ FIX="${3:-}"
26
+
27
+ ROLES="ceo coo cdo cto cqo ops hiring resource-manager brick-office shared"
28
+ violations=()
29
+ fixed=0
30
+
31
+ is_index_file() {
32
+ local base="$1"
33
+ case " $ROLES " in *" ${base%.md} "*) return 0 ;; esac
34
+ return 1
35
+ }
36
+
37
+ # Roles this file/entry names as an audience, whitespace-separated, deduped.
38
+ #
39
+ # A declaration must OWN its line. Anchoring here is what keeps the audit from
40
+ # reading its own documentation as data: prose that shows the syntax inline
41
+ # (`<!-- roles: cto, cqo -->` inside backticks) is an example, not a routing,
42
+ # and counting it makes the audit report holes that do not exist.
43
+ declared_roles() {
44
+ {
45
+ grep -oiE '^[[:space:]]*<!--[[:space:]]*roles?:[^>]*-->' "$1" 2>/dev/null | sed -E 's/^[[:space:]]*<!--[[:space:]]*[Rr]oles?:[[:space:]]*//; s/[[:space:]]*-->//'
46
+ grep -oiE '^[[:space:]]*[-*][[:space:]]*\*\*Roles?\*\*:.*' "$1" 2>/dev/null | sed -E 's/.*\*\*[Rr]oles?\*\*:[[:space:]]*//'
47
+ } | tr ',' '\n' | tr -d ' \t' | grep -v '^$' | tr 'A-Z' 'a-z' | sort -u
48
+ }
49
+
50
+ # Does <index> reach <target basename>? Only a real markdown link target counts.
51
+ # A bare mention of the filename in prose is not a route a reader can follow,
52
+ # and counting it would make the audit report success on exactly the entries it
53
+ # exists to find. Matches `](./file.md`, `](file.md`, with or without an anchor.
54
+ reaches() {
55
+ local index="$1" target="$2"
56
+ [ -f "$index" ] || return 1
57
+ grep -qF -- "](./$target" "$index" || grep -qF -- "]($target" "$index"
58
+ }
59
+
60
+ audit_dir() {
61
+ local kind="$1" dir="$PROJECT_ROOT/.harness/$1"
62
+ [ -d "$dir" ] || return 0
63
+ local f base host_role roles role index
64
+ for f in "$dir"/*.md; do
65
+ [ -e "$f" ] || continue
66
+ base="$(basename "$f")"
67
+ [ "$base" = "README.md" ] && continue
68
+ # shared.md is read by every role by construction (Hard Rule 11), so an entry
69
+ # living there is already reachable to every audience it could name.
70
+ [ "$base" = "shared.md" ] && continue
71
+ host_role="${base%.md}"
72
+ roles="$(declared_roles "$f")"
73
+ [ -n "$roles" ] || continue
74
+ while IFS= read -r role; do
75
+ [ -n "$role" ] || continue
76
+ # The host file is reachable to its own role by construction.
77
+ if is_index_file "$base" && [ "$role" = "$host_role" ]; then continue; fi
78
+ case " $ROLES " in *" $role "*) ;; *) continue ;; esac
79
+ # Naming `shared` as an audience is satisfied by construction.
80
+ [ "$role" = "shared" ] && continue
81
+ index="$dir/${role}.md"
82
+ if reaches "$index" "$base"; then continue; fi
83
+ if [ "$FIX" = "--fix" ]; then
84
+ [ -f "$index" ] || printf '# %s — %s\n' "${role}" "${kind}" > "$index"
85
+ if ! grep -qF 'Cross-role Links' "$index"; then
86
+ printf '\n## Cross-role Links\n\nItems written elsewhere that name this role as an audience.\n' >> "$index"
87
+ fi
88
+ printf -- '- [%s](./%s) — declares `%s`\n' "${base%.md}" "$base" "$role" >> "$index"
89
+ fixed=$((fixed + 1))
90
+ else
91
+ violations+=("$kind/$base:$role")
92
+ fi
93
+ done <<EOF
94
+ $roles
95
+ EOF
96
+ done
97
+ }
98
+
99
+ audit_dir conventions
100
+ audit_dir gotchas
101
+
102
+ if [ "$FIX" = "--fix" ]; then
103
+ echo "[corpus-reachability] linked $fixed previously unreachable routing(s)"
104
+ exit 0
105
+ fi
106
+
107
+ if [ "${#violations[@]}" -eq 0 ]; then
108
+ [ "$MODE" = "json" ] && { jq -nc '{ok:true, unreachable:[]}' 2>/dev/null || echo '{"ok":true,"unreachable":[]}'; }
109
+ [ "$MODE" = "text" ] && echo "[corpus-reachability] all declared audiences reachable"
110
+ exit 0
111
+ fi
112
+
113
+ if [ "$MODE" = "json" ]; then
114
+ printf '%s\n' "${violations[@]}" | jq -Rcs '
115
+ split("\n")[:-1]
116
+ | map(capture("(?<item>[^:]+):(?<role>.*)"))
117
+ | {ok:false, unreachable:.}
118
+ '
119
+ else
120
+ echo "Corpus reachability violation (AGENTS.md Hard Rule 11):"
121
+ for v in "${violations[@]}"; do
122
+ echo "- item: ${v%%:*}"
123
+ echo " names role '${v#*:}' as an audience, but ${v#*:}'s index does not reach it"
124
+ done
125
+ echo " An entry is registered only when every role it names can reach it by the"
126
+ echo " path that role is told to use. Fix: harness-corpus-reachability.sh <root> text --fix"
127
+ fi
128
+ exit 1
@@ -11,7 +11,8 @@
11
11
  # --right "올바른 행동" \
12
12
  # --why "근거" \
13
13
  # --scope "적용 범위" \
14
- # --source "evaluator-functional:F-003"
14
+ # --source "evaluator-functional:F-003" \\
15
+ # --roles "cqo, cto"
15
16
  #
16
17
  # 2) 일괄 등록 (JSON stdin/파일):
17
18
  # bash harness-gotcha-register.sh <project-root> --from-json <path>
@@ -69,6 +70,7 @@ TODAY="$(date +%Y-%m-%d)"
69
70
  # ─────────────────────────────────────────
70
71
  register_one() {
71
72
  local target="$1" rule_id="$2" title="$3" wrong="$4" right="$5" why="$6" scope="$7" source="$8"
73
+ local roles="${9:-$target}"
72
74
  local file="$GOTCHAS_DIR/${target}.md"
73
75
  mkdir -p "$(dirname "$file")"
74
76
 
@@ -118,6 +120,7 @@ EOF
118
120
  {
119
121
  echo ""
120
122
  echo "### [$g_id] $title <!-- rule_id: $rule_id -->"
123
+ echo "- **Roles**: $roles"
121
124
  echo "- **Status**: unverified"
122
125
  echo "- **Date**: $TODAY"
123
126
  echo "- **Source**: $source"
@@ -132,6 +135,13 @@ EOF
132
135
 
133
136
  echo "[gotcha-register] $target: registered $g_id ($rule_id) — unverified"
134
137
 
138
+ # Hard Rule 11: an entry filed only under its author is indexed but unreachable
139
+ # to the other roles it names. Link it now, while the audience is still known.
140
+ local reach="$(dirname "$0")/harness-corpus-reachability.sh"
141
+ if [ -x "$reach" ] && [ "$roles" != "$target" ]; then
142
+ bash "$reach" "$PROJECT_ROOT" text --fix >/dev/null 2>&1 || true
143
+ fi
144
+
135
145
  # Log to progress.log if present
136
146
  local progress_log="$PROJECT_ROOT/.harness/progress.log"
137
147
  if [ -f "$progress_log" ]; then
@@ -141,7 +151,9 @@ EOF
141
151
 
142
152
  # ─────────────────────────────────────────
143
153
  # JSON 배열에서 일괄 등록
144
- # schema: [{ target, rule_id, title, wrong, right, why, scope, source }]
154
+ # schema: [{ target, rule_id, title, wrong, right, why, scope, source, roles }]
155
+ # `roles` is the comma-separated list of every role that must be able to FIND
156
+ # this entry — not only the one that wrote it (Hard Rule 11). Defaults to target.
145
157
  # ─────────────────────────────────────────
146
158
  register_from_json() {
147
159
  local json="$1"
@@ -153,7 +165,7 @@ register_from_json() {
153
165
 
154
166
  local i
155
167
  for ((i=0; i<count; i++)); do
156
- local t r ti w ri wh sc so
168
+ local t r ti w ri wh sc so rl
157
169
  t=$(echo "$json" | jq -r ".[$i].target // empty")
158
170
  r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
159
171
  ti=$(echo "$json" | jq -r ".[$i].title // empty")
@@ -162,12 +174,13 @@ register_from_json() {
162
174
  wh=$(echo "$json" | jq -r ".[$i].why // empty")
163
175
  sc=$(echo "$json" | jq -r ".[$i].scope // \"항상\"")
164
176
  so=$(echo "$json" | jq -r ".[$i].source // \"evaluator:auto\"")
177
+ rl=$(echo "$json" | jq -r ".[$i].roles // empty")
165
178
 
166
179
  if [ -z "$t" ] || [ -z "$r" ] || [ -z "$ti" ]; then
167
180
  echo "[gotcha-register] skip: missing target/rule_id/title at index $i" >&2
168
181
  continue
169
182
  fi
170
- register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so"
183
+ register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so" "${rl:-$t}"
171
184
  done
172
185
  }
173
186
 
@@ -354,6 +367,7 @@ RIGHT=""
354
367
  WHY=""
355
368
  SCOPE="항상"
356
369
  SOURCE="evaluator:auto"
370
+ ROLES_DECL=""
357
371
  MODE="single"
358
372
  FROM_JSON=""
359
373
 
@@ -367,6 +381,7 @@ while [ $# -gt 0 ]; do
367
381
  --why) WHY="$2"; shift 2 ;;
368
382
  --scope) SCOPE="$2"; shift 2 ;;
369
383
  --source) SOURCE="$2"; shift 2 ;;
384
+ --roles) ROLES_DECL="$2"; shift 2 ;;
370
385
  --from-json) MODE="json"; FROM_JSON="$2"; shift 2 ;;
371
386
  --scan-evaluations) MODE="scan"; shift ;;
372
387
  --scan-all) MODE="scan-all"; shift ;;
@@ -380,7 +395,7 @@ case "$MODE" in
380
395
  echo "[gotcha-register] usage: --target X --rule-id Y --title Z [...]" >&2
381
396
  exit 1
382
397
  fi
383
- register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE"
398
+ register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE" "${ROLES_DECL:-$TARGET}"
384
399
  ;;
385
400
  json)
386
401
  if [ ! -f "$FROM_JSON" ]; then echo "[gotcha-register] file not found: $FROM_JSON" >&2; exit 1; fi
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # harness-spec-pin.sh — AGENTS.md Hard Rule 4 (spec-pin clause).
3
+ #
4
+ # A category is complete AGAINST A SPEC VERSION, never in the abstract. Measured:
5
+ # a spec moved v0.7 -> v0.9, changing a response contract, while the category
6
+ # built against v0.7 sat marked complete — two later revisions had landed
7
+ # silently, and nothing in the harness recorded which version the work was for.
8
+ # The symptom was a lookup key that no longer matched: no error, no log, three
9
+ # overlays dropped as null.
10
+ #
11
+ # Usage:
12
+ # harness-spec-pin.sh <project-root> <mission-rel> add <name> <path> [version]
13
+ # harness-spec-pin.sh <project-root> <mission-rel> verify [text|json]
14
+ # harness-spec-pin.sh <project-root> <mission-rel> list
15
+ set -uo pipefail
16
+
17
+ PROJECT_ROOT="${1:-.}"
18
+ MISSION_REL="${2:-}"
19
+ CMD="${3:-verify}"
20
+ DOCS="$PROJECT_ROOT/.harness/documents"
21
+ PINS="$DOCS/$MISSION_REL/spec-pins.json"
22
+
23
+ command -v jq >/dev/null 2>&1 || { echo "[spec-pin] jq required" >&2; exit 2; }
24
+ [ -n "$MISSION_REL" ] || { echo "[spec-pin] mission path required" >&2; exit 2; }
25
+
26
+ hash_of() {
27
+ local f="$1"
28
+ [ -f "$f" ] || { printf 'MISSING'; return; }
29
+ if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$f" | awk '{print $1}'
30
+ elif command -v sha256sum >/dev/null 2>&1; then sha256sum "$f" | awk '{print $1}'
31
+ else printf 'NOHASHER'; fi
32
+ }
33
+
34
+ case "$CMD" in
35
+ add)
36
+ name="${4:-}"; specpath="${5:-}"; version="${6:-unversioned}"
37
+ [ -n "$name" ] && [ -n "$specpath" ] || { echo "[spec-pin] usage: add <name> <path> [version]" >&2; exit 2; }
38
+ mkdir -p "$(dirname "$PINS")"
39
+ [ -f "$PINS" ] || echo '{"pins":[]}' > "$PINS"
40
+ abs="$specpath"; case "$specpath" in /*) ;; *) abs="$PROJECT_ROOT/$specpath" ;; esac
41
+ h="$(hash_of "$abs")"
42
+ tmp="$(mktemp)"
43
+ jq --arg n "$name" --arg p "$specpath" --arg v "$version" --arg h "$h" \
44
+ '.pins = ((.pins // []) | map(select(.name != $n)) + [{name:$n, path:$p, version:$v, sha256:$h, pinned_at:(now|todate)}])' \
45
+ "$PINS" > "$tmp" && mv "$tmp" "$PINS"
46
+ echo "[spec-pin] pinned $name @ $version ($h)"
47
+ ;;
48
+ list)
49
+ [ -f "$PINS" ] && jq -r '.pins[] | "- \(.name) @ \(.version) — \(.path) [\(.sha256[0:12])]"' "$PINS" || echo "(no pins)"
50
+ ;;
51
+ verify)
52
+ mode="${4:-text}"
53
+ # No pins file is not a failure: not every mission builds against an external spec.
54
+ if [ ! -f "$PINS" ]; then
55
+ [ "$mode" = "json" ] && jq -nc '{ok:true, drifted:[], pinned:0}'
56
+ exit 0
57
+ fi
58
+ drifted=()
59
+ while IFS=$'\t' read -r name specpath version want; do
60
+ [ -n "$name" ] || continue
61
+ abs="$specpath"; case "$specpath" in /*) ;; *) abs="$PROJECT_ROOT/$specpath" ;; esac
62
+ got="$(hash_of "$abs")"
63
+ [ "$got" = "NOHASHER" ] && continue
64
+ if [ "$got" != "$want" ]; then
65
+ if [ "$got" = "MISSING" ]; then drifted+=("$name|$version|spec file is gone: $specpath")
66
+ else drifted+=("$name|$version|content changed since it was pinned: $specpath"); fi
67
+ fi
68
+ done < <(jq -r '.pins[]? | [.name, .path, .version, .sha256] | @tsv' "$PINS" 2>/dev/null)
69
+ total="$(jq '[.pins[]?] | length' "$PINS" 2>/dev/null || echo 0)"
70
+ if [ "${#drifted[@]}" -eq 0 ]; then
71
+ [ "$mode" = "json" ] && jq -nc --argjson n "$total" '{ok:true, drifted:[], pinned:$n}'
72
+ [ "$mode" = "text" ] && echo "[spec-pin] $total pin(s) still match the current documents"
73
+ exit 0
74
+ fi
75
+ if [ "$mode" = "json" ]; then
76
+ printf '%s\n' "${drifted[@]}" | jq -Rcs 'split("\n")[:-1] | map(split("|") | {name:.[0], pinned_version:.[1], detail:.[2]}) | {ok:false, drifted:.}'
77
+ else
78
+ echo "Spec pin drift (AGENTS.md Hard Rule 4):"
79
+ for d in "${drifted[@]}"; do
80
+ rest="${d#*|}"
81
+ echo "- ${d%%|*} (built against ${rest%%|*})"
82
+ echo " ${d##*|}"
83
+ done
84
+ echo " This mission was built against a spec that has since moved. Re-check the"
85
+ echo " affected work before marking anything complete, then re-pin:"
86
+ echo " scripts/harness-spec-pin.sh . $MISSION_REL add <name> <path> <new-version>"
87
+ fi
88
+ exit 1
89
+ ;;
90
+ *) echo "[spec-pin] unknown command: $CMD" >&2; exit 2 ;;
91
+ esac
@@ -120,6 +120,32 @@ done <<EOF
120
120
  $mission_dirs
121
121
  EOF
122
122
 
123
+ # Record drift (AGENTS.md Hard Rule 12): a conclusion the session holds but has
124
+ # not written into the state file is not held by the company. Measured cost of
125
+ # the unreconciled case: a finished step the orchestration loop went on trying
126
+ # to spawn 70 times, because only the report knew it was done.
127
+ PROGRESS_FILE="$PROJECT_ROOT/.harness/progress.json"
128
+ if [ -f "$PROGRESS_FILE" ]; then
129
+ while IFS=$'\t' read -r wname wreport; do
130
+ [ -n "$wname" ] && [ -n "$wreport" ] || continue
131
+ report_abs="$wreport"
132
+ case "$wreport" in /*) ;; *) report_abs="$PROJECT_ROOT/$wreport" ;; esac
133
+ [ -f "$report_abs" ] || continue
134
+ # Section-scoped reading: the Status body may be quoted or indented.
135
+ if awk '
136
+ /^[[:space:]]*>?[[:space:]]*##[[:space:]]+Status[[:space:]]*$/ { inb=1; next }
137
+ inb && /^[[:space:]]*>?[[:space:]]*#/ { inb=0 }
138
+ inb && /COMPLETE/ { found=1 }
139
+ END { exit(found ? 0 : 1) }
140
+ ' "$report_abs" 2>/dev/null; then
141
+ violations+=("state-file:worker-$wname-reported-COMPLETE-but-progress.json-still-running")
142
+ fi
143
+ done < <(jq -r '
144
+ [.company_state.workers[]? | select((.status // "") | test("running|busy"))
145
+ | [(.name // .worker // .feature // "unknown"), (.report // .report_path // .path // "")]]
146
+ | .[] | @tsv' "$PROGRESS_FILE" 2>/dev/null)
147
+ fi
148
+
123
149
  if [ "${#violations[@]}" -eq 0 ]; then
124
150
  if [ "$mode" = "json" ]; then
125
151
  jq -nc '{ok:true, violations:[]}'