muse-crew 0.13.3 → 0.14.1

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.
@@ -70,7 +70,7 @@ let telemetryBuffer = [];
70
70
  async function telemetryStart(workflowName) {
71
71
  try {
72
72
  const out = await agent(crewCmd("record-run-start", {
73
- task_id: taskId, workflow: workflowName, launched_by: "cron"
73
+ task_id: taskId, workflow: workflowName, launched_by: inputs.launched_by
74
74
  }), { key: "telemetry-start", label: "Recording run start" });
75
75
  const parsed = typeof out === "string" ? JSON.parse(out) : out;
76
76
  if (parsed && parsed.run_id) telemetryRunId = parsed.run_id;
@@ -487,6 +487,7 @@ async function recordPublishLedger(entry, rework) {
487
487
  attempt: entry.attempt || null,
488
488
  agent_id: entry.agent_id || null,
489
489
  applied_report: entry.applied_report || null,
490
+ manifest_before: entry.manifest_before || null,
490
491
  outcome: entry.outcome,
491
492
  detail: entry.detail || ""
492
493
  });
@@ -500,9 +501,7 @@ async function recordPublishLedger(entry, rework) {
500
501
  label: "Recording publish attempt in ledger",
501
502
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
502
503
  );
503
- var ok = !!(res && res.result && res.result.indexOf("LEDGER_OK") !== -1);
504
- log("Publish ledger: outcome '" + entry.outcome + "' for task " + taskId +
505
- (ok ? " recorded." : " NOT confirmed (" + ((res && res.result) || "no output") + ")"));
504
+ log("Noted publish outcome '" + entry.outcome + "' for task " + taskId + " in ledger");
506
505
  } catch (e) {
507
506
  log("Publish ledger: write failed for task " + taskId + " (non-fatal, observability only): " + (e && e.message ? e.message : e));
508
507
  }
