muse-crew 0.7.10 → 0.7.12
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 +36 -14
- package/docs/guide.md +5 -5
- package/docs/ooda-report.md +150 -0
- package/docs/publish-verification.md +277 -88
- package/docs/visual-verdict.md +81 -67
- package/lib/AGENTS.md +8 -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 +467 -43
- package/lib/edit-image.py +216 -0
- package/lib/read-ooda-verdict.js +94 -0
- package/lib/readback-disk.js +186 -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 +323 -0
- package/lib/write-ooda-verdict.js +147 -0
- package/package.json +1 -1
- package/seed/cron-body-template.md +30 -4
- package/seed/crons.json +1 -1
- package/workflows/bugfix.js +512 -220
- package/workflows/chore.js +389 -119
- package/workflows/crew-dispatch.js +72 -13
- package/workflows/docs.js +11 -2
- package/workflows/standard.js +413 -235
package/API.md
CHANGED
|
@@ -50,10 +50,11 @@ Atomically claim a task for a workflow step. Used by the dispatcher and by workf
|
|
|
50
50
|
| `identity` | string (1–80) | yes | The crew identity performing the step (e.g. `sage`, `wren`) |
|
|
51
51
|
| `step` | string (1–120) | no | The workflow phase name |
|
|
52
52
|
| `notes` | string (≤ 3000) | no | Defaults to `""` |
|
|
53
|
+
| `expected_next_phase` | string (1–120) | no | The `next_phase` routing the dispatcher launched this run for. When the claim wins, the matching `next_phase` is cleared in the **same transaction** as the winning session insert — there is no claim→consume window for a platform death to replay the routing through. Only an exact match clears; a stale or superseded routing survives. Returned as `next_phase_consumed: true/false` on the winning response (absent when the field was not passed). A losing claim never touches `next_phase`. |
|
|
53
54
|
|
|
54
55
|
Returns one of:
|
|
55
56
|
|
|
56
|
-
- `{ "ok": true, "claimed": true, "session_id": "<uuid>", "session": {...} }`
|
|
57
|
+
- `{ "ok": true, "claimed": true, "session_id": "<uuid>", "session": {...}, "next_phase_consumed": true/false }`
|
|
57
58
|
- `{ "ok": true, "claimed": false, "reason": "already_claimed", "existing_session_id": "<uuid>" }`
|
|
58
59
|
|
|
59
60
|
`claimed` is the single field callers branch on. `reason` is present only when `claimed` is false.
|
|
@@ -66,9 +67,22 @@ Send a stuck or failed task to a specific workflow phase for recovery.
|
|
|
66
67
|
|-------|------|----------|-------|
|
|
67
68
|
| `task_id` | uuid | yes | |
|
|
68
69
|
| `action` | `"send_to"` | yes | Only `send_to` is supported |
|
|
69
|
-
| `target_phase` | string (1–120) | yes | The phase to send the task to |
|
|
70
|
+
| `target_phase` | string (1–120) | yes | The phase to send the task to — must be a phase of the task's workflow (validated against the workflow's phase registry; unknown phases fail closed) |
|
|
70
71
|
| `updated_description` | string (≤ 5000) | no | Optional revised description |
|
|
71
72
|
|
|
73
|
+
Recovery intent, not a verdict: accepts a latest session of `failed`, `timed_out`, or `stalled` — or no session at all (pre-claim platform death). Rejects a `running` latest session (work in flight) and terminal task states. Sets `tasks.next_phase` to the target and moves `parked`/`todo` tasks to `in_progress`. Inserts **no** session: recovery never consumes retry budget. The dispatcher routes the task to `next_phase` one-shot (bypassing the retry cap); the launched workflow's successful self-claim clears it atomically (`claim-task` with `expected_next_phase`). Returns the task and `valid_phases`.
|
|
74
|
+
|
|
75
|
+
### `consume-next-phase`
|
|
76
|
+
|
|
77
|
+
One-shot consumption of a routed `next_phase`. Retained as an idempotent public helper for out-of-band recovery tooling — workflows no longer call this; `claim-task` with `expected_next_phase` clears the routed `next_phase` in the same transaction as the winning session insert, so the old claim→consume death window is closed.
|
|
78
|
+
|
|
79
|
+
| Field | Type | Required | Notes |
|
|
80
|
+
|-------|------|----------|-------|
|
|
81
|
+
| `task_id` | uuid | yes | |
|
|
82
|
+
| `expected` | string (1–120) | yes | The `next_phase` value the dispatcher routed on |
|
|
83
|
+
|
|
84
|
+
Clears `tasks.next_phase` only when the stored value still matches `expected` (atomic conditional update). Returns `{ "consumed": true/false }`. A newer `recovertask` written in the race window survives a stale consumer.
|
|
85
|
+
|
|
72
86
|
### `parktask`
|
|
73
87
|
|
|
74
88
|
Atomically park a task for human attention: sets `state` to `parked`, writes the explanatory `note` event, and settles every `running` session for the task — in one transaction. Either all land or none do: callers never see a note without a park, a park without a note, or a parked task with a ghost `running` session left behind (a terminal task transition settles its sessions). Settled sessions move to `failed` — the status `recovertask` accepts, so recovery works immediately after a park — with `ended_at` stamped and `Parked: <message>` appended to their notes; `failure_reason` stays null.
|
|
@@ -80,6 +94,16 @@ Atomically park a task for human attention: sets `state` to `parked`, writes the
|
|
|
80
94
|
|
|
81
95
|
Returns `{ "ok": true, "task": {...}, "settled_sessions": <n> }` with the updated task record and the count of sessions settled (`0` when none were running).
|
|
82
96
|
|
|
97
|
+
### `resolvepublishunknown`
|
|
98
|
+
|
|
99
|
+
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).
|
|
100
|
+
|
|
101
|
+
| Field | Type | Required | Notes |
|
|
102
|
+
|-------|------|----------|-------|
|
|
103
|
+
| `task_id` | uuid | yes | Must be parked with a latest publish-ledger outcome of `unknown` |
|
|
104
|
+
|
|
105
|
+
Returns `{ "resolved": true, "task_id", "commit", "evidence_dir", "audit_report_ok" }` on success, or `{ "resolved": false, "reason" }` when the recovery preconditions are not met.
|
|
106
|
+
|
|
83
107
|
---
|
|
84
108
|
|
|
85
109
|
## Sessions
|
|
@@ -296,7 +320,7 @@ The platform records workflow run status in `runtime.workflow_runs` — separate
|
|
|
296
320
|
Two layers, in order of preference:
|
|
297
321
|
|
|
298
322
|
1. **Prevention — the launcher stays alive.** Async workflow `agent()` authorization is tied to the launcher's lifetime: if the cron tick ends while a workflow is still running, the workflow's next `agent()` call fails. So the poll tick runs with a 90-minute execution timeout (`timeout_secs: 5400` in `seed/crons.json`) and its Step 5 monitor stays alive until every launched run reaches a terminal state. A launcher that outlives its runs never triggers the failure in the first place.
|
|
299
|
-
2. **Recovery — per-tick correlation through the durable mapping.** If the launcher dies early anyway (platform kill, cell recycle), the next tick's Step 0 finds the dead run, `record-platform-failure` correlates it via the `platform_run_tasks` mapping, and `retry-platform-failure` requeues the task — at most ~
|
|
323
|
+
2. **Recovery — per-tick correlation through the durable mapping.** If the launcher dies early anyway (platform kill, cell recycle), the next tick's Step 0 finds the dead run, `record-platform-failure` correlates it via the `platform_run_tasks` mapping, and `retry-platform-failure` requeues the task — at most ~15 minutes later, with the ghost session settled so the dispatcher treats it as a retry candidate immediately.
|
|
300
324
|
|
|
301
325
|
### `record-platform-failure`
|
|
302
326
|
|
|
@@ -477,21 +501,19 @@ Phases whose outcome drives control flow (Build, Review, QA, Reproduce, Integrat
|
|
|
477
501
|
Artifact Publish passes only when the parent has verified content independently:
|
|
478
502
|
|
|
479
503
|
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
|
|
504
|
+
build to completion, and parks with
|
|
482
505
|
`publish: verification-requested <commit>` — it never stamps provenance
|
|
483
506
|
and never treats the builder's applied-changes report as verification.
|
|
484
507
|
The applied report is derived from the carried diff and demonstrated an
|
|
485
508
|
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.
|
|
509
|
+
builder had applied); it is logged as observation only.
|
|
510
|
+
2. The parent's verification protocol (docs/publish-verification.md) owns
|
|
511
|
+
the independent content confirmation. The independent read-back step is
|
|
512
|
+
currently unavailable: `artifact_inspect` was removed by the platform
|
|
513
|
+
(2026-09-14) and no agent-callable replacement exists (`artifact.inspect`
|
|
514
|
+
is malfunction diagnosis, not a read-back tool). Until a read-back path
|
|
515
|
+
exists, the parent cannot confirm content independently and the task
|
|
516
|
+
stays parked at `publish: verification-requested`.
|
|
495
517
|
|
|
496
518
|
Verification fails closed. The loop never retries a verification and never
|
|
497
519
|
issues a blind re-publish on an unknown outcome.
|
package/docs/guide.md
CHANGED
|
@@ -17,7 +17,7 @@ This is the full setup and operations reference. If you're new, start with the [
|
|
|
17
17
|
- A **release** — the first immutable snapshot of the crew's runtime code.
|
|
18
18
|
- An **`.orchestration/` directory** in the crew home with identities, personas, workflow docs, and feedback conventions.
|
|
19
19
|
- A **project registration** — the task service registered as its own first project.
|
|
20
|
-
- **Cron jobs** — the scheduler state declared in `seed/crons.json`: a polling loop (every
|
|
20
|
+
- **Cron jobs** — the scheduler state declared in `seed/crons.json`: a polling loop (every 15 minutes via Muse's scheduling, even when nobody's in the conversation). Owner is `space:<slug>`, so deleting the task service also removes the crons.
|
|
21
21
|
|
|
22
22
|
The agent running in the main chat receives the dispatcher's claims and launches each task workflow. Workflows can't launch workflows, so this handoff is structural.
|
|
23
23
|
|
|
@@ -197,7 +197,7 @@ A task in `todo` state becomes eligible for dispatch on the next polling tick. A
|
|
|
197
197
|
|
|
198
198
|
## How the loop works
|
|
199
199
|
|
|
200
|
-
Every
|
|
200
|
+
Every 15 minutes, the `crew-poll` cron fires:
|
|
201
201
|
|
|
202
202
|
1. **Cron runs the dispatcher** — `crew-dispatch.js` with the task service slug and `crewHome` as arguments.
|
|
203
203
|
|
|
@@ -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>)`. "Landed" requires positive evidence (canary 2026-09-15, task `1d692d91`): the build poll must have positively observed our build — a running build with the receipt `agent_id`, or a completed-build record matching it. Absence of a running build is not evidence our build ran; an unobserved "done" is an unknown outcome, parked fail-closed with an append-only `unknown` ledger entry — never parked as verification-requested. 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
|
|
|
@@ -424,10 +424,10 @@ crew-release.sh current
|
|
|
424
424
|
|
|
425
425
|
2. **Stuck sessions.** If a workflow dies mid-run, its session stays `running` indefinitely and the cron skips it every tick. Manual cleanup is currently required.
|
|
426
426
|
|
|
427
|
-
3. **No streaming.** The polling loop checks every
|
|
427
|
+
3. **No streaming.** The polling loop checks every 15 minutes. There is no webhook or event-driven dispatch.
|
|
428
428
|
|
|
429
429
|
4. **Single dispatcher.** Only one cron runs the dispatcher. Concurrent ticks are prevented by the session-claiming atomicity, but there is no explicit distributed lock.
|
|
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,150 @@
|
|
|
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": "FAIL",
|
|
67
|
+
"attempt": "1",
|
|
68
|
+
"summary": "Login button missing on desktop header.",
|
|
69
|
+
"expected": "login button on header",
|
|
70
|
+
"actual": "no login control in the ARIA tree",
|
|
71
|
+
"missing_evidence": ["mobile viewport not checked"],
|
|
72
|
+
"reason": "Header renders but no login control present in the ARIA tree on desktop."
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`verdict` is `PASS`, `FAIL`, or `NOT_POSSIBLE`. `missing_evidence` lists what
|
|
77
|
+
was not checked — an honest gap is recorded, never hidden. Unknown/
|
|
78
|
+
inconclusive is neither PASS nor FAIL.
|
|
79
|
+
|
|
80
|
+
`reason` is REQUIRED and must be non-empty when the verdict is `FAIL` or
|
|
81
|
+
`NOT_POSSIBLE`: the writer rejects a reason-less negative verdict with exit
|
|
82
|
+
2 and writes nothing. A FAIL verdict without a machine-readable reason is
|
|
83
|
+
not writable — the defect that parked the 2026-09-15 canary at Publish was a
|
|
84
|
+
bare `VERDICT: FAIL` with an all-positive report and no recorded reason.
|
|
85
|
+
|
|
86
|
+
Two records are written:
|
|
87
|
+
|
|
88
|
+
- `verdict.json` — the LATEST verdict, what the workflow closeout reads.
|
|
89
|
+
Overwritten on each call.
|
|
90
|
+
- `verdicts.jsonl` — the append-only ledger. One JSON line per verdict,
|
|
91
|
+
NEVER overwritten: `{seq, attempt, verdict, summary, expected, actual,
|
|
92
|
+
missing_evidence[], reason?, ts?}`. `seq` is assigned mechanically
|
|
93
|
+
(existing lines + 1). A QA retry that FAILs after an earlier PASS keeps
|
|
94
|
+
both — a later attempt can never silently erase an earlier verdict.
|
|
95
|
+
|
|
96
|
+
## Screenshot archival
|
|
97
|
+
|
|
98
|
+
`SEE_ACT_ARCHIVE_DIR=<phase-dir>` (set on every see-act invocation — shell env
|
|
99
|
+
vars do not persist between the agent's commands) funnels every screenshot
|
|
100
|
+
into the phase dir automatically:
|
|
101
|
+
|
|
102
|
+
- `--out` becomes optional; the archived path is the durable record.
|
|
103
|
+
- Sequential names: `001-shot-desktop.png`, `002-click-mobile.png`, ...
|
|
104
|
+
(counter in `<dir>/.seq` — stable across the driver's one-process-per-invocation shape).
|
|
105
|
+
- The JSON response carries `screenshot` (what to READ) and `archived` (the durable copy).
|
|
106
|
+
- Explicit `--out` + archive: the file is captured to `--out` AND copied into the archive.
|
|
107
|
+
- An unusable archive dir is `NOT POSSIBLE` (exit 3) — lost evidence fails loudly, never silently.
|
|
108
|
+
|
|
109
|
+
No frame can be lost: Reproduce, QA, and exploratory hunting all funnel through
|
|
110
|
+
the same wrapper.
|
|
111
|
+
|
|
112
|
+
## Workflow integration
|
|
113
|
+
|
|
114
|
+
- **Standard QA** (artifact): archive to `task-evidence/<task>/postchange/`,
|
|
115
|
+
bounded at 8 steps, logs every step, writes verdict.
|
|
116
|
+
- **Bugfix Reproduce**: archive to `task-evidence/<task>/repro/`, bounded at 8
|
|
117
|
+
steps, logs every step, writes verdict (the bug is confirmed or not).
|
|
118
|
+
- **Bugfix QA**: archive to `task-evidence/<task>/postchange/`, bounded at 8
|
|
119
|
+
steps, re-runs the reproduction steps plus surrounding views, logs every
|
|
120
|
+
step, writes verdict.
|
|
121
|
+
|
|
122
|
+
No nested agents — the depth-1 work agent runs the browser steps itself.
|
|
123
|
+
|
|
124
|
+
## Reading a report
|
|
125
|
+
|
|
126
|
+
1. Read `verdict.json` for the terminal claim.
|
|
127
|
+
2. Read `ooda-log.jsonl` for the ordered trace — each observation next to its frame.
|
|
128
|
+
3. Open the archived PNGs and compare against the written observations. Never trust prose alone.
|
|
129
|
+
|
|
130
|
+
The report is the evidence. A QA claim without its OODA report is an unverified claim.
|
|
131
|
+
|
|
132
|
+
## Reading the verdict back (2026-09-15)
|
|
133
|
+
|
|
134
|
+
`lib/read-ooda-verdict.js --dir <phase-dir> --expect <PASS|FAIL>` is the
|
|
135
|
+
deterministic cross-checker the bugfix QA closeout runs after extracting the
|
|
136
|
+
prose `VERDICT:` line. It prints one JSON line to stdout and never touches
|
|
137
|
+
the clock or randomness:
|
|
138
|
+
|
|
139
|
+
- Exit 0, `{ok:true, verdict, reason, summary, expected, actual, attempt}` —
|
|
140
|
+
the record exists, parses, carries a `verdict` field, agrees with the
|
|
141
|
+
prose expectation, and a FAIL carries a non-empty reason.
|
|
142
|
+
- Exit 2, `{ok:false, code}` — `missing` (no verdict.json), `corrupt`
|
|
143
|
+
(unparseable or no verdict field), `contradiction` (record disagrees with
|
|
144
|
+
the prose line), `no_reason` (FAIL with no machine-readable reason).
|
|
145
|
+
|
|
146
|
+
The prose `VERDICT:` line routes, but `verdict.json` carries the reason the
|
|
147
|
+
workflow reads. On a cross-check failure the QA phase is recorded as failed
|
|
148
|
+
and retried at the same step — an unreasoned or contradictory verdict never
|
|
149
|
+
routes to rework. No LLM judges report-prose consistency; the machine only
|
|
150
|
+
enforces that the reason is present and the records agree.
|