@tea-agent/loop-agent 0.38.0 → 0.38.2

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.
Files changed (147) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/dist/application/dag/generate-task-dag.js +9 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/commands/init-upgrade.js +97 -19
  5. package/dist/executors/dag-pi-executor.js +42 -4
  6. package/dist/executors/shell-executor.js +269 -15
  7. package/dist/infrastructure/harness/atomic-write.js +2 -1
  8. package/dist/shared/openspec-spec.js +76 -0
  9. package/dist/shared/operator/capabilities.js +149 -27
  10. package/dist/task/config-types.js +43 -0
  11. package/dist/task/frontend-project-capability.js +292 -38
  12. package/dist/worker/console/chat/deferred-turn.js +91 -0
  13. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  14. package/dist/worker/console/chat/pi-runtime.js +165 -83
  15. package/dist/worker/console/chat/repo-walk.js +10 -1
  16. package/dist/worker/console/chat/routes.js +477 -55
  17. package/dist/worker/console/chat/semantic-activity.js +20 -8
  18. package/dist/worker/console/chat/session-store.js +146 -2
  19. package/dist/worker/console/chat/turn-process.js +17 -30
  20. package/dist/worker/console/harness-pi-model-matrix.js +137 -0
  21. package/dist/worker/console/operator-actions.js +128 -4
  22. package/dist/worker/console/operator-user-error.js +19 -0
  23. package/dist/worker/console/pi-readiness.js +8 -1
  24. package/dist/worker/console/recovery-cta.js +4 -4
  25. package/dist/worker/console/recovery-error-copy.js +4 -4
  26. package/dist/worker/console/security.js +20 -1
  27. package/dist/worker/console/server.js +6 -1
  28. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-C4bmo3iY.js} +1 -1
  29. package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-SAD7McOS.js} +1 -1
  30. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DAVLUdK1.js} +1 -1
  31. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-CPPdv4vj.js} +1 -1
  32. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-D1GRGZax.js} +1 -1
  33. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +1 -0
  34. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-D_Bq2ZqK.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-CPLVtamK.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-DnaZWcL3.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BRTmFgnq.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BRlfYZoH.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-CjNybD4u.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-D3e-74fG.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Br3ir6f2.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-1NgVuRbh.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-BTYuvuWN.js} +1 -1
  44. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +1 -0
  45. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +1 -0
  46. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-BC6Bgosf.js} +1 -1
  47. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DV-HypfL.js} +1 -1
  48. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DhTaddO-.js} +1 -1
  49. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-BnEcOmiI.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-xphGRVKp.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CrdD1mRG.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-D4YdLsaF.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-LCKyGZ5U.js} +1 -1
  54. package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-BeiTe7t0.js} +1 -1
  55. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BO2cq8GS.js} +1 -1
  56. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-CmWww3Nt.js} +1 -1
  57. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-C2kkUYg5.js} +1 -1
  58. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-DY_JZ2iQ.js} +1 -1
  59. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-BizhCSw9.js} +1 -1
  60. package/dist/worker/console/static/assets/index-BXN89VPX.js +325 -0
  61. package/dist/worker/console/static/assets/index-CuH5CNFu.css +1 -0
  62. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-Dlo2eK_d.js} +1 -1
  63. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js} +1 -1
  64. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DiMDe5Yh.js} +1 -1
  65. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-sN553oIN.js} +1 -1
  66. package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-D9Py31Ld.js} +1 -1
  67. package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-D7Occzk_.js} +5 -5
  68. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-DjDRboiR.js} +1 -1
  69. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Cgc4qlzp.js} +1 -1
  70. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-DxZj8pKd.js} +1 -1
  71. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CJ50MngR.js} +1 -1
  72. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-BY-OD8Tr.js} +1 -1
  73. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-CQFButfK.js} +1 -1
  74. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BB_QUK_b.js} +1 -1
  75. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CqhXZjyx.js} +1 -1
  76. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-2l1U4rgJ.js} +1 -1
  77. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-BX1FbrhJ.js} +1 -1
  78. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +1 -0
  79. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DOSnT7hb.js} +2 -2
  80. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +8 -0
  81. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DVJQA7AN.js} +1 -1
  82. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-74uxl9gx.js} +1 -1
  83. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-DsBxlcg_.js} +1 -1
  84. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-snuW9Mjw.js} +1 -1
  85. package/dist/worker/console/static/index.html +2 -2
  86. package/dist/worker/console/static-src/app/console-types.js +39 -0
  87. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  88. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  89. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +82 -22
  90. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
  91. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  92. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  93. package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
  94. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +18 -0
  95. package/dist/worker/console/static-src/operator-chat/useChatStream.js +216 -10
  96. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  97. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  98. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
  99. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  100. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  101. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  102. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  103. package/dist/workflows/dag/backend-test-layout.js +133 -0
  104. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  105. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  106. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  107. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  108. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  109. package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
  110. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  111. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  112. package/dist/workflows/dag/convergence/controller.js +141 -0
  113. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  114. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  115. package/dist/workflows/dag/failure-routing.js +9 -4
  116. package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
  117. package/dist/workflows/dag/frontend-plan-render.js +90 -0
  118. package/dist/workflows/dag/frontend-prewrite-gate.js +479 -28
  119. package/dist/workflows/dag/init-hybrid.js +920 -110
  120. package/dist/workflows/dag/lifecycle.js +4 -0
  121. package/dist/workflows/dag/node-execution.js +77 -8
  122. package/dist/workflows/dag/prompt.js +45 -0
  123. package/dist/workflows/dag/report.js +6 -0
  124. package/dist/workflows/dag/rerun-feedback.js +212 -0
  125. package/dist/workflows/dag/rerun-plan.js +93 -27
  126. package/dist/workflows/dag/rerun-run.js +3 -0
  127. package/dist/workflows/dag/rerun-task.js +61 -2
  128. package/dist/workflows/dag/retry-policy.js +33 -0
  129. package/dist/workflows/dag/types.js +148 -4
  130. package/dist/workflows/dag/validate.js +7 -2
  131. package/docs/templates/backend-test-dag.json +60 -35
  132. package/docs/templates/frontend-design-contract.md +40 -14
  133. package/docs/templates/frontend-implementation-contract.schema.json +9 -2
  134. package/docs/templates/frontend-task-constraints.md +40 -13
  135. package/docs/templates/frontend-task-requirement.md +54 -20
  136. package/harness.json +3 -1
  137. package/package.json +1 -1
  138. package/skills/frontend-design-review/references/review-checklist.md +2 -0
  139. package/skills/frontend-implementation/references/design-spec.md +16 -19
  140. package/skills/frontend-implementation/references/node-contracts.md +15 -11
  141. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  142. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  143. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  144. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  145. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  147. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
