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.
- package/docs/publish-unknown-recovery.md +113 -0
- package/lib/AGENTS.md +7 -2
- package/lib/advance-publish-base.js +74 -28
- package/lib/classify-publish-absence.js +451 -0
- package/lib/crew-api.js +584 -21
- package/lib/publish-content.js +154 -0
- package/lib/retry-publish.js +370 -0
- package/lib/scaffold-crew.js +129 -0
- package/lib/verify-publish.js +101 -84
- package/package.json +2 -2
- package/seed/cron-body-template.md +57 -2
- package/workflows/bugfix.js +38 -23
- package/workflows/chore.js +37 -22
- package/workflows/crew-dispatch.js +43 -8
- package/workflows/crew-init.js +333 -78
- package/workflows/crew-uninstall.js +57 -10
- package/workflows/standard.js +35 -16
- package/workflows/upgrade.js +28 -14
package/workflows/chore.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
"
|
|
1461
|
-
"Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null\n" +
|
|
1462
|
-
"Return JSON { \"dirs\": \"<newline-separated names, empty string when
|
|
1463
|
-
{ key: attemptKey("publish-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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.
|