muse-crew 0.6.6 → 0.6.8
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/AGENTS.md +1 -1
- package/API.md +53 -9
- package/docs/guide.md +9 -6
- package/docs/publish-verification.md +205 -0
- package/lib/AGENTS.md +5 -5
- package/lib/crew-api.js +1101 -0
- package/lib/crew-release.sh +19 -0
- package/lib/merge-lock.sh +18 -5
- package/lib/publish-npm.sh +6 -5
- package/lib/schema.sql +101 -0
- package/lib/test-merge-lock.sh +13 -0
- package/lib/test-publish-skip.sh +2 -1
- package/lib/test-terminal-cleanup.sh +131 -0
- package/lib/test-verify-merge.sh +1 -0
- package/lib/test-worktree-backend.sh +74 -0
- package/lib/worktree-lifecycle.sh +164 -16
- package/package.json +2 -2
- package/seed/AGENTS.md +0 -1
- package/seed/cron-body-template.md +2 -2
- package/seed/crons.json +0 -13
- package/workflows/AGENTS.md +4 -2
- package/workflows/bugfix.js +987 -193
- package/workflows/chore.js +936 -177
- package/workflows/crew-dispatch.js +201 -37
- package/workflows/crew-init.js +85 -50
- package/workflows/docs.js +117 -48
- package/workflows/standard.js +970 -195
- package/workflows/tests/read-board-parse.test.mjs +162 -0
- package/lib/orphan-sweep.sh +0 -287
- package/lib/test-orphan-sweep.sh +0 -276
- package/seed/cron-sweep-body-template.md +0 -11
package/AGENTS.md
CHANGED
|
@@ -5,7 +5,7 @@ Muse Crew source repository. The repo is the product; the personal instance (`$C
|
|
|
5
5
|
## Structure
|
|
6
6
|
|
|
7
7
|
- `API.md` — the Crew API contract: every action a task service must implement
|
|
8
|
-
- `.orchestration/` — crew configuration notes (deploy config lives in the
|
|
8
|
+
- `.orchestration/` — crew configuration notes (deploy config lives in the crew's project record — the single source of truth, read via the Crew API)
|
|
9
9
|
- `identities/` — crew member character files and portraits
|
|
10
10
|
- `lib/` — shell scripts for release, merge, worktree, and cleanup
|
|
11
11
|
- `personas/` — QA perspective costumes for Hazel
|
package/API.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The contract between Muse Crew and any task service implementation. The dispatcher and task workflows call these actions. A conforming task service must implement all of them.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The crew owns its state. The reference implementation is `lib/crew-api.js` — a Node CLI backed by SQLite at `$CREW_HOME/crew-state.db`. The dashboard is a pure client of this API; it never owns crew state.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -101,6 +101,16 @@ Create or update an agent session record. Sessions track which identity is worki
|
|
|
101
101
|
| `failure_reason` | string (≤ 3000) or null | no | |
|
|
102
102
|
| `caveats` | array of `{text, severity}` | no | |
|
|
103
103
|
|
|
104
|
+
### `heartbeat-session`
|
|
105
|
+
|
|
106
|
+
Update the `last_heartbeat` timestamp for a running session. Workflows call this periodically during long phases to signal liveness and prevent the zombie sweep from marking the session as `timed_out`. The sweep (in `getdispatchstate`) marks a `running` session as `timed_out` only if its `last_heartbeat` (or `started_at` for pre-migration sessions) is older than one hour.
|
|
107
|
+
|
|
108
|
+
| Field | Type | Required | Notes |
|
|
109
|
+
|-------|------|----------|-------|
|
|
110
|
+
| `id` | uuid | yes | Session ID; must be `running` |
|
|
111
|
+
|
|
112
|
+
Returns the updated session. Fails with `not_found` if the session does not exist, `conflict` if the session is not `running`.
|
|
113
|
+
|
|
104
114
|
---
|
|
105
115
|
|
|
106
116
|
## Events
|
|
@@ -111,7 +121,7 @@ Log an event to the activity timeline.
|
|
|
111
121
|
|
|
112
122
|
| Field | Type | Required | Notes |
|
|
113
123
|
|-------|------|----------|-------|
|
|
114
|
-
| `type` | `dispatched` · `completed` · `failed` · `blocked` · `created` · `note` · `release_activated` · `deployed` | yes | |
|
|
124
|
+
| `type` | `dispatched` · `completed` · `failed` · `blocked` · `created` · `note` · `release_activated` · `deployed` · `rejected` | yes | `rejected` records a Review/QA rejection as an event, the same word the workflow stores as the session's `status` — one vocabulary for both, so a rejection can never store the session but fail the event (canary 2026-09-11). |
|
|
115
125
|
| `message` | string (1–1000) | yes | |
|
|
116
126
|
| `task_id` | uuid or null | no | Associate with a specific task |
|
|
117
127
|
| `identity` | slug or null | no | Which crew member logged it |
|
|
@@ -126,6 +136,22 @@ Read the event timeline.
|
|
|
126
136
|
| `task_id` | uuid | no | Return only events for this task |
|
|
127
137
|
| `limit` | integer (1–100) | no | Defaults to 50 |
|
|
128
138
|
|
|
139
|
+
### `recordphase` (composite)
|
|
140
|
+
|
|
141
|
+
Atomically record a workflow phase outcome: writes the agent session (with its verdict status) and the corresponding event in a single transaction. This is the only safe way to record a phase result — writing the session and event as two separate calls can leave the session stored but the event rejected (or vice versa), crashing the workflow mid-run. The `rejected` vocabulary is valid for both the session `status` and the event `type`.
|
|
142
|
+
|
|
143
|
+
| Field | Type | Required | Notes |
|
|
144
|
+
|-------|------|----------|-------|
|
|
145
|
+
| `task_id` | uuid | yes | |
|
|
146
|
+
| `identity` | slug | yes | The crew identity that ran the phase |
|
|
147
|
+
| `step` | string (1–120) | yes | Workflow phase (e.g. `Review`, `QA`) |
|
|
148
|
+
| `status` | `completed` · `failed` · `rejected` · `timed_out` · `stalled` | yes | The phase verdict |
|
|
149
|
+
| `notes` | string (≤ 3000) | no | Phase summary |
|
|
150
|
+
| `event_type` | event type | no | Defaults to the `status` value; use `note` for non-verdict events |
|
|
151
|
+
| `event_message` | string (1–1000) | no | Defaults to "`<identity>` `<step>` `<status>`" |
|
|
152
|
+
|
|
153
|
+
The CLI exposes this as `record-phase`. All CLI commands use kebab-case (`create-task`, `get-dispatch-state`, etc.) mapping to the action names documented here.
|
|
154
|
+
|
|
129
155
|
---
|
|
130
156
|
|
|
131
157
|
## Dispatch
|
|
@@ -182,6 +208,7 @@ Register a new project.
|
|
|
182
208
|
| `description` | string (≤ 3000) | no | |
|
|
183
209
|
| `simultaneity` | integer (1–100) | no | Max concurrent tasks; defaults to 2 |
|
|
184
210
|
| `quiesced` | boolean | no | Start paused; defaults to false |
|
|
211
|
+
| `visual_protocol` | boolean or null | no | Tri-state: `null` = inherit crew default (off), `true` = enable for this project, `false` = explicit off. Defaults to null. |
|
|
185
212
|
|
|
186
213
|
### `updateproject`
|
|
187
214
|
|
|
@@ -189,6 +216,8 @@ Update project fields. Only `id` is required; all others are optional patch fiel
|
|
|
189
216
|
|
|
190
217
|
**Context-change guard:** changing `repo_path`, `deploy_type`, or `deploy_slug` is blocked while any task in the project has an active run (an agent session with status `running` that started within the last hour): the call throws, because live runs keep the old project config and the change would split the project context mid-run. Wait for the runs to finish, or recover/park those tasks first, then retry.
|
|
191
218
|
|
|
219
|
+
**`visual_protocol`:** accepted values are `true`, `false`, `null`; anything else is a validation error. `null` clears the override and returns the project to inherit. It is **not** covered by the context-change guard: the dispatcher reads the value once into launch args at dispatch time, so changing it mid-run only affects future launches. Effective value rule: `null` (inherit) and `false` (explicit off) both resolve to off — the visual protocol only runs when the project record carries `true`. The dispatcher passes the resolved boolean to launched workflows as the `visual_protocol` launch arg; workflows receive a plain boolean and never see the tri-state.
|
|
220
|
+
|
|
192
221
|
### `deleteproject`
|
|
193
222
|
|
|
194
223
|
Remove a project registration.
|
|
@@ -269,7 +298,7 @@ Stamp publication provenance. Called by the crew Publish phase after rebuilding
|
|
|
269
298
|
|
|
270
299
|
### `getprovenance`
|
|
271
300
|
|
|
272
|
-
Read the last publication provenance. Returns `{ "provenance": { "source_commit", "crew_release", "published_at" } }`, or `{ "provenance": null }` when nothing has been published yet.
|
|
301
|
+
Read the last publication provenance. Returns `{ "provenance": { "source_commit", "crew_release", "published_at", "task_id" } }`, or `{ "provenance": null }` when nothing has been published yet. `task_id` is the publishing task, for traceability — present when `setprovenance` was called with one, absent otherwise.
|
|
273
302
|
|
|
274
303
|
---
|
|
275
304
|
|
|
@@ -287,12 +316,27 @@ Phases whose outcome drives control flow (Build, Review, QA, Reproduce, Integrat
|
|
|
287
316
|
|
|
288
317
|
### Artifact publish verification
|
|
289
318
|
|
|
290
|
-
Artifact Publish passes only when
|
|
291
|
-
|
|
292
|
-
1. The
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
319
|
+
Artifact Publish passes only when the parent has verified content independently:
|
|
320
|
+
|
|
321
|
+
1. The workflow carries the merged diff to the artifact builder, polls the
|
|
322
|
+
build to completion, triggers an independent `artifact_inspect` read-back
|
|
323
|
+
of the changed regions, and parks with
|
|
324
|
+
`publish: verification-requested <commit>` — it never stamps provenance
|
|
325
|
+
and never treats the builder's applied-changes report as verification.
|
|
326
|
+
The applied report is derived from the carried diff and demonstrated an
|
|
327
|
+
unreliable false-negative mode (2026-09-12: `applied:[]` for a diff the
|
|
328
|
+
builder had applied); it is logged as observation only. The read-back
|
|
329
|
+
request carries the observed builder build identifier (`build.agent_id`)
|
|
330
|
+
so the read-back can be correlated to the exact builder run that built
|
|
331
|
+
this attempt.
|
|
332
|
+
2. The parent compares the read-back against the merged diff mechanically
|
|
333
|
+
(every added line present, every removed line absent), correlates the
|
|
334
|
+
builder build identifier, and stamps provenance via `set-provenance` only
|
|
335
|
+
on a match. A mismatch, a build-id mismatch, or an unobtainable read-back
|
|
336
|
+
never stamps; the task stays parked.
|
|
337
|
+
|
|
338
|
+
Verification fails closed. The loop never retries a verification and never
|
|
339
|
+
issues a blind re-publish on an unknown outcome.
|
|
296
340
|
|
|
297
341
|
### Integrate merge verification
|
|
298
342
|
|
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 3 minutes via Muse's scheduling, even when nobody's in the conversation)
|
|
20
|
+
- **Cron jobs** — the scheduler state declared in `seed/crons.json`: a polling loop (every 3 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
|
|
|
@@ -55,7 +55,7 @@ Required arguments:
|
|
|
55
55
|
|
|
56
56
|
Optional arguments:
|
|
57
57
|
- `dashboardName` — display name for the project registration (default: `"Muse Crew"`).
|
|
58
|
-
- `cronIds` — manifest id → live id map for parallel instances on one account (default: `{}`). The manifest's ids are used as-is unless overridden, e.g. `{"crew-poll": "crew-poll-canary"
|
|
58
|
+
- `cronIds` — manifest id → live id map for parallel instances on one account (default: `{}`). The manifest's ids are used as-is unless overridden, e.g. `{"crew-poll": "crew-poll-canary"}`.
|
|
59
59
|
|
|
60
60
|
### What init creates
|
|
61
61
|
|
|
@@ -67,7 +67,7 @@ Init runs four phases, each idempotent — re-running converges anything that dr
|
|
|
67
67
|
|
|
68
68
|
3. **Project registration** — registers the task service as a project in its own database via `createproject`. The dashboard becomes its own first project, so the crew can work on the dashboard itself.
|
|
69
69
|
|
|
70
|
-
4. **Crons** — reads the manifest at `seed/crons.json` and makes the scheduler match it: missing jobs are created from the entry (title, enabled, mode, schedule, owner, timeout_secs, and the body built from the entry's template with `crewHome` and the dashboard slug substituted in); existing jobs are viewed and converged — drifted fields are updated, unchanged jobs are left alone. One exception: init never touches `enabled` on an existing job. `enabled` is a creation-time default only — a disabled job is a deliberate human decision
|
|
70
|
+
4. **Crons** — reads the manifest at `seed/crons.json` and makes the scheduler match it: missing jobs are created from the entry (title, enabled, mode, schedule, owner, timeout_secs, and the body built from the entry's template with `crewHome` and the dashboard slug substituted in); existing jobs are viewed and converged — drifted fields are updated, unchanged jobs are left alone. One exception: init never touches `enabled` on an existing job. `enabled` is a creation-time default only — a disabled job is a deliberate human decision, and re-init must not silently resurrect it. Jobs removed from the manifest are left alone; deletion is a human decision.
|
|
71
71
|
|
|
72
72
|
### Verification
|
|
73
73
|
|
|
@@ -83,8 +83,7 @@ After init completes, it returns a summary:
|
|
|
83
83
|
"scaffold": { "created": 23, "skipped": 0 },
|
|
84
84
|
"project": "registered",
|
|
85
85
|
"crons": [
|
|
86
|
-
{ "id": "crew-poll", "action": "created", "updated_fields": [] }
|
|
87
|
-
{ "id": "crew-orphan-sweep", "action": "created", "updated_fields": [] }
|
|
86
|
+
{ "id": "crew-poll", "action": "created", "updated_fields": [] }
|
|
88
87
|
]
|
|
89
88
|
}
|
|
90
89
|
```
|
|
@@ -268,6 +267,10 @@ Configuration (per project, via the existing `getconfig` / `updateconfig` action
|
|
|
268
267
|
|
|
269
268
|
For tasks that change anything rendered and visible in the project's artifact ("experiential" tasks), QA owns the visual verdict. Capture runs right after Triage to collect pre-change baseline evidence (parent-driven — see `docs/visual-verdict.md`); Map cannot be written without it; final QA covers mechanical checks only and the task does not complete until a `visual_verdict: PASS` is recorded. A FAIL reworks at Build within the shared budget; a `rendering impossible:` FAIL parks for human attention. Hazel judges against the artist-eye rubric (alignment, spacing, hierarchy, composition, balance, finish, taste) in `identities/hazel.md`, from rendered evidence only — never from prose descriptions.
|
|
270
269
|
|
|
270
|
+
### Publish content verification
|
|
271
|
+
|
|
272
|
+
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 triggers an independent `artifact_inspect` read-back of the changed regions and parks with `publish: verification-requested <commit> (build <agent_id|agent_id unobserved>)`. The parent compares the read-back against the merged diff and stamps provenance only on a match (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). QA's provenance check then enforces the stamp mechanically, so an unverified publish fails loudly in QA instead of passing silently.
|
|
273
|
+
|
|
271
274
|
## Identities
|
|
272
275
|
|
|
273
276
|
Each phase has an assigned identity — a character with a defined personality:
|
|
@@ -280,7 +283,7 @@ Each phase has an assigned identity — a character with a defined personality:
|
|
|
280
283
|
| Build | **Wren** | Quietest one, trusts the plan |
|
|
281
284
|
| Review | **Cass** | Fair but exacting — holds the spec as the contract |
|
|
282
285
|
| Integrate | **Wren** | Merges the work, pushes `main` to the repo (succeeds vacuously when the task branch is empty — runtime-state deliverable) |
|
|
283
|
-
| Publish | **Wren** | Ships the merged code to the publish target (skipped when none). The workflow verifies the side effect mechanically — npm via registry version
|
|
286
|
+
| Publish | **Wren** | Ships the merged code to the publish target (skipped when none). The workflow verifies the side effect mechanically — npm via registry version; artifact via an independent content read-back before the parent stamps provenance (see `docs/publish-verification.md`) — and fails closed if the worker's report and system state disagree |
|
|
284
287
|
| QA | **Hazel** | Code-blind, persistent, wears persona costumes |
|
|
285
288
|
| Reproduce | **Hazel** | Reproduces bugs before fixing |
|
|
286
289
|
| Write | **Tate** | Docs writer, observational voice |
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Publish content verification — parent protocol
|
|
2
|
+
|
|
3
|
+
Provenance is the artifact's claim that its live content came from a specific
|
|
4
|
+
repo commit. The workflow used to stamp it; it no longer does. This document
|
|
5
|
+
is the parent-side runbook. The workflow script handles the mechanical
|
|
6
|
+
halves; the parent handles the stamp.
|
|
7
|
+
|
|
8
|
+
## Why the parent stamps
|
|
9
|
+
|
|
10
|
+
The builder's `applied` report is derived from the diff the workflow carries
|
|
11
|
+
to it — so `verifyAppliedChanges` (report vs. diff) is circular: a fabricated
|
|
12
|
+
report passes by construction. Canary run 8 (2026-09-11) proved it: the build
|
|
13
|
+
was hollow, the report matched the diff, the workflow stamped provenance, and
|
|
14
|
+
all eight phases went green on stale content. The old post-hoc check
|
|
15
|
+
(get-provenance vs. HEAD) only verified the stamp, not the content.
|
|
16
|
+
|
|
17
|
+
Task `23ca8f3f` (2026-09-12, canary `orchestra-dashboard-2`) proved the report
|
|
18
|
+
is unreliable in the other direction too: the builder applied the one-line
|
|
19
|
+
diff, the applied-report came back `applied: []`, and the workflow's old
|
|
20
|
+
applied-report gate parked the task fail-closed as unverified — on a publish
|
|
21
|
+
that had actually landed. A later independent read-back of the live artifact
|
|
22
|
+
showed the change present.
|
|
23
|
+
|
|
24
|
+
The rule: **the builder's applied report is never a verification signal, in
|
|
25
|
+
either direction.** A matching report certifies nothing (it is derived from
|
|
26
|
+
the carried diff — circular by construction, canary run 8). A mismatching or
|
|
27
|
+
empty report blocks nothing (false-negative mode demonstrated by `23ca8f3f`).
|
|
28
|
+
The workflow computes the report comparison as an observation only — it
|
|
29
|
+
never parks on it. The parent ignores the applied report entirely when
|
|
30
|
+
deciding whether to stamp: an `applied:[]` or mismatched report with a
|
|
31
|
+
matching independent read-back stamps normally.
|
|
32
|
+
|
|
33
|
+
The contract is split on purpose:
|
|
34
|
+
|
|
35
|
+
- **Workflow-owned:** carrying the merged diff to the builder, the
|
|
36
|
+
applied-report observation (logged, never a park), the build-completion
|
|
37
|
+
poll, post-deploy cleanup, triggering an independent `artifact_inspect`
|
|
38
|
+
read-back of the changed regions (carrying the observed builder build
|
|
39
|
+
identifier for correlation), recording the Publish session completed, and
|
|
40
|
+
parking with `publish: verification-requested <commit>` instead of
|
|
41
|
+
stamping.
|
|
42
|
+
- **Parent-owned:** reading back the artifact's actual content, comparing it
|
|
43
|
+
mechanically against the merged diff, checking for supersession, stamping
|
|
44
|
+
provenance only on a match, reading the stamp back exactly, and re-queuing
|
|
45
|
+
the task to `in_progress`. In standard/bugfix, QA's provenance check then
|
|
46
|
+
enforces the stamp — an unstamped publish fails loudly in QA instead of
|
|
47
|
+
passing silently. Chore has no QA: the parent's stamp read-back is the
|
|
48
|
+
final gate.
|
|
49
|
+
|
|
50
|
+
No artifact publish completes without parent-stamped provenance. A missing or
|
|
51
|
+
mismatched read-back never stamps.
|
|
52
|
+
|
|
53
|
+
## The park
|
|
54
|
+
|
|
55
|
+
When the artifact build lands, the workflow parks the task with the message:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
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).
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The parked message is stored as `Parked: publish: verification-requested
|
|
71
|
+
<commit> …`. Match on the contained exact string
|
|
72
|
+
`publish: verification-requested`. The `<commit>` is the merged commit whose
|
|
73
|
+
content must be verified. The `(build …)` suffix carries the
|
|
74
|
+
`build.agent_id` the workflow observed for this publish attempt (the
|
|
75
|
+
artifact system's durable build identifier — the parent uses it for the
|
|
76
|
+
build-ID correlation in step 4b); `agent_id unobserved` means the edit was
|
|
77
|
+
accepted but the workflow never correlated it to a builder run. The merge
|
|
78
|
+
lock is already released (post-deploy ran before the park), so the parked
|
|
79
|
+
task holds no resources.
|
|
80
|
+
|
|
81
|
+
## Parent verification procedure
|
|
82
|
+
|
|
83
|
+
For a task parked with `publish: verification-requested <commit>`:
|
|
84
|
+
|
|
85
|
+
1. **Resolve the project.** Read the task's project via the Crew API
|
|
86
|
+
(`getproject`); you need `repo_path` (the git checkout) and the artifact
|
|
87
|
+
slug (the project's publish target).
|
|
88
|
+
2. **Expected change.** Run `git show <commit>` (or
|
|
89
|
+
`git diff <commit>^1 <commit>`) in `repo_path`. This is the expected
|
|
90
|
+
content — the same diff the workflow carried to the builder.
|
|
91
|
+
3. **Actual content.** Get the read-back:
|
|
92
|
+
- If the park message names an `inspection_id`, read that inspection's
|
|
93
|
+
result (async `artifact_inspect` results are delivered to the parent,
|
|
94
|
+
like the visual-verdict inspections).
|
|
95
|
+
- If the trigger failed, call `artifact_inspect` yourself with
|
|
96
|
+
`repair_authorized: false` and the `verbatim_request` rebuilt from the
|
|
97
|
+
workflow source: `buildPublishReadbackRequest(taskId, commit, diff,
|
|
98
|
+
buildAgentId)` in `workflows/standard.js` (identical in `bugfix.js`
|
|
99
|
+
and `chore.js`) — pass the task id, the commit, the diff from step 2,
|
|
100
|
+
and the agent_id from the park message's `(build …)` suffix (ledger
|
|
101
|
+
lookup from step 4b if the suffix says `agent_id unobserved`; null if
|
|
102
|
+
neither is available).
|
|
103
|
+
- If no read-back can be obtained at all, log
|
|
104
|
+
`publish: verification-blocked <commit> <reason>` and leave the task
|
|
105
|
+
parked for human attention. Never stamp without a read-back.
|
|
106
|
+
4. **Compare mechanically.** For every added (`+`) line in the diff, the
|
|
107
|
+
read-back must report it PRESENT in the artifact's current source. For
|
|
108
|
+
every removed (`-`) line, the read-back must report it ABSENT. Quote the
|
|
109
|
+
observed lines; do not infer from prose.
|
|
110
|
+
4b. **Build-ID correlation.** Prove the read-back inspected the live build
|
|
111
|
+
of THIS publish attempt — not a different build's output:
|
|
112
|
+
1. **Expected** = the agent_id in the park message's `(build …)` suffix.
|
|
113
|
+
If the suffix says `agent_id unobserved`, look up the workflow's
|
|
114
|
+
durable publish ledger at `$CREW_HOME/.publish-ledger/<slug>.jsonl`
|
|
115
|
+
for the `submitted` entry with this `<commit>` and use its
|
|
116
|
+
`agent_id` field (it may still be null — then this step is vacuous).
|
|
117
|
+
2. **Live** = the build agent_id the read-back reports for the live
|
|
118
|
+
build (the request asks for it explicitly; the inspector may state
|
|
119
|
+
that no build/agent_id is visible).
|
|
120
|
+
3. If expected and live are both non-null and DIFFER, the read-back may
|
|
121
|
+
have inspected a different build's output — log
|
|
122
|
+
`publish: build-mismatch <commit> expected <expected> observed <live>`,
|
|
123
|
+
stay parked, never stamp, never re-queue. This is not a retry signal:
|
|
124
|
+
a later inspection of the right build can be recorded by the human
|
|
125
|
+
if desired; the loop never re-drives the verification itself.
|
|
126
|
+
4. Otherwise the content match from step 4 decides; log the correlation
|
|
127
|
+
outcome (expected/live compared, or vacuous) in the `publish:
|
|
128
|
+
verified` note.
|
|
129
|
+
5. **Supersession check.** Before stamping, prove the inspected live
|
|
130
|
+
artifact still represents the commit being certified. Read the crew's
|
|
131
|
+
current provenance record (`get-provenance`): if `source_commit` names a
|
|
132
|
+
commit NEWER than `<commit>` (a later Publish already landed), the
|
|
133
|
+
read-back is stale — do NOT stamp `<commit>` on top of it. Log
|
|
134
|
+
`publish: superseded <commit> by <newer_commit>` and leave the task
|
|
135
|
+
parked for human attention. Never claim commit A after commit B changed
|
|
136
|
+
the artifact.
|
|
137
|
+
6. **Stamp, verify the stamp, then re-queue:**
|
|
138
|
+
- **Match** — stamp provenance with the Crew API CLI `set-provenance`
|
|
139
|
+
(the crew-owned store). Do NOT use the artifact's `setprovenance`
|
|
140
|
+
action — it writes a different, non-authoritative store that QA never
|
|
141
|
+
reads, so the stamp would be invisible to every gate:
|
|
142
|
+
`set-provenance --json '{"source_commit":"<commit>","crew_release":"<basename of $crewHome/current>","published_at":"<date -u +%Y-%m-%dT%H:%M:%SZ>","task_id":"<task>"}'`.
|
|
143
|
+
Then read the stamp back with `get-provenance`
|
|
144
|
+
and confirm all four fields match exactly what was sent — a stamp
|
|
145
|
+
that cannot be read back is not a stamp. Only then log the task note
|
|
146
|
+
event `publish: verified <commit> (<inspection_id>)` and re-queue
|
|
147
|
+
with `update-task` → state `in_progress` (never `todo` — `todo`
|
|
148
|
+
restarts Triage and resets retry accounting). The dispatcher resumes
|
|
149
|
+
at QA from the completed Publish session (standard/bugfix); chore has
|
|
150
|
+
no QA — it proceeds to terminal completion. QA's provenance check
|
|
151
|
+
(standard/bugfix) enforces the stamp mechanically.
|
|
152
|
+
- **Mismatch** — do NOT stamp. Log the exact FAIL evidence with
|
|
153
|
+
`publish: content-mismatch <commit> <details>` (quote the observed
|
|
154
|
+
lines from the read-back) and leave the task parked. Never stamp
|
|
155
|
+
provenance on a mismatch; never re-queue a mismatched publish into
|
|
156
|
+
QA — QA would fail it and burn rework budget communicating a Publish
|
|
157
|
+
problem that is not QA's to solve. The task stays parked for human
|
|
158
|
+
attention; the loop does not retry the verification.
|
|
159
|
+
- **Stamp failure** — if `set-provenance` fails after a matched read-back,
|
|
160
|
+
log `publish: stamp-failed <commit> <reason>` and leave the task parked
|
|
161
|
+
for human attention. Never re-queue an unstamped-but-verified task into
|
|
162
|
+
QA — QA would fail it and burn rework budget on a stamping problem.
|
|
163
|
+
|
|
164
|
+
## Exact note-event prefixes
|
|
165
|
+
|
|
166
|
+
Case-sensitive, exact-prefix matches — match on prefixes, never on English
|
|
167
|
+
meaning:
|
|
168
|
+
|
|
169
|
+
- `publish: verification-requested <commit>` — workflow park; contained in
|
|
170
|
+
the stored `Parked: …` message.
|
|
171
|
+
- `publish: verified <commit> (<inspection_id>)` — parent, after stamping
|
|
172
|
+
AND reading the stamp back exactly; re-queued to `in_progress` (never
|
|
173
|
+
`todo`).
|
|
174
|
+
- `publish: content-mismatch <commit> <details>` — parent; exact FAIL
|
|
175
|
+
evidence quoted; stays parked, never stamped, never re-queued to QA.
|
|
176
|
+
- `publish: build-mismatch <commit> expected <expected> observed <live>` —
|
|
177
|
+
parent; the read-back inspected a different build's output than the
|
|
178
|
+
publish attempt (step 4b); stays parked, never stamped, never re-queued.
|
|
179
|
+
- `publish: superseded <commit> by <newer_commit>` — parent; a later Publish
|
|
180
|
+
already landed; stays parked for human attention.
|
|
181
|
+
- `publish: verification-blocked <commit> <reason>` — parent; no read-back
|
|
182
|
+
obtainable; stays parked for a human.
|
|
183
|
+
- `publish: stamp-failed <commit> <reason>` — parent; read-back matched but
|
|
184
|
+
the stamp call failed; stays parked for a human.
|
|
185
|
+
|
|
186
|
+
## Workflow differences
|
|
187
|
+
|
|
188
|
+
- **standard / bugfix:** after `publish: verified`, the dispatcher resumes
|
|
189
|
+
at QA from the completed Publish session. QA's provenance check enforces
|
|
190
|
+
the stamp mechanically — an unstamped publish fails loudly there.
|
|
191
|
+
- **chore:** there is no QA phase. After `publish: verified` (stamp +
|
|
192
|
+
exact stamp read-back), the dispatcher proceeds to terminal completion.
|
|
193
|
+
The parent's stamp read-back is the final gate — no downstream phase
|
|
194
|
+
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).
|
package/lib/AGENTS.md
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
Shell scripts for the crew's infrastructure. Called by workflow scripts, cron, and the release system.
|
|
4
4
|
|
|
5
5
|
- `build-registry.js` — deterministic extractor that generates `workflows/registry.json` (workflow step registry) from the workflow files' `meta` blocks at release time; invoked by `crew-release.sh` deploy
|
|
6
|
+
- `crew-api.js` — the crew-owned task-service API (dependency inversion, 2026-09-11): a zero-dependency Node CLI implementing the API.md contract against `$CREW_HOME/crew-state.db` (schema in `schema.sql`). Workflows call it through their agents' shell; the dashboard delegates to it. All state-machine invariants live as CHECK constraints in the schema, never in client prose. Includes the `record-phase` composite (session + event in one transaction) and a one-time `migrate` import from a dashboard app.db.
|
|
7
|
+
- `schema.sql` — the crew-owned state schema: projects, tasks, poll_state, config, agent_sessions, events. Vocabularies enforced by CHECK constraints; `rejected` is a valid event type (the 2026-09-11 crash was a stored session whose event was rejected). Column names match the historical dashboard tables for a verbatim migration.
|
|
6
8
|
- `crew-release.sh` — immutable release manager: deploy, rollback, prune
|
|
7
|
-
- `merge-lock.sh` — serialized merge lock for concurrent agents: time-based holder lease (bug 2fc8f52f — an unexpired lease is held regardless of process liveness; only an expired lease may be broken). Lock file is key=value: task_id, opaque holder identity (never a PID), acquired_at epoch, lease_seconds (default 600, override via MERGE_LOCK_LEASE_SECONDS). acquire/refresh/release/status/force-release; holder-only refresh and release; every op appends to $CREW_HOME/.merge-lock.log
|
|
8
|
-
- `worktree-lifecycle.sh` — the worktree lifecycle seam: prepare/cleanup/inspect/integrate/verify-merge/status/post-deploy/refresh-lock/lock-status over git worktrees (`.worktrees/<id>`, branch `task/<id>`). The crew registry (`<repo>/.worktrees/.registry/<id>`) is the source of truth for task→branch/path — never reconstruct it from git state. Prepare fails closed on dirty `main`; cleanup is forgiving. `lock-status` reports the merge-lock state explicitly (`UNLOCKED`, or key=value: locked=true, task_id, holder, acquired_at, lease_seconds, age_seconds, remaining_seconds — always exit 0) so Publish can distinguish an empty-diff Integrate (no lock taken) from a refresh failure.
|
|
9
|
-
- `test-worktree-backend.sh` — regression tests for the lifecycle script (validate, prepare/reuse, inspect, status, cleanup, idempotent cleanup, dirty-main preflight) on
|
|
9
|
+
- `merge-lock.sh` — serialized merge lock for concurrent agents: time-based holder lease (bug 2fc8f52f — an unexpired lease is held regardless of process liveness; only an expired lease may be broken). Requires both `CREW_REPO` and `CREW_HOME` (fail closed: BLOCKED, exit 2 when either is unset). Lock file is key=value: task_id, opaque holder identity (never a PID), acquired_at epoch, lease_seconds (default 600, override via MERGE_LOCK_LEASE_SECONDS). acquire/refresh/release/status/force-release; holder-only refresh and release; every op appends to $CREW_HOME/.merge-lock.log
|
|
10
|
+
- `worktree-lifecycle.sh` — the worktree lifecycle seam: prepare/cleanup/inspect/integrate/verify-merge/status/post-deploy/terminal-cleanup/refresh-lock/lock-status over git worktrees (`integrate` reconciles `origin/main` under the merge lock after the task merge — canary `a6d8b0c8`, 2026-09-11 — so the push is a fast-forward; fail-soft with no remote, fail closed on conflict) (`.worktrees/<id>`, branch `task/<id>`). The crew registry (`<repo>/.worktrees/.registry/<id>`) is the source of truth for task→branch/path — never reconstruct it from git state. Prepare fails closed on dirty `main`; cleanup is forgiving. `terminal-cleanup` is the run's last act at every park/fail boundary (called from `parkTask` in standard/bugfix/chore): releases the merge lock unconditionally and reclaims the worktree+branch only when the task branch is fully merged into main — unmerged work is preserved for the human by design, and a dirty worktree is reported, never force-removed. `post-deploy` reports worktree removal honestly (a lying "removed" echo hid real leftovers — canary run 9, 2026-09-12). Requires both `CREW_REPO` and `CREW_HOME` (fail closed: BLOCKED, exit 2 when either is unset); `CREW_REPO` is exported so internal merge-lock.sh calls inherit the repo being worked on; `LIB_DIR` defaults to `$CREW_HOME/lib` (`CREW_LIB` override). `lock-status` reports the merge-lock state explicitly (`UNLOCKED`, or key=value: locked=true, task_id, holder, acquired_at, lease_seconds, age_seconds, remaining_seconds — always exit 0) so Publish can distinguish an empty-diff Integrate (no lock taken) from a refresh failure.
|
|
11
|
+
- `test-worktree-backend.sh` — regression tests for the lifecycle script (validate, prepare/reuse, inspect, status, cleanup, idempotent cleanup, dirty-main preflight, integrate remote-reconcile with fixture sensitivity + no-remote fail-soft) on scratch repos
|
|
10
12
|
- `test-version-write.sh` — regression tests for the escape-preserving step-8 version write in publish-npm.sh (fixture: current package.json with the \\u2014 escape; extracts the shipped block by anchor)
|
|
11
13
|
- `test-publish-verify.sh` — regression tests for the retry-tolerant step-12 verification in publish-npm.sh (canary 5a027278): extracts the shipped block by anchor and runs it against a fake npm whose read replica lags (non-zero exits, then the old version, then the target) — requires convergence on success, fail-closed `PUBLISH_FAILED=verify` on exhaustion, and `--prefer-online` on every read
|
|
12
14
|
- `test-publish-skip.sh` — regression tests for the no-lock graceful publish skip (park 2026-09-11): `lock-status` returns exact `UNLOCKED`/`LOCKED by <holder> since <ts> (pid <pid>)`; a fake empty-diff integrate takes the MERGED_EMPTY branch and never takes the lock; the shipped step-5 block from publish-npm.sh (extracted by anchor) skips gracefully on UNLOCKED (no park, no `PUBLISH_COMPLETE`), follows the refresh path when a lock is held, and fails closed on a `lock-status` query failure; the gate sits inside the ALREADY_PUBLISHED=0 branch and mutation steps are gated on NO_LOCK_HELD=0
|
|
13
|
-
- `orphan-sweep.sh` — find and clean stale worktrees and merge locks: parses `merge-lock.sh status` key=value fields (never a PID); a lock whose task has a running session is untouched regardless of lease age; a lock with no running session is broken only when its lease is expired (via merge-lock.sh release so the break lands in the audit log); reaps a lifecycle pin only after N consecutive clean-mode observations (default 3, `ORPHAN_SWEEP_REAP_AFTER`) with the task absent from the active-run list — a single negative observation only plants a tombstone, so a daemon-restart window can't reap a live task's pin
|
|
14
15
|
- `publish-npm.sh` — deterministic npm publish: lock refresh, release install, version write/commit, pack, registry publish, verify, push, post-deploy. Takes TARGET_VERSION as input; idempotent on retry/resume.
|
|
15
16
|
- `compose-evidence.py` — deterministic visual-evidence compositor (Pillow): pairs identical PNG stems from baseline/ and postchange/ dirs, emits `<stem>-sidebyside.png` and amplified-difference `<stem>-overlay.png` into composites/, prints `PAIR`/`SKIP` manifests. Byte-deterministic; nonzero exit on errors.
|
|
16
|
-
- `test-orphan-sweep.sh` — regression tests for orphan-sweep.sh (active-run guard, verified removal, fail-closed)
|