@gobing-ai/spur 0.3.76 → 0.3.77

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.
@@ -39,7 +39,8 @@ sinks; this skill owns the protocol.
39
39
  testee (a /sp:... command, Skill(...), or shell CLI invocation)
40
40
  → PLAN classify + derive steps + open dual artifacts (live + docs/dogfood) with status:running
41
41
  → EXECUTE run each step as a user; on failure, bounded diagnose→fix→re-run (or observe-only)
42
- → MONITOR dual-write ledger row to disk on every step resolve never reconstruct from memory
42
+ → MONITOR live-ledger row to disk every step resolve; mirror frozen inside a proof window —
43
+ never reconstruct from memory
43
44
  → REPORT finalize-or-abort (non-skippable): status complete|aborted, Cost block, both paths, footer
44
45
  ```
45
46
 
@@ -105,9 +106,8 @@ The command forwards these via `$ARGUMENTS`:
105
106
  ```
106
107
 
107
108
  - Exit **2** → print the stdout refuse line and **stop** (do not plan). The CLI refuses on either
108
- of two independent mutation sources (task 0293); print whichever refuse message it emits:
109
- - pipeline-driving: `⚠ pipeline-driving testee detected; pass --max-retry 0 (observe-only) or --max-retry N (fix mode, tree mutation acknowledged)`.
110
- - mutating `--fix`: `⚠ mutating --fix mode detected (--fix all | --fix blockers-first); pass --max-retry 0 (observe-only for the driver; the testee still mutates the tree) or --max-retry N (fix mode, driver + testee both mutate)`.
109
+ of two independent mutation sources (task 0293); its two refuse lines are the ones quoted
110
+ verbatim in the repo-mutation warning above.
111
111
  - Exit **0** → proceed. Do not auto-substitute `--max-retry 0`.
112
112
  - The matcher contract is unit-checked by `tests/dogfood-testing/pipeline-detect.test.ts`.
113
113
  See [§Pipeline-driving word-boundary contract](#pipeline-driving-word-boundary-contract) and
@@ -186,17 +186,18 @@ report — the report is assembled from the files, not from memory.
186
186
  On **every** step resolve:
187
187
 
188
188
  1. Append/update the ledger row on the **live** file first.
189
- 2. Mirror the same row to the **report** path under `docs/dogfood/`.
189
+ 2. Mirror the same row to the **report** path under `docs/dogfood/` — unconditional **outside** a
190
+ pipeline proof window; **inside** one the mirror stays **frozen** (live rows only) until the
191
+ window closes, then sync/validate with live-based recovery (task 0804 R2 —
192
+ [monitor-ledger.md](references/monitor-ledger.md) live-ledger rule 3).
190
193
  3. Do **not** batch rows until Phase 4.
191
194
 
192
- The final report MUST include a `### 3. Monitor Ledger` section containing those rows, and the
193
- ledger's data-row count MUST equal the `**Steps:** N derived, N executed` declared in §2 of the report (N/A steps
194
- documented explicitly as rows) the cardinality rule in
195
- [monitor-ledger.md](references/monitor-ledger.md). Full
196
- methodology, column contract, token/cache estimation, multi-source Cost honesty, the cache-health
197
- finding rule, and the **cache-conservation discipline** live in
198
- **[monitor-ledger.md](references/monitor-ledger.md)**. Apply the conservation discipline while
199
- monitoring — low cache% is usually the driver re-fetching data it already holds.
195
+ The final report MUST include a `### 3. Monitor Ledger` section containing those rows (cardinality:
196
+ row count == the declared executed steps). Cardinality, full methodology, column contract,
197
+ token/cache estimation, multi-source Cost honesty, the cache-health finding rule, and the
198
+ **cache-conservation discipline** live in
199
+ **[monitor-ledger.md](references/monitor-ledger.md)** apply conservation while monitoring; low
200
+ cache% is usually the driver re-fetching data it already holds.
200
201
 
201
202
  ## Phase 4 — Report (finalize-or-abort — non-skippable)
202
203
 