@@ -21,6 +21,17 @@ const REVIEW_VERDICT_NODE_ID = "review-verdict-recovery-pi";
21
21
  const INITIAL_REVIEW_NODE_ID = "initial-review-pi";
22
22
  const INITIAL_REVIEW_GATE_NODE_ID = "initial-review-gate-shell";
23
23
  const INITIAL_REVIEW_PASS_NODE_ID = "initial-review-pass-shell";
24
+ /**
25
+ * Standard-dag governance verify chain ids. When the convergence chain
26
+ * observes the governance standard gate, a legitimate verify-pi
27
+ * `request-revision` re-enters a bounded implement-repair loop instead of
28
+ * terminating the run at the gate, mirroring the review-chain re-entry
29
+ * semantics (AC3/AC4). Protocol/provider/safety failures on verify-pi keep
30
+ * failing closed; only a safe `request-revision` drives code repair here.
31
+ */
32
+ const GOVERNANCE_VERIFY_GATE_NODE_ID = "governance-standard-gate-shell";
33
+ const GOVERNANCE_VERIFY_NODE_ID = "verify-pi";
34
+ const GOVERNANCE_VERIFY_WRITER_NODE_ID = "implement-pi";
24
35
  // Automatic code repair is authorized only for an ordinary command/test
25
36
  // failure. Unknown, protocol, validation, provider, safety, governance, and
26
37
  // human-decision categories all fail closed instead of reaching a writer.
@@ -57,6 +68,15 @@ export async function runConvergencePassController(input) {
57
68
  }
58
69
  const chain = getConvergenceChain(input.spec);
59
70
  const observesReview = chain.includes(REVIEW_GATE_NODE_ID);
