muse-crew 0.14.5 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +61 -7
- package/docs/decisions/AGENTS.md +3 -0
- package/docs/decisions/publish-path.md +191 -1
- package/docs/guide.md +41 -5
- package/docs/publish-unknown-recovery.md +28 -120
- package/docs/publish-verification.md +148 -501
- package/lib/AGENTS.md +4 -9
- package/lib/advance-publish-base.js +3 -3
- package/lib/compose-evidence-caption.js +1 -2
- package/lib/compute-publish-diff.js +82 -5
- package/lib/crew-api.js +734 -1023
- package/lib/publish-note-vocabulary.js +135 -42
- package/lib/schema.sql +19 -0
- package/package.json +1 -1
- package/seed/AGENTS.md +1 -0
- package/seed/cron-body-ack-scan.md +44 -0
- package/seed/cron-body-template.md +53 -87
- package/seed/crons.json +12 -0
- package/workflows/bugfix.js +55 -118
- package/workflows/chore.js +55 -118
- package/workflows/crew-init.js +23 -0
- package/workflows/crew-uninstall.js +7 -4
- package/workflows/standard.js +55 -140
- package/lib/build-readback-request.js +0 -140
- package/lib/check-intent-freshness.js +0 -101
- package/lib/classify-publish-absence.js +0 -462
- package/lib/publish-content.js +0 -154
- package/lib/readback-disk.js +0 -195
- package/lib/retry-publish.js +0 -417
- package/lib/verify-publish.js +0 -416
|
@@ -1,509 +1,156 @@
|
|
|
1
|
-
# Publish
|
|
1
|
+
# Publish acknowledgement — the version protocol (0.14.6)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
> edits land in its on-disk working copy of the artifact source
|
|
6
|
-
> (`~/workspace/ts-spaces/<slug>/` — verified empirically 2026-09-15:
|
|
7
|
-
> added lines present, removed lines absent across real platform commits),
|
|
8
|
-
> so `lib/readback-disk.js` performs the read-back deterministically: it
|
|
9
|
-
> reads the working copy and emits the exact machine-readable findings
|
|
10
|
-
> block `lib/verify-publish.js` already parses. No LLM, no async handoff,
|
|
11
|
-
> no prose to parse. The verifier is unchanged — the sensor changed, the
|
|
12
|
-
> judge didn't.
|
|
3
|
+
> **Eric's publication contract (2026-09-20):** "If we hear that the artifact
|
|
4
|
+
> acknowledges our version, that's it. We don't verify against content."
|
|
13
5
|
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
6
|
+
> The content-verification architecture (read-back, `verify-publish.js`,
|
|
7
|
+
> the unknown-recovery classifier) is RETIRED. Exact per-attempt version
|
|
8
|
+
> acknowledgement is the SOLE positive completion criterion. Provenance
|
|
9
|
+
> certifies that the issuance request was acknowledged — never byte
|
|
10
|
+
> equality with the staged diff.
|
|
19
11
|
|
|
20
|
-
##
|
|
12
|
+
## The version
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
child to call `artifact_edit`, then observed the outcome. Room #24 proved
|
|
24
|
-
the child cannot reach `artifact_edit` — the tool requires a
|
|
25
|
-
parent-conversation session ID that `agent()` children do not have, and all
|
|
26
|
-
four journeys parked at Publish on exactly that failure.
|
|
27
|
-
|
|
28
|
-
The path is now one-party: the workflow prepares the publish and parks at
|
|
29
|
-
**intent**; the session-carrying tick worker — the only caller class that
|
|
30
|
-
can reach `artifact_edit` — issues the edit directly in its own turn.
|
|
31
|
-
|
|
32
|
-
- **Workflow-owned (Publish phase, read-only):** preflight, provenance
|
|
33
|
-
base, checksummed diff (`lib/compute-publish-diff.js` → staged at
|
|
34
|
-
`$CREW_HOME/.publish-diffs/<taskId>.diff`), artifact toolcheck,
|
|
35
|
-
pre-trigger manifest baseline. It writes ONE issuer-stamped ledger entry
|
|
36
|
-
(`outcome: "publish-intent"`, `issuer: "workflow"`, carrying `diff_path`
|
|
37
|
-
and `diff_sha256`) and parks with
|
|
38
|
-
`publish: publish-requested <commit> <attempt>`. It performs no edit and
|
|
39
|
-
writes no issuance.
|
|
40
|
-
- **Tick-worker-owned (seed/cron-body-template.md, step 4.4b):** the tick
|
|
41
|
-
claims the intent (`publish: publish-intent-claimed <expiry>`, 1-hour
|
|
42
|
-
lease, via `scan-publish-unknown`'s `intent` bucket), verifies the staged
|
|
43
|
-
diff's sha256 (regenerating deterministically from `base..commit` via
|
|
44
|
-
`lib/compute-publish-diff.js --commit` when the file is missing — a diff
|
|
45
|
-
that cannot be (re)generated byte-identically is recorded terminally via
|
|
46
|
-
`record-intent-unissuable` as `publish: publish-unissuable`, never
|
|
47
|
-
re-claimed in a loop; a mismatched diff never becomes an edit), and on a
|
|
48
|
-
re-claimed intent runs the deterministic manifest-freshness check first
|
|
49
|
-
(`lib/check-intent-freshness.js` compares the on-disk manifest's
|
|
50
|
-
`content_sha256`/`built_at` against the intent's `manifest_before` — the
|
|
51
|
-
dead tick may have issued and died before recording, and a re-claim never
|
|
52
|
-
blindly re-issues; the check's JSON evidence is recorded on a `recovered`
|
|
53
|
-
entry). It calls `artifact_edit` directly in its own turn, then
|
|
54
|
-
records the outcome through `record-intent-issuance` (compare-and-swap on
|
|
55
|
-
the claim): `accepted`/`recovered` writes the issuer-stamped
|
|
56
|
-
`submitted` ledger entry (`issuer: "tick-worker"`; a `recovered` entry's
|
|
57
|
-
`issued_at` is the dead tick's original claim time — the lower bound on
|
|
58
|
-
the unobserved issuance) and mirrors `publish: verification-requested`;
|
|
59
|
-
`refused` writes `rejected` and the terminal `publish: publish-refused`
|
|
60
|
-
note. An inconclusive edit call (tool unavailable, timeout, ambiguous
|
|
61
|
-
result) is never recorded — the tick logs it and lets the claim expire.
|
|
62
|
-
- **Verified re-entry:** if the tick died between issuing and mirroring,
|
|
63
|
-
the next scan mirrors the missing `publish: verification-requested`
|
|
64
|
-
from the issuer-stamped `submitted` entry — it never re-issues. The
|
|
65
|
-
workflow that parked at intent never acts on the park note again: its
|
|
66
|
-
Publish session already ended. When a verification-pending task is later
|
|
67
|
-
re-queued and the dispatcher resumes Publish, the workflow's provenance
|
|
68
|
-
re-entry guard (stamp has this `task_id` and `source_commit === HEAD`)
|
|
69
|
-
returns PASS without any publish action.
|
|
70
|
-
|
|
71
|
-
Everything below about parent verification (read-back, mechanical
|
|
72
|
-
comparison, supersession, stamping) is unchanged: the one-party model
|
|
73
|
-
changes WHO issues the edit, not what certifies it.
|
|
74
|
-
|
|
75
|
-
Provenance is the artifact's claim that its live content came from a specific
|
|
76
|
-
repo commit. The workflow never stamps it. This document is the parent-side
|
|
77
|
-
protocol. Deterministic code detects, claims, and certifies; the tick worker
|
|
78
|
-
(the live root agent) is only the async ferry for the inspection.
|
|
79
|
-
|
|
80
|
-
## Why the parent stamps
|
|
81
|
-
|
|
82
|
-
The builder's `applied` report is derived from the diff the workflow carries
|
|
83
|
-
to it — so `verifyAppliedChanges` (report vs. diff) is circular: a fabricated
|
|
84
|
-
report passes by construction. Canary run 8 (2026-09-11) proved it: the build
|
|
85
|
-
was hollow, the report matched the diff, the workflow stamped provenance, and
|
|
86
|
-
all eight phases went green on stale content. The old post-hoc check
|
|
87
|
-
(get-provenance vs. HEAD) only verified the stamp, not the content.
|
|
88
|
-
|
|
89
|
-
Task `23ca8f3f` (2026-09-12, canary `orchestra-dashboard-2`) proved the report
|
|
90
|
-
is unreliable in the other direction too: the builder applied the one-line
|
|
91
|
-
diff, the applied-report came back `applied: []`, and the workflow's old
|
|
92
|
-
applied-report gate parked the task fail-closed as unverified — on a publish
|
|
93
|
-
that had actually landed. A later independent read-back of the live artifact
|
|
94
|
-
showed the change present.
|
|
95
|
-
|
|
96
|
-
The rule: **the builder's applied report is never a verification signal, in
|
|
97
|
-
either direction.** A matching report certifies nothing (it is derived from
|
|
98
|
-
the carried diff — circular by construction, canary run 8). A mismatching or
|
|
99
|
-
empty report blocks nothing (false-negative mode demonstrated by `23ca8f3f`).
|
|
100
|
-
The workflow dropped the report entirely on 2026-09-16 (clean-room task
|
|
101
|
-
`e2a8d9f8`): the trigger's JSON closeout contract traveled over the
|
|
102
|
-
stochastic text channel and the runtime's JSON-candidate heuristic misfired
|
|
103
|
-
on its prose ("workflow agent output was not JSON"), parking a task whose
|
|
104
|
-
edit may have gone through. 2026-09-20 retired the trigger child outright
|
|
105
|
-
(blocker 22: children cannot reach `artifact_edit`): the session-carrying
|
|
106
|
-
tick worker issues the edit directly in its own turn and records the
|
|
107
|
-
outcome itself — there is no trigger call to close out, and no report for
|
|
108
|
-
the parent to ignore. `applied_report` survives on ledger lines only as
|
|
109
|
-
`missing-report` (legacy) or null.
|
|
110
|
-
|
|
111
|
-
The contract is split on purpose:
|
|
112
|
-
|
|
113
|
-
- **Workflow-owned:** preparing the publish read-only (preflight,
|
|
114
|
-
provenance base, checksummed diff, toolcheck, pre-trigger manifest
|
|
115
|
-
baseline), recording the `publish-intent` ledger entry, and parking with
|
|
116
|
-
`publish: publish-requested <commit> <attempt>` instead of stamping. The
|
|
117
|
-
workflow never issues the edit — issuance belongs to the session-carrying
|
|
118
|
-
tick worker (one-party publish, 2026-09-20).
|
|
119
|
-
- **Parent-owned (deterministic code, ferried by the tick worker):**
|
|
120
|
-
scanning for verification-pending parks, atomically claiming them,
|
|
121
|
-
building the read-back request, triggering the inspection, waiting for the
|
|
122
|
-
result, comparing it mechanically against the merged diff, checking
|
|
123
|
-
build-ID correlation and supersession, stamping provenance only on a
|
|
124
|
-
match, reading the stamp back exactly, logging the terminal verdict, and
|
|
125
|
-
re-queuing the task to `in_progress`.
|
|
126
|
-
|
|
127
|
-
No artifact publish completes without parent-stamped provenance. A missing or
|
|
128
|
-
mismatched read-back never stamps.
|
|
129
|
-
|
|
130
|
-
## The carried diff: BASE..HEAD from the stamped provenance
|
|
131
|
-
|
|
132
|
-
The "merged diff" the workflow carries is `BASE..HEAD` where `BASE` is the
|
|
133
|
-
previously-stamped provenance `source_commit` — the artifact's actual
|
|
134
|
-
content — never `HEAD^1`. (Task `0c53af4e`, 2026-09-14: a push-time
|
|
135
|
-
reconcile merge put the task's own changes behind an intermediate merge, so
|
|
136
|
-
`HEAD^1..HEAD` carried only the reconcile delta and silently omitted the
|
|
137
|
-
task's fix; the artifact built without it. The stamped base is the only
|
|
138
|
-
ground truth for what the artifact already has; `BASE..HEAD` is the complete
|
|
139
|
-
unpublished delta.)
|
|
140
|
-
|
|
141
|
-
The workflow reads the base via `get-provenance` before computing the diff,
|
|
142
|
-
and the computation is guarded mechanically:
|
|
143
|
-
|
|
144
|
-
- Empty base (no provenance stamped) → the empty tree
|
|
145
|
-
`4b825dc642cb6eb9a060e54bf8d69288fbee4904`, and only then. A present but
|
|
146
|
-
malformed base SHA parks fail-closed.
|
|
147
|
-
- `git merge-base --is-ancestor BASE HEAD` must pass; a non-ancestor base
|
|
148
|
-
parks fail-closed (the stamped provenance must lead to the integrated
|
|
149
|
-
commit, otherwise the artifact has drifted or the stamp is wrong).
|
|
150
|
-
- The agent-reported base must equal the stamped base; a mismatch parks.
|
|
151
|
-
- The expected base content hashes (pre-publish observation) are computed at
|
|
152
|
-
the stamped base, not the merge parent — the artifact's tree should match
|
|
153
|
-
the stamp, and the observation is only meaningful against it.
|
|
154
|
-
|
|
155
|
-
The parent verifier (`lib/verify-publish.js`, `--base`) and the read-back
|
|
156
|
-
request builder (`lib/build-readback-request.js`, `--base`) use the identical
|
|
157
|
-
base: the previously-stamped provenance, or the empty tree for a genuine
|
|
158
|
-
first publish. Request builder and verifier never disagree on the base.
|
|
159
|
-
|
|
160
|
-
**Diff transport (room #14, 2026-09-17):** the diff is computed by the
|
|
161
|
-
deterministic `lib/compute-publish-diff.js` (pinned per-run alongside the
|
|
162
|
-
other lifecycle files), which writes the raw diff bytes to
|
|
163
|
-
`$CREW_HOME/.publish-diffs/<taskId>[-rN].diff` and returns only a small JSON
|
|
164
|
-
summary (`sha256`, `changed_lines` = added+removed, `has_binary`,
|
|
165
|
-
`has_rename`). The LLM never carries diff bytes — an earlier shape asked an
|
|
166
|
-
agent to return the raw diff as a JSON string field, and the JSON ferry
|
|
167
|
-
dropped a valid 700-line diff ("Publish diff parsed to zero files"). The
|
|
168
|
-
trigger agent verifies the file with `sha256sum` before any `artifact_edit`
|
|
169
|
-
call and pastes the verified content into the edit. The 200 changed-line
|
|
170
|
-
budget counts added+removed lines from the summary, never raw diff output
|
|
171
|
-
lines (context lines inflated the old count ~2x).
|
|
172
|
-
|
|
173
|
-
## The shape: code detects, the tick ferries, code certifies
|
|
174
|
-
|
|
175
|
-
A standalone verification workflow cannot work with the async inspection
|
|
176
|
-
model: async inspection results are delivered to the root agent of the
|
|
177
|
-
agent tree, never into a workflow run — so a verify workflow would wait
|
|
178
|
-
forever for a result it can never receive. The tick worker IS the live
|
|
179
|
-
root agent, so it is the only component that can both trigger an
|
|
180
|
-
inspection and receive its result. (The inspection tool itself,
|
|
181
|
-
`artifact_inspect`, was removed by the platform 2026-09-14 — see the
|
|
182
|
-
BLOCKED notice at the top.)
|
|
183
|
-
|
|
184
|
-
But the tick worker is a generalist LLM, and the certification decision is
|
|
185
|
-
safety-critical: a misjudged "match" stamps unverified content, and nothing
|
|
186
|
-
downstream can ever detect it (QA checks the stamp, not the content). So the
|
|
187
|
-
LLM never judges. The division:
|
|
188
|
-
|
|
189
|
-
1. **Scan (code):** `scan-verification-pending` finds parked tasks whose
|
|
190
|
-
latest parent note is `publish: verification-requested`, with no terminal
|
|
191
|
-
verdict and no unexpired claim. It atomically claims each one by logging
|
|
192
|
-
`publish: verification-claimed <expiry>` (1-hour lease) — the task stays
|
|
193
|
-
parked, so the dispatcher never dispatches QA mid-verification, and a
|
|
194
|
-
second tick cannot start a duplicate verification. It also reconciles the
|
|
195
|
-
verified-but-still-parked gap (verdict recorded, re-queue lost to a crash)
|
|
196
|
-
back to `in_progress`.
|
|
197
|
-
2. **Build (code):** `build-readback-request.js` builds the EXACT inspection
|
|
198
|
-
request from the publish delta — `git diff <base> <commit>` where
|
|
199
|
-
`<base>` is the previously-stamped provenance `source_commit` (or the
|
|
200
|
-
empty tree for a first publish). The tick never hand-writes the request,
|
|
201
|
-
and never uses `commit^1` as the base: push-time reconcile merges put
|
|
202
|
-
the task's own changes behind an intermediate merge, so `commit^1`
|
|
203
|
-
covers only the reconcile delta (2026-09-14, task `0c53af4e`).
|
|
204
|
-
3. **Ferry (tick worker):** runs the deterministic sensor
|
|
205
|
-
`lib/readback-disk.js` (`--repo-path`, `--commit`, `--base` — the same
|
|
206
|
-
base as step 2 — `--slug` from the project's `deploy_slug`, `--task-id`)
|
|
207
|
-
and saves its stdout to the result file. The sensor exits 0 only when it
|
|
208
|
-
actually read the working copy; on a non-zero exit the tick must NOT
|
|
209
|
-
save stdout — log `publish: verification-procedural-error <commit>
|
|
210
|
-
<stderr>` and leave the task parked for the next tick to retry (a sensor
|
|
211
|
-
failure is procedural — the read could not be performed — not a content
|
|
212
|
-
verdict). `build-readback-request.js` is retained for the manual LLM
|
|
213
|
-
fallback below.
|
|
214
|
-
4. **Certify (code):** `verify-publish.js` parses the inspector's
|
|
215
|
-
machine-readable findings block, compares every added/removed diff line
|
|
216
|
-
against the reported present/absent verdicts, checks build-ID correlation
|
|
217
|
-
and supersession via git, and only then stamps provenance, reads the
|
|
218
|
-
stamp back exactly, logs the terminal verdict, and re-queues to
|
|
219
|
-
`in_progress`. Unparseable findings, mismatches, supersession, and stamp
|
|
220
|
-
failures all fail CLOSED with a terminal `publish: verification-failed`
|
|
221
|
-
verdict — never a stamp. The terminal vocabulary is a closed registry in
|
|
222
|
-
`lib/publish-note-vocabulary.js` (D7, 2026-09-19): `scan-publish-unknown`
|
|
223
|
-
skips a recognized terminal note as terminal with its meaning named (never
|
|
224
|
-
as `unrecognized-publish-note`), and `verify-publish.js`'s `terminal()`
|
|
225
|
-
asserts its emitted verb is in the registry before writing.
|
|
226
|
-
`tests/publish-note-vocabulary.test.js` closes the enum structurally:
|
|
227
|
-
every `publish: <verb>` literal in lib/ must be declared in the terminal
|
|
228
|
-
registry or the pinned transitional set, so no future terminal verb ships
|
|
229
|
-
unrecognized.
|
|
230
|
-
- **Envelope:** the tick saves the COMPLETE handoff — the full prose
|
|
231
|
-
report AND the full JSON result, both verbatim (raw prose, JSON, or
|
|
232
|
-
both concatenated are all accepted). Observed 2026-09-14: the
|
|
233
|
-
platform's JSON envelope carries NO machine-readable findings
|
|
234
|
-
block; the block lives in the prose handoff. The verifier locates
|
|
235
|
-
the findings block in prose text and JSON string values (including
|
|
236
|
-
double-encoded ones) and prefers the block whose file paths cover
|
|
237
|
-
the expected diff — an echoed request template or stray prose never
|
|
238
|
-
outranks the real block. No covering block => `unreadable-result`,
|
|
239
|
-
fail closed. Saving JSON-only strands verification.
|
|
240
|
-
fail closed.
|
|
241
|
-
- **Release identity:** `scan-verification-pending` resolves
|
|
242
|
-
`crew_release` through the crew home's `current` symlink (the immutable
|
|
243
|
-
active release) and cross-checks it against the running code's own
|
|
244
|
-
realpath. Unresolvable or disputed => the scan throws fail-closed
|
|
245
|
-
BEFORE writing any claim — provenance is never stamped `unknown`, and
|
|
246
|
-
a stale cron body running an old release cannot certify.
|
|
247
|
-
|
|
248
|
-
The tick body (seed/cron-body-template.md, step 4.5) wires these together.
|
|
249
|
-
The publisher never certifies itself, and the LLM never makes the
|
|
250
|
-
safety-critical match decision.
|
|
251
|
-
|
|
252
|
-
## The park
|
|
253
|
-
|
|
254
|
-
When the artifact build lands, the workflow parks the task with the message:
|
|
14
|
+
Each publish attempt carries a deterministic version:
|
|
255
15
|
|
|
256
16
|
```
|
|
257
|
-
publish:
|
|
258
|
-
finalized, provenance NOT stamped. Parent: run docs/publish-verification.md.
|
|
17
|
+
sha256("publish-version:v1:" + task_id + ":" + commit + ":" + publish_attempt)
|
|
259
18
|
```
|
|
260
19
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
`
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
`
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
- **Crash between stamp and re-queue:** the next scan sees
|
|
399
|
-
`publish: verified` on a still-parked task and reconciles it to
|
|
400
|
-
`in_progress`. Failure verdicts are never reconciled — they stay parked
|
|
401
|
-
for human attention.
|
|
402
|
-
- **Two ticks verify concurrently:** impossible — the atomic claim means the
|
|
403
|
-
second scan sees the unexpired `publish: verification-claimed` note and
|
|
404
|
-
skips. The lease expiry bounds the damage if a claimer dies.
|
|
405
|
-
|
|
406
|
-
## Unknown-outcome recovery (2026-09-14, Gate 1 Journey 3 attempt 7; one-party 2026-09-20)
|
|
407
|
-
|
|
408
|
-
Attempt 7 parked at Publish with outcome `unknown`: the old two-party
|
|
409
|
-
path's rebuild-trigger child failed structured closeout and the
|
|
410
|
-
in-flight-only build-state poll could not see the completed build — even
|
|
411
|
-
though the build HAD run. The two-party machinery (trigger child,
|
|
412
|
-
build-state observation, receipt attribution) was retired 2026-09-20:
|
|
413
|
-
blocker 22 proved children cannot reach `artifact_edit`, so there is no
|
|
414
|
-
trigger child anymore and no observation gap to close.
|
|
415
|
-
|
|
416
|
-
What remains of unknown-recovery is the legacy classifier for parks that
|
|
417
|
-
predate one-party publish, plus the one-party crash windows:
|
|
418
|
-
|
|
419
|
-
- **Legacy `due` parks** (`publish outcome unknown` parks from the old
|
|
420
|
-
path): `scan-publish-unknown` claims them for the deterministic
|
|
421
|
-
six-way classifier (`lib/classify-publish-absence.js`) exactly as
|
|
422
|
-
before. The only accepted trigger anchor is an issuer-stamped
|
|
423
|
-
`submitted` ledger entry (`issuer: "tick-worker"` — the only issuance
|
|
424
|
-
class that exists now); a `submitted` without issuer never binds.
|
|
425
|
-
- **One-party crash windows:** the tick dies after issuing but before
|
|
426
|
-
`record-intent-issuance` → the next scan mirrors
|
|
427
|
-
`publish: verification-requested` from the issuer-stamped `submitted`
|
|
428
|
-
entry (never re-issues). The tick dies before issuing → the intent
|
|
429
|
-
claim expires and the next scan re-claims with `reclaimed: true`, and
|
|
430
|
-
the manifest-freshness check decides between `recovered` and a fresh
|
|
431
|
-
issuance — a re-claim never blindly re-issues.
|
|
432
|
-
|
|
433
|
-
### Retired two-party machinery (kept for the record)
|
|
434
|
-
|
|
435
|
-
**1. Pre-trigger toolcheck + baseline.** In the two-party path, before the
|
|
436
|
-
trigger, a tiny schema'd child proved the artifact tool namespace was
|
|
437
|
-
available and captured a pre-trigger build-state baseline; after the
|
|
438
|
-
trigger, the workflow diffed the post-trigger build state against the
|
|
439
|
-
baseline for a receipt. In the one-party path the workflow still performs
|
|
440
|
-
the read-only preflight (toolcheck + manifest baseline — the baseline is
|
|
441
|
-
carried in the `publish-intent` ledger entry's `manifest_before`), but
|
|
442
|
-
there is no trigger child and no receipt attribution: the tick worker
|
|
443
|
-
issues the edit directly and records the outcome itself.
|
|
444
|
-
|
|
445
|
-
**2. Workflow-side durable evidence.** In the two-party path, before the
|
|
446
|
-
rebuild trigger, the workflow snapshotted the artifact's audit-directory
|
|
447
|
-
listing and re-diffed it after the trigger as fallback evidence. Retired
|
|
448
|
-
with the trigger child — the observation gap it closed no longer exists.
|
|
449
|
-
|
|
450
|
-
**2. `resolve-publish-unknown` (Crew API).** For attempts already parked
|
|
451
|
-
`unknown` before this fix: given a task parked with a latest ledger outcome
|
|
452
|
-
of `unknown`, it derives the publish window (Integrate-completion event →
|
|
453
|
-
unknown-outcome park event) and checks for a timestamped audit build inside
|
|
454
|
-
that window. On evidence, it appends `unknown-resolved` to the ledger
|
|
455
|
-
(never rewriting the original entry), writes `publish: unknown-resolved`
|
|
456
|
-
and a mirrored `publish: verification-requested <commit>` note (the mirror
|
|
457
|
-
is timestamped strictly later so the scan sees it as the latest), and leaves
|
|
458
|
-
the task parked for the normal scan. Still-unknown cases stay parked:
|
|
459
|
-
unparked task, non-`unknown` latest ledger outcome, missing commit, no audit
|
|
460
|
-
build in the window, unobservable window, or an already-resolved attempt
|
|
461
|
-
(idempotent).
|
|
462
|
-
|
|
463
|
-
## Exact note-event prefixes
|
|
464
|
-
|
|
465
|
-
Case-sensitive, exact-prefix matches — match on prefixes, never on English
|
|
466
|
-
meaning:
|
|
467
|
-
|
|
468
|
-
- `publish: publish-requested <commit> <attempt>` — workflow park at
|
|
469
|
-
intent (one-party publish, 2026-09-20): the merged change is staged as a
|
|
470
|
-
checksummed diff; the tick worker owns issuance. Not a verdict.
|
|
471
|
-
- `publish: publish-intent-claimed <ISO-expiry>` — tick-worker scan;
|
|
472
|
-
atomic claim with 1-hour lease. Not a verdict.
|
|
473
|
-
- `publish: publish-refused <commit>` — tick worker; the platform refused
|
|
474
|
-
the directly-issued edit. Terminal: parked for human attention.
|
|
475
|
-
- `publish: verification-requested <commit>` — workflow park; contained in
|
|
476
|
-
the stored `Parked: …` message.
|
|
477
|
-
- `publish: verification-claimed <ISO-expiry>` — parent scan; atomic claim
|
|
478
|
-
with lease. Not a verdict.
|
|
479
|
-
- `publish: verified <commit> (<inspection_id>)` — parent, after stamping
|
|
480
|
-
AND reading the stamp back exactly; re-queued to `in_progress` (never
|
|
481
|
-
`todo`).
|
|
482
|
-
- `publish: content-mismatch <commit> <details>` — parent; exact FAIL
|
|
483
|
-
evidence quoted; stays parked, never stamped, never re-queued to QA.
|
|
484
|
-
- `publish: build-mismatch <commit> expected <expected> observed <live>` —
|
|
485
|
-
parent; the read-back positively identified a different build's output
|
|
486
|
-
(step 4b); stays parked, never stamped, never re-queued.
|
|
487
|
-
- `publish: superseded <commit> by <head>` — parent; HEAD moved past the
|
|
488
|
-
commit; stays parked for human attention.
|
|
489
|
-
- `publish: verification-blocked <commit> <reason>` — parent; no read-back
|
|
490
|
-
obtainable; stays parked for a human.
|
|
491
|
-
- `publish: unknown-resolved <commit>` — recovery; durable build evidence
|
|
492
|
-
found inside the publish window for a previously-unknown attempt (see
|
|
493
|
-
"Unknown-outcome recovery"). The original `unknown` outcome is preserved;
|
|
494
|
-
the mirrored `verification-requested` note (written strictly later) is
|
|
495
|
-
what the scan claims.
|
|
496
|
-
- `publish: stamp-failed <commit> <reason>` — parent; read-back matched but
|
|
497
|
-
the stamp call failed; stays parked for a human.
|
|
498
|
-
- `publish: reconciled verified-but-parked -> in_progress` — parent scan;
|
|
499
|
-
the verified verdict was recorded but the re-queue was lost.
|
|
500
|
-
|
|
501
|
-
## Workflow differences
|
|
502
|
-
|
|
503
|
-
- **standard / bugfix:** after `publish: verified`, the dispatcher resumes
|
|
504
|
-
at QA from the completed Publish session. QA's provenance check enforces
|
|
505
|
-
the stamp mechanically — an unstamped publish fails loudly there.
|
|
506
|
-
- **chore:** there is no QA phase. After `publish: verified` (stamp +
|
|
507
|
-
exact stamp read-back), the dispatcher proceeds to terminal completion.
|
|
508
|
-
The parent's stamp read-back is the final gate — no downstream phase
|
|
509
|
-
re-checks it.
|
|
20
|
+
- `task_id` — the crew task
|
|
21
|
+
- `commit` — the merge commit being published
|
|
22
|
+
- `publish_attempt` — 1, 2, 3 (numeric; separate from the workflow's
|
|
23
|
+
string replay key `attempt`)
|
|
24
|
+
|
|
25
|
+
`lib/compute-publish-diff.js --task-id <id> --attempt <n>` derives the version
|
|
26
|
+
and synthesizes it into the staged diff as a `.crew-publish-version-<task_id>`
|
|
27
|
+
new-file hunk. The receipt is scoped per task, not per slug: two tasks of
|
|
28
|
+
the same project can sit in `edit-issued` concurrently, and a shared
|
|
29
|
+
per-slug receipt would let each issuance overwrite the other's
|
|
30
|
+
acknowledgement. The diff checksum covers the version hunk. The version is
|
|
31
|
+
NEVER committed to the repo — it exists only in the staged diff and the
|
|
32
|
+
ledger.
|
|
33
|
+
|
|
34
|
+
## The attempt loop
|
|
35
|
+
|
|
36
|
+
1. **Workflow (Publish phase):** computes the diff with `--task-id` /
|
|
37
|
+
`--attempt 1`, records ONE issuer-stamped `publish-intent` ledger entry
|
|
38
|
+
(`outcome: "publish-intent"`, `issuer: "workflow"`, carrying `diff_path`,
|
|
39
|
+
`diff_sha256`, `version`, `base`), parks with
|
|
40
|
+
`publish: publish-requested <commit> attempt=1`. The workflow NEVER issues
|
|
41
|
+
the edit — its children cannot reach `artifact_edit`.
|
|
42
|
+
|
|
43
|
+
2. **Tick worker (`scan-publish-intent`):** claims the parked intent (1-hour
|
|
44
|
+
lease, CAS on `claim_expiry`), re-derives the version (never trusts the
|
|
45
|
+
ledger's), verifies the staged diff's sha256, checks the base against
|
|
46
|
+
stamped provenance. Then issues the edit DIRECTLY in its own turn —
|
|
47
|
+
the tick worker is the only caller class that can reach `artifact_edit`.
|
|
48
|
+
|
|
49
|
+
3. **Tick worker (`record-intent-issuance`):** records the outcome:
|
|
50
|
+
- `accepted` → issuer-stamped `submitted` ledger entry +
|
|
51
|
+
`publish: edit-issued` note. The ack scan owns the task from here.
|
|
52
|
+
- `refused` → `rejected` entry + terminal `publish: publish-refused`.
|
|
53
|
+
The platform said no; parked for human attention.
|
|
54
|
+
|
|
55
|
+
4. **Ack scan (`scan-ack-pending`):** every tick, for each `edit-issued` task:
|
|
56
|
+
- Reads the artifact's on-disk `.crew-publish-version-<task_id>` receipt — the
|
|
57
|
+
SOLE positive evidence (2026-09-20 REVIEW removed the circular
|
|
58
|
+
builder-report positive path).
|
|
59
|
+
- A builder report that echoes the EXACT version with outcome `refused`
|
|
60
|
+
(`record-builder-report` mechanically requires the version in
|
|
61
|
+
`report_text`) is the explicit-refusal channel — never an ack.
|
|
62
|
+
- **Any exact on-disk acknowledgement for an issued version on the same
|
|
63
|
+
task/commit counts** — including a late earlier attempt's version.
|
|
64
|
+
- On ack: stamps provenance, writes terminal
|
|
65
|
+
`publish: version-acknowledged`, re-queues the task.
|
|
66
|
+
- On explicit refusal: terminal `publish: publish-refused`.
|
|
67
|
+
- On window expiry (30 minutes per attempt, anchored at the LATEST
|
|
68
|
+
issuance): stages attempt N+1 with a FRESH version, immediately
|
|
69
|
+
claimable (no backoff).
|
|
70
|
+
- When the 2-hour total budget from the FIRST issuance is exhausted:
|
|
71
|
+
terminal `publish: version-timeout`.
|
|
72
|
+
|
|
73
|
+
## Bounds (Eric's, re-set 2026-09-20)
|
|
74
|
+
|
|
75
|
+
- 30-minute acknowledgement window per attempt (anchored at the latest issuance)
|
|
76
|
+
- 2-hour total budget from the first issuance — then terminal `publish: version-timeout`
|
|
77
|
+
- No backoff: a reissue is immediately claimable
|
|
78
|
+
- The window is evidence-set: room #25's one healthy ack landed ~15-17
|
|
79
|
+
minutes after issuance — the window is ~2x that plus margin. The ledger
|
|
80
|
+
records issued_at and first-seen ack per attempt, so the distribution is
|
|
81
|
+
measurable and the window is tunable from real data
|
|
82
|
+
- Explicit refusal is terminal (never re-issued)
|
|
83
|
+
- Silence is unknown — fails closed
|
|
84
|
+
|
|
85
|
+
## What the version proves
|
|
86
|
+
|
|
87
|
+
The version on the artifact's disk proves the platform applied THIS attempt's
|
|
88
|
+
edit request. It does NOT prove the content matches the staged diff byte-
|
|
89
|
+
for-byte — and per Eric's contract, we don't check. The builder is trusted
|
|
90
|
+
to apply the diff it was given; the version proves the request landed.
|
|
91
|
+
|
|
92
|
+
**Receipt scope.** The version receipt proves the edit was adopted into the
|
|
93
|
+
artifact's on-disk source tree — NOT that a rebuild succeeded, and NOT
|
|
94
|
+
that a redeployment picked it up. A version acknowledgement followed by a
|
|
95
|
+
failed build leaves the served bundle stale with a green publish verdict;
|
|
96
|
+
that gap belongs to the artifact platform's build pipeline, not to this
|
|
97
|
+
machine.
|
|
98
|
+
|
|
99
|
+
## Operator recovery
|
|
100
|
+
|
|
101
|
+
A task parked at Publish with a terminal note (`publish: version-timeout`,
|
|
102
|
+
`publish: publish-refused`, `publish: publish-unissuable`) is the designed
|
|
103
|
+
human decision point — parked→Todo is a human call, never automatic.
|
|
104
|
+
|
|
105
|
+
**Diagnose before requeue.** The park note names the reason; the evidence
|
|
106
|
+
lives in two places:
|
|
107
|
+
|
|
108
|
+
- `$CREW_HOME/.publish-ledger/<deploy-slug>.jsonl` — the append-only
|
|
109
|
+
ledger: every intent, issuance, acknowledgement, and refusal for the
|
|
110
|
+
task, with commits, attempts, versions, and timestamps.
|
|
111
|
+
- `$CREW_HOME/.publish-diffs/<task-id>-a<attempt>.diff` — the staged
|
|
112
|
+
checksummed diff that was (or was to be) issued.
|
|
113
|
+
|
|
114
|
+
**Timeout semantics.** `publish: version-timeout` means the version was
|
|
115
|
+
unobserved within the 2-hour total budget — NOT "not deployed." The edit may
|
|
116
|
+
still have landed (the platform applied it but the receipt was never
|
|
117
|
+
observed, or the observation raced the window). Before requeueing:
|
|
118
|
+
|
|
119
|
+
1. Check the artifact's current on-disk source for the version. If present,
|
|
120
|
+
the edit landed — stamp provenance manually or requeue to let the ack
|
|
121
|
+
scan observe it.
|
|
122
|
+
2. If absent, the edit likely did not land. Fix the cause (tool
|
|
123
|
+
availability, platform refusal, stale base), then move the task to Todo.
|
|
124
|
+
The next Publish run stages a fresh diff with a fresh version.
|
|
125
|
+
|
|
126
|
+
**Never requeue blindly.** A timeout without diagnosis risks a duplicate
|
|
127
|
+
edit (the unobserved edit may have landed). The machine fails closed for
|
|
128
|
+
exactly this reason — the human opens it only with evidence.
|
|
129
|
+
|
|
130
|
+
## Decommissioning
|
|
131
|
+
|
|
132
|
+
This machine is a compensating control for the platform's missing real
|
|
133
|
+
completion receipt: `artifact_edit` returns no build handle and no
|
|
134
|
+
deployment verdict, so the crew attributes completion by content (the
|
|
135
|
+
version) instead. The day the platform issues a trustworthy receipt — one
|
|
136
|
+
that names the edit, its outcome, and its build — delete this machine:
|
|
137
|
+
the version derivation, the receipt file, both scans' publish paths, and
|
|
138
|
+
the `publish:` note verbs that belong to them. Until then, the
|
|
139
|
+
compensating control IS the receipt.
|
|
140
|
+
|
|
141
|
+
## Retired
|
|
142
|
+
|
|
143
|
+
- `lib/verify-publish.js` — content verifier (deleted)
|
|
144
|
+
- `lib/readback-disk.js` — disk read-back sensor (deleted)
|
|
145
|
+
- `lib/build-readback-request.js` — read-back request builder (deleted)
|
|
146
|
+
- `lib/classify-publish-absence.js` — unknown-recovery classifier (deleted)
|
|
147
|
+
- `lib/check-intent-freshness.js` — intent freshness check (deleted)
|
|
148
|
+
- `lib/retry-publish.js` — retry harness (deleted)
|
|
149
|
+
- `lib/publish-content.js` — publish content helper (deleted)
|
|
150
|
+
|
|
151
|
+
The `publish: verification-requested`, `publish: verification-claimed`,
|
|
152
|
+
`publish: ambiguous`, `publish: retry-superseded`, `publish: retry-refused`,
|
|
153
|
+
`publish: verified`, `publish: superseded`, `publish: verification-failed`,
|
|
154
|
+
and `publish: intent-unverifiable` note verbs are legacy — the exact
|
|
155
|
+
registry in `lib/publish-note-vocabulary.js` (no wildcard families).
|
|
156
|
+
Recognized so old history skips as terminal, never written anew.
|