muse-crew 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +40 -7
- package/docs/guide.md +1 -1
- package/docs/publish-verification.md +24 -3
- package/docs/state-boundary-spec.md +37 -0
- package/lib/AGENTS.md +6 -3
- package/lib/advance-publish-base.js +271 -0
- package/lib/classify-surface.js +109 -0
- package/lib/compose-evidence-caption.js +59 -10
- package/lib/compute-publish-diff.js +145 -0
- package/lib/crew-api.js +283 -49
- package/lib/schema.sql +12 -0
- package/lib/test-worktree-backend.sh +29 -0
- package/lib/verify-publish.js +4 -3
- package/lib/worktree-lifecycle.sh +9 -3
- package/package.json +1 -1
- package/seed/cron-body-template.md +10 -7
- package/workflows/bugfix.js +67 -69
- package/workflows/chore.js +66 -68
- package/workflows/crew-dispatch.js +21 -6
- package/workflows/crew-init.js +69 -3
- package/workflows/standard.js +67 -69
- package/workflows/upgrade.js +8 -3
package/workflows/bugfix.js
CHANGED
|
@@ -81,9 +81,20 @@ const PUBLISH_NPM = RUN_LIB + "/publish-npm.sh";
|
|
|
81
81
|
const CREW_API_PINNED = RUN_LIB + "/crew-api.js";
|
|
82
82
|
const SCHEMA_SQL_SRC = crewHome + "/lib/schema.sql";
|
|
83
83
|
const SCHEMA_SQL_PINNED = RUN_LIB + "/schema.sql";
|
|
84
|
-
|
|
84
|
+
const COMPUTE_DIFF_SRC = crewHome + "/current/lib/compute-publish-diff.js";
|
|
85
|
+
const COMPUTE_DIFF = RUN_LIB + "/compute-publish-diff.js";
|
|
86
|
+
const CLASSIFY_SURFACE_SRC = crewHome + "/current/lib/classify-surface.js";
|
|
87
|
+
const CLASSIFY_SURFACE = RUN_LIB + "/classify-surface.js";
|
|
88
|
+
// The seven basenames the pin step must materialize — asserted mechanically
|
|
85
89
|
// by workflow code from the verbatim listing, never from agent prose.
|
|
86
|
-
|
|
90
|
+
// COMPUTE_DIFF is the deterministic publish-diff computer (room #14,
|
|
91
|
+
// 2026-09-17): the diff is computed by this script, never ferried as an
|
|
92
|
+
// agent JSON string. Pinned like the other publish-critical modules so a
|
|
93
|
+
// mid-run release swap cannot change it under the workflow.
|
|
94
|
+
// CLASSIFY_SURFACE is the surface classifier (room #15, 2026-09-18):
|
|
95
|
+
// crew-api.js statically imports it, so the pin must carry it — a pin
|
|
96
|
+
// without it kills every claim with ERR_MODULE_NOT_FOUND.
|
|
97
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, COMPUTE_DIFF, CLASSIFY_SURFACE].map(function (p) { return p.split("/").pop(); });
|
|
87
98
|
|
|
88
99
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
89
100
|
const projectConfig = inputs.project_config || {};
|
|
@@ -259,7 +270,7 @@ function attemptKey(base, reworkCount) {
|
|
|
259
270
|
return base + (reworkCount > 0 ? "-r" + reworkCount : "");
|
|
260
271
|
}
|
|
261
272
|
// pinLifecycle(key) — snapshot the lifecycle scripts into RUN_LIB and return
|
|
262
|
-
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the
|
|
273
|
+
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the six pinned
|
|
263
274
|
// basenames; the agent cannot self-certify. (The pin step was the one place
|
|
264
275
|
// the workflows trusted agent prose: task 24be1cd6 walked to Publish on an
|
|
265
276
|
// empty pin dir.) Byte-identical across standard/bugfix/chore — pinned by
|
|
@@ -267,7 +278,7 @@ function attemptKey(base, reworkCount) {
|
|
|
267
278
|
function pinLifecycle(key) {
|
|
268
279
|
return agent(
|
|
269
280
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
270
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
281
|
+
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && cp " + COMPUTE_DIFF_SRC + " " + COMPUTE_DIFF + " && cp " + CLASSIFY_SURFACE_SRC + " " + CLASSIFY_SURFACE + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
271
282
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
272
283
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
273
284
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -448,37 +459,6 @@ function hydrateReleaseDecision(rec) {
|
|
|
448
459
|
return null;
|
|
449
460
|
}
|
|
450
461
|
|
|
451
|
-
// Publish diff transport: parse a unified diff into per-file {path, added,
|
|
452
|
-
// removed} line lists. Pure function — no I/O, no clock. Used by Publish to
|
|
453
|
-
// verify the artifact builder applied the carried change (canary run 4,
|
|
454
|
-
// 2026-09-11: the builder's source tree was stale and disconnected from the
|
|
455
|
-
// crew's repo; "rebuild from current source" rebuilt stale code and the
|
|
456
|
-
// workflow stamped the new commit hash on it — provenance fiction).
|
|
457
|
-
function parseUnifiedDiff(diffText) {
|
|
458
|
-
var files = [];
|
|
459
|
-
var current = null;
|
|
460
|
-
var lines = (diffText || "").split("\n");
|
|
461
|
-
for (var i = 0; i < lines.length; i++) {
|
|
462
|
-
var line = lines[i];
|
|
463
|
-
var m = /^diff --git a\/(.*) b\/(.*)$/.exec(line);
|
|
464
|
-
if (m) {
|
|
465
|
-
current = { path: m[2], added: [], removed: [] };
|
|
466
|
-
files.push(current);
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (!current) continue;
|
|
470
|
-
if (/^--- /.test(line) || /^\+\+\+ /.test(line)) continue;
|
|
471
|
-
if (/^@@ /.test(line)) continue;
|
|
472
|
-
if (line.charAt(0) === "+") {
|
|
473
|
-
current.added.push(line.slice(1));
|
|
474
|
-
} else if (line.charAt(0) === "-") {
|
|
475
|
-
current.removed.push(line.slice(1));
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return files;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
462
|
// Publish read-back request (currently unavailable): the verbatim_request
|
|
483
463
|
// the parent protocol (docs/publish-verification.md) would hand to an
|
|
484
464
|
// independent read-back tool after the artifact build lands. artifact_inspect
|
|
@@ -1573,7 +1553,7 @@ while (i < STEPS.length) {
|
|
|
1573
1553
|
// first publish (no provenance stamped yet).
|
|
1574
1554
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
1575
1555
|
var provResult = await agent(
|
|
1576
|
-
crewCmd("get-provenance", {}) + "\n" +
|
|
1556
|
+
crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
1577
1557
|
"Return JSON { \"provenance\": <the CLI's provenance object, or null when nothing is stamped> } and nothing else. Do not interpret it.",
|
|
1578
1558
|
{ key: attemptKey("publish-provenance-base-" + taskId, totalReworkCount), label: "Reading stamped publish base",
|
|
1579
1559
|
schema: { type: "object", properties: { provenance: { type: ["object", "null"] } }, required: ["provenance"] } }
|
|
@@ -1586,59 +1566,77 @@ while (i < STEPS.length) {
|
|
|
1586
1566
|
} else if (!/^[0-9a-f]{40}$/.test(publishBase)) {
|
|
1587
1567
|
return await parkTask("Publish base '" + publishBase + "' is not a valid commit SHA — cannot compute the publish diff. Human attention needed.");
|
|
1588
1568
|
}
|
|
1589
|
-
//
|
|
1590
|
-
//
|
|
1591
|
-
//
|
|
1592
|
-
//
|
|
1593
|
-
//
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1569
|
+
// Publish diff transport (room #14, 2026-09-17): the diff used to be
|
|
1570
|
+
// ferried as a JSON string field in the agent's response — the agent
|
|
1571
|
+
// produced a valid 700-line diff on disk but the JSON ferry dropped
|
|
1572
|
+
// it, and the parse saw zero files ("Publish diff parsed to zero
|
|
1573
|
+
// files"). The diff now travels git -> file -> deterministic script
|
|
1574
|
+
// summary; the LLM never carries diff bytes. The agent is pure hands:
|
|
1575
|
+
// it runs exactly one command (the PINNED compute-publish-diff.js —
|
|
1576
|
+
// a mid-run release swap cannot change it under the workflow) and
|
|
1577
|
+
// returns the small JSON summary verbatim. All fail-closed parks
|
|
1578
|
+
// below are unchanged in meaning.
|
|
1579
|
+
var publishDiffFile = crewHome + "/.publish-diffs/" + taskId + (totalReworkCount > 0 ? "-r" + totalReworkCount : "") + ".diff";
|
|
1597
1580
|
var diffResult = await agent(
|
|
1598
|
-
"Run
|
|
1599
|
-
|
|
1600
|
-
"
|
|
1601
|
-
"if [ \"$ANCESTOR\" = yes ]; then echo '---DIFF---' && git diff \"$BASE\" \"$HEAD\" && echo '---NAMES---' && git diff-tree --no-commit-id --name-only -r \"$BASE\" \"$HEAD\"; fi\n" +
|
|
1602
|
-
"Return JSON { \"commit\": \"<HEAD trimmed>\", \"base\": \"<BASE trimmed>\", \"ancestor\": \"<yes|no>\", \"diff\": \"<raw unified diff, may be multi-line>\", \"files\": \"<newline-separated paths>\" } and nothing else.",
|
|
1581
|
+
"Run exactly one command and nothing else:\n" +
|
|
1582
|
+
"node " + COMPUTE_DIFF + " --repo-path \"" + REPO_PATH + "\" --base " + publishBase + " --out \"" + publishDiffFile + "\"\n" +
|
|
1583
|
+
"The script prints one small JSON object to stdout. Return the stdout verbatim as { \"output\": \"<verbatim stdout>\" } and nothing else. Do not interpret it.",
|
|
1603
1584
|
{ key: attemptKey("publish-artifact-diff-" + taskId, totalReworkCount), label: "Computing publish diff from stamped base",
|
|
1604
|
-
schema: { type: "object", properties: {
|
|
1585
|
+
schema: { type: "object", properties: { output: { type: "string" } }, required: ["output"] } }
|
|
1605
1586
|
);
|
|
1606
|
-
|
|
1607
|
-
|
|
1587
|
+
var diffSummary = null;
|
|
1588
|
+
try {
|
|
1589
|
+
diffSummary = JSON.parse((diffResult.output || "").trim());
|
|
1590
|
+
} catch (e) {
|
|
1591
|
+
return await parkTask("Publish diff computation returned unparseable output — cannot verify application. Human attention needed.");
|
|
1592
|
+
}
|
|
1593
|
+
if (!diffSummary || diffSummary.ok !== true) {
|
|
1594
|
+
if (diffSummary && diffSummary.error === "not-ancestor") {
|
|
1595
|
+
return await parkTask("Publish base " + publishBase.slice(0, 12) + " is not an ancestor of HEAD " + String(diffSummary.commit || "").slice(0, 12) + " — the stamped provenance does not lead to the integrated commit. Human attention needed.");
|
|
1596
|
+
}
|
|
1597
|
+
return await parkTask("Publish diff computation failed (" + ((diffSummary && diffSummary.error) || "unknown error") + ") — cannot verify application. Human attention needed.");
|
|
1608
1598
|
}
|
|
1609
|
-
if (
|
|
1610
|
-
return await parkTask("Publish diff base mismatch:
|
|
1599
|
+
if (diffSummary.base !== publishBase) {
|
|
1600
|
+
return await parkTask("Publish diff base mismatch: script reported '" + String(diffSummary.base || "").slice(0, 12) + "' but the stamped base is '" + publishBase.slice(0, 12) + "'. Human attention needed.");
|
|
1611
1601
|
}
|
|
1612
|
-
var mergeCommitForPublish = (
|
|
1613
|
-
var
|
|
1614
|
-
if (!
|
|
1602
|
+
var mergeCommitForPublish = String(diffSummary.commit || "").trim();
|
|
1603
|
+
var publishDiffSha256 = String(diffSummary.sha256 || "");
|
|
1604
|
+
if (!diffSummary.bytes) {
|
|
1615
1605
|
return await parkTask("Publish diff is empty for commit " + (mergeCommitForPublish || "unknown") + " — a merge lock was held but there is no change to carry. Human attention needed.");
|
|
1616
1606
|
}
|
|
1617
|
-
if (
|
|
1618
|
-
return await parkTask("Publish diff contains binary files — the
|
|
1607
|
+
if (diffSummary.has_binary) {
|
|
1608
|
+
return await parkTask("Publish diff contains binary files — the diff transport cannot carry them. Human attention needed.");
|
|
1619
1609
|
}
|
|
1620
|
-
if (
|
|
1610
|
+
if (diffSummary.has_rename) {
|
|
1621
1611
|
return await parkTask("Publish diff contains a rename — the diff transport cannot carry renames. Human attention needed.");
|
|
1622
1612
|
}
|
|
1623
|
-
var expectedChanges = parseUnifiedDiff(mergeDiff);
|
|
1624
1613
|
// Budget counts CHANGED lines (added + removed), not raw unified-diff
|
|
1625
1614
|
// output lines: context lines and file headers inflated the old
|
|
1626
1615
|
// split("\n").length count ~2x, parking a 95-line change against a
|
|
1627
|
-
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13).
|
|
1628
|
-
|
|
1616
|
+
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13). The count
|
|
1617
|
+
// now comes from the deterministic script summary, never from the LLM.
|
|
1618
|
+
var mergeDiffChangedLines = diffSummary.changed_lines || 0;
|
|
1629
1619
|
if (mergeDiffChangedLines > 200) {
|
|
1630
1620
|
return await parkTask("Publish diff changes " + mergeDiffChangedLines + " lines (budget 200) — too large for the diff transport. Human attention needed.");
|
|
1631
1621
|
}
|
|
1632
|
-
if (
|
|
1622
|
+
if (!diffSummary.file_count) {
|
|
1633
1623
|
return await parkTask("Publish diff parsed to zero files for commit " + (mergeCommitForPublish || "unknown") + " — cannot verify application. Human attention needed.");
|
|
1634
1624
|
}
|
|
1625
|
+
// The trigger agent reads the diff from the checksummed file — the
|
|
1626
|
+
// workflow never holds diff bytes (room #14, 2026-09-17). The file
|
|
1627
|
+
// is small by construction: the 200-line budget above gates the
|
|
1628
|
+
// trigger. The sha256 check is the agent's only verification step;
|
|
1629
|
+
// a mismatch stops the trigger before any artifact_edit call.
|
|
1635
1630
|
var rebuildPrompt =
|
|
1636
1631
|
ARTIFACT_LOAD_PREAMBLE +
|
|
1637
|
-
"
|
|
1632
|
+
"The change to apply is the unified diff in the file \"" + publishDiffFile + "\" (sha256 " + publishDiffSha256 + ").\n" +
|
|
1633
|
+
"1. Verify the file: run sha256sum on it. If the printed hash is not exactly " + publishDiffSha256 + ", STOP and end your turn — do not call artifact_edit.\n" +
|
|
1634
|
+
"2. Read the file's full content.\n" +
|
|
1635
|
+
"3. Call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
1638
1636
|
"'Apply the following change to your source tree, then rebuild and deploy.\n" +
|
|
1639
1637
|
"\n" +
|
|
1640
1638
|
"UNIFIED DIFF (relative to your source tree):\n" +
|
|
1641
|
-
"```diff\n
|
|
1639
|
+
"```diff\n<the full content of the verified file, pasted verbatim>\n```\n" +
|
|
1642
1640
|
"\n" +
|
|
1643
1641
|
"Rules:\n" +
|
|
1644
1642
|
"- For each file in the diff, apply its hunks to the same path in your source tree (use git apply or equivalent).\n" +
|
|
@@ -2406,7 +2404,7 @@ while (i < STEPS.length) {
|
|
|
2406
2404
|
if (PUBLISH_TYPE === "artifact") {
|
|
2407
2405
|
instructions = "PROVENANCE CHECK (this project publishes to a dashboard artifact).\n" +
|
|
2408
2406
|
"Provenance is crew-owned state: read it from the Crew API, never from the artifact's own getprovenance action (a different, non-authoritative store).\n" +
|
|
2409
|
-
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-provenance", {}) + "\n" +
|
|
2407
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
2410
2408
|
"If provenance is null, report 'provenance missing — publish did not stamp source/crew release', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
2411
2409
|
"Run: cd " + REPO_PATH + " && git rev-parse HEAD — call this LIVE_HEAD.\n" +
|
|
2412
2410
|
"Run: test -d " + crewHome + "/releases/<provenance.crew_release> (substitute the real stamped hash; do not run the literal placeholder). If the directory does not exist, report 'provenance mismatch: crew_release [value from get-provenance] not found in release registry', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
@@ -2951,11 +2949,11 @@ while (i < STEPS.length) {
|
|
|
2951
2949
|
// dashboard QA source check).
|
|
2952
2950
|
try {
|
|
2953
2951
|
var provRefresh = await agent(
|
|
2954
|
-
"Run in shell and read the stdout JSON:\n" + crewCmd("get-provenance", {}) + "\n" +
|
|
2952
|
+
"Run in shell and read the stdout JSON:\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
2955
2953
|
"If the response has no provenance (null), return JSON { \"refreshed\": false, \"reason\": \"no-record\" } and stop. " +
|
|
2956
2954
|
"Otherwise run: basename $(readlink " + crewHome + "/current) — call this REL; " +
|
|
2957
2955
|
"run: date -u +%Y-%m-%dT%H:%M:%SZ — call this TS. " +
|
|
2958
|
-
"Then run in shell: node " + CREW_API + " --crew-home " + crewHome + " set-provenance --json '{\"source_commit\":\"<the existing provenance.source_commit value>\",\"crew_release\":\"<REL>\",\"published_at\":\"<TS>\",\"task_id\":\"" + taskId + "\"}' " +
|
|
2956
|
+
"Then run in shell: node " + CREW_API + " --crew-home " + crewHome + " set-provenance --json '{\"project_id\":\"" + LAUNCH_PROJECT_ID + "\",\"source_commit\":\"<the existing provenance.source_commit value>\",\"crew_release\":\"<REL>\",\"published_at\":\"<TS>\",\"task_id\":\"" + taskId + "\"}' " +
|
|
2959
2957
|
"(substitute the real values; the JSON must be single-quote-wrapped for the shell). " +
|
|
2960
2958
|
"Return JSON { \"refreshed\": <true if the set-provenance stdout contains ok: true, false otherwise>, \"crew_release\": \"<REL trimmed>\", \"published_at\": \"<TS trimmed>\" } and nothing else.",
|
|
2961
2959
|
{ key: attemptKey("publish-provenance-refresh-" + taskId, totalReworkCount), label: "Refreshing dashboard provenance after crew release",
|
package/workflows/chore.js
CHANGED
|
@@ -138,9 +138,20 @@ const PUBLISH_NPM = RUN_LIB + "/publish-npm.sh";
|
|
|
138
138
|
const CREW_API_PINNED = RUN_LIB + "/crew-api.js";
|
|
139
139
|
const SCHEMA_SQL_SRC = crewHome + "/lib/schema.sql";
|
|
140
140
|
const SCHEMA_SQL_PINNED = RUN_LIB + "/schema.sql";
|
|
141
|
-
|
|
141
|
+
const COMPUTE_DIFF_SRC = crewHome + "/current/lib/compute-publish-diff.js";
|
|
142
|
+
const COMPUTE_DIFF = RUN_LIB + "/compute-publish-diff.js";
|
|
143
|
+
const CLASSIFY_SURFACE_SRC = crewHome + "/current/lib/classify-surface.js";
|
|
144
|
+
const CLASSIFY_SURFACE = RUN_LIB + "/classify-surface.js";
|
|
145
|
+
// The seven basenames the pin step must materialize — asserted mechanically
|
|
142
146
|
// by workflow code from the verbatim listing, never from agent prose.
|
|
143
|
-
|
|
147
|
+
// COMPUTE_DIFF is the deterministic publish-diff computer (room #14,
|
|
148
|
+
// 2026-09-17): the diff is computed by this script, never ferried as an
|
|
149
|
+
// agent JSON string. Pinned like the other publish-critical modules so a
|
|
150
|
+
// mid-run release swap cannot change it under the workflow.
|
|
151
|
+
// CLASSIFY_SURFACE is the surface classifier (room #15, 2026-09-18):
|
|
152
|
+
// crew-api.js statically imports it, so the pin must carry it — a pin
|
|
153
|
+
// without it kills every claim with ERR_MODULE_NOT_FOUND.
|
|
154
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, COMPUTE_DIFF, CLASSIFY_SURFACE].map(function (p) { return p.split("/").pop(); });
|
|
144
155
|
|
|
145
156
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
146
157
|
const projectConfig = inputs.project_config || {};
|
|
@@ -311,7 +322,7 @@ function attemptKey(base, reworkCount) {
|
|
|
311
322
|
return base + (reworkCount > 0 ? "-r" + reworkCount : "");
|
|
312
323
|
}
|
|
313
324
|
// pinLifecycle(key) — snapshot the lifecycle scripts into RUN_LIB and return
|
|
314
|
-
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the
|
|
325
|
+
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the six pinned
|
|
315
326
|
// basenames; the agent cannot self-certify. (The pin step was the one place
|
|
316
327
|
// the workflows trusted agent prose: task 24be1cd6 walked to Publish on an
|
|
317
328
|
// empty pin dir.) Byte-identical across standard/bugfix/chore — pinned by
|
|
@@ -319,7 +330,7 @@ function attemptKey(base, reworkCount) {
|
|
|
319
330
|
function pinLifecycle(key) {
|
|
320
331
|
return agent(
|
|
321
332
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
322
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
333
|
+
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && cp " + COMPUTE_DIFF_SRC + " " + COMPUTE_DIFF + " && cp " + CLASSIFY_SURFACE_SRC + " " + CLASSIFY_SURFACE + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
323
334
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
324
335
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
325
336
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -500,37 +511,6 @@ function hydrateReleaseDecision(rec) {
|
|
|
500
511
|
return null;
|
|
501
512
|
}
|
|
502
513
|
|
|
503
|
-
// Publish diff transport: parse a unified diff into per-file {path, added,
|
|
504
|
-
// removed} line lists. Pure function — no I/O, no clock. Used by Publish to
|
|
505
|
-
// verify the artifact builder applied the carried change (canary run 4,
|
|
506
|
-
// 2026-09-11: the builder's source tree was stale and disconnected from the
|
|
507
|
-
// crew's repo; "rebuild from current source" rebuilt stale code and the
|
|
508
|
-
// workflow stamped the new commit hash on it — provenance fiction).
|
|
509
|
-
function parseUnifiedDiff(diffText) {
|
|
510
|
-
var files = [];
|
|
511
|
-
var current = null;
|
|
512
|
-
var lines = (diffText || "").split("\n");
|
|
513
|
-
for (var i = 0; i < lines.length; i++) {
|
|
514
|
-
var line = lines[i];
|
|
515
|
-
var m = /^diff --git a\/(.*) b\/(.*)$/.exec(line);
|
|
516
|
-
if (m) {
|
|
517
|
-
current = { path: m[2], added: [], removed: [] };
|
|
518
|
-
files.push(current);
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
if (!current) continue;
|
|
522
|
-
if (/^--- /.test(line) || /^\+\+\+ /.test(line)) continue;
|
|
523
|
-
if (/^@@ /.test(line)) continue;
|
|
524
|
-
if (line.charAt(0) === "+") {
|
|
525
|
-
current.added.push(line.slice(1));
|
|
526
|
-
} else if (line.charAt(0) === "-") {
|
|
527
|
-
current.removed.push(line.slice(1));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return files;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
514
|
// Publish read-back request (currently unavailable): the verbatim_request
|
|
535
515
|
// the parent protocol (docs/publish-verification.md) would hand to an
|
|
536
516
|
// independent read-back tool after the artifact build lands. artifact_inspect
|
|
@@ -1460,7 +1440,7 @@ while (i < STEPS.length) {
|
|
|
1460
1440
|
// first publish (no provenance stamped yet).
|
|
1461
1441
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
1462
1442
|
var provResult = await agent(
|
|
1463
|
-
crewCmd("get-provenance", {}) + "\n" +
|
|
1443
|
+
crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
1464
1444
|
"Return JSON { \"provenance\": <the CLI's provenance object, or null when nothing is stamped> } and nothing else. Do not interpret it.",
|
|
1465
1445
|
{ key: attemptKey("publish-provenance-base-" + taskId, reworkCount), label: "Reading stamped publish base",
|
|
1466
1446
|
schema: { type: "object", properties: { provenance: { type: ["object", "null"] } }, required: ["provenance"] } }
|
|
@@ -1473,59 +1453,77 @@ while (i < STEPS.length) {
|
|
|
1473
1453
|
} else if (!/^[0-9a-f]{40}$/.test(publishBase)) {
|
|
1474
1454
|
return await parkTask("Publish base '" + publishBase + "' is not a valid commit SHA — cannot compute the publish diff. Human attention needed.");
|
|
1475
1455
|
}
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1479
|
-
//
|
|
1480
|
-
//
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1456
|
+
// Publish diff transport (room #14, 2026-09-17): the diff used to be
|
|
1457
|
+
// ferried as a JSON string field in the agent's response — the agent
|
|
1458
|
+
// produced a valid 700-line diff on disk but the JSON ferry dropped
|
|
1459
|
+
// it, and the parse saw zero files ("Publish diff parsed to zero
|
|
1460
|
+
// files"). The diff now travels git -> file -> deterministic script
|
|
1461
|
+
// summary; the LLM never carries diff bytes. The agent is pure hands:
|
|
1462
|
+
// it runs exactly one command (the PINNED compute-publish-diff.js —
|
|
1463
|
+
// a mid-run release swap cannot change it under the workflow) and
|
|
1464
|
+
// returns the small JSON summary verbatim. All fail-closed parks
|
|
1465
|
+
// below are unchanged in meaning.
|
|
1466
|
+
var publishDiffFile = crewHome + "/.publish-diffs/" + taskId + (reworkCount > 0 ? "-r" + reworkCount : "") + ".diff";
|
|
1484
1467
|
var diffResult = await agent(
|
|
1485
|
-
"Run
|
|
1486
|
-
|
|
1487
|
-
"
|
|
1488
|
-
"if [ \"$ANCESTOR\" = yes ]; then echo '---DIFF---' && git diff \"$BASE\" \"$HEAD\" && echo '---NAMES---' && git diff-tree --no-commit-id --name-only -r \"$BASE\" \"$HEAD\"; fi\n" +
|
|
1489
|
-
"Return JSON { \"commit\": \"<HEAD trimmed>\", \"base\": \"<BASE trimmed>\", \"ancestor\": \"<yes|no>\", \"diff\": \"<raw unified diff, may be multi-line>\", \"files\": \"<newline-separated paths>\" } and nothing else.",
|
|
1468
|
+
"Run exactly one command and nothing else:\n" +
|
|
1469
|
+
"node " + COMPUTE_DIFF + " --repo-path \"" + REPO_PATH + "\" --base " + publishBase + " --out \"" + publishDiffFile + "\"\n" +
|
|
1470
|
+
"The script prints one small JSON object to stdout. Return the stdout verbatim as { \"output\": \"<verbatim stdout>\" } and nothing else. Do not interpret it.",
|
|
1490
1471
|
{ key: attemptKey("publish-artifact-diff-" + taskId, reworkCount), label: "Computing publish diff from stamped base",
|
|
1491
|
-
schema: { type: "object", properties: {
|
|
1472
|
+
schema: { type: "object", properties: { output: { type: "string" } }, required: ["output"] } }
|
|
1492
1473
|
);
|
|
1493
|
-
|
|
1494
|
-
|
|
1474
|
+
var diffSummary = null;
|
|
1475
|
+
try {
|
|
1476
|
+
diffSummary = JSON.parse((diffResult.output || "").trim());
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
return await parkTask("Publish diff computation returned unparseable output — cannot verify application. Human attention needed.");
|
|
1479
|
+
}
|
|
1480
|
+
if (!diffSummary || diffSummary.ok !== true) {
|
|
1481
|
+
if (diffSummary && diffSummary.error === "not-ancestor") {
|
|
1482
|
+
return await parkTask("Publish base " + publishBase.slice(0, 12) + " is not an ancestor of HEAD " + String(diffSummary.commit || "").slice(0, 12) + " — the stamped provenance does not lead to the integrated commit. Human attention needed.");
|
|
1483
|
+
}
|
|
1484
|
+
return await parkTask("Publish diff computation failed (" + ((diffSummary && diffSummary.error) || "unknown error") + ") — cannot verify application. Human attention needed.");
|
|
1495
1485
|
}
|
|
1496
|
-
if (
|
|
1497
|
-
return await parkTask("Publish diff base mismatch:
|
|
1486
|
+
if (diffSummary.base !== publishBase) {
|
|
1487
|
+
return await parkTask("Publish diff base mismatch: script reported '" + String(diffSummary.base || "").slice(0, 12) + "' but the stamped base is '" + publishBase.slice(0, 12) + "'. Human attention needed.");
|
|
1498
1488
|
}
|
|
1499
|
-
var mergeCommitForPublish = (
|
|
1500
|
-
var
|
|
1501
|
-
if (!
|
|
1489
|
+
var mergeCommitForPublish = String(diffSummary.commit || "").trim();
|
|
1490
|
+
var publishDiffSha256 = String(diffSummary.sha256 || "");
|
|
1491
|
+
if (!diffSummary.bytes) {
|
|
1502
1492
|
return await parkTask("Publish diff is empty for commit " + (mergeCommitForPublish || "unknown") + " — a merge lock was held but there is no change to carry. Human attention needed.");
|
|
1503
1493
|
}
|
|
1504
|
-
if (
|
|
1505
|
-
return await parkTask("Publish diff contains binary files — the
|
|
1494
|
+
if (diffSummary.has_binary) {
|
|
1495
|
+
return await parkTask("Publish diff contains binary files — the diff transport cannot carry them. Human attention needed.");
|
|
1506
1496
|
}
|
|
1507
|
-
if (
|
|
1497
|
+
if (diffSummary.has_rename) {
|
|
1508
1498
|
return await parkTask("Publish diff contains a rename — the diff transport cannot carry renames. Human attention needed.");
|
|
1509
1499
|
}
|
|
1510
|
-
var expectedChanges = parseUnifiedDiff(mergeDiff);
|
|
1511
1500
|
// Budget counts CHANGED lines (added + removed), not raw unified-diff
|
|
1512
1501
|
// output lines: context lines and file headers inflated the old
|
|
1513
1502
|
// split("\n").length count ~2x, parking a 95-line change against a
|
|
1514
|
-
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13).
|
|
1515
|
-
|
|
1503
|
+
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13). The count
|
|
1504
|
+
// now comes from the deterministic script summary, never from the LLM.
|
|
1505
|
+
var mergeDiffChangedLines = diffSummary.changed_lines || 0;
|
|
1516
1506
|
if (mergeDiffChangedLines > 200) {
|
|
1517
1507
|
return await parkTask("Publish diff changes " + mergeDiffChangedLines + " lines (budget 200) — too large for the diff transport. Human attention needed.");
|
|
1518
1508
|
}
|
|
1519
|
-
if (
|
|
1509
|
+
if (!diffSummary.file_count) {
|
|
1520
1510
|
return await parkTask("Publish diff parsed to zero files for commit " + (mergeCommitForPublish || "unknown") + " — cannot verify application. Human attention needed.");
|
|
1521
1511
|
}
|
|
1512
|
+
// The trigger agent reads the diff from the checksummed file — the
|
|
1513
|
+
// workflow never holds diff bytes (room #14, 2026-09-17). The file
|
|
1514
|
+
// is small by construction: the 200-line budget above gates the
|
|
1515
|
+
// trigger. The sha256 check is the agent's only verification step;
|
|
1516
|
+
// a mismatch stops the trigger before any artifact_edit call.
|
|
1522
1517
|
var rebuildPrompt =
|
|
1523
1518
|
ARTIFACT_LOAD_PREAMBLE +
|
|
1524
|
-
"
|
|
1519
|
+
"The change to apply is the unified diff in the file \"" + publishDiffFile + "\" (sha256 " + publishDiffSha256 + ").\n" +
|
|
1520
|
+
"1. Verify the file: run sha256sum on it. If the printed hash is not exactly " + publishDiffSha256 + ", STOP and end your turn — do not call artifact_edit.\n" +
|
|
1521
|
+
"2. Read the file's full content.\n" +
|
|
1522
|
+
"3. Call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
1525
1523
|
"'Apply the following change to your source tree, then rebuild and deploy.\n" +
|
|
1526
1524
|
"\n" +
|
|
1527
1525
|
"UNIFIED DIFF (relative to your source tree):\n" +
|
|
1528
|
-
"```diff\n
|
|
1526
|
+
"```diff\n<the full content of the verified file, pasted verbatim>\n```\n" +
|
|
1529
1527
|
"\n" +
|
|
1530
1528
|
"Rules:\n" +
|
|
1531
1529
|
"- For each file in the diff, apply its hunks to the same path in your source tree (use git apply or equivalent).\n" +
|
|
@@ -2528,11 +2526,11 @@ while (i < STEPS.length) {
|
|
|
2528
2526
|
// dashboard QA source check).
|
|
2529
2527
|
try {
|
|
2530
2528
|
var provRefresh = await agent(
|
|
2531
|
-
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-provenance", {}) + "\n" +
|
|
2529
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
2532
2530
|
"If the response has no provenance (null), return JSON { \"refreshed\": false, \"reason\": \"no-record\" } and stop. " +
|
|
2533
2531
|
"Otherwise run: basename $(readlink " + crewHome + "/current) — call this REL; " +
|
|
2534
2532
|
"run: date -u +%Y-%m-%dT%H:%M:%SZ — call this TS. " +
|
|
2535
|
-
"Then run in shell:\n" + crewCmd("set-provenance", { source_commit: "<existing provenance.source_commit>", crew_release: "<REL trimmed>", published_at: "<TS trimmed>", task_id: taskId }) + "\n" +
|
|
2533
|
+
"Then run in shell:\n" + crewCmd("set-provenance", { project_id: LAUNCH_PROJECT_ID, source_commit: "<existing provenance.source_commit>", crew_release: "<REL trimmed>", published_at: "<TS trimmed>", task_id: taskId }) + "\n" +
|
|
2536
2534
|
"(substitute the real existing source_commit, REL, and TS for the placeholders). " +
|
|
2537
2535
|
"Return JSON { \"refreshed\": <true if the set-provenance stdout contains ok: true, false otherwise>, \"crew_release\": \"<REL trimmed>\", \"published_at\": \"<TS trimmed>\" } and nothing else.",
|
|
2538
2536
|
{ key: attemptKey("publish-provenance-refresh-" + taskId, reworkCount), label: "Refreshing dashboard provenance after crew release",
|
|
@@ -941,11 +941,17 @@ await agent(
|
|
|
941
941
|
// not timestamps — no wall-clock calls (see tests/determinism.test.js).
|
|
942
942
|
// Fire-and-forget: the script swallows its own errors and exits 0, the
|
|
943
943
|
// agent call carries no schema, and the whole call is wrapped in
|
|
944
|
-
// try/catch — a failed write can never fail the tick.
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
944
|
+
// try/catch — a failed write can never fail the tick. The script echoes
|
|
945
|
+
// the appended line to stdout (2026-09-17, rooms #12–#14): the fire-and-forget
|
|
946
|
+
// agent() call carries no schema and its return is discarded, but the
|
|
947
|
+
// platform's JSON-candidate scan throws on an EMPTY schema-less result —
|
|
948
|
+
// which is what the writer returned before it echoed. Every other
|
|
949
|
+
// fire-and-forget call in this file returns the CLI's non-empty JSON
|
|
950
|
+
// stdout verbatim, so the writer now speaks the same proven shape.
|
|
951
|
+
// Placement: every run that completes the poll (dispatching, NO_DISPATCH,
|
|
952
|
+
// partial-cap) reaches this point. Concurrency edge: overlapping ticks
|
|
953
|
+
// could duplicate seq (count+append is not atomic) but appends are atomic,
|
|
954
|
+
// so lines never interleave — seq is advisory, file order is the proof.
|
|
949
955
|
var tickReleaseScript = [
|
|
950
956
|
"var crewHome=process.argv[1];",
|
|
951
957
|
"try{",
|
|
@@ -954,7 +960,16 @@ var tickReleaseScript = [
|
|
|
954
960
|
"var file=path.join(crewHome,\".tick-releases.jsonl\");",
|
|
955
961
|
"var count=0;",
|
|
956
962
|
"try{var lines=fs.readFileSync(file,\"utf8\").split(\"\\n\");for(var i=0;i<lines.length;i++){if(lines[i].trim()!==\"\"){count++;}}}catch(e){}",
|
|
957
|
-
"
|
|
963
|
+
"var line=JSON.stringify({seq:count+1,release:release});",
|
|
964
|
+
"fs.appendFileSync(file,line+\"\\n\");",
|
|
965
|
+
// Rooms #12–#14: echo the appended line to stdout. The fire-and-forget
|
|
966
|
+
// agent() call below carries no schema and its return is discarded — but
|
|
967
|
+
// the platform's JSON-candidate scan throws on an EMPTY schema-less
|
|
968
|
+
// result, which is exactly what this script returned before (exit 0, no
|
|
969
|
+
// output). Every other fire-and-forget call in this file returns the
|
|
970
|
+
// CLI's non-empty JSON stdout verbatim, so the writer now speaks the same
|
|
971
|
+
// proven shape: the echoed line is the evidence of the write.
|
|
972
|
+
"process.stdout.write(line+\"\\n\");",
|
|
958
973
|
"}catch(e){}",
|
|
959
974
|
"process.exit(0);"
|
|
960
975
|
].join("");
|
package/workflows/crew-init.js
CHANGED
|
@@ -350,6 +350,7 @@ log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped"
|
|
|
350
350
|
// projects later via crew-api.js create-project.
|
|
351
351
|
phase("project");
|
|
352
352
|
var projectResult;
|
|
353
|
+
var initialProvenanceWarning = null;
|
|
353
354
|
if (!dashboardSlug) {
|
|
354
355
|
log("Project: skipped (CLI-only mode — no dashboard; create projects via crew-api.js create-project)");
|
|
355
356
|
projectResult = { action: "skipped", project_id: null };
|
|
@@ -380,7 +381,8 @@ try {
|
|
|
380
381
|
" Return { action: \"repaired\", project_id: \"" + dashboardSlug + "\" }.\n" +
|
|
381
382
|
"3. If not found (error), create it:\n" +
|
|
382
383
|
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " create-project --json '{\"id\": \"" + dashboardSlug + "\", \"display_name\": \"" + safeDashboardName + "\", \"repo_path\": \"" + safeDashboardRepo + "\", \"deploy_type\": \"artifact\", \"deploy_slug\": \"" + dashboardSlug + "\", \"environment_type\": \"artifact\", \"description\": \"The dashboard task service — the crew\\u0027s first project\"}'\n" +
|
|
383
|
-
"
|
|
384
|
+
" The create-project response carries initial_provenance { stamped, reason?, source_commit?, crew_release? } — pass it through verbatim.\n" +
|
|
385
|
+
" Return { action: \"created\", project_id: \"" + dashboardSlug + "\", initial_provenance: <the verbatim initial_provenance object> }.\n\n" +
|
|
384
386
|
"4. Seed the update watcher's trusted dashboard base (enrollment) — the\n" +
|
|
385
387
|
" commit this installation starts from. Only ever seeds when no base\n" +
|
|
386
388
|
" exists; an established base is never moved by a re-init:\n" +
|
|
@@ -394,7 +396,7 @@ try {
|
|
|
394
396
|
" dashboard upgrade with oldSha unknown and park it with enrollment\n" +
|
|
395
397
|
" guidance, fail-closed.\n\n" +
|
|
396
398
|
"The repo_path must be the dashboard git repository — never the crew home.\n" +
|
|
397
|
-
"Return JSON with action (\"exists\", \"repaired\", or \"created\")
|
|
399
|
+
"Return JSON with action (\"exists\", \"repaired\", or \"created\"), project_id (string), and — only when action is \"created\" — the create-project response's initial_provenance object verbatim.",
|
|
398
400
|
{
|
|
399
401
|
key: "project-1",
|
|
400
402
|
label: "Register dashboard project",
|
|
@@ -402,7 +404,8 @@ try {
|
|
|
402
404
|
type: "object",
|
|
403
405
|
properties: {
|
|
404
406
|
action: { type: "string", enum: ["exists", "repaired", "created"] },
|
|
405
|
-
project_id: { type: "string" }
|
|
407
|
+
project_id: { type: "string" },
|
|
408
|
+
initial_provenance: { type: "object" }
|
|
406
409
|
},
|
|
407
410
|
required: ["action", "project_id"]
|
|
408
411
|
}
|
|
@@ -412,6 +415,18 @@ try {
|
|
|
412
415
|
return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Project registration failed", message: String(e.message || e) } };
|
|
413
416
|
}
|
|
414
417
|
log("Project: " + dashboardSlug + " " + projectResult.action + " (repo_path=" + gateFacts.dashboardRepoExpanded + ")");
|
|
418
|
+
if (projectResult && projectResult.action === "created" &&
|
|
419
|
+
projectResult.initial_provenance && !projectResult.initial_provenance.stamped) {
|
|
420
|
+
// Room #15 (reliability): create-project's initial stamp is best-effort,
|
|
421
|
+
// but "reported" used to mean "returned into the void" — zero production
|
|
422
|
+
// code read it, so a skipped stamp (the J1/J2 shape) stayed invisible.
|
|
423
|
+
// Surface it here; the publish-base advance below re-stamps right after,
|
|
424
|
+
// and fails loudly (exit 1) if the re-stamp itself cannot stamp.
|
|
425
|
+
initialProvenanceWarning = "Project created but its initial provenance stamp was skipped (" +
|
|
426
|
+
(projectResult.initial_provenance.reason || "unknown reason") +
|
|
427
|
+
") — the publish-base advance re-stamps it next; if that also fails, the first task's publish diffs from the empty tree.";
|
|
428
|
+
log("WARNING: " + initialProvenanceWarning);
|
|
429
|
+
}
|
|
415
430
|
} // end dashboard-mode project registration
|
|
416
431
|
|
|
417
432
|
// ── Project repo setup (2026-09-18) ─────────────────────────────────
|
|
@@ -492,6 +507,56 @@ if (dashboardRepoPath) {
|
|
|
492
507
|
log("Repo setup: no repo (CLI-only mode) — skipping.");
|
|
493
508
|
}
|
|
494
509
|
|
|
510
|
+
// ── Publish-base advance (2026-09-18) ─────────────────────────────────
|
|
511
|
+
// Room #14: create-project stamps provenance.source_commit at the
|
|
512
|
+
// pre-scaffold repo HEAD, then the scaffold commits .orchestration/ (and the
|
|
513
|
+
// consented .gitignore) on top. The first task's publish diff then carries
|
|
514
|
+
// the whole scaffold (517 insertions in room #14) and blows the 200-line
|
|
515
|
+
// diff-transport budget. The scaffold is crew-owned infrastructure the
|
|
516
|
+
// dashboard artifact never consumes, so the correct publish base is the
|
|
517
|
+
// scaffold commit itself. Advance it deterministically via
|
|
518
|
+
// lib/advance-publish-base.js — only when this init created the project
|
|
519
|
+
// (the stamp is ours, never a real publish's), the scaffold commit's parent
|
|
520
|
+
// is exactly the stamped base (nothing slipped between), and the scaffold
|
|
521
|
+
// touches only crew-owned paths. Best-effort: a failed advance never blocks
|
|
522
|
+
// init — the first task then publishes from the stamped base as before.
|
|
523
|
+
var publishBaseResult = null;
|
|
524
|
+
if (dashboardRepoPath && projectResult && projectResult.action === "created" &&
|
|
525
|
+
setupRepoResult && setupRepoResult.commit && setupRepoResult.commit.committed) {
|
|
526
|
+
try {
|
|
527
|
+
publishBaseResult = await agent(
|
|
528
|
+
"Run the deterministic publish-base advance script. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
529
|
+
"Command:\n" +
|
|
530
|
+
"node " + crewHome + "/current/lib/advance-publish-base.js --crew-home \"" + crewHome + "\" --repo \"" + gateFacts.dashboardRepoExpanded + "\" --scaffold-sha " + setupRepoResult.commit.sha + " --project-id " + projectResult.project_id + " --project-created\n\n" +
|
|
531
|
+
"The script prints one JSON object: { ok, action, reason?, from?, to? }.\n" +
|
|
532
|
+
"Return that JSON verbatim.",
|
|
533
|
+
{
|
|
534
|
+
key: "advance-publish-base",
|
|
535
|
+
label: "Advance publish base past scaffold commit",
|
|
536
|
+
schema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
ok: { type: "boolean" },
|
|
540
|
+
action: { type: "string" },
|
|
541
|
+
reason: {},
|
|
542
|
+
from: {},
|
|
543
|
+
to: {}
|
|
544
|
+
},
|
|
545
|
+
required: ["ok", "action"]
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
log("Publish base: " + publishBaseResult.action +
|
|
550
|
+
(publishBaseResult.reason ? " (" + publishBaseResult.reason + ")" : "") +
|
|
551
|
+
(publishBaseResult.from ? " " + publishBaseResult.from + " -> " + publishBaseResult.to : ""));
|
|
552
|
+
} catch (e) {
|
|
553
|
+
log("Publish base: advance script failed (" + String(e.message || e) + ") — continuing with the stamped base (non-fatal).");
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
log("Publish base: no advance (project " + (projectResult ? projectResult.action : "unknown") +
|
|
557
|
+
", scaffold " + (setupRepoResult && setupRepoResult.commit && setupRepoResult.commit.committed ? "committed" : "not committed") + ").");
|
|
558
|
+
}
|
|
559
|
+
|
|
495
560
|
// ── Scheduler identity (2026-09-16) ──────────────────────────────────
|
|
496
561
|
// The crew's scheduler identity is dashboard-independent and chosen once.
|
|
497
562
|
// First init uses the crew-home basename; a re-run keeps the instanceId
|
|
@@ -751,6 +816,7 @@ return {
|
|
|
751
816
|
scaffold: { created: scaffoldCreated, skipped: scaffoldSkipped },
|
|
752
817
|
project: projectResult.action,
|
|
753
818
|
repoSetupWarning: setupRepoWarning,
|
|
819
|
+
initialProvenanceWarning: initialProvenanceWarning,
|
|
754
820
|
crons: cronsResult.summary.crons,
|
|
755
821
|
registry: cronsResult.summary.registry,
|
|
756
822
|
autoUpdate: {
|