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.
@@ -529,52 +529,22 @@ function verifyAppliedChanges(expected, applied) {
529
529
  return { ok: true };
530
530
  }
531
531
 
532
- // Publish read-back request builder: the verbatim_request the workflow hands
533
- // to artifact_inspect (via a child) after the artifact build lands. Pure
534
- // function — no I/O, no clock. The request carries the merged diff as the
535
- // expected change and asks for an independent read of the artifact's actual
536
- // source: for each file, the exact current text of the changed regions plus
537
- // a per-line present/absent finding. The parent (docs/publish-verification.md)
538
- // compares these findings against the diff mechanically and stamps provenance
539
- // only on a match. This breaks the circularity that hollowed canary run 8
540
- // (2026-09-11): verifyAppliedChanges compares the builder's applied-report
541
- // against the diff the report was derived from — a fabricated report passes
542
- // by construction. Independent read-back cannot be fabricated from the diff;
543
- // it must match the artifact's real content.
544
- function buildPublishReadbackRequest(taskId, commit, diff, buildAgentId) {
545
- // Build-ID correlation (2026-09-12): buildAgentId is the build.agent_id the
546
- // workflow observed for the publish attempt (the artifact system's in-flight
547
- // build correlation ID — not a durable post-completion identifier). The read-back request carries it so the parent can
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, check 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.";
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 is async and will not return in time.\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;
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 triggers an independent artifact_inspect
1392
- // read-back, records the session completed, and parks with
1393
- // "publish: verification-requested". The parent stamps provenance only
1394
- // after the read-back confirms the content (docs/publish-verification.md);
1395
- // Chore has no QA: the parent's stamp read-back is the final gate.
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 + " && git rev-parse HEAD && echo '---PARENT---' && git rev-parse HEAD^1 && echo '---DIFF---' && git diff HEAD^1 HEAD && echo '---NAMES---' && git diff-tree --no-commit-id --name-only -r HEAD\n" +
1445
- "Return JSON { \"commit\": \"<HEAD trimmed>\", \"parent\": \"<HEAD^1 trimmed>\", \"diff\": \"<raw unified diff, may be multi-line>\", \"files\": \"<newline-separated paths>\" } and nothing else.",
1446
- { key: attemptKey("publish-artifact-diff-" + taskId, reworkCount), label: "Computing merged diff for publish",
1447
- schema: { type: "object", properties: { commit: { type: "string" }, parent: { type: "string" }, diff: { type: "string" }, files: { type: "string" } }, required: ["commit", "diff"] } }
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
- try {
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(mergeParentForPublish) + " && 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" +
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 + " (merge parent " + (mergeParentForPublish || "unknown").slice(0, 12) + "): " + JSON.stringify(expectedBaseHashes));
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
- // after an independent artifact_inspect read-back confirms the
1811
- // artifact's actual content matches the merged diff, the parent
1812
- // stamps provenance and re-queues; QA's provenance check then
1813
- // enforces the stamp mechanically, so an unverified publish fails
1814
- // loudly in QA instead of passing silently here.
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: trigger an independent artifact_inspect read-back of the changed
2198
- // regions here; the parent stamps provenance only after mechanically
2199
- // confirming the artifact's actual content matches the merged diff. QA's
2200
- // provenance check enforces the stamp — an unverified publish fails loudly
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
- var publishVerifyInspect = { triggered: false, inspection_id: "", error: "" };
2208
- if (step.name === "Publish" && PUBLISH_TYPE === "artifact" && PUBLISH_SLUG) {
2209
- if (publishSkippedNoLock) {
2210
- log("Publish skipped for task " + taskId + " (no merge lock held — empty-diff Integrate): content verification vacuous, nothing was shipped");
2211
- } else if (!publishBuildLanded) {
2212
- log("Publish build did not land for task " + taskId + " — no content to verify (the failure park above already fired)");
2213
- } else {
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 (visual inspection via artifact_inspect for artifact projects; read-only dashboard API checks otherwise).\n" +
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, check clarity, confirm the docs workflow assignment.\nReport back in plain prose — your assessment.";
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 : "") +