muse-crew 0.7.10 → 0.7.11
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 +19 -11
- package/docs/guide.md +2 -2
- package/docs/ooda-report.md +123 -0
- package/docs/publish-verification.md +253 -88
- package/docs/visual-verdict.md +81 -67
- package/lib/AGENTS.md +7 -0
- package/lib/append-ooda-step.js +167 -0
- package/lib/build-readback-request.js +130 -0
- package/lib/compose-evidence-caption.js +141 -0
- package/lib/crew-api.js +281 -2
- package/lib/edit-image.py +216 -0
- package/lib/render-html.js +142 -0
- package/lib/see-act.js +327 -0
- package/lib/serve-artifact.js +203 -0
- package/lib/verify-publish.js +265 -0
- package/lib/write-ooda-verdict.js +130 -0
- package/package.json +1 -1
- package/seed/cron-body-template.md +25 -3
- package/workflows/bugfix.js +212 -210
- package/workflows/chore.js +153 -109
- package/workflows/crew-dispatch.js +1 -1
- package/workflows/docs.js +1 -1
- package/workflows/standard.js +177 -225
package/API.md
CHANGED
|
@@ -80,6 +80,16 @@ Atomically park a task for human attention: sets `state` to `parked`, writes the
|
|
|
80
80
|
|
|
81
81
|
Returns `{ "ok": true, "task": {...}, "settled_sessions": <n> }` with the updated task record and the count of sessions settled (`0` when none were running).
|
|
82
82
|
|
|
83
|
+
### `resolvepublishunknown`
|
|
84
|
+
|
|
85
|
+
Recovery contract for publish attempts parked with an `unknown` outcome (2026-09-14): the rebuild trigger's child failed structured closeout and the in-flight-only build-state poll could not see the completed build, so the workflow parked fail-closed — but the edit may still have gone through. When a platform audit build completed inside the publish window (between the Integrate-completion event and the unknown-outcome park), this action routes the task to the parent's independent content verification WITHOUT re-issuing the edit and WITHOUT stamping provenance. The original unknown ledger entry and park event are preserved; the resolution is appended to the publish ledger (`outcome: "unknown-resolved"`) and to the event log, and a `publish: verification-requested <commit>` note is written so `scan-verification-pending` claims the task on the next tick. The parent's read-back ([publish verification](docs/publish-verification.md)) remains the real verification and can still fail terminally. Cases that stay parked with `{ "resolved": false, ... }`: task not parked, latest ledger outcome not `unknown`, no usable commit on the unknown entry, no audit build inside the publish window, unobservable publish window (missing Integrate-completion or park event), or an already-resolved attempt (idempotent).
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Notes |
|
|
88
|
+
|-------|------|----------|-------|
|
|
89
|
+
| `task_id` | uuid | yes | Must be parked with a latest publish-ledger outcome of `unknown` |
|
|
90
|
+
|
|
91
|
+
Returns `{ "resolved": true, "task_id", "commit", "evidence_dir", "audit_report_ok" }` on success, or `{ "resolved": false, "reason" }` when the recovery preconditions are not met.
|
|
92
|
+
|
|
83
93
|
---
|
|
84
94
|
|
|
85
95
|
## Sessions
|
|
@@ -477,21 +487,19 @@ Phases whose outcome drives control flow (Build, Review, QA, Reproduce, Integrat
|
|
|
477
487
|
Artifact Publish passes only when the parent has verified content independently:
|
|
478
488
|
|
|
479
489
|
1. The workflow carries the merged diff to the artifact builder, polls the
|
|
480
|
-
build to completion,
|
|
481
|
-
of the changed regions, and parks with
|
|
490
|
+
build to completion, and parks with
|
|
482
491
|
`publish: verification-requested <commit>` — it never stamps provenance
|
|
483
492
|
and never treats the builder's applied-changes report as verification.
|
|
484
493
|
The applied report is derived from the carried diff and demonstrated an
|
|
485
494
|
unreliable false-negative mode (2026-09-12: `applied:[]` for a diff the
|
|
486
|
-
builder had applied); it is logged as observation only.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
never stamps; the task stays parked.
|
|
495
|
+
builder had applied); it is logged as observation only.
|
|
496
|
+
2. The parent's verification protocol (docs/publish-verification.md) owns
|
|
497
|
+
the independent content confirmation. The independent read-back step is
|
|
498
|
+
currently unavailable: `artifact_inspect` was removed by the platform
|
|
499
|
+
(2026-09-14) and no agent-callable replacement exists (`artifact.inspect`
|
|
500
|
+
is malfunction diagnosis, not a read-back tool). Until a read-back path
|
|
501
|
+
exists, the parent cannot confirm content independently and the task
|
|
502
|
+
stays parked at `publish: verification-requested`.
|
|
495
503
|
|
|
496
504
|
Verification fails closed. The loop never retries a verification and never
|
|
497
505
|
issues a blind re-publish on an unknown outcome.
|
package/docs/guide.md
CHANGED
|
@@ -302,7 +302,7 @@ For tasks that change anything rendered and visible in the project's artifact ("
|
|
|
302
302
|
|
|
303
303
|
### Publish content verification
|
|
304
304
|
|
|
305
|
-
The artifact builder's `applied` report is derived from the diff the workflow carries to it, so comparing the report to the diff is circular — canary run 8 (2026-09-11) stamped provenance on a hollow build and every phase went green. The workflow therefore never stamps provenance itself: after the build lands it
|
|
305
|
+
The artifact builder's `applied` report is derived from the diff the workflow carries to it, so comparing the report to the diff is circular — canary run 8 (2026-09-11) stamped provenance on a hollow build and every phase went green. The workflow therefore never stamps provenance itself: after the build lands it parks with `publish: verification-requested <commit> (build <agent_id|agent_id unobserved>)`. The parent protocol owns the independent content confirmation (parent-driven — see `docs/publish-verification.md`); the park message records the observed builder build identifier as `(build <agent_id|agent_id unobserved>)`, and the parent correlates the read-back's live build agent_id against it — a mismatch logs `publish: build-mismatch <commit> …`, stays parked, and is never stamped (parent-driven — see `docs/publish-verification.md` step 4b). The independent read-back step is currently unavailable: `artifact_inspect` was removed by the platform (2026-09-14) and no agent-callable replacement exists (`artifact.inspect` is malfunction diagnosis, not a read-back tool), so the parent cannot confirm content independently and tasks stay parked at verification-requested until a read-back path exists. QA's provenance check then enforces the stamp mechanically, so an unverified publish fails loudly in QA instead of passing silently.
|
|
306
306
|
|
|
307
307
|
## Identities
|
|
308
308
|
|
|
@@ -430,4 +430,4 @@ crew-release.sh current
|
|
|
430
430
|
|
|
431
431
|
5. **No backoff on retry.** Failed phases retry on the next tick with no delay. The consecutive-failure cap (default 3, then park — see "Failure handling and retry") bounds the retries, but there's no exponential backoff between attempts.
|
|
432
432
|
|
|
433
|
-
6. **QA is code-blind.** Hazel
|
|
433
|
+
6. **QA is code-blind.** Hazel never reads source code — this is by design (context isolation), so QA covers the mechanical checks only: data-level effects via the Crew API, the docs gate, and the provenance check. There is currently no agent-callable visual-inspection tool (`artifact_inspect` was removed by the platform 2026-09-14; `artifact.inspect` is malfunction diagnosis, not a substitute), so visual/functional inspection of rendered output is unavailable until a capture path exists (see `docs/visual-verdict.md`).
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# OODA Report
|
|
2
|
+
|
|
3
|
+
The experiential report produced by the Reproduce and QA phases' see-act loop
|
|
4
|
+
(2026-09-14; hardened 2026-09-15). When an agent drives the browser one step
|
|
5
|
+
at a time — observing each frame, deciding the next action — the complete
|
|
6
|
+
record of that session is the **OODA report**: the ordered
|
|
7
|
+
Observe/Orient/Decide/Act trace, the archived frames, and a machine-readable
|
|
8
|
+
terminal verdict.
|
|
9
|
+
|
|
10
|
+
## Location
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
$CREW_HOME/task-evidence/<taskId>/<phase>/
|
|
14
|
+
ooda-log.jsonl # ordered step records (every attempt preserved)
|
|
15
|
+
verdict.json # latest terminal verdict (what the workflow reads)
|
|
16
|
+
verdicts.jsonl # append-only ledger: every attempt's verdict, never overwritten
|
|
17
|
+
001-shot-desktop.png
|
|
18
|
+
002-click-desktop.png
|
|
19
|
+
...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`<phase>` is `repro` (bugfix Reproduce), `postchange` (standard QA, bugfix QA).
|
|
23
|
+
|
|
24
|
+
## ooda-log.jsonl
|
|
25
|
+
|
|
26
|
+
One JSON object per line, appended after every browser action via
|
|
27
|
+
`lib/append-ooda-step.js`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{"step":1,"attempt":"1","action":"aria","args":{},"exit":0,"screenshot":null,"observation":"Login button found in header.","ts":"..."}
|
|
31
|
+
{"step":2,"attempt":"1","action":"shot","args":{},"exit":0,"screenshot":"/abs/.../001-shot-desktop.png","observation":"Header renders, no console errors.","ts":"..."}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Schema: `step` (positive int), `attempt` (the attempt/run identity —
|
|
35
|
+
`"1"`, `"2"` for successive QA loops, `"repro-1"` for a reproduce run),
|
|
36
|
+
`action`, `args` (object), `exit` (the see-act exit code), `screenshot`
|
|
37
|
+
(absolute path or null), `observation` (the agent's 1–2 sentences: what was
|
|
38
|
+
seen and what it implies — this is the payload, not the filename), `ts`
|
|
39
|
+
(optional, agent-supplied).
|
|
40
|
+
|
|
41
|
+
**Attempts never overwrite.** Step numbers are strictly monotonic *per
|
|
42
|
+
attempt*: an attempt's first step is 1 and each following step is exactly
|
|
43
|
+
previous+1. A retry starts a NEW `--attempt` in the same log — it never
|
|
44
|
+
renumbers or overwrites. A non-monotonic step, a gap, or a corrupt line fails
|
|
45
|
+
loudly (exit 2) and appends nothing.
|
|
46
|
+
|
|
47
|
+
**Actions** (2026-09-15): `aria|shot|click|scroll|type` (see-act driver) plus
|
|
48
|
+
the image tools — `crop|zoom|label|nup` (`lib/edit-image.py`: pixel-exact
|
|
49
|
+
crop, pixel-crisp zoom, caption bars, n-up grids) and `compose`
|
|
50
|
+
(`lib/render-html.js`: Chromium-rendered HTML compositions — the reef-qa
|
|
51
|
+
pattern, real typography and layout for before/after and annotated evidence).
|
|
52
|
+
Every frame-producing action (`shot|crop|zoom|label|nup|compose`) requires
|
|
53
|
+
`--screenshot`: the frame the agent looked at must be the frame it logs. A
|
|
54
|
+
captured-but-unlogged frame is invisible to everyone after the agent.
|
|
55
|
+
|
|
56
|
+
The writer validates and fails loudly (exit 2) on bad input. The agent never
|
|
57
|
+
hand-writes JSON — it supplies fields as flags.
|
|
58
|
+
|
|
59
|
+
## verdict.json and verdicts.jsonl
|
|
60
|
+
|
|
61
|
+
Written at phase end via `lib/write-ooda-verdict.js --dir <phase-dir>
|
|
62
|
+
--attempt <id> --verdict <PASS|FAIL|NOT_POSSIBLE>`:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"verdict": "PASS",
|
|
67
|
+
"attempt": "1",
|
|
68
|
+
"summary": "Footer fix verified on desktop and mobile.",
|
|
69
|
+
"expected": "published N hr ago, no Last polled mislabel",
|
|
70
|
+
"actual": "published 6 hr ago on both viewports",
|
|
71
|
+
"missing_evidence": []
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`verdict` is `PASS`, `FAIL`, or `NOT_POSSIBLE`. `missing_evidence` lists what
|
|
76
|
+
was not checked — an honest gap is recorded, never hidden. Unknown/
|
|
77
|
+
inconclusive is neither PASS nor FAIL.
|
|
78
|
+
|
|
79
|
+
Two records are written:
|
|
80
|
+
|
|
81
|
+
- `verdict.json` — the LATEST verdict, what the workflow closeout reads.
|
|
82
|
+
Overwritten on each call.
|
|
83
|
+
- `verdicts.jsonl` — the append-only ledger. One JSON line per verdict,
|
|
84
|
+
NEVER overwritten: `{seq, attempt, verdict, summary, expected, actual,
|
|
85
|
+
missing_evidence[], reason?, ts?}`. `seq` is assigned mechanically
|
|
86
|
+
(existing lines + 1). A QA retry that FAILs after an earlier PASS keeps
|
|
87
|
+
both — a later attempt can never silently erase an earlier verdict.
|
|
88
|
+
|
|
89
|
+
## Screenshot archival
|
|
90
|
+
|
|
91
|
+
`SEE_ACT_ARCHIVE_DIR=<phase-dir>` (set on every see-act invocation — shell env
|
|
92
|
+
vars do not persist between the agent's commands) funnels every screenshot
|
|
93
|
+
into the phase dir automatically:
|
|
94
|
+
|
|
95
|
+
- `--out` becomes optional; the archived path is the durable record.
|
|
96
|
+
- Sequential names: `001-shot-desktop.png`, `002-click-mobile.png`, ...
|
|
97
|
+
(counter in `<dir>/.seq` — stable across the driver's one-process-per-invocation shape).
|
|
98
|
+
- The JSON response carries `screenshot` (what to READ) and `archived` (the durable copy).
|
|
99
|
+
- Explicit `--out` + archive: the file is captured to `--out` AND copied into the archive.
|
|
100
|
+
- An unusable archive dir is `NOT POSSIBLE` (exit 3) — lost evidence fails loudly, never silently.
|
|
101
|
+
|
|
102
|
+
No frame can be lost: Reproduce, QA, and exploratory hunting all funnel through
|
|
103
|
+
the same wrapper.
|
|
104
|
+
|
|
105
|
+
## Workflow integration
|
|
106
|
+
|
|
107
|
+
- **Standard QA** (artifact): archive to `task-evidence/<task>/postchange/`,
|
|
108
|
+
bounded at 8 steps, logs every step, writes verdict.
|
|
109
|
+
- **Bugfix Reproduce**: archive to `task-evidence/<task>/repro/`, bounded at 8
|
|
110
|
+
steps, logs every step, writes verdict (the bug is confirmed or not).
|
|
111
|
+
- **Bugfix QA**: archive to `task-evidence/<task>/postchange/`, bounded at 8
|
|
112
|
+
steps, re-runs the reproduction steps plus surrounding views, logs every
|
|
113
|
+
step, writes verdict.
|
|
114
|
+
|
|
115
|
+
No nested agents — the depth-1 work agent runs the browser steps itself.
|
|
116
|
+
|
|
117
|
+
## Reading a report
|
|
118
|
+
|
|
119
|
+
1. Read `verdict.json` for the terminal claim.
|
|
120
|
+
2. Read `ooda-log.jsonl` for the ordered trace — each observation next to its frame.
|
|
121
|
+
3. Open the archived PNGs and compare against the written observations. Never trust prose alone.
|
|
122
|
+
|
|
123
|
+
The report is the evidence. A QA claim without its OODA report is an unverified claim.
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# Publish content verification — parent protocol
|
|
2
2
|
|
|
3
|
+
> **BLOCKED (2026-09-14):** `artifact_inspect` was removed by the platform.
|
|
4
|
+
> No agent-callable replacement exists (`artifact.inspect` is malfunction
|
|
5
|
+
> diagnosis, not a read-back tool), so the ferry step below cannot currently
|
|
6
|
+
> run. Tasks park at `publish: verification-requested` and stay parked until
|
|
7
|
+
> a read-back path exists. The rest of this document describes the protocol
|
|
8
|
+
> as designed, so the shape is preserved for when the capability returns.
|
|
9
|
+
|
|
3
10
|
Provenance is the artifact's claim that its live content came from a specific
|
|
4
|
-
repo commit. The workflow
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
repo commit. The workflow never stamps it. This document is the parent-side
|
|
12
|
+
protocol. Deterministic code detects, claims, and certifies; the tick worker
|
|
13
|
+
(the live root agent) is only the async ferry for the inspection.
|
|
7
14
|
|
|
8
15
|
## Why the parent stamps
|
|
9
16
|
|
|
@@ -34,37 +41,126 @@ The contract is split on purpose:
|
|
|
34
41
|
|
|
35
42
|
- **Workflow-owned:** carrying the merged diff to the builder, the
|
|
36
43
|
applied-report observation (logged, never a park), the build-completion
|
|
37
|
-
poll, post-deploy cleanup,
|
|
38
|
-
read-back of the changed regions (carrying the observed builder build
|
|
39
|
-
identifier for correlation), recording the Publish session completed, and
|
|
44
|
+
poll, post-deploy cleanup, recording the Publish session completed, and
|
|
40
45
|
parking with `publish: verification-requested <commit>` instead of
|
|
41
|
-
stamping.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
stamping. The workflow does NOT trigger the read-back inspection — an
|
|
47
|
+
async inspection triggered from inside a workflow run delivers its result
|
|
48
|
+
to the root agent, never back into the run, so a workflow-side trigger is
|
|
49
|
+
an orphan the verifier cannot consume. The parent triggers the one
|
|
50
|
+
inspection it can actually receive.
|
|
51
|
+
- **Parent-owned (deterministic code, ferried by the tick worker):**
|
|
52
|
+
scanning for verification-pending parks, atomically claiming them,
|
|
53
|
+
building the read-back request, triggering the inspection, waiting for the
|
|
54
|
+
result, comparing it mechanically against the merged diff, checking
|
|
55
|
+
build-ID correlation and supersession, stamping provenance only on a
|
|
56
|
+
match, reading the stamp back exactly, logging the terminal verdict, and
|
|
57
|
+
re-queuing the task to `in_progress`.
|
|
49
58
|
|
|
50
59
|
No artifact publish completes without parent-stamped provenance. A missing or
|
|
51
60
|
mismatched read-back never stamps.
|
|
52
61
|
|
|
62
|
+
## The carried diff: BASE..HEAD from the stamped provenance
|
|
63
|
+
|
|
64
|
+
The "merged diff" the workflow carries is `BASE..HEAD` where `BASE` is the
|
|
65
|
+
previously-stamped provenance `source_commit` — the artifact's actual
|
|
66
|
+
content — never `HEAD^1`. (Task `0c53af4e`, 2026-09-14: a push-time
|
|
67
|
+
reconcile merge put the task's own changes behind an intermediate merge, so
|
|
68
|
+
`HEAD^1..HEAD` carried only the reconcile delta and silently omitted the
|
|
69
|
+
task's fix; the artifact built without it. The stamped base is the only
|
|
70
|
+
ground truth for what the artifact already has; `BASE..HEAD` is the complete
|
|
71
|
+
unpublished delta.)
|
|
72
|
+
|
|
73
|
+
The workflow reads the base via `get-provenance` before computing the diff,
|
|
74
|
+
and the computation is guarded mechanically:
|
|
75
|
+
|
|
76
|
+
- Empty base (no provenance stamped) → the empty tree
|
|
77
|
+
`4b825dc642cb6eb9a060e54bf8d69288fbee4904`, and only then. A present but
|
|
78
|
+
malformed base SHA parks fail-closed.
|
|
79
|
+
- `git merge-base --is-ancestor BASE HEAD` must pass; a non-ancestor base
|
|
80
|
+
parks fail-closed (the stamped provenance must lead to the integrated
|
|
81
|
+
commit, otherwise the artifact has drifted or the stamp is wrong).
|
|
82
|
+
- The agent-reported base must equal the stamped base; a mismatch parks.
|
|
83
|
+
- The expected base content hashes (pre-publish observation) are computed at
|
|
84
|
+
the stamped base, not the merge parent — the artifact's tree should match
|
|
85
|
+
the stamp, and the observation is only meaningful against it.
|
|
86
|
+
|
|
87
|
+
The parent verifier (`lib/verify-publish.js`, `--base`) and the read-back
|
|
88
|
+
request builder (`lib/build-readback-request.js`, `--base`) use the identical
|
|
89
|
+
base: the previously-stamped provenance, or the empty tree for a genuine
|
|
90
|
+
first publish. Request builder and verifier never disagree on the base.
|
|
91
|
+
|
|
92
|
+
## The shape: code detects, the tick ferries, code certifies
|
|
93
|
+
|
|
94
|
+
A standalone verification workflow cannot work with the async inspection
|
|
95
|
+
model: async inspection results are delivered to the root agent of the
|
|
96
|
+
agent tree, never into a workflow run — so a verify workflow would wait
|
|
97
|
+
forever for a result it can never receive. The tick worker IS the live
|
|
98
|
+
root agent, so it is the only component that can both trigger an
|
|
99
|
+
inspection and receive its result. (The inspection tool itself,
|
|
100
|
+
`artifact_inspect`, was removed by the platform 2026-09-14 — see the
|
|
101
|
+
BLOCKED notice at the top.)
|
|
102
|
+
|
|
103
|
+
But the tick worker is a generalist LLM, and the certification decision is
|
|
104
|
+
safety-critical: a misjudged "match" stamps unverified content, and nothing
|
|
105
|
+
downstream can ever detect it (QA checks the stamp, not the content). So the
|
|
106
|
+
LLM never judges. The division:
|
|
107
|
+
|
|
108
|
+
1. **Scan (code):** `scan-verification-pending` finds parked tasks whose
|
|
109
|
+
latest parent note is `publish: verification-requested`, with no terminal
|
|
110
|
+
verdict and no unexpired claim. It atomically claims each one by logging
|
|
111
|
+
`publish: verification-claimed <expiry>` (1-hour lease) — the task stays
|
|
112
|
+
parked, so the dispatcher never dispatches QA mid-verification, and a
|
|
113
|
+
second tick cannot start a duplicate verification. It also reconciles the
|
|
114
|
+
verified-but-still-parked gap (verdict recorded, re-queue lost to a crash)
|
|
115
|
+
back to `in_progress`.
|
|
116
|
+
2. **Build (code):** `build-readback-request.js` builds the EXACT inspection
|
|
117
|
+
request from the publish delta — `git diff <base> <commit>` where
|
|
118
|
+
`<base>` is the previously-stamped provenance `source_commit` (or the
|
|
119
|
+
empty tree for a first publish). The tick never hand-writes the request,
|
|
120
|
+
and never uses `commit^1` as the base: push-time reconcile merges put
|
|
121
|
+
the task's own changes behind an intermediate merge, so `commit^1`
|
|
122
|
+
covers only the reconcile delta (2026-09-14, task `0c53af4e`).
|
|
123
|
+
3. **Ferry (tick worker):** when a read-back tool is available, calls it
|
|
124
|
+
with the built request, waits for the async handoff, saves the full
|
|
125
|
+
result JSON to a file. (Currently blocked — no agent-callable tool
|
|
126
|
+
exists.)
|
|
127
|
+
4. **Certify (code):** `verify-publish.js` parses the inspector's
|
|
128
|
+
machine-readable findings block, compares every added/removed diff line
|
|
129
|
+
against the reported present/absent verdicts, checks build-ID correlation
|
|
130
|
+
and supersession via git, and only then stamps provenance, reads the
|
|
131
|
+
stamp back exactly, logs the terminal verdict, and re-queues to
|
|
132
|
+
`in_progress`. Unparseable findings, mismatches, supersession, and stamp
|
|
133
|
+
failures all fail CLOSED with a terminal `publish: verification-failed`
|
|
134
|
+
verdict — never a stamp.
|
|
135
|
+
- **Envelope:** the tick saves the COMPLETE handoff — the full prose
|
|
136
|
+
report AND the full JSON result, both verbatim (raw prose, JSON, or
|
|
137
|
+
both concatenated are all accepted). Observed 2026-09-14: the
|
|
138
|
+
platform's JSON envelope carries NO machine-readable findings
|
|
139
|
+
block; the block lives in the prose handoff. The verifier locates
|
|
140
|
+
the findings block in prose text and JSON string values (including
|
|
141
|
+
double-encoded ones) and prefers the block whose file paths cover
|
|
142
|
+
the expected diff — an echoed request template or stray prose never
|
|
143
|
+
outranks the real block. No covering block => `unreadable-result`,
|
|
144
|
+
fail closed. Saving JSON-only strands verification.
|
|
145
|
+
fail closed.
|
|
146
|
+
- **Release identity:** `scan-verification-pending` resolves
|
|
147
|
+
`crew_release` through the crew home's `current` symlink (the immutable
|
|
148
|
+
active release) and cross-checks it against the running code's own
|
|
149
|
+
realpath. Unresolvable or disputed => the scan throws fail-closed
|
|
150
|
+
BEFORE writing any claim — provenance is never stamped `unknown`, and
|
|
151
|
+
a stale cron body running an old release cannot certify.
|
|
152
|
+
|
|
153
|
+
The tick body (seed/cron-body-template.md, step 4.5) wires these together.
|
|
154
|
+
The publisher never certifies itself, and the LLM never makes the
|
|
155
|
+
safety-critical match decision.
|
|
156
|
+
|
|
53
157
|
## The park
|
|
54
158
|
|
|
55
159
|
When the artifact build lands, the workflow parks the task with the message:
|
|
56
160
|
|
|
57
161
|
```
|
|
58
162
|
publish: verification-requested <commit> (build <agent_id|agent_id unobserved>) — artifact build landed, post-deploy
|
|
59
|
-
finalized, provenance NOT stamped. Parent: run docs/publish-verification.md
|
|
60
|
-
(content read-back inspection <inspection_id> already triggered).
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
or, if the workflow's inspect trigger failed:
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
publish: verification-requested <commit> (build <agent_id|agent_id unobserved>) — ... (read-back inspect trigger
|
|
67
|
-
failed: <reason> — parent: trigger artifact_inspect manually).
|
|
163
|
+
finalized, provenance NOT stamped. Parent: run docs/publish-verification.md.
|
|
68
164
|
```
|
|
69
165
|
|
|
70
166
|
The parked message is stored as `Parked: publish: verification-requested
|
|
@@ -80,71 +176,87 @@ task holds no resources.
|
|
|
80
176
|
|
|
81
177
|
## Parent verification procedure
|
|
82
178
|
|
|
179
|
+
The automated path is the tick body's step 4.5 (scan → build → ferry →
|
|
180
|
+
verify). The manual fallback below is the same protocol run by hand; it
|
|
181
|
+
exists for when the artifact namespace is unavailable to the tick worker.
|
|
182
|
+
|
|
83
183
|
For a task parked with `publish: verification-requested <commit>`:
|
|
84
184
|
|
|
85
185
|
1. **Resolve the project.** Read the task's project via the Crew API
|
|
86
|
-
(`
|
|
186
|
+
(`get-project`); you need `repo_path` (the git checkout) and the artifact
|
|
87
187
|
slug (the project's publish target).
|
|
88
|
-
2. **Expected change.**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
188
|
+
2. **Expected change.** The publish delta is `git diff <base> <commit>`
|
|
189
|
+
in `repo_path`, where `<base>` is the previously-stamped provenance
|
|
190
|
+
`source_commit` (read it via `get-provenance`; use the empty-tree sha
|
|
191
|
+
`4b825dc642cb6eb9a060e54bf8d69288fbee4904` when no provenance is
|
|
192
|
+
stamped yet — a first publish). Never use `commit^1` as the base and
|
|
193
|
+
never take the expected change from the builder's report: push-time
|
|
194
|
+
reconcile merges violate the `merge^1 == previously-published tree`
|
|
195
|
+
invariant, so `commit^1..commit` can omit the task's own fix
|
|
196
|
+
(2026-09-14, task `0c53af4e`).
|
|
197
|
+
3. **Actual content.** When a read-back tool is available, call it with
|
|
198
|
+
`repair_authorized: false` and the `verbatim_request` built by
|
|
199
|
+
`lib/build-readback-request.js` (pass `--repo-path`, `--commit`,
|
|
200
|
+
`--base` (the same base as step 2),
|
|
201
|
+
`--task-id`, `--slug`, and `--build-agent-id` from the park message's
|
|
202
|
+
`(build …)` suffix when it is not `agent_id unobserved`). Currently
|
|
203
|
+
blocked — `artifact_inspect` was removed by the platform (2026-09-14)
|
|
204
|
+
and no agent-callable replacement exists. The request demands a
|
|
205
|
+
machine-readable findings block:
|
|
206
|
+
```
|
|
207
|
+
FILE: <path>
|
|
208
|
+
ADDED: <exact added line> :: PRESENT|ABSENT
|
|
209
|
+
REMOVED: <exact removed line> :: PRESENT|ABSENT
|
|
210
|
+
END_FILE
|
|
211
|
+
```
|
|
212
|
+
If no read-back can be obtained at all, log
|
|
213
|
+
`publish: verification-blocked <commit> <reason>` and leave the task
|
|
214
|
+
parked for human attention. Never stamp without a read-back.
|
|
106
215
|
4. **Compare mechanically.** For every added (`+`) line in the diff, the
|
|
107
|
-
read-back must report it PRESENT in the
|
|
108
|
-
every removed (`-`) line,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
216
|
+
read-back's machine-readable block must report it PRESENT in the
|
|
217
|
+
artifact's current source. For every removed (`-`) line, it must report
|
|
218
|
+
it ABSENT. The comparison is computed by `lib/verify-publish.js` — never
|
|
219
|
+
by eyeballing prose. A missing or malformed findings block fails closed
|
|
220
|
+
as `unreadable-result`, never as a pass.
|
|
221
|
+
4b. **Build-ID correlation.** The read-back may have inspected a different
|
|
222
|
+
build's output than this publish attempt's:
|
|
112
223
|
1. **Expected** = the agent_id in the park message's `(build …)` suffix.
|
|
113
224
|
If the suffix says `agent_id unobserved`, look up the workflow's
|
|
114
225
|
durable publish ledger at `$CREW_HOME/.publish-ledger/<slug>.jsonl`
|
|
115
226
|
for the `submitted` entry with this `<commit>` and use its
|
|
116
227
|
`agent_id` field (it may still be null — then this step is vacuous).
|
|
117
|
-
2. **Live** = the
|
|
118
|
-
|
|
119
|
-
that
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
4. Otherwise the content match from step 4 decides
|
|
127
|
-
|
|
128
|
-
verified` note.
|
|
228
|
+
2. **Live** = whether the expected agent_id appears anywhere in the
|
|
229
|
+
read-back result (the live artifact status exposes no durable
|
|
230
|
+
agent_id — only an in-flight correlation ID that expires with the
|
|
231
|
+
publish attempt, so absence is the common case, not evidence of a
|
|
232
|
+
mismatch).
|
|
233
|
+
3. If expected is non-null and the read-back positively reports a
|
|
234
|
+
DIFFERENT live build identity for this attempt's output, log
|
|
235
|
+
`publish: build-mismatch <commit> expected <expected> observed
|
|
236
|
+
<live>`, stay parked, never stamp, never re-queue.
|
|
237
|
+
4. Otherwise the content match from step 4 decides — the stamp certifies
|
|
238
|
+
CONTENT, not the builder's identity. Log the correlation outcome
|
|
239
|
+
(correlated / unobserved) in the `publish: verified` note.
|
|
129
240
|
5. **Supersession check.** Before stamping, prove the inspected live
|
|
130
|
-
artifact still represents the commit being certified
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
the
|
|
137
|
-
6. **Stamp, verify the stamp, then re-queue:**
|
|
241
|
+
artifact still represents the commit being certified: `git rev-parse
|
|
242
|
+
HEAD` in `repo_path` must equal `<commit>`. If HEAD has moved (a later
|
|
243
|
+
Publish landed), the read-back is stale — log
|
|
244
|
+
`publish: superseded <commit> by <head>` and leave the task parked for
|
|
245
|
+
human attention. Never stamp a superseded commit.
|
|
246
|
+
6. **Stamp, verify the stamp, then re-queue** (all in `lib/verify-publish.js`;
|
|
247
|
+
the manual equivalent):
|
|
138
248
|
- **Match** — stamp provenance with the Crew API CLI `set-provenance`
|
|
139
249
|
(the crew-owned store). Do NOT use the artifact's `setprovenance`
|
|
140
250
|
action — it writes a different, non-authoritative store that QA never
|
|
141
251
|
reads, so the stamp would be invisible to every gate:
|
|
142
|
-
`set-provenance --json '{"source_commit":"<commit>","crew_release":"<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
252
|
+
`set-provenance --json '{"source_commit":"<commit>","crew_release":"<release>","task_id":"<task>"}'`
|
|
253
|
+
(crew_release is the basename of the active release, e.g.
|
|
254
|
+
`pkg-0.7.10`). Then read the stamp back with `get-provenance`
|
|
255
|
+
and confirm source_commit, crew_release, and task_id match exactly
|
|
256
|
+
what was sent — a stamp that cannot be read back is not a stamp. Only
|
|
257
|
+
then log the task note event
|
|
258
|
+
`publish: verified <commit> (<inspection_id>)` and re-queue with
|
|
259
|
+
`update-task` → state `in_progress` (never `todo` — `todo`
|
|
148
260
|
restarts Triage and resets retry accounting). The dispatcher resumes
|
|
149
261
|
at QA from the completed Publish session (standard/bugfix); chore has
|
|
150
262
|
no QA — it proceeds to terminal completion. QA's provenance check
|
|
@@ -161,6 +273,61 @@ For a task parked with `publish: verification-requested <commit>`:
|
|
|
161
273
|
for human attention. Never re-queue an unstamped-but-verified task into
|
|
162
274
|
QA — QA would fail it and burn rework budget on a stamping problem.
|
|
163
275
|
|
|
276
|
+
## Crash recovery
|
|
277
|
+
|
|
278
|
+
- **Tick dies before triggering the inspection:** the claim expires after
|
|
279
|
+
1 hour; the next scan re-claims and re-verifies from scratch. The stamp
|
|
280
|
+
is an idempotent upsert, so a duplicate verification cannot corrupt it.
|
|
281
|
+
- **Tick dies after the inspection but before the stamp:** same as above —
|
|
282
|
+
the next scan re-runs the whole verification (new inspection, new
|
|
283
|
+
comparison). Wasteful but correct.
|
|
284
|
+
- **Crash between stamp and re-queue:** the next scan sees
|
|
285
|
+
`publish: verified` on a still-parked task and reconciles it to
|
|
286
|
+
`in_progress`. Failure verdicts are never reconciled — they stay parked
|
|
287
|
+
for human attention.
|
|
288
|
+
- **Two ticks verify concurrently:** impossible — the atomic claim means the
|
|
289
|
+
second scan sees the unexpired `publish: verification-claimed` note and
|
|
290
|
+
skips. The lease expiry bounds the damage if a claimer dies.
|
|
291
|
+
|
|
292
|
+
## Unknown-outcome recovery (2026-09-14, Gate 1 Journey 3 attempt 7)
|
|
293
|
+
|
|
294
|
+
Attempt 7 parked at Publish with outcome `unknown`: the rebuild trigger's
|
|
295
|
+
child failed structured closeout and the in-flight-only build-state poll
|
|
296
|
+
could not see the completed build — even though the build HAD run (a fresh
|
|
297
|
+
platform audit directory existed). Two mechanisms close this gap.
|
|
298
|
+
|
|
299
|
+
**1. Workflow-side durable evidence.** Before the rebuild trigger, the
|
|
300
|
+
workflow snapshots the artifact's audit-directory listing
|
|
301
|
+
(`~/workspace/ts-spaces/<slug>/audits/` — best-effort, never a gate). On a
|
|
302
|
+
structured-output failure with no in-flight build observed, it re-lists and
|
|
303
|
+
diffs: a timestamped directory that appeared during the trigger window is
|
|
304
|
+
positive evidence the edit went through and the build completed. The
|
|
305
|
+
fallback never re-issues the edit, never stamps provenance, and only routes
|
|
306
|
+
to the parent's independent content read-back. No new directory still parks
|
|
307
|
+
`unknown` fail-closed. The ledger distinguishes the two confirmations: `edit
|
|
308
|
+
confirmed via durable audit evidence …` vs `edit confirmed via build-state
|
|
309
|
+
poll …`.
|
|
310
|
+
|
|
311
|
+
The fallback's known limitation: audit directories are not attributed to
|
|
312
|
+
tasks, so two concurrent publishes to the same artifact could cross-read.
|
|
313
|
+
The consequence is bounded — the fallback only routes to the parent
|
|
314
|
+
read-back, and the parent still certifies the exact commit's content
|
|
315
|
+
mechanically (a wrong build's content fails closed as `publish:
|
|
316
|
+
content-mismatch` / `publish: build-mismatch`, never stamps).
|
|
317
|
+
|
|
318
|
+
**2. `resolve-publish-unknown` (Crew API).** For attempts already parked
|
|
319
|
+
`unknown` before this fix: given a task parked with a latest ledger outcome
|
|
320
|
+
of `unknown`, it derives the publish window (Integrate-completion event →
|
|
321
|
+
unknown-outcome park event) and checks for a timestamped audit build inside
|
|
322
|
+
that window. On evidence, it appends `unknown-resolved` to the ledger
|
|
323
|
+
(never rewriting the original entry), writes `publish: unknown-resolved`
|
|
324
|
+
and a mirrored `publish: verification-requested <commit>` note (the mirror
|
|
325
|
+
is timestamped strictly later so the scan sees it as the latest), and leaves
|
|
326
|
+
the task parked for the normal scan. Still-unknown cases stay parked:
|
|
327
|
+
unparked task, non-`unknown` latest ledger outcome, missing commit, no audit
|
|
328
|
+
build in the window, unobservable window, or an already-resolved attempt
|
|
329
|
+
(idempotent).
|
|
330
|
+
|
|
164
331
|
## Exact note-event prefixes
|
|
165
332
|
|
|
166
333
|
Case-sensitive, exact-prefix matches — match on prefixes, never on English
|
|
@@ -168,20 +335,29 @@ meaning:
|
|
|
168
335
|
|
|
169
336
|
- `publish: verification-requested <commit>` — workflow park; contained in
|
|
170
337
|
the stored `Parked: …` message.
|
|
338
|
+
- `publish: verification-claimed <ISO-expiry>` — parent scan; atomic claim
|
|
339
|
+
with lease. Not a verdict.
|
|
171
340
|
- `publish: verified <commit> (<inspection_id>)` — parent, after stamping
|
|
172
341
|
AND reading the stamp back exactly; re-queued to `in_progress` (never
|
|
173
342
|
`todo`).
|
|
174
343
|
- `publish: content-mismatch <commit> <details>` — parent; exact FAIL
|
|
175
344
|
evidence quoted; stays parked, never stamped, never re-queued to QA.
|
|
176
345
|
- `publish: build-mismatch <commit> expected <expected> observed <live>` —
|
|
177
|
-
parent; the read-back
|
|
178
|
-
|
|
179
|
-
- `publish: superseded <commit> by <
|
|
180
|
-
|
|
346
|
+
parent; the read-back positively identified a different build's output
|
|
347
|
+
(step 4b); stays parked, never stamped, never re-queued.
|
|
348
|
+
- `publish: superseded <commit> by <head>` — parent; HEAD moved past the
|
|
349
|
+
commit; stays parked for human attention.
|
|
181
350
|
- `publish: verification-blocked <commit> <reason>` — parent; no read-back
|
|
182
351
|
obtainable; stays parked for a human.
|
|
352
|
+
- `publish: unknown-resolved <commit>` — recovery; durable build evidence
|
|
353
|
+
found inside the publish window for a previously-unknown attempt (see
|
|
354
|
+
"Unknown-outcome recovery"). The original `unknown` outcome is preserved;
|
|
355
|
+
the mirrored `verification-requested` note (written strictly later) is
|
|
356
|
+
what the scan claims.
|
|
183
357
|
- `publish: stamp-failed <commit> <reason>` — parent; read-back matched but
|
|
184
358
|
the stamp call failed; stays parked for a human.
|
|
359
|
+
- `publish: reconciled verified-but-parked -> in_progress` — parent scan;
|
|
360
|
+
the verified verdict was recorded but the re-queue was lost.
|
|
185
361
|
|
|
186
362
|
## Workflow differences
|
|
187
363
|
|
|
@@ -192,14 +368,3 @@ meaning:
|
|
|
192
368
|
exact stamp read-back), the dispatcher proceeds to terminal completion.
|
|
193
369
|
The parent's stamp read-back is the final gate — no downstream phase
|
|
194
370
|
re-checks it.
|
|
195
|
-
|
|
196
|
-
## Recovery: rebuilding the read-back request
|
|
197
|
-
|
|
198
|
-
If the park message names no inspection and you must trigger the read-back
|
|
199
|
-
manually, the request is deterministic — rebuild it from the workflow source
|
|
200
|
-
(`buildPublishReadbackRequest` in `workflows/standard.js`, identical in
|
|
201
|
-
`bugfix.js` and `chore.js`) with the task id, the `<commit>` from the park
|
|
202
|
-
message, the diff from `git show <commit>` in the project's `repo_path`,
|
|
203
|
-
and the agent_id from the park message's `(build …)` suffix (use the ledger
|
|
204
|
-
lookup from step 4b if the suffix says `agent_id unobserved`; pass null if
|
|
205
|
-
neither is available).
|