@tea-agent/loop-agent 0.39.2 → 0.40.0-beta.1

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 (215) hide show
  1. package/CHANGELOG.md +133 -92
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +12 -0
  6. package/dist/cli/program.js +23 -1
  7. package/dist/commands/dag-artifact.js +284 -0
  8. package/dist/commands/dag-context.js +184 -0
  9. package/dist/commands/dag-rerun.js +206 -1
  10. package/dist/executors/dag-pi-executor.js +108 -19
  11. package/dist/executors/pi-executor.js +4 -2
  12. package/dist/executors/pi-sdk-executor.js +49 -1
  13. package/dist/executors/pi-writer-tool-policy.js +57 -1
  14. package/dist/executors/shell-executor.js +42 -2
  15. package/dist/executors/workspace-write-snapshot.js +68 -0
  16. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  17. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  18. package/dist/infrastructure/console/context-export-store.js +160 -0
  19. package/dist/infrastructure/console/dir-lock.js +132 -0
  20. package/dist/infrastructure/harness/artifact-store.js +10 -1
  21. package/dist/infrastructure/harness/atomic-write.js +35 -2
  22. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  23. package/dist/shared/context-transfer.js +418 -0
  24. package/dist/shared/operator/capabilities.js +14 -0
  25. package/dist/shared/operator/safe-run-summary.js +1 -0
  26. package/dist/shared/path-safety.js +93 -0
  27. package/dist/shared/preview.js +28 -4
  28. package/dist/task/config-types.js +10 -0
  29. package/dist/worker/console/chat/chat-event-store.js +77 -7
  30. package/dist/worker/console/chat/context-panel.js +7 -0
  31. package/dist/worker/console/chat/deferred-turn.js +12 -0
  32. package/dist/worker/console/chat/pi-runtime.js +331 -18
  33. package/dist/worker/console/chat/provider-error.js +98 -0
  34. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  35. package/dist/worker/console/chat/routes.js +389 -56
  36. package/dist/worker/console/chat/session-stats.js +179 -0
  37. package/dist/worker/console/chat/session-store.js +94 -64
  38. package/dist/worker/console/chat/todos.js +115 -0
  39. package/dist/worker/console/chat/tool-preview.js +42 -0
  40. package/dist/worker/console/chat/turn-process.js +20 -55
  41. package/dist/worker/console/chat/user-questions.js +266 -0
  42. package/dist/worker/console/chat/workspace-landing.js +79 -0
  43. package/dist/worker/console/console-update-runtime.js +1 -1
  44. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  45. package/dist/worker/console/dag-confirmation.js +1 -1
  46. package/dist/worker/console/dag-execution-receipt.js +1 -1
  47. package/dist/worker/console/doctor.js +1 -1
  48. package/dist/worker/console/draft-store.js +1 -1
  49. package/dist/worker/console/human-gate-token.js +1 -1
  50. package/dist/worker/console/index.js +3 -3
  51. package/dist/worker/console/interview/assessment.js +1 -1
  52. package/dist/worker/console/interview/session.js +1 -1
  53. package/dist/worker/console/native-directory-picker.js +13 -0
  54. package/dist/worker/console/operation-runner.js +45 -4
  55. package/dist/worker/console/operation-sse.js +1 -1
  56. package/dist/worker/console/operation-wait.js +1 -1
  57. package/dist/worker/console/operator-actions.js +104 -3
  58. package/dist/worker/console/operator-surface-health.js +1 -1
  59. package/dist/worker/console/operator-user-error.js +128 -0
  60. package/dist/worker/console/routes.js +927 -8
  61. package/dist/worker/console/security.js +39 -4
  62. package/dist/worker/console/server.js +532 -187
  63. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
  64. package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
  65. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
  66. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
  67. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
  68. package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
  69. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
  78. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
  79. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
  80. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
  81. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
  83. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
  84. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
  89. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
  90. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
  91. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
  92. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
  93. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
  94. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
  95. package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
  96. package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
  97. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
  98. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
  99. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
  100. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
  101. package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
  102. package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
  103. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
  104. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
  105. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
  106. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
  107. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
  108. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
  109. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
  110. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
  111. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
  112. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
  113. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
  114. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
  115. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
  116. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
  117. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
  118. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
  119. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
  120. package/dist/worker/console/static/index.html +7 -2
  121. package/dist/worker/console/static-src/app/console-types.js +29 -4
  122. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  123. package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
  124. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  125. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  126. package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
  127. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  128. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  129. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  130. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  131. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  132. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  133. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  134. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  135. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  136. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  137. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  138. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  139. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  140. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  141. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  142. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  143. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  144. package/dist/worker/console/workspace-context.js +260 -0
  145. package/dist/worker/console/workspace-registry.js +214 -0
  146. package/dist/worker/continuation/worker-continuation.js +278 -0
  147. package/dist/worker/observe/health.js +1 -1
  148. package/dist/worker/observe/node-transparency.js +572 -0
  149. package/dist/worker/observe/routes.js +51 -1
  150. package/dist/worker/observe/server.js +4 -0
  151. package/dist/worker/observe/static/api.js +94 -10
  152. package/dist/worker/observe/static/app.js +18 -70
  153. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  154. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  155. package/dist/worker/observe/static/index.html +1 -1
  156. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  157. package/dist/worker/observe/static/operator-chrome.css +145 -20
  158. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  159. package/dist/worker/observe/static/operator-chrome.js +330 -100
  160. package/dist/worker/observe/static/router.d.ts +46 -0
  161. package/dist/worker/observe/static/router.js +25 -1
  162. package/dist/worker/observe/static/state.d.ts +50 -0
  163. package/dist/worker/observe/static/state.js +175 -1
  164. package/dist/worker/observe/static/styles.css +1039 -128
  165. package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
  166. package/dist/worker/observe/static/views/dag.js +2 -1
  167. package/dist/worker/observe/static/views/dashboard.js +6 -4
  168. package/dist/worker/observe/static/views/night.js +2 -2
  169. package/dist/worker/observe/static/views/pool.js +1 -0
  170. package/dist/worker/observe/static/views/run.js +3 -1
  171. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  172. package/dist/worker/observe/static/views/task.js +1 -1
  173. package/dist/worker/pool/attempt-lease.js +97 -66
  174. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  175. package/dist/worker/pool/reconcile.js +46 -1
  176. package/dist/worker/pool/run-store.js +2 -0
  177. package/dist/worker/pool/state-projection.js +2 -0
  178. package/dist/workflows/dag/artifact-bindings.js +149 -0
  179. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  180. package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
  181. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
  182. package/dist/workflows/dag/context-receipt.js +305 -0
  183. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  184. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  185. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  186. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  187. package/dist/workflows/dag/init-hybrid.js +178 -47
  188. package/dist/workflows/dag/interrupt-request.js +3 -3
  189. package/dist/workflows/dag/lifecycle.js +3 -3
  190. package/dist/workflows/dag/node-execution.js +112 -27
  191. package/dist/workflows/dag/path-safety.js +1 -0
  192. package/dist/workflows/dag/prompt.js +29 -4
  193. package/dist/workflows/dag/rerun-plan.js +272 -0
  194. package/dist/workflows/dag/rerun-run.js +390 -12
  195. package/dist/workflows/dag/skill-snapshot.js +1 -0
  196. package/dist/workflows/dag/types.js +203 -8
  197. package/docs/README.md +1 -0
  198. package/docs/architecture/runtime-boundaries.md +6 -6
  199. package/docs/operations/local-development-environment.md +21 -0
  200. package/docs/templates/agent-dag.schema.json +50 -0
  201. package/docs/templates/backend-test-dag.json +265 -237
  202. package/harness.json +3 -3
  203. package/package.json +5 -1
  204. package/skills/loop-agent/references/command-reference.md +6 -0
  205. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
  206. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
  207. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
  208. package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
  209. package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
  210. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
  211. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
  212. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  213. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  214. /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
  215. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -10,6 +10,7 @@ import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
