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
@@ -6,8 +6,9 @@ You are the dispatch trigger for Muse Crew. Run the authoritative dispatcher wor
6
6
 
7
7
  The failures below are settled and recorded in the Gate 1 OODA state. The authoritative list lives at `~/workspace/goals/gate-1-onboarding-canary-passes/hidden_files/ooda/known-blockers.md` — if this copy ever disagrees with that file, the file wins. Their fixes are already queued in the main repo BUILD. If you observe one, log exactly one line — `known-blocker: <id> — <one-line observation>` — and move on. Do NOT write root-cause analysis and do NOT write "follow-up needed"; the follow-up is already owned.
8
8
 
9
- - **Blocker 22 — wrong publication caller (settled 2026-09-20):** workflow children cannot call `artifact_edit` (the builder requires a parent-conversation session id they don't have). The 0.14.x trigger/observation path is dead by architecture; `submitted` writes from it are phantom. Fix in BUILD: one-party worker-owned artifact publication.
10
- - **Blocker 23 — retry harness cannot access Crew state (settled 2026-09-20):** `lib/retry-publish.js` imported `better-sqlite3` (not shipped; the release uses `node:sqlite`) and targeted `crew.db` instead of the real `crew-state.db`. Every retry prepare returned `db-unavailable` and failed closed before any lock, edit, or journal write. Fixed in BUILD (0.14.5): ported to `node:sqlite` + `$CREW_HOME/crew-state.db`, the missing `CREW_REPO` for the merge lock supplied, and the trigger step converted from a spawned child to the session-carrying tick worker's direct `artifact_edit` call (blocker 22). The retry path is worker-owned issuance now; it is subsumed under one-party publish, not a separate queue.
9
+ - **Blocker 22 — wrong publication caller (settled 2026-09-20):** workflow children cannot call `artifact_edit` (the builder requires a parent-conversation session id they don't have). The 0.14.x trigger/observation path is dead by architecture; `submitted` writes from it are phantom. Fix in BUILD: one-party worker-owned artifact publication. (0.14.6: publication is a handoff with strong suggestions, not deterministic patch application. Each attempt carries a fresh deterministic version — `sha256("publish-version:v1:" + task_id + ":" + commit + ":" + attempt)` — synthesized into the staged diff as `.crew-publish-version`. Acknowledgement is the exact version string echoed, nothing else — Eric's contract: "If we hear that the artifact acknowledges our version, that's it. We don't verify against content.")
10
+ - **Blocker 23 — retry harness cannot access Crew state (settled 2026-09-20):** `lib/retry-publish.js` imported `better-sqlite3` (not shipped; the release uses `node:sqlite`) and targeted `crew.db` instead of the real `crew-state.db`. Every retry prepare returned `db-unavailable` and failed closed before any lock, edit, or journal write. Fixed in BUILD (0.14.5): ported to `node:sqlite` + `$CREW_HOME/crew-state.db`, the missing `CREW_REPO` for the merge lock supplied, and the trigger step converted from a spawned child to the session-carrying tick worker's direct `artifact_edit` call (blocker 22). (0.14.6: the retry harness, the publish-absence classifier, and the content-verification chain are RETIRED — subsumed into the single version attempt loop: a 30-minute window per attempt with immediate reissue (no backoff), a 2-hour total budget from first issuance, and the ack scan as the sole verdict. There is no separate retry queue and no content verifier.)
11
+ - **Blocker 28 — Step 0 sees completed runs too (settled 2026-09-20):** a COMPLETED platform run can still contain FAILED `agent()` calls — the old Step 0 query (failed runs only) was blind to it. The Step 0 check now also sweeps completed runs in the last 15 minutes with failed agent() calls, through the same `record-platform-failure` / `retry-platform-failure` path. If you observe a failed agent() call inside a completed run, log `known-blocker: 28 — <one line>` and move on.
11
12
 
12
13
  (Maintainer note: this snapshot is refreshed from known-blockers.md at release time. The poller always treats the file as authoritative.)
13
14
 
@@ -27,13 +28,22 @@ The failures below are settled and recorded in the Gate 1 OODA state. The author
27
28
  `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-platform-failure --json '{"platform_run_id": "<run_id>", "error_message": "<error>", "platform_created_at": "<created_at>"}'`
28
29
  - Then retry each failure (clears stale reservation, re-queues task, or parks after 3 attempts):
29
30
  `node {crewHome}/lib/crew-api.js --crew-home {crewHome} retry-platform-failure --json '{"platform_run_id": "<run_id>"}'`
31
+ - **Also** sweep COMPLETED runs containing failed agent() calls (2026-09-20, blocker 28 — the failed-runs query above is blind to a run that completed with a dead child):
32
+ ```sql
33
+ SELECT w.run_id, w.created_at, c.error
34
+ FROM runtime.workflow_runs w
35
+ JOIN runtime.workflow_agent_calls c ON c.run_id = w.run_id AND c.status = 'failed'
36
+ WHERE w.created_at > now() - interval '15 minutes' AND w.status = 'completed'
37
+ ORDER BY w.created_at DESC
38
+ ```
39
+ - Route each completed-run failure through the same two commands — `record-platform-failure`, then `retry-platform-failure` — exactly as above.
30
40
  - Log the results. This ensures transient platform failures don't strand tasks.
31
41
 
32
42
  1. **Load tools:** Call tool_search_load_tool_namespace with paths ["workflow_launch"].
33
43
 
34
- 2. **Load the workflow registry:** Read the file "{crewHome}/workflows/registry.json" with the read tool and parse it as JSON. If the file does not exist (the live release predates the registry), proceed without it — omit the `registry` arg and the dispatcher will load the registry the slow way and log a warning. If the read FAILS on a file that exists (transient read error — observed 2026-09-13; the file itself was healthy and later ticks read it fine), retry the read once; if it still fails, write the error text into your run summary (observability — never silently swallow a failed read) and proceed without the registry the same way.
44
+ 2. **Load the workflow registry:** Read the file "{crewHome}/workflows/registry.json" with the read tool and keep its RAW TEXT — do NOT parse it, do NOT re-emit it as a JSON object. Pass the text verbatim: never reformat, summarize, or hand-transcribe it (room #26: 15+ launch rejections 09-19→09-21 from garbled nested-object args, and one hallucinated registry that passed validation). If the file does not exist (the live release predates the registry), proceed without it — omit the `registryText` arg and the dispatcher will load the registry the slow way and log a warning. If the read FAILS on a file that exists (transient read error — observed 2026-09-13; the file itself was healthy and later ticks read it fine), retry the read once; if it still fails, write the error text into your run summary (observability — never silently swallow a failed read) and proceed without the registry the same way.
35
45
 
36
- 3. **Run the dispatcher:** Call workflow_launch with scriptPath "{crewHome}/workflows/crew-dispatch.js" and args {"crewHome": "{crewHome}", "registry": <parsed registry JSON, or omit the key when the file was missing>}.
46
+ 3. **Run the dispatcher:** Call workflow_launch with scriptPath "{crewHome}/workflows/crew-dispatch.js" and args {"crewHome": "{crewHome}", "registryText": <raw registry file text, or omit the key when the file was missing>}. The dispatcher parses the text itself — a nested-object `registry` arg is not accepted. If the launch is rejected, retry once with only {"crewHome": "{crewHome}"} (the sanctioned fallback — the dispatcher loads the registry the slow way); a second rejection is a platform problem, not an args problem — write it in your run summary and move on.
37
47
 
38
48
  Wait for it to complete. It reads the crew's task state, determines eligibility, claims tasks, acknowledges the poll, and returns structured results.
39
49
 
@@ -44,99 +54,55 @@ The failures below are settled and recorded in the Gate 1 OODA state. The author
44
54
  - If the acknowledge fails (no reservation exists), DO NOT LAUNCH — the dispatcher did not acquire this task. This is a safety invariant.
45
55
  - The launched workflow self-claims the task and clears the reservation as its first actions. If the task was already claimed or is done, the claim fails closed and the run stands down quietly — this is the mechanical duplicate protection, not an error.
46
56
 
47
- If the dispatcher returned no claims or the claims array is empty, log NO_DISPATCH and CONTINUE to Step 4.4 — do NOT exit. Unknown-recovery (4.4), verification (4.5) and evidence (6) run independently of dispatch claims. A no-claims tick must still recover parked publishes, verify them, and deliver evidence. (Fixed 2026-09-14: the old "exit on NO_DISPATCH" skipped verification permanently.)
48
-
49
- 4.4. **Unknown-recovery (docs/publish-unknown-recovery.md):** tasks parked with "Publish outcome unknown" are the receipt-less trigger gap (2026-09-18, blocker 15). The note is the state machine; deterministic code owns every transition; you are the ferry (scan → classifier → record / retry protocol). Run this BEFORE Step 4.5 so a re-triggered build's mirrored verification-requested is visible to the verification scan on a later tick. Log every list the scan returns.
50
- - Scan (code): `node {crewHome}/lib/crew-api.js --crew-home {crewHome} scan-publish-unknown`
51
- Returns `{ waiting: [...], due: [...], intent: [...], retry_due: [...], mirrored: [...], skipped: [...], requeued: [...] }`. `waiting` = parked < 30m — leave alone, no claim. `mirrored`/`skipped` = already handled by code — log only, take no action. `requeued` = stale-base intents sent back to the workflow's Publish for re-preparation — log only, take no action. `intent` = claimed publish intents — handle in Step 4.4b.
52
- - For each entry in `due`: classify (code), then record the decision (code). Use the entry's fields verbatim.
53
- - `node {crewHome}/lib/classify-publish-absence.js --repo-path "<repo_path>" --commit <commit> --base <base> --slug "<slug>" --trigger-ts "<trigger_ts>" --park-ts "<park_ts>" --task-id <task_id> --manifest-before '<manifest_before JSON>'`
54
- Serialize the entry's `manifest_before` field to compact JSON for the `--manifest-before` value; when the entry's `manifest_before` is null, omit the flag (the classifier falls back to the time-based advance check and the Step 4.5 verifier fails closed without a baseline).
55
- Exit 0 prints the decision JSON (`{ ok:true, decision, reasons, details }`) — or `{ ok:false, ... }` for a semantic non-verdict. Exit non-zero (usage/git/IO failure) → log the stderr line and LEAVE THE TASK: do NOT record anything; the claim expires and the next tick re-claims. A classifier crash is never a verdict.
56
- - `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-unknown-classification --json '{"task_id": "<task_id>", "claim_expiry": "<claim_expiry>", "decision": <classifier stdout JSON>}'`
57
- Paste the classifier's stdout verbatim as the decision value. If it returns `recorded: false` → log the reason and continue (another tick owns the task). The routes are the code's — log the returned decision and `routed` value:
58
- verified → `publish: verification-requested` (no re-trigger); provably-dropped → `publish: dropped` (queues the retry protocol); applied-not-built / ambiguous → `publish: ambiguous` (terminal); superseded → `publish: superseded` (terminal); deferred → nothing (the claim expires; the next scan re-claims).
59
- - Never invent a decision. Never re-trigger the edit for a `due` entry — classification only.
60
- - For each entry in `retry_due` (a provably-dropped edit with a free retry budget — exactly one retry per task, enforced from note history; the scan only emits `retry_due` when no `publish: retry-issued` exists in the task's history): run the deterministic retry protocol via `lib/retry-publish.js`. The command owns all lock handling, journaling, diff generation, ledger writes, and cleanup with try/finally lock release — you only mediate the unavoidable `artifact_edit` tool call.
61
-
62
- Phase 1 — prepare (deterministic):
63
- `node {crewHome}/lib/retry-publish.js --crew-home {crewHome} --task-id <task_id> --phase prepare`
64
- - Exit 0 with `{"ok": true, "outcome": "ready", ...}`: the merge lock is HELD. The output carries `diff_path`, `diff_sha256`, and `slug`. Proceed to the trigger below.
65
- - Exit 0 with `{"ok": true, "outcome": "superseded"|"lock-held"|"not-dropped", ...}`: terminal for this tick. Do NOT proceed to the trigger. If `outcome` is `not-dropped`, the output carries the full `classification` — pass it to `record-retry-recheck`: `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-retry-recheck --json '{"task_id": "<task_id>", "decision": <classification JSON>}'` and STOP for this task.
66
- - Exit non-zero: the command failed (lock released via try/finally). Log the stderr line and STOP — do NOT trigger.
67
-
68
- Trigger (tick-mediated — the ONLY prose-owned step):
69
- Load the artifact namespace in YOUR OWN turn (`tool_search.load_tool_namespace` with paths `["artifact"]`) and call `artifact_edit` directly — NEVER spawn a child (blocker 22, room #24: children cannot reach artifact_edit). First verify the staged diff: `sha256sum "<diff_path>"` must print exactly `<diff_sha256>`; if it does not, STOP and end this task's retry (log loudly — the claim expires, the next tick re-claims; a mismatched diff never becomes an edit). Read the verified file's full content and call artifact_edit with slug `"<slug>"` and this verbatim_request:
70
- 'Apply the following change to your source tree, then rebuild and deploy.
71
-
72
- UNIFIED DIFF (relative to your source tree):
73
- ```diff
74
- <the full content of the verified file, pasted verbatim>
75
- ```
57
+ If the dispatcher returned no claims or the claims array is empty, log NO_DISPATCH and CONTINUE to Step 4.4 — do NOT exit. Intent scan (4.4), ack scan (4.5) and evidence (6) run independently of dispatch claims. A no-claims tick must still issue parked publish intents, scan for acknowledgements, and deliver evidence. (Fixed 2026-09-14: the old "exit on NO_DISPATCH" skipped publication permanently.)
76
58
 
77
- Rules:
78
- - For each file in the diff, apply its hunks to the same path in your source tree (use git apply or equivalent).
79
- - For a new file (--- /dev/null), create it with the added (+) lines as its full content.
80
- - For a deleted file (+++ /dev/null), delete it.
81
- - If any hunk does not apply cleanly, STOP and report the failure — do not improvise or skip hunks.
82
- - Do not make any other source changes.
83
- - After applying, rebuild and deploy.'
84
- Edit-request contract:
85
- - Call artifact_edit exactly once with the slug and verbatim_request above. Never retry the edit yourself: if the edit is not accepted, do NOT call artifact_edit again — proceed to the complete phase below.
86
- - If artifact_edit explicitly refuses the edit (the call is rejected — e.g. the artifact does not exist), do NOT call artifact_edit again: record the first line of the refusal for the complete phase below.
87
- - If artifact_edit is not available after the load, do NOT improvise — log it and proceed to the complete phase with `refused` and the refusal text "artifact tool namespace unavailable in tick".
88
-
89
- Phase 2 — complete (deterministic):
90
- `node {crewHome}/lib/retry-publish.js --crew-home {crewHome} --task-id <task_id> --phase complete --edit-outcome <accepted|refused> [--refusal-text "<first line>"]`
91
- - If artifact_edit explicitly refused the edit: `--edit-outcome refused --refusal-text "<first line of the refusal>"`.
92
- - Otherwise: `--edit-outcome accepted`.
93
- - The command writes the outcome notes, appends the `-retry1` ledger entry (issuer `tick-worker`, with the pre-retry `manifest_before` baseline), mirrors verification-requested, and RELEASES the lock in a finally block. Exit 0 means the protocol completed.
94
-
95
- - Crash rules (fail closed, never re-trigger blind): if this tick dies anywhere in the retry protocol, the note journal is the recovery state. `publish: retry-intended` without `retry-issued` → the next scan mirrors `verification-requested` (never re-triggers). `publish: retry-issued` without a mirrored request → the next scan mirrors it. A leaked merge lock expires after 600s. The next tick's scan never emits `retry_due` for a task with `retry-issued` in history — exactly one retry, enforced from notes, not from the ledger.
96
-
97
- 4.4b. **One-party intent issuance (2026-09-20, blocker 22):** the workflow parks at Publish with a `publish: publish-requested` note and a checksummed `publish-intent` ledger entry; issuance belongs to YOU, the session-carrying tick worker, calling `artifact_edit` directly in this turn. The Step 4.4 scan returns the `intent: [...]` bucket — each entry is claimed for this tick (1-hour lease) and carries `task_id`, `commit`, `attempt`, `slug`, `repo_path`, `base`, `diff_path`, `diff_sha256`, `manifest_before`, `claim_expiry`, and `reclaimed`.
98
- - For each intent entry:
99
- 1. **Verify the staged diff (code):** `sha256sum "<diff_path>"` must print exactly `diff_sha256`. If the file is missing, regenerate deterministically and re-verify:
100
- `node {crewHome}/lib/compute-publish-diff.js --repo-path "<repo_path>" --base <base> --commit <commit> --out "<diff_path>"`
101
- then re-run the sha256sum. The `--commit` flag is honored: the script diffs `base..<commit>` (never an implicit HEAD) and reports that target as `commit`. If the regeneration exits non-zero, or the hash still does not match after regeneration, the diff is UNISSUABLE — record it terminally (code, never prose):
59
+ 4.4. **Publish intent scan (0.14.6):** the workflow parks at Publish with a version-carrying publish-intent ledger entry; issuance belongs to the tick. Deterministic code owns every transition; you are the ferry (scan → issue → record). Run this BEFORE Step 4.5 so an issued edit's ack window is open to the acknowledgement scan on a later tick. Log every list the scan returns.
60
+ - Scan (code): `node {crewHome}/lib/crew-api.js --crew-home {crewHome} scan-publish-intent`
61
+ This atomically claims parked publish intents for this tick (1-hour lease, so a second tick cannot double-issue). It returns `{ intent: [...], skipped: [...], requeued: [...] }`. Each intent entry carries `task_id`, `commit`, `attempt`, `slug`, `repo_path`, `project_id`, `ledger_path`, `base`, `diff_path`, `diff_sha256`, `version` (`<commit>:<attempt>`), `files` (changed paths), `claimed_at`, `claim_expiry`, `reclaimed` (true when a previous tick died mid-issuance). Copy the entry's fields verbatim — never re-derive them.
62
+ - `skipped` = log only, take no action. `requeued` (the intent was superseded — a newer commit restaged and the task is back in the workflow) = log only, take no action.
63
+ - For each entry in `intent`: issue it in Step 4.4b. Issuance is one-party and version-bound; the suggestion handoff and the record protocol live there.
64
+
65
+ 4.4b. **One-party intent issuance (0.14.6, blocker 22):** publication is a handoff with strong suggestions — byte-for-byte control is not the contract. The staged diff already contains the synthesized protocol receipt `.crew-publish-version` (content = this attempt's version + newline); EXACT acknowledgement of the version is the sole positive completion criterion (Eric's contract). Issuance belongs to YOU, the session-carrying tick worker, calling `artifact_edit` directly in this turn. Never spawn a child for it (blocker 22: children cannot reach `artifact_edit`).
66
+ - For each intent entry from the 4.4 scan (copy the entry's `task_id`, `commit`, `attempt`, `version`, and `claim_expiry` verbatim — never re-derive them):
67
+ 1. **Verify the staged diff (code):** `sha256sum "<diff_path>"` must print exactly `diff_sha256`. If the file is missing, regenerate deterministically — the version pair regenerates the identical version and identical bytes — and re-verify:
68
+ `node {crewHome}/lib/compute-publish-diff.js --repo-path "<repo_path>" --base <base> --commit <commit> --task-id "<task_id>" --attempt <attempt> --out "<diff_path>"`
69
+ then re-run the sha256sum. If the regeneration exits non-zero, or the hash still does not match after regeneration, the diff is UNISSUABLE — record it terminally (code, never prose):
102
70
  `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-intent-unissuable --json '{"task_id": "<task_id>", "claim_expiry": "<claim_expiry>", "reason": "<missing-staged-diff|diff-unregenerable|hash-mismatch-after-regeneration>"}'`
103
71
  (copy `claim_expiry` verbatim from the scan entry — it is the compare-and-swap). This writes the terminal `publish: publish-unissuable` note and an issuer-stamped ledger entry — parked for human attention. Never re-claim, never loop on an hourly reclaim; a diff that cannot be checksummed never becomes an edit.
104
- 2. **Freshness check on a re-claimed intent (code, never prose):** when the entry's `reclaimed` is true, a previous tick died mid-issuance and may have issued the edit without recording it. Run the deterministic freshness helper — it reads the on-disk artifact manifest at `~/workspace/ts-spaces/<slug>/.space-build/manifest.json` (the same path the verifier uses) and compares `content_sha256`/`built_at` against the entry's `manifest_before`:
105
- `node {crewHome}/lib/check-intent-freshness.js --slug "<slug>" --manifest-before '<manifest_before JSON>'`
106
- (copy the entry's `manifest_before` verbatim as JSON; use the entry's `slug` verbatim). The helper prints exactly one JSON object: `{ok:true, advanced:<true|false>, baseline:{...}, current:{...}}`. If `advanced` is true → the edit is already live: record `recovered` in step 4 below (passing the helper's stdout JSON as `freshness_evidence`) and NEVER re-issue. If `advanced` is false → the manifest still shows the pre-edit state: proceed to issuance. If the helper exits non-zero or prints `ok:false` → the freshness question is unanswerable: record NOTHING, log loudly in one line, and let the claim expire — the next scan re-claims. Never decide this from prose, from `artifact_status`, or from an inspector's summary.
107
- 3. **Issue the edit directly — in this turn, never in a child.** Blocker 22 is proven: workflow children cannot reach `artifact_edit` (room #24's four journeys died on exactly this). Load the artifact namespace in your own turn and call `artifact_edit` with the entry's `slug` and this verbatim_request (paste the verified diff file's full content verbatim):
108
- 'Apply the following change to your source tree, then rebuild and deploy.
72
+ 2. **Re-claim check — the version receipt decides (code):** when the entry's `reclaimed` is true, a previous tick died mid-issuance and may have issued the edit without recording it. Read the artifact's on-disk version receipt `~/workspace/ts-spaces/<slug>/.crew-publish-version-<task_id>` (trim whitespace) and compare it to the entry's `version`. If it EQUALS the dead attempt's version → the edit landed: call `record-version-ack --json '{"task_id":"<task_id>","version":"<entry version>","via":"disk"}'` and NEVER re-issue (report `receipt_check: "match"` for the record). If it differs → `receipt_check: "different"`. If the file is missing or unreadable → `receipt_check: "unreadable"`. A differing or unreadable receipt is NOT proof the edit did not land — the async edit may have issued while the receipt was never applied or is not yet visible. Never decide this from prose, from `artifact_status`, or from an inspector's summary. You MUST run this check and report its exact outcome to the record step (step 4) — the record API refuses reclaimed claims without an explicit `receipt_check`.
73
+ 3. **Issue the edit directly — in this turn, never in a child.** Load the artifact namespace in your own turn (`tool_search.load_tool_namespace` with paths `["artifact"]`) and call `artifact_edit` with the entry's `slug` and this suggestion handoff (paste the verified staged diff file's full content):
74
+ 'Adopt the following change into your source tree, then rebuild and redeploy.
75
+
76
+ CHANGE: task <task_id>, commit <first 12 hex of commit>. Files: <comma-separated files list from the entry's `files`>.
109
77
 
110
- UNIFIED DIFF (relative to your source tree):
78
+ SUGGESTED PATCH (staged unified diff, relative to your source tree — treat as a strong suggestion, apply with judgment):
111
79
  ```diff
112
- <the full content of the verified file, pasted verbatim>
80
+ <the full content of the verified staged file, pasted>
113
81
  ```
114
82
 
115
- Rules:
116
- - For each file in the diff, apply its hunks to the same path in your source tree (use git apply or equivalent).
117
- - For a new file (--- /dev/null), create it with the added (+) lines as its full content.
118
- - For a deleted file (+++ /dev/null), delete it.
119
- - If any hunk does not apply cleanly, STOP and report the failure — do not improvise or skip hunks.
120
- - Do not make any other source changes.
121
- - After applying, rebuild and deploy.'
122
- Call artifact_edit exactly once. Never retry it yourself. If the call is explicitly refused, note the first line of the refusal text.
123
- `accepted` means exactly one thing: the direct `artifact_edit` call completed without error and without an explicit refusal. Tool unavailable (the artifact namespace would not load), a timeout, a transport error, or any otherwise ambiguous result is NOT accepted and NOT refused — it is inconclusive: record NOTHING for this task, log the ambiguity in one line, and let the claim expire; the next scan re-claims and step 2's freshness check decides. An unrecorded outcome is the only honest record of an unknown one.
83
+ PROTOCOL (the one hard requirement):
84
+ - One file in the patch is `.crew-publish-version-<task_id>` — a protocol receipt, not product code. Apply it like any other file.
85
+ - When you report the result, quote the exact 64-character version `<version>` verbatim in your report text. If you cannot or will not apply this change, say REFUSED and quote the version anyway.
86
+ - Do not make any other source changes.'
87
+ The diff and rules are suggestions now, not byte-exact commands (this also sidesteps the platform newline-escaping defect on verbatim requests — the receipt file is what matters, not byte fidelity).
88
+ Edit-request contract: call artifact_edit exactly once. Never retry the edit yourself. Tool unavailable (the artifact namespace would not load), a timeout, a transport error, or any otherwise ambiguous result is inconclusive: record NOTHING for this task, log the ambiguity in one line, and let the claim expire; the next scan re-claims and step 2's receipt check decides. An unrecorded outcome is the only honest record of an unknown one.
89
+ After the call returns, inspect the result text. (2026-09-20 REVIEW) Do NOT record an "acknowledged" builder report — the positive builder-report path was circular (the worker knows the version, so the report proves nothing) and is removed; the ack scan reads the version receipt from the artifact's disk as the sole positive evidence. If the result is an explicit refusal → `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-builder-report --json '{"task_id":"<task_id>","version":"<version>","outcome":"refused","report_text":"<refusal text verbatim>"}'` (the command MECHANICALLY rejects unless `report_text` contains the exact version — if the refusal text lacks the version, do NOT call record-builder-report; just proceed to the record step with `refused`).
124
90
  4. **Record the outcome (code):**
125
- `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-intent-issuance --json '{"task_id": "<task_id>", "claim_expiry": "<claim_expiry>", "edit_outcome": "<accepted|refused|recovered>", "refusal_text": "<first line>"}'`
126
- (omit `refusal_text` unless refused). For `recovered`, pass `freshness_evidence` with the step-2 helper's stdout JSON verbatim — the command refuses a recovery without machine evidence. Copy `claim_expiry` verbatim from the scan entry — it is the compare-and-swap. If the command returns `recorded: false`, log the reason and stop: another tick owns the task. `accepted`/`recovered` writes the issuer-stamped `submitted` ledger entry and mirrors `publish: verification-requested` (the Step 4.5 scan claims it on this or a later tick); `refused` writes `rejected` and the terminal `publish: publish-refused` note. There is no outcome for an inconclusive call (step 3) — do not call record-intent-issuance at all when the edit's fate is unknown.
127
- - Crash rules (fail closed, never blind re-issue): if this tick dies between the artifact_edit call and the record step, the next scan re-claims with `reclaimed: true` and step 2's manifest-freshness check decides between `recovered` and a fresh issuance. The issuance contract is at-most-one RECORDED issuance per attempt: a duplicate physical issuance remains possible across a lease expiry when the first attempt's fate went unrecorded (the dead tick issued but died before recording), but it is content-identical — the same checksummed diff — and the verifier's content check bounds it. If the tick dies before issuing, the claim simply expires and the next tick claims it.
128
-
129
- 4.5. **Parent publish verification (docs/publish-verification.md):** The publisher parks instead of stamping provenance; the parent — this tick, the live root agent — verifies content and stamps. Deterministic code detects, reads back, and certifies; you are only the ferry between the deterministic steps (scan → sensor → verifier).
130
- - Scan (code): `node {crewHome}/lib/crew-api.js --crew-home {crewHome} scan-verification-pending`
131
- This atomically claims each verification-pending task (1-hour lease, so a second tick cannot double-verify) and reconciles verified-but-still-parked tasks to `in_progress`. It returns `{ to_verify: [...], reconciled: [...] }`. Log both lists. If the scan exits 2 (e.g. the active release cannot be resolved), log the error loudly and continue — do NOT work around it.
132
- - **Read-back (deterministic sensor, 2026-09-15):** no platform inspection tool is needed — `lib/readback-disk.js` reads the platform's on-disk working copy of the artifact source (`~/workspace/ts-spaces/<slug>/`) and emits the machine-readable findings block the verifier parses. For each entry in `to_verify`, resolve the base via `get-provenance` (`source_commit`; the empty-tree sha `4b825dc642cb6eb9a060e54bf8d69288fbee4904` when nothing is stamped yet — a first publish):
133
- `node {crewHome}/lib/crew-api.js --crew-home {crewHome} get-provenance --json '{"project_id": "<project_id>"}'`
134
- (use the entry's `project_id` verbatim), then run:
135
- `node {crewHome}/lib/readback-disk.js --repo-path "<repo_path>" --commit <commit> --base <base> --slug "<deploy_slug>" --task-id <task_id> > /tmp/readback-<task_id>.txt 2> /tmp/readback-<task_id>.err`
136
- Use the entry's `repo_path` and `deploy_slug` verbatim. If the sensor exits 0, the result file holds the findings block — hand it to the verify step below. If it exits non-zero, do NOT save or use stdout: log `publish: verification-procedural-error <commit> <first line of the .err file>` and leave the task parked — the next tick retries. A sensor failure is procedural (the read could not be performed), never a content verdict. Do NOT judge content yourself, and do NOT stamp provenance.
137
- - **Verify (code):** `node {crewHome}/lib/verify-publish.js --crew-home {crewHome} --task-id <task_id> --commit <commit> --base <base> --repo-path "<repo_path>" --slug "<deploy_slug>" --crew-release <crew_release> --project-id <project_id> --inspection-id <task_id>-disk --result-file /tmp/readback-<task_id>.txt`
138
- Pass `--build-agent-id <id>` from the entry's `build_agent_id` when it is present. Pass `--attempt <attempt>` from the entry's `attempt` when it is present (2026-09-18, blocker 4: the verifier binds the ledger entry by exact (task_id, commit, attempt) — without the attempt, a retry's trigger is indistinguishable from the original's). Use the SAME `<base>` the sensor ran with and the entry's `project_id` verbatim. The verifier parses the findings, compares mechanically against the base..commit diff, checks supersession, and stamps only on a match. Its terminal verdicts (`publish: verified` → task re-queued; `publish: verification-failed` → stays parked) are final — log them and continue.
139
- - Never stamp provenance from prose. Never infer a verdict from an inspector's summary text. The verify script's machine-checked comparison is the only certification.
91
+ `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-intent-issuance --json '{"task_id": "<task_id>", "claim_expiry": "<claim_expiry>", "edit_outcome": "<accepted|refused>", "refusal_text": "<first line>", "reclaimed": <true|false>, "receipt_check": "<different|unreadable>"}'`
92
+ (omit `refusal_text` unless refused; copy `reclaimed` verbatim from the scan entry — it is mechanical, never inferred; `receipt_check` is REQUIRED when `reclaimed` is true and must name the exact step-2 outcome — omit `receipt_check` when `reclaimed` is false. A `match` in step 2 never reaches this step: the edit already landed, so you ack instead of issuing.) Copy `claim_expiry` verbatim from the scan entry — it is the compare-and-swap. If the command returns `recorded: false`, log the reason and stop: another tick owns the task. `accepted` writes the issuer-stamped `submitted` ledger entry and mirrors `publish: edit-issued <commit> attempt=<n> version=<commit>:<attempt>` (the 4.5 ack scan claims it on a later tick); `refused` writes `rejected` and the terminal `publish: publish-refused` note. There is no outcome for an inconclusive call (step 3) — do not call record-intent-issuance at all when the edit's fate is unknown.
93
+ - Prose never mints `publish:` notes directly. Any tick-authored publish note goes through exactly one path (code asserts the verb against the closed registry, rejects unknown verbs):
94
+ `node {crewHome}/lib/crew-api.js --crew-home {crewHome} record-publish-note --json '{"task_id":"<task_id>","note":"<full note text>"}'`
95
+ - Crash rules (fail closed, never blind re-issue): if this tick dies between the artifact_edit call and the record step, the next scan re-claims with `reclaimed: true` and step 2's version-receipt check decides between ack (`record-version-ack` with `via: "disk"`) and a fresh issuance — recorded with its explicit `receipt_check`, since a differing/unreadable receipt is not proof the prior attempt's edit did not land. The issuance contract is at-most-one RECORDED issuance per attempt; a duplicate physical issuance across a lease expiry is content-identical (the same checksummed staged diff) and bounded by the ack scan's 2-hour budget.
96
+
97
+ 4.5. **Acknowledgement scan (version, 0.14.6):** the version acknowledgement is the entire publish verdict — Eric's contract: "If we hear that the artifact acknowledges our version, that's it. We don't verify against content." No content verification, no manifest comparison, no read-back. A dedicated 5-minute `crew-ack-scan` cron runs the same scan on an observation-only tick (it never issues edits) so verdicts land quickly; this tick ALSO runs the scan AFTER 4.4b so an issued edit's verdict path is synchronous. Deterministic code evaluates the evidence and owns every transition; you only log.
98
+ - Scan (code): `node {crewHome}/lib/crew-api.js --crew-home {crewHome} scan-ack-pending`
99
+ For each issued attempt inside its 30-minute window, code checks the on-disk version receipt at `~/workspace/ts-spaces/<slug>/.crew-publish-version-<task_id>` (the SOLE positive evidence — 2026-09-20 REVIEW removed the circular builder-report positive path) plus durably recorded builder reports for explicit refusals. It returns `{ acknowledged: [...], refused: [...], reissued: [...], timeouts: [...], skipped: [...] }`. Log every list.
100
+ - `acknowledged` = fully handled by code — take NO action, just log. The version was acknowledged (exact string match on disk). Code stamps provenance and writes the terminal `publish: version-acknowledged` note; the task is requeued. Any acknowledgement for the same task/commit counts — including a late earlier attempt's version — the receipt is what matters, not which attempt was latest.
101
+ - `refused` = fully handled by code — take NO action, just log. A recorded builder report echoed the version with outcome `refused`. Code writes the terminal `publish: publish-refused` note. An explicit refusal is never re-issued.
102
+ - `timeouts` = fully handled by code — take NO action, just log. The 2-hour total budget from first issuance is exhausted with no acknowledgement; code writes the terminal `publish: version-timeout` note — parked for human attention. `timeouts` is terminal-budget exhaustion only — a window expiry is a `reissued`, never a timeout.
103
+ - `reissued` = fresh intents for the next attempt — a 30-minute window expired with no acknowledgement, so code re-issued with a fresh version (by derivation), immediately claimable (no backoff). This tick does NOT issue them; the NEXT tick's `scan-publish-intent` claims them. Log only.
104
+ - `skipped` = window still open or evidence absent — log only, take no action.
105
+ - Never stamp provenance from prose. Never infer a verdict from an inspector's summary text. The exact version string is the sole positive signal.
140
106
 
141
107
  5. **Monitor launched workflows until terminal (stay-alive — 2026-09-13):** The platform ties async workflow `agent()` authorization to the launcher's lifetime: if THIS tick ends while a workflow is still running, the workflow's next `agent()` call fails with "subagent bootstrap is no longer authorized" / "subagent reservation owner is terminal". Prevention beats recovery here, so this tick is configured with a 90-minute execution timeout (`timeout_secs: 5400` in seed/crons.json) and you MUST stay alive until every launched run reaches a terminal state. Do not exit early while a launched run is still `running` — your death is what kills it.
142
108
  - For each launched run_id, poll its status every ~2 minutes via muse.db:
package/seed/crons.json CHANGED
@@ -23,6 +23,18 @@
23
23
  },
24
24
  "timeout_secs": 600,
25
25
  "title": "Muse Crew automatic update watcher"
26
+ },
27
+ {
28
+ "body_template": "cron-body-ack-scan.md",
29
+ "enabled": true,
30
+ "id": "crew-ack-scan",
31
+ "mode": "task",
32
+ "schedule": {
33
+ "every": "5m",
34
+ "kind": "interval"
35
+ },
36
+ "timeout_secs": 600,
37
+ "title": "Muse Crew acknowledgement scan"
26
38
  }
27
39
  ],
28
40
  "version": 1
@@ -2,7 +2,7 @@
2
2
 
3
3
  Executable Muse workflow scripts (JavaScript). These are what the workflow runtime actually runs.
4
4
 
5
- - `crew-dispatch.js` — reads the board, recommends eligible tasks, returns structured launch records (the launched workflow self-claims; the dispatcher never writes claims). Skips tasks on quiesced projects and on projects with no repo_path configured.
5
+ - `crew-dispatch.js` — reads the board, recommends eligible tasks, returns structured launch records (the launched workflow self-claims; the dispatcher never writes claims). Skips tasks on quiesced projects and on projects with no repo_path configured. Registry seam (2026-09-21): prefers raw `inputs.registryText` with deterministic `JSON.parse` — never LLM re-emission; loudly falls back to slow registry loading for missing/non-string/malformed input; validates every `reworkTarget` names a real step; the slow-loaded registry is re-validated and fails closed (throw) — no second fallback.
6
6
 
7
7
  All four workflow scripts share byte-identical transport helpers (`workRetryKey`, `buildTransportRetryTrailer`, `describeWorkAgentFailure` — pinned by `tests/closeout.test.js`). The transport-retry loop treats a worker report naming the missing artifact tool namespace (bug 3472bf36, a per-launch platform flake) as a retryable attempt with a fresh launch rather than accepting a useless report.
8
8