@@ -1455,20 +1454,36 @@ while (i < STEPS.length) {
1455
1454
  // See docs/decisions/publish-path.md#durable-evidence-snapshot: snapshot the audit-dir listing BEFORE the trigger; fallback diffs before/after.
1456
1455
  var auditDirsBeforeTrigger = [];
1457
1456
  var auditBeforeOk = false;
1457
+ // Pre-trigger baselines (design §1.9): the audit-dir listing and the
1458
+ // manifest snapshot. The verified-path freshness check compares the
1459
+ // post-trigger manifest against the baseline (built_at advance +
1460
+ // content_sha256 change). Best-effort, never gates — a missing
1461
+ // manifest baseline fails the verified path closed.
1462
+ var preTriggerManifest = null;
1458
1463
  try {
1459
1464
  var auditBefore = await agent(
1460
- "List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshot, never a gate).\n" +
1461
- "Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null\n" +
1462
- "Return JSON { \"dirs\": \"<newline-separated names, empty string when the audits directory does not exist or is empty>\" } and nothing else.",
1463
- { key: attemptKey("publish-audit-before-" + taskId, reworkCount), label: "Snapshotting audit dirs before rebuild trigger",
1464
- schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
1465
+ "Capture pre-trigger baselines for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshots, never gates).\n" +
1466
+ "Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null; echo ---MANIFEST---; cat ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/.space-build/manifest.json 2>/dev/null\n" +
1467
+ "Return JSON { \"dirs\": \"<newline-separated names, empty string when missing>\", \"manifest\": \"<the manifest's full text, or empty string when missing/unreadable>\" } and nothing else.",
1468
+ { key: attemptKey("publish-baseline-before-" + taskId, reworkCount), label: "Snapshotting baselines before rebuild trigger",
1469
+ schema: { type: "object", properties: { dirs: { type: "string" }, manifest: { type: "string" } }, required: ["dirs", "manifest"] } }
1465
1470
  );
1466
1471
  auditDirsBeforeTrigger = String((auditBefore && auditBefore.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
1472
+ var manifestText = String((auditBefore && auditBefore.manifest) || "").trim();
1473
+ if (manifestText) {
1474
+ var manifestJson = JSON.parse(manifestText);
1475
+ preTriggerManifest = {
1476
+ built_at: typeof manifestJson.built_at === "string" ? manifestJson.built_at : null,
1477
+ content_sha256: typeof manifestJson.content_sha256 === "string" ? manifestJson.content_sha256 : null,
1478
+ };
1479
+ }
1480
+ // Arm only after both baselines parse.
1467
1481
  auditBeforeOk = true;
1468
- log("Publish audit-dir snapshot before trigger for task " + taskId + ": " + auditDirsBeforeTrigger.length + " entries");
1482
+ log("Publish pre-trigger baselines for task " + taskId + ": " + auditDirsBeforeTrigger.length + " audit dirs, manifest " + (preTriggerManifest ? "built_at=" + preTriggerManifest.built_at : "none"));
1469
1483
  } catch (auditBeforeErr) {
1470
- log("Publish audit-dir snapshot before trigger failed for task " + taskId + " (non-fatal): audit fallback DISABLED for this attempt — without a baseline, historical dirs would look new: " + (auditBeforeErr && auditBeforeErr.message ? auditBeforeErr.message : auditBeforeErr));
1484
+ log("Publish pre-trigger baselines failed for task " + taskId + " (non-fatal): audit fallback DISABLED for this attempt — without a baseline, historical dirs would look new: " + (auditBeforeErr && auditBeforeErr.message ? auditBeforeErr.message : auditBeforeErr));
1471
1485
  }
1486
+
1472
1487
  // See docs/decisions/publish-path.md#fire-and-forget-trigger: the trigger child returns immediately; the workflow owns observation and verdict.
1473
1488
  var publishToolsOk = false;
1474
1489
  var publishToolsMissing = false;
@@ -1673,6 +1688,7 @@ while (i < STEPS.length) {
1673
1688
  attempt: rebuildAttemptKey,
1674
1689
  agent_id: rebuildAgentId,
1675
1690
  applied_report: publishAppliedObservation,
1691
+ manifest_before: preTriggerManifest,
1676
1692
  outcome: "submitted",
1677
1693
  detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
1678
1694
  }, reworkCount);
@@ -1743,7 +1759,8 @@ while (i < STEPS.length) {
1743
1759
  attempt: rebuildAttemptKey,
1744
1760
  agent_id: null,
1745
1761
  applied_report: publishAppliedObservation,
1746
- outcome: "submitted",
1762
+ manifest_before: preTriggerManifest,
1763
+ outcome: "build-observed",
1747
1764
  detail: "durable audit evidence shows a build completed during the attempt window (no receipt agent_id — attribution by window, not identity; receipt poll bypassed (verdict decided), routed to parent verification)"
1748
1765
  }, reworkCount);
1749
1766
  log("Publish verdict LANDED for task " + taskId + ": a completed build was observed during the attempt window — receipt poll bypassed (verdict decided, no receipt to chain to), routing directly to parent verification.");
@@ -2015,7 +2032,8 @@ while (i < STEPS.length) {
2015
2032
  attempt: rebuildAttemptKey,
2016
2033
  agent_id: rebuildAgentId,
2017
2034
  applied_report: publishAppliedObservation,
2018
- outcome: "submitted",
2035
+ manifest_before: preTriggerManifest,
2036
+ outcome: "build-observed",
2019
2037
  detail: "durable audit evidence shows a build completed during the attempt window (audit dir " + newestAuditDirAfterPoll + ", report ok=true); routed to parent verification"
2020
2038
  }, reworkCount);
2021
2039
  } else if (auditOkAfterPoll === false) {
@@ -2078,11 +2096,11 @@ while (i < STEPS.length) {
2078
2096
  if (!postDeploy.deployed) {
2079
2097
  return await parkTask("Post-deploy failed after a skipped publish: " + (postDeploy.output || "no output") + ". Nothing was published; worktree cleanup and lock state unknown — human attention needed.");
2080
2098
  }
2081
- log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy finalized cleanup");
2099
+ log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy step finished");
2082
2100
  } else {
2083
2101
  if (publishFailure) {
2084
2102
  return await parkTask(publishFailure + (postDeploy.deployed
2085
- ? " Post-deploy finalized cleanup."
2103
+ ? " Post-deploy step finished (cleanup status unknown)."
2086
2104
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2087
2105
  }
2088
2106
  if (!publishBuildLanded) {
@@ -2095,7 +2113,7 @@ while (i < STEPS.length) {
2095
2113
  // commit (design §1.5: "the trigger was sent for commit <short-sha>").
2096
2114
  if (publishUnknownFields) publishUnknownFields.commitShortSha = mergeCommitShortForPublish;
2097
2115
  return await parkTask(composeUnattributedParkReason(publishUnknownFields) + (postDeploy.deployed
2098
- ? " Post-deploy finalized cleanup."
2116
+ ? " Post-deploy step finished (cleanup status unknown)."
2099
2117
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2100
2118
  }
2101
2119
  if (!postDeploy.deployed) {
@@ -2124,7 +2142,7 @@ while (i < STEPS.length) {
2124
2142
  // artifact_edit would trigger a duplicate build.
2125
2143
  if (publishSkippedNoLock) {
2126
2144
  instructions = "Publish was skipped deterministically by the workflow before your step — do NOT call artifact_edit, artifact_status, setprovenance, or post-deploy yourself; doing so would disturb the finalized state.\n\n" +
2127
- "Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship. The workflow finalized cleanup via post-deploy.\n\n" +
2145
+ "Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship. Post-deploy step finished per its return — do NOT run post-deploy yourself.\n\n" +
2128
2146
  "Write plain prose describing the skip, then on its own line: VERDICT: PASS\n" +
2129
2147
  "The VERDICT line must be the last line of your report.";
2130
2148
  } else {
@@ -2438,11 +2456,7 @@ while (i < STEPS.length) {
2438
2456
  { key: attemptKey("publish-provenance-refresh-" + taskId, reworkCount), label: "Refreshing dashboard provenance after crew release",
2439
2457
  schema: { type: "object", properties: { refreshed: { type: "boolean" }, reason: { type: "string" }, crew_release: { type: "string" }, published_at: { type: "string" } }, required: ["refreshed"] } }
2440
2458
  );
2441
- if (provRefresh.refreshed) {
2442
- log("Provenance refreshed for task " + taskId + ": crew_release " + provRefresh.crew_release);
2443
- } else if (provRefresh.reason === "no-record") {
2444
- log("Provenance refresh skipped for task " + taskId + ": no existing record to refresh (fresh instance — the dashboard's first artifact publish will create it)");
2445
- } else {
2459
+ if (!provRefresh.refreshed && provRefresh.reason !== "no-record") {
2446
2460
  return await parkTask("Provenance refresh failed after a verified npm publish (crew_release " + (provRefresh.crew_release || "unknown") + "). The release is live but the dashboard record is stale — fail-closed.");
2447
2461
  }
2448
2462
  } catch (e) {
@@ -2552,6 +2566,7 @@ while (i < STEPS.length) {
2552
2566
  // means the parent's independent read-back has not happened yet; this
2553
2567
  // park is NOT proof the content is correct.
2554
2568
  return await parkTask("publish: verification-requested " + mergeCommitForPublish +
2569
+ " attempt=" + rebuildAttemptKey +
2555
2570
  " Do NOT republish: a duplicate build would re-publish the same change. " +
2556
2571
  "Artifact build landed, post-deploy finalized, provenance not stamped — the crew has not yet independently confirmed the live artifact contains exactly the change; waiting on the manual read-back in docs/publish-verification.md. " +
2557
2572
  "Appendix: build=" + (rebuildAgentId || "agent_id unobserved") + "; provenance=unstamped; content_check=pending.");
@@ -794,13 +794,44 @@ if (eligible.length === 0) {
794
794
  );
795
795
 
796
796
  if (fileResult.task_id) {
797
- eligible.push({
798
- task: { id: fileResult.task_id, title: ptTitle, description: ptDesc, project: ptProject, workflow: "standard", state: "todo" },
799
- startStep: qaStepIndex("standard"),
800
- reason: "playtest",
801
- workflow: "standard"
802
- });
803
- log("Filed idle playtest \"" + ptTitle + "\" [" + ptProject + "] journey " + pti + "/" + J + ", persona " + ppi + "/" + P + ", max_filings " + maxFilings);
797
+ // Razor: the filed task_id is testimony. Do an independent witness
798
+ // re-read of the board before entering eligible — the task must be
799
+ // observed in todo state, not assumed. The downstream atomic claim
800
+ // remains the authoritative guard; this is fail-fast, fail-closed.
801
+ var ptWitnessOk = false;
802
+ try {
803
+ var ptBoardRaw = await agent(
804
+ "Re-read the dispatch state to verify the filed playtest task.\\n" +
805
+ "Run in shell and return the stdout as a raw string:\\n" + crewCmd("get-dispatch-state", {}) + "\\n" +
806
+ "Return the command's stdout JSON as a plain string, byte-for-byte, unmodified. " +
807
+ "Do NOT parse the JSON — your return value must be the raw stdout string, never an object.",
808
+ {
809
+ key: "verify-playtest",
810
+ label: "Verifying filed playtest task"
811
+ }
812
+ );
813
+ var ptBoard = unwrapBoardResult(parseBoardJson(ptBoardRaw));
814
+ var ptTasks = (ptBoard.ready_tasks || []).map(projectTaskRecord).filter(function (t) { return t !== null; });
815
+ for (var pti2 = 0; pti2 < ptTasks.length; pti2++) {
816
+ if (ptTasks[pti2].id === fileResult.task_id && ptTasks[pti2].state === "todo") {
817
+ ptWitnessOk = true;
818
+ break;
819
+ }
820
+ }
821
+ } catch (e) {
822
+ log("Playtest witness re-read failed (" + String(e.message || e) + ") — task not verified, skipping eligible");
823
+ }
824
+ if (ptWitnessOk) {
825
+ eligible.push({
826
+ task: { id: fileResult.task_id, title: ptTitle, description: ptDesc, project: ptProject, workflow: "standard", state: "todo" },
827
+ startStep: qaStepIndex("standard"),
828
+ reason: "playtest",
829
+ workflow: "standard"
830
+ });
831
+ log("Filed idle playtest \"" + ptTitle + "\" [" + ptProject + "] journey " + pti + "/" + J + ", persona " + ppi + "/" + P + ", max_filings " + maxFilings + " (witness observed todo)");
832
+ } else {
833
+ log("Idle playtest task " + fileResult.task_id + " not observed in todo state on re-read — skipping eligible (fail-closed)");
834
+ }
804
835
  } else {
805
836
  log("Idle playtest filing failed — cursors already advanced, assignment skipped");
806
837
  }
@@ -1022,7 +1053,11 @@ if (recommended.length > 0) {
1022
1053
  );
1023
1054
  var reserveParsed = extractJsonObject(reserveOut);
1024
1055
  if (reserveParsed && reserveParsed.acquired) {
1025
- log("Reserved task " + rec.task_id.slice(0, 8));
1056
+ // Razor: the acquired flag is testimony from the reservation agent's
1057
+ // stdout ferry, not proof. The downstream atomic claim-task remains
1058
+ // the authoritative guard. We route on the flag (fail-closed if
1059
+ // absent) but do not assert the reservation as established fact.
1060
+ log("Reservation agent returned acquired=true for task " + rec.task_id.slice(0, 8) + " (not independently verified; claim-task is authoritative)");
1026
1061
  acquired.push(rec);
1027
1062
  } else {
1028
1063
  // FAIL CLOSED: Another dispatcher owns this task. Do not recommend it.