@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,24 +1,56 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
6
|
import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
|
|
6
7
|
import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
|
|
7
8
|
import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
|
|
8
|
-
import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
|
|
9
|
+
import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, transferDagRunDir, } from "./lifecycle.js";
|
|
9
10
|
import { buildDagRunId, runDagContinuation, } from "./runner.js";
|
|
10
|
-
import { computePlanHash, evaluateDagRerunPlan, evaluateLiveDagRerunBindings, } from "./rerun-plan.js";
|
|
11
|
+
import { computePlanHash, evaluateArtifactOverrideRerunPlan, evaluateDagRerunPlan, evaluateLiveDagRerunBindings, } from "./rerun-plan.js";
|
|
11
12
|
import { prepareActiveRunDir, writeNodeRecord, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
12
13
|
import { cloneParentSkillSnapshotForContinuation, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
13
14
|
import { topoSortToRanks } from "./topo.js";
|
|
14
15
|
import { captureWorkspaceCheckpoint, readWorkspaceCheckpoint, WORKSPACE_CHECKPOINT_START_REL, WORKSPACE_CHECKPOINT_TERMINAL_REL, writeWorkspaceCheckpoint, } from "./workspace-checkpoint.js";
|
|
15
|
-
import {
|
|
16
|
+
import { captureWriterBaseline, readJournalFile, recordWriterAttemptFromCurrent, restoreJournal, writeJournalFile, } from "./frontend-writer-rollback.js";
|
|
16
17
|
export const RERUN_PLAN_REL_PATH = ".runtime/rerun-plan.json";
|
|
17
18
|
export const IMPORT_MANIFEST_REL_PATH = ".runtime/import-manifest.json";
|
|
18
19
|
export const WORKER_ASSOCIATION_REL_PATH = ".runtime/worker-association.json";
|
|
20
|
+
export const CONTINUATION_WRITER_ROLLBACK_REL_PATH = ".runtime/continuation-writer-rollback.json";
|
|
21
|
+
export const CONTINUATION_WRITER_TRANSACTION_REL_PATH = ".runtime/continuation-writer-transaction.json";
|
|
19
22
|
function sha256Hex(content) {
|
|
20
23
|
return createHash("sha256").update(content).digest("hex");
|
|
21
24
|
}
|
|
25
|
+
function constantTimeHashEqual(left, right) {
|
|
26
|
+
if (!/^[a-f0-9]{64}$/.test(left) || !/^[a-f0-9]{64}$/.test(right))
|
|
27
|
+
return false;
|
|
28
|
+
return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
29
|
+
}
|
|
30
|
+
export async function snapshotDagRunManifest(runDir) {
|
|
31
|
+
const entries = [];
|
|
32
|
+
const walk = async (dir) => {
|
|
33
|
+
for (const entry of (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
34
|
+
const absolute = path.join(dir, entry.name);
|
|
35
|
+
const relative = normalizeRunRelativePath(path.relative(runDir, absolute));
|
|
36
|
+
if (entry.isSymbolicLink())
|
|
37
|
+
throw new Error(`symlink rejected in DAG run manifest: ${relative}`);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
await walk(absolute);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!entry.isFile())
|
|
43
|
+
throw new Error(`non-regular DAG run entry: ${relative}`);
|
|
44
|
+
const stat = await lstat(absolute);
|
|
45
|
+
if (stat.nlink !== 1)
|
|
46
|
+
throw new Error(`hardlink rejected in DAG run manifest: ${relative}`);
|
|
47
|
+
const bytes = await readFile(absolute);
|
|
48
|
+
entries.push({ path: relative, sha256: sha256Hex(bytes), bytes: bytes.byteLength });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
await walk(runDir);
|
|
52
|
+
return sha256Hex(JSON.stringify(entries));
|
|
53
|
+
}
|
|
22
54
|
async function fileExists(filePath) {
|
|
23
55
|
try {
|
|
24
56
|
await access(filePath);
|
|
@@ -31,18 +63,67 @@ async function fileExists(filePath) {
|
|
|
31
63
|
function operatorRequestPath(cwd, requestId) {
|
|
32
64
|
return path.join(cwd, DAG_RUNS_DIR, ".operator-requests", `${requestId}.json`);
|
|
33
65
|
}
|
|
66
|
+
/** Strict envelope for persisted operator-request replays (SF2). */
|
|
67
|
+
const operatorRequestEnvelopeSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
schemaVersion: z.literal(1),
|
|
70
|
+
kind: z.literal("dag-rerun-from-node-request"),
|
|
71
|
+
requestId: z.string().min(1),
|
|
72
|
+
parentRunId: z.string().min(1),
|
|
73
|
+
newRunId: z.string().min(1),
|
|
74
|
+
createdAt: z.string().min(1),
|
|
75
|
+
result: z
|
|
76
|
+
.object({
|
|
77
|
+
ok: z.boolean(),
|
|
78
|
+
plan: z
|
|
79
|
+
.object({
|
|
80
|
+
parentRunId: z.string().min(1),
|
|
81
|
+
planHash: z.string().min(1),
|
|
82
|
+
})
|
|
83
|
+
.passthrough(),
|
|
84
|
+
newRunId: z.string().min(1).optional(),
|
|
85
|
+
})
|
|
86
|
+
.passthrough(),
|
|
87
|
+
})
|
|
88
|
+
.strict();
|
|
34
89
|
async function readExistingOperatorRequest(cwd, requestId) {
|
|
35
90
|
try {
|
|
36
91
|
const raw = JSON.parse(await readFile(operatorRequestPath(cwd, requestId), "utf-8"));
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
92
|
+
const parsed = operatorRequestEnvelopeSchema.safeParse(raw);
|
|
93
|
+
if (!parsed.success)
|
|
94
|
+
return undefined;
|
|
95
|
+
const result = parsed.data.result;
|
|
96
|
+
return {
|
|
97
|
+
...result,
|
|
98
|
+
plan: result.plan,
|
|
99
|
+
idempotentReplay: true,
|
|
100
|
+
};
|
|
40
101
|
}
|
|
41
102
|
catch {
|
|
42
103
|
// not found or unreadable
|
|
43
104
|
}
|
|
44
105
|
return undefined;
|
|
45
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Idempotent replay of a previously persisted operator request, if any.
|
|
109
|
+
* When expected context is supplied, a replay that belongs to a different
|
|
110
|
+
* parent run or plan is a request-id reuse conflict rather than a valid
|
|
111
|
+
* answer.
|
|
112
|
+
*/
|
|
113
|
+
export async function readOperatorRequestReplay(cwd, requestId, expected) {
|
|
114
|
+
const replay = await readExistingOperatorRequest(cwd, requestId);
|
|
115
|
+
if (!replay)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (expected?.parentRunId !== undefined &&
|
|
118
|
+
replay.plan?.parentRunId !== expected.parentRunId) {
|
|
119
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different parent run");
|
|
120
|
+
}
|
|
121
|
+
if (expected?.planHash !== undefined &&
|
|
122
|
+
replay.plan?.planHash !== expected.planHash) {
|
|
123
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different plan");
|
|
124
|
+
}
|
|
125
|
+
return replay;
|
|
126
|
+
}
|
|
46
127
|
async function persistOperatorRequest(cwd, result, requestId) {
|
|
47
128
|
await writeJsonAtomic(operatorRequestPath(cwd, requestId), {
|
|
48
129
|
schemaVersion: 1,
|
|
@@ -247,6 +328,54 @@ export async function planDagRerun(input) {
|
|
|
247
328
|
bindingStatus,
|
|
248
329
|
});
|
|
249
330
|
}
|
|
331
|
+
export async function planDagArtifactOverride(input) {
|
|
332
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
333
|
+
if (!located)
|
|
334
|
+
throw new Error(`dag run not found: ${input.parentRunId}`);
|
|
335
|
+
if (located.lifecycle !== "completed") {
|
|
336
|
+
throw new Error(`artifact continuation requires completed parent: ${input.parentRunId}`);
|
|
337
|
+
}
|
|
338
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
339
|
+
const parentState = await readDagRunState(located.runDir);
|
|
340
|
+
const currentWorkspace = await captureWorkspaceCheckpoint(input.cwd).catch(() => undefined);
|
|
341
|
+
const parentTerminalWorkspace = await readWorkspaceCheckpoint(located.runDir, WORKSPACE_CHECKPOINT_TERMINAL_REL);
|
|
342
|
+
const fingerprints = await resolveControllerFingerprints(located.runDir);
|
|
343
|
+
const skillSnapshotOk = await verifyParentSkillSnapshot(located.runDir, parentState, input.parentRunId);
|
|
344
|
+
const workerHints = await detectWorkerManagedRun(located.runDir, parentState, parentSpec);
|
|
345
|
+
const liveBindings = await evaluateLiveDagRerunBindings({ cwd: input.cwd, parentSpec });
|
|
346
|
+
return evaluateArtifactOverrideRerunPlan({
|
|
347
|
+
parentRunId: input.parentRunId,
|
|
348
|
+
parentSpec,
|
|
349
|
+
parentState,
|
|
350
|
+
artifactId: input.artifactId,
|
|
351
|
+
artifactOverrides: input.artifactOverrides,
|
|
352
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
353
|
+
parentLifecycle: "completed",
|
|
354
|
+
bindings: {
|
|
355
|
+
...liveBindings,
|
|
356
|
+
controllerIdentityMatched: fingerprints.currentControllerFingerprint === undefined ||
|
|
357
|
+
fingerprints.parentControllerFingerprint === undefined ||
|
|
358
|
+
fingerprints.currentControllerFingerprint === fingerprints.parentControllerFingerprint,
|
|
359
|
+
skillSnapshotVerified: skillSnapshotOk,
|
|
360
|
+
workspaceMatched: parentTerminalWorkspace?.fingerprint !== undefined &&
|
|
361
|
+
currentWorkspace?.fingerprint !== undefined &&
|
|
362
|
+
parentTerminalWorkspace.fingerprint === currentWorkspace.fingerprint,
|
|
363
|
+
},
|
|
364
|
+
parentTerminalWorkspaceFingerprint: parentTerminalWorkspace?.fingerprint,
|
|
365
|
+
currentWorkspaceFingerprint: currentWorkspace?.fingerprint,
|
|
366
|
+
workerAssociation: workerHints.workerManaged
|
|
367
|
+
? {
|
|
368
|
+
kind: "worker-managed",
|
|
369
|
+
...(workerHints.featureId ? { featureId: workerHints.featureId } : {}),
|
|
370
|
+
...(workerHints.taskId ? { taskId: workerHints.taskId } : {}),
|
|
371
|
+
...(workerHints.workerRunId ? { workerRunId: workerHints.workerRunId } : {}),
|
|
372
|
+
}
|
|
373
|
+
: { kind: "standalone" },
|
|
374
|
+
...(input.workerContinuationAuthority
|
|
375
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
376
|
+
: {}),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
250
379
|
function normalizeRunRelativePath(relPath) {
|
|
251
380
|
return relPath.replace(/\\/g, "/");
|
|
252
381
|
}
|
|
@@ -360,6 +489,18 @@ function buildImportedNodeRecord(parentRecord, parentRunId, sourceNodeRecordSha2
|
|
|
360
489
|
const { origin: _origin, ...rest } = parentRecord;
|
|
361
490
|
void _origin;
|
|
362
491
|
const imported = structuredClone(rest);
|
|
492
|
+
imported.declaredArtifacts = imported.declaredArtifacts?.map((artifact) => ({
|
|
493
|
+
...artifact,
|
|
494
|
+
...(artifact.sha256
|
|
495
|
+
? {
|
|
496
|
+
origin: {
|
|
497
|
+
kind: "imported",
|
|
498
|
+
parentRunId,
|
|
499
|
+
sourceSha256: artifact.sha256,
|
|
500
|
+
},
|
|
501
|
+
}
|
|
502
|
+
: {}),
|
|
503
|
+
}));
|
|
363
504
|
imported.nodeRecordPath = path.join(newRunDir, `${parentRecord.id}.json`);
|
|
364
505
|
for (const key of [
|
|
365
506
|
"stdoutArtifactPath",
|
|
@@ -425,25 +566,204 @@ function applyPromptOverridesToSpec(input) {
|
|
|
425
566
|
}
|
|
426
567
|
return spec;
|
|
427
568
|
}
|
|
569
|
+
function collectMaterializedDynamicChildIds(spec, dynamicParentNodeIds) {
|
|
570
|
+
const parentSet = new Set(dynamicParentNodeIds);
|
|
571
|
+
const matchers = [];
|
|
572
|
+
for (const task of spec.tasks) {
|
|
573
|
+
if (!parentSet.has(task.id))
|
|
574
|
+
continue;
|
|
575
|
+
if (task.dynamicExpansion) {
|
|
576
|
+
const escaped = task.dynamicExpansion.childIdPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
577
|
+
matchers.push(new RegExp(`^${escaped}-\\d{4}$`));
|
|
578
|
+
}
|
|
579
|
+
if (task.dynamicLoopUntil) {
|
|
580
|
+
const escaped = task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
581
|
+
matchers.push(new RegExp(`^${escaped}-r\\d+-`));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return new Set(spec.tasks
|
|
585
|
+
.map((task) => task.id)
|
|
586
|
+
.filter((nodeId) => matchers.some((matcher) => matcher.test(nodeId))));
|
|
587
|
+
}
|
|
588
|
+
async function applyArtifactOverrideToStagedRun(input) {
|
|
589
|
+
const override = input.plan.artifactOverrides?.overrides[0];
|
|
590
|
+
if (!override)
|
|
591
|
+
throw new Error("artifact override missing from V2 plan");
|
|
592
|
+
if (override.parentArtifact.runId !== input.parentRunId) {
|
|
593
|
+
throw new Error("artifact override parent run drift");
|
|
594
|
+
}
|
|
595
|
+
if (!constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
596
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate hash mismatch");
|
|
597
|
+
}
|
|
598
|
+
if (input.candidateBytes.byteLength !== override.replacement.bytes) {
|
|
599
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate byte length mismatch");
|
|
600
|
+
}
|
|
601
|
+
const relativePath = assertArtifactPathWithinParentRun(input.parentRunDir, override.parentArtifact.path);
|
|
602
|
+
const parentBytes = await readFile(path.join(input.parentRunDir, ...relativePath.split("/")));
|
|
603
|
+
if (!constantTimeHashEqual(sha256Hex(parentBytes), override.parentArtifact.sha256)) {
|
|
604
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: parent artifact changed");
|
|
605
|
+
}
|
|
606
|
+
const destinationPath = path.join(input.newRunDir, ...relativePath.split("/"));
|
|
607
|
+
const resolvedRun = path.resolve(input.newRunDir);
|
|
608
|
+
if (!path.resolve(destinationPath).startsWith(`${resolvedRun}${path.sep}`)) {
|
|
609
|
+
throw new Error("ARTIFACT_REVISION_PATH_INVALID");
|
|
610
|
+
}
|
|
611
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
612
|
+
await writeFile(destinationPath, input.candidateBytes, { mode: 0o600 });
|
|
613
|
+
const producer = input.nodes[override.parentArtifact.nodeId];
|
|
614
|
+
if (!producer || producer.origin?.kind !== "imported") {
|
|
615
|
+
throw new Error("artifact producer must be imported into continuation");
|
|
616
|
+
}
|
|
617
|
+
producer.structuredArtifactPath = destinationPath;
|
|
618
|
+
producer.structuredArtifactSha256 = override.replacement.sha256;
|
|
619
|
+
producer.declaredArtifacts = producer.declaredArtifacts?.map((artifact) => artifact.path === relativePath && artifact.sha256 === override.parentArtifact.sha256
|
|
620
|
+
? {
|
|
621
|
+
...artifact,
|
|
622
|
+
sha256: override.replacement.sha256,
|
|
623
|
+
bytes: override.replacement.bytes,
|
|
624
|
+
origin: {
|
|
625
|
+
kind: "overridden",
|
|
626
|
+
parentRunId: input.parentRunId,
|
|
627
|
+
revisionId: override.revisionId,
|
|
628
|
+
supersedes: override.supersedes,
|
|
629
|
+
},
|
|
630
|
+
}
|
|
631
|
+
: artifact);
|
|
632
|
+
await writeNodeRecord(input.newRunDir, override.parentArtifact.nodeId, producer);
|
|
633
|
+
const manifestNode = input.manifestNodes.find((entry) => entry.nodeId === override.parentArtifact.nodeId);
|
|
634
|
+
if (manifestNode) {
|
|
635
|
+
manifestNode.importedArtifacts = manifestNode.importedArtifacts.filter((artifact) => artifact.destinationRelativePath !== relativePath);
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
revisionId: override.revisionId,
|
|
639
|
+
producerNodeId: override.parentArtifact.nodeId,
|
|
640
|
+
destinationRelativePath: relativePath,
|
|
641
|
+
parentSha256: override.parentArtifact.sha256,
|
|
642
|
+
replacementSha256: override.replacement.sha256,
|
|
643
|
+
bytes: override.replacement.bytes,
|
|
644
|
+
mediaType: override.replacement.mediaType,
|
|
645
|
+
supersedes: override.supersedes,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async function captureContinuationWriterTransaction(input) {
|
|
649
|
+
const writer = input.plan.schemaVersion === 2 ? input.plan.highRiskAuthority?.writer : undefined;
|
|
650
|
+
if (!writer)
|
|
651
|
+
return;
|
|
652
|
+
const snapshot = await captureWriterBaseline(input.cwd, writer.writeSet);
|
|
653
|
+
await writeJournalFile(path.join(input.runDir, CONTINUATION_WRITER_ROLLBACK_REL_PATH), snapshot);
|
|
654
|
+
await writeJsonAtomic(path.join(input.runDir, CONTINUATION_WRITER_TRANSACTION_REL_PATH), {
|
|
655
|
+
schemaVersion: 1,
|
|
656
|
+
state: "prepared",
|
|
657
|
+
preparedAt: new Date().toISOString(),
|
|
658
|
+
writerNodeIds: writer.nodeIds,
|
|
659
|
+
writeSet: writer.writeSet,
|
|
660
|
+
writeSetSha256: writer.writeSetSha256,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
async function finalizeContinuationWriterTransaction(input) {
|
|
664
|
+
const writer = input.plan.schemaVersion === 2 ? input.plan.highRiskAuthority?.writer : undefined;
|
|
665
|
+
if (!writer)
|
|
666
|
+
return { state: "not-applicable" };
|
|
667
|
+
const transactionPath = path.join(input.runDir, CONTINUATION_WRITER_TRANSACTION_REL_PATH);
|
|
668
|
+
if (input.committed) {
|
|
669
|
+
await writeJsonAtomic(transactionPath, {
|
|
670
|
+
schemaVersion: 1,
|
|
671
|
+
state: "committed",
|
|
672
|
+
committedAt: new Date().toISOString(),
|
|
673
|
+
writerNodeIds: writer.nodeIds,
|
|
674
|
+
writeSet: writer.writeSet,
|
|
675
|
+
writeSetSha256: writer.writeSetSha256,
|
|
676
|
+
});
|
|
677
|
+
return { state: "committed" };
|
|
678
|
+
}
|
|
679
|
+
const journalPath = path.join(input.runDir, CONTINUATION_WRITER_ROLLBACK_REL_PATH);
|
|
680
|
+
const baseline = await readJournalFile(journalPath);
|
|
681
|
+
if (!baseline) {
|
|
682
|
+
const reason = "continuation writer rollback journal is missing or invalid";
|
|
683
|
+
await writeJsonAtomic(transactionPath, {
|
|
684
|
+
schemaVersion: 1,
|
|
685
|
+
state: "blocked",
|
|
686
|
+
blockedAt: new Date().toISOString(),
|
|
687
|
+
reason,
|
|
688
|
+
writerNodeIds: writer.nodeIds,
|
|
689
|
+
writeSet: writer.writeSet,
|
|
690
|
+
writeSetSha256: writer.writeSetSha256,
|
|
691
|
+
});
|
|
692
|
+
return { state: "blocked", reason };
|
|
693
|
+
}
|
|
694
|
+
const recorded = await recordWriterAttemptFromCurrent(baseline, {
|
|
695
|
+
repoRoot: input.cwd,
|
|
696
|
+
});
|
|
697
|
+
await writeJournalFile(journalPath, recorded);
|
|
698
|
+
await input.onAttemptRecorded?.();
|
|
699
|
+
const restored = await restoreJournal(recorded, { repoRoot: input.cwd });
|
|
700
|
+
await writeJournalFile(journalPath, restored.journal);
|
|
701
|
+
if (restored.state === "blocked") {
|
|
702
|
+
const reason = restored.blockedReason ?? "continuation writer rollback boundary is unprovable";
|
|
703
|
+
await writeJsonAtomic(transactionPath, {
|
|
704
|
+
schemaVersion: 1,
|
|
705
|
+
state: "blocked",
|
|
706
|
+
blockedAt: new Date().toISOString(),
|
|
707
|
+
reason,
|
|
708
|
+
restoredPaths: restored.restoredPaths,
|
|
709
|
+
writerNodeIds: writer.nodeIds,
|
|
710
|
+
writeSet: writer.writeSet,
|
|
711
|
+
writeSetSha256: writer.writeSetSha256,
|
|
712
|
+
});
|
|
713
|
+
return { state: "blocked", reason };
|
|
714
|
+
}
|
|
715
|
+
await writeJsonAtomic(transactionPath, {
|
|
716
|
+
schemaVersion: 1,
|
|
717
|
+
state: "rolled-back",
|
|
718
|
+
rolledBackAt: new Date().toISOString(),
|
|
719
|
+
restoredPaths: restored.restoredPaths,
|
|
720
|
+
writerNodeIds: writer.nodeIds,
|
|
721
|
+
writeSet: writer.writeSet,
|
|
722
|
+
writeSetSha256: writer.writeSetSha256,
|
|
723
|
+
});
|
|
724
|
+
return { state: "rolled-back" };
|
|
725
|
+
}
|
|
428
726
|
async function stageContinuationRun(input) {
|
|
429
|
-
const
|
|
727
|
+
const promptAdjustedSpec = input.promptOverrides !== undefined && input.promptOverrides.length > 0
|
|
430
728
|
? applyPromptOverridesToSpec({
|
|
431
729
|
spec: input.parentSpec,
|
|
432
730
|
plan: input.plan,
|
|
433
731
|
overrides: input.promptOverrides,
|
|
434
732
|
})
|
|
435
|
-
: input.parentSpec;
|
|
733
|
+
: structuredClone(input.parentSpec);
|
|
734
|
+
const dynamicParentNodeIds = input.plan.schemaVersion === 2
|
|
735
|
+
? (input.plan.highRiskAuthority?.dynamic?.nodeIds ?? [])
|
|
736
|
+
: [];
|
|
737
|
+
const materializedDynamicChildIds = collectMaterializedDynamicChildIds(promptAdjustedSpec, dynamicParentNodeIds);
|
|
738
|
+
const specForRun = {
|
|
739
|
+
...promptAdjustedSpec,
|
|
740
|
+
tasks: promptAdjustedSpec.tasks.filter((task) => !materializedDynamicChildIds.has(task.id)),
|
|
741
|
+
};
|
|
436
742
|
const { ranks } = topoSortToRanks(specForRun);
|
|
437
|
-
const
|
|
743
|
+
const workerContinuationAuthority = input.plan.schemaVersion === 2
|
|
744
|
+
? input.plan.workerContinuationAuthority
|
|
745
|
+
: undefined;
|
|
746
|
+
const runId = workerContinuationAuthority?.childWorkerRunId
|
|
747
|
+
?? await buildDagRunId(specForRun, { cwd: input.cwd });
|
|
748
|
+
if (workerContinuationAuthority && await locateDagRun(input.cwd, runId)) {
|
|
749
|
+
throw new Error(`WORKER_CONTINUATION_NEEDS_RECONCILE: child DAG already exists: ${runId}`);
|
|
750
|
+
}
|
|
438
751
|
const runDir = getDagRunDir(input.cwd, "active", runId);
|
|
439
752
|
await prepareActiveRunDir(runDir);
|
|
440
753
|
await writeRunSpec(runDir, specForRun);
|
|
754
|
+
await captureContinuationWriterTransaction({
|
|
755
|
+
cwd: input.cwd,
|
|
756
|
+
runDir,
|
|
757
|
+
plan: input.plan,
|
|
758
|
+
});
|
|
441
759
|
const resetSet = new Set(input.plan.resetNodeIds);
|
|
442
760
|
const importedSet = new Set(input.plan.importedNodeIds);
|
|
443
761
|
const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
|
|
444
762
|
const importManifestNodes = [];
|
|
445
763
|
const nodes = {};
|
|
446
764
|
for (const nodeId of input.plan.importedNodeIds) {
|
|
765
|
+
if (materializedDynamicChildIds.has(nodeId))
|
|
766
|
+
continue;
|
|
447
767
|
const parentRecord = input.parentState.nodes[nodeId];
|
|
448
768
|
if (!parentRecord) {
|
|
449
769
|
throw new Error(`parent node record missing for imported node: ${nodeId}`);
|
|
@@ -459,6 +779,8 @@ async function stageContinuationRun(input) {
|
|
|
459
779
|
nodes[nodeId] = importedRecord;
|
|
460
780
|
}
|
|
461
781
|
for (const nodeId of input.plan.resetNodeIds) {
|
|
782
|
+
if (materializedDynamicChildIds.has(nodeId))
|
|
783
|
+
continue;
|
|
462
784
|
const task = tasksById.get(nodeId);
|
|
463
785
|
if (!task) {
|
|
464
786
|
throw new Error(`reset node missing from spec: ${nodeId}`);
|
|
@@ -466,6 +788,8 @@ async function stageContinuationRun(input) {
|
|
|
466
788
|
nodes[nodeId] = buildPendingNodeRecord(task);
|
|
467
789
|
}
|
|
468
790
|
for (const task of input.parentSpec.tasks) {
|
|
791
|
+
if (materializedDynamicChildIds.has(task.id))
|
|
792
|
+
continue;
|
|
469
793
|
if (!resetSet.has(task.id) && !importedSet.has(task.id)) {
|
|
470
794
|
throw new Error(`node ${task.id} missing from rerun plan partition`);
|
|
471
795
|
}
|
|
@@ -484,6 +808,29 @@ async function stageContinuationRun(input) {
|
|
|
484
808
|
...(input.plan.promptOverrides
|
|
485
809
|
? { promptOverrides: input.plan.promptOverrides }
|
|
486
810
|
: {}),
|
|
811
|
+
...(input.plan.schemaVersion === 2 && input.plan.artifactOverrides
|
|
812
|
+
? { artifactOverrides: input.plan.artifactOverrides }
|
|
813
|
+
: {}),
|
|
814
|
+
...(workerContinuationAuthority
|
|
815
|
+
? { workerContinuationAuthority }
|
|
816
|
+
: {}),
|
|
817
|
+
...(input.plan.schemaVersion === 2 && input.plan.highRiskAuthority?.budget
|
|
818
|
+
? {
|
|
819
|
+
budgetLineage: {
|
|
820
|
+
parentLedgerSha256: input.plan.highRiskAuthority.budget.parentLedgerSha256,
|
|
821
|
+
inheritedAt: createdAt,
|
|
822
|
+
inheritedConsumed: structuredClone(input.plan.highRiskAuthority.budget.inheritedConsumed),
|
|
823
|
+
},
|
|
824
|
+
}
|
|
825
|
+
: {}),
|
|
826
|
+
...(input.plan.schemaVersion === 2 && input.plan.highRiskAuthority?.writer
|
|
827
|
+
? {
|
|
828
|
+
writerTransaction: {
|
|
829
|
+
writeSetSha256: input.plan.highRiskAuthority.writer.writeSetSha256,
|
|
830
|
+
writerNodeIds: input.plan.highRiskAuthority.writer.nodeIds,
|
|
831
|
+
},
|
|
832
|
+
}
|
|
833
|
+
: {}),
|
|
487
834
|
createdAt,
|
|
488
835
|
};
|
|
489
836
|
const state = {
|
|
@@ -505,6 +852,19 @@ async function stageContinuationRun(input) {
|
|
|
505
852
|
...(input.parentState.budget
|
|
506
853
|
? { budget: structuredClone(input.parentState.budget) }
|
|
507
854
|
: {}),
|
|
855
|
+
...(input.parentState.budgetLedger
|
|
856
|
+
? { budgetLedger: structuredClone(input.parentState.budgetLedger) }
|
|
857
|
+
: {}),
|
|
858
|
+
...(workerContinuationAuthority
|
|
859
|
+
? {
|
|
860
|
+
workerAssociation: {
|
|
861
|
+
schemaVersion: 1,
|
|
862
|
+
workerRunId: workerContinuationAuthority.childWorkerRunId,
|
|
863
|
+
featureId: workerContinuationAuthority.featureId,
|
|
864
|
+
taskId: workerContinuationAuthority.taskId,
|
|
865
|
+
},
|
|
866
|
+
}
|
|
867
|
+
: {}),
|
|
508
868
|
};
|
|
509
869
|
if (!input.parentState.skillSnapshotRef) {
|
|
510
870
|
throw new Error("parent run is missing skill snapshot ref");
|
|
@@ -515,30 +875,67 @@ async function stageContinuationRun(input) {
|
|
|
515
875
|
parentRunId: input.parentRunId,
|
|
516
876
|
newRunId: runId,
|
|
517
877
|
newRunDir: runDir,
|
|
878
|
+
spec: specForRun,
|
|
518
879
|
});
|
|
519
880
|
state.controllerIdentityRef = await captureControllerIdentity({ runDir });
|
|
520
881
|
try {
|
|
521
882
|
const startCheckpoint = await captureWorkspaceCheckpoint(input.cwd);
|
|
883
|
+
const highRiskWorkspace = input.plan.schemaVersion === 2
|
|
884
|
+
? input.plan.highRiskAuthority?.workspaceFingerprint
|
|
885
|
+
: undefined;
|
|
886
|
+
if (highRiskWorkspace && startCheckpoint.fingerprint !== highRiskWorkspace) {
|
|
887
|
+
throw new Error("WORKSPACE_DRIFT: continuation staging no longer matches the planned parent terminal workspace");
|
|
888
|
+
}
|
|
522
889
|
await writeWorkspaceCheckpoint(runDir, WORKSPACE_CHECKPOINT_START_REL, startCheckpoint);
|
|
523
890
|
}
|
|
524
891
|
catch (error) {
|
|
892
|
+
if (input.plan.schemaVersion === 2 && input.plan.highRiskAuthority) {
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
525
895
|
console.warn(`[dag rerun] warning: failed to write workspace start checkpoint: ${error instanceof Error ? error.message : String(error)}`);
|
|
526
896
|
}
|
|
897
|
+
const overrideManifest = input.plan.schemaVersion === 2 && input.plan.trigger === "artifact-override"
|
|
898
|
+
? [await applyArtifactOverrideToStagedRun({
|
|
899
|
+
parentRunDir: input.parentRunDir,
|
|
900
|
+
newRunDir: runDir,
|
|
901
|
+
parentRunId: input.parentRunId,
|
|
902
|
+
plan: input.plan,
|
|
903
|
+
candidateBytes: input.artifactCandidateBytes ?? Buffer.alloc(0),
|
|
904
|
+
nodes,
|
|
905
|
+
manifestNodes: importManifestNodes,
|
|
906
|
+
})]
|
|
907
|
+
: undefined;
|
|
527
908
|
const importManifest = {
|
|
528
909
|
schemaVersion: 1,
|
|
529
910
|
parentRunId: input.parentRunId,
|
|
530
911
|
createdAt,
|
|
531
912
|
nodes: importManifestNodes,
|
|
913
|
+
...(overrideManifest ? { overrides: overrideManifest } : {}),
|
|
532
914
|
};
|
|
533
915
|
await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
|
|
534
916
|
await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
|
|
535
917
|
await writeRunState(runDir, state);
|
|
536
918
|
return { runId, runDir, state, spec: specForRun };
|
|
537
919
|
}
|
|
920
|
+
async function settleStagedContinuationWithoutExecution(input) {
|
|
921
|
+
input.staged.state.status = "failed";
|
|
922
|
+
input.staged.state.finishedAt = new Date().toISOString();
|
|
923
|
+
input.staged.state.failureCategory = input.failureCategory;
|
|
924
|
+
input.staged.state.terminalReason = input.terminalReason;
|
|
925
|
+
await writeRunState(input.staged.runDir, input.staged.state);
|
|
926
|
+
return transferDagRunDir(input.staged.runDir, getDagRunDir(input.cwd, "completed", input.staged.runId));
|
|
927
|
+
}
|
|
538
928
|
export async function executeDagRerun(input) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
929
|
+
// Replay-first with SF2 envelope validation at the core boundary (same
|
|
930
|
+
// contract as executeDagArtifactOverrideRerun): an existing operator
|
|
931
|
+
// request is only a valid idempotent answer when it matches the requested
|
|
932
|
+
// parent run and plan hash; otherwise REQUEST_ID_REUSE_CONFLICT.
|
|
933
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
934
|
+
parentRunId: input.parentRunId,
|
|
935
|
+
planHash: input.planHash,
|
|
936
|
+
});
|
|
937
|
+
if (replay) {
|
|
938
|
+
return replay;
|
|
542
939
|
}
|
|
543
940
|
const plan = await planDagRerun({
|
|
544
941
|
cwd: input.cwd,
|
|
@@ -546,7 +943,7 @@ export async function executeDagRerun(input) {
|
|
|
546
943
|
selectedNodeId: input.selectedNodeId,
|
|
547
944
|
promptOverrides: input.promptOverrides,
|
|
548
945
|
});
|
|
549
|
-
if (plan.planHash
|
|
946
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
550
947
|
return {
|
|
551
948
|
ok: false,
|
|
552
949
|
plan,
|
|
@@ -573,20 +970,6 @@ export async function executeDagRerun(input) {
|
|
|
573
970
|
}
|
|
574
971
|
const parentSpec = await readDagRunSpec(located.runDir);
|
|
575
972
|
const parentState = await readDagRunState(located.runDir);
|
|
576
|
-
const recoveryLease = await acquireDagRecoveryLease({
|
|
577
|
-
cwd: input.cwd,
|
|
578
|
-
parentRunId: input.parentRunId,
|
|
579
|
-
requestId: input.requestId,
|
|
580
|
-
kind: "rerun-from-node",
|
|
581
|
-
});
|
|
582
|
-
if (recoveryLease.kind === "held") {
|
|
583
|
-
return {
|
|
584
|
-
ok: false,
|
|
585
|
-
plan,
|
|
586
|
-
errorCode: "RECOVERY_LEASE_HELD",
|
|
587
|
-
message: `parent run recovery is already claimed by ${recoveryLease.lease.kind} request ${recoveryLease.lease.requestId}`,
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
973
|
const staged = await stageContinuationRun({
|
|
591
974
|
cwd: input.cwd,
|
|
592
975
|
parentRunDir: located.runDir,
|
|
@@ -598,14 +981,6 @@ export async function executeDagRerun(input) {
|
|
|
598
981
|
requestId: input.requestId,
|
|
599
982
|
promptOverrides: input.promptOverrides,
|
|
600
983
|
});
|
|
601
|
-
// Stage is non-executing; bind the child identity immediately before handing
|
|
602
|
-
// it to the runner so no recovery DAG can start without durable parent
|
|
603
|
-
// lineage ownership.
|
|
604
|
-
await bindDagRecoveryLeaseExecution({
|
|
605
|
-
leasePath: recoveryLease.leasePath,
|
|
606
|
-
lease: recoveryLease.lease,
|
|
607
|
-
executionRef: staged.state.runId,
|
|
608
|
-
});
|
|
609
984
|
const continuationOptions = {
|
|
610
985
|
cwd: input.cwd,
|
|
611
986
|
spec: staged.spec,
|
|
@@ -625,10 +1000,198 @@ export async function executeDagRerun(input) {
|
|
|
625
1000
|
summary,
|
|
626
1001
|
};
|
|
627
1002
|
if (await fileExists(operatorRequestPath(input.cwd, input.requestId))) {
|
|
628
|
-
const replay = await
|
|
1003
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
1004
|
+
parentRunId: input.parentRunId,
|
|
1005
|
+
planHash: input.planHash,
|
|
1006
|
+
});
|
|
629
1007
|
if (replay)
|
|
630
1008
|
return replay;
|
|
631
1009
|
}
|
|
632
1010
|
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
633
1011
|
return result;
|
|
634
1012
|
}
|
|
1013
|
+
export async function executeDagArtifactOverrideRerun(input) {
|
|
1014
|
+
// Replay-first, but with SF2 envelope validation at the core boundary: an
|
|
1015
|
+
// existing operator request is only an idempotent answer when it belongs to
|
|
1016
|
+
// the same parent run AND the same plan hash. A caller that supplies a
|
|
1017
|
+
// different planHash/parent (direct call or a race between CLI validation
|
|
1018
|
+
// and this core) must get REQUEST_ID_REUSE_CONFLICT, never an unvalidated
|
|
1019
|
+
// replay.
|
|
1020
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
1021
|
+
parentRunId: input.parentRunId,
|
|
1022
|
+
planHash: input.planHash,
|
|
1023
|
+
});
|
|
1024
|
+
if (replay)
|
|
1025
|
+
return replay;
|
|
1026
|
+
const plan = await planDagArtifactOverride({
|
|
1027
|
+
cwd: input.cwd,
|
|
1028
|
+
parentRunId: input.parentRunId,
|
|
1029
|
+
artifactId: input.artifactId,
|
|
1030
|
+
artifactOverrides: input.artifactOverrides,
|
|
1031
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
1032
|
+
...(input.workerContinuationAuthority
|
|
1033
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
1034
|
+
: {}),
|
|
1035
|
+
});
|
|
1036
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
1037
|
+
return {
|
|
1038
|
+
ok: false,
|
|
1039
|
+
plan,
|
|
1040
|
+
errorCode: "RERUN_PLAN_DRIFT",
|
|
1041
|
+
message: "plan hash mismatch; refresh impact preview before executing",
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
if (!plan.eligible) {
|
|
1045
|
+
return {
|
|
1046
|
+
ok: false,
|
|
1047
|
+
plan,
|
|
1048
|
+
errorCode: "INELIGIBLE",
|
|
1049
|
+
message: plan.blockedReasons.join(", ") || "artifact continuation is not eligible",
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const override = plan.artifactOverrides?.overrides[0];
|
|
1053
|
+
if (!override || !constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
1054
|
+
return {
|
|
1055
|
+
ok: false,
|
|
1056
|
+
plan,
|
|
1057
|
+
errorCode: "ARTIFACT_REVISION_DRIFT",
|
|
1058
|
+
message: "staged revision bytes no longer match the plan",
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
1062
|
+
if (!located || located.lifecycle !== "completed") {
|
|
1063
|
+
return {
|
|
1064
|
+
ok: false,
|
|
1065
|
+
plan,
|
|
1066
|
+
errorCode: "PARENT_LIFECYCLE",
|
|
1067
|
+
message: `parent run must be completed: ${input.parentRunId}`,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const parentManifestBefore = await snapshotDagRunManifest(located.runDir);
|
|
1071
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
1072
|
+
const parentState = await readDagRunState(located.runDir);
|
|
1073
|
+
let staged;
|
|
1074
|
+
try {
|
|
1075
|
+
staged = await stageContinuationRun({
|
|
1076
|
+
cwd: input.cwd,
|
|
1077
|
+
parentRunDir: located.runDir,
|
|
1078
|
+
parentRunId: input.parentRunId,
|
|
1079
|
+
parentSpec,
|
|
1080
|
+
parentState,
|
|
1081
|
+
plan,
|
|
1082
|
+
reason: input.reason,
|
|
1083
|
+
requestId: input.requestId,
|
|
1084
|
+
artifactCandidateBytes: input.candidateBytes,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
catch (error) {
|
|
1088
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1089
|
+
if (message.startsWith("WORKER_CONTINUATION_NEEDS_RECONCILE:")) {
|
|
1090
|
+
const result = {
|
|
1091
|
+
ok: false,
|
|
1092
|
+
plan,
|
|
1093
|
+
newRunId: input.workerContinuationAuthority?.childWorkerRunId,
|
|
1094
|
+
errorCode: "WORKER_CONTINUATION_NEEDS_RECONCILE",
|
|
1095
|
+
message,
|
|
1096
|
+
};
|
|
1097
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
1098
|
+
return result;
|
|
1099
|
+
}
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1102
|
+
const parentManifestAfterStage = await snapshotDagRunManifest(located.runDir);
|
|
1103
|
+
if (!constantTimeHashEqual(parentManifestBefore, parentManifestAfterStage)) {
|
|
1104
|
+
const settledRunDir = await settleStagedContinuationWithoutExecution({
|
|
1105
|
+
cwd: input.cwd,
|
|
1106
|
+
staged,
|
|
1107
|
+
failureCategory: "parent-immutability-violation",
|
|
1108
|
+
terminalReason: "parent run changed while staging the continuation",
|
|
1109
|
+
});
|
|
1110
|
+
return {
|
|
1111
|
+
ok: false,
|
|
1112
|
+
plan,
|
|
1113
|
+
newRunId: staged.runId,
|
|
1114
|
+
runDir: settledRunDir,
|
|
1115
|
+
errorCode: "PARENT_IMMUTABILITY_VIOLATION",
|
|
1116
|
+
message: "parent run changed while staging the child; child execution was blocked",
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
await input.onStaged?.({ runId: staged.runId, runDir: staged.runDir });
|
|
1120
|
+
const plannedWorkspace = plan.highRiskAuthority?.workspaceFingerprint;
|
|
1121
|
+
if (plannedWorkspace) {
|
|
1122
|
+
const beforeExecution = await captureWorkspaceCheckpoint(input.cwd).catch(() => undefined);
|
|
1123
|
+
if (!beforeExecution || beforeExecution.fingerprint !== plannedWorkspace) {
|
|
1124
|
+
const settledRunDir = await settleStagedContinuationWithoutExecution({
|
|
1125
|
+
cwd: input.cwd,
|
|
1126
|
+
staged,
|
|
1127
|
+
failureCategory: "workspace-drift",
|
|
1128
|
+
terminalReason: "workspace changed after continuation staging and before writer execution",
|
|
1129
|
+
});
|
|
1130
|
+
return {
|
|
1131
|
+
ok: false,
|
|
1132
|
+
plan,
|
|
1133
|
+
newRunId: staged.runId,
|
|
1134
|
+
runDir: settledRunDir,
|
|
1135
|
+
errorCode: "WORKSPACE_DRIFT",
|
|
1136
|
+
message: "workspace changed after staging; revision was not consumed and writer execution was blocked",
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (input.consumeRevision) {
|
|
1141
|
+
await input.consumeRevision({
|
|
1142
|
+
revisionId: override.revisionId,
|
|
1143
|
+
expectedSha256: override.replacement.sha256,
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
const writerTransactionRef = { current: { state: "not-applicable" } };
|
|
1147
|
+
const summary = await runDagContinuation({
|
|
1148
|
+
cwd: input.cwd,
|
|
1149
|
+
spec: staged.spec,
|
|
1150
|
+
state: staged.state,
|
|
1151
|
+
runDir: staged.runDir,
|
|
1152
|
+
...(input.maxConcurrent !== undefined ? { maxConcurrent: input.maxConcurrent } : {}),
|
|
1153
|
+
...(input.executeNode ? { executeNode: input.executeNode } : {}),
|
|
1154
|
+
beforeTerminalArchive: async ({ runDir, state }) => {
|
|
1155
|
+
writerTransactionRef.current = await finalizeContinuationWriterTransaction({
|
|
1156
|
+
cwd: input.cwd,
|
|
1157
|
+
runDir,
|
|
1158
|
+
plan,
|
|
1159
|
+
committed: state.status === "finished",
|
|
1160
|
+
...(input.onWriterAttemptRecorded
|
|
1161
|
+
? { onAttemptRecorded: input.onWriterAttemptRecorded }
|
|
1162
|
+
: {}),
|
|
1163
|
+
});
|
|
1164
|
+
if (writerTransactionRef.current.state === "blocked") {
|
|
1165
|
+
state.status = "failed";
|
|
1166
|
+
state.failureCategory = "writer-rollback-blocked";
|
|
1167
|
+
state.terminalReason = writerTransactionRef.current.reason;
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
const writerTransaction = writerTransactionRef.current;
|
|
1172
|
+
const parentManifestAfterRun = await snapshotDagRunManifest(located.runDir);
|
|
1173
|
+
const immutable = constantTimeHashEqual(parentManifestBefore, parentManifestAfterRun);
|
|
1174
|
+
const result = immutable && writerTransaction.state !== "blocked"
|
|
1175
|
+
? {
|
|
1176
|
+
ok: summary.status === "finished",
|
|
1177
|
+
plan,
|
|
1178
|
+
newRunId: summary.runId,
|
|
1179
|
+
runDir: summary.runDir,
|
|
1180
|
+
summary,
|
|
1181
|
+
}
|
|
1182
|
+
: {
|
|
1183
|
+
ok: false,
|
|
1184
|
+
plan,
|
|
1185
|
+
newRunId: summary.runId,
|
|
1186
|
+
runDir: summary.runDir,
|
|
1187
|
+
summary,
|
|
1188
|
+
errorCode: writerTransaction.state === "blocked"
|
|
1189
|
+
? "WRITER_ROLLBACK_BLOCKED"
|
|
1190
|
+
: "PARENT_IMMUTABILITY_VIOLATION",
|
|
1191
|
+
message: writerTransaction.state === "blocked"
|
|
1192
|
+
? writerTransaction.reason
|
|
1193
|
+
: "parent run manifest changed during child execution",
|
|
1194
|
+
};
|
|
1195
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
1196
|
+
return result;
|
|
1197
|
+
}
|