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/bugfix.js
CHANGED
|
@@ -1334,9 +1334,10 @@ while (i < STEPS.length) {
|
|
|
1334
1334
|
// 2026-09-11), so the stamp moved to the parent — after the build
|
|
1335
1335
|
// lands, the workflow records the session completed and parks with
|
|
1336
1336
|
// "publish: verification-requested". The parent owns verification
|
|
1337
|
-
// (docs/publish-verification.md); the
|
|
1338
|
-
//
|
|
1339
|
-
// artifact_inspect was removed by the platform
|
|
1337
|
+
// (docs/publish-verification.md); the primary sensor is the
|
|
1338
|
+
// deterministic lib/readback-disk.js (the agent-callable read-back
|
|
1339
|
+
// tool is unavailable — artifact_inspect was removed by the platform
|
|
1340
|
+
// 2026-09-14 — so the LLM-inspector path is manual-fallback only).
|
|
1340
1341
|
// QA's provenance check enforces the stamp mechanically.
|
|
1341
1342
|
var artifactPublish = null;
|
|
1342
1343
|
var publishLockRefreshed = false;
|
|
@@ -1483,8 +1484,10 @@ while (i < STEPS.length) {
|
|
|
1483
1484
|
// The builder's applied report is gone (2026-09-16): it rode on the
|
|
1484
1485
|
// trigger's JSON closeout contract, which is removed below. The
|
|
1485
1486
|
// parent's independent read-back (docs/publish-verification.md) is
|
|
1486
|
-
// the verification — this field stays "missing-report" on
|
|
1487
|
-
//
|
|
1487
|
+
// the verification — this field stays "missing-report" on ledger
|
|
1488
|
+
// lines for issued triggers; pre-trigger parks (toolcheck
|
|
1489
|
+
// rejected/inconclusive) and unattributed-unknown parks write null
|
|
1490
|
+
// (no trigger was observed, so there is nothing to report).
|
|
1488
1491
|
var publishAppliedObservation = "missing-report";
|
|
1489
1492
|
// Durable-evidence snapshot (2026-09-14): the observation below only
|
|
1490
1493
|
// detects IN-FLIGHT builds. A build that finished before the
|
|
@@ -1495,10 +1498,13 @@ while (i < STEPS.length) {
|
|
|
1495
1498
|
// fallback can diff before/after: a directory appearing during the
|
|
1496
1499
|
// trigger window is positive evidence the edit went through and
|
|
1497
1500
|
// the build completed. Best-effort and non-gating: if the snapshot
|
|
1498
|
-
// fails,
|
|
1499
|
-
//
|
|
1501
|
+
// fails, auditBeforeOk stays false and BOTH fallback comparisons
|
|
1502
|
+
// are disabled (2026-09-16, critic finding 4) — without a baseline,
|
|
1503
|
+
// an empty before-list would make every historical audit dir look
|
|
1504
|
+
// "new". No wall-clock in-script (deterministic replay) — the
|
|
1500
1505
|
// comparison is a pure before/after set diff.
|
|
1501
1506
|
var auditDirsBeforeTrigger = [];
|
|
1507
|
+
var auditBeforeOk = false;
|
|
1502
1508
|
try {
|
|
1503
1509
|
var auditBefore = await agent(
|
|
1504
1510
|
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshot, never a gate).\n" +
|
|
@@ -1508,9 +1514,10 @@ while (i < STEPS.length) {
|
|
|
1508
1514
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1509
1515
|
);
|
|
1510
1516
|
auditDirsBeforeTrigger = String((auditBefore && auditBefore.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1517
|
+
auditBeforeOk = true;
|
|
1511
1518
|
log("Publish audit-dir snapshot before trigger for task " + taskId + ": " + auditDirsBeforeTrigger.length + " entries");
|
|
1512
1519
|
} catch (auditBeforeErr) {
|
|
1513
|
-
log("Publish audit-dir snapshot before trigger failed for task " + taskId + " (non-fatal,
|
|
1520
|
+
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));
|
|
1514
1521
|
}
|
|
1515
1522
|
// Fire-and-forget trigger + workflow-owned observation (2026-09-16,
|
|
1516
1523
|
// clean-room task e2a8d9f8): the trigger's JSON closeout contract
|
|
@@ -1534,10 +1541,16 @@ while (i < STEPS.length) {
|
|
|
1534
1541
|
// Pre-trigger toolcheck (tiny, schema'd): the artifact namespace is
|
|
1535
1542
|
// deferred for workflow children — the child self-loads it and emits
|
|
1536
1543
|
// one exact signal line, read mechanically (never English prose).
|
|
1537
|
-
//
|
|
1538
|
-
//
|
|
1539
|
-
//
|
|
1544
|
+
// Only a parsed ARTIFACT_TOOLS: missing signal is explicit negative
|
|
1545
|
+
// evidence: it gets one bounded retry with a fresh key, then parks
|
|
1546
|
+
// rejected — without the tools the edit provably did NOT go through,
|
|
1547
|
+
// so this is the one safe retry on the publish path. A throw (or an
|
|
1548
|
+
// unparseable signal) is INCONCLUSIVE transport noise, never
|
|
1549
|
+
// evidence of missing tools (2026-09-16, critic finding 3): it is
|
|
1550
|
+
// recorded, it retries once in case the flake clears, but it can
|
|
1551
|
+
// never take the rejected path.
|
|
1540
1552
|
var publishToolsOk = false;
|
|
1553
|
+
var publishToolsMissing = false;
|
|
1541
1554
|
for (var toolcheckAttempt = 1; toolcheckAttempt <= 2 && !publishToolsOk; toolcheckAttempt++) {
|
|
1542
1555
|
try {
|
|
1543
1556
|
var toolcheckResult = await agent(
|
|
@@ -1549,12 +1562,29 @@ while (i < STEPS.length) {
|
|
|
1549
1562
|
label: "Checking artifact tool availability" + (toolcheckAttempt === 1 ? "" : " (retry)"),
|
|
1550
1563
|
schema: { type: "object", properties: { signal: { type: "string" } }, required: ["signal"] } }
|
|
1551
1564
|
);
|
|
1552
|
-
|
|
1553
|
-
|
|
1565
|
+
var toolSignal = String((toolcheckResult && toolcheckResult.signal) || "");
|
|
1566
|
+
if (/ARTIFACT_TOOLS:\s*ok/.test(toolSignal)) {
|
|
1567
|
+
publishToolsOk = true;
|
|
1568
|
+
} else if (/ARTIFACT_TOOLS:\s*missing/.test(toolSignal)) {
|
|
1569
|
+
publishToolsMissing = true;
|
|
1570
|
+
}
|
|
1571
|
+
log("Publish artifact toolcheck for task " + taskId + " (attempt " + toolcheckAttempt + " of 2): " +
|
|
1572
|
+
(publishToolsOk ? "tools ok" : publishToolsMissing ? "tools missing (explicit parsed signal)" : "inconclusive (no ARTIFACT_TOOLS signal parsed)"));
|
|
1554
1573
|
} catch (toolcheckErr) {
|
|
1555
|
-
log("Publish artifact toolcheck for task " + taskId + " (attempt " + toolcheckAttempt + " of 2)
|
|
1574
|
+
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");
|
|
1556
1575
|
}
|
|
1557
1576
|
}
|
|
1577
|
+
if (!publishToolsOk && !publishToolsMissing) {
|
|
1578
|
+
await recordPublishLedger({
|
|
1579
|
+
commit: mergeCommitForPublish,
|
|
1580
|
+
attempt: rebuildAttemptKey,
|
|
1581
|
+
agent_id: null,
|
|
1582
|
+
applied_report: null,
|
|
1583
|
+
outcome: "unknown",
|
|
1584
|
+
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"
|
|
1585
|
+
}, totalReworkCount);
|
|
1586
|
+
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.");
|
|
1587
|
+
}
|
|
1558
1588
|
if (!publishToolsOk) {
|
|
1559
1589
|
await recordPublishLedger({
|
|
1560
1590
|
commit: mergeCommitForPublish,
|
|
@@ -1562,9 +1592,9 @@ while (i < STEPS.length) {
|
|
|
1562
1592
|
agent_id: null,
|
|
1563
1593
|
applied_report: null,
|
|
1564
1594
|
outcome: "rejected",
|
|
1565
|
-
detail: "artifact tool namespace missing
|
|
1595
|
+
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"
|
|
1566
1596
|
}, totalReworkCount);
|
|
1567
|
-
return await parkTask("Publish cannot proceed for task " + taskId + ": the artifact tool namespace was missing
|
|
1597
|
+
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.");
|
|
1568
1598
|
}
|
|
1569
1599
|
// Pre-trigger build-state baseline (tiny, schema'd): one read of
|
|
1570
1600
|
// artifact_status. The post-trigger observation diffs against this
|
|
@@ -1590,19 +1620,26 @@ while (i < STEPS.length) {
|
|
|
1590
1620
|
baselineFailed = true;
|
|
1591
1621
|
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");
|
|
1592
1622
|
}
|
|
1593
|
-
// The trigger itself:
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
// call
|
|
1597
|
-
//
|
|
1598
|
-
//
|
|
1599
|
-
//
|
|
1600
|
-
//
|
|
1623
|
+
// The trigger itself: the artifact_edit call is AWAITED (the workflow
|
|
1624
|
+
// waits for it to complete) but its return value is intentionally
|
|
1625
|
+
// UNCONSUMED — NO schema, so no schema validation can fail this
|
|
1626
|
+
// call: a schema-less call resolves to the child's raw response as
|
|
1627
|
+
// a plain string (probed live 2026-09-16 — never parsed, never
|
|
1628
|
+
// throws on content). One caveat, also probed: the runtime still
|
|
1629
|
+
// scans the response for a JSON candidate, and an unparseable
|
|
1630
|
+
// {...}-looking substring in the child's prose throws ("response
|
|
1631
|
+
// JSON candidate", probe P6). The prompt tells the child to end its
|
|
1632
|
+
// turn with no prose at all, which keeps the common case clean —
|
|
1633
|
+
// but the channel is stochastic, so any throw is possible and
|
|
1634
|
+
// inconclusive: the edit may still have gone through, so the
|
|
1635
|
+
// outcome stays unknown until the observation below confirms it —
|
|
1636
|
+
// never inferred from the throw, and never blind-retried (a blind
|
|
1637
|
+
// re-trigger duplicated the edit on 2026-09-12).
|
|
1601
1638
|
var rebuildTrigger = null;
|
|
1602
1639
|
try {
|
|
1603
1640
|
var triggerResultLength = String(await agent(rebuildPrompt,
|
|
1604
1641
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "").length;
|
|
1605
|
-
log("Publish rebuild trigger for task " + taskId + " returned (" + triggerResultLength + " chars
|
|
1642
|
+
log("Publish rebuild trigger for task " + taskId + " returned (" + triggerResultLength + " chars; awaited but return intentionally unconsumed)");
|
|
1606
1643
|
} catch (triggerErr) {
|
|
1607
1644
|
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");
|
|
1608
1645
|
}
|
|
@@ -1637,6 +1674,16 @@ while (i < STEPS.length) {
|
|
|
1637
1674
|
log("Publish post-trigger build-state check failed for task " + taskId + " (" + (buildCheckErr && buildCheckErr.message ? buildCheckErr.message : buildCheckErr) + ") — this signal is unknown, not negative");
|
|
1638
1675
|
}
|
|
1639
1676
|
var observedAgentId = (buildState && buildState.build && typeof buildState.build.agent_id === "string" && buildState.build.agent_id) || null;
|
|
1677
|
+
// Known limitation (failure-mode audit 2026-09-16): attribution
|
|
1678
|
+
// is timing-based — any agent_id new relative to the baseline is
|
|
1679
|
+
// treated as this edit's receipt. A stranger's build starting inside
|
|
1680
|
+
// the trigger window is indistinguishable by timing and would be
|
|
1681
|
+
// misattributed here. The consequence is bounded: the completion
|
|
1682
|
+
// poll below tracks the recorded id, and the parent's mechanical
|
|
1683
|
+
// content read-back (docs/publish-verification.md) certifies the
|
|
1684
|
+
// exact commit's content — a wrong build's content fails closed as
|
|
1685
|
+
// verification-failed, never stamped. Timing narrows the candidate;
|
|
1686
|
+
// content decides.
|
|
1640
1687
|
var receiptAgentId = (!buildStateFailed && !baselineFailed && observedAgentId && observedAgentId !== baselineAgentId) ? observedAgentId : null;
|
|
1641
1688
|
if (receiptAgentId) {
|
|
1642
1689
|
// The edit went through — a build with a new agent_id appeared
|
|
@@ -1656,6 +1703,31 @@ while (i < STEPS.length) {
|
|
|
1656
1703
|
}, totalReworkCount);
|
|
1657
1704
|
} else {
|
|
1658
1705
|
var newAuditDirs = [];
|
|
1706
|
+
// auditReportOk: pure tri-state read of a report.json body —
|
|
1707
|
+
// true (build ok), false (build failed), null (missing or
|
|
1708
|
+
// unreadable — not evidence either way). The child returns the
|
|
1709
|
+
// raw body verbatim; interpretation lives here, never in prose.
|
|
1710
|
+
// Defined here so both the immediate and post-poll audit
|
|
1711
|
+
// fallbacks share it.
|
|
1712
|
+
var auditReportOk = function (raw) {
|
|
1713
|
+
if (typeof raw !== "string") return null;
|
|
1714
|
+
var trimmed = raw.trim();
|
|
1715
|
+
if (trimmed === "" || trimmed === "MISSING") return null;
|
|
1716
|
+
var parsed;
|
|
1717
|
+
try { parsed = JSON.parse(trimmed); } catch (e) { return null; }
|
|
1718
|
+
if (parsed && typeof parsed.ok === "boolean") return parsed.ok;
|
|
1719
|
+
return null;
|
|
1720
|
+
};
|
|
1721
|
+
// (2026-09-16, critic finding 2) When durable audit evidence
|
|
1722
|
+
// confirms (or refutes) the build, there is no receipt agent_id
|
|
1723
|
+
// to chain the completion poll to — skipReceiptPoll bypasses the
|
|
1724
|
+
// poll below, which with a null receipt could only observe
|
|
1725
|
+
// strangers or nothing.
|
|
1726
|
+
var skipReceiptPoll = false;
|
|
1727
|
+
// publishFailure is declared here (moved up from below) so the
|
|
1728
|
+
// immediate audit fallback can record an explicit build failure
|
|
1729
|
+
// without the later declaration resetting it.
|
|
1730
|
+
var publishFailure = null;
|
|
1659
1731
|
try {
|
|
1660
1732
|
var auditAfter = await agent(
|
|
1661
1733
|
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort, never a gate).\n" +
|
|
@@ -1666,25 +1738,77 @@ while (i < STEPS.length) {
|
|
|
1666
1738
|
);
|
|
1667
1739
|
var auditDirsAfterTrigger = String((auditAfter && auditAfter.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1668
1740
|
// Only timestamped build dirs count — the "latest" symlink
|
|
1669
|
-
// and anything else are not builds.
|
|
1670
|
-
|
|
1741
|
+
// and anything else are not builds. Gated on auditBeforeOk:
|
|
1742
|
+
// without a baseline every historical dir would look new.
|
|
1743
|
+
newAuditDirs = auditBeforeOk ? auditDirsAfterTrigger.filter(function (d) {
|
|
1671
1744
|
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1672
|
-
});
|
|
1745
|
+
}) : [];
|
|
1673
1746
|
} catch (auditAfterErr) {
|
|
1674
1747
|
log("Publish audit-dir re-list after trigger failed for task " + taskId + " (non-fatal, durable-evidence check degraded): " + (auditAfterErr && auditAfterErr.message ? auditAfterErr.message : auditAfterErr));
|
|
1675
1748
|
}
|
|
1676
1749
|
if (newAuditDirs.length > 0) {
|
|
1677
1750
|
rebuildTrigger = { edit_started: true };
|
|
1678
1751
|
rebuildAgentId = null;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1752
|
+
newAuditDirs.sort();
|
|
1753
|
+
var newestImmediateDir = newAuditDirs[newAuditDirs.length - 1];
|
|
1754
|
+
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.");
|
|
1755
|
+
// (2026-09-16, critic finding 2) Durable audit evidence exists,
|
|
1756
|
+
// but there is no receipt agent_id to chain the completion poll
|
|
1757
|
+
// to — polling with a null receipt can only observe strangers
|
|
1758
|
+
// (any running build differs from "null") or nothing, burning
|
|
1759
|
+
// 10.5 minutes to park unknown. Read the build report now
|
|
1760
|
+
// instead of polling: ok=true confirms completion and routes
|
|
1761
|
+
// directly to parent verification (the poll is skipped);
|
|
1762
|
+
// ok=false is explicit failure; unreadable is unknown.
|
|
1763
|
+
var immediateReportOk = null;
|
|
1764
|
+
try {
|
|
1765
|
+
var immediateOkRead = await agent(
|
|
1766
|
+
"Read the artifact build report for slug \"" + PUBLISH_SLUG + "\".\n" +
|
|
1767
|
+
"Run: cat ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/" + newestImmediateDir + "/report.json 2>/dev/null || echo MISSING\n" +
|
|
1768
|
+
"Return JSON { \"raw\": \"<verbatim file contents, or the literal string MISSING when the file does not exist>\" } and nothing else.",
|
|
1769
|
+
{ key: attemptKey("publish-audit-ok-immediate-" + taskId, totalReworkCount), label: "Reading build report for audit-confirmed build",
|
|
1770
|
+
schema: { type: "object", properties: { raw: { type: "string" } }, required: ["raw"] } }
|
|
1771
|
+
);
|
|
1772
|
+
immediateReportOk = auditReportOk(immediateOkRead && immediateOkRead.raw);
|
|
1773
|
+
} catch (immediateOkErr) {
|
|
1774
|
+
log("Publish build-report read for audit-confirmed dir failed for task " + taskId + " (treated as unknown): " + (immediateOkErr && immediateOkErr.message ? immediateOkErr.message : immediateOkErr));
|
|
1775
|
+
immediateReportOk = null;
|
|
1776
|
+
}
|
|
1777
|
+
if (immediateReportOk === true) {
|
|
1778
|
+
publishBuildLanded = true;
|
|
1779
|
+
artifactPublish = { source_commit: mergeCommitForPublish, pending_parent_verification: true };
|
|
1780
|
+
skipReceiptPoll = true;
|
|
1781
|
+
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");
|
|
1782
|
+
await recordPublishLedger({
|
|
1783
|
+
commit: mergeCommitForPublish,
|
|
1784
|
+
attempt: rebuildAttemptKey,
|
|
1785
|
+
agent_id: null,
|
|
1786
|
+
applied_report: publishAppliedObservation,
|
|
1787
|
+
outcome: "submitted",
|
|
1788
|
+
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"
|
|
1789
|
+
}, totalReworkCount);
|
|
1790
|
+
} else if (immediateReportOk === false) {
|
|
1791
|
+
skipReceiptPoll = true;
|
|
1792
|
+
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.";
|
|
1793
|
+
await recordPublishLedger({
|
|
1794
|
+
commit: mergeCommitForPublish,
|
|
1795
|
+
attempt: rebuildAttemptKey,
|
|
1796
|
+
agent_id: null,
|
|
1797
|
+
applied_report: publishAppliedObservation,
|
|
1798
|
+
outcome: "failed",
|
|
1799
|
+
detail: "a build ran and failed: audit dir " + newestImmediateDir + " report ok=false (immediate audit evidence, no receipt)"
|
|
1800
|
+
}, totalReworkCount);
|
|
1801
|
+
} else {
|
|
1802
|
+
await recordPublishLedger({
|
|
1803
|
+
commit: mergeCommitForPublish,
|
|
1804
|
+
attempt: rebuildAttemptKey,
|
|
1805
|
+
agent_id: null,
|
|
1806
|
+
applied_report: null,
|
|
1807
|
+
outcome: "unknown",
|
|
1808
|
+
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"
|
|
1809
|
+
}, totalReworkCount);
|
|
1810
|
+
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.");
|
|
1811
|
+
}
|
|
1688
1812
|
} else {
|
|
1689
1813
|
// No attributable build and no durable evidence — but that
|
|
1690
1814
|
// proves nothing (a fast-completing build can finish between
|
|
@@ -1701,7 +1825,7 @@ while (i < STEPS.length) {
|
|
|
1701
1825
|
outcome: "unknown",
|
|
1702
1826
|
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"
|
|
1703
1827
|
}, totalReworkCount);
|
|
1704
|
-
return await parkTask("Publish outcome unknown for task " + taskId + ": the rebuild trigger was issued fire-and-forget (no
|
|
1828
|
+
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.");
|
|
1705
1829
|
}
|
|
1706
1830
|
}
|
|
1707
1831
|
|
|
@@ -1714,8 +1838,9 @@ while (i < STEPS.length) {
|
|
|
1714
1838
|
// already recorded the ledger's submitted line on both positive paths
|
|
1715
1839
|
// and parked on unknown — there is no applied report to observe and
|
|
1716
1840
|
// no rejection signal to record.
|
|
1717
|
-
|
|
1718
|
-
|
|
1841
|
+
// (publishFailure is declared with the immediate audit fallback
|
|
1842
|
+
// above so an explicit build failure there survives to here.)
|
|
1843
|
+
if (rebuildTrigger.edit_started && !skipReceiptPoll) {
|
|
1719
1844
|
// (2026-09-16) There is no builder report: the fire-and-forget
|
|
1720
1845
|
// trigger carries no JSON contract, so there is nothing to
|
|
1721
1846
|
// compare and no pre-hash diagnostic. The builder's old
|
|
@@ -1816,10 +1941,11 @@ while (i < STEPS.length) {
|
|
|
1816
1941
|
// the old report check was circular — a fabricated report
|
|
1817
1942
|
// passed by construction, and every phase went green on a hollow
|
|
1818
1943
|
// build. The stamp moves to the parent (docs/publish-verification.md);
|
|
1819
|
-
// the
|
|
1820
|
-
// agent-callable read-back tool
|
|
1821
|
-
// removed by the platform 2026-09-14
|
|
1822
|
-
//
|
|
1944
|
+
// the deterministic lib/readback-disk.js is the primary sensor
|
|
1945
|
+
// (the agent-callable read-back tool is unavailable —
|
|
1946
|
+
// artifact_inspect was removed by the platform 2026-09-14 — so
|
|
1947
|
+
// the LLM-inspector path is manual-fallback only), and the task
|
|
1948
|
+
// parks for parent verification.
|
|
1823
1949
|
// QA's provenance check enforces the stamp mechanically.
|
|
1824
1950
|
// An unverified publish fails loudly in QA instead of passing
|
|
1825
1951
|
// silently here.
|
|
@@ -1862,26 +1988,17 @@ while (i < STEPS.length) {
|
|
|
1862
1988
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1863
1989
|
);
|
|
1864
1990
|
var auditDirsAfterPollList = String((auditAfterPoll && auditAfterPoll.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1865
|
-
|
|
1991
|
+
// Gated on auditBeforeOk (critic finding 4): without a baseline
|
|
1992
|
+
// every historical dir would look new.
|
|
1993
|
+
newAuditDirsAfterPoll = auditBeforeOk ? auditDirsAfterPollList.filter(function (d) {
|
|
1866
1994
|
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1867
|
-
});
|
|
1995
|
+
}) : [];
|
|
1868
1996
|
log("Publish audit-dir re-list after build poll for task " + taskId + ": " + newAuditDirsAfterPoll.length + " new timestamped dir(s)");
|
|
1869
1997
|
} catch (auditAfterPollErr) {
|
|
1870
1998
|
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));
|
|
1871
1999
|
}
|
|
1872
|
-
//
|
|
1873
|
-
//
|
|
1874
|
-
// unreadable — not evidence either way). The child returns the
|
|
1875
|
-
// raw body verbatim; interpretation lives here, never in prose.
|
|
1876
|
-
var auditReportOk = function (raw) {
|
|
1877
|
-
if (typeof raw !== "string") return null;
|
|
1878
|
-
var trimmed = raw.trim();
|
|
1879
|
-
if (trimmed === "" || trimmed === "MISSING") return null;
|
|
1880
|
-
var parsed;
|
|
1881
|
-
try { parsed = JSON.parse(trimmed); } catch (e) { return null; }
|
|
1882
|
-
if (parsed && typeof parsed.ok === "boolean") return parsed.ok;
|
|
1883
|
-
return null;
|
|
1884
|
-
};
|
|
2000
|
+
// The shared auditReportOk (defined with the immediate fallback
|
|
2001
|
+
// above) interprets the raw body here too.
|
|
1885
2002
|
var auditOkAfterPoll = null;
|
|
1886
2003
|
var newestAuditDirAfterPoll = null;
|
|
1887
2004
|
if (newAuditDirsAfterPoll.length > 0 && !strangerObserved) {
|
|
@@ -1942,7 +2059,7 @@ while (i < STEPS.length) {
|
|
|
1942
2059
|
} else {
|
|
1943
2060
|
// Unreachable: the observation above either attributes the edit
|
|
1944
2061
|
// (edit_started) or parks. Defensive only — never a silent pass.
|
|
1945
|
-
publishFailure = "Artifact rebuild trigger failed: the edit was not attributed to any observed build. The publish
|
|
2062
|
+
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.";
|
|
1946
2063
|
}
|
|
1947
2064
|
} // end: publishSkippedNoLock — no rebuild, no stamp, nothing to ship
|
|
1948
2065
|
// STEP 2 (mechanical, always — skip path included): post-deploy
|