71
+ const observesGovernanceVerify = chain.includes(GOVERNANCE_VERIFY_GATE_NODE_ID);
72
+ // Standard-dag governance verify convergence: verify-pi drives a bounded
73
+ // implement-repair loop on `request-revision`. This shape has no
74
+ // hard-verify/review nodes; dispatch before the legacy hard-verify logic so
75
+ // the standard governance DAG never falls into `unsupported-dag-shape`/
76
+ // `non-retry-failure` dead ends.
77
+ if (observesGovernanceVerify && !observesReview) {
78
+ return handleGovernanceVerifyConvergence(input);
79
+ }
60
80
  if (input.tasksById.has(INITIAL_REVIEW_NODE_ID)) {
61
81
  const initialReview = input.state.nodes[INITIAL_REVIEW_NODE_ID];
62
82
  const initialGate = input.state.nodes[INITIAL_REVIEW_GATE_NODE_ID];
@@ -397,6 +417,127 @@ async function handleReviewRequestRevision(input, reviewGate) {
397
417
  function getConvergenceChain(spec) {
398
418
  return spec.convergence?.chainNodeIds ?? DEFAULT_CONVERGENCE_CHAIN_NODE_IDS;
399
419
  }
420
+ /**
421
+ * Legitimate governance verify revision: verify-pi finished and emitted
422
+ * `request-revision`. No secondary verdict-recovery node exists on the
423
+ * standard dag, so the single verify-pi verdict plus a non-transport failure
424
+ * category is the legitimacy bar (protocol-invalid/invalid-output/timeout on
425
+ * verify-pi fail closed instead of driving repair).
426
+ */
427
+ function isLegitimateGovernanceVerifyRevision(state) {
428
+ const verifyNode = state.nodes[GOVERNANCE_VERIFY_NODE_ID];
429
+ return (verifyNode?.status === "FINISHED" &&
430
+ parseProcessVerdict(verifyNode) === "request-revision");
431
+ }
432
+ function isLegitimateGovernanceVerifyPass(state) {
433
+ const verifyNode = state.nodes[GOVERNANCE_VERIFY_NODE_ID];
434
+ return (verifyNode?.status === "FINISHED" &&
435
+ parseProcessVerdict(verifyNode) === "pass");
436
+ }
437
+ /**
438
+ * Standard-dag governance verify convergence loop. Terminal pass when
439
+ * verify-pi passes; bounded re-entry (reset implement-pi + verify chain) when
440
+ * verify-pi legitimately requests revision. The reset lets the next
441
+ * implement-pi pass consume the prior verify findings through the structured
442
+ * convergence feedback block (node-execution injects it for implementer
443
+ * roles), so `already-satisfied` no-ops are explicitly contradicted by the
444
+ * preserved verify-pi evidence refs.
445
+ */
446
+ async function handleGovernanceVerifyConvergence(input) {
447
+ const convergence = input.state.convergence;
448
+ const currentPass = convergence.currentPass || 1;
449
+ const verifyNode = input.state.nodes[GOVERNANCE_VERIFY_NODE_ID];
450
+ const verifyShell = input.state.nodes["verify-shell"];
451
+ // Hard verify commands failed: not a governance verdict issue. Without a
452
+ // supervised repair chain, this stays terminal so the failure-aware
453
+ // closeout reports it truthfully.
454
+ if (verifyShell?.status === "ERROR") {
455
+ const passRecord = await buildConvergencePassRecord({
456
+ pass: currentPass,
457
+ status: "terminal",
458
+ reason: "hard-verify-failed",
459
+ state: input.state,
460
+ runDir: input.runDir,
461
+ spec: input.spec,
462
+ });
463
+ convergence.passHistory.push(passRecord);
464
+ convergence.terminalReason = "hard-verify-failed";
465
+ await input.persistState();
466
+ return { retry: false };
467
+ }
468
+ if (isLegitimateGovernanceVerifyPass(input.state)) {
469
+ convergence.terminalReason = "hard-verify-pass";
470
+ await appendConvergenceKnowledgePattern({
471
+ cwd: input.cwd,
472
+ state: input.state,
473
+ });
474
+ return { retry: false };
475
+ }
476
+ const verifyFailureCategory = verifyNode?.failureCategory;
477
+ const legitimateRevision = isLegitimateGovernanceVerifyRevision(input.state);
478
+ if (!legitimateRevision ||
479
+ (verifyFailureCategory &&
480
+ REVIEW_SOURCE_NON_RETRY_FAILURES.has(verifyFailureCategory))) {
481
+ const passRecord = await buildConvergencePassRecord({
482
+ pass: currentPass,
483
+ status: "terminal",
484
+ reason: "non-retry-failure",
485
+ state: input.state,
486
+ runDir: input.runDir,
487
+ spec: input.spec,
488
+ });
489
+ convergence.passHistory.push(passRecord);
490
+ convergence.terminalReason = "non-retry-failure";
491
+ await input.persistState();
492
+ return { retry: false };
493
+ }
494
+ const passRecord = await buildConvergencePassRecord({
495
+ pass: currentPass,
496
+ status: "retrying",
497
+ reason: "verify-request-revision",
498
+ state: input.state,
499
+ runDir: input.runDir,
500
+ spec: input.spec,
501
+ });
502
+ if (currentPass >= convergence.maxPasses) {
503
+ passRecord.status = "terminal";
504
+ passRecord.reason = "max-passes";
505
+ convergence.passHistory.push(passRecord);
506
+ convergence.terminalReason = "max-passes";
507
+ await input.persistState();
508
+ return { retry: false };
509
+ }
510
+ convergence.passHistory.push(passRecord);
511
+ convergence.currentPass = currentPass + 1;
512
+ // Reset the writer plus the governance verify chain: the next implement-pi
513
+ // pass must consume the preserved verify-pi findings (injected as
514
+ // convergence feedback) instead of returning already-satisfied.
515
+ for (const id of collectGovernanceVerifyResetIds(input.spec)) {
516
+ const task = input.tasksById.get(id);
517
+ if (!task)
518
+ continue;
519
+ input.state.nodes[id] = freshNodeRecord(task);
520
+ }
521
+ await input.persistState();
522
+ return { retry: true };
523
+ }
524
+ /**
525
+ * Reset set for the governance verify loop: the exclusive writer and the
526
+ * verify chain closure (verify-shell, verify-pi, governance gate, and every
527
+ * transitive descendant so closeout re-adjudicates from fresh evidence).
528
+ */
529
+ function collectGovernanceVerifyResetIds(spec) {
530
+ const resetIds = new Set([
531
+ GOVERNANCE_VERIFY_WRITER_NODE_ID,
532
+ "verify-shell",
533
+ GOVERNANCE_VERIFY_NODE_ID,
534
+ GOVERNANCE_VERIFY_GATE_NODE_ID,
535
+ ]);
536
+ for (const id of collectTransitiveDescendantTaskIds(spec, GOVERNANCE_VERIFY_GATE_NODE_ID)) {
537
+ resetIds.add(id);
538
+ }
539
+ return resetIds;
540
+ }
400
541
  function hasConvergenceChain(tasksById, spec) {
401
542
  const chain = getConvergenceChain(spec);
402
543
  return chain.every((id) => tasksById.has(id));
@@ -367,19 +367,26 @@ export async function executeDynamicMapExpansion(input) {
367
367
  }
368
368
  }
369
369
  const budgetBlockedIds = new Set(blockedChildren.map((entry) => entry.nodeId));
370
+ const failOnTokenBudgetExhaustion = input.expansion.failOnTokenBudgetExhaustion === true;
370
371
  const failedChildren = tolerateChildFailures
371
372
  ? []
372
373
  : childNodeIds.filter((nodeId) => {
373
374
  const record = input.state.nodes[nodeId];
374
375
  if (!record || record.status === "FINISHED")
375
376
  return false;
376
- if (budgetBlockedIds.has(nodeId) || isBudgetBlockedRecord(record))
377
- return false;
377
+ if (budgetBlockedIds.has(nodeId) || isBudgetBlockedRecord(record)) {
378
+ return failOnTokenBudgetExhaustion;
379
+ }
378
380
  return true;
379
381
  });
382
+ const finishedItemCount = childNodeIds.filter((nodeId) => input.state.nodes[nodeId]?.status === "FINISHED").length;
383
+ const blockedItemCount = budgetBlockedIds.size;
380
384
  const aggregate = {
381
385
  workflowNodeId: input.expansion.workflowNodeId,
382
386
  itemCount: items.length,
387
+ expectedItemCount: items.length,
388
+ finishedItemCount,
389
+ blockedItemCount,
383
390
  children: childNodeIds.map((nodeId, index) => ({
384
391
  nodeId,
385
392
  item: items[index],
@@ -399,21 +406,30 @@ export async function executeDynamicMapExpansion(input) {
399
406
  ?.reason,
400
407
  })),
401
408
  tokensUsed: totalTokensUsed,
409
+ maxTotalTokens: tokenBudget?.maxTotalTokens,
402
410
  caseOutcomeNotes: caseOutcomeNotes.length > 0 ? caseOutcomeNotes : undefined,
403
411
  };
412
+ const tokenBudgetFailure = failOnTokenBudgetExhaustion && blockedItemCount > 0;
404
413
  return {
405
- // With tolerateChildFailures: barrier always succeeds; product outcomes live in
406
- // case evidence + result materialization. Without it: real child failures fail-close.
407
- ok: failedChildren.length === 0,
414
+ // With tolerateChildFailures: barrier succeeds and product outcomes live in
415
+ // case evidence. Backend-test opts into token-budget fail-closed because a
416
+ // partial generated module set is never a valid downstream input.
417
+ ok: failedChildren.length === 0 && !tokenBudgetFailure,
408
418
  stdout: JSON.stringify(aggregate),
409
419
  stderr: caseOutcomeNotes.length > 0
410
420
  ? `map children recorded as case outcomes: ${caseOutcomeNotes
411
421
  .map((entry) => `${entry.nodeId}=${entry.status}:${entry.reason}`)
412
422
  .join(", ")}`
423
+ : tokenBudgetFailure
424
+ ? `dynamic map token budget exhausted: expected=${items.length} finished=${finishedItemCount} blocked=${blockedItemCount} tokensUsed=${totalTokensUsed} maxTotalTokens=${tokenBudget?.maxTotalTokens ?? "unbounded"}`
425
+ : failedChildren.length > 0
426
+ ? `dynamic map children failed: ${failedChildren.join(", ")}`
427
+ : "",
428
+ failureCategory: tokenBudgetFailure
429
+ ? "dynamic-expansion-token-budget-exhausted"
413
430
  : failedChildren.length > 0
414
- ? `dynamic map children failed: ${failedChildren.join(", ")}`
415
- : "",
416
- failureCategory: failedChildren.length > 0 ? "dynamic-expansion-child-failed" : "success",
431
+ ? "dynamic-expansion-child-failed"
432
+ : "success",
417
433
  durationMs: Date.now() - started,
418
434
  };
419
435
  }
@@ -30,7 +30,7 @@ export function getNodeOutputAsJson(state, nodeId) {
30
30
  /** Normalize a line the same way verdict gates do (strip whole-line Markdown emphasis). */
31
31
  export function normalizeVerdictCandidateLine(value) {
32
32
  const trimmed = String(value).trim();
33
- const emphasized = trimmed.match(/^\*{1,3}\s*(VERDICT:[^*]+?)\s*\*{1,3}$/);
33
+ const emphasized = trimmed.match(/^(?:`{1,3}|\*{1,3})\s*(VERDICT:[^`*]+?)\s*(?:`{1,3}|\*{1,3})$/);
34
34
  const line = (emphasized ? emphasized[1] : trimmed).trim();
35
35
  // Canonicalize the colon separator and trailing sentence punctuation so
36
36
  // "VERDICT:pass", "VERDICT: pass", or "VERDICT: pass." match the declared
@@ -44,7 +44,9 @@ function routeToProductLine(input) {
44
44
  case "decision-envelope":
45
45
  return "NeedsHuman";
46
46
  case "skipped":
47
- return "DependencyFailure";
47
+ return input.skippedReason === "frontend-prewrite-not-authorized"
48
+ ? "ContractMismatch"
49
+ : "DependencyFailure";
48
50
  case "shell-command":
49
51
  if (/\b(ENOENT|PATH|command not found|No such file|not found in PATH|bash: .*: No such file)\b/i.test(raw)) {
50
52
  return "EnvFailure";
@@ -90,9 +92,12 @@ export function routeDagFailure(input) {
90
92
  if (!productLineFailureCategory)
91
93
  return {};
92
94
  const recommendedFollowUp = productLineFailureCategory === "ContractMismatch" &&
93
- isFrontendDesignGateNode(input.nodeId ?? "")
94
- ? "frontend-plan-revision-and-rerun"
95
- : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
95
+ input.skippedReason === "frontend-prewrite-not-authorized"
96
+ ? "fix-frontend-task-contract-and-regenerate-dag"
97
+ : productLineFailureCategory === "ContractMismatch" &&
98
+ isFrontendDesignGateNode(input.nodeId ?? "")
99
+ ? "frontend-plan-revision-and-rerun"
100
+ : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
96
101
  return {
97
102
  productLineFailureCategory,
98
103
  recommendedFollowUp,