muse-crew 0.7.9 → 0.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +19 -11
- package/docs/guide.md +2 -2
- package/docs/ooda-report.md +123 -0
- package/docs/publish-verification.md +253 -88
- package/docs/visual-verdict.md +81 -67
- package/lib/AGENTS.md +7 -0
- package/lib/append-ooda-step.js +167 -0
- package/lib/build-readback-request.js +130 -0
- package/lib/compose-evidence-caption.js +141 -0
- package/lib/crew-api.js +281 -2
- package/lib/edit-image.py +216 -0
- package/lib/render-html.js +142 -0
- package/lib/see-act.js +327 -0
- package/lib/serve-artifact.js +203 -0
- package/lib/verify-publish.js +265 -0
- package/lib/write-ooda-verdict.js +130 -0
- package/package.json +1 -1
- package/seed/cron-body-template.md +25 -3
- package/workflows/bugfix.js +220 -214
- package/workflows/chore.js +161 -113
- package/workflows/crew-dispatch.js +1 -1
- package/workflows/docs.js +1 -1
- package/workflows/standard.js +185 -229
package/workflows/chore.js
CHANGED
|
@@ -529,52 +529,22 @@ function verifyAppliedChanges(expected, applied) {
|
|
|
529
529
|
return { ok: true };
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
// Publish read-back request
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// prove the read-back inspected the live build of THIS attempt — not a
|
|
549
|
-
// different build's output. Null/empty means the edit was accepted but
|
|
550
|
-
// never correlated to a builder run. Pure function of inputs — no I/O,
|
|
551
|
-
// no clock.
|
|
552
|
-
var buildIdLine = (typeof buildAgentId === "string" && buildAgentId.length > 0)
|
|
553
|
-
? "Expected builder build agent_id: " + buildAgentId + " (the artifact system's in-flight correlation ID for this publish attempt — not a durable post-completion identifier).\n"
|
|
554
|
-
: "No build agent_id was observed for this publish attempt (the edit was accepted but never correlated to a builder run) — say so explicitly in your report.\n";
|
|
555
|
-
return (
|
|
556
|
-
"Publish content read-back for task " + taskId + ", merge commit " + commit + ".\n" +
|
|
557
|
-
"The unified diff below was supposed to be applied to this artifact's source tree and deployed. Do NOT modify anything.\n" +
|
|
558
|
-
"Do NOT rely on the builder's applied-changes report — it is derived from this same diff, so it cannot confirm the content. Read the artifact's CURRENT source directly.\n" +
|
|
559
|
-
"\n" +
|
|
560
|
-
buildIdLine +
|
|
561
|
-
"Report the live build's agent_id as seen in artifact_status (or state explicitly that no build/agent_id is visible). If an expected agent_id is given above and the live one differs, say so exactly — the read-back may be inspecting a different build's output.\n" +
|
|
562
|
-
"\n" +
|
|
563
|
-
"UNIFIED DIFF (expected change):\n" +
|
|
564
|
-
"```diff\n" + diff + "\n```\n" +
|
|
565
|
-
"\n" +
|
|
566
|
-
"For each file in the diff:\n" +
|
|
567
|
-
"1. Read the file's CURRENT content in the artifact source tree.\n" +
|
|
568
|
-
"2. Quote the exact current text of the regions around the changed lines.\n" +
|
|
569
|
-
"3. For every added (+) line in the diff, state whether that exact line is PRESENT in the current source.\n" +
|
|
570
|
-
"4. For every removed (-) line in the diff, state whether that exact line is ABSENT from the current source.\n" +
|
|
571
|
-
"5. Report build/deploy health and the console error count.\n" +
|
|
572
|
-
"\n" +
|
|
573
|
-
"Return the per-file present/absent findings with the quoted observed lines. Do not modify anything.\n" +
|
|
574
|
-
"This read-back feeds the parent content-verification protocol (docs/publish-verification.md): the parent stamps provenance only when every added line is present and every removed line is absent."
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
|
|
532
|
+
// Publish read-back request (currently unavailable): the verbatim_request
|
|
533
|
+
// the parent protocol (docs/publish-verification.md) would hand to an
|
|
534
|
+
// independent read-back tool after the artifact build lands. artifact_inspect
|
|
535
|
+
// was removed by the platform (2026-09-14); artifact.inspect is malfunction
|
|
536
|
+
// diagnosis, not a substitute — so no agent-callable read-back tool exists
|
|
537
|
+
// and this request cannot currently be issued. Pure function — no I/O, no
|
|
538
|
+
// clock. The request carries the merged diff as the expected change and asks
|
|
539
|
+
// for an independent read of the artifact's actual source: for each file, the
|
|
540
|
+
// exact current text of the changed regions plus a per-line present/absent
|
|
541
|
+
// finding. Until a read-back path exists, the parent cannot independently
|
|
542
|
+
// confirm content and verification parks at "publish: verification-requested"
|
|
543
|
+
// (see docs/publish-verification.md). This preserves the circularity break
|
|
544
|
+
// that hollowed canary run 8 (2026-09-11): verifyAppliedChanges compares the
|
|
545
|
+
// builder's applied-report against the diff the report was derived from — a
|
|
546
|
+
// fabricated report passes by construction. Independent read-back cannot be
|
|
547
|
+
// fabricated from the diff; it must match the artifact's real content.
|
|
578
548
|
// Pre-publish base observation (diagnostic, 2026-09-12): instruction fragment
|
|
579
549
|
// for the builder's edit request, asking it to report the sha256 of each
|
|
580
550
|
// touched file's CURRENT content BEFORE applying the diff. Pure function —
|
|
@@ -1260,7 +1230,7 @@ while (i < STEPS.length) {
|
|
|
1260
1230
|
var instructions = "";
|
|
1261
1231
|
|
|
1262
1232
|
if (step.name === "Triage") {
|
|
1263
|
-
instructions = "Validate the task,
|
|
1233
|
+
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, note dependencies, confirm the chore workflow assignment.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything rendered and visible in the project's user-facing artifact (pages, components, styles, layout, copy, visual states)? If yes it is experiential and gets baseline captures. End your report with exactly one line on its own, lowercase, unrephrased: experiential: yes — or experiential: no. This line is machine-read.";
|
|
1264
1234
|
|
|
1265
1235
|
} else if (step.name === "Map") {
|
|
1266
1236
|
var mapGatePara = "";
|
|
@@ -1272,7 +1242,7 @@ while (i < STEPS.length) {
|
|
|
1272
1242
|
" If the baseline evidence is missing with no baseline:none recorded, do not write the spec — report 'baseline evidence missing — Map gate bounce required' and stop.\n" +
|
|
1273
1243
|
"Declare capture targets for the post-change visual capture: end your report with a line `capture_targets: <comma-separated views/controls this change affects>` (optional; falls back to the task description).";
|
|
1274
1244
|
}
|
|
1275
|
-
instructions = "Research options, pick the path, write a clear spec for the builder.\nThe builder will edit source files in a git worktree of the project at " + REPO_PATH + ".\nProject: " + PROJECT_DESC + "\nTo understand the current code, read source files directly using the read tool. Do NOT use artifact_inspect — it
|
|
1245
|
+
instructions = "Research options, pick the path, write a clear spec for the builder.\nThe builder will edit source files in a git worktree of the project at " + REPO_PATH + ".\nProject: " + PROJECT_DESC + "\nTo understand the current code, read source files directly using the read tool. Do NOT use artifact_inspect — it was removed by the platform (2026-09-14) and does not exist; do not substitute artifact.inspect (malfunction diagnosis, not an inspection tool).\nIdentify the exact files and changes needed. Be specific: file paths, what to add or change.\nReport back in plain prose — what you specified." + mapGatePara;
|
|
1276
1246
|
|
|
1277
1247
|
} else if (step.name === "Build") {
|
|
1278
1248
|
instructions = "STEP 1: Prepare your worktree.\n" +
|
|
@@ -1388,11 +1358,12 @@ while (i < STEPS.length) {
|
|
|
1388
1358
|
// (fail-closed). There is deliberately NO workflow-side provenance
|
|
1389
1359
|
// stamp: the builder's applied-report is circular (canary run 8,
|
|
1390
1360
|
// 2026-09-11), so the stamp moved to the parent — after the build
|
|
1391
|
-
// lands, the workflow
|
|
1392
|
-
//
|
|
1393
|
-
//
|
|
1394
|
-
//
|
|
1395
|
-
//
|
|
1361
|
+
// lands, the workflow records the session completed and parks with
|
|
1362
|
+
// "publish: verification-requested". The parent owns verification
|
|
1363
|
+
// (docs/publish-verification.md); the independent read-back step is
|
|
1364
|
+
// currently unavailable (no agent-callable read-back tool exists —
|
|
1365
|
+
// artifact_inspect was removed by the platform 2026-09-14).
|
|
1366
|
+
// Chore has no QA: the parent's verification is the final gate.
|
|
1396
1367
|
var artifactPublish = null;
|
|
1397
1368
|
var publishLockRefreshed = false;
|
|
1398
1369
|
var publishSkippedNoLock = false;
|
|
@@ -1440,14 +1411,45 @@ while (i < STEPS.length) {
|
|
|
1440
1411
|
// changes — no prose claim to trust. If the artifact tool namespace
|
|
1441
1412
|
// is missing from this child it reports honestly and the workflow
|
|
1442
1413
|
// retries once with a fresh key (bounded); anything else parks.
|
|
1414
|
+
// Publish diff base (2026-09-14, task 0c53af4e): the carried diff is
|
|
1415
|
+
// BASE..HEAD where BASE is the previously-stamped provenance
|
|
1416
|
+
// source_commit — NOT HEAD^1. A push-time reconcile merge puts the
|
|
1417
|
+
// task's own changes behind an intermediate merge, so HEAD^1..HEAD
|
|
1418
|
+
// silently drops the task's fix while the artifact builds without
|
|
1419
|
+
// it. The stamped base is the artifact's actual content; BASE..HEAD
|
|
1420
|
+
// is the complete unpublished delta. Empty tree only for a genuine
|
|
1421
|
+
// first publish (no provenance stamped yet).
|
|
1422
|
+
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
1423
|
+
var provResult = await agent(
|
|
1424
|
+
crewCmd("get-provenance", {}) + "\n" +
|
|
1425
|
+
"Return JSON { \"provenance\": <the CLI's provenance object, or null when nothing is stamped> } and nothing else. Do not interpret it.",
|
|
1426
|
+
{ key: attemptKey("publish-provenance-base-" + taskId, reworkCount), label: "Reading stamped publish base",
|
|
1427
|
+
schema: { type: "object", properties: { provenance: { type: ["object", "null"] } }, required: ["provenance"] } }
|
|
1428
|
+
);
|
|
1429
|
+
var publishBase = (provResult.provenance && provResult.provenance.source_commit) || "";
|
|
1430
|
+
publishBase = String(publishBase).trim();
|
|
1431
|
+
if (!publishBase) {
|
|
1432
|
+
publishBase = EMPTY_TREE_SHA;
|
|
1433
|
+
log("Publish base for task " + taskId + ": no provenance stamped yet — using empty tree (first publish)");
|
|
1434
|
+
} else if (!/^[0-9a-f]{40}$/.test(publishBase)) {
|
|
1435
|
+
return await parkTask("Publish base '" + publishBase + "' is not a valid commit SHA — cannot compute the publish diff. Human attention needed.");
|
|
1436
|
+
}
|
|
1443
1437
|
var diffResult = await agent(
|
|
1444
|
-
"Run: cd " + REPO_PATH + " &&
|
|
1445
|
-
"
|
|
1446
|
-
|
|
1447
|
-
|
|
1438
|
+
"Run: cd " + REPO_PATH + " && BASE='" + publishBase + "' && HEAD=$(git rev-parse HEAD) && " +
|
|
1439
|
+
"if [ \"$BASE\" = '" + EMPTY_TREE_SHA + "' ]; then ANCESTOR=yes; else git merge-base --is-ancestor \"$BASE\" \"$HEAD\" && ANCESTOR=yes || ANCESTOR=no; fi && " +
|
|
1440
|
+
"echo '---COMMIT---' && echo \"$HEAD\" && echo '---BASE---' && echo \"$BASE\" && echo '---ANCESTOR---' && echo \"$ANCESTOR\" && " +
|
|
1441
|
+
"if [ \"$ANCESTOR\" = yes ]; then echo '---DIFF---' && git diff \"$BASE\" \"$HEAD\" && echo '---NAMES---' && git diff-tree --no-commit-id --name-only -r \"$BASE\" \"$HEAD\"; fi\n" +
|
|
1442
|
+
"Return JSON { \"commit\": \"<HEAD trimmed>\", \"base\": \"<BASE trimmed>\", \"ancestor\": \"<yes|no>\", \"diff\": \"<raw unified diff, may be multi-line>\", \"files\": \"<newline-separated paths>\" } and nothing else.",
|
|
1443
|
+
{ key: attemptKey("publish-artifact-diff-" + taskId, reworkCount), label: "Computing publish diff from stamped base",
|
|
1444
|
+
schema: { type: "object", properties: { commit: { type: "string" }, base: { type: "string" }, ancestor: { type: "string" }, diff: { type: "string" }, files: { type: "string" } }, required: ["commit", "base", "ancestor", "diff"] } }
|
|
1448
1445
|
);
|
|
1446
|
+
if ((diffResult.ancestor || "").trim() !== "yes") {
|
|
1447
|
+
return await parkTask("Publish base " + publishBase.slice(0, 12) + " is not an ancestor of HEAD " + (diffResult.commit || "").trim().slice(0, 12) + " — the stamped provenance does not lead to the integrated commit. Human attention needed.");
|
|
1448
|
+
}
|
|
1449
|
+
if ((diffResult.base || "").trim() !== publishBase) {
|
|
1450
|
+
return await parkTask("Publish diff base mismatch: agent reported '" + (diffResult.base || "").trim().slice(0, 12) + "' but the stamped base is '" + publishBase.slice(0, 12) + "'. Human attention needed.");
|
|
1451
|
+
}
|
|
1449
1452
|
var mergeCommitForPublish = (diffResult.commit || "").trim();
|
|
1450
|
-
var mergeParentForPublish = (diffResult.parent || "").trim();
|
|
1451
1453
|
var mergeDiff = diffResult.diff || "";
|
|
1452
1454
|
if (!mergeDiff.trim()) {
|
|
1453
1455
|
return await parkTask("Publish diff is empty for commit " + (mergeCommitForPublish || "unknown") + " — a merge lock was held but there is no change to carry. Human attention needed.");
|
|
@@ -1458,11 +1460,15 @@ while (i < STEPS.length) {
|
|
|
1458
1460
|
if (/^rename from /m.test(mergeDiff)) {
|
|
1459
1461
|
return await parkTask("Publish diff contains a rename — the diff transport cannot carry renames. Human attention needed.");
|
|
1460
1462
|
}
|
|
1461
|
-
var mergeDiffLines = mergeDiff.split("\n").length;
|
|
1462
|
-
if (mergeDiffLines > 200) {
|
|
1463
|
-
return await parkTask("Publish diff is " + mergeDiffLines + " lines (budget 200) — too large for the diff transport. Human attention needed.");
|
|
1464
|
-
}
|
|
1465
1463
|
var expectedChanges = parseUnifiedDiff(mergeDiff);
|
|
1464
|
+
// Budget counts CHANGED lines (added + removed), not raw unified-diff
|
|
1465
|
+
// output lines: context lines and file headers inflated the old
|
|
1466
|
+
// split("\n").length count ~2x, parking a 95-line change against a
|
|
1467
|
+
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13).
|
|
1468
|
+
var mergeDiffChangedLines = expectedChanges.reduce(function (n, f) { return n + f.added.length + f.removed.length; }, 0);
|
|
1469
|
+
if (mergeDiffChangedLines > 200) {
|
|
1470
|
+
return await parkTask("Publish diff changes " + mergeDiffChangedLines + " lines (budget 200) — too large for the diff transport. Human attention needed.");
|
|
1471
|
+
}
|
|
1466
1472
|
if (expectedChanges.length === 0) {
|
|
1467
1473
|
return await parkTask("Publish diff parsed to zero files for commit " + (mergeCommitForPublish || "unknown") + " — cannot verify application. Human attention needed.");
|
|
1468
1474
|
}
|
|
@@ -1475,12 +1481,16 @@ while (i < STEPS.length) {
|
|
|
1475
1481
|
// but never parks. The observation tells us what the publish actually
|
|
1476
1482
|
// reads, so the subsequent fix can require the right base.
|
|
1477
1483
|
var expectedBaseHashes = {};
|
|
1478
|
-
|
|
1484
|
+
if (publishBase === EMPTY_TREE_SHA) {
|
|
1485
|
+
// First publish: every file in the diff is new to the artifact.
|
|
1486
|
+
expectedChanges.forEach(function (f) { expectedBaseHashes[f.path] = "NEW-FILE"; });
|
|
1487
|
+
log("Publish expected base hashes for task " + taskId + ": empty tree (first publish) — all " + expectedChanges.length + " file(s) new");
|
|
1488
|
+
} else try {
|
|
1479
1489
|
// Shell-quote helper (no regex-with-quote: the test parser does not
|
|
1480
1490
|
// understand regex literals containing quotes).
|
|
1481
1491
|
var sq = function(s) { return "'" + String(s).split("'").join("'\\''") + "'"; };
|
|
1482
1492
|
var baseHashResult = await agent(
|
|
1483
|
-
"Run: cd " + REPO_PATH + " && parent=" + sq(
|
|
1493
|
+
"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" +
|
|
1484
1494
|
"Return JSON { \"hashes\": \"<newline-separated <path>:<sha256> lines, empty hash means the file is new in this diff>\" } and nothing else.",
|
|
1485
1495
|
{ key: attemptKey("publish-base-hashes-" + taskId, reworkCount), label: "Computing expected base content hashes",
|
|
1486
1496
|
schema: { type: "object", properties: { hashes: { type: "string" } }, required: ["hashes"] } }
|
|
@@ -1489,7 +1499,7 @@ while (i < STEPS.length) {
|
|
|
1489
1499
|
var m = /^([^:]+):([0-9a-f]*)$/.exec(line.trim());
|
|
1490
1500
|
if (m) expectedBaseHashes[m[1]] = m[2] || "NEW-FILE";
|
|
1491
1501
|
});
|
|
1492
|
-
log("Publish expected base hashes for task " + taskId + " (
|
|
1502
|
+
log("Publish expected base hashes for task " + taskId + " (stamped base " + publishBase.slice(0, 12) + "): " + JSON.stringify(expectedBaseHashes));
|
|
1493
1503
|
} catch (e) {
|
|
1494
1504
|
log("Publish expected base hash computation failed for task " + taskId + " (non-fatal, observation degraded): " + (e && e.message ? e.message : e));
|
|
1495
1505
|
}
|
|
@@ -1547,6 +1557,7 @@ while (i < STEPS.length) {
|
|
|
1547
1557
|
required: ["edit_started", "build_agent_id", "applied"] };
|
|
1548
1558
|
var rebuildTrigger = null;
|
|
1549
1559
|
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
|
|
1560
|
+
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
|
|
1550
1561
|
// The trigger key of the attempt that last ran, for the publish ledger.
|
|
1551
1562
|
// Minted once here (not re-minted per use site) so the ledger always
|
|
1552
1563
|
// records the exact key that was issued — and so a re-minted duplicate
|
|
@@ -1565,6 +1576,32 @@ while (i < STEPS.length) {
|
|
|
1565
1576
|
// (2026-09-12, task 23ca8f3f): computed once the trigger outcome is
|
|
1566
1577
|
// known, logged loudly, never a park.
|
|
1567
1578
|
var publishAppliedObservation = null; // "match" | "mismatch: <reason>" | "missing-report" — observation only, never a park
|
|
1579
|
+
// Durable-evidence snapshot (2026-09-14): the structured-output
|
|
1580
|
+
// fallback below only observes IN-FLIGHT builds. A build that
|
|
1581
|
+
// finished before the poll leaves no in-flight trace — but the
|
|
1582
|
+
// platform's audit harness leaves a durable one:
|
|
1583
|
+
// ~/workspace/ts-spaces/<slug>/audits/<timestamp>-<id>/ per
|
|
1584
|
+
// completed build. Snapshot the listing BEFORE the trigger so the
|
|
1585
|
+
// fallback can diff before/after: a directory appearing during the
|
|
1586
|
+
// trigger window is positive evidence the edit went through and
|
|
1587
|
+
// the build completed. Best-effort and non-gating: if the snapshot
|
|
1588
|
+
// fails, the durable check is skipped and the fallback behaves as
|
|
1589
|
+
// before. No wall-clock in-script (deterministic replay) — the
|
|
1590
|
+
// comparison is a pure before/after set diff.
|
|
1591
|
+
var auditDirsBeforeTrigger = [];
|
|
1592
|
+
try {
|
|
1593
|
+
var auditBefore = await agent(
|
|
1594
|
+
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort snapshot, never a gate).\n" +
|
|
1595
|
+
"Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null\n" +
|
|
1596
|
+
"Return JSON { \"dirs\": \"<newline-separated names, empty string when the audits directory does not exist or is empty>\" } and nothing else.",
|
|
1597
|
+
{ key: attemptKey("publish-audit-before-" + taskId, reworkCount), label: "Snapshotting audit dirs before rebuild trigger",
|
|
1598
|
+
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1599
|
+
);
|
|
1600
|
+
auditDirsBeforeTrigger = String((auditBefore && auditBefore.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1601
|
+
log("Publish audit-dir snapshot before trigger for task " + taskId + ": " + auditDirsBeforeTrigger.length + " entries");
|
|
1602
|
+
} catch (auditBeforeErr) {
|
|
1603
|
+
log("Publish audit-dir snapshot before trigger failed for task " + taskId + " (non-fatal, durable-evidence check degraded): " + (auditBeforeErr && auditBeforeErr.message ? auditBeforeErr.message : auditBeforeErr));
|
|
1604
|
+
}
|
|
1568
1605
|
try {
|
|
1569
1606
|
rebuildTrigger = await agent(rebuildPrompt,
|
|
1570
1607
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild", schema: rebuildSchema });
|
|
@@ -1623,7 +1660,46 @@ while (i < STEPS.length) {
|
|
|
1623
1660
|
rebuildTrigger = { edit_started: true, error: "", applied: null };
|
|
1624
1661
|
rebuildReportMissing = true;
|
|
1625
1662
|
rebuildAgentId = acceptedAgentId;
|
|
1663
|
+
rebuildEvidenceNote = "edit confirmed via build-state poll after structured-output failure (build " + acceptedAgentId + "); builder applied-report missing";
|
|
1626
1664
|
} else {
|
|
1665
|
+
// Durable completion check (2026-09-14): the in-flight poll
|
|
1666
|
+
// above only sees RUNNING builds. Attempt 7 (2026-09-14) proved
|
|
1667
|
+
// the gap: the trigger child applied the edit, the build ran
|
|
1668
|
+
// and completed — the platform's audit harness captured it
|
|
1669
|
+
// mid-window — then the child failed to return JSON. The
|
|
1670
|
+
// fallback poll saw no in-flight build, so a successful publish
|
|
1671
|
+
// parked as "unknown". Diff the audit-dir listing against the
|
|
1672
|
+
// pre-trigger snapshot: a timestamped directory that appeared
|
|
1673
|
+
// during the trigger window is positive evidence the edit went
|
|
1674
|
+
// through and the build completed. This never re-issues the
|
|
1675
|
+
// edit and never stamps provenance — it only routes to the
|
|
1676
|
+
// parent's independent content read-back, which remains the
|
|
1677
|
+
// real verification.
|
|
1678
|
+
var newAuditDirs = [];
|
|
1679
|
+
try {
|
|
1680
|
+
var auditAfter = await agent(
|
|
1681
|
+
"List the artifact audit directories for slug \"" + PUBLISH_SLUG + "\" (best-effort, never a gate).\n" +
|
|
1682
|
+
"Run: ls -1 ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/audits/ 2>/dev/null\n" +
|
|
1683
|
+
"Return JSON { \"dirs\": \"<newline-separated names, empty string when the audits directory does not exist or is empty>\" } and nothing else.",
|
|
1684
|
+
{ key: attemptKey("publish-audit-after-" + taskId, reworkCount), label: "Re-listing audit dirs after trigger failure",
|
|
1685
|
+
schema: { type: "object", properties: { dirs: { type: "string" } }, required: ["dirs"] } }
|
|
1686
|
+
);
|
|
1687
|
+
var auditDirsAfterTrigger = String((auditAfter && auditAfter.dirs) || "").split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1688
|
+
// Only timestamped build dirs count — the "latest" symlink
|
|
1689
|
+
// and anything else are not builds.
|
|
1690
|
+
newAuditDirs = auditDirsAfterTrigger.filter(function (d) {
|
|
1691
|
+
return auditDirsBeforeTrigger.indexOf(d) === -1 && /^20\d\d-\d\d-\d\dT\d\d-\d\d-\d\dZ-/.test(d);
|
|
1692
|
+
});
|
|
1693
|
+
} catch (auditAfterErr) {
|
|
1694
|
+
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));
|
|
1695
|
+
}
|
|
1696
|
+
if (newAuditDirs.length > 0) {
|
|
1697
|
+
log("Publish rebuild trigger: new audit dir(s) during the trigger window (" + newAuditDirs.join(", ") + ") — the edit went through and the build completed despite the structured-output failure. Skipping applied-report smoke-check; parent read-back is the verification.");
|
|
1698
|
+
rebuildTrigger = { edit_started: true, error: "", applied: null };
|
|
1699
|
+
rebuildReportMissing = true;
|
|
1700
|
+
rebuildAgentId = null;
|
|
1701
|
+
rebuildEvidenceNote = "edit confirmed via durable audit evidence after structured-output failure (new audit dir " + newAuditDirs[0] + "); builder applied-report missing";
|
|
1702
|
+
} else {
|
|
1627
1703
|
// No build observed — but that proves nothing (a fast-completing
|
|
1628
1704
|
// build can finish between polls, or the check itself failed). The
|
|
1629
1705
|
// outcome is UNKNOWN. No retry: re-issuing the edit here duplicated
|
|
@@ -1639,6 +1715,7 @@ while (i < STEPS.length) {
|
|
|
1639
1715
|
detail: "structured-output failure on rebuild trigger; build-state poll saw no build (or the check itself failed); edit may have been accepted as pending_init"
|
|
1640
1716
|
}, reworkCount);
|
|
1641
1717
|
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.");
|
|
1718
|
+
}
|
|
1642
1719
|
}
|
|
1643
1720
|
}
|
|
1644
1721
|
if (!rebuildReportMissing && !rebuildTrigger.edit_started && rebuildTrigger.error === "artifact_tools missing after load") {
|
|
@@ -1708,7 +1785,7 @@ while (i < STEPS.length) {
|
|
|
1708
1785
|
applied_report: publishAppliedObservation,
|
|
1709
1786
|
outcome: "submitted",
|
|
1710
1787
|
detail: rebuildReportMissing
|
|
1711
|
-
? "edit confirmed via build-state poll after structured-output failure (build " + (rebuildAgentId || "agent_id unknown") + "); builder applied-report missing"
|
|
1788
|
+
? (rebuildEvidenceNote || "edit confirmed via build-state poll after structured-output failure (build " + (rebuildAgentId || "agent_id unknown") + "); builder applied-report missing")
|
|
1712
1789
|
: "edit accepted; builder applied-report received"
|
|
1713
1790
|
}, reworkCount);
|
|
1714
1791
|
} else if (rebuildTrigger) {
|
|
@@ -1806,12 +1883,12 @@ while (i < STEPS.length) {
|
|
|
1806
1883
|
// builder's applied-report is derived from the carried diff, so
|
|
1807
1884
|
// verifyAppliedChanges above is circular — a fabricated report
|
|
1808
1885
|
// passes by construction, and every phase went green on a hollow
|
|
1809
|
-
// build. The stamp moves to the parent (docs/publish-verification.md)
|
|
1810
|
-
//
|
|
1811
|
-
//
|
|
1812
|
-
//
|
|
1813
|
-
//
|
|
1814
|
-
//
|
|
1886
|
+
// build. The stamp moves to the parent (docs/publish-verification.md);
|
|
1887
|
+
// the independent read-back step is currently unavailable (no
|
|
1888
|
+
// agent-callable read-back tool exists — artifact_inspect was
|
|
1889
|
+
// removed by the platform 2026-09-14), so the parent cannot
|
|
1890
|
+
// confirm content and the task parks for verification.
|
|
1891
|
+
// Chore has no QA: the parent's verification is the final gate.
|
|
1815
1892
|
publishBuildLanded = true;
|
|
1816
1893
|
artifactPublish = { source_commit: mergeCommitForPublish, pending_parent_verification: true };
|
|
1817
1894
|
log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
|
|
@@ -2194,48 +2271,22 @@ while (i < STEPS.length) {
|
|
|
2194
2271
|
// it to HEAD: that verifies the stamp, not the content. Canary run 8
|
|
2195
2272
|
// (2026-09-11) passed it with a hollow build — the stamp was honest, the
|
|
2196
2273
|
// artifact was stale, all eight phases green. The stamp now moves to the
|
|
2197
|
-
// parent
|
|
2198
|
-
//
|
|
2199
|
-
//
|
|
2200
|
-
//
|
|
2201
|
-
// there instead of passing silently here.
|
|
2274
|
+
// parent (docs/publish-verification.md); the independent read-back step
|
|
2275
|
+
// is currently unavailable (no agent-callable read-back tool exists —
|
|
2276
|
+
// artifact_inspect was removed by the platform 2026-09-14), so the parent
|
|
2277
|
+
// cannot confirm content and the task parks for verification.
|
|
2202
2278
|
// Skip-aware (park 2026-09-11): an empty-diff Integrate takes no merge
|
|
2203
2279
|
// lock, and the deterministic publish path skips rebuild/stamp entirely —
|
|
2204
2280
|
// there is no new content to verify, so verification is vacuous.
|
|
2205
2281
|
// publishSkippedNoLock is workflow-computed state from the explicit
|
|
2206
2282
|
// lock-status read in STEP 0, not agent prose.
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
try {
|
|
2215
|
-
var inspectResult = await agent(
|
|
2216
|
-
ARTIFACT_LOAD_PREAMBLE +
|
|
2217
|
-
"Call artifact_inspect with slug \"" + PUBLISH_SLUG + "\", repair_authorized false, and verbatim_request exactly as follows:\n" +
|
|
2218
|
-
"<<<READBACK_REQUEST\n" + buildPublishReadbackRequest(taskId, mergeCommitForPublish, mergeDiff, rebuildAgentId) + "\nREADBACK_REQUEST\n" +
|
|
2219
|
-
"If artifact_inspect is still not available after the load, do NOT improvise — return { \"triggered\": false, \"inspection_id\": \"\", \"error\": \"artifact_tools missing after load\" } and nothing else.\n" +
|
|
2220
|
-
"Return JSON { \"triggered\": <true if the inspection started, false otherwise>, \"inspection_id\": \"<the inspection id, or empty string>\", \"error\": \"<details or empty string>\" } and nothing else.",
|
|
2221
|
-
{ key: attemptKey("publish-verify-inspect-" + taskId, reworkCount), label: "Triggering publish content read-back",
|
|
2222
|
-
schema: { type: "object", properties: { triggered: { type: "boolean" }, inspection_id: { type: "string" }, error: { type: "string" } }, required: ["triggered"] } }
|
|
2223
|
-
);
|
|
2224
|
-
publishVerifyInspect.triggered = !!(inspectResult && inspectResult.triggered);
|
|
2225
|
-
publishVerifyInspect.inspection_id = (inspectResult && inspectResult.inspection_id) || "";
|
|
2226
|
-
publishVerifyInspect.error = (inspectResult && inspectResult.error) || "";
|
|
2227
|
-
if (publishVerifyInspect.triggered) {
|
|
2228
|
-
log("Publish content read-back inspection triggered for task " + taskId + ": " + publishVerifyInspect.inspection_id);
|
|
2229
|
-
} else {
|
|
2230
|
-
log("Publish content read-back inspect trigger failed for task " + taskId + ": " + (publishVerifyInspect.error || "not started") + " — the park below asks the parent to trigger it manually");
|
|
2231
|
-
}
|
|
2232
|
-
} catch (e) {
|
|
2233
|
-
publishVerifyInspect.error = (e && e.message ? e.message : String(e)).slice(0, 200);
|
|
2234
|
-
log("Publish content read-back inspect trigger threw for task " + taskId + ": " + publishVerifyInspect.error + " — the park below asks the parent to trigger it manually");
|
|
2235
|
-
}
|
|
2236
|
-
} // end: !publishSkippedNoLock && publishBuildLanded — a skipped or failed publish has nothing to verify
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2283
|
+
// The parent (tick worker) triggers the ONE read-back inspection it can
|
|
2284
|
+
// actually receive (async results go to the root agent, never into a
|
|
2285
|
+
// workflow run — a workflow-side trigger would be an orphan). The workflow
|
|
2286
|
+
// only parks; the parent's scan builds the request deterministically via
|
|
2287
|
+
// lib/build-readback-request.js and ferries the inspection.
|
|
2288
|
+
// publishBuildLanded and publishSkippedNoLock are workflow-computed state;
|
|
2289
|
+
// a skipped or failed publish has nothing to verify.
|
|
2239
2290
|
// Session notes. Machine-readable marker lines are extracted from the full
|
|
2240
2291
|
// worker report and appended AFTER the slice so a long report can never
|
|
2241
2292
|
// amputate them; later phases (Review reading repo_diff:, QA backstop
|
|
@@ -2319,10 +2370,7 @@ while (i < STEPS.length) {
|
|
|
2319
2370
|
if (passed && step.name === "Publish" && PUBLISH_TYPE === "artifact" && PUBLISH_SLUG && !publishSkippedNoLock && publishBuildLanded) {
|
|
2320
2371
|
return await parkTask("publish: verification-requested " + mergeCommitForPublish +
|
|
2321
2372
|
" (build " + (rebuildAgentId || "agent_id unobserved") + ")" +
|
|
2322
|
-
" — artifact build landed, post-deploy finalized, provenance NOT stamped. Parent: run docs/publish-verification.md"
|
|
2323
|
-
(publishVerifyInspect.triggered
|
|
2324
|
-
? " (content read-back inspection " + publishVerifyInspect.inspection_id + " already triggered)."
|
|
2325
|
-
: " (read-back inspect trigger failed: " + (publishVerifyInspect.error || "not started") + " — parent: trigger artifact_inspect manually)."));
|
|
2373
|
+
" — artifact build landed, post-deploy finalized, provenance NOT stamped. Parent: run docs/publish-verification.md.");
|
|
2326
2374
|
}
|
|
2327
2375
|
|
|
2328
2376
|
i++;
|
|
@@ -424,7 +424,7 @@ function buildPlaytestDescription(journey, personaName, personaFile, maxFilings)
|
|
|
424
424
|
"\n" +
|
|
425
425
|
"Rules:\n" +
|
|
426
426
|
"- Walk the journey end to end from a user's perspective, wearing the persona.\n" +
|
|
427
|
-
"- Use the normal QA surface for this project (
|
|
427
|
+
"- Use the normal QA surface for this project (the published artifact's own user interface for artifact projects; read-only dashboard API checks otherwise). Note: there is currently no agent-callable visual-inspection tool (artifact_inspect was removed by the platform 2026-09-14; artifact.inspect is malfunction diagnosis, not a substitute) — judge only what you can observe directly.\n" +
|
|
428
428
|
"- There is no code change under test: skip change-verification steps (provenance / publish checks) and judge only what you observe.\n" +
|
|
429
429
|
"- " + filingRule + "\n" +
|
|
430
430
|
"- File via the existing createtask action with filed_by \"hazel\": set workflow to \"standard\" when the fix is clear feature work, or omit workflow (untriaged) when unsure — triage routes untriaged filings to bugfix/feature as usual.\n"
|
package/workflows/docs.js
CHANGED
|
@@ -393,7 +393,7 @@ function describeWorkAgentFailure(stepName, identity, attempts) {
|
|
|
393
393
|
|
|
394
394
|
var instructions = "";
|
|
395
395
|
if (step.name === "Triage") {
|
|
396
|
-
instructions = "Validate the task,
|
|
396
|
+
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, confirm the docs workflow assignment.\nReport back in plain prose — your assessment.";
|
|
397
397
|
} else if (step.name === "Write") {
|
|
398
398
|
instructions = "Write or revise the documentation the task asks for.\nFollow Tate's voice — clear, conversational, no jargon unless it earns its place.\nDo your work in the project repository at " + REPO_PATH + " — all doc files go there, not under the crew home." +
|
|
399
399
|
(rejectionNotes ? "\n\nREWORK after review rejection. Address:\n" + rejectionNotes : "") +
|