@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.10

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 (226) hide show
  1. package/CHANGELOG.md +379 -416
  2. package/dist/application/evaluation/budget.js +21 -0
  3. package/dist/application/evaluation/corpus-hash.js +10 -15
  4. package/dist/application/evaluation/corpus.js +2 -1
  5. package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
  6. package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
  7. package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
  8. package/dist/application/evaluation/frontend-pair-registration.js +143 -0
  9. package/dist/application/evaluation/frontend-paired-summary.js +150 -0
  10. package/dist/application/evaluation/frontend-run-observation.js +144 -0
  11. package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
  12. package/dist/application/evaluation/types.js +45 -12
  13. package/dist/application/task-lifecycle/observe.js +26 -1
  14. package/dist/build-stamp.json +3 -3
  15. package/dist/cli/command-definitions.js +11 -0
  16. package/dist/cli/program.js +4 -0
  17. package/dist/commands/task-advance.js +3 -5
  18. package/dist/commands/task-source-prepare.js +13 -1
  19. package/dist/executors/dag-pi/guards.js +4 -0
  20. package/dist/executors/dag-pi/model-config.js +30 -0
  21. package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
  22. package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
  23. package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
  24. package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
  25. package/dist/executors/dag-pi/plan/record-tools.js +861 -0
  26. package/dist/executors/dag-pi/plan/schema.js +129 -0
  27. package/dist/executors/dag-pi/plan/session.js +1 -0
  28. package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
  29. package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
  30. package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
  31. package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
  32. package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
  33. package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
  34. package/dist/executors/dag-pi-executor.js +398 -3384
  35. package/dist/executors/pi-executor.js +6 -1
  36. package/dist/executors/pi-sdk-executor.js +112 -19
  37. package/dist/executors/shell-executor.js +88 -12
  38. package/dist/executors/shell-presets.js +1 -1
  39. package/dist/executors/shell-write-guard.js +26 -3
  40. package/dist/infrastructure/console/operation-store.js +48 -14
  41. package/dist/infrastructure/evaluation/corpus-store.js +37 -12
  42. package/dist/shared/file-identity.js +23 -0
  43. package/dist/shared/git-workspace-status.js +482 -0
  44. package/dist/shared/operator/capabilities.js +10 -5
  45. package/dist/shared/path-safety.js +2 -2
  46. package/dist/shared/wizard-local-telemetry.js +51 -0
  47. package/dist/task/config-types.js +22 -8
  48. package/dist/task/contract/project.js +13 -0
  49. package/dist/task/contract/schema.js +2 -7
  50. package/dist/task/frontend-preflight.js +1 -1
  51. package/dist/task/source-prepare/parse-intent.js +124 -5
  52. package/dist/task/source-prepare/prepare.js +6 -1
  53. package/dist/task/source-prepare/semantic-intake.js +18 -3
  54. package/dist/task/source-prepare/source-execution.js +65 -0
  55. package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
  56. package/dist/task/source-prepare/source-provider-budget.js +290 -0
  57. package/dist/worker/console/chat/assistant-content.js +5 -34
  58. package/dist/worker/console/chat/chat-event-store.js +62 -10
  59. package/dist/worker/console/chat/context-insights.js +723 -0
  60. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  61. package/dist/worker/console/chat/db-connections.js +854 -0
  62. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
  63. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
  64. package/dist/worker/console/chat/pi-runtime.js +445 -36
  65. package/dist/worker/console/chat/provider-error.js +0 -12
  66. package/dist/worker/console/chat/repo-browser.js +52 -4
  67. package/dist/worker/console/chat/routes.js +577 -72
  68. package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
  69. package/dist/worker/console/chat/session-store.js +147 -19
  70. package/dist/worker/console/chat/shortcuts.js +7 -0
  71. package/dist/worker/console/chat/timeline-message-order.js +22 -0
  72. package/dist/worker/console/chat/tools.js +34 -0
  73. package/dist/worker/console/chat/turn-process.js +237 -66
  74. package/dist/worker/console/chat/user-turn-outline.js +19 -0
  75. package/dist/worker/console/console-update-runtime.js +2 -2
  76. package/dist/worker/console/dag-confirmation.js +3 -0
  77. package/dist/worker/console/dag-execution-receipt.js +3 -0
  78. package/dist/worker/console/operator-actions.js +32 -3
  79. package/dist/worker/console/operator-user-error.js +28 -1
  80. package/dist/worker/console/pi-readiness.js +4 -4
  81. package/dist/worker/console/prd-intake-bridge.js +5 -12
  82. package/dist/worker/console/recovery-cta.js +14 -14
  83. package/dist/worker/console/server.js +5 -1
  84. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
  85. package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
  86. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
  87. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
  88. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
  89. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
  90. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
  100. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
  101. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
  102. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
  103. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
  104. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
  105. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
  110. package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
  111. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
  112. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
  113. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
  114. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
  115. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
  116. package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
  117. package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
  118. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
  119. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
  120. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
  121. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
  122. package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
  123. package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
  124. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
  125. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
  126. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
  127. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
  128. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
  129. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
  130. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
  131. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
  132. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
  133. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
  134. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
  135. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
  136. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
  137. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
  138. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
  139. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
  140. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
  141. package/dist/worker/console/static/index.html +2 -2
  142. package/dist/worker/console/static-src/app/console-types.js +9 -1
  143. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  144. package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
  145. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
  146. package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
  147. package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
  148. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  149. package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
  150. package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
  151. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
  152. package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
  153. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
  154. package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
  155. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
  156. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
  157. package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
  158. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
  159. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
  160. package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
  161. package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
  162. package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
  163. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
  164. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
  165. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
  166. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  167. package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
  168. package/dist/worker/console/workspace-context.js +2 -0
  169. package/dist/worker/console/workspace-initialization.js +3 -3
  170. package/dist/worker/observability/read-model.js +23 -2
  171. package/dist/worker/observe/node-context-insights.js +479 -0
  172. package/dist/worker/observe/static/console-theme.css +174 -0
  173. package/dist/worker/observe/static/console-theme.js +36 -0
  174. package/dist/worker/observe/static/operator-chrome.css +85 -0
  175. package/dist/worker/observe/static/operator-chrome.js +51 -0
  176. package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
  177. package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
  178. package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
  179. package/dist/workflows/dag/budget-enforcement.js +31 -2
  180. package/dist/workflows/dag/frontend-closeout.js +3 -1
  181. package/dist/workflows/dag/frontend-contract-facts.js +11 -8
  182. package/dist/workflows/dag/frontend-design-policy.js +6 -6
  183. package/dist/workflows/dag/frontend-durable-tools.js +90 -70
  184. package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
  185. package/dist/workflows/dag/frontend-plan-canary.js +66 -16
  186. package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
  187. package/dist/workflows/dag/frontend-plan-progress.js +92 -0
  188. package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
  189. package/dist/workflows/dag/frontend-recovery-run.js +51 -46
  190. package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
  191. package/dist/workflows/dag/frontend-review-context.js +29 -1
  192. package/dist/workflows/dag/frontend-review-scopes.js +92 -2
  193. package/dist/workflows/dag/frontend-risk.js +8 -3
  194. package/dist/workflows/dag/frontend-root-observation.js +261 -0
  195. package/dist/workflows/dag/frontend-session-budget.js +52 -39
  196. package/dist/workflows/dag/frontend-session-context.js +10 -0
  197. package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
  198. package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
  199. package/dist/workflows/dag/frontend-verification-trace.js +6 -0
  200. package/dist/workflows/dag/frontend-writer-admission.js +3 -1
  201. package/dist/workflows/dag/init-hybrid.js +108 -37
  202. package/dist/workflows/dag/node-execution.js +11 -11
  203. package/dist/workflows/dag/output-protocol.js +1 -0
  204. package/dist/workflows/dag/rerun-feedback.js +35 -4
  205. package/dist/workflows/dag/rerun-task.js +62 -66
  206. package/dist/workflows/dag/runner.js +58 -12
  207. package/dist/workflows/dag/types.js +15 -8
  208. package/dist/workflows/loop/benchmark.js +11 -8
  209. package/docs/README.md +1 -1
  210. package/docs/architecture/runtime-boundaries.md +1 -1
  211. package/docs/governance/harness-methodology-verification.md +11 -0
  212. package/docs/templates/backend-test-dag.json +5 -4
  213. package/docs/templates/frontend-implementation-contract.schema.json +31 -11
  214. package/package.json +6 -4
  215. package/skills/frontend-design-review/SKILL.md +8 -11
  216. package/skills/frontend-design-review/references/review-checklist.md +3 -4
  217. package/skills/frontend-review/SKILL.md +5 -1
  218. package/skills/loop-agent/references/command-reference.md +8 -0
  219. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  220. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
  221. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
  222. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
  223. package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
  224. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
  225. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
  226. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
