@tea-agent/loop-agent 0.40.0-next.9 → 0.41.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 (218) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +130 -3
  3. package/README.md +2 -2
  4. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  5. package/dist/adapters/context-transfer/pi-session.js +61 -0
  6. package/dist/application/dag/generate-task-dag.js +6 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +21 -5
  9. package/dist/cli/program.js +61 -4
  10. package/dist/commands/client-recovery.js +8 -36
  11. package/dist/commands/dag-artifact.js +284 -0
  12. package/dist/commands/dag-context.js +184 -0
  13. package/dist/commands/dag-rerun.js +206 -1
  14. package/dist/commands/init-model-catalog.js +22 -0
  15. package/dist/commands/task-advance.js +23 -1
  16. package/dist/commands/task-source-prepare.js +5 -0
  17. package/dist/commands/task-status.js +1 -0
  18. package/dist/executors/dag-pi-executor.js +39 -12
  19. package/dist/executors/pi-sdk-executor.js +36 -5
  20. package/dist/executors/shell-executor.js +64 -17
  21. package/dist/governance/checks.js +1 -0
  22. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  23. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  24. package/dist/infrastructure/console/context-export-store.js +160 -0
  25. package/dist/infrastructure/console/dir-lock.js +132 -0
  26. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  27. package/dist/infrastructure/harness/artifact-store.js +10 -1
  28. package/dist/infrastructure/harness/atomic-write.js +12 -2
  29. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  30. package/dist/shared/context-transfer.js +418 -0
  31. package/dist/shared/operator/capabilities.js +23 -1
  32. package/dist/shared/operator/safe-run-summary.js +1 -0
  33. package/dist/shared/path-safety.js +93 -0
  34. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  35. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  36. package/dist/shared/pi-context-pressure/env.js +58 -0
  37. package/dist/shared/pi-context-pressure/extension.js +100 -0
  38. package/dist/shared/pi-context-pressure/index.js +7 -0
  39. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  40. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  41. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  42. package/dist/shared/preview.js +28 -4
  43. package/dist/task/config-types.js +2 -1
  44. package/dist/task/task-demand-routing.js +3 -0
  45. package/dist/worker/console/chat/assistant-content.js +7 -0
  46. package/dist/worker/console/chat/chat-event-store.js +84 -13
  47. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  48. package/dist/worker/console/chat/goals.js +300 -0
  49. package/dist/worker/console/chat/pi-runtime.js +285 -8
  50. package/dist/worker/console/chat/provider-error.js +2 -1
  51. package/dist/worker/console/chat/repo-browser.js +94 -12
  52. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  53. package/dist/worker/console/chat/routes.js +487 -30
  54. package/dist/worker/console/chat/session-catalog.js +48 -0
  55. package/dist/worker/console/chat/session-stats.js +96 -0
  56. package/dist/worker/console/chat/session-store.js +28 -19
  57. package/dist/worker/console/chat/shortcuts.js +7 -0
  58. package/dist/worker/console/chat/sift-bridge.js +1 -0
  59. package/dist/worker/console/chat/turn-process.js +32 -3
  60. package/dist/worker/console/chat/user-questions.js +1 -1
  61. package/dist/worker/console/console-update-runtime.js +1 -1
  62. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  63. package/dist/worker/console/dag-confirmation.js +1 -1
  64. package/dist/worker/console/dag-execution-receipt.js +21 -3
  65. package/dist/worker/console/doctor.js +1 -1
  66. package/dist/worker/console/draft-store.js +1 -1
  67. package/dist/worker/console/human-gate-token.js +1 -1
  68. package/dist/worker/console/index.js +3 -3
  69. package/dist/worker/console/interview/assessment.js +1 -1
  70. package/dist/worker/console/interview/session.js +1 -1
  71. package/dist/worker/console/operation-runner.js +45 -4
  72. package/dist/worker/console/operation-sse.js +1 -1
  73. package/dist/worker/console/operation-wait.js +1 -1
  74. package/dist/worker/console/operator-actions.js +124 -8
  75. package/dist/worker/console/operator-selection.js +6 -2
  76. package/dist/worker/console/operator-surface-health.js +1 -1
  77. package/dist/worker/console/operator-user-error.js +169 -0
  78. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  79. package/dist/worker/console/pi-plugins.js +179 -0
  80. package/dist/worker/console/routes.js +864 -13
  81. package/dist/worker/console/security.js +35 -0
  82. package/dist/worker/console/server.js +177 -44
  83. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
  84. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
  85. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
  86. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
  87. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
  88. package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
  89. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
  90. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
  99. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
  100. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
  101. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
  102. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
  103. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
  104. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
  105. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
  111. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
  112. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
  113. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
  114. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
  115. package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
  116. package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
  117. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
  118. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
  119. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
  120. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
  121. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
  122. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
  123. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
  124. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
  125. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
  126. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
  127. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
  128. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
  129. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
  130. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
  131. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
  132. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
  133. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
  134. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
  135. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
  136. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
  137. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
  138. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
  139. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
  140. package/dist/worker/console/static/index.html +7 -2
  141. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  142. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  143. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  144. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  145. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  146. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  147. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  148. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  149. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  150. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  151. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  152. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  153. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  154. package/dist/worker/console/workspace-context.js +56 -5
  155. package/dist/worker/console/workspace-registry.js +335 -62
  156. package/dist/worker/continuation/worker-continuation.js +278 -0
  157. package/dist/worker/observe/health.js +1 -1
  158. package/dist/worker/observe/node-transparency.js +572 -0
  159. package/dist/worker/observe/routes.js +51 -1
  160. package/dist/worker/observe/static/api.js +69 -5
  161. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  162. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  163. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  164. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  165. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  166. package/dist/worker/observe/static/operator-chrome.css +23 -4
  167. package/dist/worker/observe/static/operator-chrome.js +7 -2
  168. package/dist/worker/observe/static/state.js +3 -1
  169. package/dist/worker/observe/static/styles.css +1108 -135
  170. package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
  171. package/dist/worker/observe/static/views/dags.js +7 -5
  172. package/dist/worker/observe/static/views/session-timeline.js +210 -10
  173. package/dist/worker/outcomes/adapters.js +19 -0
  174. package/dist/worker/outcomes/types.js +1 -0
  175. package/dist/worker/pool/attempt-lease.js +97 -66
  176. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  177. package/dist/worker/pool/reconcile.js +46 -1
  178. package/dist/worker/pool/run-store.js +2 -0
  179. package/dist/worker/pool/state-projection.js +2 -0
  180. package/dist/worker/task-spec/workflow-routing.js +8 -3
  181. package/dist/workflows/dag/artifact-bindings.js +149 -0
  182. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  183. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  184. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  185. package/dist/workflows/dag/backend-test-result-contract.js +4 -0
  186. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  187. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  188. package/dist/workflows/dag/budget-enforcement.js +10 -0
  189. package/dist/workflows/dag/context-receipt.js +305 -0
  190. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  191. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  192. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  193. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  194. package/dist/workflows/dag/init-hybrid.js +151 -4
  195. package/dist/workflows/dag/node-execution.js +112 -27
  196. package/dist/workflows/dag/path-safety.js +1 -0
  197. package/dist/workflows/dag/prompt.js +29 -4
  198. package/dist/workflows/dag/rerun-plan.js +555 -1
  199. package/dist/workflows/dag/rerun-run.js +601 -15
  200. package/dist/workflows/dag/runner.js +20 -1
  201. package/dist/workflows/dag/skill-snapshot.js +17 -0
  202. package/dist/workflows/dag/types.js +183 -7
  203. package/docs/architecture/runtime-boundaries.md +8 -7
  204. package/docs/architecture/worker-and-feature.md +1 -1
  205. package/docs/templates/agent-dag.schema.json +32 -0
  206. package/docs/templates/backend-test-dag.json +6 -5
  207. package/docs/templates/init-managed-agents.md +1 -1
  208. package/docs/templates/product-line/task.yaml +1 -1
  209. package/package.json +5 -2
  210. package/skills/loop-agent/references/command-reference.md +21 -1
  211. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  212. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  213. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  214. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  215. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  216. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  217. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
  218. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -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,202 @@
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
+ import { assertValidMaterializedDagTask } from "./validate.js";
11
+ const rerunBindingsV1Schema = z
12
+ .object({
13
+ sourceBindingMatched: z.boolean(),
14
+ taskContractBindingMatched: z.boolean(),
15
+ controllerIdentityMatched: z.boolean(),
16
+ skillSnapshotVerified: z.boolean(),
17
+ workspaceMatched: z.boolean(),
18
+ })
19
+ .strict();
20
+ const hash64Schema = z.string().regex(/^[a-f0-9]{64}$/);
21
+ const highRiskContinuationAuthorityV1Schema = z
22
+ .object({
23
+ schemaVersion: z.literal(1),
24
+ workspaceFingerprint: hash64Schema,
25
+ writer: z
26
+ .object({
27
+ nodeIds: z.array(z.string().min(1)).min(1),
28
+ writeSet: z.array(z.string().min(1)).min(1),
29
+ writeSetSha256: hash64Schema,
30
+ })
31
+ .strict()
32
+ .optional(),
33
+ dynamic: z
34
+ .object({
35
+ nodeIds: z.array(z.string().min(1)).min(1),
36
+ graphSha256: hash64Schema,
37
+ })
38
+ .strict()
39
+ .optional(),
40
+ budget: z
41
+ .object({
42
+ parentLedgerSha256: hash64Schema,
43
+ inheritedConsumed: z
44
+ .object({
45
+ tokens: z.number().int().nonnegative().nullable(),
46
+ wallTimeMs: z.number().nonnegative(),
47
+ executorCalls: z.number().int().nonnegative(),
48
+ repairPasses: z.number().int().nonnegative(),
49
+ peakContextChars: z.number().int().nonnegative(),
50
+ missingTokenNodeIds: z.array(z.string()),
51
+ })
52
+ .strict(),
53
+ })
54
+ .strict()
55
+ .optional(),
56
+ })
57
+ .strict();
58
+ const rerunPlanCommonSchema = {
59
+ eligible: z.boolean(),
60
+ parentRunId: z.string().min(1),
61
+ parentLifecycle: z.enum(["completed", "dry-run", "init-only"]),
62
+ selectedNodeId: z.string().min(1),
63
+ effectiveFromNodeId: z.string().min(1).optional(),
64
+ rewriteApplied: z.boolean(),
65
+ resetNodeIds: z.array(z.string().min(1)),
66
+ importedNodeIds: z.array(z.string().min(1)),
67
+ blockingNodes: z.array(z.object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) }).strict()),
68
+ reasonCodes: z.array(z.string().min(1)),
69
+ blockedReasons: z.array(z.string().min(1)),
70
+ risk: z.enum(["low", "medium", "high"]),
71
+ verificationPolicy: z
72
+ .object({
73
+ mode: z.enum(["inherited", "rerun"]),
74
+ nodeIds: z.array(z.string().min(1)),
75
+ inheritedFromRunId: z.string().min(1).optional(),
76
+ })
77
+ .strict()
78
+ .optional(),
79
+ workerAssociation: z.discriminatedUnion("kind", [
80
+ z.object({ kind: z.literal("standalone") }).strict(),
81
+ z
82
+ .object({
83
+ kind: z.literal("worker-managed"),
84
+ featureId: z.string().min(1).optional(),
85
+ taskId: z.string().min(1).optional(),
86
+ workerRunId: z.string().min(1).optional(),
87
+ })
88
+ .strict(),
89
+ ]),
90
+ estimatedExecution: z
91
+ .object({
92
+ nodeCount: z.number().int().nonnegative(),
93
+ piCallCount: z.number().int().nonnegative(),
94
+ shellNodeCount: z.number().int().nonnegative(),
95
+ avoidedNodeCount: z.number().int().nonnegative(),
96
+ avoidedPiCallCount: z.number().int().nonnegative(),
97
+ })
98
+ .strict(),
99
+ suggestedAction: z.enum([
100
+ "dag-rerun",
101
+ "standalone-task-rerun",
102
+ "worker-task-retry",
103
+ "regenerate",
104
+ "resume",
105
+ "reconcile",
106
+ "manual",
107
+ ]),
108
+ promptOverrides: z
109
+ .array(z
110
+ .object({
111
+ nodeId: z.string().min(1),
112
+ mode: z.enum(["replace", "append"]),
113
+ basePromptSha256: z.string().regex(/^[a-f0-9]{64}$/),
114
+ promptSha256: z.string().regex(/^[a-f0-9]{64}$/),
115
+ })
116
+ .strict())
117
+ .optional(),
118
+ workerContinuationAuthority: workerContinuationAuthorityV1Schema.optional(),
119
+ highRiskAuthority: highRiskContinuationAuthorityV1Schema.optional(),
120
+ };
121
+ export const dagRerunPlanV2Schema = z
122
+ .object({
123
+ schemaVersion: z.literal(2),
124
+ ...rerunPlanCommonSchema,
125
+ trigger: z.enum(["node-rerun", "prompt-override", "artifact-override"]),
126
+ artifactOverrides: artifactOverrideSetV1Schema.optional(),
127
+ impact: z
128
+ .object({
129
+ producerNodeId: z.string().min(1).optional(),
130
+ directConsumerNodeIds: z.array(z.string().min(1)),
131
+ effectiveFromNodeId: z.string().min(1),
132
+ affectedNodeIds: z.array(z.string().min(1)),
133
+ importedNodeIds: z.array(z.string().min(1)),
134
+ blockedNodeIds: z.array(z
135
+ .object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) })
136
+ .strict()),
137
+ })
138
+ .strict(),
139
+ bindings: rerunBindingsV1Schema
140
+ .extend({
141
+ artifactGraphSha256: hash64Schema,
142
+ overrideSetHash: hash64Schema.optional(),
143
+ stagingRevisionSha256: hash64Schema.optional(),
144
+ parentWorkspaceFingerprint: hash64Schema.optional(),
145
+ currentWorkspaceFingerprint: hash64Schema.optional(),
146
+ budgetLedgerSha256: hash64Schema.optional(),
147
+ })
148
+ .strict(),
149
+ planHash: z.string().regex(/^[a-f0-9]{64}$/),
150
+ })
151
+ .strict()
152
+ .superRefine((plan, ctx) => {
153
+ const artifactTrigger = plan.trigger === "artifact-override";
154
+ if (artifactTrigger !== Boolean(plan.artifactOverrides)) {
155
+ ctx.addIssue({
156
+ code: z.ZodIssueCode.custom,
157
+ path: ["artifactOverrides"],
158
+ message: "artifact override trigger and override set must appear together",
159
+ });
160
+ }
161
+ if (plan.artifactOverrides &&
162
+ (plan.bindings.overrideSetHash !== plan.artifactOverrides.overrideSetHash ||
163
+ !plan.bindings.stagingRevisionSha256)) {
164
+ ctx.addIssue({
165
+ code: z.ZodIssueCode.custom,
166
+ path: ["bindings"],
167
+ message: "artifact override bindings must match the override set and revision",
168
+ });
169
+ }
170
+ if (plan.workerContinuationAuthority &&
171
+ plan.workerAssociation.kind !== "worker-managed") {
172
+ ctx.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ path: ["workerContinuationAuthority"],
175
+ message: "Worker continuation authority requires a Worker-managed parent",
176
+ });
177
+ }
178
+ if (plan.highRiskAuthority) {
179
+ if (plan.bindings.parentWorkspaceFingerprint !==
180
+ plan.highRiskAuthority.workspaceFingerprint ||
181
+ plan.bindings.currentWorkspaceFingerprint !==
182
+ plan.highRiskAuthority.workspaceFingerprint) {
183
+ ctx.addIssue({
184
+ code: z.ZodIssueCode.custom,
185
+ path: ["highRiskAuthority", "workspaceFingerprint"],
186
+ message: "high-risk authority must bind the matching workspace fingerprints",
187
+ });
188
+ }
189
+ if (plan.highRiskAuthority.budget &&
190
+ plan.bindings.budgetLedgerSha256 !==
191
+ plan.highRiskAuthority.budget.parentLedgerSha256) {
192
+ ctx.addIssue({
193
+ code: z.ZodIssueCode.custom,
194
+ path: ["bindings", "budgetLedgerSha256"],
195
+ message: "budget ledger binding must match high-risk authority",
196
+ });
197
+ }
198
+ }
199
+ });
7
200
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
8
201
  const SAFE_SHELL_GATE_KEYS = [
9
202
  "verdictGate",
@@ -117,6 +310,68 @@ function isWriterTask(task, spec) {
117
310
  const writePolicy = resolveWritePolicy(task, spec);
118
311
  return writePolicy === "exclusive" || writePolicy === "docs-only";
119
312
  }
313
+ function passesWriterBoundaryValidation(task, parentSpec) {
314
+ try {
315
+ assertValidMaterializedDagTask(task, parentSpec);
316
+ return true;
317
+ }
318
+ catch {
319
+ return false;
320
+ }
321
+ }
322
+ function isBoundedArtifactContinuationWriter(task, parentSpec) {
323
+ return (task.executor === "pi" &&
324
+ task.toolProfile === "write" &&
325
+ task.writePolicy === "exclusive" &&
326
+ (task.writeSet?.length ?? 0) > 0 &&
327
+ (task.allowedPaths?.length ?? 0) > 0 &&
328
+ (task.forbiddenPaths?.length ?? 0) > 0 &&
329
+ passesWriterBoundaryValidation(task, parentSpec));
330
+ }
331
+ function isDynamicWriterTemplate(task) {
332
+ return (task.toolProfile === "write" ||
333
+ task.writePolicy === "exclusive" ||
334
+ task.writePolicy === "docs-only");
335
+ }
336
+ function isBoundedDynamicWriterTemplate(task, parentSpec) {
337
+ if (!(task.executor === "pi" &&
338
+ task.toolProfile === "write" &&
339
+ task.writePolicy === "exclusive" &&
340
+ (task.writeSet?.length ?? 0) > 0 &&
341
+ (task.allowedPaths?.length ?? 0) > 0 &&
342
+ (task.forbiddenPaths?.length ?? 0) > 0))
343
+ return false;
344
+ return passesWriterBoundaryValidation({
345
+ id: "continuation-dynamic-writer-template",
346
+ depends_on: [],
347
+ executor: task.executor,
348
+ role: task.role,
349
+ complexity: task.complexity,
350
+ subtask_prompt: task.subtaskPromptTemplate,
351
+ toolProfile: task.toolProfile,
352
+ writePolicy: task.writePolicy,
353
+ allowedPaths: task.allowedPaths,
354
+ forbiddenPaths: task.forbiddenPaths,
355
+ writeSet: task.writeSet,
356
+ outputContract: task.outputContract,
357
+ }, parentSpec);
358
+ }
359
+ function collectDynamicWriterTemplates(task) {
360
+ const writers = [];
361
+ const expansionChild = task.dynamicExpansion?.childTask;
362
+ if (expansionChild && isDynamicWriterTemplate(expansionChild)) {
363
+ writers.push({ id: `${task.id}#child`, task: expansionChild });
364
+ }
365
+ for (const bodyTask of task.dynamicLoopUntil?.bodyTasks ?? []) {
366
+ if (isDynamicWriterTemplate(bodyTask)) {
367
+ writers.push({ id: `${task.id}#${bodyTask.id}`, task: bodyTask });
368
+ }
369
+ }
370
+ return writers;
371
+ }
372
+ function hasOnlyBoundedDynamicWriters(task, parentSpec) {
373
+ return collectDynamicWriterTemplates(task).every(({ task: writer }) => isBoundedDynamicWriterTemplate(writer, parentSpec));
374
+ }
120
375
  function hasDynamicRuntime(task) {
121
376
  return Boolean(task.dynamicExpansion ||
122
377
  task.dynamicReduction ||
@@ -174,6 +429,34 @@ function isCanonicalGeneratedVerifyShell(task) {
174
429
  return false;
175
430
  return true;
176
431
  }
432
+ /**
433
+ * Frozen frontend workflow shells are deterministic runtime adapters. They may
434
+ * materialize run-owned evidence or run the already-frozen verification
435
+ * commands, but they do not receive a generic shell-continuation exemption.
436
+ */
437
+ function isCanonicalFrontendArtifactContinuationShell(task) {
438
+ if (task.executor !== "shell" || task.writePolicy !== "read-only" || !task.shell) {
439
+ return false;
440
+ }
441
+ const shell = task.shell;
442
+ if (shell.envAllowlist?.length || shell.nonZeroExitPolicy === "record")
443
+ return false;
444
+ switch (task.id) {
445
+ case "frontend-prewrite-gate-shell":
446
+ return Boolean(shell.frontendPrewriteGate) && (shell.commands?.length ?? 0) === 0;
447
+ case "frontend-lint-baseline-shell":
448
+ return Boolean(shell.frontendLintBaseline &&
449
+ shell.commands?.length === shell.frontendLintBaseline.lintCommands.length &&
450
+ shell.commands.every((command, index) => command === shell.frontendLintBaseline.lintCommands[index]));
451
+ case "frontend-verify-assess-shell":
452
+ case "frontend-reverify-shell":
453
+ return Boolean(shell.frontendVerificationBundle) && (shell.commands?.length ?? 0) === 0;
454
+ case "frontend-review-context-shell":
455
+ return Boolean(shell.frontendReviewContext) && (shell.commands?.length ?? 0) === 0;
456
+ default:
457
+ return false;
458
+ }
459
+ }
177
460
  function isSafeGateOnlyShell(task) {
178
461
  if (task.executor !== "shell")
179
462
  return false;
@@ -323,6 +606,10 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
323
606
  continue;
324
607
  }
325
608
  if (isWriterTask(task, spec)) {
609
+ if (options.artifactContinuation === true &&
610
+ isBoundedArtifactContinuationWriter(task, spec)) {
611
+ continue;
612
+ }
326
613
  if (nodeId === options.allowedTransportWriterNodeId)
327
614
  continue;
328
615
  if (nodeId === options.allowedExclusiveShellFromNodeId &&
@@ -345,6 +632,10 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
345
632
  continue;
346
633
  }
347
634
  if (hasDynamicRuntime(task)) {
635
+ if (options.artifactContinuation === true &&
636
+ hasOnlyBoundedDynamicWriters(task, spec)) {
637
+ continue;
638
+ }
348
639
  blockingNodes.push({
349
640
  nodeId,
350
641
  reasonCode: "dynamic-rerun-unsupported",
@@ -367,7 +658,9 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
367
658
  }
368
659
  if (task.executor === "shell") {
369
660
  if (!isSafeGateOnlyShell(task) &&
370
- !isCanonicalGeneratedVerifyShell(task)) {
661
+ !isCanonicalGeneratedVerifyShell(task) &&
662
+ !(options.artifactContinuation === true &&
663
+ isCanonicalFrontendArtifactContinuationShell(task))) {
371
664
  blockingNodes.push({
372
665
  nodeId,
373
666
  reasonCode: "restart-subgraph-contains-unsafe-shell",
@@ -480,6 +773,267 @@ export function computePlanHash(planWithoutHash) {
480
773
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
481
774
  return createHash("sha256").update(canonical, "utf8").digest("hex");
482
775
  }
776
+ function hashCanonicalValue(value) {
777
+ return createHash("sha256")
778
+ .update(JSON.stringify(sortKeysDeep(value)), "utf8")
779
+ .digest("hex");
780
+ }
781
+ function collectHighRiskContinuationAuthority(input) {
782
+ const tasks = taskById(input.parentSpec);
783
+ const writerNodeIds = [];
784
+ const writeSet = new Set();
785
+ const dynamicNodeIds = [];
786
+ for (const nodeId of input.affectedNodeIds) {
787
+ const task = tasks.get(nodeId);
788
+ if (!task)
789
+ continue;
790
+ if (isBoundedArtifactContinuationWriter(task, input.parentSpec)) {
791
+ writerNodeIds.push(nodeId);
792
+ for (const pattern of task.writeSet ?? [])
793
+ writeSet.add(pattern);
794
+ }
795
+ if (hasDynamicRuntime(task)) {
796
+ dynamicNodeIds.push(nodeId);
797
+ for (const writer of collectDynamicWriterTemplates(task)) {
798
+ writerNodeIds.push(writer.id);
799
+ for (const pattern of writer.task.writeSet ?? [])
800
+ writeSet.add(pattern);
801
+ }
802
+ }
803
+ }
804
+ const ledger = input.parentState.budgetLedger;
805
+ const hasHardBudget = (input.parentSpec.budget?.mode ?? input.parentState.budget?.mode) === "hard";
806
+ const highRisk = writerNodeIds.length > 0 || dynamicNodeIds.length > 0 || hasHardBudget;
807
+ if (!highRisk || !input.workspaceFingerprint)
808
+ return undefined;
809
+ const normalizedWriters = [...new Set(writerNodeIds)].sort();
810
+ const normalizedWriteSet = [...writeSet].sort();
811
+ const normalizedDynamic = [...new Set(dynamicNodeIds)].sort();
812
+ return {
813
+ schemaVersion: 1,
814
+ workspaceFingerprint: input.workspaceFingerprint,
815
+ ...(normalizedWriters.length > 0
816
+ ? {
817
+ writer: {
818
+ nodeIds: normalizedWriters,
819
+ writeSet: normalizedWriteSet,
820
+ writeSetSha256: hashCanonicalValue(normalizedWriteSet),
821
+ },
822
+ }
823
+ : {}),
824
+ ...(normalizedDynamic.length > 0
825
+ ? {
826
+ dynamic: {
827
+ nodeIds: normalizedDynamic,
828
+ graphSha256: hashCanonicalValue(normalizedDynamic.map((nodeId) => tasks.get(nodeId))),
829
+ },
830
+ }
831
+ : {}),
832
+ ...(hasHardBudget && ledger
833
+ ? {
834
+ budget: {
835
+ parentLedgerSha256: hashCanonicalValue(ledger),
836
+ inheritedConsumed: structuredClone(ledger.consumed),
837
+ },
838
+ }
839
+ : {}),
840
+ };
841
+ }
842
+ export function evaluateArtifactOverrideRerunPlan(input) {
843
+ const graph = buildArtifactBindingGraph(input.parentSpec);
844
+ const declaration = graph.artifacts.find((artifact) => artifact.artifactId === input.artifactId);
845
+ const impact = computeArtifactImpact(input.parentSpec, input.artifactId);
846
+ const reasonCodes = [];
847
+ const blockedReasons = [];
848
+ const blockingNodes = [];
849
+ const workerAssociation = input.workerAssociation ?? { kind: "standalone" };
850
+ const block = (code, nodeId) => {
851
+ reasonCodes.push(code);
852
+ blockedReasons.push(code);
853
+ if (nodeId)
854
+ blockingNodes.push({ nodeId, reasonCode: code });
855
+ };
856
+ if (!graph.available)
857
+ block("ARTIFACT_BINDING_UNAVAILABLE");
858
+ if (!declaration)
859
+ block("ARTIFACT_IDENTITY_AMBIGUOUS");
860
+ if (!impact)
861
+ block("ARTIFACT_CONSUMER_UNKNOWN", declaration?.producerNodeId);
862
+ if (input.parentLifecycle !== "completed")
863
+ block("parent-lifecycle-ineligible");
864
+ const overrideRevisionId = input.artifactOverrides.overrides[0]?.revisionId;
865
+ const authority = input.workerContinuationAuthority;
866
+ if (workerAssociation.kind === "worker-managed") {
867
+ if (!authority) {
868
+ block("WORKER_MANAGED_CONTINUATION_UNSUPPORTED");
869
+ }
870
+ else if (!workerAssociation.workerRunId ||
871
+ !workerAssociation.featureId ||
872
+ !workerAssociation.taskId ||
873
+ authority.parentWorkerRunId !== workerAssociation.workerRunId ||
874
+ authority.featureId !== workerAssociation.featureId ||
875
+ authority.taskId !== workerAssociation.taskId ||
876
+ authority.revisionId !== overrideRevisionId) {
877
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
878
+ }
879
+ }
880
+ else if (authority) {
881
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
882
+ }
883
+ if (input.parentSpec.convergence?.enabled || input.parentState.convergence?.enabled) {
884
+ block("convergence-rerun-unsupported");
885
+ }
886
+ if (input.parentSpec.evaluation || input.parentState.evaluation) {
887
+ block("evaluation-rerun-unsupported");
888
+ }
889
+ const hardBudget = (input.parentSpec.budget?.mode ?? input.parentState.budget?.mode) === "hard";
890
+ if (hardBudget) {
891
+ const ledger = input.parentState.budgetLedger;
892
+ if (!ledger) {
893
+ block("budget-lineage-missing");
894
+ }
895
+ else if (ledger.status === "breached" || ledger.breaches.length > 0) {
896
+ block("budget-lineage-breached");
897
+ }
898
+ else if (ledger.mode !== "hard" ||
899
+ JSON.stringify(ledger.limits) !==
900
+ JSON.stringify(input.parentSpec.budget?.limits ?? input.parentState.budget?.limits)) {
901
+ block("budget-lineage-drift");
902
+ }
903
+ }
904
+ for (const [name, matched] of Object.entries(input.bindings)) {
905
+ if (!matched)
906
+ block(`${name}-drift`);
907
+ }
908
+ const override = input.artifactOverrides.overrides[0];
909
+ const parentFact = declaration
910
+ ? input.parentState.nodes[declaration.producerNodeId]?.declaredArtifacts?.find((fact) => fact.artifactId === input.artifactId)
911
+ : undefined;
912
+ if (!declaration ||
913
+ declaration.revisionPolicy !== "replace-file" ||
914
+ parentFact?.status !== "materialized" ||
915
+ parentFact.revisionEligible !== true ||
916
+ !parentFact.sha256) {
917
+ block("ARTIFACT_NOT_REVISION_ELIGIBLE", declaration?.producerNodeId);
918
+ }
919
+ if (!override ||
920
+ override.parentArtifact.runId !== input.parentRunId ||
921
+ override.parentArtifact.nodeId !== declaration?.producerNodeId ||
922
+ override.parentArtifact.path !== declaration?.path ||
923
+ override.parentArtifact.sha256 !== parentFact?.sha256 ||
924
+ override.supersedes !== parentFact?.sha256 ||
925
+ override.replacement.sha256 !== input.stagingRevisionSha256) {
926
+ block("ARTIFACT_REVISION_DRIFT", declaration?.producerNodeId);
927
+ }
928
+ const affectedNodeIds = impact?.affectedNodeIds ?? [];
929
+ const importedNodeIds = impact?.importedNodeIds ?? [];
930
+ const closureSafety = assessResetClosureSafety(input.parentSpec, affectedNodeIds, {
931
+ parentNodes: input.parentState.nodes,
932
+ artifactContinuation: true,
933
+ });
934
+ for (const entry of closureSafety.blockingNodes) {
935
+ blockingNodes.push(entry);
936
+ block("ARTIFACT_IMPACT_UNSAFE");
937
+ }
938
+ for (const nodeId of importedNodeIds) {
939
+ const importedTask = taskById(input.parentSpec).get(nodeId);
940
+ if (importedTask?.decisionGate?.enabled) {
941
+ const code = "restart-subgraph-contains-decision-gate";
942
+ blockingNodes.push({ nodeId, reasonCode: code });
943
+ block(code);
944
+ }
945
+ const status = input.parentState.nodes[nodeId]?.status;
946
+ if (status !== "FINISHED" && status !== "SKIPPED") {
947
+ block("parent-facts-invalid", nodeId);
948
+ }
949
+ }
950
+ const highRiskAuthority = collectHighRiskContinuationAuthority({
951
+ parentSpec: input.parentSpec,
952
+ parentState: input.parentState,
953
+ affectedNodeIds,
954
+ workspaceFingerprint: input.parentTerminalWorkspaceFingerprint,
955
+ });
956
+ const highRiskExpected = affectedNodeIds.some((nodeId) => {
957
+ const task = taskById(input.parentSpec).get(nodeId);
958
+ return Boolean(task && (isWriterTask(task, input.parentSpec) || hasDynamicRuntime(task)));
959
+ }) || hardBudget;
960
+ if (highRiskExpected) {
961
+ if (!input.parentTerminalWorkspaceFingerprint ||
962
+ !input.currentWorkspaceFingerprint) {
963
+ block("workspace-checkpoint-missing");
964
+ }
965
+ else if (input.parentTerminalWorkspaceFingerprint !==
966
+ input.currentWorkspaceFingerprint) {
967
+ block("workspace-drift");
968
+ }
969
+ else if (!highRiskAuthority) {
970
+ block("HIGH_RISK_AUTHORITY_MISSING");
971
+ }
972
+ }
973
+ const uniqueReasons = [...new Set(reasonCodes)];
974
+ const uniqueBlocked = [...new Set(blockedReasons)];
975
+ const eligible = uniqueBlocked.length === 0;
976
+ const shellNodeIds = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "shell");
977
+ const piCallCount = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length;
978
+ const planWithoutHash = {
979
+ schemaVersion: 2,
980
+ eligible,
981
+ parentRunId: input.parentRunId,
982
+ parentLifecycle: input.parentLifecycle,
983
+ selectedNodeId: declaration?.producerNodeId ?? input.artifactId,
984
+ effectiveFromNodeId: impact?.effectiveFromNodeId,
985
+ rewriteApplied: false,
986
+ resetNodeIds: affectedNodeIds,
987
+ importedNodeIds,
988
+ blockingNodes,
989
+ reasonCodes: uniqueReasons,
990
+ blockedReasons: uniqueBlocked,
991
+ risk: !eligible ? "high" : affectedNodeIds.length <= 2 ? "low" : "medium",
992
+ bindings: {
993
+ ...input.bindings,
994
+ artifactGraphSha256: graph.artifactGraphSha256,
995
+ overrideSetHash: input.artifactOverrides.overrideSetHash,
996
+ stagingRevisionSha256: input.stagingRevisionSha256,
997
+ ...(input.parentTerminalWorkspaceFingerprint
998
+ ? { parentWorkspaceFingerprint: input.parentTerminalWorkspaceFingerprint }
999
+ : {}),
1000
+ ...(input.currentWorkspaceFingerprint
1001
+ ? { currentWorkspaceFingerprint: input.currentWorkspaceFingerprint }
1002
+ : {}),
1003
+ ...(highRiskAuthority?.budget
1004
+ ? { budgetLedgerSha256: highRiskAuthority.budget.parentLedgerSha256 }
1005
+ : {}),
1006
+ },
1007
+ ...(shellNodeIds.length > 0
1008
+ ? { verificationPolicy: { mode: "rerun", nodeIds: shellNodeIds } }
1009
+ : {}),
1010
+ workerAssociation,
1011
+ ...(authority ? { workerContinuationAuthority: authority } : {}),
1012
+ ...(highRiskAuthority ? { highRiskAuthority } : {}),
1013
+ estimatedExecution: {
1014
+ nodeCount: affectedNodeIds.length,
1015
+ piCallCount,
1016
+ shellNodeCount: shellNodeIds.length,
1017
+ avoidedNodeCount: importedNodeIds.length,
1018
+ avoidedPiCallCount: importedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length,
1019
+ },
1020
+ suggestedAction: eligible ? "dag-rerun" : workerAssociation.kind === "worker-managed" ? "worker-task-retry" : "manual",
1021
+ trigger: "artifact-override",
1022
+ artifactOverrides: input.artifactOverrides,
1023
+ impact: {
1024
+ ...(impact ? { producerNodeId: impact.producerNodeId } : {}),
1025
+ directConsumerNodeIds: impact?.directConsumerNodeIds ?? [],
1026
+ effectiveFromNodeId: impact?.effectiveFromNodeId ?? declaration?.producerNodeId ?? input.artifactId,
1027
+ affectedNodeIds,
1028
+ importedNodeIds,
1029
+ blockedNodeIds: blockingNodes,
1030
+ },
1031
+ };
1032
+ return dagRerunPlanV2Schema.parse({
1033
+ ...planWithoutHash,
1034
+ planHash: computePlanHash(planWithoutHash),
1035
+ });
1036
+ }
483
1037
  function countExecutorNodes(spec, nodeIds, executor) {
484
1038
  const tasks = taskById(spec);
485
1039
  return nodeIds.filter((nodeId) => tasks.get(nodeId)?.executor === executor)