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.
Files changed (41) hide show
  1. package/API.md +106 -12
  2. package/docs/decisions/AGENTS.md +5 -0
  3. package/docs/decisions/publish-path.md +247 -9
  4. package/docs/decisions/qa-reproduce.md +30 -0
  5. package/docs/guide.md +41 -5
  6. package/docs/publish-unknown-recovery.md +28 -120
  7. package/docs/publish-verification.md +148 -501
  8. package/lib/AGENTS.md +9 -13
  9. package/lib/advance-publish-base.js +3 -3
  10. package/lib/compose-evidence-caption.js +1 -2
  11. package/lib/compute-publish-diff.js +82 -5
  12. package/lib/crew-api.js +1134 -1027
  13. package/lib/merge-lock.sh +153 -41
  14. package/lib/publish-note-vocabulary.js +135 -42
  15. package/lib/qa-db.js +132 -0
  16. package/lib/schema.sql +69 -0
  17. package/lib/serve-artifact.js +46 -2
  18. package/lib/test-detached-integrate.sh +122 -0
  19. package/lib/test-merge-lock.sh +30 -1
  20. package/lib/worktree-lifecycle.sh +284 -34
  21. package/package.json +1 -1
  22. package/seed/AGENTS.md +1 -0
  23. package/seed/cron-body-ack-scan.md +44 -0
  24. package/seed/cron-body-template.md +55 -89
  25. package/seed/crons.json +12 -0
  26. package/workflows/AGENTS.md +1 -1
  27. package/workflows/bugfix.js +489 -224
  28. package/workflows/chore.js +306 -226
  29. package/workflows/crew-dispatch.js +57 -4
  30. package/workflows/crew-init.js +23 -0
  31. package/workflows/crew-uninstall.js +7 -4
  32. package/workflows/docs.js +24 -2
  33. package/workflows/standard.js +494 -251
  34. package/workflows/upgrade.js +13 -1
  35. package/lib/build-readback-request.js +0 -140
  36. package/lib/check-intent-freshness.js +0 -101
  37. package/lib/classify-publish-absence.js +0 -462
  38. package/lib/publish-content.js +0 -154
  39. package/lib/readback-disk.js +0 -195
  40. package/lib/retry-publish.js +0 -417
  41. 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 content verification
370
+ ### Publish acknowledgement
368
371
 
369
- 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, and since 2026-09-20 (blocker 22) it doesn't even 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, pre-trigger manifest baseline — writes one issuer-stamped `publish-intent` ledger entry, and parks with `publish: publish-requested <commit> <attempt>`. The session-carrying tick worker (the only caller class that can reach `artifact_edit`) claims the intent, verifies the diff's sha256, issues the edit directly in its own turn, and records the issuer-stamped `submitted` entry plus `publish: verification-requested`. An unobserved outcome is an unknown outcome, parked fail-closed with an append-only `unknown` ledger entry — never stamped. The parent protocol owns the independent content confirmation: `lib/readback-disk.js` reads the platform's on-disk working copy of the artifact source and `lib/verify-publish.js` compares it mechanically against the base..commit diff (see `docs/publish-verification.md`); only a match stamps provenance. QA's provenance check then enforces the stamp mechanically, so an unverified publish fails loudly in QA instead of passing silently.
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 (blocker 15, 2026-09-18; one-party 2026-09-20)
2
-
3
- When a standard/bugfix Publish parks with "Publish outcome unknown", the
4
- artifact-edit trigger went out fire-and-forget and no receipt came back —
5
- async was planned for, receipt-less was not. The unknown-recovery loop
6
- closes that gap without re-issuing blindly.
7
-
8
- (2026-09-20, blocker 22: the trigger child is retired. New publishes park
9
- at intent, not unknown — see the one-party section of
10
- docs/publish-verification.md. This document's unknown path is the legacy
11
- recovery for pre-one-party parks, plus the retry protocol, which now issues
12
- through the session-carrying tick worker directly — never a child.)
13
-
14
- ## The note is the state machine
15
-
16
- Recovery state lives in the task's `note` events, keyed on machine-written
17
- `publish: <transition>` markers. Deterministic code (`lib/crew-api.js`) owns
18
- every transition; the cron tick (Step 4.4) is only the ferry between the
19
- deterministic steps. The initial park note is the workflow's
20
- "Publish outcome unknown …" note (no `publish:` marker — the scan matches it
21
- explicitly); every transition after that is machine-written.
22
-
23
- State diagram (latest `publish:` note wins; history is the retry budget):
24
-
25
- ```
26
- "Publish outcome unknown" park
27
- (< 30m) ──waiting──> scan: waiting (leave alone)
28
- (>= 30m) ──scan──> publish: unknown-recovery-claimed <expiry> (1h lease)
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.