@@ -214,8 +215,9 @@ contract violation**.
214
215
  declared in §2 (N/A steps documented explicitly as rows). A mismatch refuses `complete`.
215
216
  4. Write the **Cost** block under §2 (ledger `~estimate` + Method + confidence; `Meter: n/a` or
216
217
  optional ccusage/agent usage when real). For any `chained:<step>` ledger row whose meter is not
217
- observable, Fresh/Cached MUST be `~unknown` (or Cached `~0` with Basis `unobservable`) **and**
218
- emit finding `P3 chained-step cost not observable` — never invent chained totals.
218
+ observable, Fresh/Cached MUST be `~unknown` excluded from the cache% aggregate (or surfaced as
219
+ a separate unknown bucket), never counted as `Cached ~0` **and** emit finding
220
+ `P3 — chained-step cost not observable`; never invent chained totals.
219
221
  5. **R2 drift check at finalize.** If a workspace fingerprint was recorded in Phase 1, re-take
220
222
  the snapshot and diff against baseline minus the run's own touched files. If drift is detected,
221
223
  append a `drift:external` warning row to the ledger and emit a mandatory P2 report finding
@@ -292,13 +294,15 @@ Do **not** use this skill for:
292
294
  mutating pipeline — pass `--max-retry 0` first and inspect the findings before letting it apply
293
295
  fixes.
294
296
  2. **The ledger is live on disk, not reconstructed.** Honest fixed-vs-unresolved accounting depends
295
- on dual-writing each step *as it happens* to both artifacts. Reconstructing at the end produces
296
- fiction. Working-memory-only ledgers are a contract violation.
297
+ on writing each step *as it happens* to the live file (mirror per Phase 3 — frozen inside a proof
298
+ window). Reconstructing at the end produces fiction. Working-memory-only ledgers are a contract
299
+ violation.
297
300
  3. **A hiding fix is a finding.** If "fixing" a step would mask the bug, log it as a finding and
298
301
  leave the step unresolved.
299
302
  4. **Token numbers are estimates, but cache math is not free-form.** A skill cannot read its own
300
303
  exact token meter, so label numbers `~estimate` and put Method + confidence in the Cost block;
301
- however, cache% must be recomputable from Monitor Ledger row sums. Never invent or reuse a fixed
304
+ however, cache% must be recomputable from Monitor Ledger row sums of observable rows (`~unknown`
305
+ rows are excluded from the aggregate, never counted as `~0` cached). Never invent or reuse a fixed
302
306
  percentage. Optional meters (`ccusage`, agent usage) are session/day scope — never fake per-step.
303
307
  5. **Testee-scoped `--agent`.** Don't confuse the driver agent (always current) with the testee
304
308
  agent (the forwarded value).
@@ -431,11 +435,10 @@ baseline is drift.
431
435
  ### Worktree advisory (mutating dogfoods)
432
436
 
433
437
  For fix-mode dogfoods of **pipeline-driving** or **mutating-`--fix`** testees (the two refuse-gate
434
- cases above), the §Mutating `--fix` mode contract recommends running the dogfood in an **isolated
435
- `git worktree`** so concurrent external writers cannot collide with the run. This is **advisory,
436
- not a hard gate** — the refuse-gate semantics from task 0293 are unchanged. A worktree removes the
437
- drift case entirely (no concurrent writer can reach the isolated checkout), which is why it is the
438
- preferred setup for mutating dogfoods where the operator cares about clean attribution.
438
+ cases above), run in an **isolated `git worktree`** **advisory, not a hard gate**. Phase 1 must
439
+ print this advisory whenever the driver or testee may mutate and the tree is dirty, **or** the
440
+ testee drives a pipeline (incl. observe-only over a mutating testee). Rationale, trigger matrix
441
+ and wording: `references/monitor-ledger.md`.
439
442
 
440
443
  ## Step-splitting recipe (implement-heavy pipeline dogfoods)
441
444
 
@@ -486,8 +489,9 @@ Rules:
486
489
  2. When the chained step ran in a subagent or session whose usage data the driver cannot read, label