10
10
  import { resolveContextPolicy } from "./context-policy.js";
11
11
  import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
12
12
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
13
+ import { materializeDeclaredArtifactFacts } from "./artifact-bindings.js";
13
14
  import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
14
15
  import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
15
16
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
@@ -42,7 +43,11 @@ function canonicalApprovalJson(value) {
42
43
  export function parseAndValidateFinalWriteSetApproval(input) {
43
44
  const binding = input.task.finalWriteSetApproval;
44
45
  if (!binding) {
45
- return { ok: false, reason: "missing final write-set approval binding", approvalSourceNodeId: "(missing)" };
46
+ return {
47
+ ok: false,
48
+ reason: "missing final write-set approval binding",
49
+ approvalSourceNodeId: "(missing)",
50
+ };
46
51
  }
47
52
  const source = input.state.nodes[binding.approvalSourceNodeId];
48
53
  if (source?.status !== "FINISHED" || parseProcessVerdict(source) !== "pass") {
@@ -53,20 +58,44 @@ export function parseAndValidateFinalWriteSetApproval(input) {
53
58
  };
54
59
  }
55
60
  const raw = canonicalNodeOutput(source);
56
- const blocks = [...raw.matchAll(/```FINAL_WRITE_SET_APPROVAL_JSON\s*\r?\n([\s\S]*?)\r?\n```/g)];
61
+ const blocks = [
62
+ ...raw.matchAll(/```FINAL_WRITE_SET_APPROVAL_JSON\s*\r?\n([\s\S]*?)\r?\n```/g),
63
+ ];
57
64
  if (blocks.length !== 1) {
58
- return { ok: false, reason: `expected exactly one FINAL_WRITE_SET_APPROVAL_JSON block, found ${blocks.length}`, approvalSourceNodeId: binding.approvalSourceNodeId };
65
+ return {
66
+ ok: false,
67
+ reason: `expected exactly one FINAL_WRITE_SET_APPROVAL_JSON block, found ${blocks.length}`,
68
+ approvalSourceNodeId: binding.approvalSourceNodeId,
69
+ };
59
70
  }
60
71
  let approval;
61
72
  try {
62
73
  approval = JSON.parse(blocks[0][1]);
63
74
  }
64
75
  catch {
65
- return { ok: false, reason: "final write-set approval is not valid JSON", approvalSourceNodeId: binding.approvalSourceNodeId };
76
+ return {
77
+ ok: false,
78
+ reason: "final write-set approval is not valid JSON",
79
+ approvalSourceNodeId: binding.approvalSourceNodeId,
80
+ };
66
81
  }
67
- const required = ["schemaVersion", "writerNodeId", "approvalSourceNodeId", "auditedPlanNodeId", "approvedWriteSet", "taskContractSha256", "auditedPlanSha256", "approvalDigest"];
68
- if (Object.keys(approval).length !== required.length || required.some((key) => !(key in approval))) {
69
- return { ok: false, reason: "final write-set approval has an invalid schema", approvalSourceNodeId: binding.approvalSourceNodeId };
82
+ const required = [
83
+ "schemaVersion",
84
+ "writerNodeId",
85
+ "approvalSourceNodeId",
86
+ "auditedPlanNodeId",
87
+ "approvedWriteSet",
88
+ "taskContractSha256",
89
+ "auditedPlanSha256",
90
+ "approvalDigest",
91
+ ];
92
+ if (Object.keys(approval).length !== required.length ||
93
+ required.some((key) => !(key in approval))) {
94
+ return {
95
+ ok: false,
96
+ reason: "final write-set approval has an invalid schema",
97
+ approvalSourceNodeId: binding.approvalSourceNodeId,
98
+ };
70
99
  }
71
100
  const approved = approval.approvedWriteSet;
72
101
  if (approval.schemaVersion !== 1 ||
@@ -79,12 +108,20 @@ export function parseAndValidateFinalWriteSetApproval(input) {
79
108
  typeof approval.taskContractSha256 !== "string" ||
80
109
  typeof approval.auditedPlanSha256 !== "string" ||
81
110
  typeof approval.approvalDigest !== "string") {
82
- return { ok: false, reason: "final write-set approval binding or field types are invalid", approvalSourceNodeId: binding.approvalSourceNodeId };
111
+ return {
112
+ ok: false,
113
+ reason: "final write-set approval binding or field types are invalid",
114
+ approvalSourceNodeId: binding.approvalSourceNodeId,
115
+ };
83
116
  }
84
117
  if (!/^[a-f0-9]{64}$/.test(approval.taskContractSha256) ||
85
118
  !/^[a-f0-9]{64}$/.test(approval.auditedPlanSha256) ||
86
119
  !/^[a-f0-9]{64}$/.test(approval.approvalDigest)) {
87
- return { ok: false, reason: "final write-set approval digest fields are invalid", approvalSourceNodeId: binding.approvalSourceNodeId };
120
+ return {
121
+ ok: false,
122
+ reason: "final write-set approval digest fields are invalid",
123
+ approvalSourceNodeId: binding.approvalSourceNodeId,
124
+ };
88
125
  }
89
126
  const canonicalPayload = { ...approval };
90
127
  delete canonicalPayload.approvalDigest;
@@ -92,33 +129,70 @@ export function parseAndValidateFinalWriteSetApproval(input) {
92
129
  .update(canonicalApprovalJson(canonicalPayload))
93
130
  .digest("hex");
94
131
  if (approval.approvalDigest !== expectedDigest) {
95
- return { ok: false, reason: "final write-set approval digest mismatch", approvalSourceNodeId: binding.approvalSourceNodeId };
132
+ return {
133
+ ok: false,
134
+ reason: "final write-set approval digest mismatch",
135
+ approvalSourceNodeId: binding.approvalSourceNodeId,
136
+ };
96
137
  }
97
138
  const expectedTaskDigest = input.spec.taskContractBinding?.canonicalHash;
98
139
  const plan = input.state.nodes[binding.auditedPlanNodeId];
99
140
  const expectedPlanDigest = createHash("sha256")
100
141
  .update(canonicalNodeOutput(plan))
101
142
  .digest("hex");
102
- if (!expectedTaskDigest || approval.taskContractSha256 !== expectedTaskDigest || approval.auditedPlanSha256 !== expectedPlanDigest) {
103
- return { ok: false, reason: "final write-set approval is stale for the task contract or audited plan", approvalSourceNodeId: binding.approvalSourceNodeId };
143
+ if (!expectedTaskDigest ||
144
+ approval.taskContractSha256 !== expectedTaskDigest ||
145
+ approval.auditedPlanSha256 !== expectedPlanDigest) {
146
+ return {
147
+ ok: false,
148
+ reason: "final write-set approval is stale for the task contract or audited plan",
149
+ approvalSourceNodeId: binding.approvalSourceNodeId,
150
+ };
104
151
  }
105
152
  const effectiveWriteSet = approved;
106
- if (effectiveWriteSet.length === 0 || new Set(effectiveWriteSet).size !== effectiveWriteSet.length) {
107
- return { ok: false, reason: "final write-set approval must contain a non-empty ordered unique path set", approvalSourceNodeId: binding.approvalSourceNodeId };
153
+ if (effectiveWriteSet.length === 0 ||
154
+ new Set(effectiveWriteSet).size !== effectiveWriteSet.length) {
155
+ return {
156
+ ok: false,
157
+ reason: "final write-set approval must contain a non-empty ordered unique path set",
158
+ approvalSourceNodeId: binding.approvalSourceNodeId,
159
+ };
108
160
  }
109
161
  for (const entry of effectiveWriteSet) {
110
162
  const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
111
- if (!normalized || normalized === "." || normalized === ".." || normalized.includes("*") || normalized.includes("?") || normalized.includes("REPLACE/") || normalized.includes("PLACEHOLDER")) {
112
- return { ok: false, reason: `final write-set approval contains a broad or placeholder path: ${entry}`, approvalSourceNodeId: binding.approvalSourceNodeId };
163
+ if (!normalized ||
164
+ normalized === "." ||
165
+ normalized === ".." ||
166
+ normalized.includes("*") ||
167
+ normalized.includes("?") ||
168
+ normalized.includes("REPLACE/") ||
169
+ normalized.includes("PLACEHOLDER")) {
170
+ return {
171
+ ok: false,
172
+ reason: `final write-set approval contains a broad or placeholder path: ${entry}`,
173
+ approvalSourceNodeId: binding.approvalSourceNodeId,
174
+ };
113
175
  }
114
176
  if (!input.task.allowedPaths.some((allowed) => pathMatchesPattern(normalized, allowed))) {
115
- return { ok: false, reason: `final write-set approval exceeds allowedPaths: ${entry}`, approvalSourceNodeId: binding.approvalSourceNodeId };
177
+ return {
178
+ ok: false,
179
+ reason: `final write-set approval exceeds allowedPaths: ${entry}`,
180
+ approvalSourceNodeId: binding.approvalSourceNodeId,
181
+ };
116
182
  }
117
183
  if (input.task.forbiddenPaths.some((forbidden) => pathMatchesPattern(normalized, forbidden))) {
118
- return { ok: false, reason: `final write-set approval overlaps forbiddenPaths: ${entry}`, approvalSourceNodeId: binding.approvalSourceNodeId };
184
+ return {
185
+ ok: false,
186
+ reason: `final write-set approval overlaps forbiddenPaths: ${entry}`,
187
+ approvalSourceNodeId: binding.approvalSourceNodeId,
188
+ };
119
189
  }
120
190
  }
121
- return { ok: true, effectiveWriteSet, approvalDigest: approval.approvalDigest };
191
+ return {
192
+ ok: true,
193
+ effectiveWriteSet,
194
+ approvalDigest: approval.approvalDigest,
195
+ };
122
196
  }
123
197
  export function buildNodePrompt(spec, task, upstream, options) {
124
198
  const policy = resolveContextPolicy(spec);
@@ -126,6 +200,7 @@ export function buildNodePrompt(spec, task, upstream, options) {
126
200
  spec,
127
201
  task,
128
202
  upstream,
203
+ runDir: options?.runDir,
129
204
  resolvedSkills: policy.resolveSkills(spec, task),
130
205
  maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
131
206
  projectGovernanceContext: options?.projectGovernanceContext,
@@ -314,10 +389,7 @@ function promptRestartCandidateForAttempt(input) {
314
389
  }
315
390
  const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
316
391
  if (input.attemptPrompt.startsWith(appendedPrefix)) {
317
- return [
318
- input.taskPrompt,
319
- input.attemptPrompt.slice(appendedPrefix.length),
320
- ]
392
+ return [input.taskPrompt, input.attemptPrompt.slice(appendedPrefix.length)]
321
393
  .filter(Boolean)
322
394
  .join("\n\n");
323
395
  }
@@ -353,6 +425,7 @@ export async function buildNodePromptWithResolvedSkillInstructions(spec, task, u
353
425
  spec,
354
426
  task,
355
427
  upstream,
428
+ runDir: options?.runDir,
356
429
  resolvedSkills: skillNames,
357
430
  resolvedSkillInstructions,
358
431
  maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
@@ -533,7 +606,11 @@ export async function executeDagNode(input) {
533
606
  await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
534
607
  };
535
608
  if (task.finalWriteSetApproval) {
536
- const authorization = parseAndValidateFinalWriteSetApproval({ task, spec, state });
609
+ const authorization = parseAndValidateFinalWriteSetApproval({
610
+ task,
611
+ spec,
612
+ state,
613
+ });
537
614
  if (!authorization.ok) {
538
615
  node.runtimeWriteAuthorization = {
539
616
  schemaVersion: 1,
@@ -622,6 +699,7 @@ export async function executeDagNode(input) {
622
699
  spec,
623
700
  task,
624
701
  upstream: state.nodes,
702
+ runDir,
625
703
  snapshot: skillSnapshot,
626
704
  projectGovernanceContext,
627
705
  });
@@ -712,6 +790,7 @@ export async function executeDagNode(input) {
712
790
  else {
713
791
  ({ prompt, resolvedSkills } =
714
792
  await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd, {
793
+ runDir,
715
794
  projectGovernanceContext,
716
795
  convergenceFeedback: deriveConvergenceFeedback(state),
717
796
  }));
@@ -875,6 +954,7 @@ export async function executeDagNode(input) {
875
954
  model,
876
955
  ...(thinking ? { thinking } : {}),
877
956
  prompt: attemptPrompt,
957
+ resolvedSkills: resolvedSkills.map((skill) => skill.name),
878
958
  attempt: attemptNumber,
879
959
  reportActivity,
880
960
  timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
@@ -912,9 +992,7 @@ export async function executeDagNode(input) {
912
992
  ...result,
913
993
  ok: false,
914
994
  failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
915
- stderr: [result.stderr, audit.reason]
916
- .filter(Boolean)
917
- .join("\n"),
995
+ stderr: [result.stderr, audit.reason].filter(Boolean).join("\n"),
918
996
  };
919
997
  }
920
998
  }
@@ -1237,6 +1315,13 @@ export async function executeDagNode(input) {
1237
1315
  node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
1238
1316
  node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
1239
1317
  }
1318
+ if ((task.producesArtifacts?.length ?? 0) > 0) {
1319
+ node.declaredArtifacts = await materializeDeclaredArtifactFacts({
1320
+ runDir,
1321
+ task,
1322
+ node,
1323
+ });
1324
+ }
1240
1325
  }
1241
1326
  const finishedAt = new Date().toISOString();
1242
1327
  node.finishedAt = finishedAt;
@@ -0,0 +1 @@
1
+ export { assertReadableRegularFile, assertRealPathInsideBoundary, readFileBoundarySafe, } from "../../shared/path-safety.js";
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { DEFAULT_DAG_OUTPUT_LANGUAGE, } from "./types.js";
2
3
  import { formatStdoutPreview, formatUpstreamArtifactPointerMap, } from "./upstream-artifacts.js";
3
4
  export const MAX_UPSTREAM_CHARS = 2_000;
@@ -62,9 +63,10 @@ export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE
62
63
  "如果当前节点任务明确要求其他语言,以节点的明确要求为准。",
63
64
  ].join("\n");
64
65
  }
65
- export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHARS) {
66
+ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHARS, runDir) {
66
67
  const sections = [];
67
68
  const toleratedFailures = new Set(task.failureAwareDependsOn ?? []);
69
+ const consumedArtifactIds = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
68
70
  for (const depId of task.depends_on) {
69
71
  const record = upstream[depId];
70
72
  const stdout = record?.stdout?.trim() ? record.stdout : "";
@@ -118,6 +120,22 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
118
120
  }
119
121
  if (!record || record.status !== "FINISHED")
120
122
  continue;
123
+ const consumedArtifacts = (record.declaredArtifacts ?? []).filter((artifact) => consumedArtifactIds.has(artifact.artifactId));
124
+ const consumedArtifactSection = runDir && consumedArtifacts.length > 0
125
+ ? [
126
+ "Declared artifact bindings (read-only run evidence — use the exact paths below; do not edit):",
127
+ ...consumedArtifacts.flatMap((artifact) => {
128
+ const absolutePath = path.resolve(runDir, ...artifact.path.split("/"));
129
+ return [
130
+ `- artifactId: ${artifact.artifactId}`,
131
+ ` path: ${absolutePath}`,
132
+ ` status: ${artifact.status}`,
133
+ ` sha256: ${artifact.sha256 ?? "unknown"}`,
134
+ ` mediaType: ${artifact.mediaType}`,
135
+ ];
136
+ }),
137
+ ].join("\n")
138
+ : "";
121
139
  if (record.structuredArtifactPath &&
122
140
  record.structuredArtifactSchemaId ===
123
141
  "frontend-implementation-contract-v1") {
@@ -130,8 +148,12 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
130
148
  ].join("\n"));
131
149
  continue;
132
150
  }
133
- if (!upstreamText)
151
+ if (!upstreamText) {
152
+ if (consumedArtifactSection) {
153
+ sections.push(`## Upstream output: ${depId}\n${consumedArtifactSection}`);
154
+ }
134
155
  continue;
156
+ }
135
157
  const artifactKind = stdout ? "stdout" : "assistant";
136
158
  const artifactPath = stdout
137
159
  ? record.stdoutArtifactPath
@@ -147,6 +169,9 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
147
169
  `- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
148
170
  ].join("\n");
149
171
  }
172
+ if (consumedArtifactSection) {
173
+ section += `\n\n${consumedArtifactSection}`;
174
+ }
150
175
  if (truncated && artifactPath) {
151
176
  section += formatUpstreamArtifactPointerMap(record, artifactKind);
152
177
  }
@@ -329,7 +354,7 @@ function formatProjectGovernanceContext(ctx) {
329
354
  return lines.join("\n");
330
355
  }
331
356
  export function buildDagNodePromptEnvelope(input) {
332
- const { spec, task, upstream, resolvedSkills = [], resolvedSkillInstructions = [], maxUpstreamChars = MAX_UPSTREAM_CHARS, projectGovernanceContext, convergenceFeedback, } = input;
357
+ const { spec, task, upstream, runDir, resolvedSkills = [], resolvedSkillInstructions = [], maxUpstreamChars = MAX_UPSTREAM_CHARS, projectGovernanceContext, convergenceFeedback, } = input;
333
358
  const objective = spec.objective ?? spec.title;
334
359
  const successCriteria = formatBulletList(spec.successCriteria, "(none specified)");
335
360
  const globalConstraints = formatBulletList(spec.globalConstraints, "(none specified)");
@@ -339,7 +364,7 @@ export function buildDagNodePromptEnvelope(input) {
339
364
  ? resolvedSkills.map((skill) => `- ${skill}`).join("\n")
340
365
  : "(none)";
341
366
  const skillInstructionsSection = formatResolvedSkillInstructions(resolvedSkillInstructions);
342
- const upstreamContext = buildUpstreamContext(task, upstream, maxUpstreamChars);
367
+ const upstreamContext = buildUpstreamContext(task, upstream, maxUpstreamChars, runDir);
343
368
  const readOnlyBoundary = buildReadOnlyBoundary(writePolicy);
344
369
  const sections = [
345
370
  `<dag_objective>\n${objective}\n</dag_objective>`,
@@ -1,9 +1,138 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { z } from "zod";
3
4
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
5
  import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
5
6
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
6
7
  import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
8
+ import { artifactOverrideSetV1Schema, workerContinuationAuthorityV1Schema, } from "../../shared/context-transfer.js";
9
+ import { buildArtifactBindingGraph, computeArtifactImpact, } from "./artifact-bindings.js";
10
+ const rerunBindingsV1Schema = z
11
+ .object({
12
+ sourceBindingMatched: z.boolean(),
13
+ taskContractBindingMatched: z.boolean(),
14
+ controllerIdentityMatched: z.boolean(),
15
+ skillSnapshotVerified: z.boolean(),
16
+ workspaceMatched: z.boolean(),
17
+ })
18
+ .strict();
19
+ const rerunPlanCommonSchema = {
20
+ eligible: z.boolean(),
21
+ parentRunId: z.string().min(1),
22
+ parentLifecycle: z.enum(["completed", "dry-run", "init-only"]),
23
+ selectedNodeId: z.string().min(1),
24
+ effectiveFromNodeId: z.string().min(1).optional(),
25
+ rewriteApplied: z.boolean(),
26
+ resetNodeIds: z.array(z.string().min(1)),
27
+ importedNodeIds: z.array(z.string().min(1)),
28
+ blockingNodes: z.array(z.object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) }).strict()),
29
+ reasonCodes: z.array(z.string().min(1)),
30
+ blockedReasons: z.array(z.string().min(1)),
31
+ risk: z.enum(["low", "medium", "high"]),
32
+ verificationPolicy: z
33
+ .object({
34
+ mode: z.enum(["inherited", "rerun"]),
35
+ nodeIds: z.array(z.string().min(1)),
36
+ inheritedFromRunId: z.string().min(1).optional(),
37
+ })
38
+ .strict()
39
+ .optional(),
40
+ workerAssociation: z.discriminatedUnion("kind", [
41
+ z.object({ kind: z.literal("standalone") }).strict(),
42
+ z
43
+ .object({
44
+ kind: z.literal("worker-managed"),
45
+ featureId: z.string().min(1).optional(),
46
+ taskId: z.string().min(1).optional(),
47
+ workerRunId: z.string().min(1).optional(),
48
+ })
49
+ .strict(),
50
+ ]),
51
+ estimatedExecution: z
52
+ .object({
53
+ nodeCount: z.number().int().nonnegative(),
54
+ piCallCount: z.number().int().nonnegative(),
55
+ shellNodeCount: z.number().int().nonnegative(),
56
+ avoidedNodeCount: z.number().int().nonnegative(),
57
+ avoidedPiCallCount: z.number().int().nonnegative(),
58
+ })
59
+ .strict(),
60
+ suggestedAction: z.enum([
61
+ "dag-rerun",
62
+ "standalone-task-rerun",
63
+ "worker-task-retry",
64
+ "regenerate",
65
+ "resume",
66
+ "reconcile",
67
+ "manual",
68
+ ]),
69
+ promptOverrides: z
70
+ .array(z
71
+ .object({
72
+ nodeId: z.string().min(1),
73
+ mode: z.enum(["replace", "append"]),
74
+ basePromptSha256: z.string().regex(/^[a-f0-9]{64}$/),
75
+ promptSha256: z.string().regex(/^[a-f0-9]{64}$/),
76
+ })
77
+ .strict())
78
+ .optional(),
79
+ workerContinuationAuthority: workerContinuationAuthorityV1Schema.optional(),
80
+ };
81
+ export const dagRerunPlanV2Schema = z
82
+ .object({
83
+ schemaVersion: z.literal(2),
84
+ ...rerunPlanCommonSchema,
85
+ trigger: z.enum(["node-rerun", "prompt-override", "artifact-override"]),
86
+ artifactOverrides: artifactOverrideSetV1Schema.optional(),
87
+ impact: z
88
+ .object({
89
+ producerNodeId: z.string().min(1).optional(),
90
+ directConsumerNodeIds: z.array(z.string().min(1)),
91
+ effectiveFromNodeId: z.string().min(1),
92
+ affectedNodeIds: z.array(z.string().min(1)),
93
+ importedNodeIds: z.array(z.string().min(1)),
94
+ blockedNodeIds: z.array(z
95
+ .object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) })
96
+ .strict()),
97
+ })
98
+ .strict(),
99
+ bindings: rerunBindingsV1Schema
100
+ .extend({
101
+ artifactGraphSha256: z.string().regex(/^[a-f0-9]{64}$/),
102
+ overrideSetHash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
103
+ stagingRevisionSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
104
+ })
105
+ .strict(),
106
+ planHash: z.string().regex(/^[a-f0-9]{64}$/),
107
+ })
108
+ .strict()
109
+ .superRefine((plan, ctx) => {
110
+ const artifactTrigger = plan.trigger === "artifact-override";
111
+ if (artifactTrigger !== Boolean(plan.artifactOverrides)) {
112
+ ctx.addIssue({
113
+ code: z.ZodIssueCode.custom,
114
+ path: ["artifactOverrides"],
115
+ message: "artifact override trigger and override set must appear together",
116
+ });
117
+ }
118
+ if (plan.artifactOverrides &&
119
+ (plan.bindings.overrideSetHash !== plan.artifactOverrides.overrideSetHash ||
120
+ !plan.bindings.stagingRevisionSha256)) {
121
+ ctx.addIssue({
122
+ code: z.ZodIssueCode.custom,
123
+ path: ["bindings"],
124
+ message: "artifact override bindings must match the override set and revision",
125
+ });
126
+ }
127
+ if (plan.workerContinuationAuthority &&
128
+ plan.workerAssociation.kind !== "worker-managed") {
129
+ ctx.addIssue({
130
+ code: z.ZodIssueCode.custom,
131
+ path: ["workerContinuationAuthority"],
132
+ message: "Worker continuation authority requires a Worker-managed parent",
133
+ });
134
+ }
135
+ });
7
136
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
8
137
  const SAFE_SHELL_GATE_KEYS = [
9
138
  "verdictGate",
@@ -480,6 +609,149 @@ export function computePlanHash(planWithoutHash) {
480
609
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
481
610
  return createHash("sha256").update(canonical, "utf8").digest("hex");
482
611
  }
612
+ export function evaluateArtifactOverrideRerunPlan(input) {
613
+ const graph = buildArtifactBindingGraph(input.parentSpec);
614
+ const declaration = graph.artifacts.find((artifact) => artifact.artifactId === input.artifactId);
615
+ const impact = computeArtifactImpact(input.parentSpec, input.artifactId);
616
+ const reasonCodes = [];
617
+ const blockedReasons = [];
618
+ const blockingNodes = [];
619
+ const workerAssociation = input.workerAssociation ?? { kind: "standalone" };
620
+ const block = (code, nodeId) => {
621
+ reasonCodes.push(code);
622
+ blockedReasons.push(code);
623
+ if (nodeId)
624
+ blockingNodes.push({ nodeId, reasonCode: code });
625
+ };
626
+ if (!graph.available)
627
+ block("ARTIFACT_BINDING_UNAVAILABLE");
628
+ if (!declaration)
629
+ block("ARTIFACT_IDENTITY_AMBIGUOUS");
630
+ if (!impact)
631
+ block("ARTIFACT_CONSUMER_UNKNOWN", declaration?.producerNodeId);
632
+ if (input.parentLifecycle !== "completed")
633
+ block("parent-lifecycle-ineligible");
634
+ const overrideRevisionId = input.artifactOverrides.overrides[0]?.revisionId;
635
+ const authority = input.workerContinuationAuthority;
636
+ if (workerAssociation.kind === "worker-managed") {
637
+ if (!authority) {
638
+ block("WORKER_MANAGED_CONTINUATION_UNSUPPORTED");
639
+ }
640
+ else if (!workerAssociation.workerRunId ||
641
+ !workerAssociation.featureId ||
642
+ !workerAssociation.taskId ||
643
+ authority.parentWorkerRunId !== workerAssociation.workerRunId ||
644
+ authority.featureId !== workerAssociation.featureId ||
645
+ authority.taskId !== workerAssociation.taskId ||
646
+ authority.revisionId !== overrideRevisionId) {
647
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
648
+ }
649
+ }
650
+ else if (authority) {
651
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
652
+ }
653
+ if (input.parentSpec.convergence?.enabled || input.parentState.convergence?.enabled) {
654
+ block("convergence-rerun-unsupported");
655
+ }
656
+ if (input.parentSpec.evaluation || input.parentState.evaluation) {
657
+ block("evaluation-rerun-unsupported");
658
+ }
659
+ if ((input.parentSpec.budget?.mode ?? input.parentState.budget?.mode) === "hard") {
660
+ block("budget-rerun-unsupported");
661
+ }
662
+ for (const [name, matched] of Object.entries(input.bindings)) {
663
+ if (!matched)
664
+ block(`${name}-drift`);
665
+ }
666
+ const override = input.artifactOverrides.overrides[0];
667
+ const parentFact = declaration
668
+ ? input.parentState.nodes[declaration.producerNodeId]?.declaredArtifacts?.find((fact) => fact.artifactId === input.artifactId)
669
+ : undefined;
670
+ if (!declaration ||
671
+ declaration.revisionPolicy !== "replace-file" ||
672
+ parentFact?.status !== "materialized" ||
673
+ parentFact.revisionEligible !== true ||
674
+ !parentFact.sha256) {
675
+ block("ARTIFACT_NOT_REVISION_ELIGIBLE", declaration?.producerNodeId);
676
+ }
677
+ if (!override ||
678
+ override.parentArtifact.runId !== input.parentRunId ||
679
+ override.parentArtifact.nodeId !== declaration?.producerNodeId ||
680
+ override.parentArtifact.path !== declaration?.path ||
681
+ override.parentArtifact.sha256 !== parentFact?.sha256 ||
682
+ override.supersedes !== parentFact?.sha256 ||
683
+ override.replacement.sha256 !== input.stagingRevisionSha256) {
684
+ block("ARTIFACT_REVISION_DRIFT", declaration?.producerNodeId);
685
+ }
686
+ const affectedNodeIds = impact?.affectedNodeIds ?? [];
687
+ const importedNodeIds = impact?.importedNodeIds ?? [];
688
+ const closureSafety = assessResetClosureSafety(input.parentSpec, affectedNodeIds, {
689
+ parentNodes: input.parentState.nodes,
690
+ });
691
+ for (const entry of closureSafety.blockingNodes) {
692
+ blockingNodes.push(entry);
693
+ block("ARTIFACT_IMPACT_UNSAFE");
694
+ }
695
+ for (const nodeId of importedNodeIds) {
696
+ const status = input.parentState.nodes[nodeId]?.status;
697
+ if (status !== "FINISHED" && status !== "SKIPPED") {
698
+ block("parent-facts-invalid", nodeId);
699
+ }
700
+ }
701
+ const uniqueReasons = [...new Set(reasonCodes)];
702
+ const uniqueBlocked = [...new Set(blockedReasons)];
703
+ const eligible = uniqueBlocked.length === 0;
704
+ const shellNodeIds = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "shell");
705
+ const piCallCount = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length;
706
+ const planWithoutHash = {
707
+ schemaVersion: 2,
708
+ eligible,
709
+ parentRunId: input.parentRunId,
710
+ parentLifecycle: input.parentLifecycle,
711
+ selectedNodeId: declaration?.producerNodeId ?? input.artifactId,
712
+ effectiveFromNodeId: impact?.effectiveFromNodeId,
713
+ rewriteApplied: false,
714
+ resetNodeIds: affectedNodeIds,
715
+ importedNodeIds,
716
+ blockingNodes,
717
+ reasonCodes: uniqueReasons,
718
+ blockedReasons: uniqueBlocked,
719
+ risk: !eligible ? "high" : affectedNodeIds.length <= 2 ? "low" : "medium",
720
+ bindings: {
721
+ ...input.bindings,
722
+ artifactGraphSha256: graph.artifactGraphSha256,
723
+ overrideSetHash: input.artifactOverrides.overrideSetHash,
724
+ stagingRevisionSha256: input.stagingRevisionSha256,
725
+ },
726
+ ...(shellNodeIds.length > 0
727
+ ? { verificationPolicy: { mode: "rerun", nodeIds: shellNodeIds } }
728
+ : {}),
729
+ workerAssociation,
730
+ ...(authority ? { workerContinuationAuthority: authority } : {}),
731
+ estimatedExecution: {
732
+ nodeCount: affectedNodeIds.length,
733
+ piCallCount,
734
+ shellNodeCount: shellNodeIds.length,
735
+ avoidedNodeCount: importedNodeIds.length,
736
+ avoidedPiCallCount: importedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length,
737
+ },
738
+ suggestedAction: eligible ? "dag-rerun" : workerAssociation.kind === "worker-managed" ? "worker-task-retry" : "manual",
739
+ trigger: "artifact-override",
740
+ artifactOverrides: input.artifactOverrides,
741
+ impact: {
742
+ ...(impact ? { producerNodeId: impact.producerNodeId } : {}),
743
+ directConsumerNodeIds: impact?.directConsumerNodeIds ?? [],
744
+ effectiveFromNodeId: impact?.effectiveFromNodeId ?? declaration?.producerNodeId ?? input.artifactId,
745
+ affectedNodeIds,
746
+ importedNodeIds,
747
+ blockedNodeIds: blockingNodes,
748
+ },
749
+ };
750
+ return dagRerunPlanV2Schema.parse({
751
+ ...planWithoutHash,
752
+ planHash: computePlanHash(planWithoutHash),
753
+ });
754
+ }
483
755
  function countExecutorNodes(spec, nodeIds, executor) {
484
756
  const tasks = taskById(spec);
485
757
  return nodeIds.filter((nodeId) => tasks.get(nodeId)?.executor === executor)