@@ -6,6 +6,26 @@ import { buildFrontendSplitAggregateCloseout, computeFrontendSplitDigest, finali
6
6
  import { frontendCloseoutFactsSchema, frontendTestHandoffSchema, } from "../../workflows/dag/frontend-closeout.js";
7
7
  import { listJsonFiles, readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
8
8
  export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out", "restarting"]);
9
+ /**
10
+ * Operation states that hold an admission slot. needs-reconcile requires
11
+ * inspection but does not itself prove a live operation; callers still check
12
+ * DAG/Worker/chat activity separately. restarting is the terminal receipt of
13
+ * a successful Console handoff, retained across boots, not a live slot.
14
+ * A needs-reconcile record remains visible as a non-business outcome, but
15
+ * must never strand later mutations after its runtime is gone.
16
+ */
17
+ export const OCCUPIED_OPERATION_STATES = new Set(["queued", "starting", "running"]);
18
+ /** True when the state can still make progress and therefore holds the slot. */
19
+ export function isOperationOccupied(state) {
20
+ return OCCUPIED_OPERATION_STATES.has(state);
21
+ }
22
+ /**
23
+ * True when no further state transition will ever occur: a business terminal
24
+ * (succeeded/failed/timed-out/restarting) or a stranded needs-reconcile.
25
+ */
26
+ export function isOperationSettled(state) {
27
+ return TERMINAL_OPERATION_STATES.has(state) || state === "needs-reconcile";
28
+ }
9
29
  function operationFile(appData, operationId) {
10
30
  return path.join(appData.operations, `${operationId}.json`);
11
31
  }
@@ -359,14 +379,11 @@ export class OperationStore {
359
379
  for (const op of await this.list()) {
360
380
  if (TERMINAL_OPERATION_STATES.has(op.state))
361
381
  continue;
362
- // A consoleUpdateAndInit whose record already proves a requested
363
- // handoff died before writing its terminal state: the update and
364
- // restart themselves succeeded — the booting process IS the
365
- // handoff target. Mark it succeeded instead of surfacing the
366
- // generic needs-reconcile failure copy to the operator.
382
+ // A consoleUpdateAndInit that proved its handoff before the process
383
+ // died must not be reported as a stranded failure: the booting
384
+ // process IS the handoff target, so the update actually succeeded.
367
385
  if (op.action === "consoleUpdateAndInit" &&
368
- op.state === "needs-reconcile" &&
369
- isProvenConsoleUpdateHandoff(op.result)) {
386
+ isProvenConsoleUpdateHandoff(op)) {
370
387
  changed.push(await this.update(op.operationId, {
371
388
  state: "succeeded",
372
389
  errorCode: undefined,
@@ -398,17 +415,34 @@ export function isTerminalOperationState(state) {
398
415
  return TERMINAL_OPERATION_STATES.has(state);
399
416
  }
400
417
  /**
401
- * True when a consoleUpdateAndInit result proves the handoff was requested
402
- * (install verified + init clean + new Console spawned). Used by boot
403
- * reconcile to close the record as succeeded even though the old process
404
- * died before writing its terminal state.
418
+ * True when a consoleUpdateAndInit record proves the handoff actually
419
+ * succeeded (install verified + new Console spawned), so boot reconcile can
420
+ * close it as succeeded even though the dying process never wrote its
421
+ * terminal state.
422
+ *
423
+ * The proof reads the maintenance projection rather than only `result.state`.
424
+ * The old check required `result.state === "restarting"`, but the executor
425
+ * writes `state` and `maintenance` together (`console-update-and-init.ts`),
426
+ * so any path that recorded the handoff proof together with a non-restarting
427
+ * `state` — most importantly the `needs-reconcile` record that
428
+ * `acceptConsoleUpdateAndInit` writes when the executor throws *after* a
429
+ * successful handoff — could never satisfy it. Such records were stranded
430
+ * forever: boot reconcile skipped them and they blocked later updates.
431
+ *
432
+ * `handoff.status === "requested"` is only ever set on the success path, and
433
+ * `package.status === "failed"` explicitly disqualifies a record, so this
434
+ * stays fail-closed for genuine failures.
405
435
  */
406
- function isProvenConsoleUpdateHandoff(result) {
436
+ export function isProvenConsoleUpdateHandoff(operation) {
437
+ if (!operation || operation.action !== "consoleUpdateAndInit")
438
+ return false;
439
+ const result = operation.result;
407
440
  if (!result || typeof result !== "object")
408
441
  return false;
409
442
  const shape = result;
410
- return (shape.state === "restarting" &&
411
- shape.maintenance?.handoff?.status === "requested");
443
+ if (shape.maintenance?.handoff?.status !== "requested")
444
+ return false;
445
+ return shape.maintenance?.package?.status !== "failed";
412
446
  }
413
447
  export function clipPreview(text, max = 4096) {
414
448
  if (text.length <= max)
@@ -1,3 +1,4 @@
1
+ import { readFileBoundarySafe } from "../../shared/path-safety.js";
1
2
  import { access, mkdir, readdir, readFile, rename, rm } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { mkdtemp } from "node:fs/promises";
@@ -30,28 +31,27 @@ async function pathExists(filePath) {
30
31
  function serializeManifest(manifest) {
31
32
  return `${JSON.stringify(manifest, null, 2)}\n`;
32
33
  }
34
+ function compareArtifactPaths(a, b) {
35
+ return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
36
+ }
33
37
  function normalizeTasksForStorage(tasks) {
34
38
  return [...tasks]
35
39
  .map((task) => ({
36
40
  ...task,
37
41
  taskRef: task.taskRef.replace(/\\/g, "/"),
38
42
  seeds: [...task.seeds].sort((a, b) => a - b),
43
+ ...("category" in task ? {
44
+ requirements: [...task.requirements].sort(compareArtifactPaths),
45
+ initialFiles: [...task.initialFiles].sort(compareArtifactPaths),
46
+ oracleFiles: [...task.oracleFiles].sort(compareArtifactPaths),
47
+ } : {}),
39
48
  }))
40
49
  .sort((a, b) => a.taskRef < b.taskRef ? -1 : a.taskRef > b.taskRef ? 1 : 0);
41
50
  }
42
51
  export async function materializeCorpusManifest(raw) {
43
52
  const input = corpusManifestInputSchema.parse(raw);
44
53
  const tasks = normalizeTasksForStorage(input.tasks);
45
- const withoutHash = {
46
- schemaVersion: 1,
47
- corpusId: input.corpusId,
48
- createdAt: input.createdAt,
49
- ...(input.description !== undefined
50
- ? { description: input.description }
51
- : {}),
52
- tasks,
53
- };
54
- const corpusHash = computeCorpusHash(withoutHash);
54
+ const corpusHash = computeCorpusHash(input);
55
55
  if (input.corpusHash) {
56
56
  const provided = formatContentSha(normalizeContentSha(input.corpusHash));
57
57
  if (provided !== corpusHash) {
@@ -59,10 +59,30 @@ export async function materializeCorpusManifest(raw) {
59
59
  }
60
60
  }
61
61
  return corpusManifestSchema.parse({
62
- ...withoutHash,
62
+ ...input,
63
+ tasks,
63
64
  corpusHash,
64
65
  });
65
66
  }
67
+ export async function assertCorpusArtifacts(repoRoot, manifest) {
68
+ if (manifest.schemaVersion !== 2)
69
+ return;
70
+ const snapshots = new Map();
71
+ for (const task of manifest.tasks)
72
+ for (const ref of [...task.requirements, ...task.initialFiles, ...task.oracleFiles]) {
73
+ const bytes = await readFileBoundarySafe({ boundaryRoot: repoRoot,
74
+ targetPath: path.resolve(repoRoot, ref.path), label: "frontend corpus artifact" });
75
+ if (sha256Hex(bytes) !== ref.sha256)
76
+ throw Error(`corpus artifact hash mismatch: ${ref.path}`);
77
+ snapshots.set(ref.path, bytes);
78
+ }
79
+ for (const [file, expected] of snapshots) {
80
+ const actual = await readFileBoundarySafe({ boundaryRoot: repoRoot,
81
+ targetPath: path.resolve(repoRoot, file), label: "frontend corpus artifact recheck" });
82
+ if (!actual.equals(expected))
83
+ throw Error(`corpus artifact changed during collection: ${file}`);
84
+ }
85
+ }
66
86
  async function assertManifestIntegrity(repoRoot, corpusId, rawManifest) {
67
87
  const integrityPath = path.join(corpusDir(repoRoot, corpusId), MANIFEST_INTEGRITY_FILE);
68
88
  const expected = (await readFile(integrityPath, "utf-8")).trim();
@@ -98,6 +118,7 @@ export async function readCorpusManifest(repoRoot, corpusId) {
98
118
  if (recomputed !== stored.corpusHash) {
99
119
  throw new Error(`corpusHash mismatch for ${corpusId}: expected ${recomputed}, got ${stored.corpusHash}`);
100
120
  }
121
+ await assertCorpusArtifacts(repoRoot, stored);
101
122
  return stored;
102
123
  }
103
124
  export async function listCorpusIds(repoRoot) {
@@ -117,7 +138,11 @@ export async function listCorpusIds(repoRoot) {
117
138
  }
118
139
  }
119
140
  export async function registerCorpusManifest(input) {
120
- const { repoRoot, manifest } = input;
141
+ const repoRoot = input.repoRoot;
142
+ const manifest = corpusManifestSchema.parse(input.manifest);
143
+ if (computeCorpusHash(manifest) !== manifest.corpusHash)
144
+ throw Error("corpusHash mismatch");
145
+ await assertCorpusArtifacts(repoRoot, manifest);
121
146
  assertCorpusId(manifest.corpusId);
122
147
  const manifestPath = corpusManifestPath(repoRoot, manifest.corpusId);
123
148
  if (await pathExists(manifestPath)) {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compare two stat records as the same file identity.
3
+ *
4
+ * POSIX: `dev` and `ino` together identify the inode; both must match.
5
+ *
6
+ * win32: libuv reports `dev` inconsistently across stat APIs — `fstat()`
7
+ * (from an open handle) returns the volume serial number while `lstat()`
8
+ * (from a path) returns 0. A cross-API comparison therefore always fails on
9
+ * Windows even when the path still refers to the same file, breaking every
10
+ * guard that rechecks identity after opening (user-reported on 0.42.1 and
11
+ * 0.43.0, Node 23.x). On win32 the `dev` field is skipped and `ino` plus the
12
+ * caller-side type/link/realpath checks remain the identity boundary.
13
+ *
14
+ * When both stats come from the same API the comparison is exact on every
15
+ * platform; the win32 relaxation only affects cross-API rechecks.
16
+ */
17
+ export function sameFileIdentity(opened, current, platform = process.platform) {
18
+ if (current.ino !== opened.ino)
19
+ return false;
20
+ if (platform !== "win32" && current.dev !== opened.dev)
21
+ return false;
22
+ return true;
23
+ }