muse-crew 0.14.2 → 0.14.3

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.
@@ -416,7 +416,10 @@ function hydrateReleaseDecision(rec) {
416
416
  // fabricated from the diff; it must match the artifact's real content.
417
417
 
418
418
  // See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
419
- async function recordPublishLedger(entry, rework) {
419
+ // keyTag (optional): same-outcome ledger writes in one pass need distinct
420
+ // agent-call keys — the runtime replays cached calls by key, so a shared key
421
+ // silently drops the second write (2026-09-19, submitted-on-issuance).
422
+ async function recordPublishLedger(entry, rework, keyTag) {
420
423
  try {
421
424
  var ledgerDir = crewHome + "/.publish-ledger";
422
425
  var line = JSON.stringify({
@@ -438,7 +441,7 @@ async function recordPublishLedger(entry, rework) {
438
441
  "Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
439
442
  " | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
440
443
  "Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
441
- { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
444
+ { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
442
445
  label: "Recording publish attempt in ledger",
443
446
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
444
447
  );
@@ -1289,7 +1292,7 @@ while (i < STEPS.length) {
1289
1292
  "The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
1290
1293
  "- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
1291
1294
  "- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
1292
- "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash, VERDICT: PASS; PUSH_SKIPPED — the detached line is room-local (the merge commit + record are the evidence), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1295
+ "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1293
1296
  "- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
1294
1297
  "- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
1295
1298
  "RESOLUTION:\n" +
@@ -1302,7 +1305,7 @@ while (i < STEPS.length) {
1302
1305
  "ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
1303
1306
  "- ERROR: report it. VERDICT: FAIL.\n\n" +
1304
1307
  "R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
1305
- "PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED or NO_REMOTE_PUSH — nothing to push, VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1308
+ "PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1306
1309
  "NEVER force-push.\n\n" +
1307
1310
  "Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1308
1311
 
@@ -1640,7 +1643,7 @@ while (i < STEPS.length) {
1640
1643
  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");
1641
1644
  }
1642
1645
  // See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
1643
- var rebuildTrigger = null;
1646
+ var rebuildTrigger = null, triggerThrew = false;
1644
1647
  try {
1645
1648
  var triggerText = String(await agent(rebuildPrompt,
1646
1649
  { key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
@@ -1660,7 +1663,18 @@ while (i < STEPS.length) {
1660
1663
  }
1661
1664
  } catch (triggerErr) {
1662
1665
  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");
1666
+ triggerThrew = true;
1663
1667
  }
1668
+ // (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
1669
+ await recordPublishLedger({
1670
+ commit: mergeCommitForPublish,
1671
+ attempt: rebuildAttemptKey,
1672
+ agent_id: null,
1673
+ applied_report: publishAppliedObservation,
1674
+ manifest_before: preTriggerManifest,
1675
+ outcome: "submitted",
1676
+ detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
1677
+ }, totalReworkCount, "issuance");
1664
1678
  // Post-trigger observation (primary, not fallback): the workflow
1665
1679
  // attributes the edit itself. First the in-flight build state — a
1666
1680
  // build whose agent_id is new relative to the pre-trigger baseline
@@ -1780,8 +1794,8 @@ while (i < STEPS.length) {
1780
1794
  applied_report: publishAppliedObservation,
1781
1795
  manifest_before: preTriggerManifest,
1782
1796
  outcome: "submitted",
1783
- detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
1784
- }, totalReworkCount);
1797
+ detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
1798
+ }, totalReworkCount, "receipt");
1785
1799
  } else {
1786
1800
  var newAuditDirs = [];
1787
1801
  try {
@@ -1912,10 +1926,6 @@ while (i < STEPS.length) {
1912
1926
  // its outcome — submitted, rejected, or unknown (unknown is recorded
1913
1927
  // at the park site above). A later run or human matches commit hash +
1914
1928
  // attempt key against the builder's eventual completion.
1915
- // (2026-09-16) The trigger is fire-and-forget: the observation above
1916
- // already recorded the ledger's submitted line on both positive paths
1917
- // and parked on unknown — there is no applied report to observe and
1918
- // no rejection signal to record.
1919
1929
  // (2026-09-18, H2 verdict-first) The verdict was decided exactly
1920
1930
  // once above; dispatch on it. landed bypasses the receipt poll
1921
1931
  // (the audit evidence already proved completion); an explicit
@@ -2049,21 +2059,12 @@ while (i < STEPS.length) {
2049
2059
  log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
2050
2060
  } else {
2051
2061
  // STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
2052
- // the poll above only observes IN-FLIGHT builds. A build that
2053
- // finished between the receipt capture and the poll's first check
2054
- // leaves no in-flight trace — but the platform's audit harness
2055
- // leaves a durable one (~/workspace/ts-spaces/<slug>/audits/
2056
- // <timestamp>-<id>/ per completed build). Diff the audit-dir
2057
- // listing against the pre-trigger snapshot: a timestamped dir
2058
- // that appeared during the attempt window is evidence a build
2059
- // completed. Attribution is by window, not by build identity:
2060
- // the poll's saw_stranger signal only catches stranger builds in
2061
- // flight AT a check — a stranger that finished entirely inside
2062
- // the window is indistinguishable, so any observed stranger
2063
- // blocks attribution and the outcome stays unknown. This never
2064
- // re-issues the edit and never stamps provenance — ok=true only
2065
- // routes to the parent's independent content read-back, which
2066
- // remains the real verification.
2062
+ // the poll observes only in-flight builds; a build that finished
2063
+ // inside the window leaves a durable audit dir. Diff the audit
2064
+ // listing against the pre-trigger snapshot: a new timestamped dir
2065
+ // is evidence a build completed. Attribution is by window, not by build identity: any
2066
+ // observed stranger blocks it. Never re-issues, never stamps;
2067
+ // ok=true routes to the parent's read-back (the verification).
2067
2068
  //
2068
2069
  // The poll end-state is read from the poll's own observations,
2069
2070
  // not from build_done alone: a build in flight at the last check
@@ -475,7 +475,10 @@ function hydrateReleaseDecision(rec) {
475
475
  // fabricated from the diff; it must match the artifact's real content.
476
476
 
477
477
  // See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
478
- async function recordPublishLedger(entry, rework) {
478
+ // keyTag (optional): same-outcome ledger writes in one pass need distinct
479
+ // agent-call keys — the runtime replays cached calls by key, so a shared key
480
+ // silently drops the second write (2026-09-19, submitted-on-issuance).
481
+ async function recordPublishLedger(entry, rework, keyTag) {
479
482
  try {
480
483
  var ledgerDir = crewHome + "/.publish-ledger";
481
484
  var line = JSON.stringify({
@@ -497,7 +500,7 @@ async function recordPublishLedger(entry, rework) {
497
500
  "Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
498
501
  " | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
499
502
  "Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
500
- { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
503
+ { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
501
504
  label: "Recording publish attempt in ledger",
502
505
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
503
506
  );
@@ -1195,7 +1198,7 @@ while (i < STEPS.length) {
1195
1198
  "The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
1196
1199
  "- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
1197
1200
  "- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
1198
- "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash, VERDICT: PASS; PUSH_SKIPPED — the detached line is room-local (the merge commit + record are the evidence), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1201
+ "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1199
1202
  "- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
1200
1203
  "- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
1201
1204
  "RESOLUTION:\n" +
@@ -1208,7 +1211,7 @@ while (i < STEPS.length) {
1208
1211
  "ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
1209
1212
  "- ERROR: report it. VERDICT: FAIL.\n\n" +
1210
1213
  "R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
1211
- "PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED or NO_REMOTE_PUSH — nothing to push, VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1214
+ "PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1212
1215
  "NEVER force-push.\n\n" +
1213
1216
  "Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1214
1217
 
@@ -1546,7 +1549,7 @@ while (i < STEPS.length) {
1546
1549
  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");
1547
1550
  }
1548
1551
  // See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
1549
- var rebuildTrigger = null;
1552
+ var rebuildTrigger = null, triggerThrew = false;
1550
1553
  try {
1551
1554
  var triggerText = String(await agent(rebuildPrompt,
1552
1555
  { key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
@@ -1566,7 +1569,18 @@ while (i < STEPS.length) {
1566
1569
  }
1567
1570
  } catch (triggerErr) {
1568
1571
  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");
1572
+ triggerThrew = true;
1569
1573
  }
1574
+ // (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
1575
+ await recordPublishLedger({
1576
+ commit: mergeCommitForPublish,
1577
+ attempt: rebuildAttemptKey,
1578
+ agent_id: null,
1579
+ applied_report: publishAppliedObservation,
1580
+ manifest_before: preTriggerManifest,
1581
+ outcome: "submitted",
1582
+ detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
1583
+ }, reworkCount, "issuance");
1570
1584
  // Post-trigger observation (primary, not fallback): the workflow
1571
1585
  // attributes the edit itself. First the in-flight build state — a
1572
1586
  // build whose agent_id is new relative to the pre-trigger baseline
@@ -1686,8 +1700,8 @@ while (i < STEPS.length) {
1686
1700
  applied_report: publishAppliedObservation,
1687
1701
  manifest_before: preTriggerManifest,
1688
1702
  outcome: "submitted",
1689
- detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
1690
- }, reworkCount);
1703
+ detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
1704
+ }, reworkCount, "receipt");
1691
1705
  } else {
1692
1706
  var newAuditDirs = [];
1693
1707
  try {
@@ -1818,10 +1832,6 @@ while (i < STEPS.length) {
1818
1832
  // its outcome — submitted, rejected, or unknown (unknown is recorded
1819
1833
  // at the park site above). A later run or human matches commit hash +
1820
1834
  // attempt key against the builder's eventual completion.
1821
- // (2026-09-16) The trigger is fire-and-forget: the observation above
1822
- // already recorded the ledger's submitted line on both positive paths
1823
- // and parked on unknown — there is no applied report to observe and
1824
- // no rejection signal to record.
1825
1835
  // (2026-09-18, H2 verdict-first) The verdict was decided exactly
1826
1836
  // once above; dispatch on it. landed bypasses the receipt poll
1827
1837
  // (the audit evidence already proved completion); an explicit
@@ -1955,21 +1965,12 @@ while (i < STEPS.length) {
1955
1965
  log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
1956
1966
  } else {
1957
1967
  // STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
1958
- // the poll above only observes IN-FLIGHT builds. A build that
1959
- // finished between the receipt capture and the poll's first check
1960
- // leaves no in-flight trace — but the platform's audit harness
1961
- // leaves a durable one (~/workspace/ts-spaces/<slug>/audits/
1962
- // <timestamp>-<id>/ per completed build). Diff the audit-dir
1963
- // listing against the pre-trigger snapshot: a timestamped dir
1964
- // that appeared during the attempt window is evidence a build
1965
- // completed. Attribution is by window, not by build identity:
1966
- // the poll's saw_stranger signal only catches stranger builds in
1967
- // flight AT a check — a stranger that finished entirely inside
1968
- // the window is indistinguishable, so any observed stranger
1969
- // blocks attribution and the outcome stays unknown. This never
1970
- // re-issues the edit and never stamps provenance — ok=true only
1971
- // routes to the parent's independent content read-back, which
1972
- // remains the real verification.
1968
+ // the poll observes only in-flight builds; a build that finished
1969
+ // inside the window leaves a durable audit dir. Diff the audit
1970
+ // listing against the pre-trigger snapshot: a new timestamped dir
1971
+ // is evidence a build completed. Attribution is by window, not by build identity: any
1972
+ // observed stranger blocks it. Never re-issues, never stamps;
1973
+ // ok=true routes to the parent's read-back (the verification).
1973
1974
  //
1974
1975
  // The poll end-state is read from the poll's own observations,
1975
1976
  // not from build_done alone: a build in flight at the last check
@@ -428,7 +428,10 @@ function hydrateReleaseDecision(rec) {
428
428
  // it must match the artifact's real content.
429
429
 
430
430
  // See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
431
- async function recordPublishLedger(entry, rework) {
431
+ // keyTag (optional): same-outcome ledger writes in one pass need distinct
432
+ // agent-call keys — the runtime replays cached calls by key, so a shared key
433
+ // silently drops the second write (2026-09-19, submitted-on-issuance).
434
+ async function recordPublishLedger(entry, rework, keyTag) {
432
435
  try {
433
436
  var ledgerDir = crewHome + "/.publish-ledger";
434
437
  var line = JSON.stringify({
@@ -450,7 +453,7 @@ async function recordPublishLedger(entry, rework) {
450
453
  "Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
451
454
  " | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
452
455
  "Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
453
- { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
456
+ { key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
454
457
  label: "Recording publish attempt in ledger",
455
458
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
456
459
  );
@@ -1182,7 +1185,7 @@ while (i < STEPS.length) {
1182
1185
  "The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
1183
1186
  "- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
1184
1187
  "- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
1185
- "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash, VERDICT: PASS; PUSH_SKIPPED — the detached line is room-local (the merge commit + record are the evidence), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1188
+ "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1186
1189
  "- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
1187
1190
  "- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
1188
1191
  "RESOLUTION:\n" +
@@ -1195,7 +1198,7 @@ while (i < STEPS.length) {
1195
1198
  "ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
1196
1199
  "- ERROR: report it. VERDICT: FAIL.\n\n" +
1197
1200
  "R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
1198
- "PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED or NO_REMOTE_PUSH — nothing to push, VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1201
+ "PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1199
1202
  "NEVER force-push.\n\n" +
1200
1203
  "Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1201
1204
 
@@ -1533,7 +1536,7 @@ while (i < STEPS.length) {
1533
1536
  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");
1534
1537
  }
1535
1538
  // See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
1536
- var rebuildTrigger = null;
1539
+ var rebuildTrigger = null, triggerThrew = false;
1537
1540
  try {
1538
1541
  var triggerText = String(await agent(rebuildPrompt,
1539
1542
  { key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
@@ -1553,7 +1556,18 @@ while (i < STEPS.length) {
1553
1556
  }
1554
1557
  } catch (triggerErr) {
1555
1558
  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");
1559
+ triggerThrew = true;
1556
1560
  }
1561
+ // (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
1562
+ await recordPublishLedger({
1563
+ commit: mergeCommitForPublish,
1564
+ attempt: rebuildAttemptKey,
1565
+ agent_id: null,
1566
+ applied_report: publishAppliedObservation,
1567
+ manifest_before: preTriggerManifest,
1568
+ outcome: "submitted",
1569
+ detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
1570
+ }, totalReworkCount, "issuance");
1557
1571
  // Post-trigger observation (primary, not fallback): the workflow
1558
1572
  // attributes the edit itself. First the in-flight build state — a
1559
1573
  // build whose agent_id is new relative to the pre-trigger baseline
@@ -1673,8 +1687,8 @@ while (i < STEPS.length) {
1673
1687
  applied_report: publishAppliedObservation,
1674
1688
  manifest_before: preTriggerManifest,
1675
1689
  outcome: "submitted",
1676
- detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
1677
- }, totalReworkCount);
1690
+ detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
1691
+ }, totalReworkCount, "receipt");
1678
1692
  } else {
1679
1693
  var newAuditDirs = [];
1680
1694
  try {
@@ -1805,10 +1819,6 @@ while (i < STEPS.length) {
1805
1819
  // its outcome — submitted, rejected, or unknown (unknown is recorded
1806
1820
  // at the park site above). A later run or human matches commit hash +
1807
1821
  // attempt key against the builder's eventual completion.
1808
- // (2026-09-16) The trigger is fire-and-forget: the observation above
1809
- // already recorded the ledger's submitted line on both positive paths
1810
- // and parked on unknown — there is no applied report to observe and
1811
- // no rejection signal to record.
1812
1822
  // (2026-09-18, H2 verdict-first) The verdict was decided exactly
1813
1823
  // once above; dispatch on it. landed bypasses the receipt poll
1814
1824
  // (the audit evidence already proved completion); an explicit
@@ -1942,21 +1952,12 @@ while (i < STEPS.length) {
1942
1952
  log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
1943
1953
  } else {
1944
1954
  // STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
1945
- // the poll above only observes IN-FLIGHT builds. A build that
1946
- // finished between the receipt capture and the poll's first check
1947
- // leaves no in-flight trace — but the platform's audit harness
1948
- // leaves a durable one (~/workspace/ts-spaces/<slug>/audits/
1949
- // <timestamp>-<id>/ per completed build). Diff the audit-dir
1950
- // listing against the pre-trigger snapshot: a timestamped dir
1951
- // that appeared during the attempt window is evidence a build
1952
- // completed. Attribution is by window, not by build identity:
1953
- // the poll's saw_stranger signal only catches stranger builds in
1954
- // flight AT a check — a stranger that finished entirely inside
1955
- // the window is indistinguishable, so any observed stranger
1956
- // blocks attribution and the outcome stays unknown. This never
1957
- // re-issues the edit and never stamps provenance — ok=true only
1958
- // routes to the parent's independent content read-back, which
1959
- // remains the real verification.
1955
+ // the poll observes only in-flight builds; a build that finished
1956
+ // inside the window leaves a durable audit dir. Diff the audit
1957
+ // listing against the pre-trigger snapshot: a new timestamped dir
1958
+ // is evidence a build completed. Attribution is by window, not by build identity: any
1959
+ // observed stranger blocks it. Never re-issues, never stamps;
1960
+ // ok=true routes to the parent's read-back (the verification).
1960
1961
  //
1961
1962
  // The poll end-state is read from the poll's own observations,
1962
1963
  // not from build_done alone: a build in flight at the last check