muse-crew 0.14.5 → 0.14.7
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 +106 -12
- package/docs/decisions/AGENTS.md +5 -0
- package/docs/decisions/publish-path.md +247 -9
- package/docs/decisions/qa-reproduce.md +30 -0
- package/docs/guide.md +41 -5
- package/docs/publish-unknown-recovery.md +28 -120
- package/docs/publish-verification.md +148 -501
- package/lib/AGENTS.md +9 -13
- package/lib/advance-publish-base.js +3 -3
- package/lib/compose-evidence-caption.js +1 -2
- package/lib/compute-publish-diff.js +82 -5
- package/lib/crew-api.js +1134 -1027
- package/lib/merge-lock.sh +153 -41
- package/lib/publish-note-vocabulary.js +135 -42
- package/lib/qa-db.js +132 -0
- package/lib/schema.sql +69 -0
- package/lib/serve-artifact.js +46 -2
- package/lib/test-detached-integrate.sh +122 -0
- package/lib/test-merge-lock.sh +30 -1
- package/lib/worktree-lifecycle.sh +284 -34
- package/package.json +1 -1
- package/seed/AGENTS.md +1 -0
- package/seed/cron-body-ack-scan.md +44 -0
- package/seed/cron-body-template.md +55 -89
- package/seed/crons.json +12 -0
- package/workflows/AGENTS.md +1 -1
- package/workflows/bugfix.js +489 -224
- package/workflows/chore.js +306 -226
- package/workflows/crew-dispatch.js +57 -4
- package/workflows/crew-init.js +23 -0
- package/workflows/crew-uninstall.js +7 -4
- package/workflows/docs.js +24 -2
- package/workflows/standard.js +494 -251
- package/workflows/upgrade.js +13 -1
- package/lib/build-readback-request.js +0 -140
- package/lib/check-intent-freshness.js +0 -101
- package/lib/classify-publish-absence.js +0 -462
- package/lib/publish-content.js +0 -154
- package/lib/readback-disk.js +0 -195
- package/lib/retry-publish.js +0 -417
- package/lib/verify-publish.js +0 -416
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 15 minutes via Muse's scheduling, even when nobody's in the conversation). The scheduler identity is dashboard-independent — chosen once at first init (no owner; the platform rejects `cli:` owners) — so deleting the dashboard artifact does not stop the crew. Removing a crew entirely is `crew-uninstall`'s job.
|
|
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), a 5-minute acknowledgement scan (observation-only — runs `scan-ack-pending` so publish verdicts land quickly; it never issues edits), and a daily update watcher. The scheduler identity is dashboard-independent — chosen once at first init (no owner; the platform rejects `cli:` owners) — so deleting the dashboard artifact does not stop the crew. Removing a crew entirely is `crew-uninstall`'s job.
|
|
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
|
|
|
@@ -126,7 +126,7 @@ The run has four phases:
|
|
|
126
126
|
|
|
127
127
|
1. **Gates** — the crew home must be inside the workspace (uninstall refuses to delete anything outside it), and `confirm: true` must be present. Either missing blocks the run before anything is touched.
|
|
128
128
|
2. **Safety** — if any task is `in_progress`, the run blocks unless `force: true`. Uninstalling under live workflows would strand them.
|
|
129
|
-
3. **Crons** — removes the crew's scheduler jobs, and only those: the exact ids from `$CREW_HOME/.cron-registry.json` (written by init; covers id overrides), plus discovery of `crew-poll-*` jobs whose body contains the crew home path (covers installs from before the registry existed). Anything not on that union is never touched — shared crons are safe by construction, and a missing home still gets its orphaned crons removed.
|
|
129
|
+
3. **Crons** — removes the crew's scheduler jobs, and only those: the exact ids from `$CREW_HOME/.cron-registry.json` (written by init; covers id overrides), plus discovery of `crew-poll-*` / `crew-ack-scan-*` / `crew-update-watch-*` jobs whose body contains the crew home path (covers installs from before the registry existed). Anything not on that union is never touched — shared crons are safe by construction, and a missing home still gets its orphaned crons removed.
|
|
130
130
|
4. **Home** — best-effort `git worktree prune` on the crew's project repos, then deletes the crew home directory and verifies it is gone.
|
|
131
131
|
|
|
132
132
|
Dashboard artifacts are left untouched — uninstall removes the crew instance (its crons and its home), never the user's artifacts.
|
|
@@ -230,7 +230,10 @@ A task in `todo` state becomes eligible for dispatch on the next polling tick. A
|
|
|
230
230
|
|
|
231
231
|
## How the loop works
|
|
232
232
|
|
|
233
|
-
Every 15 minutes, the `crew-poll` cron fires
|
|
233
|
+
Every 15 minutes, the `crew-poll` cron fires. (Publish verdicts don't wait
|
|
234
|
+
for it: a separate 5-minute `crew-ack-scan` cron runs the observation-only
|
|
235
|
+
`scan-ack-pending` pass so acknowledgements, refusals, and timeouts land
|
|
236
|
+
within one scan cadence — issuance stays in the poll tick below.)
|
|
234
237
|
|
|
235
238
|
1. **Cron runs the dispatcher** — `crew-dispatch.js` with the task service slug and `crewHome` as arguments.
|
|
236
239
|
|
|
@@ -364,9 +367,42 @@ Bugfix tasks reproduce at the layer where the bug lives — not always in the br
|
|
|
364
367
|
|
|
365
368
|
The workflow dispatches the Reproduce strategy mechanically on the classification. A garbled or missing marker degrades to `artifact` (the old behavior) — a task is never parked on it. The experiential loop is forbidden for engine/docs tasks: if the reproducer drives the browser anyway, the closeout fails the phase loudly for retry, so wrong-layer verdicts never reach Map.
|
|
366
369
|
|
|
367
|
-
### Publish
|
|
370
|
+
### Publish acknowledgement
|
|
368
371
|
|
|
369
|
-
|
|
372
|
+
Eric's publication contract (2026-09-20): "If we hear that the artifact acknowledges our version, that's it. We don't verify against content."
|
|
373
|
+
|
|
374
|
+
The workflow never stamps provenance itself, and it doesn't issue the edit: workflow children cannot call `artifact_edit` (the tool requires a parent-conversation session id they don't have), so publish is one-party. The workflow prepares — preflight, provenance base, checksummed diff with the deterministic per-attempt version — writes one issuer-stamped `publish-intent` ledger entry, and parks with `publish: publish-requested <commit> attempt=N`. The session-carrying tick worker (the only caller class that can reach `artifact_edit`) claims the intent via `scan-publish-intent`, re-derives the version, verifies the diff's sha256, issues the edit directly in its own turn, and records the issuer-stamped `submitted` entry plus `publish: edit-issued` via `record-intent-issuance`.
|
|
375
|
+
|
|
376
|
+
The ack scan (`scan-ack-pending`) owns the verdict from there: the exact per-attempt version on the artifact's disk is the sole positive completion criterion (2026-09-20 REVIEW removed the circular builder-report positive path; builder reports carry explicit refusals only). Bounds: 30-minute window per attempt, 2-hour total budget from first issuance, no backoff (reissue immediately claimable), fresh version per attempt. Explicit refusal parks terminally (`publish: publish-refused`); budget exhaustion parks `publish: version-timeout`. Silence is unknown and fails closed. See `docs/publish-verification.md` for the full protocol. QA's provenance check then enforces the stamp mechanically, so an unacknowledged publish fails loudly in QA instead of passing silently.
|
|
377
|
+
|
|
378
|
+
### When publish is stuck
|
|
379
|
+
|
|
380
|
+
Two different workers move a publish forward, and they own different halves:
|
|
381
|
+
|
|
382
|
+
- The **5-minute `crew-ack-scan`** is observation-only: it watches the
|
|
383
|
+
on-disk version receipt and, when a 30-minute window expires, stages the
|
|
384
|
+
next attempt's intent (fresh version, immediately claimable). It never
|
|
385
|
+
issues an edit.
|
|
386
|
+
- The **15-minute poll tick** is the only half that issues: its
|
|
387
|
+
`scan-publish-intent` claims staged intents and its session-carrying
|
|
388
|
+
worker calls `artifact_edit`.
|
|
389
|
+
|
|
390
|
+
If the poll tick is down, reissues stay staged and unclaimed — the task
|
|
391
|
+
sits at `publish: edit-issued` (or `publish: publish-requested` for a
|
|
392
|
+
staged reissue) with no new issuance, and eventually the 2-hour total
|
|
393
|
+
budget expires and the ack scan writes the terminal
|
|
394
|
+
`publish: version-timeout`. The timeout is honest: the budget is measured
|
|
395
|
+
from the first issuance, not from the last successful observation, so a
|
|
396
|
+
dead issuer cannot stretch it.
|
|
397
|
+
|
|
398
|
+
**Runbook:** check the poll tick first — is `crew-poll-*` enabled and
|
|
399
|
+
producing recent tick reports? If yes, the stuck task is a publish-path
|
|
400
|
+
problem (diagnose via `docs/publish-verification.md`'s Operator recovery).
|
|
401
|
+
If no, the ack scan's staged reissues are waiting on an issuer that isn't
|
|
402
|
+
running — fix the poll tick, not the publish machine. In either case, do
|
|
403
|
+
not hand-issue the edit: issuance belongs to the tick worker's
|
|
404
|
+
claim-and-issue path, and an out-of-band edit has no ledger anchor for
|
|
405
|
+
the ack scan to verify against.
|
|
370
406
|
|
|
371
407
|
## Identities
|
|
372
408
|
|
|
@@ -1,120 +1,28 @@
|
|
|
1
|
-
# Publish-unknown recovery
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
│
|
|
30
|
-
├─ tick runs lib/classify-publish-absence.js, then
|
|
31
|
-
│ record-unknown-classification (CAS on the claim expiry):
|
|
32
|
-
│
|
|
33
|
-
├─ verified ──────────> publish: verification-requested <commit>
|
|
34
|
-
│ (content IS present; Step 4.5 verifies;
|
|
35
|
-
│ ledger: unknown-resolved)
|
|
36
|
-
├─ provably-dropped ──> publish: dropped <commit>
|
|
37
|
-
│ (queues the retry protocol, next tick)
|
|
38
|
-
├─ applied-not-built / ambiguous ──> publish: ambiguous <commit>
|
|
39
|
-
│ (terminal; ledger: unknown-classified)
|
|
40
|
-
├─ superseded ────────> publish: superseded <commit> (terminal)
|
|
41
|
-
└─ deferred ──────────> (no note; the claim expires; the next scan
|
|
42
|
-
re-claims and re-classifies)
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Retry protocol (Step 4.4, for `publish: dropped` with a free budget):
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
publish: dropped
|
|
49
|
-
── tick: HEAD == commit? no ──> publish: retry-superseded (terminal)
|
|
50
|
-
── tick: acquire merge lock publish-retry:<task> (600s); held ──> stop, retry next tick
|
|
51
|
-
── tick: re-read content in the lock (classifier)
|
|
52
|
-
├─ not provably-dropped ──> record-retry-recheck routes it
|
|
53
|
-
│ (verified → verification-requested; ambiguous → terminal;
|
|
54
|
-
│ superseded → terminal; deferred → no-op)
|
|
55
|
-
└─ provably-dropped ──> publish: retry-intended <commit> <ts>
|
|
56
|
-
── tick issues the edit DIRECTLY in its own turn (never a child;
|
|
57
|
-
blocker 22 — children cannot reach artifact_edit)
|
|
58
|
-
├─ explicit refusal ──> publish: retry-refused (terminal)
|
|
59
|
-
└─ no refusal ──> publish: retry-issued <commit>
|
|
60
|
-
──> publish: verification-requested <commit> not-before=<ts+20m>
|
|
61
|
-
(Step 4.5 skips not-before entries until the window passes)
|
|
62
|
-
──> one -retry1 ledger entry (best-effort)
|
|
63
|
-
── release the merge lock (every path)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Crash recovery (fail closed, never re-trigger blind):
|
|
67
|
-
|
|
68
|
-
- `publish: retry-intended` without `retry-issued` → the next scan mirrors
|
|
69
|
-
`verification-requested` with `not-before=intended+20m`. The trigger may
|
|
70
|
-
or may not have gone out; content verification is the arbiter.
|
|
71
|
-
- `publish: retry-issued` without a mirrored request → the next scan writes
|
|
72
|
-
the missing mirror.
|
|
73
|
-
- Exactly one retry per task, enforced from note history: the scan emits
|
|
74
|
-
`retry_due` only when no `publish: retry-issued` exists in the task's
|
|
75
|
-
history — including a retry for an earlier unknown attempt on a reworked
|
|
76
|
-
task. When the budget is spent, the drop is terminal: `publish: ambiguous`.
|
|
77
|
-
|
|
78
|
-
## Commands
|
|
79
|
-
|
|
80
|
-
- `scan-publish-unknown` — the cron scan (Step 4.5 of the old numbering).
|
|
81
|
-
Returns `{ waiting, due, retry_due, mirrored, skipped }`. `due` entries
|
|
82
|
-
ferry the classifier inputs: `commit`, `trigger_ts` (from the `submitted`
|
|
83
|
-
ledger entry — never a time window), `park_ts`, `slug`, `repo_path`,
|
|
84
|
-
`base` (provenance `source_commit`, else the empty tree), and
|
|
85
|
-
`claim_expiry` for the record CAS. `retry_due` entries ferry the retry
|
|
86
|
-
inputs (`commit`, `attempt`, `slug`, `repo_path`, `base`, `ledger_path`).
|
|
87
|
-
- `record-unknown-classification --json '{task_id, claim_expiry, decision}'`
|
|
88
|
-
— routes the classifier's decision; CAS on the claim expiry (a stale tick
|
|
89
|
-
records nothing).
|
|
90
|
-
- `record-retry-recheck --json '{task_id, decision}'` — routes the retry
|
|
91
|
-
protocol's in-lock content re-read; CAS on latest being `publish: dropped`.
|
|
92
|
-
- `resolve-publish-unknown` — the manual one-shot for a single parked task
|
|
93
|
-
(unchanged; the audit-window contract, not the classifier).
|
|
94
|
-
|
|
95
|
-
## Classifier (lib/classify-publish-absence.js)
|
|
96
|
-
|
|
97
|
-
Decides, from the task's repo and the platform's on-disk state, whether the
|
|
98
|
-
dropped edit is proven:
|
|
99
|
-
|
|
100
|
-
- `verified` — the content IS present (the platform applied it; the receipt
|
|
101
|
-
was the only thing lost). Never re-issue.
|
|
102
|
-
- `provably-dropped` — the old source is live AND the manifest shows no
|
|
103
|
-
build since the trigger. Only this decision may retry.
|
|
104
|
-
- `applied-not-built` — the new source is live but no build ran (the
|
|
105
|
-
trigger reached the platform but the build didn't). Ambiguous outcome,
|
|
106
|
-
terminal: a retry would double-apply.
|
|
107
|
-
- `ambiguous` — the content check is inconclusive. Never retry blind.
|
|
108
|
-
- `deferred` — not yet quiesced; re-check next tick.
|
|
109
|
-
- `superseded` — HEAD moved past the attempt's commit. Terminal.
|
|
110
|
-
|
|
111
|
-
The classifier's verified path requires the CURRENT manifest to be a new
|
|
112
|
-
build identity: `built_at` advanced past the trigger AND `content_sha256`
|
|
113
|
-
differs from the pre-trigger baseline the workflow snapshots into the
|
|
114
|
-
submitted ledger entry (design §1.9 — defeats a replayed manifest). When
|
|
115
|
-
the baseline is unavailable the classifier falls back to the time-based
|
|
116
|
-
advance check and notes it; the Step 4.5 verifier (`lib/verify-publish.js`)
|
|
117
|
-
is the strict gate and fails closed without a baseline before stamping.
|
|
118
|
-
Build-in-flight is checked twice bracketing the content read via manifest
|
|
119
|
-
state change (no mtime heuristics), and the suite carries add-only and
|
|
120
|
-
removal-only regression fixtures.
|
|
1
|
+
# Publish-unknown recovery — RETIRED (0.14.6)
|
|
2
|
+
|
|
3
|
+
The unknown-recovery machinery (the `publish: unknown-*` note state machine,
|
|
4
|
+
`lib/classify-publish-absence.js`, `lib/retry-publish.js`,
|
|
5
|
+
`lib/resolve-publish-unknown` flow) is RETIRED as of 0.14.6 (2026-09-20).
|
|
6
|
+
|
|
7
|
+
It is subsumed by the version attempt loop: a 30-minute acknowledgement
|
|
8
|
+
window per attempt, fresh per-attempt versions with immediate reissue (no
|
|
9
|
+
backoff), a 2-hour total budget from first issuance, and `scan-ack-pending`
|
|
10
|
+
as the sole verdict. There is no separate unknown state — silence within
|
|
11
|
+
the window is unknown and fails closed; expiry re-issues with a fresh version;
|
|
12
|
+
budget exhaustion parks `publish: version-timeout`.
|
|
13
|
+
|
|
14
|
+
See `docs/publish-verification.md` for the current protocol.
|
|
15
|
+
|
|
16
|
+
## Historical note
|
|
17
|
+
|
|
18
|
+
The unknown path existed because the old two-party publish fired the
|
|
19
|
+
artifact edit through a trigger child and got no receipt back — async was
|
|
20
|
+
planned for, receipt-less was not. The one-party publish (2026-09-20,
|
|
21
|
+
blocker 22) retired the trigger child: the workflow parks at intent, the
|
|
22
|
+
session-carrying tick worker issues directly, and the ack scan attributes
|
|
23
|
+
by exact version. The `publish: ambiguous`, `publish: retry-superseded`,
|
|
24
|
+
`publish: retry-refused`, `publish: verified`, `publish: superseded`,
|
|
25
|
+
`publish: verification-failed`, and `publish: intent-unverifiable` verbs
|
|
26
|
+
remain in the vocabulary's legacy registry — the exact list in
|
|
27
|
+
`lib/publish-note-vocabulary.js` (no wildcard families). Recognized so old
|
|
28
|
+
note history skips as terminal, never written anew.
|