muse-crew 0.14.2 → 0.14.4
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/AGENTS.md +1 -1
- package/docs/decisions/AGENTS.md +4 -0
- package/docs/decisions/publish-path.md +229 -0
- package/docs/publish-verification.md +14 -3
- package/docs/release-integrity.md +60 -0
- package/docs/reviews/critic-0143-redo.md +94 -0
- package/docs/reviews/critic-0143.md +108 -0
- package/docs/reviews/critic-0144.md +83 -0
- package/lib/AGENTS.md +14 -6
- package/lib/advance-publish-base.js +8 -0
- package/lib/append-ooda-step.js +12 -3
- package/lib/build-readback-request.js +10 -0
- package/lib/build-registry.js +8 -3
- package/lib/classify-publish-absence.js +11 -0
- package/lib/classify-surface.js +12 -2
- package/lib/commit-scaffold.js +22 -6
- package/lib/compose-evidence-caption.js +15 -4
- package/lib/compute-publish-diff.js +9 -0
- package/lib/crew-api.js +55 -20
- package/lib/crew-release.sh +233 -1
- package/lib/gitignore.js +23 -5
- package/lib/package.json +1 -0
- package/lib/publish-note-vocabulary.js +44 -0
- package/lib/publish-npm.sh +39 -10
- package/lib/read-ooda-verdict.js +11 -3
- package/lib/readback-disk.js +9 -0
- package/lib/render-html.js +17 -7
- package/lib/repo-orchestration.js +21 -5
- package/lib/retry-publish.js +79 -44
- package/lib/sample-project.js +22 -6
- package/lib/scaffold-crew.js +11 -2
- package/lib/see-act.js +17 -8
- package/lib/serve-artifact.js +12 -6
- package/lib/setup-project-repo.js +26 -7
- package/lib/test-detached-integrate.sh +79 -3
- package/lib/test-publish-preflight.sh +94 -5
- package/lib/update-watch.js +34 -17
- package/lib/ux-doctrine.js +31 -6
- package/lib/verify-publish.js +41 -6
- package/lib/worktree-lifecycle.sh +67 -25
- package/lib/write-ooda-verdict.js +12 -3
- package/package.json +1 -1
- package/workflows/bugfix.js +60 -61
- package/workflows/chore.js +60 -61
- package/workflows/standard.js +60 -42
- package/workflows/upgrade.js +4 -2
package/workflows/chore.js
CHANGED
|
@@ -127,8 +127,10 @@ const COMPUTE_DIFF_SRC = crewHome + "/current/lib/compute-publish-diff.js";
|
|
|
127
127
|
const COMPUTE_DIFF = RUN_LIB + "/compute-publish-diff.js";
|
|
128
128
|
const CLASSIFY_SURFACE_SRC = crewHome + "/current/lib/classify-surface.js";
|
|
129
129
|
const CLASSIFY_SURFACE = RUN_LIB + "/classify-surface.js";
|
|
130
|
+
const NOTE_VOCAB_SRC = crewHome + "/current/lib/publish-note-vocabulary.js";
|
|
131
|
+
const NOTE_VOCAB = RUN_LIB + "/publish-note-vocabulary.js";
|
|
130
132
|
// See docs/decisions/qa-reproduce.md#pin-basenames: the pin step materializes the required scripts.
|
|
131
|
-
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(); });
|
|
133
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, COMPUTE_DIFF, CLASSIFY_SURFACE, NOTE_VOCAB].map(function (p) { return p.split("/").pop(); });
|
|
132
134
|
|
|
133
135
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
134
136
|
const projectConfig = inputs.project_config || {};
|
|
@@ -250,19 +252,12 @@ async function reaskVerdict(stepName, reworkSuffix, workerText) {
|
|
|
250
252
|
return verdict;
|
|
251
253
|
}
|
|
252
254
|
// Transport retry: the work-agent agent() call can throw even when the agent
|
|
253
|
-
// did the work
|
|
254
|
-
// the
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
// The verdict re-ask covers an unreadable verdict inside a RECEIVED report;
|
|
260
|
-
// this covers the report never arriving. The assignment is retried boundedly
|
|
261
|
-
// with fresh keys (never a cached replay) before failing closed. Re-entry is
|
|
262
|
-
// safe: lifecycle scripts answer REUSED for existing worktrees/branches, the
|
|
263
|
-
// retry trailer tells the agent to check existing state first and report
|
|
264
|
-
// rather than duplicate completed side effects, and the rework path already
|
|
265
|
-
// re-runs Build after rejection — Build re-entry is an established pattern.
|
|
255
|
+
// did the work — the runtime's JSON-candidate heuristic trips on brace-shaped
|
|
256
|
+
// prose. Retry the assignment boundedly with fresh keys (never a cached
|
|
257
|
+
// replay) before failing closed. Re-entry is safe: lifecycle scripts answer
|
|
258
|
+
// REUSED for existing worktrees/branches, the retry trailer tells the agent
|
|
259
|
+
// to check existing state first, and Build re-entry after rejection is an
|
|
260
|
+
// established pattern. Full history: docs/decisions/workflow-core.md.
|
|
266
261
|
function workRetryKey(stepName, reworkSuffix, attempt) {
|
|
267
262
|
return "work-" + stepName + reworkSuffix + "-t" + attempt;
|
|
268
263
|
}
|
|
@@ -274,7 +269,7 @@ function attemptKey(base, reworkCount) {
|
|
|
274
269
|
function pinLifecycle(key) {
|
|
275
270
|
return agent(
|
|
276
271
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
277
|
-
"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" +
|
|
272
|
+
"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 + " && cp " + NOTE_VOCAB_SRC + " " + NOTE_VOCAB + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
278
273
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
279
274
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
280
275
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -455,27 +450,11 @@ function hydrateReleaseDecision(rec) {
|
|
|
455
450
|
return null;
|
|
456
451
|
}
|
|
457
452
|
|
|
458
|
-
// Publish read-back request (currently unavailable): the verbatim_request
|
|
459
|
-
// the parent protocol (docs/publish-verification.md) would hand to an
|
|
460
|
-
// independent read-back tool after the artifact build lands. artifact_inspect
|
|
461
|
-
// was removed by the platform (2026-09-14); artifact.inspect is malfunction
|
|
462
|
-
// diagnosis, not a substitute — so no agent-callable read-back tool exists
|
|
463
|
-
// and this request cannot currently be issued. Pure function — no I/O, no
|
|
464
|
-
// clock. The request carries the merged diff as the expected change and asks
|
|
465
|
-
// for an independent read of the artifact's actual source: for each file, the
|
|
466
|
-
// exact current text of the changed regions plus a per-line present/absent
|
|
467
|
-
// finding. Until a read-back path exists, the parent cannot independently
|
|
468
|
-
// confirm content and verification parks at "publish: verification-requested"
|
|
469
|
-
// (see docs/publish-verification.md). This preserves the circularity break
|
|
470
|
-
// that hollowed canary run 8 (2026-09-11): the old verifyAppliedChanges
|
|
471
|
-
// compared the builder's applied-report against the diff the report was
|
|
472
|
-
// derived from — a fabricated report passed by construction. The report
|
|
473
|
-
// itself is gone now (2026-09-16 fire-and-forget trigger). Independent
|
|
474
|
-
// read-back cannot be
|
|
475
|
-
// fabricated from the diff; it must match the artifact's real content.
|
|
476
|
-
|
|
477
453
|
// See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
|
|
478
|
-
|
|
454
|
+
// keyTag (optional): same-outcome ledger writes in one pass need distinct
|
|
455
|
+
// agent-call keys — the runtime replays cached calls by key, so a shared key
|
|
456
|
+
// silently drops the second write (2026-09-19, submitted-on-issuance).
|
|
457
|
+
async function recordPublishLedger(entry, rework, keyTag) {
|
|
479
458
|
try {
|
|
480
459
|
var ledgerDir = crewHome + "/.publish-ledger";
|
|
481
460
|
var line = JSON.stringify({
|
|
@@ -489,7 +468,10 @@ async function recordPublishLedger(entry, rework) {
|
|
|
489
468
|
applied_report: entry.applied_report || null,
|
|
490
469
|
manifest_before: entry.manifest_before || null,
|
|
491
470
|
outcome: entry.outcome,
|
|
492
|
-
detail: entry.detail || ""
|
|
471
|
+
detail: entry.detail || "",
|
|
472
|
+
// D1 (2026-09-19): issued_at = upper bound on the trigger-issuance
|
|
473
|
+
// instant (null when no trigger); ts = ledger-write instant.
|
|
474
|
+
issued_at: entry.issued_at || null,
|
|
493
475
|
});
|
|
494
476
|
var sq = function(s) { return "'" + String(s).split("'").join("'\\''") + "'"; };
|
|
495
477
|
var res = await agent(
|
|
@@ -497,7 +479,7 @@ async function recordPublishLedger(entry, rework) {
|
|
|
497
479
|
"Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
|
|
498
480
|
" | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
|
|
499
481
|
"Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
|
|
500
|
-
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
|
|
482
|
+
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
|
|
501
483
|
label: "Recording publish attempt in ledger",
|
|
502
484
|
schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
|
|
503
485
|
);
|
|
@@ -1195,7 +1177,7 @@ while (i < STEPS.length) {
|
|
|
1195
1177
|
"The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
|
|
1196
1178
|
"- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
|
|
1197
1179
|
"- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
|
|
1198
|
-
"- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash
|
|
1180
|
+
"- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
|
|
1199
1181
|
"- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
|
|
1200
1182
|
"- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
|
|
1201
1183
|
"RESOLUTION:\n" +
|
|
@@ -1208,7 +1190,7 @@ while (i < STEPS.length) {
|
|
|
1208
1190
|
"ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
|
|
1209
1191
|
"- ERROR: report it. VERDICT: FAIL.\n\n" +
|
|
1210
1192
|
"R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
|
|
1211
|
-
"PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED
|
|
1193
|
+
"PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
|
|
1212
1194
|
"NEVER force-push.\n\n" +
|
|
1213
1195
|
"Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
1214
1196
|
|
|
@@ -1546,7 +1528,7 @@ while (i < STEPS.length) {
|
|
|
1546
1528
|
log("Publish pre-trigger baseline read failed for task " + taskId + " (" + (baselineErr && baselineErr.message ? baselineErr.message : baselineErr) + ") — receipt attribution skipped; durable audit-dir evidence is the only positive signal");
|
|
1547
1529
|
}
|
|
1548
1530
|
// See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
|
|
1549
|
-
var rebuildTrigger = null;
|
|
1531
|
+
var rebuildTrigger = null, triggerThrew = false;
|
|
1550
1532
|
try {
|
|
1551
1533
|
var triggerText = String(await agent(rebuildPrompt,
|
|
1552
1534
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
|
|
@@ -1566,7 +1548,36 @@ while (i < STEPS.length) {
|
|
|
1566
1548
|
}
|
|
1567
1549
|
} catch (triggerErr) {
|
|
1568
1550
|
log("Publish rebuild trigger for task " + taskId + " threw (" + (triggerErr && triggerErr.message ? triggerErr.message : triggerErr) + ") — outcome unknown until observation confirms it; the edit may have gone through");
|
|
1551
|
+
triggerThrew = true;
|
|
1569
1552
|
}
|
|
1553
|
+
// D1 (2026-09-19; F-A1 upper-bound 2026-09-20): issued_at is an UPPER
|
|
1554
|
+
// bound on issuance — captured AFTER the trigger-agent call returns,
|
|
1555
|
+
// ferried from one shell date call (workflows have no clock) and
|
|
1556
|
+
// validated mechanically; null when unobserved (fallback: ts).
|
|
1557
|
+
// See docs/decisions/publish-path.md#d1-issuance-time.
|
|
1558
|
+
var triggerIssuedAt = null;
|
|
1559
|
+
try {
|
|
1560
|
+
var issuedAtFerry = await agent(
|
|
1561
|
+
"Run: date -u +%Y-%m-%dT%H:%M:%SZ\n" +
|
|
1562
|
+
"Return JSON { \"ts\": \"<the date output, verbatim>\" } and nothing else.",
|
|
1563
|
+
{ key: attemptKey("publish-issued-at-" + taskId, reworkCount), label: "Capturing post-trigger instant (upper bound on issuance)",
|
|
1564
|
+
schema: { type: "object", properties: { ts: { type: "string" } }, required: ["ts"] } }
|
|
1565
|
+
);
|
|
1566
|
+
var issuedAtRaw = String((issuedAtFerry && issuedAtFerry.ts) || "").trim();
|
|
1567
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(issuedAtRaw)) triggerIssuedAt = issuedAtRaw;
|
|
1568
|
+
} catch (issuedAtErr) { /* issued_at stays null: the verifier falls back to ts */ }
|
|
1569
|
+
if (!triggerIssuedAt) log("Publish issuance instant unobserved for task " + taskId + " — issued_at null; the freshness gate falls back to ledger-write ts");
|
|
1570
|
+
// (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
|
|
1571
|
+
await recordPublishLedger({
|
|
1572
|
+
commit: mergeCommitForPublish,
|
|
1573
|
+
attempt: rebuildAttemptKey,
|
|
1574
|
+
agent_id: null,
|
|
1575
|
+
applied_report: publishAppliedObservation,
|
|
1576
|
+
manifest_before: preTriggerManifest,
|
|
1577
|
+
outcome: "submitted",
|
|
1578
|
+
issued_at: triggerIssuedAt,
|
|
1579
|
+
detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
|
|
1580
|
+
}, reworkCount, "issuance");
|
|
1570
1581
|
// Post-trigger observation (primary, not fallback): the workflow
|
|
1571
1582
|
// attributes the edit itself. First the in-flight build state — a
|
|
1572
1583
|
// build whose agent_id is new relative to the pre-trigger baseline
|
|
@@ -1686,8 +1697,9 @@ while (i < STEPS.length) {
|
|
|
1686
1697
|
applied_report: publishAppliedObservation,
|
|
1687
1698
|
manifest_before: preTriggerManifest,
|
|
1688
1699
|
outcome: "submitted",
|
|
1689
|
-
|
|
1690
|
-
|
|
1700
|
+
issued_at: triggerIssuedAt,
|
|
1701
|
+
detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
|
|
1702
|
+
}, reworkCount, "receipt");
|
|
1691
1703
|
} else {
|
|
1692
1704
|
var newAuditDirs = [];
|
|
1693
1705
|
try {
|
|
@@ -1818,10 +1830,6 @@ while (i < STEPS.length) {
|
|
|
1818
1830
|
// its outcome — submitted, rejected, or unknown (unknown is recorded
|
|
1819
1831
|
// at the park site above). A later run or human matches commit hash +
|
|
1820
1832
|
// attempt key against the builder's eventual completion.
|
|
1821
|
-
// (2026-09-16) The trigger is fire-and-forget: the observation above
|
|
1822
|
-
// already recorded the ledger's submitted line on both positive paths
|
|
1823
|
-
// and parked on unknown — there is no applied report to observe and
|
|
1824
|
-
// no rejection signal to record.
|
|
1825
1833
|
// (2026-09-18, H2 verdict-first) The verdict was decided exactly
|
|
1826
1834
|
// once above; dispatch on it. landed bypasses the receipt poll
|
|
1827
1835
|
// (the audit evidence already proved completion); an explicit
|
|
@@ -1955,21 +1963,12 @@ while (i < STEPS.length) {
|
|
|
1955
1963
|
log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
|
|
1956
1964
|
} else {
|
|
1957
1965
|
// STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
|
|
1958
|
-
// the poll
|
|
1959
|
-
//
|
|
1960
|
-
//
|
|
1961
|
-
//
|
|
1962
|
-
//
|
|
1963
|
-
//
|
|
1964
|
-
// that appeared during the attempt window is evidence a build
|
|
1965
|
-
// completed. Attribution is by window, not by build identity:
|
|
1966
|
-
// the poll's saw_stranger signal only catches stranger builds in
|
|
1967
|
-
// flight AT a check — a stranger that finished entirely inside
|
|
1968
|
-
// the window is indistinguishable, so any observed stranger
|
|
1969
|
-
// blocks attribution and the outcome stays unknown. This never
|
|
1970
|
-
// re-issues the edit and never stamps provenance — ok=true only
|
|
1971
|
-
// routes to the parent's independent content read-back, which
|
|
1972
|
-
// remains the real verification.
|
|
1966
|
+
// the poll observes only in-flight builds; a build that finished
|
|
1967
|
+
// inside the window leaves a durable audit dir. Diff the audit
|
|
1968
|
+
// listing against the pre-trigger snapshot: a new timestamped dir
|
|
1969
|
+
// is evidence a build completed. Attribution is by window, not by build identity: any
|
|
1970
|
+
// observed stranger blocks it. Never re-issues, never stamps;
|
|
1971
|
+
// ok=true routes to the parent's read-back (the verification).
|
|
1973
1972
|
//
|
|
1974
1973
|
// The poll end-state is read from the poll's own observations,
|
|
1975
1974
|
// not from build_done alone: a build in flight at the last check
|
package/workflows/standard.js
CHANGED
|
@@ -78,8 +78,10 @@ const COMPUTE_DIFF_SRC = crewHome + "/current/lib/compute-publish-diff.js";
|
|
|
78
78
|
const COMPUTE_DIFF = RUN_LIB + "/compute-publish-diff.js";
|
|
79
79
|
const CLASSIFY_SURFACE_SRC = crewHome + "/current/lib/classify-surface.js";
|
|
80
80
|
const CLASSIFY_SURFACE = RUN_LIB + "/classify-surface.js";
|
|
81
|
+
const NOTE_VOCAB_SRC = crewHome + "/current/lib/publish-note-vocabulary.js";
|
|
82
|
+
const NOTE_VOCAB = RUN_LIB + "/publish-note-vocabulary.js";
|
|
81
83
|
// See docs/decisions/qa-reproduce.md#pin-basenames: the pin step materializes the required scripts.
|
|
82
|
-
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(); });
|
|
84
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, COMPUTE_DIFF, CLASSIFY_SURFACE, NOTE_VOCAB].map(function (p) { return p.split("/").pop(); });
|
|
83
85
|
|
|
84
86
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
85
87
|
const projectConfig = inputs.project_config || {};
|
|
@@ -199,19 +201,12 @@ async function reaskVerdict(stepName, reworkSuffix, workerText) {
|
|
|
199
201
|
return verdict;
|
|
200
202
|
}
|
|
201
203
|
// Transport retry: the work-agent agent() call can throw even when the agent
|
|
202
|
-
// did the work
|
|
203
|
-
// the
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
// The verdict re-ask covers an unreadable verdict inside a RECEIVED report;
|
|
209
|
-
// this covers the report never arriving. The assignment is retried boundedly
|
|
210
|
-
// with fresh keys (never a cached replay) before failing closed. Re-entry is
|
|
211
|
-
// safe: lifecycle scripts answer REUSED for existing worktrees/branches, the
|
|
212
|
-
// retry trailer tells the agent to check existing state first and report
|
|
213
|
-
// rather than duplicate completed side effects, and the rework path already
|
|
214
|
-
// re-runs Build after rejection — Build re-entry is an established pattern.
|
|
204
|
+
// did the work — the runtime's JSON-candidate heuristic trips on brace-shaped
|
|
205
|
+
// prose. Retry the assignment boundedly with fresh keys (never a cached
|
|
206
|
+
// replay) before failing closed. Re-entry is safe: lifecycle scripts answer
|
|
207
|
+
// REUSED for existing worktrees/branches, the retry trailer tells the agent
|
|
208
|
+
// to check existing state first, and Build re-entry after rejection is an
|
|
209
|
+
// established pattern. Full history: docs/decisions/workflow-core.md.
|
|
215
210
|
function workRetryKey(stepName, reworkSuffix, attempt) {
|
|
216
211
|
return "work-" + stepName + reworkSuffix + "-t" + attempt;
|
|
217
212
|
}
|
|
@@ -223,7 +218,7 @@ function attemptKey(base, reworkCount) {
|
|
|
223
218
|
function pinLifecycle(key) {
|
|
224
219
|
return agent(
|
|
225
220
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
226
|
-
"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" +
|
|
221
|
+
"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 + " && cp " + NOTE_VOCAB_SRC + " " + NOTE_VOCAB + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
227
222
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
228
223
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
229
224
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -428,7 +423,10 @@ function hydrateReleaseDecision(rec) {
|
|
|
428
423
|
// it must match the artifact's real content.
|
|
429
424
|
|
|
430
425
|
// See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
|
|
431
|
-
|
|
426
|
+
// keyTag (optional): same-outcome ledger writes in one pass need distinct
|
|
427
|
+
// agent-call keys — the runtime replays cached calls by key, so a shared key
|
|
428
|
+
// silently drops the second write (2026-09-19, submitted-on-issuance).
|
|
429
|
+
async function recordPublishLedger(entry, rework, keyTag) {
|
|
432
430
|
try {
|
|
433
431
|
var ledgerDir = crewHome + "/.publish-ledger";
|
|
434
432
|
var line = JSON.stringify({
|
|
@@ -442,7 +440,10 @@ async function recordPublishLedger(entry, rework) {
|
|
|
442
440
|
applied_report: entry.applied_report || null,
|
|
443
441
|
manifest_before: entry.manifest_before || null,
|
|
444
442
|
outcome: entry.outcome,
|
|
445
|
-
detail: entry.detail || ""
|
|
443
|
+
detail: entry.detail || "",
|
|
444
|
+
// D1 (2026-09-19): issued_at = upper bound on the trigger-issuance
|
|
445
|
+
// instant (null when no trigger); ts = ledger-write instant.
|
|
446
|
+
issued_at: entry.issued_at || null,
|
|
446
447
|
});
|
|
447
448
|
var sq = function(s) { return "'" + String(s).split("'").join("'\\''") + "'"; };
|
|
448
449
|
var res = await agent(
|
|
@@ -450,7 +451,7 @@ async function recordPublishLedger(entry, rework) {
|
|
|
450
451
|
"Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
|
|
451
452
|
" | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
|
|
452
453
|
"Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
|
|
453
|
-
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
|
|
454
|
+
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
|
|
454
455
|
label: "Recording publish attempt in ledger",
|
|
455
456
|
schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
|
|
456
457
|
);
|
|
@@ -1182,7 +1183,7 @@ while (i < STEPS.length) {
|
|
|
1182
1183
|
"The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
|
|
1183
1184
|
"- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
|
|
1184
1185
|
"- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
|
|
1185
|
-
"- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash
|
|
1186
|
+
"- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
|
|
1186
1187
|
"- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
|
|
1187
1188
|
"- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
|
|
1188
1189
|
"RESOLUTION:\n" +
|
|
@@ -1195,7 +1196,7 @@ while (i < STEPS.length) {
|
|
|
1195
1196
|
"ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
|
|
1196
1197
|
"- ERROR: report it. VERDICT: FAIL.\n\n" +
|
|
1197
1198
|
"R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
|
|
1198
|
-
"PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED
|
|
1199
|
+
"PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
|
|
1199
1200
|
"NEVER force-push.\n\n" +
|
|
1200
1201
|
"Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
1201
1202
|
|
|
@@ -1533,7 +1534,7 @@ while (i < STEPS.length) {
|
|
|
1533
1534
|
log("Publish pre-trigger baseline read failed for task " + taskId + " (" + (baselineErr && baselineErr.message ? baselineErr.message : baselineErr) + ") — receipt attribution skipped; durable audit-dir evidence is the only positive signal");
|
|
1534
1535
|
}
|
|
1535
1536
|
// See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
|
|
1536
|
-
var rebuildTrigger = null;
|
|
1537
|
+
var rebuildTrigger = null, triggerThrew = false;
|
|
1537
1538
|
try {
|
|
1538
1539
|
var triggerText = String(await agent(rebuildPrompt,
|
|
1539
1540
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
|
|
@@ -1553,7 +1554,36 @@ while (i < STEPS.length) {
|
|
|
1553
1554
|
}
|
|
1554
1555
|
} catch (triggerErr) {
|
|
1555
1556
|
log("Publish rebuild trigger for task " + taskId + " threw (" + (triggerErr && triggerErr.message ? triggerErr.message : triggerErr) + ") — outcome unknown until observation confirms it; the edit may have gone through");
|
|
1557
|
+
triggerThrew = true;
|
|
1556
1558
|
}
|
|
1559
|
+
// D1 (2026-09-19; F-A1 upper-bound 2026-09-20): issued_at is an UPPER
|
|
1560
|
+
// bound on issuance — captured AFTER the trigger-agent call returns,
|
|
1561
|
+
// ferried from one shell date call (workflows have no clock) and
|
|
1562
|
+
// validated mechanically; null when unobserved (fallback: ts).
|
|
1563
|
+
// See docs/decisions/publish-path.md#d1-issuance-time.
|
|
1564
|
+
var triggerIssuedAt = null;
|
|
1565
|
+
try {
|
|
1566
|
+
var issuedAtFerry = await agent(
|
|
1567
|
+
"Run: date -u +%Y-%m-%dT%H:%M:%SZ\n" +
|
|
1568
|
+
"Return JSON { \"ts\": \"<the date output, verbatim>\" } and nothing else.",
|
|
1569
|
+
{ key: attemptKey("publish-issued-at-" + taskId, totalReworkCount), label: "Capturing post-trigger instant (upper bound on issuance)",
|
|
1570
|
+
schema: { type: "object", properties: { ts: { type: "string" } }, required: ["ts"] } }
|
|
1571
|
+
);
|
|
1572
|
+
var issuedAtRaw = String((issuedAtFerry && issuedAtFerry.ts) || "").trim();
|
|
1573
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(issuedAtRaw)) triggerIssuedAt = issuedAtRaw;
|
|
1574
|
+
} catch (issuedAtErr) { /* issued_at stays null: the verifier falls back to ts */ }
|
|
1575
|
+
if (!triggerIssuedAt) log("Publish issuance instant unobserved for task " + taskId + " — issued_at null; the freshness gate falls back to ledger-write ts");
|
|
1576
|
+
// (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
|
|
1577
|
+
await recordPublishLedger({
|
|
1578
|
+
commit: mergeCommitForPublish,
|
|
1579
|
+
attempt: rebuildAttemptKey,
|
|
1580
|
+
agent_id: null,
|
|
1581
|
+
applied_report: publishAppliedObservation,
|
|
1582
|
+
manifest_before: preTriggerManifest,
|
|
1583
|
+
outcome: "submitted",
|
|
1584
|
+
issued_at: triggerIssuedAt,
|
|
1585
|
+
detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
|
|
1586
|
+
}, totalReworkCount, "issuance");
|
|
1557
1587
|
// Post-trigger observation (primary, not fallback): the workflow
|
|
1558
1588
|
// attributes the edit itself. First the in-flight build state — a
|
|
1559
1589
|
// build whose agent_id is new relative to the pre-trigger baseline
|
|
@@ -1673,8 +1703,9 @@ while (i < STEPS.length) {
|
|
|
1673
1703
|
applied_report: publishAppliedObservation,
|
|
1674
1704
|
manifest_before: preTriggerManifest,
|
|
1675
1705
|
outcome: "submitted",
|
|
1676
|
-
|
|
1677
|
-
|
|
1706
|
+
issued_at: triggerIssuedAt,
|
|
1707
|
+
detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
|
|
1708
|
+
}, totalReworkCount, "receipt");
|
|
1678
1709
|
} else {
|
|
1679
1710
|
var newAuditDirs = [];
|
|
1680
1711
|
try {
|
|
@@ -1805,10 +1836,6 @@ while (i < STEPS.length) {
|
|
|
1805
1836
|
// its outcome — submitted, rejected, or unknown (unknown is recorded
|
|
1806
1837
|
// at the park site above). A later run or human matches commit hash +
|
|
1807
1838
|
// attempt key against the builder's eventual completion.
|
|
1808
|
-
// (2026-09-16) The trigger is fire-and-forget: the observation above
|
|
1809
|
-
// already recorded the ledger's submitted line on both positive paths
|
|
1810
|
-
// and parked on unknown — there is no applied report to observe and
|
|
1811
|
-
// no rejection signal to record.
|
|
1812
1839
|
// (2026-09-18, H2 verdict-first) The verdict was decided exactly
|
|
1813
1840
|
// once above; dispatch on it. landed bypasses the receipt poll
|
|
1814
1841
|
// (the audit evidence already proved completion); an explicit
|
|
@@ -1942,21 +1969,12 @@ while (i < STEPS.length) {
|
|
|
1942
1969
|
log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
|
|
1943
1970
|
} else {
|
|
1944
1971
|
// STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
|
|
1945
|
-
// the poll
|
|
1946
|
-
//
|
|
1947
|
-
//
|
|
1948
|
-
//
|
|
1949
|
-
//
|
|
1950
|
-
//
|
|
1951
|
-
// that appeared during the attempt window is evidence a build
|
|
1952
|
-
// completed. Attribution is by window, not by build identity:
|
|
1953
|
-
// the poll's saw_stranger signal only catches stranger builds in
|
|
1954
|
-
// flight AT a check — a stranger that finished entirely inside
|
|
1955
|
-
// the window is indistinguishable, so any observed stranger
|
|
1956
|
-
// blocks attribution and the outcome stays unknown. This never
|
|
1957
|
-
// re-issues the edit and never stamps provenance — ok=true only
|
|
1958
|
-
// routes to the parent's independent content read-back, which
|
|
1959
|
-
// remains the real verification.
|
|
1972
|
+
// the poll observes only in-flight builds; a build that finished
|
|
1973
|
+
// inside the window leaves a durable audit dir. Diff the audit
|
|
1974
|
+
// listing against the pre-trigger snapshot: a new timestamped dir
|
|
1975
|
+
// is evidence a build completed. Attribution is by window, not by build identity: any
|
|
1976
|
+
// observed stranger blocks it. Never re-issues, never stamps;
|
|
1977
|
+
// ok=true routes to the parent's read-back (the verification).
|
|
1960
1978
|
//
|
|
1961
1979
|
// The poll end-state is read from the poll's own observations,
|
|
1962
1980
|
// not from build_done alone: a build in flight at the last check
|
package/workflows/upgrade.js
CHANGED
|
@@ -165,12 +165,14 @@ const SCHEMA_SQL_SRC = crewHome + "/lib/schema.sql";
|
|
|
165
165
|
const SCHEMA_SQL_PINNED = RUN_LIB + "/schema.sql";
|
|
166
166
|
const CLASSIFY_SURFACE_SRC = crewHome + "/current/lib/classify-surface.js";
|
|
167
167
|
const CLASSIFY_SURFACE = RUN_LIB + "/classify-surface.js";
|
|
168
|
+
const NOTE_VOCAB_SRC = crewHome + "/current/lib/publish-note-vocabulary.js";
|
|
169
|
+
const NOTE_VOCAB = RUN_LIB + "/publish-note-vocabulary.js";
|
|
168
170
|
// The six basenames the pin step must materialize — asserted mechanically
|
|
169
171
|
// by workflow code from the verbatim listing, never from agent prose.
|
|
170
172
|
// CLASSIFY_SURFACE is the surface classifier (room #15, 2026-09-18):
|
|
171
173
|
// crew-api.js statically imports it, so the pin must carry it — a pin
|
|
172
174
|
// without it kills every claim with ERR_MODULE_NOT_FOUND.
|
|
173
|
-
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, CLASSIFY_SURFACE].map(function (p) { return p.split("/").pop(); });
|
|
175
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, CLASSIFY_SURFACE, NOTE_VOCAB].map(function (p) { return p.split("/").pop(); });
|
|
174
176
|
|
|
175
177
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
176
178
|
const projectConfig = inputs.project_config || {};
|
|
@@ -201,7 +203,7 @@ if (!taskId) {
|
|
|
201
203
|
function pinLifecycle(key) {
|
|
202
204
|
return agent(
|
|
203
205
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
204
|
-
"Run: mkdir -p " + sq(RUN_LIB) + " && cp " + sq(LIFECYCLE_SRC) + " " + sq(LIFECYCLE) + " && cp " + sq(MERGE_LOCK_SRC) + " " + sq(MERGE_LOCK) + " && cp " + sq(PUBLISH_NPM_SRC) + " " + sq(PUBLISH_NPM) + " && cp " + sq(CREW_API_SRC) + " " + sq(CREW_API_PINNED) + " && cp " + sq(SCHEMA_SQL_SRC) + " " + sq(SCHEMA_SQL_PINNED) + " && cp " + sq(CLASSIFY_SURFACE_SRC) + " " + sq(CLASSIFY_SURFACE) + " && chmod +x " + sq(LIFECYCLE) + " " + sq(MERGE_LOCK) + " " + sq(PUBLISH_NPM) + " && ls -1 " + sq(RUN_LIB) + "\n" +
|
|
206
|
+
"Run: mkdir -p " + sq(RUN_LIB) + " && cp " + sq(LIFECYCLE_SRC) + " " + sq(LIFECYCLE) + " && cp " + sq(MERGE_LOCK_SRC) + " " + sq(MERGE_LOCK) + " && cp " + sq(PUBLISH_NPM_SRC) + " " + sq(PUBLISH_NPM) + " && cp " + sq(CREW_API_SRC) + " " + sq(CREW_API_PINNED) + " && cp " + sq(SCHEMA_SQL_SRC) + " " + sq(SCHEMA_SQL_PINNED) + " && cp " + sq(CLASSIFY_SURFACE_SRC) + " " + sq(CLASSIFY_SURFACE) + " && cp " + sq(NOTE_VOCAB_SRC) + " " + sq(NOTE_VOCAB) + " && chmod +x " + sq(LIFECYCLE) + " " + sq(MERGE_LOCK) + " " + sq(PUBLISH_NPM) + " && ls -1 " + sq(RUN_LIB) + "\n" +
|
|
205
207
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
206
208
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
207
209
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|