muse-crew 0.7.18 → 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 +45 -22
- 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 +368 -397
- package/workflows/chore.js +370 -397
- package/workflows/standard.js +378 -404
package/workflows/standard.js
CHANGED
|
@@ -450,91 +450,29 @@ function parseUnifiedDiff(diffText) {
|
|
|
450
450
|
return files;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
-
// Compare the artifact builder's reported applied-changes against the diff's
|
|
454
|
-
// expected changes. Every added/removed line must match exactly per path,
|
|
455
|
-
// and the file counts must match — the builder applies exactly the carried
|
|
456
|
-
// change, nothing more, nothing less. Pure function — no I/O, no clock.
|
|
457
|
-
// OBSERVATION INPUT ONLY (2026-09-12, task 23ca8f3f): the applied report
|
|
458
|
-
// has a demonstrated false-negative mode (applied:[] for a diff the builder
|
|
459
|
-
// had actually applied), and it is derived from the carried diff, so a match
|
|
460
|
-
// certifies nothing either. A mismatch is logged as observation; it never
|
|
461
|
-
// parks and never blocks the stamp. The verification is the independent
|
|
462
|
-
// read-back (docs/publish-verification.md).
|
|
463
|
-
function verifyAppliedChanges(expected, applied) {
|
|
464
|
-
if (!Array.isArray(applied)) {
|
|
465
|
-
return { ok: false, reason: "builder returned no applied-changes list" };
|
|
466
|
-
}
|
|
467
|
-
function sorted(a) { return (a || []).slice().sort(); }
|
|
468
|
-
function eq(a, b) {
|
|
469
|
-
a = sorted(a); b = sorted(b);
|
|
470
|
-
if (a.length !== b.length) return false;
|
|
471
|
-
for (var i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
for (var i = 0; i < expected.length; i++) {
|
|
475
|
-
var exp = expected[i];
|
|
476
|
-
var got = null;
|
|
477
|
-
for (var j = 0; j < applied.length; j++) {
|
|
478
|
-
if (applied[j] && applied[j].path === exp.path) { got = applied[j]; break; }
|
|
479
|
-
}
|
|
480
|
-
if (!got) {
|
|
481
|
-
return { ok: false, reason: "builder did not report changing '" + exp.path + "'" };
|
|
482
|
-
}
|
|
483
|
-
if (!eq(exp.added, got.added)) {
|
|
484
|
-
return { ok: false, reason: "added lines for '" + exp.path + "' do not match the carried diff" };
|
|
485
|
-
}
|
|
486
|
-
if (!eq(exp.removed, got.removed)) {
|
|
487
|
-
return { ok: false, reason: "removed lines for '" + exp.path + "' do not match the carried diff" };
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (applied.length !== expected.length) {
|
|
491
|
-
return { ok: false, reason: "builder reported changing " + applied.length + " file(s), diff carries " + expected.length };
|
|
492
|
-
}
|
|
493
|
-
return { ok: true };
|
|
494
|
-
}
|
|
495
453
|
|
|
496
|
-
// Publish read-back request (currently unavailable): the
|
|
497
|
-
// the parent protocol (docs/publish-verification.md) would
|
|
498
|
-
// independent read-back tool after the artifact build lands.
|
|
499
|
-
// was removed by the platform (2026-09-14);
|
|
500
|
-
// diagnosis, not a substitute — so no
|
|
501
|
-
//
|
|
502
|
-
//
|
|
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
|
|
503
464
|
// for an independent read of the artifact's actual source: for each file, the
|
|
504
465
|
// exact current text of the changed regions plus a per-line present/absent
|
|
505
466
|
// finding. Until a read-back path exists, the parent cannot independently
|
|
506
467
|
// confirm content and verification parks at "publish: verification-requested"
|
|
507
468
|
// (see docs/publish-verification.md). This preserves the circularity break
|
|
508
|
-
// that hollowed canary run 8 (2026-09-11):
|
|
509
|
-
// builder's applied-report against the diff the report was
|
|
510
|
-
// fabricated report
|
|
469
|
+
// that hollowed canary run 8 (2026-09-11): the old verifyAppliedChanges
|
|
470
|
+
// compared the builder's applied-report against the diff the report was
|
|
471
|
+
// derived from — a fabricated report passed by construction. The report
|
|
472
|
+
// itself is gone now (2026-09-16 fire-and-forget trigger). Independent
|
|
473
|
+
// read-back cannot be
|
|
511
474
|
// fabricated from the diff;
|
|
512
475
|
// it must match the artifact's real content.
|
|
513
|
-
// Pre-publish base observation (diagnostic, 2026-09-12): instruction fragment
|
|
514
|
-
// for the builder's edit request, asking it to report the sha256 of each
|
|
515
|
-
// touched file's CURRENT content BEFORE applying the diff. Pure function —
|
|
516
|
-
// no I/O, no clock.
|
|
517
|
-
//
|
|
518
|
-
// Why: the builder applies the carried diff to its own source tree, whose
|
|
519
|
-
// base state is unrecorded. The post-hoc read-back only checks the changed
|
|
520
|
-
// regions AFTER the edit; it cannot tell us what base the diff landed on.
|
|
521
|
-
// If the tree was dirty or drifted before the edit, the read-back still
|
|
522
|
-
// passes (the diff's lines are present) while the artifact silently carries
|
|
523
|
-
// uncommitted content — the production validateRepoPath incident
|
|
524
|
-
// (2026-09-12), where the live artifact contained code absent from every git
|
|
525
|
-
// ref. These pre-hashes, compared against the workflow-computed expected
|
|
526
|
-
// base hashes (merge parent), reveal what the publish actually read.
|
|
527
|
-
// Observation only — the workflow logs mismatches but never parks on them.
|
|
528
|
-
function buildPreHashInstruction(files) {
|
|
529
|
-
var paths = files.map(function(f) { return f.path; }).join(", ");
|
|
530
|
-
return (
|
|
531
|
-
"- BEFORE applying anything, compute the sha256 hash of each file below as it CURRENTLY exists in your source tree (before your changes). Use the exact bytes of the current file content.\n" +
|
|
532
|
-
"- Report these hashes in the \"pre_hashes\" field of your return JSON, as { \"<path>\": \"<sha256 hex>\" }.\n" +
|
|
533
|
-
"- If a file does not exist in your tree, report its hash as the string \"MISSING\".\n" +
|
|
534
|
-
"- Do this BEFORE applying the diff — the hashes must reflect the pre-edit state.\n" +
|
|
535
|
-
" Files: " + paths + "\n"
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
476
|
|
|
539
477
|
// Durable publish-attempt ledger (2026-09-12): every artifact publish
|
|
540
478
|
// attempt is recorded append-only at $CREW_HOME/.publish-ledger/<slug>.jsonl
|
|
@@ -1364,9 +1302,10 @@ while (i < STEPS.length) {
|
|
|
1364
1302
|
// 2026-09-11), so the stamp moved to the parent — after the build
|
|
1365
1303
|
// lands, the workflow records the session completed and parks with
|
|
1366
1304
|
// "publish: verification-requested". The parent owns verification
|
|
1367
|
-
// (docs/publish-verification.md); the
|
|
1368
|
-
//
|
|
1369
|
-
// 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).
|
|
1370
1309
|
// QA's provenance check enforces the stamp mechanically.
|
|
1371
1310
|
var artifactPublish = null;
|
|
1372
1311
|
var publishLockRefreshed = false;
|
|
@@ -1476,38 +1415,8 @@ while (i < STEPS.length) {
|
|
|
1476
1415
|
if (expectedChanges.length === 0) {
|
|
1477
1416
|
return await parkTask("Publish diff parsed to zero files for commit " + (mergeCommitForPublish || "unknown") + " — cannot verify application. Human attention needed.");
|
|
1478
1417
|
}
|
|
1479
|
-
// Pre-publish base observation (diagnostic, 2026-09-12): the builder
|
|
1480
|
-
// applies the diff to its own source tree, whose base state is
|
|
1481
|
-
// unrecorded. Compute the trustworthy expected base — the sha256 of
|
|
1482
|
-
// each touched file at the merge parent commit — so the builder's
|
|
1483
|
-
// self-reported pre-edit hashes (see buildPreHashInstruction) can be
|
|
1484
|
-
// compared against it. Observation only: a mismatch is logged loudly
|
|
1485
|
-
// but never parks. The observation tells us what the publish actually
|
|
1486
|
-
// reads, so the subsequent fix can require the right base.
|
|
1487
|
-
var expectedBaseHashes = {};
|
|
1488
|
-
if (publishBase === EMPTY_TREE_SHA) {
|
|
1489
|
-
// First publish: every file in the diff is new to the artifact.
|
|
1490
|
-
expectedChanges.forEach(function (f) { expectedBaseHashes[f.path] = "NEW-FILE"; });
|
|
1491
|
-
log("Publish expected base hashes for task " + taskId + ": empty tree (first publish) — all " + expectedChanges.length + " file(s) new");
|
|
1492
|
-
} else try {
|
|
1493
|
-
// Shell-quote helper (no regex-with-quote: the test parser does not
|
|
1494
|
-
// understand regex literals containing quotes).
|
|
1495
|
-
var sq = function(s) { return "'" + String(s).split("'").join("'\\''") + "'"; };
|
|
1496
|
-
var baseHashResult = await agent(
|
|
1497
|
-
"Run: cd " + REPO_PATH + " && parent=" + sq(publishBase) + " && for f in " + expectedChanges.map(function(f) { return sq(f.path); }).join(" ") + "; do printf '%s:' \"$f\"; git show \"$parent:$f\" 2>/dev/null | sha256sum | cut -d' ' -f1; done\n" +
|
|
1498
|
-
"Return JSON { \"hashes\": \"<newline-separated <path>:<sha256> lines, empty hash means the file is new in this diff>\" } and nothing else.",
|
|
1499
|
-
{ key: attemptKey("publish-base-hashes-" + taskId, totalReworkCount), label: "Computing expected base content hashes",
|
|
1500
|
-
schema: { type: "object", properties: { hashes: { type: "string" } }, required: ["hashes"] } }
|
|
1501
|
-
);
|
|
1502
|
-
(baseHashResult.hashes || "").split("\n").forEach(function(line) {
|
|
1503
|
-
var m = /^([^:]+):([0-9a-f]*)$/.exec(line.trim());
|
|
1504
|
-
if (m) expectedBaseHashes[m[1]] = m[2] || "NEW-FILE";
|
|
1505
|
-
});
|
|
1506
|
-
log("Publish expected base hashes for task " + taskId + " (stamped base " + publishBase.slice(0, 12) + "): " + JSON.stringify(expectedBaseHashes));
|
|
1507
|
-
} catch (e) {
|
|
1508
|
-
log("Publish expected base hash computation failed for task " + taskId + " (non-fatal, observation degraded): " + (e && e.message ? e.message : e));
|
|
1509
|
-
}
|
|
1510
1418
|
var rebuildPrompt =
|
|
1419
|
+
ARTIFACT_LOAD_PREAMBLE +
|
|
1511
1420
|
"Call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
1512
1421
|
"'Apply the following change to your source tree, then rebuild and deploy.\n" +
|
|
1513
1422
|
"\n" +
|
|
@@ -1520,79 +1429,50 @@ while (i < STEPS.length) {
|
|
|
1520
1429
|
"- For a deleted file (+++ /dev/null), delete it.\n" +
|
|
1521
1430
|
"- If any hunk does not apply cleanly, STOP and report the failure — do not improvise or skip hunks.\n" +
|
|
1522
1431
|
"- Do not make any other source changes.\n" +
|
|
1523
|
-
"- After applying, rebuild and deploy
|
|
1524
|
-
"-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
"
|
|
1528
|
-
"
|
|
1529
|
-
"- The artifact namespace is already loaded (see below). BEFORE calling artifact_edit, call artifact_status with slug \"" + PUBLISH_SLUG + "\" and note the running build's agent_id (or null when no build is running). This is the pre-edit baseline.\n" +
|
|
1530
|
-
"- Call artifact_edit as instructed above.\n" +
|
|
1531
|
-
"- IMMEDIATELY after artifact_edit returns, call artifact_status again. If a build is running whose agent_id DIFFERS from the pre-edit baseline (or the baseline was null), that build is this edit's — its agent_id is the receipt.\n" +
|
|
1532
|
-
"- If the post-edit status shows the SAME agent_id as the baseline, or no build at all, wait about 15 seconds and check artifact_status again, up to 4 more times. If a build with a NEW agent_id appears, that is the receipt.\n" +
|
|
1533
|
-
"- If no new build appears, the receipt is null: the build may be pending_init-invisible, may have finished before the capture, or may be queued behind the earlier build. Return null — do NOT guess, and do NOT substitute the baseline build's agent_id.\n" +
|
|
1534
|
-
"Make no other calls. Return JSON { \"edit_started\": <true if the edit was accepted, false otherwise>, \"build_agent_id\": <the receipt agent_id string, or null when no build could be attributed to this edit>, \"error\": \"<details or empty string>\", \"applied\": [{\"path\": \"<file path>\", \"added\": [\"<added lines>\"], \"removed\": [\"<removed lines>\"]}], \"pre_hashes\": {\"<file path>\": \"<sha256 of that file's content BEFORE you applied the diff, or \"MISSING\">\"} } and nothing else.";
|
|
1535
|
-
var rebuildSchema =
|
|
1536
|
-
{ type: "object",
|
|
1537
|
-
properties: {
|
|
1538
|
-
edit_started: { type: "boolean" },
|
|
1539
|
-
build_agent_id: {
|
|
1540
|
-
type: ["string", "null"],
|
|
1541
|
-
description: "Receipt-chained publish (2026-09-13): the platform build's in-flight correlation ID (build.agent_id from artifact_status) captured immediately after the edit was accepted — the receipt the follow-up poll chains to. Null when no build could be attributed to this edit. Required: an accepted edit with a null receipt parks fail-closed as unknown."
|
|
1542
|
-
},
|
|
1543
|
-
error: { type: "string" },
|
|
1544
|
-
applied: {
|
|
1545
|
-
type: "array",
|
|
1546
|
-
items: {
|
|
1547
|
-
type: "object",
|
|
1548
|
-
properties: {
|
|
1549
|
-
path: { type: "string" },
|
|
1550
|
-
added: { type: "array", items: { type: "string" } },
|
|
1551
|
-
removed: { type: "array", items: { type: "string" } }
|
|
1552
|
-
},
|
|
1553
|
-
required: ["path", "added", "removed"]
|
|
1554
|
-
}
|
|
1555
|
-
},
|
|
1556
|
-
pre_hashes: {
|
|
1557
|
-
type: "object",
|
|
1558
|
-
description: "Diagnostic (2026-09-12): sha256 of each touched file's content BEFORE the builder applied the diff, as reported by the builder. Compared against the workflow-computed expected base hashes (merge parent) — observation only, never gating."
|
|
1559
|
-
}
|
|
1560
|
-
},
|
|
1561
|
-
required: ["edit_started", "build_agent_id", "applied"] };
|
|
1562
|
-
var rebuildTrigger = null;
|
|
1563
|
-
var rebuildReportMissing = false; // true if the edit went through but the agent returned no applied report (structured-output failure) — the smoke-check is skipped; the parent's independent read-back is the verification
|
|
1564
|
-
var rebuildEvidenceNote = null; // human-readable evidence line for the ledger when the edit is confirmed via fallback evidence (in-flight poll or durable audit dir) rather than the trigger's own report
|
|
1432
|
+
"- After applying, rebuild and deploy.'\n" +
|
|
1433
|
+
"Edit-request contract (read carefully):\n" +
|
|
1434
|
+
"- Call artifact_edit exactly once with the slug and verbatim_request above. Never retry the edit yourself: if the edit is not accepted, do NOT call artifact_edit again — end your turn.\n" +
|
|
1435
|
+
"- If artifact_edit is not available after the load, do NOT improvise — end your turn.\n" +
|
|
1436
|
+
"- You do NOT call setprovenance, artifact_inspect, or post-deploy yourself.\n" +
|
|
1437
|
+
"No report is needed: do not return JSON, do not summarize what you did, do not echo the diff. End your turn after the artifact_edit call.\n";
|
|
1565
1438
|
// The trigger key of the attempt that last ran, for the publish ledger.
|
|
1566
1439
|
// Minted once here (not re-minted per use site) so the ledger always
|
|
1567
1440
|
// records the exact key that was issued — and so a re-minted duplicate
|
|
1568
1441
|
// can never drift from it.
|
|
1569
1442
|
var rebuildAttemptKey = attemptKey("publish-artifact-rebuild-" + taskId, totalReworkCount);
|
|
1570
|
-
// The artifact build's agent_id,
|
|
1571
|
-
//
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
//
|
|
1443
|
+
// The artifact build's agent_id, attributed to this edit by the
|
|
1444
|
+
// workflow-owned observation below. The agent_id is the artifact
|
|
1445
|
+
// system's in-flight correlation ID (research 2026-09-12):
|
|
1446
|
+
// artifact.edit returns pending_init with NO agent_id, but
|
|
1447
|
+
// artifact_status exposes build.agent_id immediately after
|
|
1448
|
+
// acceptance, stable across polls. Recorded in the ledger so an
|
|
1449
|
+
// attempt correlates to the exact builder run; null when no build
|
|
1450
|
+
// was ever observed.
|
|
1578
1451
|
var rebuildAgentId = null;
|
|
1579
|
-
// The builder's applied
|
|
1580
|
-
//
|
|
1581
|
-
//
|
|
1582
|
-
|
|
1583
|
-
//
|
|
1584
|
-
//
|
|
1585
|
-
//
|
|
1586
|
-
|
|
1452
|
+
// The builder's applied report is gone (2026-09-16): it rode on the
|
|
1453
|
+
// trigger's JSON closeout contract, which is removed below. The
|
|
1454
|
+
// parent's independent read-back (docs/publish-verification.md) is
|
|
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).
|
|
1459
|
+
var publishAppliedObservation = "missing-report";
|
|
1460
|
+
// Durable-evidence snapshot (2026-09-14): the observation below only
|
|
1461
|
+
// detects IN-FLIGHT builds. A build that finished before the
|
|
1462
|
+
// observation leaves no in-flight trace — but the platform's audit
|
|
1463
|
+
// harness leaves a durable one:
|
|
1587
1464
|
// ~/workspace/ts-spaces/<slug>/audits/<timestamp>-<id>/ per
|
|
1588
1465
|
// completed build. Snapshot the listing BEFORE the trigger so the
|
|
1589
1466
|
// fallback can diff before/after: a directory appearing during the
|
|
1590
1467
|
// trigger window is positive evidence the edit went through and
|
|
1591
1468
|
// the build completed. Best-effort and non-gating: if the snapshot
|
|
1592
|
-
// fails,
|
|
1593
|
-
//
|
|
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
|
|
1594
1473
|
// comparison is a pure before/after set diff.
|
|
1595
1474
|
var auditDirsBeforeTrigger = [];
|
|
1475
|
+
var auditBeforeOk = false;
|
|
1596
1476
|
try {
|
|
1597
1477
|
var auditBefore = await agent(
|
|
1598
1478
|
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshot, never a gate).\n" +
|
|
@@ -1602,242 +1482,342 @@ while (i < STEPS.length) {
|
|
|
1602
1482
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1603
1483
|
);
|
|
1604
1484
|
auditDirsBeforeTrigger = String((auditBefore && auditBefore.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1485
|
+
auditBeforeOk = true;
|
|
1605
1486
|
log("Publish audit-dir snapshot before trigger for task " + taskId + ": " + auditDirsBeforeTrigger.length + " entries");
|
|
1606
1487
|
} catch (auditBeforeErr) {
|
|
1607
|
-
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));
|
|
1608
1489
|
}
|
|
1490
|
+
// Fire-and-forget trigger + workflow-owned observation (2026-09-16,
|
|
1491
|
+
// clean-room task e2a8d9f8): the trigger's JSON closeout contract
|
|
1492
|
+
// traveled over the stochastic text channel, and the runtime's
|
|
1493
|
+
// JSON-candidate heuristic misfired on it ("workflow agent output
|
|
1494
|
+
// was not JSON: no JSON object or array found in final response"),
|
|
1495
|
+
// parking a task whose edit may have gone through. The contract's
|
|
1496
|
+
// content was already observation-only (the applied report never
|
|
1497
|
+
// gated; the pre_hashes were diagnostic-only), so the contract is
|
|
1498
|
+
// removed: the trigger carries NO schema and its return value is
|
|
1499
|
+
// never consumed, which takes the extraction heuristic out of this
|
|
1500
|
+
// call entirely. The workflow attributes the edit itself through
|
|
1501
|
+
// the tiny schema'd reads below — no prose is parsed for the
|
|
1502
|
+
// trigger outcome.
|
|
1503
|
+
// (Probe, 2026-09-16: the workflow scope exposes only agent() —
|
|
1504
|
+
// tool_search, artifact_edit and artifact_status are undefined
|
|
1505
|
+
// there — so the workflow cannot call the artifact tools directly;
|
|
1506
|
+
// observation still goes through minimal child calls with tiny
|
|
1507
|
+
// schemas, never a broad JSON contract.)
|
|
1508
|
+
//
|
|
1509
|
+
// Pre-trigger toolcheck (tiny, schema'd): the artifact namespace is
|
|
1510
|
+
// deferred for workflow children — the child self-loads it and emits
|
|
1511
|
+
// one exact signal line, read mechanically (never English prose).
|
|
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.
|
|
1520
|
+
var publishToolsOk = false;
|
|
1521
|
+
var publishToolsMissing = false;
|
|
1522
|
+
for (var toolcheckAttempt = 1; toolcheckAttempt <= 2 && !publishToolsOk; toolcheckAttempt++) {
|
|
1523
|
+
try {
|
|
1524
|
+
var toolcheckResult = await agent(
|
|
1525
|
+
"Check whether the artifact tool namespace is available.\n" +
|
|
1526
|
+
"Call tool_search.load_tool_namespace with paths [\"artifact\"].\n" +
|
|
1527
|
+
"Then emit exactly one line and nothing else: ARTIFACT_TOOLS: <ok if the load succeeded and artifact_edit and artifact_status are now functions, missing otherwise>.\n" +
|
|
1528
|
+
"Return JSON { \"signal\": \"<the exact ARTIFACT_TOOLS line>\" } and nothing else.",
|
|
1529
|
+
{ key: attemptKey("publish-artifact-toolcheck-" + taskId + (toolcheckAttempt === 1 ? "" : "-retry2"), totalReworkCount),
|
|
1530
|
+
label: "Checking artifact tool availability" + (toolcheckAttempt === 1 ? "" : " (retry)"),
|
|
1531
|
+
schema: { type: "object", properties: { signal: { type: "string" } }, required: ["signal"] } }
|
|
1532
|
+
);
|
|
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)"));
|
|
1541
|
+
} catch (toolcheckErr) {
|
|
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");
|
|
1543
|
+
}
|
|
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
|
+
}
|
|
1556
|
+
if (!publishToolsOk) {
|
|
1557
|
+
await recordPublishLedger({
|
|
1558
|
+
commit: mergeCommitForPublish,
|
|
1559
|
+
attempt: rebuildAttemptKey,
|
|
1560
|
+
agent_id: null,
|
|
1561
|
+
applied_report: null,
|
|
1562
|
+
outcome: "rejected",
|
|
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"
|
|
1564
|
+
}, totalReworkCount);
|
|
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.");
|
|
1566
|
+
}
|
|
1567
|
+
// Pre-trigger build-state baseline (tiny, schema'd): one read of
|
|
1568
|
+
// artifact_status. The post-trigger observation diffs against this
|
|
1569
|
+
// baseline — a build whose agent_id was absent from (or differs
|
|
1570
|
+
// from) the baseline is attributed to our edit; a build already in
|
|
1571
|
+
// flight at baseline predates the trigger and is never attributed
|
|
1572
|
+
// to it. If the baseline read itself fails, receipt attribution is
|
|
1573
|
+
// skipped and the durable audit-dir evidence below is the only
|
|
1574
|
+
// positive signal.
|
|
1575
|
+
var baselineAgentId = null;
|
|
1576
|
+
var baselineFailed = false;
|
|
1577
|
+
try {
|
|
1578
|
+
var publishBaseline = await agent(
|
|
1579
|
+
ARTIFACT_LOAD_PREAMBLE +
|
|
1580
|
+
"Call artifact_status with slug \"" + PUBLISH_SLUG + "\" once.\n" +
|
|
1581
|
+
"Return JSON { \"build\": <the raw \"build\" value exactly as returned, or null when there is none> } and nothing else.",
|
|
1582
|
+
{ key: attemptKey("publish-artifact-baseline-" + taskId, totalReworkCount), label: "Reading pre-trigger build state",
|
|
1583
|
+
schema: { type: "object", properties: { build: { type: ["object", "null"] } }, required: ["build"] } }
|
|
1584
|
+
);
|
|
1585
|
+
baselineAgentId = (publishBaseline && publishBaseline.build && typeof publishBaseline.build.agent_id === "string" && publishBaseline.build.agent_id) || null;
|
|
1586
|
+
log("Publish pre-trigger baseline for task " + taskId + ": " + (baselineAgentId ? "build " + baselineAgentId + " already in flight (predates the trigger — never attributed to this edit)" : "no build in flight"));
|
|
1587
|
+
} catch (baselineErr) {
|
|
1588
|
+
baselineFailed = true;
|
|
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");
|
|
1590
|
+
}
|
|
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).
|
|
1606
|
+
var rebuildTrigger = null;
|
|
1607
|
+
try {
|
|
1608
|
+
var triggerResultLength = String(await agent(rebuildPrompt,
|
|
1609
|
+
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "").length;
|
|
1610
|
+
log("Publish rebuild trigger for task " + taskId + " returned (" + triggerResultLength + " chars; awaited but return intentionally unconsumed)");
|
|
1611
|
+
} catch (triggerErr) {
|
|
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");
|
|
1613
|
+
}
|
|
1614
|
+
// Post-trigger observation (primary, not fallback): the workflow
|
|
1615
|
+
// attributes the edit itself. First the in-flight build state — a
|
|
1616
|
+
// build whose agent_id is new relative to the pre-trigger baseline
|
|
1617
|
+
// is this edit's receipt. Then the durable audit-dir diff — a
|
|
1618
|
+
// timestamped directory appearing during the trigger window proves
|
|
1619
|
+
// the edit went through and the build completed even when no
|
|
1620
|
+
// in-flight build was ever observed (the 2026-09-14 attempt-7 gap).
|
|
1621
|
+
// Absence of both signals proves nothing: the outcome is UNKNOWN,
|
|
1622
|
+
// never "did not go through". No blind retry — record the attempt
|
|
1623
|
+
// and park fail-closed; correlate via the ledger, never by
|
|
1624
|
+
// re-issuing.
|
|
1625
|
+
log("Publish rebuild trigger issued for task " + taskId + " — observing build state to attribute the edit");
|
|
1626
|
+
var buildState = null;
|
|
1627
|
+
var buildStateFailed = false;
|
|
1609
1628
|
try {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1629
|
+
buildState = await agent(
|
|
1630
|
+
ARTIFACT_LOAD_PREAMBLE +
|
|
1631
|
+
"Call artifact_status with slug \"" + PUBLISH_SLUG + "\".\n" +
|
|
1632
|
+
"Poll up to 3 times, about 20 seconds apart, until the response shows a build (the \"build\" value is an object, not null). " +
|
|
1633
|
+
"Return the raw \"build\" value verbatim as JSON — the build object exactly as returned, with its agent_id, operation, status, and any other fields untouched. " +
|
|
1634
|
+
"Do not summarize, interpret, or derive booleans from it. " +
|
|
1635
|
+
"If no build appears after 3 polls, return null. " +
|
|
1636
|
+
"Return JSON { \"build\": <the raw build object or null> } and nothing else.",
|
|
1637
|
+
{ key: attemptKey("publish-artifact-buildcheck-" + taskId, totalReworkCount), label: "Reading artifact build state after trigger",
|
|
1638
|
+
schema: { type: "object", properties: { build: { type: ["object", "null"] } }, required: ["build"] } }
|
|
1639
|
+
);
|
|
1640
|
+
} catch (buildCheckErr) {
|
|
1641
|
+
buildStateFailed = true;
|
|
1642
|
+
log("Publish post-trigger build-state check failed for task " + taskId + " (" + (buildCheckErr && buildCheckErr.message ? buildCheckErr.message : buildCheckErr) + ") — this signal is unknown, not negative");
|
|
1643
|
+
}
|
|
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.
|
|
1655
|
+
var receiptAgentId = (!buildStateFailed && !baselineFailed && observedAgentId && observedAgentId !== baselineAgentId) ? observedAgentId : null;
|
|
1656
|
+
if (receiptAgentId) {
|
|
1657
|
+
// The edit went through — a build with a new agent_id appeared
|
|
1658
|
+
// after the trigger. The parent's independent read-back
|
|
1659
|
+
// (docs/publish-verification.md) is the verification, not any
|
|
1660
|
+
// builder report.
|
|
1661
|
+
rebuildTrigger = { edit_started: true };
|
|
1662
|
+
rebuildAgentId = receiptAgentId;
|
|
1663
|
+
log("Publish rebuild trigger for task " + taskId + ": artifact_status shows build " + receiptAgentId + " for slug " + PUBLISH_SLUG + " (new relative to the pre-trigger baseline) — the edit went through.");
|
|
1664
|
+
await recordPublishLedger({
|
|
1665
|
+
commit: mergeCommitForPublish,
|
|
1666
|
+
attempt: rebuildAttemptKey,
|
|
1667
|
+
agent_id: rebuildAgentId,
|
|
1668
|
+
applied_report: publishAppliedObservation,
|
|
1669
|
+
outcome: "submitted",
|
|
1670
|
+
detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
|
|
1671
|
+
}, totalReworkCount);
|
|
1672
|
+
} else {
|
|
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;
|
|
1636
1699
|
try {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
"
|
|
1640
|
-
"
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
"If no build appears after 3 polls, return null. " +
|
|
1644
|
-
"Return JSON { \"build\": <the raw build object or null> } and nothing else.",
|
|
1645
|
-
{ key: attemptKey("publish-artifact-buildcheck-" + taskId, totalReworkCount), label: "Reading artifact build state after trigger failure",
|
|
1646
|
-
schema: { type: "object", properties: { build: { type: ["object", "null"] } }, required: ["build"] } }
|
|
1700
|
+
var auditAfter = await agent(
|
|
1701
|
+
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort, never a gate).\n" +
|
|
1702
|
+
"Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null\n" +
|
|
1703
|
+
"Return JSON { \"dirs\": \"<newline-separated names, empty string when the audits directory does not exist or is empty>\" } and nothing else.",
|
|
1704
|
+
{ key: attemptKey("publish-audit-after-" + taskId, totalReworkCount), label: "Re-listing audit dirs after trigger",
|
|
1705
|
+
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1647
1706
|
);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1707
|
+
var auditDirsAfterTrigger = String((auditAfter && auditAfter.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1708
|
+
// Only timestamped build dirs count — the "latest" symlink
|
|
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) {
|
|
1712
|
+
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1713
|
+
}) : [];
|
|
1714
|
+
} catch (auditAfterErr) {
|
|
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));
|
|
1651
1716
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
//
|
|
1659
|
-
//
|
|
1660
|
-
//
|
|
1661
|
-
//
|
|
1662
|
-
//
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
rebuildEvidenceNote = "edit confirmed via build-state poll after structured-output failure (build " + acceptedAgentId + "); builder applied-report missing";
|
|
1668
|
-
} else {
|
|
1669
|
-
// Durable completion check (2026-09-14): the in-flight poll
|
|
1670
|
-
// above only sees RUNNING builds. Attempt 7 (2026-09-14) proved
|
|
1671
|
-
// the gap: the trigger child applied the edit, the build ran
|
|
1672
|
-
// and completed — the platform's audit harness captured it
|
|
1673
|
-
// mid-window — then the child failed to return JSON. The
|
|
1674
|
-
// fallback poll saw no in-flight build, so a successful publish
|
|
1675
|
-
// parked as "unknown". Diff the audit-dir listing against the
|
|
1676
|
-
// pre-trigger snapshot: a timestamped directory that appeared
|
|
1677
|
-
// during the trigger window is positive evidence the edit went
|
|
1678
|
-
// through and the build completed. This never re-issues the
|
|
1679
|
-
// edit and never stamps provenance — it only routes to the
|
|
1680
|
-
// parent's independent content read-back, which remains the
|
|
1681
|
-
// real verification.
|
|
1682
|
-
var newAuditDirs = [];
|
|
1717
|
+
if (newAuditDirs.length > 0) {
|
|
1718
|
+
rebuildTrigger = { edit_started: true };
|
|
1719
|
+
rebuildAgentId = null;
|
|
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;
|
|
1683
1732
|
try {
|
|
1684
|
-
var
|
|
1685
|
-
"
|
|
1686
|
-
"Run:
|
|
1687
|
-
"Return JSON { \"
|
|
1688
|
-
{ key: attemptKey("publish-audit-
|
|
1689
|
-
schema: { type: "object", properties: {
|
|
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"] } }
|
|
1690
1739
|
);
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1696
|
-
});
|
|
1697
|
-
} catch (auditAfterErr) {
|
|
1698
|
-
log("Publish audit-dir re-list after trigger failure failed for task " + taskId + " (non-fatal, durable-evidence check degraded): " + (auditAfterErr && auditAfterErr.message ? auditAfterErr.message : auditAfterErr));
|
|
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;
|
|
1699
1744
|
}
|
|
1700
|
-
if (
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
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);
|
|
1706
1769
|
} else {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
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
|
+
}
|
|
1780
|
+
} else {
|
|
1781
|
+
// No attributable build and no durable evidence — but that
|
|
1782
|
+
// proves nothing (a fast-completing build can finish between
|
|
1783
|
+
// polls, or the checks themselves failed). The outcome is
|
|
1784
|
+
// UNKNOWN. No retry: re-issuing the edit here duplicated it on
|
|
1785
|
+
// 2026-09-12. Record the attempt durably and park fail-closed;
|
|
1711
1786
|
// correlate via the ledger, never by guessing from a blind poll.
|
|
1712
|
-
log("Publish rebuild trigger: no build observed
|
|
1787
|
+
log("Publish rebuild trigger for task " + taskId + ": no attributable build observed and no new audit dir — outcome UNKNOWN. Recording the attempt and parking fail-closed; no blind retry.");
|
|
1713
1788
|
await recordPublishLedger({
|
|
1714
1789
|
commit: mergeCommitForPublish,
|
|
1715
1790
|
attempt: rebuildAttemptKey,
|
|
1716
1791
|
agent_id: null,
|
|
1717
1792
|
applied_report: null,
|
|
1718
1793
|
outcome: "unknown",
|
|
1719
|
-
detail: "
|
|
1720
|
-
}, totalReworkCount);
|
|
1721
|
-
return await parkTask("Publish outcome unknown: the rebuild trigger's child did not return JSON, and the follow-up build-state poll could not observe a build for slug " + PUBLISH_SLUG + ". 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 before re-driving Publish. Fail-closed.");
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
if (!rebuildReportMissing && !rebuildTrigger.edit_started && rebuildTrigger.error === "artifact_tools missing after load") {
|
|
1726
|
-
log("Publish rebuild trigger: artifact_tools missing after load — one bounded retry with a fresh key");
|
|
1727
|
-
rebuildTrigger = await agent(rebuildPrompt,
|
|
1728
|
-
{ key: attemptKey("publish-artifact-rebuild-" + taskId + "-retry2", totalReworkCount), label: "Triggering artifact rebuild (retry)", schema: rebuildSchema });
|
|
1729
|
-
rebuildAttemptKey = attemptKey("publish-artifact-rebuild-" + taskId + "-retry2", totalReworkCount);
|
|
1730
|
-
}
|
|
1731
|
-
// Receipt chaining (2026-09-13): adopt the trigger's build receipt,
|
|
1732
|
-
// or park on an uncorrelated acceptance. The trigger's closeout schema
|
|
1733
|
-
// requires build_agent_id — the platform build's in-flight correlation
|
|
1734
|
-
// ID captured immediately after the edit was accepted.
|
|
1735
|
-
// An accepted edit (edit_started=true) with a null receipt is UNKNOWN,
|
|
1736
|
-
// not "did not go through": the build may be pending_init-invisible,
|
|
1737
|
-
// may have finished before the capture window, or may be queued behind
|
|
1738
|
-
// a still-running earlier build. No re-trigger is issued on unknown —
|
|
1739
|
-
// a blind re-trigger duplicated the edit on 2026-09-12, and the platform
|
|
1740
|
-
// offers no idempotency proof that would make re-issue safe.
|
|
1741
|
-
// (Retry-semantics reconciliation, 2026-09-13: the "no receipt → safe
|
|
1742
|
-
// re-trigger" sketch assumed the edit command idempotently publishes
|
|
1743
|
-
// what's on git; the duplicate-edit incident disproves the assumption,
|
|
1744
|
-
// and the standing rule retries only on explicit negative evidence.
|
|
1745
|
-
// Re-trigger stays exactly where it was: the edit_started=false
|
|
1746
|
-
// explicit-rejection path above.) Record the attempt and park
|
|
1747
|
-
// fail-closed; correlate via the ledger and the parent's content
|
|
1748
|
-
// read-back before re-driving Publish.
|
|
1749
|
-
if (rebuildTrigger && rebuildTrigger.edit_started && !rebuildReportMissing) {
|
|
1750
|
-
var triggerAgentId = (typeof rebuildTrigger.build_agent_id === "string" && rebuildTrigger.build_agent_id.length > 0) ? rebuildTrigger.build_agent_id : null;
|
|
1751
|
-
if (!triggerAgentId) {
|
|
1752
|
-
var uncorrelatedObservation = (function () { var c = verifyAppliedChanges(expectedChanges, rebuildTrigger.applied); return c.ok ? "match" : "mismatch: " + c.reason; })();
|
|
1753
|
-
log("Publish receipt missing for task " + taskId + ": the trigger reported edit_started=true but captured no build receipt (build_agent_id null) — no build attributable to this edit. Parking fail-closed without re-triggering.");
|
|
1754
|
-
await recordPublishLedger({
|
|
1755
|
-
commit: mergeCommitForPublish,
|
|
1756
|
-
attempt: rebuildAttemptKey,
|
|
1757
|
-
agent_id: null,
|
|
1758
|
-
applied_report: uncorrelatedObservation,
|
|
1759
|
-
outcome: "unknown",
|
|
1760
|
-
detail: "edit accepted (edit_started=true) but the trigger captured no build receipt in its capture window: no build attributable to this edit (pending_init-invisible, finished before capture, or queued behind an earlier build). No re-trigger issued — a blind re-trigger on an unknown outcome duplicated the edit on 2026-09-12 and the platform offers no idempotency proof."
|
|
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"
|
|
1761
1795
|
}, totalReworkCount);
|
|
1762
|
-
return await parkTask("Publish outcome unknown: the rebuild trigger
|
|
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.");
|
|
1763
1797
|
}
|
|
1764
|
-
rebuildAgentId = triggerAgentId;
|
|
1765
|
-
log("Publish receipt chained for task " + taskId + ": build " + triggerAgentId + " — the follow-up poll waits on this build only.");
|
|
1766
1798
|
}
|
|
1799
|
+
|
|
1767
1800
|
// Durable publish-attempt ledger: record the trigger outcome while the
|
|
1768
1801
|
// attempt key and commit are in scope. Every attempt lands here with
|
|
1769
1802
|
// its outcome — submitted, rejected, or unknown (unknown is recorded
|
|
1770
1803
|
// at the park site above). A later run or human matches commit hash +
|
|
1771
1804
|
// attempt key against the builder's eventual completion.
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
agent_id: rebuildAgentId,
|
|
1789
|
-
applied_report: publishAppliedObservation,
|
|
1790
|
-
outcome: "submitted",
|
|
1791
|
-
detail: rebuildReportMissing
|
|
1792
|
-
? (rebuildEvidenceNote || "edit confirmed via build-state poll after structured-output failure (build " + (rebuildAgentId || "agent_id unknown") + "); builder applied-report missing")
|
|
1793
|
-
: "edit accepted; builder applied-report received"
|
|
1794
|
-
}, totalReworkCount);
|
|
1795
|
-
} else if (rebuildTrigger) {
|
|
1796
|
-
await recordPublishLedger({
|
|
1797
|
-
commit: mergeCommitForPublish,
|
|
1798
|
-
attempt: rebuildAttemptKey,
|
|
1799
|
-
applied_report: null,
|
|
1800
|
-
outcome: "rejected",
|
|
1801
|
-
detail: "edit not accepted: " + (rebuildTrigger.error || "no error detail")
|
|
1802
|
-
}, totalReworkCount);
|
|
1803
|
-
}
|
|
1804
|
-
var publishFailure = null;
|
|
1805
|
-
if (rebuildTrigger.edit_started) {
|
|
1806
|
-
// STEP 1b (observation only): publishAppliedObservation was
|
|
1807
|
-
// computed and logged above, inside the ledger block — the
|
|
1808
|
-
// builder's applied-report never parks and never blocks the stamp.
|
|
1809
|
-
// Task 23ca8f3f (2026-09-12) proved its false-negative mode:
|
|
1810
|
-
// applied:[] for a diff the builder had actually applied, which
|
|
1811
|
-
// parked a successful publish as unverified. A "match" certifies
|
|
1812
|
-
// nothing either — the report is derived from the carried diff
|
|
1813
|
-
// (canary run 8). The flow proceeds to the build poll and the
|
|
1814
|
-
// independent read-back trigger regardless of what the report
|
|
1815
|
-
// claimed; real verification is the parent's read-back
|
|
1816
|
-
// (docs/publish-verification.md) before the provenance stamp.
|
|
1817
|
-
// Pre-publish base observation (diagnostic, 2026-09-12): compare
|
|
1818
|
-
// the builder's self-reported pre-edit hashes against the
|
|
1819
|
-
// workflow-computed expected base (merge parent). This tells us
|
|
1820
|
-
// what base state the publish actually read. OBSERVATION ONLY —
|
|
1821
|
-
// a mismatch is logged loudly but never parks and never blocks
|
|
1822
|
-
// the stamp. If the tree was dirty or drifted, the evidence is
|
|
1823
|
-
// here; the fix (requiring the right base) comes after we see it.
|
|
1824
|
-
try {
|
|
1825
|
-
var preHashes = (rebuildTrigger && rebuildTrigger.pre_hashes) || {};
|
|
1826
|
-
var baseLines = expectedChanges.map(function(f) {
|
|
1827
|
-
var expected = expectedBaseHashes[f.path];
|
|
1828
|
-
var actual = preHashes[f.path];
|
|
1829
|
-
var expShort = (expected || "UNKNOWN").slice(0, 12);
|
|
1830
|
-
var actShort = String(actual || "NOT-REPORTED").slice(0, 12);
|
|
1831
|
-
var match = (expected !== undefined && actual !== undefined) ? (expected === actual) : "unknown";
|
|
1832
|
-
return " " + f.path + ": expected_base=" + expShort + " builder_pre=" + actShort + " match=" + match;
|
|
1833
|
-
});
|
|
1834
|
-
var anyMismatch = expectedChanges.some(function(f) {
|
|
1835
|
-
return expectedBaseHashes[f.path] !== undefined && preHashes[f.path] !== undefined && expectedBaseHashes[f.path] !== preHashes[f.path];
|
|
1836
|
-
});
|
|
1837
|
-
log("Publish pre-tree base observation for task " + taskId + (anyMismatch ? " — BASE MISMATCH DETECTED (tree was not at the expected merge-parent state when the diff was applied):" : " — base matches expected merge-parent state:") + "\n" + baseLines.join("\n"));
|
|
1838
|
-
} catch (e) {
|
|
1839
|
-
log("Publish pre-tree base observation failed for task " + taskId + " (non-fatal): " + (e && e.message ? e.message : e));
|
|
1840
|
-
}
|
|
1805
|
+
// (2026-09-16) The trigger is fire-and-forget: the observation above
|
|
1806
|
+
// already recorded the ledger's submitted line on both positive paths
|
|
1807
|
+
// and parked on unknown — there is no applied report to observe and
|
|
1808
|
+
// no rejection signal to record.
|
|
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) {
|
|
1812
|
+
// (2026-09-16) There is no builder report: the fire-and-forget
|
|
1813
|
+
// trigger carries no JSON contract, so there is nothing to
|
|
1814
|
+
// compare and no pre-hash diagnostic. The builder's old
|
|
1815
|
+
// self-report was circular by construction (canary run 8) with a
|
|
1816
|
+
// demonstrated false-negative mode (task 23ca8f3f, 2026-09-12:
|
|
1817
|
+
// applied:[] for a diff the builder had applied). The flow
|
|
1818
|
+
// proceeds to the build poll regardless; real verification is the
|
|
1819
|
+
// parent's independent read-back (docs/publish-verification.md)
|
|
1820
|
+
// before the provenance stamp.
|
|
1841
1821
|
// STEP 1b (mechanical): bounded poll for build completion, chunked so
|
|
1842
1822
|
// the merge-lock lease is refreshed before it can expire. The 600s
|
|
1843
1823
|
// lease is shorter than the worst-case 10-minute build poll, so the
|
|
@@ -1925,14 +1905,15 @@ while (i < STEPS.length) {
|
|
|
1925
1905
|
if (buildPoll.build_done && pollSawOurBuild) {
|
|
1926
1906
|
// STEP 1c (mechanical): NO provenance stamp here. Canary run 8
|
|
1927
1907
|
// (2026-09-11) proved the stamp cannot certify content: the
|
|
1928
|
-
// builder's applied-report
|
|
1929
|
-
//
|
|
1930
|
-
//
|
|
1908
|
+
// builder's applied-report was derived from the carried diff, so
|
|
1909
|
+
// the old report check was circular — a fabricated report
|
|
1910
|
+
// passed by construction, and every phase went green on a hollow
|
|
1931
1911
|
// build. The stamp moves to the parent (docs/publish-verification.md);
|
|
1932
|
-
// the
|
|
1933
|
-
// agent-callable read-back tool
|
|
1934
|
-
// removed by the platform 2026-09-14
|
|
1935
|
-
//
|
|
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.
|
|
1936
1917
|
// QA's provenance check enforces the stamp mechanically.
|
|
1937
1918
|
// An unverified publish fails loudly in QA instead of passing
|
|
1938
1919
|
// silently here.
|
|
@@ -1975,26 +1956,17 @@ while (i < STEPS.length) {
|
|
|
1975
1956
|
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1976
1957
|
);
|
|
1977
1958
|
var auditDirsAfterPollList = String((auditAfterPoll && auditAfterPoll.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1978
|
-
|
|
1959
|
+
// Gated on auditBeforeOk (critic finding 4): without a baseline
|
|
1960
|
+
// every historical dir would look new.
|
|
1961
|
+
newAuditDirsAfterPoll = auditBeforeOk ? auditDirsAfterPollList.filter(function (d) {
|
|
1979
1962
|
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1980
|
-
});
|
|
1963
|
+
}) : [];
|
|
1981
1964
|
log("Publish audit-dir re-list after build poll for task " + taskId + ": " + newAuditDirsAfterPoll.length + " new timestamped dir(s)");
|
|
1982
1965
|
} catch (auditAfterPollErr) {
|
|
1983
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));
|
|
1984
1967
|
}
|
|
1985
|
-
//
|
|
1986
|
-
//
|
|
1987
|
-
// unreadable — not evidence either way). The child returns the
|
|
1988
|
-
// raw body verbatim; interpretation lives here, never in prose.
|
|
1989
|
-
var auditReportOk = function (raw) {
|
|
1990
|
-
if (typeof raw !== "string") return null;
|
|
1991
|
-
var trimmed = raw.trim();
|
|
1992
|
-
if (trimmed === "" || trimmed === "MISSING") return null;
|
|
1993
|
-
var parsed;
|
|
1994
|
-
try { parsed = JSON.parse(trimmed); } catch (e) { return null; }
|
|
1995
|
-
if (parsed && typeof parsed.ok === "boolean") return parsed.ok;
|
|
1996
|
-
return null;
|
|
1997
|
-
};
|
|
1968
|
+
// The shared auditReportOk (defined with the immediate fallback
|
|
1969
|
+
// above) interprets the raw body here too.
|
|
1998
1970
|
var auditOkAfterPoll = null;
|
|
1999
1971
|
var newestAuditDirAfterPoll = null;
|
|
2000
1972
|
if (newAuditDirsAfterPoll.length > 0 && !strangerObserved) {
|
|
@@ -2053,7 +2025,9 @@ while (i < STEPS.length) {
|
|
|
2053
2025
|
}
|
|
2054
2026
|
}
|
|
2055
2027
|
} else {
|
|
2056
|
-
|
|
2028
|
+
// Unreachable: the observation above either attributes the edit
|
|
2029
|
+
// (edit_started) or parks. Defensive only — never a silent pass.
|
|
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.";
|
|
2057
2031
|
}
|
|
2058
2032
|
} // end: publishSkippedNoLock — no rebuild, no stamp, nothing to ship
|
|
2059
2033
|
// STEP 2 (mechanical, always — skip path included): post-deploy
|