487
490
  the chained row `~unknown` and emit a **P3** finding: "chained-step cost not observable — candidate
488
491
  for surfacing subagent usage in the driver context." Do not invent a number.
489
- 3. The chained row still counts toward the aggregate cache% but mark it pessimistically
490
- (`Cached = ~0`) when the basis is missing, per the anti-fiction rule in
492
+ 3. An observable chained row counts toward the aggregate cache%. A `~unknown` chained row is
493
+ **excluded** from the aggregate (or surfaced as a separate unknown bucket) never folded in as
494
+ `Cached = ~0` — per the anti-fiction rule in
491
495
  [monitor-ledger.md](references/monitor-ledger.md).
492
496
 
493
497
  ## `--next` chain stop-at-testing
@@ -518,18 +522,17 @@ Do NOT:
518
522
  driver permission to read the chained leg's named artifacts (`.spur/run/<wbs>-verdict.json`,
519
523
  task-file section diffs, review tables) after the leg completes and attribute normally. The flag
520
524
  licenses **reading** chained-leg evidence that already exists — it does NOT license the driver to
521
- execute the chained leg itself. The legacy "operator may direct" prose direction is still honored
522
- for back-compat; `--chain-follow` is the explicit, machine-recognizable form. Omitting the flag
523
- keeps stop-at-testing as the **default**. The flag is a driver attribute only — it does not change
524
- `detect-pipeline-driving` gate semantics (it is not a testee mutation source). See
525
- [§Arguments](#arguments).
525
+ execute the chained leg itself. The legacy "operator may direct" prose direction stays honored for
526
+ back-compat; omitting the flag keeps stop-at-testing as the **default**. The flag is a driver
527
+ attribute only it does not change `detect-pipeline-driving` gate semantics (it is not a testee
528
+ mutation source). See [§Arguments](#arguments).
526
529
 
527
530
  ## Additional Resources
528
531
 
529
- - [references/report-template.md](references/report-template.md) — the report section contract +
530
- mandatory summary footer + task-sink L3 rule.
531
- - [references/monitor-ledger.md](references/monitor-ledger.md) — the live-ledger column contract,
532
- token/cache estimation heuristic, and the cache-health finding rule.
532
+ - [references/report-template.md](references/report-template.md) — report section contract,
533
+ mandatory footer, task-sink L3 rule.
534
+ - [references/monitor-ledger.md](references/monitor-ledger.md) — live-ledger column contract,
535
+ token/cache estimation, cache-health finding rule.
533
536
 
534
537
  ## Platform Notes
535
538
 
@@ -565,8 +568,10 @@ shape just because `report-template.md` wasn't auto-loaded.
565
568
  - Report: `docs/dogfood/YYYY-MM-DD-<testee-slug>-dogfood.md`
566
569
 
567
570
  Both start with YAML frontmatter including `status: running | aborted | complete`, `run_id`,
568
- `protocol: sp:dogfood-testing@1.2`, and paths. Dual-write a ledger row to both files on every step
569
- resolve. On stop, set `status` to `complete` or `aborted` (finalize-or-abortnon-skippable).
571
+ `protocol: sp:dogfood-testing@1.2`, and paths. Write each ledger row to the live file on every step
572
+ resolve; mirror it to the report only outside a proof window frozen inside one, synced at
573
+ finalize (Phase 3). On stop, set `status` to `complete` or
574
+ `aborted` (finalize-or-abort — non-skippable).
570
575
 
571
576
  **The six mandatory section headings** (in order, each report MUST contain all six):
572
577
 
@@ -28,9 +28,14 @@ artifacts):
28
28
  1. **Open both artifacts in Phase 1**, before the first step runs (frontmatter `status: running` +
29
29
  empty ledger table in each).
30
30
  2. **Write a row the instant a step resolves** (pass, fixed, unresolved, or N/A) — not after the run.
31
- 3. **Dual-write every step:** append/update the row on the **live** file first, then mirror to the
32
- **report** path. Do not batch rows until Phase 4. If the report write fails, continue with live
33
- as SSOT, emit a P2 finding, and retry promote on finalize.
31
+ 3. **Dual-write every step EXCEPT inside a proof window (task 0804 R2).** Append/update the row
32
+ on the **live** file first; the live file is SSOT. While a pipeline proof window is open (from
33
+ the first proof capture until the final proof-sensitive action, including done/provenance
34
+ checks), the tracked report mirror stays **frozen**: append each observation to the live ledger
35
+ only, and sync/validate the mirror after the window closes (or after abort). Tracked reports are
36
+ proof-input fingerprint inputs — writing them mid-window churns the fingerprint and voids the
37
+ proof. If the report write fails at finalize, recover by recreating the mirror from the valid
38
+ live content and re-validate; missing live evidence cannot manufacture complete.
34
39
  4. **The report reads the on-disk ledger, not your memory.** Every number in the report traces to a
35
40
  ledger row on disk. If it is not in the ledger file, it does not go in the report.
36
41
  5. **Cardinality (@1.2).** The ledger's data-row count MUST equal the `**Steps:** N derived, N executed` declared
@@ -44,6 +49,19 @@ artifacts):
44
49
  a `FIXED` / `PASS` outcome — it is purely documentary. Cache columns carry `—` (not estimated).
