muse-crew 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1280 @@
1
+ # Decision history: publish path
2
+
3
+ Relocated from workflow source comments during H5 (2026-09-18). The workflows keep only the relied-upon invariant inline; the full decision history lives here.
4
+
5
+ <a id="fire-and-forget-trigger"></a>
6
+ ## Fire-and-forget trigger + workflow-owned observation
7
+
8
+ Invariant: the trigger child returns immediately; the workflow owns observation and verdict — never trust the child's self-report.
9
+
10
+ Applies to: standard, bugfix, chore.
11
+
12
+ ```
13
+ // Fire-and-forget trigger + workflow-owned observation (2026-09-16,
14
+ // clean-room task e2a8d9f8): the trigger's JSON closeout contract
15
+ // traveled over the stochastic text channel, and the runtime's
16
+ // JSON-candidate heuristic misfired on it ("workflow agent output
17
+ // was not JSON: no JSON object or array found in final response"),
18
+ // parking a task whose edit may have gone through. The contract's
19
+ // content was already observation-only (the applied report never
20
+ // gated; the pre_hashes were diagnostic-only), so the contract is
21
+ // removed: the trigger carries NO schema and its return value is
22
+ // never consumed, which takes the extraction heuristic out of this
23
+ // call entirely. The workflow attributes the edit itself through
24
+ // the tiny schema'd reads below — no prose is parsed for the
25
+ // trigger outcome.
26
+ // (Probe, 2026-09-16: the workflow scope exposes only agent() —
27
+ // tool_search, artifact_edit and artifact_status are undefined
28
+ // there — so the workflow cannot call the artifact tools directly;
29
+ // observation still goes through minimal child calls with tiny
30
+ // schemas, never a broad JSON contract.)
31
+ //
32
+ // Pre-trigger toolcheck (tiny, schema'd): the artifact namespace is
33
+ // deferred for workflow children — the child self-loads it and emits
34
+ // one exact signal line, read mechanically (never English prose).
35
+ // Only a parsed ARTIFACT_TOOLS: missing signal is explicit negative
36
+ // evidence: it gets one bounded retry with a fresh key, then parks
37
+ // rejected — without the tools the edit provably did NOT go through,
38
+ // so this is the one safe retry on the publish path. A throw (or an
39
+ // unparseable signal) is INCONCLUSIVE transport noise, never
40
+ // evidence of missing tools (2026-09-16, critic finding 3): it is
41
+ // recorded, it retries once in case the flake clears, but it can
42
+ // never take the rejected path.
43
+ ```
44
+
45
+ <a id="publish-attempt-ledger"></a>
46
+ ## Durable publish-attempt ledger
47
+
48
+ Invariant: every trigger outcome is recorded in the durable ledger while the trigger is in flight; the receipt poll reads the ledger, not the child's claims.
49
+
50
+ Applies to: standard, bugfix, chore.
51
+
52
+ ```
53
+ // Durable publish-attempt ledger (2026-09-12): every artifact publish
54
+ // attempt is recorded append-only at $CREW_HOME/.publish-ledger/<slug>.jsonl
55
+ // on persistent disk (NOT /tmp). The ledger is the correlation record for
56
+ // publish attempts whose outcome is UNKNOWN. When the rebuild trigger's
57
+ // child returns prose instead of JSON (structured-output failure), the edit
58
+ // may already have been accepted as pending_init — and artifact_status
59
+ // cannot see pending_init (diagnostic canary 2026-09-12: an edit accepted
60
+ // as pending_init was immediately followed by an all-false status check,
61
+ // and the old retry issued a DUPLICATE edit). "No build visible" is NOT
62
+ // evidence the edit did not go through, so the workflow never blind-retries
63
+ // on an unknown outcome: it records the attempt and parks fail-closed. A
64
+ // human or a later run correlates the accepted edit via the ledger (commit
65
+ // hash + attempt key + the artifact build's agent_id when one was observed)
66
+ // instead of guessing from a blind status poll.
67
+ // Best-effort observability: a failed write is logged loudly but never
68
+ // throws — the caller's park/proceed decision never depends on the ledger.
69
+ // Byte-identical across standard/bugfix/chore — pinned by
70
+ // tests/publish-ledger.test.js.
71
+ ```
72
+
73
+ <a id="pre-trigger-baseline"></a>
74
+ ## Pre-trigger build-state baseline
75
+
76
+ Invariant: snapshot the build state before the trigger; a build whose agent_id differs from the baseline is attributed to our edit.
77
+
78
+ Applies to: standard, bugfix, chore.
79
+
80
+ ```
81
+ // Pre-trigger build-state baseline (tiny, schema'd): one read of
82
+ // artifact_status. The post-trigger observation diffs against this
83
+ // baseline — a build whose agent_id was absent from (or differs
84
+ // from) the baseline is attributed to our edit; a build already in
85
+ // flight at baseline predates the trigger and is never attributed
86
+ // to it. If the baseline read itself fails, receipt attribution is
87
+ // skipped and the durable audit-dir evidence below is the only
88
+ // positive signal.
89
+ ```
90
+
91
+ <a id="deterministic-artifact-publish"></a>
92
+ ## Deterministic artifact publish
93
+
94
+ Invariant: the work agent never publishes; the parent runs the deterministic publish script and stamps the result.
95
+
96
+ Applies to: standard.
97
+
98
+ ```
99
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
100
+ // agent claimed "Rebuilt and deployed" while no build ran and no
101
+ // provenance was stamped — prose-trusted side effects, the same failure
102
+ // class as the npm double-skip (bb739316). The npm path already runs one
103
+ // deterministic script; the artifact path now has the same shape. Lock
104
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
105
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
106
+ // agent reports on the mechanical outcome and cannot skip what it never
107
+ // owned. Any step failing parks with an honest, step-specific reason
108
+ // (fail-closed). There is deliberately NO workflow-side provenance
109
+ // stamp: the builder's applied-report is circular (canary run 8,
110
+ // 2026-09-11), so the stamp moved to the parent — after the build
111
+ // lands, the workflow records the session completed and parks with
112
+ // "publish: verification-requested". The parent owns verification
113
+ // (docs/publish-verification.md); the primary sensor is the
114
+ // deterministic lib/readback-disk.js (the agent-callable read-back
115
+ // tool is unavailable — artifact_inspect was removed by the platform
116
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
117
+ // QA's provenance check enforces the stamp mechanically.
118
+ ```
119
+
120
+ Applies to: bugfix.
121
+
122
+ ```
123
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
124
+ // agent claimed "Rebuilt and deployed" while no build ran and no
125
+ // provenance was stamped — prose-trusted side effects, the same failure
126
+ // class as the npm double-skip (bb739316). The npm path already runs one
127
+ // deterministic script; the artifact path now has the same shape. Lock
128
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
129
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
130
+ // agent reports on the mechanical outcome and cannot skip what it never
131
+ // owned. Any step failing parks with an honest, step-specific reason
132
+ // (fail-closed). There is deliberately NO workflow-side provenance
133
+ // stamp: the builder's applied-report is circular (canary run 8,
134
+ // 2026-09-11), so the stamp moved to the parent — after the build
135
+ // lands, the workflow records the session completed and parks with
136
+ // "publish: verification-requested". The parent owns verification
137
+ // (docs/publish-verification.md); the primary sensor is the
138
+ // deterministic lib/readback-disk.js (the agent-callable read-back
139
+ // tool is unavailable — artifact_inspect was removed by the platform
140
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
141
+ // QA's provenance check enforces the stamp mechanically.
142
+ ```
143
+
144
+ Applies to: chore.
145
+
146
+ ```
147
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
148
+ // agent claimed "Rebuilt and deployed" while no build ran and no
149
+ // provenance was stamped — prose-trusted side effects, the same failure
150
+ // class as the npm double-skip (bb739316). The npm path already runs one
151
+ // deterministic script; the artifact path now has the same shape. Lock
152
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
153
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
154
+ // agent reports on the mechanical outcome and cannot skip what it never
155
+ // owned. Any step failing parks with an honest, step-specific reason
156
+ // (fail-closed). There is deliberately NO workflow-side provenance
157
+ // stamp: the builder's applied-report is circular (canary run 8,
158
+ // 2026-09-11), so the stamp moved to the parent — after the build
159
+ // lands, the workflow records the session completed and parks with
160
+ // "publish: verification-requested". The parent owns verification
161
+ // (docs/publish-verification.md); the primary sensor is the
162
+ // deterministic lib/readback-disk.js (the agent-callable read-back
163
+ // tool is unavailable — artifact_inspect was removed by the platform
164
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
165
+ // Chore has no QA: the parent's verification is the final gate.
166
+ ```
167
+
168
+
169
+ <a id="manual-inspector-fallback"></a>
170
+ ## Manual inspector fallback
171
+
172
+ Invariant: the LLM-inspector path is manual-fallback only; the mechanical path is primary.
173
+
174
+ Applies to: standard.
175
+
176
+ ```
177
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
178
+ // agent claimed "Rebuilt and deployed" while no build ran and no
179
+ // provenance was stamped — prose-trusted side effects, the same failure
180
+ // class as the npm double-skip (bb739316). The npm path already runs one
181
+ // deterministic script; the artifact path now has the same shape. Lock
182
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
183
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
184
+ // agent reports on the mechanical outcome and cannot skip what it never
185
+ // owned. Any step failing parks with an honest, step-specific reason
186
+ // (fail-closed). There is deliberately NO workflow-side provenance
187
+ // stamp: the builder's applied-report is circular (canary run 8,
188
+ // 2026-09-11), so the stamp moved to the parent — after the build
189
+ // lands, the workflow records the session completed and parks with
190
+ // "publish: verification-requested". The parent owns verification
191
+ // (docs/publish-verification.md); the primary sensor is the
192
+ // deterministic lib/readback-disk.js (the agent-callable read-back
193
+ // tool is unavailable — artifact_inspect was removed by the platform
194
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
195
+ // QA's provenance check enforces the stamp mechanically.
196
+ ```
197
+
198
+ Applies to: bugfix.
199
+
200
+ ```
201
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
202
+ // agent claimed "Rebuilt and deployed" while no build ran and no
203
+ // provenance was stamped — prose-trusted side effects, the same failure
204
+ // class as the npm double-skip (bb739316). The npm path already runs one
205
+ // deterministic script; the artifact path now has the same shape. Lock
206
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
207
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
208
+ // agent reports on the mechanical outcome and cannot skip what it never
209
+ // owned. Any step failing parks with an honest, step-specific reason
210
+ // (fail-closed). There is deliberately NO workflow-side provenance
211
+ // stamp: the builder's applied-report is circular (canary run 8,
212
+ // 2026-09-11), so the stamp moved to the parent — after the build
213
+ // lands, the workflow records the session completed and parks with
214
+ // "publish: verification-requested". The parent owns verification
215
+ // (docs/publish-verification.md); the primary sensor is the
216
+ // deterministic lib/readback-disk.js (the agent-callable read-back
217
+ // tool is unavailable — artifact_inspect was removed by the platform
218
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
219
+ // QA's provenance check enforces the stamp mechanically.
220
+ ```
221
+
222
+ Applies to: chore.
223
+
224
+ ```
225
+ // Deterministic artifact publish (canary b5efd1b1, 2026-09-10): the work
226
+ // agent claimed "Rebuilt and deployed" while no build ran and no
227
+ // provenance was stamped — prose-trusted side effects, the same failure
228
+ // class as the npm double-skip (bb739316). The npm path already runs one
229
+ // deterministic script; the artifact path now has the same shape. Lock
230
+ // refresh, rebuild trigger, build-completion poll, and post-deploy are
231
+ // narrow schema'd bookkeeping calls owned by the workflow — the work
232
+ // agent reports on the mechanical outcome and cannot skip what it never
233
+ // owned. Any step failing parks with an honest, step-specific reason
234
+ // (fail-closed). There is deliberately NO workflow-side provenance
235
+ // stamp: the builder's applied-report is circular (canary run 8,
236
+ // 2026-09-11), so the stamp moved to the parent — after the build
237
+ // lands, the workflow records the session completed and parks with
238
+ // "publish: verification-requested". The parent owns verification
239
+ // (docs/publish-verification.md); the primary sensor is the
240
+ // deterministic lib/readback-disk.js (the agent-callable read-back
241
+ // tool is unavailable — artifact_inspect was removed by the platform
242
+ // 2026-09-14 — so the LLM-inspector path is manual-fallback only).
243
+ // Chore has no QA: the parent's verification is the final gate.
244
+ ```
245
+
246
+
247
+ <a id="step-1c-no-stamp"></a>
248
+ ## STEP 1c: no provenance stamp
249
+
250
+ Invariant: no provenance stamp in STEP 1c; the stamp cannot certify content.
251
+
252
+ Applies to: standard.
253
+
254
+ ```
255
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
256
+ // (2026-09-11) proved the stamp cannot certify content: the
257
+ // builder's applied-report was derived from the carried diff, so
258
+ // the old report check was circular — a fabricated report
259
+ // passed by construction, and every phase went green on a hollow
260
+ // build. The stamp moves to the parent (docs/publish-verification.md);
261
+ // the deterministic lib/readback-disk.js is the primary sensor
262
+ // (the agent-callable read-back tool is unavailable —
263
+ // artifact_inspect was removed by the platform 2026-09-14 — so
264
+ // the LLM-inspector path is manual-fallback only), and the task
265
+ // parks for parent verification.
266
+ // QA's provenance check enforces the stamp mechanically.
267
+ // An unverified publish fails loudly in QA instead of passing
268
+ // silently here.
269
+ ```
270
+
271
+ Applies to: bugfix.
272
+
273
+ ```
274
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
275
+ // (2026-09-11) proved the stamp cannot certify content: the
276
+ // builder's applied-report was derived from the carried diff, so
277
+ // the old report check was circular — a fabricated report
278
+ // passed by construction, and every phase went green on a hollow
279
+ // build. The stamp moves to the parent (docs/publish-verification.md);
280
+ // the deterministic lib/readback-disk.js is the primary sensor
281
+ // (the agent-callable read-back tool is unavailable —
282
+ // artifact_inspect was removed by the platform 2026-09-14 — so
283
+ // the LLM-inspector path is manual-fallback only), and the task
284
+ // parks for parent verification.
285
+ // QA's provenance check enforces the stamp mechanically.
286
+ // An unverified publish fails loudly in QA instead of passing
287
+ // silently here.
288
+ ```
289
+
290
+ Applies to: chore.
291
+
292
+ ```
293
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
294
+ // (2026-09-11) proved the stamp cannot certify content: the
295
+ // builder's applied-report was derived from the carried diff, so
296
+ // the old report check was circular — a fabricated report
297
+ // passed by construction, and every phase went green on a hollow
298
+ // build. The stamp moves to the parent (docs/publish-verification.md);
299
+ // the deterministic lib/readback-disk.js is the primary sensor
300
+ // (the agent-callable read-back tool is unavailable —
301
+ // artifact_inspect was removed by the platform 2026-09-14 — so
302
+ // the LLM-inspector path is manual-fallback only), and the task
303
+ // parks for parent verification.
304
+ // Chore has no QA: the parent's verification is the final gate.
305
+ // An unverified publish fails loudly in QA instead of passing
306
+ // silently here.
307
+ ```
308
+
309
+
310
+ <a id="artifact-inspect-removed"></a>
311
+ ## artifact_inspect removed by platform
312
+
313
+ Invariant: artifact_inspect was removed by the platform 2026-09-14; the parent verifies content directly.
314
+
315
+ Applies to: standard.
316
+
317
+ ```
318
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
319
+ // (2026-09-11) proved the stamp cannot certify content: the
320
+ // builder's applied-report was derived from the carried diff, so
321
+ // the old report check was circular — a fabricated report
322
+ // passed by construction, and every phase went green on a hollow
323
+ // build. The stamp moves to the parent (docs/publish-verification.md);
324
+ // the deterministic lib/readback-disk.js is the primary sensor
325
+ // (the agent-callable read-back tool is unavailable —
326
+ // artifact_inspect was removed by the platform 2026-09-14 — so
327
+ // the LLM-inspector path is manual-fallback only), and the task
328
+ // parks for parent verification.
329
+ // QA's provenance check enforces the stamp mechanically.
330
+ // An unverified publish fails loudly in QA instead of passing
331
+ // silently here.
332
+ ```
333
+
334
+ Applies to: bugfix.
335
+
336
+ ```
337
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
338
+ // (2026-09-11) proved the stamp cannot certify content: the
339
+ // builder's applied-report was derived from the carried diff, so
340
+ // the old report check was circular — a fabricated report
341
+ // passed by construction, and every phase went green on a hollow
342
+ // build. The stamp moves to the parent (docs/publish-verification.md);
343
+ // the deterministic lib/readback-disk.js is the primary sensor
344
+ // (the agent-callable read-back tool is unavailable —
345
+ // artifact_inspect was removed by the platform 2026-09-14 — so
346
+ // the LLM-inspector path is manual-fallback only), and the task
347
+ // parks for parent verification.
348
+ // QA's provenance check enforces the stamp mechanically.
349
+ // An unverified publish fails loudly in QA instead of passing
350
+ // silently here.
351
+ ```
352
+
353
+ Applies to: chore.
354
+
355
+ ```
356
+ // STEP 1c (mechanical): NO provenance stamp here. Canary run 8
357
+ // (2026-09-11) proved the stamp cannot certify content: the
358
+ // builder's applied-report was derived from the carried diff, so
359
+ // the old report check was circular — a fabricated report
360
+ // passed by construction, and every phase went green on a hollow
361
+ // build. The stamp moves to the parent (docs/publish-verification.md);
362
+ // the deterministic lib/readback-disk.js is the primary sensor
363
+ // (the agent-callable read-back tool is unavailable —
364
+ // artifact_inspect was removed by the platform 2026-09-14 — so
365
+ // the LLM-inspector path is manual-fallback only), and the task
366
+ // parks for parent verification.
367
+ // Chore has no QA: the parent's verification is the final gate.
368
+ // An unverified publish fails loudly in QA instead of passing
369
+ // silently here.
370
+ ```
371
+
372
+
373
+ <a id="audit-dir-fallback"></a>
374
+ ## STEP 1b durable audit-dir fallback
375
+
376
+ Invariant: the audit-dir fallback uses the poll's own observations, not just its final verdict.
377
+
378
+ Applies to: standard, bugfix, chore.
379
+
380
+ ```
381
+ // STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
382
+ // the poll above only observes IN-FLIGHT builds. A build that
383
+ // finished between the receipt capture and the poll's first check
384
+ // leaves no in-flight trace — but the platform's audit harness
385
+ // leaves a durable one (~/workspace/ts-spaces/<slug>/audits/
386
+ // <timestamp>-<id>/ per completed build). Diff the audit-dir
387
+ // listing against the pre-trigger snapshot: a timestamped dir
388
+ // that appeared during the attempt window is evidence a build
389
+ // completed. Attribution is by window, not by build identity:
390
+ // the poll's saw_stranger signal only catches stranger builds in
391
+ // flight AT a check — a stranger that finished entirely inside
392
+ // the window is indistinguishable, so any observed stranger
393
+ // blocks attribution and the outcome stays unknown. This never
394
+ // re-issues the edit and never stamps provenance — ok=true only
395
+ // routes to the parent's independent content read-back, which
396
+ // remains the real verification.
397
+ //
398
+ // The poll end-state is read from the poll's own observations,
399
+ // not from build_done alone: a build in flight at the last check
400
+ // means the budget was shorter than the latency (or the build is
401
+ // stuck) — NOT that no build ever started; nothing observed at
402
+ // any check is the never-started signal.
403
+ ```
404
+
405
+ <a id="applied-report-gone"></a>
406
+ ## The builder's applied report is gone
407
+
408
+ Invariant: applied_report stays "missing-report" on issued triggers; null on pre-trigger/unattributed parks.
409
+
410
+ Applies to: standard, bugfix, chore.
411
+
412
+ ```
413
+ // The builder's applied report is gone (2026-09-16): it rode on the
414
+ // trigger's JSON closeout contract, which is removed below. The
415
+ // parent's independent read-back (docs/publish-verification.md) is
416
+ // the verification — this field stays "missing-report" on ledger
417
+ // lines for issued triggers; pre-trigger parks (toolcheck
418
+ // rejected/inconclusive) and unattributed-unknown parks write null
419
+ // (no trigger was observed, so there is nothing to report).
420
+ ```
421
+
422
+ <a id="h2-immediate-no-receipt"></a>
423
+ ## H2 verdict-first: immediate no-receipt fallback
424
+
425
+ Invariant: with durable audit evidence but no receipt agent_id, read the build report now and decide the verdict once via decidePublishVerdict — never poll blind on a null receipt.
426
+
427
+ Applies to: standard, bugfix, chore.
428
+
429
+ ```
430
+ // (2026-09-18, H2 verdict-first) Durable audit evidence exists,
431
+ // but there is no receipt agent_id to chain the completion poll
432
+ // to — polling with a null receipt can only observe strangers
433
+ // (any running build differs from "null") or nothing, burning
434
+ // 10.5 minutes to park unknown. Read the build report now instead
435
+ // of polling, then decide the verdict ONCE via
436
+ // decidePublishVerdict: exactly one new dir with ok=true lands
437
+ // (attribution by window, not identity — never poll blind);
438
+ // ok=false is UNKNOWN with the failure evidence preserved in the
439
+ // ledger detail (the evidence is explicit, the attribution is
440
+ // not); unreadable / zero / ambiguous dirs are UNKNOWN.
441
+ // Verdict-first: landed bypasses the poll below, unknown falls
442
+ // through to post-deploy. STEP 2 runs on every path.
443
+ ```
444
+
445
+ <a id="h2-verdict-dispatch"></a>
446
+ ## H2 verdict-first: verdict dispatch
447
+
448
+ Invariant: the verdict is decided exactly once; landed bypasses the receipt poll; an explicit publishFailure is preserved verbatim through post-deploy; otherwise the poll requires a real receipt (null-safe assertion records UNKNOWN and continues to STEP 2).
449
+
450
+ Applies to: standard, bugfix, chore.
451
+
452
+ ```
453
+ // Durable publish-attempt ledger: record the trigger outcome while the
454
+ // attempt key and commit are in scope. Every attempt lands here with
455
+ // its outcome — submitted, rejected, or unknown (unknown is recorded
456
+ // at the park site above). A later run or human matches commit hash +
457
+ // attempt key against the builder's eventual completion.
458
+ // (2026-09-16) The trigger is fire-and-forget: the observation above
459
+ // already recorded the ledger's submitted line on both positive paths
460
+ // and parked on unknown — there is no applied report to observe and
461
+ // no rejection signal to record.
462
+ // (2026-09-18, H2 verdict-first) The verdict was decided exactly
463
+ // once above; dispatch on it. landed bypasses the receipt poll
464
+ // (the audit evidence already proved completion); an explicit
465
+ // publishFailure is preserved verbatim through post-deploy.
466
+ // Otherwise the verdict is open — but the poll below is only
467
+ // legitimate against a real receipt: the null-safe assertion records
468
+ // UNKNOWN and continues to STEP 2 instead of polling blind.
469
+ ```
470
+
471
+ Root cause, fixed 2026-09-18: the immediate-landed path set
472
+ `skipReceiptPoll=true`, so the poll gate
473
+ `if (rebuildTrigger.edit_started && !skipReceiptPoll)` evaluated false —
474
+ and the code fell into an `else` the author had marked "Unreachable",
475
+ parking with 'not attributed' even though `publishBuildLanded` was already
476
+ true. The verdict the evidence had already set was overwritten by a branch
477
+ that claimed it couldn't happen. The fix decides the verdict exactly once
478
+ (above) and dispatches on it; the old "Unreachable" else is now a loud
479
+ defensive assertion that records UNKNOWN and continues to STEP 2 — it never
480
+ parks early and never polls blind.
481
+
482
+ <a id="diff-transport"></a>
483
+ ## Publish diff transport (file-backed)
484
+
485
+ Invariant: the diff travels git -> file -> deterministic script summary; the LLM never carries diff bytes; the compute script is pinned.
486
+
487
+ Applies to: standard, bugfix, chore.
488
+
489
+ ```
490
+ // Publish diff transport (room #14, 2026-09-17): the diff used to be
491
+ // ferried as a JSON string field in the agent's response — the agent
492
+ // produced a valid 700-line diff on disk but the JSON ferry dropped
493
+ // it, and the parse saw zero files ("Publish diff parsed to zero
494
+ // files"). The diff now travels git -> file -> deterministic script
495
+ // summary; the LLM never carries diff bytes. The agent is pure hands:
496
+ // it runs exactly one command (the PINNED compute-publish-diff.js —
497
+ // a mid-run release swap cannot change it under the workflow) and
498
+ // returns the small JSON summary verbatim. All fail-closed parks
499
+ // below are unchanged in meaning.
500
+ ```
501
+
502
+ <a id="chunk-containment"></a>
503
+ ## Poll-chunk exception containment
504
+
505
+ Invariant: a hung or failed chunk is inconclusive, never terminal — record it and continue; fail-closed still applies after chunk 3 and the fallback are exhausted.
506
+
507
+ Applies to: standard, bugfix, chore.
508
+
509
+ ```
510
+ // A hung or failed chunk is inconclusive, never terminal:
511
+ // record it and continue to the next chunk. (2026-09-16,
512
+ // clean-room task 1febe8eb: the platform's 270s agent
513
+ // timeout killed chunk 2, which threw out of this loop —
514
+ // skipping chunk 3 AND the STEP 1b audit-dir fallback and
515
+ // parking on the exception path.) Fail-closed still applies
516
+ // after chunk 3 and the fallback are exhausted.
517
+ ```
518
+
519
+ <a id="poll-accumulators"></a>
520
+ ## STEP 1b poll-signal accumulators
521
+
522
+ Invariant: the poll's observations (saw-our-build, saw-stranger, last check) are OR-ed across all three chunks so a signal seen in any chunk survives the chunk boundary.
523
+
524
+ Applies to: standard, bugfix, chore.
525
+
526
+ ```
527
+ // STEP 1b poll-signal accumulators (2026-09-15, task aadeccc3):
528
+ // the durable audit-dir fallback below needs the poll's own
529
+ // observations, not just its final verdict — whether our build was
530
+ // ever seen, whether a stranger's build was ever in flight, and
531
+ // what the last check observed. OR-ed across all three chunks so
532
+ // a signal seen in any chunk survives the chunk boundary.
533
+ ```
534
+
535
+ <a id="no-evidence-unknown"></a>
536
+ ## No-evidence unknown (H2 verdict-first)
537
+
538
+ Invariant: no attributable build and no durable evidence proves nothing — verdict UNKNOWN via decidePublishVerdict, recorded in the ledger, fall through to post-deploy; never poll blind on a null receipt.
539
+
540
+ Applies to: standard, bugfix, chore.
541
+
542
+ ```
543
+ // No attributable build and no durable evidence — but that proves
544
+ // nothing (a fast-completing build can finish between polls, or
545
+ // the checks themselves failed). Verdict UNKNOWN via the same
546
+ // mapping: record it in the ledger and fall through to
547
+ // post-deploy. No retry: re-issuing the edit here duplicated it
548
+ // on 2026-09-12. Never poll blind on a null receipt.
549
+ ```
550
+
551
+ <a id="explicit-refusal"></a>
552
+ ## Explicit artifact refusal
553
+
554
+ Invariant: ARTIFACT_EDIT_REFUSED is conclusive negative evidence -> park rejected, skip polling; a missing/unparseable signal is NOT a refusal (stays unknown fail-closed).
555
+
556
+ Applies to: standard, bugfix, chore.
557
+
558
+ ```
559
+ // Explicit refusal (room #16 blocker 10): the child ends its turn
560
+ // with ARTIFACT_EDIT_REFUSED when artifact_edit explicitly refused.
561
+ // Conclusive negative evidence — the edit provably did NOT go
562
+ // through — so this parks rejected and skips observation polling.
563
+ // A missing/unparseable signal is NOT a refusal: it stays unknown
564
+ // and fail-closed below.
565
+ ```
566
+
567
+ <a id="refusal-signal"></a>
568
+ ## Explicit artifact refusal (signal format)
569
+
570
+ Invariant: a refusal is conclusive negative evidence; the signal must be the ENTIRE trimmed turn output — a child quoting instructions back in prose degrades to unknown fail-closed.
571
+
572
+ Applies to: standard, bugfix, chore.
573
+
574
+ ```
575
+ // Explicit artifact refusal (room #16 blocker 10, 2026-09-18): the rebuild
576
+ // trigger child ends its turn with `ARTIFACT_EDIT_REFUSED: <text>` when
577
+ // artifact_edit explicitly refuses the edit (e.g. the artifact does not
578
+ // exist). A refusal is conclusive negative evidence — the edit provably did
579
+ // NOT go through — distinct from an unconsumed trigger return (unknown).
580
+ // Pure — pinned byte-identical across standard/bugfix/chore.
581
+ // The signal must be the ENTIRE trimmed turn output (not a line within prose):
582
+ // the trigger child is instructed to end its turn with exactly this line and
583
+ // nothing else. A confused child quoting the instructions back in prose must
584
+ // NOT produce a conclusive negative — that degrades to unknown (fail-closed).
585
+ ```
586
+
587
+ <a id="provenance-refresh"></a>
588
+ ## Provenance refresh for self-publishes
589
+
590
+ Invariant: after a verified landed publish, refresh the provenance record's crew_release to the now-live release identity, preserving the existing source_commit.
591
+
592
+ Applies to: standard, bugfix, chore.
593
+
594
+ ```
595
+ // Provenance refresh for self-publishes (task 7946d2a4): the npm path
596
+ // installs and activates a new immutable release (crew-release.sh deploy
597
+ // swaps the `current` symlink inside publish-npm.sh) but never stamped
598
+ // the dashboard's provenance record — every crew release left
599
+ // crew_release pointing at a pruned release. After a verified landed
600
+ // publish, refresh the record's crew_release to the now-live release
601
+ // identity, preserving the existing source_commit (the dashboard
602
+ // artifact's build source — a crew-repo commit here would fail the
603
+ // dashboard QA source check).
604
+ ```
605
+
606
+ <a id="parent-owned-verification"></a>
607
+ ## Publish content verification — parent-owned
608
+
609
+ Invariant: the parent owns content verification (docs/publish-verification.md); a skipped or failed publish has nothing to verify.
610
+
611
+ Applies to: standard.
612
+
613
+ ```
614
+ // Publish content verification — parent-owned (docs/publish-verification.md).
615
+ // The old block read back the workflow's OWN provenance stamp and compared
616
+ // it to HEAD: that verifies the stamp, not the content. Canary run 8
617
+ // (2026-09-11) passed it with a hollow build — the stamp was honest, the
618
+ // artifact was stale, all eight phases green. The stamp now moves to the
619
+ // parent (docs/publish-verification.md); the independent read-back step
620
+ // is currently unavailable (no agent-callable read-back tool exists —
621
+ // artifact_inspect was removed by the platform 2026-09-14), so the parent
622
+ // cannot confirm content and the task parks for verification. QA's
623
+ // provenance check enforces the stamp — an unverified publish fails loudly
624
+ // there instead of passing silently here.
625
+ // Skip-aware (park 2026-09-11): an empty-diff Integrate takes no merge
626
+ // lock, and the deterministic publish path skips rebuild/stamp entirely —
627
+ // there is no new content to verify, so verification is vacuous.
628
+ // publishSkippedNoLock is workflow-computed state from the explicit
629
+ // lock-status read in STEP 0, not agent prose.
630
+ // The parent (tick worker) triggers the ONE read-back inspection it can
631
+ // actually receive (async results go to the root agent, never into a
632
+ // workflow run — a workflow-side trigger would be an orphan). The workflow
633
+ // only parks; the parent's scan builds the request deterministically via
634
+ // lib/build-readback-request.js and ferries the inspection.
635
+ // publishBuildLanded and publishSkippedNoLock are workflow-computed state;
636
+ // a skipped or failed publish has nothing to verify.
637
+ ```
638
+
639
+ Applies to: bugfix.
640
+
641
+ ```
642
+ // Publish content verification — parent-owned (docs/publish-verification.md).
643
+ // The old block read back the workflow's OWN provenance stamp and compared
644
+ // it to HEAD: that verifies the stamp, not the content. Canary run 8
645
+ // (2026-09-11) passed it with a hollow build — the stamp was honest, the
646
+ // artifact was stale, all eight phases green. The stamp now moves to the
647
+ // parent (docs/publish-verification.md); the independent read-back step
648
+ // is currently unavailable (no agent-callable read-back tool exists —
649
+ // artifact_inspect was removed by the platform 2026-09-14), so the parent
650
+ // cannot confirm content and the task parks for verification. QA's
651
+ // provenance check enforces the stamp — an unverified publish fails loudly
652
+ // there instead of passing silently here.
653
+ // Skip-aware (park 2026-09-11): an empty-diff Integrate takes no merge
654
+ // lock, and the deterministic publish path skips rebuild/stamp entirely —
655
+ // there is no new content to verify, so verification is vacuous.
656
+ // publishSkippedNoLock is workflow-computed state from the explicit
657
+ // lock-status read in STEP 0, not agent prose.
658
+ // The parent (tick worker) triggers the ONE read-back inspection it can
659
+ // actually receive (async results go to the root agent, never into a
660
+ // workflow run — a workflow-side trigger would be an orphan). The workflow
661
+ // only parks; the parent's scan builds the request deterministically via
662
+ // lib/build-readback-request.js and ferries the inspection.
663
+ // publishBuildLanded and publishSkippedNoLock are workflow-computed state;
664
+ // a skipped or failed publish has nothing to verify.
665
+ ```
666
+
667
+ Applies to: chore.
668
+
669
+ ```
670
+ // Publish content verification — parent-owned (docs/publish-verification.md).
671
+ // The old block read back the workflow's OWN provenance stamp and compared
672
+ // it to HEAD: that verifies the stamp, not the content. Canary run 8
673
+ // (2026-09-11) passed it with a hollow build — the stamp was honest, the
674
+ // artifact was stale, all eight phases green. The stamp now moves to the
675
+ // parent (docs/publish-verification.md); the independent read-back step
676
+ // is currently unavailable (no agent-callable read-back tool exists —
677
+ // artifact_inspect was removed by the platform 2026-09-14), so the parent
678
+ // cannot confirm content and the task parks for verification.
679
+ // Skip-aware (park 2026-09-11): an empty-diff Integrate takes no merge
680
+ // lock, and the deterministic publish path skips rebuild/stamp entirely —
681
+ // there is no new content to verify, so verification is vacuous.
682
+ // publishSkippedNoLock is workflow-computed state from the explicit
683
+ // lock-status read in STEP 0, not agent prose.
684
+ // The parent (tick worker) triggers the ONE read-back inspection it can
685
+ // actually receive (async results go to the root agent, never into a
686
+ // workflow run — a workflow-side trigger would be an orphan). The workflow
687
+ // only parks; the parent's scan builds the request deterministically via
688
+ // lib/build-readback-request.js and ferries the inspection.
689
+ // publishBuildLanded and publishSkippedNoLock are workflow-computed state;
690
+ // a skipped or failed publish has nothing to verify.
691
+ ```
692
+
693
+
694
+ <a id="already-merged-corrective"></a>
695
+ ## Already-merged corrective
696
+
697
+ Invariant: when Review rejects an empty branch but the work is already on main (workflow-verified sha), Wren must declare it — not re-implement or re-commit already-landed work. Scoped to the empty-branch rejection.
698
+
699
+ Applies to: standard, bugfix, chore.
700
+
701
+ ```
702
+ // Already-merged corrective (room #16 blocker 11): when Review rejected
703
+ // an empty branch but the work is already on main (the workflow verified
704
+ // the sha), Wren must declare it — not re-implement or re-commit
705
+ // already-landed work. Scoped to the empty-branch rejection; any other
706
+ // rejection already carries its own specific notes.
707
+ ```
708
+
709
+ <a id="merge-record-helpers"></a>
710
+ ## Merge-record helpers
711
+
712
+ Invariant: the integrate step records the exact merged commit in $CREW_HOME/.merge-records/<task_id> so a re-dispatched run can verify a landed merge and publish it even after the branch was reclaimed under an expired lease. All pure — no I/O, no clock.
713
+
714
+ Applies to: standard, bugfix, chore.
715
+
716
+ ```
717
+ // Merge-record helpers (merge-lease fix, 2026-09-12): the integrate step
718
+ // records the exact merged commit in $CREW_HOME/.merge-records/<task_id>,
719
+ // so a re-dispatched run can verify a landed merge and publish it even
720
+ // after the branch was reclaimed under an expired lease. All pure — no I/O,
721
+ // no clock. Byte-identical in standard.js, bugfix.js, chore.js.
722
+ ```
723
+
724
+ <a id="publish-diff-base"></a>
725
+ ## Publish diff base (BASE..HEAD)
726
+
727
+ Invariant: the carried diff is BASE..HEAD where BASE is the previously-stamped provenance source_commit — NOT HEAD^1; a push-time reconcile merge puts the task's own changes behind an intermediate merge, so HEAD^1..HEAD silently drops the fix. Empty tree only for a genuine first publish.
728
+
729
+ Applies to: standard, bugfix, chore.
730
+
731
+ ```
732
+ // Publish diff base (2026-09-14, task 0c53af4e): the carried diff is
733
+ // BASE..HEAD where BASE is the previously-stamped provenance
734
+ // source_commit — NOT HEAD^1. A push-time reconcile merge puts the
735
+ // task's own changes behind an intermediate merge, so HEAD^1..HEAD
736
+ // silently drops the task's fix while the artifact builds without
737
+ // it. The stamped base is the artifact's actual content; BASE..HEAD
738
+ // is the complete unpublished delta. Empty tree only for a genuine
739
+ // first publish (no provenance stamped yet).
740
+ ```
741
+
742
+ <a id="attribution-limitation"></a>
743
+ ## Attribution timing limitation
744
+
745
+ Invariant: attribution is timing-based — a stranger's build starting inside the trigger window is indistinguishable and would be misattributed; the consequence is bounded because the parent's mechanical content read-back certifies the exact commit's content. Timing narrows the candidate; content decides.
746
+
747
+ Applies to: standard, bugfix, chore.
748
+
749
+ ```
750
+ // Known limitation (failure-mode audit 2026-09-16): attribution
751
+ // is timing-based — any agent_id new relative to the baseline is
752
+ // treated as this edit's receipt. A stranger's build starting inside
753
+ // the trigger window is indistinguishable by timing and would be
754
+ // misattributed here. The consequence is bounded: the completion
755
+ // poll below tracks the recorded id, and the parent's mechanical
756
+ // content read-back (docs/publish-verification.md) certifies the
757
+ // exact commit's content — a wrong build's content fails closed as
758
+ // verification-failed, never stamped. Timing narrows the candidate;
759
+ // content decides.
760
+ ```
761
+
762
+ <a id="read-back-request"></a>
763
+ ## Publish read-back request (currently unavailable)
764
+
765
+ Invariant: no agent-callable read-back tool exists; verification parks at 'verification-requested'; independent read-back cannot be fabricated from the diff.
766
+
767
+ Applies to: standard.
768
+
769
+ ```
770
+ // Publish read-back request (agent path currently unavailable): the
771
+ // verbatim_request the parent protocol (docs/publish-verification.md) would
772
+ // hand to an independent read-back tool after the artifact build lands.
773
+ // artifact_inspect was removed by the platform (2026-09-14);
774
+ // artifact.inspect is malfunction diagnosis, not a substitute — so no
775
+ // agent-callable read-back tool exists and this LLM-inspector request
776
+ // cannot currently be issued. The primary sensor is now the deterministic
777
+ // lib/readback-disk.js (reads the on-disk tree the artifact is served
778
+ // from); this request builder is retained only as the manual fallback.
779
+ // Pure function — no I/O, no clock. The request carries the merged diff as the expected change and asks
780
+ // for an independent read of the artifact's actual source: for each file, the
781
+ // exact current text of the changed regions plus a per-line present/absent
782
+ // finding. Until a read-back path exists, the parent cannot independently
783
+ // confirm content and verification parks at "publish: verification-requested"
784
+ // (see docs/publish-verification.md). This preserves the circularity break
785
+ // that hollowed canary run 8 (2026-09-11): the old verifyAppliedChanges
786
+ // compared the builder's applied-report against the diff the report was
787
+ // derived from — a fabricated report passed by construction. The report
788
+ // itself is gone now (2026-09-16 fire-and-forget trigger). Independent
789
+ // read-back cannot be
790
+ // fabricated from the diff;
791
+ // it must match the artifact's real content.
792
+ ```
793
+
794
+ Applies to: bugfix.
795
+
796
+ ```
797
+ // Publish read-back request (currently unavailable): the verbatim_request
798
+ // the parent protocol (docs/publish-verification.md) would hand to an
799
+ // independent read-back tool after the artifact build lands. artifact_inspect
800
+ // was removed by the platform (2026-09-14); artifact.inspect is malfunction
801
+ // diagnosis, not a substitute — so no agent-callable read-back tool exists
802
+ // and this request cannot currently be issued. Pure function — no I/O, no
803
+ // clock. The request carries the merged diff as the expected change and asks
804
+ // for an independent read of the artifact's actual source: for each file, the
805
+ // exact current text of the changed regions plus a per-line present/absent
806
+ // finding. Until a read-back path exists, the parent cannot independently
807
+ // confirm content and verification parks at "publish: verification-requested"
808
+ // (see docs/publish-verification.md). This preserves the circularity break
809
+ // that hollowed canary run 8 (2026-09-11): the old verifyAppliedChanges
810
+ // compared the builder's applied-report against the diff the report was
811
+ // derived from — a fabricated report passed by construction. The report
812
+ // itself is gone now (2026-09-16 fire-and-forget trigger). Independent
813
+ // read-back cannot be
814
+ // fabricated from the diff; it must match the artifact's real content.
815
+ ```
816
+
817
+ Applies to: chore.
818
+
819
+ ```
820
+ // Publish read-back request (currently unavailable): the verbatim_request
821
+ // the parent protocol (docs/publish-verification.md) would hand to an
822
+ // independent read-back tool after the artifact build lands. artifact_inspect
823
+ // was removed by the platform (2026-09-14); artifact.inspect is malfunction
824
+ // diagnosis, not a substitute — so no agent-callable read-back tool exists
825
+ // and this request cannot currently be issued. Pure function — no I/O, no
826
+ // clock. The request carries the merged diff as the expected change and asks
827
+ // for an independent read of the artifact's actual source: for each file, the
828
+ // exact current text of the changed regions plus a per-line present/absent
829
+ // finding. Until a read-back path exists, the parent cannot independently
830
+ // confirm content and verification parks at "publish: verification-requested"
831
+ // (see docs/publish-verification.md). This preserves the circularity break
832
+ // that hollowed canary run 8 (2026-09-11): the old verifyAppliedChanges
833
+ // compared the builder's applied-report against the diff the report was
834
+ // derived from — a fabricated report passed by construction. The report
835
+ // itself is gone now (2026-09-16 fire-and-forget trigger). Independent
836
+ // read-back cannot be
837
+ // fabricated from the diff; it must match the artifact's real content.
838
+ ```
839
+
840
+
841
+ <a id="step-05-target-check"></a>
842
+ ## STEP 0.5: artifact target existence check
843
+
844
+ Invariant: assert the artifact target exists before any trigger; a missing target is conclusive negative evidence -> park rejected; an inconclusive check is fail-closed unknown.
845
+
846
+ Applies to: standard, bugfix, chore.
847
+
848
+ ```
849
+ // STEP 0.5 (mechanical, room #16 blocker 10): assert the artifact
850
+ // target exists before any artifact_status / artifact_edit call. The
851
+ // project was classified as an artifact surface (deploy_slug set),
852
+ // but setup never provisioned the artifact — Publish then entered
853
+ // the trigger path against a slug with no on-disk target and the
854
+ // edit failed opaquely ("web artifact <slug> was not found on
855
+ // disk"), which the ledger could only record as unknown. A missing
856
+ // target is conclusive negative evidence: the edit provably did NOT
857
+ // go through, so this parks rejected (not unknown) with the actual
858
+ // missing path — no trigger issued, no blind retry, no observation
859
+ // polling. The check is a pure filesystem stat; the path is
860
+ // workflow-computed, never agent prose. An inconclusive check
861
+ // (throw / unparseable signal) is fail-closed unknown: without
862
+ // proof the target exists, no edit is issued. The slug is
863
+ // interpolated into a shell command — a slug outside [a-zA-Z0-9_-]
864
+ // (e.g. from a hand-edited space.json) is treated as inconclusive
865
+ // rather than risking shell injection.
866
+ ```
867
+
868
+ <a id="no-builder-report"></a>
869
+ ## No builder report (fire-and-forget)
870
+
871
+ Invariant: the fire-and-forget trigger carries no JSON contract; the builder's old self-report was circular by construction with a demonstrated false-negative mode.
872
+
873
+ Applies to: standard, bugfix, chore.
874
+
875
+ ```
876
+ // (2026-09-16) There is no builder report: the fire-and-forget
877
+ // trigger carries no JSON contract, so there is nothing to
878
+ // compare and no pre-hash diagnostic. The builder's old
879
+ // self-report was circular by construction (canary run 8) with a
880
+ // demonstrated false-negative mode (task 23ca8f3f, 2026-09-12:
881
+ // applied:[] for a diff the builder had applied). The flow
882
+ // proceeds to the build poll regardless; real verification is the
883
+ // parent's independent read-back (docs/publish-verification.md)
884
+ // before the provenance stamp.
885
+ ```
886
+
887
+ <a id="durable-evidence-snapshot"></a>
888
+ ## Durable-evidence snapshot
889
+
890
+ Invariant: snapshot the audit-dir listing BEFORE the trigger; the fallback diffs before/after; if the snapshot fails, both fallback comparisons are disabled.
891
+
892
+ Applies to: standard, bugfix, chore.
893
+
894
+ ```
895
+ // Durable-evidence snapshot (2026-09-14): the observation below only
896
+ // detects IN-FLIGHT builds. A build that finished before the
897
+ // observation leaves no in-flight trace — but the platform's audit
898
+ // harness leaves a durable one:
899
+ // ~/workspace/ts-spaces/<slug>/audits/<timestamp>-<id>/ per
900
+ // completed build. Snapshot the listing BEFORE the trigger so the
901
+ // fallback can diff before/after: a directory appearing during the
902
+ // trigger window is positive evidence the edit went through and
903
+ // the build completed. Best-effort and non-gating: if the snapshot
904
+ // fails, auditBeforeOk stays false and BOTH fallback comparisons
905
+ // are disabled (2026-09-16, critic finding 4) — without a baseline,
906
+ // an empty before-list would make every historical audit dir look
907
+ // "new". No wall-clock in-script (deterministic replay) — the
908
+ // comparison is a pure before/after set diff.
909
+ ```
910
+
911
+ <a id="already-merged-hydration"></a>
912
+ ## Already-merged hydration
913
+
914
+ Invariant: when the run did not execute Build itself, recover the workflow-verified sha; the structured session field is read FIRST, the notes line is only a fallback (session notes are hard-capped at 3000 chars).
915
+
916
+ Applies to: standard, bugfix, chore.
917
+
918
+ ```
919
+ // Already-merged hydration: when this run did not execute Build itself
920
+ // (dispatcher resume at Review after a platform death between phases),
921
+ // recover the workflow-verified sha. Room #16 blocker 11: the structured
922
+ // session field is read FIRST — the `already_merged_verified:` notes line
923
+ // is only a fallback, because session notes are hard-capped at 3000
924
+ // chars and a truthful declaration at the report's tail was silently
925
+ // truncated. The structured value was written by the workflow after a
926
+ // mechanical ancestor check — it is trusted; the builder's bare
927
+ // declaration never is. Absent both, the mechanical fact below reads
928
+ // "none declared" and Cass fails closed. The hydration read is best-effort:
929
+ // a transport throw degrades to "none declared" rather than crashing Review.
930
+ ```
931
+
932
+ <a id="already-merged-declaration"></a>
933
+ ## Already-merged declaration
934
+
935
+ Invariant: when the builder correctly makes no commit because the deliverable is already on main, it declares repo_diff: none (already-merged: <sha>) naming the main commit that carries the work.
936
+
937
+ Applies to: standard, bugfix, chore.
938
+
939
+ ```
940
+ // Already-merged idempotency (canary 2026-09-15, task 1d692d91): when the
941
+ // builder correctly makes no commit because the deliverable is already on
942
+ // main (a prior merge or hand-repair landed it), it declares
943
+ // `repo_diff: none (already-merged: <sha>)` naming the main commit that
944
+ // carries the work. Room #16 blocker 11 (2026-09-18): the line anchor
945
+ // missed Wren's mid-paragraph declaration, and the persisted notes truncated
946
+ // the tail — so the anchor is gone and a sha followed by `)`, whitespace, or
947
+ // end-of-string (truncation) is accepted. The sha is hex-only (7-40 chars)
948
+ // so the workflow can interpolate it into the mechanical ancestor check
949
+ // without injection risk; a over-long hex run never matches (the lookahead
950
+ // fails on the extra hex char). Pure — pinned byte-identical across
951
+ // standard/bugfix/chore.
952
+ ```
953
+
954
+ <a id="step1-builder-source"></a>
955
+ ## STEP 1: builder source tree
956
+
957
+ Invariant: the builder's source tree is NOT the crew's repo; the merge diff is embedded in the edit request; the workflow verifies the report matches the diff BEFORE stamping provenance.
958
+
959
+ Applies to: standard, bugfix, chore.
960
+
961
+ ```
962
+ // STEP 1 (mechanical): carry the merged change to the artifact
963
+ // builder. The builder's source tree is NOT the crew's repo —
964
+ // canary run 4 (2026-09-11) proved it: Publish asked for "rebuild
965
+ // from current source. Do not modify any source files" and the
966
+ // builder rebuilt a stale copy predating the canary's changes, then
967
+ // the workflow stamped the new commit hash on the stale build.
968
+ // Provenance fiction; all eight phases passed. The merge diff is
969
+ // embedded in the edit request; the builder applies it to its own
970
+ // tree and reports the applied changes; the workflow verifies the
971
+ // report matches the diff BEFORE stamping provenance. A mismatch
972
+ // parks without stamping — the stamp must never certify a build
973
+ // whose content was not verified.
974
+ // Skipped entirely when no lock was held — nothing merged, nothing
975
+ // to ship.
976
+ ```
977
+
978
+ <a id="verification-park"></a>
979
+ ## Verification park
980
+
981
+ Invariant: the build landed but provenance is unstamped until parent verification.
982
+
983
+ Applies to: standard, bugfix, chore.
984
+
985
+ ```
986
+ // Publish verification park: the build landed and post-deploy finalized,
987
+ // but provenance is UNSTAMPED until the parent's independent read-back
988
+ // (docs/publish-verification.md) confirms the artifact's actual content
989
+ // matches the merged diff. The parent stamps provenance, then re-queues;
990
+ // the dispatcher resumes at QA, whose provenance check enforces the stamp
991
+ // mechanically. A failed Publish never reaches this park — it returned
992
+ // failed above and retries under the dispatcher's cap. The merge lock is
993
+ // already released (post-deploy), so the parked task holds no resources.
994
+ ```
995
+
996
+ <a id="pretrigger-baseline"></a>
997
+ ## Pre-trigger baseline
998
+
999
+ Invariant: a tiny schema'd baseline is captured before the trigger.
1000
+
1001
+ Applies to: standard, bugfix, chore.
1002
+
1003
+ ```
1004
+ // Pre-trigger build-state baseline (tiny, schema'd): one read of
1005
+ // artifact_status. The post-trigger observation diffs against this
1006
+ // baseline — a build whose agent_id was absent from (or differs
1007
+ // from) the baseline is attributed to our edit; a build already in
1008
+ // flight at baseline predates the trigger and is never attributed
1009
+ // to it. If the baseline read itself fails, receipt attribution is
1010
+ // skipped and the durable audit-dir evidence below is the only
1011
+ // positive signal.
1012
+ ```
1013
+
1014
+ <a id="agent-id-attribution"></a>
1015
+ ## Agent ID attribution
1016
+
1017
+ Invariant: the artifact build's agent_id is attributed to the edit call.
1018
+
1019
+ Applies to: standard, bugfix, chore.
1020
+
1021
+ ```
1022
+ // The artifact build's agent_id, attributed to this edit by the
1023
+ // workflow-owned observation below. The agent_id is the artifact
1024
+ // system's in-flight correlation ID (research 2026-09-12):
1025
+ // artifact.edit returns pending_init with NO agent_id, but
1026
+ // artifact_status exposes build.agent_id immediately after
1027
+ // acceptance, stable across polls. Recorded in the ledger so an
1028
+ // attempt correlates to the exact builder run; null when no build
1029
+ // was ever observed.
1030
+ ```
1031
+
1032
+ <a id="hung-chunk"></a>
1033
+ ## Hung chunk
1034
+
1035
+ Invariant: a hung or failed chunk is inconclusive, never terminal.
1036
+
1037
+ Applies to: standard, bugfix, chore.
1038
+
1039
+ ```
1040
+ // A hung or failed chunk is inconclusive, never terminal:
1041
+ // record it and continue to the next chunk. (2026-09-16,
1042
+ // clean-room task 1febe8eb: the platform's 270s agent
1043
+ // timeout killed chunk 2, which threw out of this loop —
1044
+ // skipping chunk 3 AND the STEP 1b audit-dir fallback and
1045
+ // parking on the exception path.) Fail-closed still applies
1046
+ // after chunk 3 and the fallback are exhausted.
1047
+ ```
1048
+
1049
+ <a id="no-attributable-build"></a>
1050
+ ## No attributable build
1051
+
1052
+ Invariant: no attributable build and no durable evidence means no publish.
1053
+
1054
+ Applies to: standard, bugfix, chore.
1055
+
1056
+ ```
1057
+ // No attributable build and no durable evidence — but that proves
1058
+ // nothing (a fast-completing build can finish between polls, or
1059
+ // the checks themselves failed). Verdict UNKNOWN via the same
1060
+ // mapping: record it in the ledger and fall through to
1061
+ // post-deploy. No retry: re-issuing the edit here duplicated it
1062
+ // on 2026-09-12. Never poll blind on a null receipt.
1063
+ ```
1064
+
1065
+ <a id="already-merged"></a>
1066
+ ## Already-merged attestation
1067
+
1068
+ Invariant: when the Build gate verifies already-merged, attestation is recorded.
1069
+
1070
+ Applies to: standard, bugfix, chore.
1071
+
1072
+ ```
1073
+ // Already-merged attestation: when the Build gate verified the builder's
1074
+ // already-merged declaration, the workflow records its own marker line in
1075
+ // the session notes (like the builder markers above, it is appended after
1076
+ // the slice so it can never be amputated). A later run resumed at Review
1077
+ // hydrates alreadyMergedSha from this workflow-attested line — never from
1078
+ // the builder's declaration alone.
1079
+ ```
1080
+
1081
+ <a id="explicit-refusal-16"></a>
1082
+ ## Explicit refusal
1083
+
1084
+ Invariant: the child must explicitly refuse artifact work.
1085
+
1086
+ Applies to: standard, bugfix, chore.
1087
+
1088
+ ```
1089
+ // Explicit refusal (room #16 blocker 10): the child ends its turn
1090
+ // with ARTIFACT_EDIT_REFUSED when artifact_edit explicitly refused.
1091
+ // Conclusive negative evidence — the edit provably did NOT go
1092
+ // through — so this parks rejected and skips observation polling.
1093
+ // A missing/unparseable signal is NOT a refusal: it stays unknown
1094
+ // and fail-closed below.
1095
+ ```
1096
+
1097
+ <a id="publish-verify"></a>
1098
+ ## Publish verification
1099
+
1100
+ Invariant: the agent cannot verify publish; the parent does.
1101
+
1102
+ Applies to: standard, bugfix, chore.
1103
+
1104
+ ```
1105
+ // Deterministic publish verification: the agent cannot self-certify a publish.
1106
+ // Skip-aware (park 2026-09-11): when the deterministic publish script found
1107
+ // no merge lock held (empty-diff Integrate), it skips the publish path
1108
+ // gracefully and emits the machine-readable PUBLISH_SKIPPED=no-lock-held
1109
+ // marker. The preflight (bugfix 2026-09-17) emits
1110
+ // PUBLISH_SKIPPED=no-npm-publish when npm publish is not configured on
1111
+ // this machine (helper or credential absent) — also before any mutation.
1112
+ // Verification is then vacuous — nothing was shipped, and the
1113
+ // registry must NOT have moved. The marker is script-emitted explicit state
1114
+ // (pasted verbatim per the Publish agent instructions), not agent prose; a
1115
+ // report without the marker still runs the full verification fail-closed.
1116
+ ```
1117
+
1118
+ <a id="integrate-verify"></a>
1119
+ ## Integrate verification
1120
+
1121
+ Invariant: the agent cannot verify integrate mechanically; the workflow checks the diff.
1122
+
1123
+ Applies to: standard, bugfix, chore.
1124
+
1125
+ ```
1126
+ // Deterministic integrate verification: the agent cannot self-certify a
1127
+ // merge. After the Integrate agent claims success, the workflow confirms
1128
+ // mechanically that the task branch tip is an ancestor of main via the
1129
+ // lifecycle script's verify-merge command (which resolves the branch
1130
+ // through the crew registry — never by reconstructing "task/"+taskId — so
1131
+ // the check cannot verify the wrong branch). The VERIFIED marker is matched
1132
+ // by regex on the script's own stdout; agent prose is never read. This
1133
+ // closes the hole where an agent reported "merged empty" while approved
1134
+ // commits were still stranded on the task branch (bug b1b1f919). A genuine
1135
+ // empty-diff Integrate (MERGED_EMPTY: no commits ahead of main) verifies
1136
+ // vacuously — the tip is then an ancestor of main. Verification failure is
1137
+ // an operational step failure, not a park: the dispatcher retries Integrate
1138
+ // under its consecutive-failure cap, and the retry finds the commits still
1139
+ // on the branch and performs the real merge — self-healing.
1140
+ ```
1141
+
1142
+ <a id="already-merged-idem"></a>
1143
+ ## Already-merged idempotency
1144
+
1145
+ Invariant: an already-merged repo_diff is idempotent; no rebuild.
1146
+
1147
+ Applies to: standard, bugfix, chore.
1148
+
1149
+ ```
1150
+ // Already-merged idempotency: a `repo_diff: none (already-merged:
1151
+ // <sha>)` declaration is verified mechanically — <sha> must resolve
1152
+ // and be an ancestor of main in the configured repo. A fabricated or
1153
+ // mistaken declaration fails the phase here (the dispatcher retries
1154
+ // Build under its consecutive-failure cap); a verified declaration is
1155
+ // recorded in alreadyMergedSha for Review's no-diff branch. Without
1156
+ // this guard, Build correctly doing nothing left Review with no
1157
+ // mechanical way to accept an empty diff, and Cass rejected for "no
1158
+ // commits ahead of main — the builder likely forgot to commit" while
1159
+ // the deliverable sat on main (canary 2026-09-15, task 1d692d91).
1160
+ // The sha is hex-only by construction (extractAlreadyMerged), so
1161
+ // interpolating it into the shell command cannot inject.
1162
+ ```
1163
+
1164
+ <a id="ledger-trigger"></a>
1165
+ ## Ledger trigger
1166
+
1167
+ Invariant: record the trigger in the durable ledger.
1168
+
1169
+ Applies to: standard, bugfix, chore.
1170
+
1171
+ ```
1172
+ // Durable publish-attempt ledger: record the trigger outcome while the
1173
+ // attempt key and commit are in scope. Every attempt lands here with
1174
+ // its outcome — submitted, rejected, or unknown (unknown is recorded
1175
+ // at the park site above). A later run or human matches commit hash +
1176
+ // attempt key against the builder's eventual completion.
1177
+ // (2026-09-16) The trigger is fire-and-forget: the observation above
1178
+ // already recorded the ledger's submitted line on both positive paths
1179
+ // and parked on unknown — there is no applied report to observe and
1180
+ // no rejection signal to record.
1181
+ // (2026-09-18, H2 verdict-first) The verdict was decided exactly
1182
+ // once above; dispatch on it. landed bypasses the receipt poll
1183
+ // (the audit evidence already proved completion); an explicit
1184
+ // publishFailure is preserved verbatim through post-deploy.
1185
+ // Otherwise the verdict is open — but the poll below is only
1186
+ // legitimate against a real receipt: the null-safe assertion records
1187
+ // UNKNOWN and continues to STEP 2 instead of polling blind.
1188
+ ```
1189
+
1190
+ <a id="trigger-await"></a>
1191
+ ## Trigger await
1192
+
1193
+ Invariant: the artifact_edit call is awaited.
1194
+
1195
+ Applies to: standard, bugfix, chore.
1196
+
1197
+ ```
1198
+ // The trigger itself: the artifact_edit call is AWAITED (the workflow
1199
+ // waits for it to complete) but its return value is intentionally
1200
+ // UNCONSUMED — NO schema, so no schema validation can fail this
1201
+ // call: a schema-less call resolves to the child's raw response as
1202
+ // a plain string (probed live 2026-09-16 — never parsed, never
1203
+ // throws on content). One caveat, also probed: the runtime still
1204
+ // scans the response for a JSON candidate, and an unparseable
1205
+ // {...}-looking substring in the child's prose throws ("response
1206
+ // JSON candidate", probe P6). The prompt tells the child to end its
1207
+ // turn with no prose at all, which keeps the common case clean —
1208
+ // but the channel is stochastic, so any throw is possible and
1209
+ // inconclusive: the edit may still have gone through, so the
1210
+ // outcome stays unknown until the observation below confirms it —
1211
+ // never inferred from the throw, and never blind-retried (a blind
1212
+ // re-trigger duplicated the edit on 2026-09-12).
1213
+ ```
1214
+
1215
+ <a id="diff-computation"></a>
1216
+ ## Diff computation
1217
+
1218
+ Invariant: the diff is computed, the rebuild is triggered, and the report is verified.
1219
+
1220
+ Applies to: standard, bugfix, chore.
1221
+
1222
+ ```
1223
+ // (below) the diff computation, rebuild trigger, application
1224
+ // verification, bounded poll, and provenance stamp. The builder
1225
+ // only makes the artifact_edit call and reports the applied
1226
+ // changes — no prose claim to trust. If the artifact tool namespace
1227
+ // is missing from this child it reports honestly and the workflow
1228
+ // retries once with a fresh key (bounded); anything else parks.
1229
+ // Publish diff base (2026-09-14, task 0c53af4e): the carried diff is
1230
+ // BASE..HEAD where BASE is the previously-stamped provenance
1231
+ // source_commit — NOT HEAD^1. A push-time reconcile merge puts the
1232
+ // task's own changes behind an intermediate merge, so HEAD^1..HEAD
1233
+ // silently drops the task's fix while the artifact builds without
1234
+ // it. The stamped base is the artifact's actual content; BASE..HEAD
1235
+ // is the complete unpublished delta. Empty tree only for a genuine
1236
+ // first publish (no provenance stamped yet).
1237
+ ```
1238
+
1239
+ <a id="already-merged-hydra"></a>
1240
+ ## Already-merged hydration
1241
+
1242
+ Invariant: when this run did not execute, hydration uses the existing merge.
1243
+
1244
+ Applies to: standard, bugfix, chore.
1245
+
1246
+ ```
1247
+ // Already-merged hydration: when this run did not execute Build itself
1248
+ // (dispatcher resume at Review after a platform death between phases),
1249
+ // recover the workflow-verified sha. Room #16 blocker 11: the structured
1250
+ // session field is read FIRST — the `already_merged_verified:` notes line
1251
+ // is only a fallback, because session notes are hard-capped at 3000
1252
+ // chars and a truthful declaration at the report's tail was silently
1253
+ // truncated. The structured value was written by the workflow after a
1254
+ // mechanical ancestor check — it is trusted; the builder's bare
1255
+ // declaration never is. Absent both, the mechanical fact below reads
1256
+ // "none declared" and Cass fails closed. The hydration read is best-effort:
1257
+ // a transport throw degrades to "none declared" rather than crashing Review.
1258
+ ```
1259
+
1260
+ <a id="already-merged-idem2"></a>
1261
+ ## Already-merged idempotency
1262
+
1263
+ Invariant: idempotency for already-merged tasks.
1264
+
1265
+ Applies to: standard, bugfix, chore.
1266
+
1267
+ ```
1268
+ // Already-merged idempotency (canary 2026-09-15, task 1d692d91): when the
1269
+ // builder correctly makes no commit because the deliverable is already on
1270
+ // main (a prior merge or hand-repair landed it), it declares
1271
+ // `repo_diff: none (already-merged: <sha>)` naming the main commit that
1272
+ // carries the work. Room #16 blocker 11 (2026-09-18): the line anchor
1273
+ // missed Wren's mid-paragraph declaration, and the persisted notes truncated
1274
+ // the tail — so the anchor is gone and a sha followed by `)`, whitespace, or
1275
+ // end-of-string (truncation) is accepted. The sha is hex-only (7-40 chars)
1276
+ // so the workflow can interpolate it into the mechanical ancestor check
1277
+ // without injection risk; a over-long hex run never matches (the lookahead
1278
+ // fails on the extra hex char). Pure — pinned byte-identical across
1279
+ // standard/bugfix/chore.
1280
+ ```