muse-crew 0.7.19 → 0.7.20
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-verification.md +4 -2
- package/lib/AGENTS.md +4 -1
- package/lib/compose-evidence-caption.js +57 -14
- package/lib/verify-publish.js +51 -13
- package/package.json +1 -1
- package/workflows/bugfix.js +177 -60
- package/workflows/chore.js +179 -60
- package/workflows/standard.js +187 -67
package/workflows/standard.js
CHANGED
|
@@ -451,13 +451,16 @@ function parseUnifiedDiff(diffText) {
|
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
|
|
454
|
-
// Publish read-back request (currently unavailable): the
|
|
455
|
-
// the parent protocol (docs/publish-verification.md) would
|
|
456
|
-
// independent read-back tool after the artifact build lands.
|
|
457
|
-
// was removed by the platform (2026-09-14);
|
|
458
|
-
// diagnosis, not a substitute — so no
|
|
459
|
-
//
|
|
460
|
-
//
|
|
454
|
+
// Publish read-back request (agent path currently unavailable): the
|
|
455
|
+
// verbatim_request the parent protocol (docs/publish-verification.md) would
|
|
456
|
+
// hand to an independent read-back tool after the artifact build lands.
|
|
457
|
+
// artifact_inspect was removed by the platform (2026-09-14);
|
|
458
|
+
// artifact.inspect is malfunction diagnosis, not a substitute — so no
|
|
459
|
+
// agent-callable read-back tool exists and this LLM-inspector request
|
|
460
|
+
// cannot currently be issued. The primary sensor is now the deterministic
|
|
461
|
+
// lib/readback-disk.js (reads the on-disk tree the artifact is served
|
|
462
|
+
// from); this request builder is retained only as the manual fallback.
|
|
463
|
+
// Pure function — no I/O, no clock. The request carries the merged diff as the expected change and asks
|
|
461
464
|
// for an independent read of the artifact's actual source: for each file, the
|
|
462
465
|
// exact current text of the changed regions plus a per-line present/absent
|
|
463
466
|
// finding. Until a read-back path exists, the parent cannot independently
|
|
@@ -1299,9 +1302,10 @@ while (i < STEPS.length) {
|
|
|
1299
1302
|
// 2026-09-11), so the stamp moved to the parent — after the build
|
|
1300
1303
|
// lands, the workflow records the session completed and parks with
|
|
1301
1304
|
// "publish: verification-requested". The parent owns verification
|
|
1302
|
-
// (docs/publish-verification.md); the
|
|
1303
|
-
//
|
|
1304
|
-
// artifact_inspect was removed by the platform
|
|
1305
|
+
// (docs/publish-verification.md); the primary sensor is the
|
|
1306
|
+
// deterministic lib/readback-disk.js (the agent-callable read-back
|
|
1307
|
+
// tool is unavailable — artifact_inspect was removed by the platform
|
|
1308
|
+
// 2026-09-14 — so the LLM-inspector path is manual-fallback only).
|
|
1305
1309
|
// QA's provenance check enforces the stamp mechanically.
|
|
1306
1310
|
var artifactPublish = null;
|
|
1307
1311
|
var publishLockRefreshed = false;
|
|
@@ -1448,8 +1452,10 @@ while (i < STEPS.length) {
|
|
|
1448
1452
|
// The builder's applied report is gone (2026-09-16): it rode on the
|
|
1449
1453
|
// trigger's JSON closeout contract, which is removed below. The
|
|
1450
1454
|
// parent's independent read-back (docs/publish-verification.md) is
|
|
1451
|
-
// the verification — this field stays "missing-report" on
|
|
1452
|
-
//
|
|
1455
|
+
// the verification — this field stays "missing-report" on ledger
|
|
1456
|
+
// lines for issued triggers; pre-trigger parks (toolcheck
|
|
1457
|
+
// rejected/inconclusive) and unattributed-unknown parks write null
|
|
1458
|
+
// (no trigger was observed, so there is nothing to report).
|
|
1453
1459
|
var publishAppliedObservation = "missing-report";
|
|
1454
1460
|
// Durable-evidence snapshot (2026-09-14): the observation below only
|
|
1455
1461
|
// detects IN-FLIGHT builds. A build that finished before the
|
|
@@ -1460,10 +1466,13 @@ while (i < STEPS.length) {
|
|
|
1460
1466
|
// fallback can diff before/after: a directory appearing during the
|
|
1461
1467
|
// trigger window is positive evidence the edit went through and
|
|
1462
1468
|
// the build completed. Best-effort and non-gating: if the snapshot
|
|
1463
|
-
// fails,
|
|
1464
|
-
//
|
|
1469
|
+
// fails, auditBeforeOk stays false and BOTH fallback comparisons
|
|
1470
|
+
// are disabled (2026-09-16, critic finding 4) — without a baseline,
|
|
1471
|
+
// an empty before-list would make every historical audit dir look
|
|
1472
|
+
// "new". No wall-clock in-script (deterministic replay) — the
|
|
1465
1473
|
// comparison is a pure before/after set diff.
|
|
1466
1474
|
var auditDirsBeforeTrigger = [];
|
|
1475
|
+
var auditBeforeOk = false;
|
|
1467
1476
|
try {
|
|
1468
1477
|
var auditBefore = await agent(
|
|
1469
1478
|
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshot, never a gate).\n" +
|
|
@@ -1473,9 +1482,10 @@ while (i < STEPS.length) {
|
|
|
1473
1482
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1474
1483
|
);
|
|
1475
1484
|
auditDirsBeforeTrigger = String((auditBefore && auditBefore.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1485
|
+
auditBeforeOk = true;
|
|
1476
1486
|
log("Publish audit-dir snapshot before trigger for task " + taskId + ": " + auditDirsBeforeTrigger.length + " entries");
|
|
1477
1487
|
} catch (auditBeforeErr) {
|
|
1478
|
-
log("Publish audit-dir snapshot before trigger failed for task " + taskId + " (non-fatal,
|
|
1488
|
+
log("Publish audit-dir snapshot before trigger failed for task " + taskId + " (non-fatal): audit fallback DISABLED for this attempt — without a baseline, historical dirs would look new: " + (auditBeforeErr && auditBeforeErr.message ? auditBeforeErr.message : auditBeforeErr));
|
|
1479
1489
|
}
|
|
1480
1490
|
// Fire-and-forget trigger + workflow-owned observation (2026-09-16,
|
|
1481
1491
|
// clean-room task e2a8d9f8): the trigger's JSON closeout contract
|
|
@@ -1499,10 +1509,16 @@ while (i < STEPS.length) {
|
|
|
1499
1509
|
// Pre-trigger toolcheck (tiny, schema'd): the artifact namespace is
|
|
1500
1510
|
// deferred for workflow children — the child self-loads it and emits
|
|
1501
1511
|
// one exact signal line, read mechanically (never English prose).
|
|
1502
|
-
//
|
|
1503
|
-
//
|
|
1504
|
-
//
|
|
1512
|
+
// Only a parsed ARTIFACT_TOOLS: missing signal is explicit negative
|
|
1513
|
+
// evidence: it gets one bounded retry with a fresh key, then parks
|
|
1514
|
+
// rejected — without the tools the edit provably did NOT go through,
|
|
1515
|
+
// so this is the one safe retry on the publish path. A throw (or an
|
|
1516
|
+
// unparseable signal) is INCONCLUSIVE transport noise, never
|
|
1517
|
+
// evidence of missing tools (2026-09-16, critic finding 3): it is
|
|
1518
|
+
// recorded, it retries once in case the flake clears, but it can
|
|
1519
|
+
// never take the rejected path.
|
|
1505
1520
|
var publishToolsOk = false;
|
|
1521
|
+
var publishToolsMissing = false;
|
|
1506
1522
|
for (var toolcheckAttempt = 1; toolcheckAttempt <= 2 && !publishToolsOk; toolcheckAttempt++) {
|
|
1507
1523
|
try {
|
|
1508
1524
|
var toolcheckResult = await agent(
|
|
@@ -1514,12 +1530,29 @@ while (i < STEPS.length) {
|
|
|
1514
1530
|
label: "Checking artifact tool availability" + (toolcheckAttempt === 1 ? "" : " (retry)"),
|
|
1515
1531
|
schema: { type: "object", properties: { signal: { type: "string" } }, required: ["signal"] } }
|
|
1516
1532
|
);
|
|
1517
|
-
|
|
1518
|
-
|
|
1533
|
+
var toolSignal = String((toolcheckResult && toolcheckResult.signal) || "");
|
|
1534
|
+
if (/ARTIFACT_TOOLS:\s*ok/.test(toolSignal)) {
|
|
1535
|
+
publishToolsOk = true;
|
|
1536
|
+
} else if (/ARTIFACT_TOOLS:\s*missing/.test(toolSignal)) {
|
|
1537
|
+
publishToolsMissing = true;
|
|
1538
|
+
}
|
|
1539
|
+
log("Publish artifact toolcheck for task " + taskId + " (attempt " + toolcheckAttempt + " of 2): " +
|
|
1540
|
+
(publishToolsOk ? "tools ok" : publishToolsMissing ? "tools missing (explicit parsed signal)" : "inconclusive (no ARTIFACT_TOOLS signal parsed)"));
|
|
1519
1541
|
} catch (toolcheckErr) {
|
|
1520
|
-
log("Publish artifact toolcheck for task " + taskId + " (attempt " + toolcheckAttempt + " of 2)
|
|
1542
|
+
log("Publish artifact toolcheck for task " + taskId + " (attempt " + toolcheckAttempt + " of 2) threw (" + (toolcheckErr && toolcheckErr.message ? toolcheckErr.message : toolcheckErr) + ") — inconclusive: a throw proves nothing about tool availability, never counted as missing");
|
|
1521
1543
|
}
|
|
1522
1544
|
}
|
|
1545
|
+
if (!publishToolsOk && !publishToolsMissing) {
|
|
1546
|
+
await recordPublishLedger({
|
|
1547
|
+
commit: mergeCommitForPublish,
|
|
1548
|
+
attempt: rebuildAttemptKey,
|
|
1549
|
+
agent_id: null,
|
|
1550
|
+
applied_report: null,
|
|
1551
|
+
outcome: "unknown",
|
|
1552
|
+
detail: "artifact toolcheck inconclusive after two attempts (throws or unparseable signals — never an explicit ARTIFACT_TOOLS: missing): tool availability unproven, so the trigger was NOT issued; unknown parks fail closed with no blind retry"
|
|
1553
|
+
}, totalReworkCount);
|
|
1554
|
+
return await parkTask("Publish cannot proceed for task " + taskId + ": the artifact toolcheck was inconclusive after two attempts (no explicit ARTIFACT_TOOLS signal parsed — a throw is transport noise, not evidence). Tool availability is unproven, so no edit was issued and nothing was retried blindly. Human attention needed.");
|
|
1555
|
+
}
|
|
1523
1556
|
if (!publishToolsOk) {
|
|
1524
1557
|
await recordPublishLedger({
|
|
1525
1558
|
commit: mergeCommitForPublish,
|
|
@@ -1527,9 +1560,9 @@ while (i < STEPS.length) {
|
|
|
1527
1560
|
agent_id: null,
|
|
1528
1561
|
applied_report: null,
|
|
1529
1562
|
outcome: "rejected",
|
|
1530
|
-
detail: "artifact tool namespace missing
|
|
1563
|
+
detail: "artifact tool namespace explicitly missing (parsed ARTIFACT_TOOLS: missing signal, one bounded retry spent): the edit provably did not go through — no trigger issued, no blind retry"
|
|
1531
1564
|
}, totalReworkCount);
|
|
1532
|
-
return await parkTask("Publish cannot proceed for task " + taskId + ": the artifact tool namespace was missing
|
|
1565
|
+
return await parkTask("Publish cannot proceed for task " + taskId + ": the artifact tool namespace was explicitly missing (parsed signal — the edit provably did not go through, so no trigger was issued and nothing was retried blindly). Human attention needed.");
|
|
1533
1566
|
}
|
|
1534
1567
|
// Pre-trigger build-state baseline (tiny, schema'd): one read of
|
|
1535
1568
|
// artifact_status. The post-trigger observation diffs against this
|
|
@@ -1555,19 +1588,26 @@ while (i < STEPS.length) {
|
|
|
1555
1588
|
baselineFailed = true;
|
|
1556
1589
|
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");
|
|
1557
1590
|
}
|
|
1558
|
-
// The trigger itself:
|
|
1559
|
-
//
|
|
1560
|
-
//
|
|
1561
|
-
// call
|
|
1562
|
-
//
|
|
1563
|
-
//
|
|
1564
|
-
//
|
|
1565
|
-
//
|
|
1591
|
+
// The trigger itself: the artifact_edit call is AWAITED (the workflow
|
|
1592
|
+
// waits for it to complete) but its return value is intentionally
|
|
1593
|
+
// UNCONSUMED — NO schema, so no schema validation can fail this
|
|
1594
|
+
// call: a schema-less call resolves to the child's raw response as
|
|
1595
|
+
// a plain string (probed live 2026-09-16 — never parsed, never
|
|
1596
|
+
// throws on content). One caveat, also probed: the runtime still
|
|
1597
|
+
// scans the response for a JSON candidate, and an unparseable
|
|
1598
|
+
// {...}-looking substring in the child's prose throws ("response
|
|
1599
|
+
// JSON candidate", probe P6). The prompt tells the child to end its
|
|
1600
|
+
// turn with no prose at all, which keeps the common case clean —
|
|
1601
|
+
// but the channel is stochastic, so any throw is possible and
|
|
1602
|
+
// inconclusive: the edit may still have gone through, so the
|
|
1603
|
+
// outcome stays unknown until the observation below confirms it —
|
|
1604
|
+
// never inferred from the throw, and never blind-retried (a blind
|
|
1605
|
+
// re-trigger duplicated the edit on 2026-09-12).
|
|
1566
1606
|
var rebuildTrigger = null;
|
|
1567
1607
|
try {
|
|
1568
1608
|
var triggerResultLength = String(await agent(rebuildPrompt,
|
|
1569
1609
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "").length;
|
|
1570
|
-
log("Publish rebuild trigger for task " + taskId + " returned (" + triggerResultLength + " chars
|
|
1610
|
+
log("Publish rebuild trigger for task " + taskId + " returned (" + triggerResultLength + " chars; awaited but return intentionally unconsumed)");
|
|
1571
1611
|
} catch (triggerErr) {
|
|
1572
1612
|
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");
|
|
1573
1613
|
}
|
|
@@ -1602,6 +1642,16 @@ while (i < STEPS.length) {
|
|
|
1602
1642
|
log("Publish post-trigger build-state check failed for task " + taskId + " (" + (buildCheckErr && buildCheckErr.message ? buildCheckErr.message : buildCheckErr) + ") — this signal is unknown, not negative");
|
|
1603
1643
|
}
|
|
1604
1644
|
var observedAgentId = (buildState && buildState.build && typeof buildState.build.agent_id === "string" && buildState.build.agent_id) || null;
|
|
1645
|
+
// Known limitation (failure-mode audit 2026-09-16): attribution
|
|
1646
|
+
// is timing-based — any agent_id new relative to the baseline is
|
|
1647
|
+
// treated as this edit's receipt. A stranger's build starting inside
|
|
1648
|
+
// the trigger window is indistinguishable by timing and would be
|
|
1649
|
+
// misattributed here. The consequence is bounded: the completion
|
|
1650
|
+
// poll below tracks the recorded id, and the parent's mechanical
|
|
1651
|
+
// content read-back (docs/publish-verification.md) certifies the
|
|
1652
|
+
// exact commit's content — a wrong build's content fails closed as
|
|
1653
|
+
// verification-failed, never stamped. Timing narrows the candidate;
|
|
1654
|
+
// content decides.
|
|
1605
1655
|
var receiptAgentId = (!buildStateFailed && !baselineFailed && observedAgentId && observedAgentId !== baselineAgentId) ? observedAgentId : null;
|
|
1606
1656
|
if (receiptAgentId) {
|
|
1607
1657
|
// The edit went through — a build with a new agent_id appeared
|
|
@@ -1621,6 +1671,31 @@ while (i < STEPS.length) {
|
|
|
1621
1671
|
}, totalReworkCount);
|
|
1622
1672
|
} else {
|
|
1623
1673
|
var newAuditDirs = [];
|
|
1674
|
+
// auditReportOk: pure tri-state read of a report.json body —
|
|
1675
|
+
// true (build ok), false (build failed), null (missing or
|
|
1676
|
+
// unreadable — not evidence either way). The child returns the
|
|
1677
|
+
// raw body verbatim; interpretation lives here, never in prose.
|
|
1678
|
+
// Defined here so both the immediate and post-poll audit
|
|
1679
|
+
// fallbacks share it.
|
|
1680
|
+
var auditReportOk = function (raw) {
|
|
1681
|
+
if (typeof raw !== "string") return null;
|
|
1682
|
+
var trimmed = raw.trim();
|
|
1683
|
+
if (trimmed === "" || trimmed === "MISSING") return null;
|
|
1684
|
+
var parsed;
|
|
1685
|
+
try { parsed = JSON.parse(trimmed); } catch (e) { return null; }
|
|
1686
|
+
if (parsed && typeof parsed.ok === "boolean") return parsed.ok;
|
|
1687
|
+
return null;
|
|
1688
|
+
};
|
|
1689
|
+
// (2026-09-16, critic finding 2) When durable audit evidence
|
|
1690
|
+
// confirms (or refutes) the build, there is no receipt agent_id
|
|
1691
|
+
// to chain the completion poll to — skipReceiptPoll bypasses the
|
|
1692
|
+
// poll below, which with a null receipt could only observe
|
|
1693
|
+
// strangers or nothing.
|
|
1694
|
+
var skipReceiptPoll = false;
|
|
1695
|
+
// publishFailure is declared here (moved up from below) so the
|
|
1696
|
+
// immediate audit fallback can record an explicit build failure
|
|
1697
|
+
// without the later declaration resetting it.
|
|
1698
|
+
var publishFailure = null;
|
|
1624
1699
|
try {
|
|
1625
1700
|
var auditAfter = await agent(
|
|
1626
1701
|
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort, never a gate).\n" +
|
|
@@ -1631,25 +1706,77 @@ while (i < STEPS.length) {
|
|
|
1631
1706
|
);
|
|
1632
1707
|
var auditDirsAfterTrigger = String((auditAfter && auditAfter.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1633
1708
|
// Only timestamped build dirs count — the "latest" symlink
|
|
1634
|
-
// and anything else are not builds.
|
|
1635
|
-
|
|
1709
|
+
// and anything else are not builds. Gated on auditBeforeOk:
|
|
1710
|
+
// without a baseline every historical dir would look new.
|
|
1711
|
+
newAuditDirs = auditBeforeOk ? auditDirsAfterTrigger.filter(function (d) {
|
|
1636
1712
|
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1637
|
-
});
|
|
1713
|
+
}) : [];
|
|
1638
1714
|
} catch (auditAfterErr) {
|
|
1639
1715
|
log("Publish audit-dir re-list after trigger failed for task " + taskId + " (non-fatal, durable-evidence check degraded): " + (auditAfterErr && auditAfterErr.message ? auditAfterErr.message : auditAfterErr));
|
|
1640
1716
|
}
|
|
1641
1717
|
if (newAuditDirs.length > 0) {
|
|
1642
1718
|
rebuildTrigger = { edit_started: true };
|
|
1643
1719
|
rebuildAgentId = null;
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1720
|
+
newAuditDirs.sort();
|
|
1721
|
+
var newestImmediateDir = newAuditDirs[newAuditDirs.length - 1];
|
|
1722
|
+
log("Publish rebuild trigger for task " + taskId + ": new audit dir(s) during the trigger window (" + newAuditDirs.join(", ") + ") — the edit went through and a build completed; no in-flight receipt was observed.");
|
|
1723
|
+
// (2026-09-16, critic finding 2) Durable audit evidence exists,
|
|
1724
|
+
// but there is no receipt agent_id to chain the completion poll
|
|
1725
|
+
// to — polling with a null receipt can only observe strangers
|
|
1726
|
+
// (any running build differs from "null") or nothing, burning
|
|
1727
|
+
// 10.5 minutes to park unknown. Read the build report now
|
|
1728
|
+
// instead of polling: ok=true confirms completion and routes
|
|
1729
|
+
// directly to parent verification (the poll is skipped);
|
|
1730
|
+
// ok=false is explicit failure; unreadable is unknown.
|
|
1731
|
+
var immediateReportOk = null;
|
|
1732
|
+
try {
|
|
1733
|
+
var immediateOkRead = await agent(
|
|
1734
|
+
"Read the artifact build report for slug \"" + PUBLISH_SLUG + "\".\n" +
|
|
1735
|
+
"Run: cat ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/" + newestImmediateDir + "/report.json 2>/dev/null || echo MISSING\n" +
|
|
1736
|
+
"Return JSON { \"raw\": \"<verbatim file contents, or the literal string MISSING when the file does not exist>\" } and nothing else.",
|
|
1737
|
+
{ key: attemptKey("publish-audit-ok-immediate-" + taskId, totalReworkCount), label: "Reading build report for audit-confirmed build",
|
|
1738
|
+
schema: { type: "object", properties: { raw: { type: "string" } }, required: ["raw"] } }
|
|
1739
|
+
);
|
|
1740
|
+
immediateReportOk = auditReportOk(immediateOkRead && immediateOkRead.raw);
|
|
1741
|
+
} catch (immediateOkErr) {
|
|
1742
|
+
log("Publish build-report read for audit-confirmed dir failed for task " + taskId + " (treated as unknown): " + (immediateOkErr && immediateOkErr.message ? immediateOkErr.message : immediateOkErr));
|
|
1743
|
+
immediateReportOk = null;
|
|
1744
|
+
}
|
|
1745
|
+
if (immediateReportOk === true) {
|
|
1746
|
+
publishBuildLanded = true;
|
|
1747
|
+
artifactPublish = { source_commit: mergeCommitForPublish, pending_parent_verification: true };
|
|
1748
|
+
skipReceiptPoll = true;
|
|
1749
|
+
log("Publish build landed for task " + taskId + " via immediate durable audit evidence (audit dir " + newestImmediateDir + ", report ok=true) — receipt poll skipped (no receipt to chain to), routing directly to parent verification");
|
|
1750
|
+
await recordPublishLedger({
|
|
1751
|
+
commit: mergeCommitForPublish,
|
|
1752
|
+
attempt: rebuildAttemptKey,
|
|
1753
|
+
agent_id: null,
|
|
1754
|
+
applied_report: publishAppliedObservation,
|
|
1755
|
+
outcome: "submitted",
|
|
1756
|
+
detail: "durable audit evidence shows a build completed during the attempt window (audit dir " + newestImmediateDir + ", report ok=true); receipt poll skipped (no receipt agent_id), routed to parent verification"
|
|
1757
|
+
}, totalReworkCount);
|
|
1758
|
+
} else if (immediateReportOk === false) {
|
|
1759
|
+
skipReceiptPoll = true;
|
|
1760
|
+
publishFailure = "Artifact build FAILED for slug " + PUBLISH_SLUG + " (audit dir " + newestImmediateDir + ", report ok=false — immediate audit evidence, no receipt observed). Explicit negative evidence: a build ran and failed. The publish did not land — provenance was not stamped. Fail-closed.";
|
|
1761
|
+
await recordPublishLedger({
|
|
1762
|
+
commit: mergeCommitForPublish,
|
|
1763
|
+
attempt: rebuildAttemptKey,
|
|
1764
|
+
agent_id: null,
|
|
1765
|
+
applied_report: publishAppliedObservation,
|
|
1766
|
+
outcome: "failed",
|
|
1767
|
+
detail: "a build ran and failed: audit dir " + newestImmediateDir + " report ok=false (immediate audit evidence, no receipt)"
|
|
1768
|
+
}, totalReworkCount);
|
|
1769
|
+
} else {
|
|
1770
|
+
await recordPublishLedger({
|
|
1771
|
+
commit: mergeCommitForPublish,
|
|
1772
|
+
attempt: rebuildAttemptKey,
|
|
1773
|
+
agent_id: null,
|
|
1774
|
+
applied_report: null,
|
|
1775
|
+
outcome: "unknown",
|
|
1776
|
+
detail: "new audit dir " + newestImmediateDir + " appeared during the trigger window but its build report is unreadable/missing; no receipt agent_id to poll — outcome unknown, fail-closed with no blind retry"
|
|
1777
|
+
}, totalReworkCount);
|
|
1778
|
+
return await parkTask("Publish outcome unknown for task " + taskId + ": a new audit dir (" + newestImmediateDir + ") appeared during the trigger window but its build report is unreadable, and no in-flight receipt was observed to poll. The edit may have completed. Correlate the accepted edit via the publish ledger at " + crewHome + "/.publish-ledger/" + PUBLISH_SLUG + ".jsonl — do NOT reissue the edit blindly: if the trigger was accepted, a retry duplicates it (2026-09-12). Verify independently whether the build completed (audit dir + report, or the parent's content read-back) before deciding the next step. Fail-closed.");
|
|
1779
|
+
}
|
|
1653
1780
|
} else {
|
|
1654
1781
|
// No attributable build and no durable evidence — but that
|
|
1655
1782
|
// proves nothing (a fast-completing build can finish between
|
|
@@ -1666,7 +1793,7 @@ while (i < STEPS.length) {
|
|
|
1666
1793
|
outcome: "unknown",
|
|
1667
1794
|
detail: "fire-and-forget trigger; post-trigger build-state poll saw no attributable build (or the check failed) and the audit-dir diff found no new dir; the edit may have been accepted as pending_init"
|
|
1668
1795
|
}, totalReworkCount);
|
|
1669
|
-
return await parkTask("Publish outcome unknown for task " + taskId + ": the rebuild trigger was issued fire-and-forget (no
|
|
1796
|
+
return await parkTask("Publish outcome unknown for task " + taskId + ": the rebuild trigger was issued fire-and-forget (no schema, so no validation failure mode; a candidate-parse throw stays possible and is inconclusive), and the follow-up observation could not attribute a build to the edit for slug " + PUBLISH_SLUG + " — no in-flight build with a new agent_id appeared in the poll window and no new audit dir landed. The edit may have been accepted as pending_init, so no retry was issued: a blind retry duplicated the edit on 2026-09-12. The attempt is recorded in the publish ledger at " + crewHome + "/.publish-ledger/" + PUBLISH_SLUG + ".jsonl (commit " + String(mergeCommitForPublish || "unknown").slice(0, 12) + "). Correlate the accepted edit via the ledger and the builder's eventual completion — do NOT reissue the edit blindly. Verify independently whether the build completed before deciding the next step. Fail-closed.");
|
|
1670
1797
|
}
|
|
1671
1798
|
}
|
|
1672
1799
|
|
|
@@ -1679,8 +1806,9 @@ while (i < STEPS.length) {
|
|
|
1679
1806
|
// already recorded the ledger's submitted line on both positive paths
|
|
1680
1807
|
// and parked on unknown — there is no applied report to observe and
|
|
1681
1808
|
// no rejection signal to record.
|
|
1682
|
-
|
|
1683
|
-
|
|
1809
|
+
// (publishFailure is declared with the immediate audit fallback
|
|
1810
|
+
// above so an explicit build failure there survives to here.)
|
|
1811
|
+
if (rebuildTrigger.edit_started && !skipReceiptPoll) {
|
|
1684
1812
|
// (2026-09-16) There is no builder report: the fire-and-forget
|
|
1685
1813
|
// trigger carries no JSON contract, so there is nothing to
|
|
1686
1814
|
// compare and no pre-hash diagnostic. The builder's old
|
|
@@ -1781,10 +1909,11 @@ while (i < STEPS.length) {
|
|
|
1781
1909
|
// the old report check was circular — a fabricated report
|
|
1782
1910
|
// passed by construction, and every phase went green on a hollow
|
|
1783
1911
|
// build. The stamp moves to the parent (docs/publish-verification.md);
|
|
1784
|
-
// the
|
|
1785
|
-
// agent-callable read-back tool
|
|
1786
|
-
// removed by the platform 2026-09-14
|
|
1787
|
-
//
|
|
1912
|
+
// the deterministic lib/readback-disk.js is the primary sensor
|
|
1913
|
+
// (the agent-callable read-back tool is unavailable —
|
|
1914
|
+
// artifact_inspect was removed by the platform 2026-09-14 — so
|
|
1915
|
+
// the LLM-inspector path is manual-fallback only), and the task
|
|
1916
|
+
// parks for parent verification.
|
|
1788
1917
|
// QA's provenance check enforces the stamp mechanically.
|
|
1789
1918
|
// An unverified publish fails loudly in QA instead of passing
|
|
1790
1919
|
// silently here.
|
|
@@ -1827,26 +1956,17 @@ while (i < STEPS.length) {
|
|
|
1827
1956
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1828
1957
|
);
|
|
1829
1958
|
var auditDirsAfterPollList = String((auditAfterPoll && auditAfterPoll.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1830
|
-
|
|
1959
|
+
// Gated on auditBeforeOk (critic finding 4): without a baseline
|
|
1960
|
+
// every historical dir would look new.
|
|
1961
|
+
newAuditDirsAfterPoll = auditBeforeOk ? auditDirsAfterPollList.filter(function (d) {
|
|
1831
1962
|
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1832
|
-
});
|
|
1963
|
+
}) : [];
|
|
1833
1964
|
log("Publish audit-dir re-list after build poll for task " + taskId + ": " + newAuditDirsAfterPoll.length + " new timestamped dir(s)");
|
|
1834
1965
|
} catch (auditAfterPollErr) {
|
|
1835
1966
|
log("Publish audit-dir re-list after build poll failed for task " + taskId + " (non-fatal, durable-evidence check degraded): " + (auditAfterPollErr && auditAfterPollErr.message ? auditAfterPollErr.message : auditAfterPollErr));
|
|
1836
1967
|
}
|
|
1837
|
-
//
|
|
1838
|
-
//
|
|
1839
|
-
// unreadable — not evidence either way). The child returns the
|
|
1840
|
-
// raw body verbatim; interpretation lives here, never in prose.
|
|
1841
|
-
var auditReportOk = function (raw) {
|
|
1842
|
-
if (typeof raw !== "string") return null;
|
|
1843
|
-
var trimmed = raw.trim();
|
|
1844
|
-
if (trimmed === "" || trimmed === "MISSING") return null;
|
|
1845
|
-
var parsed;
|
|
1846
|
-
try { parsed = JSON.parse(trimmed); } catch (e) { return null; }
|
|
1847
|
-
if (parsed && typeof parsed.ok === "boolean") return parsed.ok;
|
|
1848
|
-
return null;
|
|
1849
|
-
};
|
|
1968
|
+
// The shared auditReportOk (defined with the immediate fallback
|
|
1969
|
+
// above) interprets the raw body here too.
|
|
1850
1970
|
var auditOkAfterPoll = null;
|
|
1851
1971
|
var newestAuditDirAfterPoll = null;
|
|
1852
1972
|
if (newAuditDirsAfterPoll.length > 0 && !strangerObserved) {
|
|
@@ -1907,7 +2027,7 @@ while (i < STEPS.length) {
|
|
|
1907
2027
|
} else {
|
|
1908
2028
|
// Unreachable: the observation above either attributes the edit
|
|
1909
2029
|
// (edit_started) or parks. Defensive only — never a silent pass.
|
|
1910
|
-
publishFailure = "Artifact rebuild trigger failed: the edit was not attributed to any observed build. The publish
|
|
2030
|
+
publishFailure = "Artifact rebuild trigger failed: the edit was not attributed to any observed build. The publish is unattributed (not proven landed, not proven failed) — provenance was not stamped. Fail-closed.";
|
|
1911
2031
|
}
|
|
1912
2032
|
} // end: publishSkippedNoLock — no rebuild, no stamp, nothing to ship
|
|
1913
2033
|
// STEP 2 (mechanical, always — skip path included): post-deploy
|