45
50
  See [SKILL.md §Workspace-drift guard](../SKILL.md#workspace-drift-guard-r2--task-0296).
46
51
 
52
+ ### Worktree advisory — planning-time surfacing (task 0804 R5)
53
+
54
+ The worktree advisory (SKILL.md §Worktree advisory) is surfaced at planning time, not only when
55
+ drift is detected. Phase 1 prints it whenever the **driver or the testee may mutate** and the tree
56
+ is dirty (`git status --porcelain` non-empty) **or** the testee itself drives a pipeline —
57
+ including observe-only driver mode over a mutating testee. The advisory is **not a hard gate** (the
58
+ refuse-gate semantics from task 0293 are unchanged); the isolated checkout is simply the preferred
59
+ setup for mutating dogfoods because no concurrent writer can reach it, so attribution stays clean
60
+ — which is why the §Mutating `--fix` mode contract recommends it for those two refuse-gate cases.
61
+ Dirtiness alone is not proof of a concurrent writer and must not be reported as one; known
62
+ concurrent writes still follow the project's one-writer rule. A clean, read-only run adds no
63
+ warning.
64
+
47
65
  ### Fast-run exemption (task 0294 R6a)
48
66
 
49
67
  The per-step live-write mandate (rules 1–4) exists to bound information loss when a mid-run crash
@@ -81,7 +99,7 @@ in the report's §6 Findings (no exemption applies).
81
99
  | `Finding` | One-line finding surfaced at this step, or `—`. A finding does **not** change `Outcome`. |
82
100
  | `Fresh Tokens` | Estimated fresh context for the step. Prefix with `~`. |
83
101
  | `Cached Tokens` | Estimated reused context for the step. Prefix with `~`. |
84
- | `Cache %` | `Cached Tokens / (Fresh Tokens + Cached Tokens)`, rounded to the nearest whole percent. |
102
+ | `Cache %` | `Cached Tokens / (Fresh Tokens + Cached Tokens)`, rounded to the nearest whole percent. An `~unknown` row carries `—`, never `0%` — unknown basis is not an observed zero. |
85
103
  | `Basis` | Observable basis for the estimate: command output, prior file read reused, generated text, etc. |
86
104
  | `Wall-clock` | Elapsed time for the step. |
87
105
 
@@ -98,8 +116,10 @@ A skill **cannot read its own exact token meter** — derive an estimate and lab
98
116
  reused by reference in this step. Use the same `ceil(characters / 4)` basis and round to the
99
117
  nearest 100. Do not count fresh command output, newly read files, or regenerated scaffolding as
100
118
  cached.
101
- 3. Compute each row: `Cache % = round(Cached Tokens / (Fresh Tokens + Cached Tokens) * 100)`.
102
- 4. Compute the report aggregate from row sums:
119
+ 3. Compute each row: `Cache % = round(Cached Tokens / (Fresh Tokens + Cached Tokens) * 100)`. A row
120
+ whose basis is unknown carries `—`, never `0%`.
121
+ 4. Compute the report aggregate from **observable rows only** — `~unknown` rows are excluded from
122
+ both sums (or surfaced as a separate unknown bucket), never folded in as `Cached ~0`:
103
123
  `aggregate cache% = round(sum(Cached Tokens) / sum(Fresh Tokens + Cached Tokens) * 100)`.
104
124
 
105
125
  The **trend across runs** is the signal, not the absolute value: rising cache% = the testee is
@@ -132,8 +152,10 @@ step stays on the driver's own row.
132
152
  - Observable chained usage (subagent output in driver context, or the operator explicitly provided
133
153
  the artifact) → estimate Fresh/Cached from that output normally.
134
154
  - Unobservable chained usage (subagent ran in a different session, usage data never surfaced) →
135
- label Fresh `~unknown`, Cached `~0`, Basis `chained-leg usage not observable from driver`. **MUST**
136
- emit a P3 finding: `P3 chained-step cost not observable` (task 0278 R3). Do not invent totals.
155
+ label Fresh `~unknown`, Cached `~unknown`, Basis `chained-leg usage not observable from driver`,
156
+ and **exclude the row from the aggregate cache%** (or surface it as a separate unknown bucket)
157
+ unknown cache use is not an observed zero. **MUST** emit a P3 finding: `P3 — chained-step cost
158
+ not observable` (task 0278 R3). Do not invent totals.
137
159
 
138
160
  Never fold a chained row into the driver's row; the whole point of dogfooding a pipeline-driving
139
161
  testee is to see the testee's own cost separately from the driver's monitoring cost. See
@@ -142,8 +164,10 @@ testee is to see the testee's own cost separately from the driver's monitoring c
142
164
  ## Anti-fiction rule
143
165
 
144
166
  Never reuse a convenient cache percentage such as `45%` because it "feels right." A cache percentage
145
- is valid only when it can be recomputed from the ledger row sums. If the basis is missing, mark the
146
- row pessimistically (`Cached Tokens = ~0`) and explain the missing basis.
167
+ is valid only when it can be recomputed from the ledger row sums of observable rows. If the basis is
168
+ missing, mark the row `~unknown`, exclude it from the aggregate (or surface it as a separate unknown
169
+ bucket), and explain the missing basis — never fold it in as `Cached Tokens = ~0`: unknown cache use
170
+ is not an observed zero-percent hit rate, and a low-cache diagnosis needs observed data.
147
171
 
148
172
  ## Cache-health finding rule
149
173
 
@@ -27,7 +27,7 @@ Every dogfood run **always** writes **two** files — with or without `--save`:
27
27
  | Artifact | Path | Role |
28
28
  | ---------- | ------ | ------ |
29
29
  | **Live** | `.spur/run/dogfood/<run_id>.md` | Mid-run SSOT; opened in Phase 1; ledger rows appended on every step resolve |
30
- | **Report** | `docs/dogfood/YYYY-MM-DD-<testee-slug>-dogfood.md` | Operator artifact; same content promoted on open + every step + finalize |
30
+ | **Report** | `docs/dogfood/YYYY-MM-DD-<testee-slug>-dogfood.md` | Operator artifact; same content promoted on open + every step + finalize — **except inside a pipeline proof window (task 0804 R2): the mirror stays frozen (live ledger only) until the window closes, then sync/validate, recovering from live if the write failed** — [monitor-ledger.md](monitor-ledger.md) → live-ledger rule 3 |
31
31
 
32
32
  `--save` is **back-compat no-op** for delivery: it still documents/prints the report path but is
33
33
  **not required** to create the file. A run that ends with no file under `docs/dogfood/` (and no live
@@ -158,14 +158,17 @@ it is the audit trail for step outcomes, fix attempts, findings, and cache math.
158
158
  |------|----------|---------|-------------|---------|--------------|---------------|---------|-------|------------|
159
159
  | resolve | 1 | PASS | — | — | ~800 | ~300 | 27% | 1 command + reused task summary | ~3s |
160
160
 
161
- **Cache calculation:** aggregate cache% = round((sum(Cached Tokens) / sum(Fresh Tokens + Cached Tokens)) * 100).
161
+ **Cache calculation:** aggregate cache% = round((sum(Cached Tokens) / sum(Fresh Tokens + Cached Tokens)) * 100),
162
+ computed over **observable rows only** — `~unknown` rows are excluded from both sums (or surfaced as
163
+ a separate unknown bucket), never counted as `~0` cached.
162
164
  ```
163
165
 
164
166
  Ledger rules:
165
167
 
166
168
  - Every executed step gets exactly one row, recorded when the step resolves (**on disk**, both files).
167
- - `Fresh Tokens` and `Cached Tokens` must be numbers with `~` prefixes; `Cache %` must be computed
168
- from those two cells, not guessed.
169
+ - `Fresh Tokens` and `Cached Tokens` must be `~`-prefixed numbers, or `~unknown` when the basis is
170
+ unobservable (that row is then excluded from the aggregate, never counted as `~0` cached);
171
+ `Cache %` must be computed from those two cells, not guessed.
169
172
  - `Basis` is mandatory. It names the observable inputs used for the estimate: command output,
170
173
  previously-read file reused from context, generated report text, or similar.
171
174
  - The aggregate cache line in `#### Cost` under §2 must equal the ledger formula above. If it
@@ -173,8 +176,9 @@ Ledger rules:
173
176
  - **Cardinality (@1.2):** the number of ledger data rows MUST equal the `**Steps:** N derived, N executed`
174
177
  declared in §2. Steps marked N/A are documented explicitly as their own rows (`Outcome: N/A`);
175
178
  an unaccounted step or an extra row refuses `status: complete` at finalize.
176
- - If the driver cannot make a defensible estimate for a row, write `~0` cached and explain the
177
- missing basis in `Basis`; do not invent a stable percentage.
179
+ - If the driver cannot make a defensible estimate for a row, write `~unknown`, exclude it from the
180
+ aggregate cache% (or surface it as a separate unknown bucket), and explain the missing basis in
181
+ `Basis`; do not fold it in as `~0` cached or invent a stable percentage.
178
182
 
179
183
  ### 4. What We Did
180
184
 
@@ -48,11 +48,28 @@ command, skill, script, or second workflow.
48
48
  chooses the subprocess workflow path.
49
49
  2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
50
50
  `.spur/run/`, and use `.spur/run/<run-id>.log` as the run log.
51
- 3. Resolve the host session id from `.spur/context/.session.json`, accepting the normalized hook key
51
+ 3. **Authoritative run identity (task 0804 R1, fail-closed).** Persist the run row through the
52
+ internal delegate before any stage executes — this is what makes bound `run.artifact` record
53
+ accept the inline run (0785 R3):
54
+
55
+ ```bash
56
+ SETUP_SCRIPT="plugins/sp/scripts/inline-run-setup.ts";
57
+ [ -f "$SETUP_SCRIPT" ] || SETUP_SCRIPT="$(superskill script path sp inline-run-setup.ts 2>/dev/null)";
58
+ [ -n "$SETUP_SCRIPT" ] && [ -f "$SETUP_SCRIPT" ] && \
59
+ bun "$SETUP_SCRIPT" --run-id "$RUN_ID" --file <selected-pipeline-yaml> \
60
+ || { echo "inline run setup failed closed — checker not found; run 'superskill install sp'" >&2; exit 1; }
61
+ ```
62
+
63
+ The delegate resolves the app service from the SPUR_BIN chain, creates-or-attaches the row,
64
+ and writes `.spur/run/<run-id>-inline-setup.json`. Seed `__runId` and `__definitionDigest`
65
+ from that file so proof capture and bound registration verify against the persisted identity.
66
+ A non-zero exit (missing row identity, changed definition, bundle-only install) stops the run —
67
+ never continue unbound and never fabricate a PASS.
68
+ 4. Resolve the host session id from `.spur/context/.session.json`, accepting the normalized hook key
52
69
  `session` and the Codex key `session_id` (in that order). If neither is available, allocate
53
70
  `host-session-<run-id>` and record that fallback in the log; provenance must never be blank or
54
71
  guessed from an executor subprocess.
55
- 4. Render the two-layer plan into the host todo list (task 0596):
72
+ 5. Render the two-layer plan into the host todo list (task 0596):
56
73
  - **Layer 1** = `spur workflow show <pipeline-yaml> --format todo --json` → its `steps[]`: the
57
74
  declared state inventory in declaration order with `initial` / `terminal` / `failure` /
58
75
  `pause` / `loopBack` / `conditional` markers. Mark the active state. Never re-derive this
@@ -69,7 +86,7 @@ command, skill, script, or second workflow.
69
86
  ended 0/11 with precheck and implement still open).
70
87
  - **Source of truth** = the CLI projection for layer 1; the YAML parsed in step 1 for layer 2.
71
88
  Never hand-copy or hand-derive the state list into the driver, a command, a skill, or a script.
72
- 5. For task execution only, record lifecycle provenance before entering the FSM:
89
+ 6. For task execution only, record lifecycle provenance before entering the FSM:
73
90
 
74
91
  ```bash
75
92
  spur task run-link <wbs> --source inline-full --run-id <run-id> --json
@@ -101,6 +118,16 @@ Action semantics come from the YAML and the workflow action contract:
101
118
  `answerFile`; assert `expectFile`; enforce `requireDiff` against a pre-action git snapshot,
102
119
  including the task-scope guard; honor declared error policy. `timeoutMs` is recorded as not
103
120
  applicable because the host session has no independent kill boundary.
121
+ - `run.artifact` — the engine's ledger registration has **no inline execution surface** (0808 R4).
122
+ The inline equivalent is a documented **registration-equivalent convention**: before the record
123
+ state mutates the task, the host validates the same refusal conditions inline — the declared
124
+ artifact exists at the resolved path and is canonical-valid for the run's wbs (for
125
+ `verify-verdict`: verdict `PASS`), `proofBinding: current` is honored against a freshly captured
126
+ proof digest, and the run-scoped review-completion marker exists — then appends one provenance
127
+ line to `.spur/run/<run-id>.log` naming the equivalence (artifact kind, path, verdict, digest) and
128
+ proceeds to `spur task record`. A failed validation stops at the state and follows the failure
129
+ contract; the step is never silently skipped. Artifact-provenance consumers read that run-log
130
+ line on the inline path — there is no ledger row.
104
131
 
105
132
  **Native-subagent dispatch (R2 eligibility, evaluated before each action):**
106
133
 
@@ -204,10 +231,11 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
204
231
  and `L3.required-section-placeholder` before the transition, not after.
205
232
  3. **Solution change-map anchor rule (L4.anchor-subject-mismatch).** A Solution change-map table must
206
233
  list **one `file:line` per row**. A ·-joined paragraph makes every anchor's "subject" the other
207
- anchors and trips the L4 subject check. Paths containing `_` (e.g. `docs/help/cmd_*.md`,
208
- `spur-cli-matrix.md`) can **never** match their cited line the snake_case filename token is
209
- extracted as the subject and cannot appear in the line content so drop those rows from the table
210
- (prose still covers them).
234
+ anchors and trips the L4 subject check. Since 0804 R9, subject extraction ignores complete parsed
235
+ citation spans, so a path's underscores no longer manufacture a subject: an underscore path row
236
+ (`docs/help/cmd_example.md:12`) is checked exactly like any other rowcite an **existing file**
237
+ with a **valid line or line range** whose content names the requirement's subject. A real absent
238
+ symbol, nonexistent file or invalid range still reports; never replace a citable row with prose.
211
239
 
212
240
  ## Failure contract
213
241