@tea-agent/loop-agent 0.41.1 → 0.42.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +397 -5
- 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 +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +19 -0
- package/dist/cli/program.js +29 -2
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +4013 -86
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- 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 +417 -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 +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +21 -0
- 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 +107 -6
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- 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/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +86 -5
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +136 -53
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +43 -1
- package/dist/worker/console/chat/shortcuts.js +18 -2
- 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/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +6 -3
- 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 +273 -10
- 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 +57 -0
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +29 -4
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D7oU983t.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BrKSRdFS.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CiXV7L56.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D_Hs_gIG.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DbM-Lqqh.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D1M8uqJw.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-4D3RFHT6.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CdwpY_n9.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BLeLZ9PG.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CeRNEMvc.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjkHP8Vy.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B54ZUQY-.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-4bKaMVmx.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Cyjfznu_.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CDN-zGFV.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-HxJmz411.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DtSt5K2w.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-VtkSpRmG.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bj4DT4du.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Bpuk6BmP.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BHYGv_yO.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DEx3WwnT.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cj0dS5e8.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-B97h8l21.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DMSFAhBd.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DhVOCbt6.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-GlakITWL.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-VxPpu9eQ.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXYuU4Uw.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C34erCbU.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DZCAiOU3.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C9gaTdUm.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CvI0Z17U.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DMMSIx7j.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BKKP0EO6.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-C3LiFbl5.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DNthMjzc.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-miFsr4Tv.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-yXUncmn6.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DLeWKLak.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CI3he1R3.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B7jfDkS5.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BmrZod7J.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CDOlHVSY.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cz-XX1gy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-caV4cI8C.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-dImPlnIO.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CtRYVJcn.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +52 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +142 -2
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +55 -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-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +8 -1
- package/dist/worker/observe/static/styles.css +1092 -86
- package/dist/worker/observe/static/views/dag-inspector.js +1228 -64
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +142 -18
- package/dist/worker/observe/static/views/task.js +7 -2
- 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 +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- 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 +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1174 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +940 -31
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +99 -7
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +568 -3
- package/dist/workflows/dag/rerun-run.js +642 -18
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +596 -121
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +426 -22
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/product-line/task.yaml +1 -1
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +7 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/operation-store.js +0 -169
- package/dist/worker/console/static/assets/channel-D1xajGpe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/index-BLzcAykx.js +0 -451
- package/dist/worker/console/static/assets/index-Cs9JUSPs.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,23 +1,57 @@
|
|
|
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";
|
|
16
|
+
import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
|
|
17
|
+
import { captureWriterBaseline, readJournalFile, recordWriterAttemptFromCurrent, restoreJournal, writeJournalFile, } from "./frontend-writer-rollback.js";
|
|
15
18
|
export const RERUN_PLAN_REL_PATH = ".runtime/rerun-plan.json";
|
|
16
19
|
export const IMPORT_MANIFEST_REL_PATH = ".runtime/import-manifest.json";
|
|
17
20
|
export const WORKER_ASSOCIATION_REL_PATH = ".runtime/worker-association.json";
|
|
21
|
+
export const CONTINUATION_WRITER_ROLLBACK_REL_PATH = ".runtime/continuation-writer-rollback.json";
|
|
22
|
+
export const CONTINUATION_WRITER_TRANSACTION_REL_PATH = ".runtime/continuation-writer-transaction.json";
|
|
18
23
|
function sha256Hex(content) {
|
|
19
24
|
return createHash("sha256").update(content).digest("hex");
|
|
20
25
|
}
|
|
26
|
+
function constantTimeHashEqual(left, right) {
|
|
27
|
+
if (!/^[a-f0-9]{64}$/.test(left) || !/^[a-f0-9]{64}$/.test(right))
|
|
28
|
+
return false;
|
|
29
|
+
return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
30
|
+
}
|
|
31
|
+
export async function snapshotDagRunManifest(runDir) {
|
|
32
|
+
const entries = [];
|
|
33
|
+
const walk = async (dir) => {
|
|
34
|
+
for (const entry of (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
35
|
+
const absolute = path.join(dir, entry.name);
|
|
36
|
+
const relative = normalizeRunRelativePath(path.relative(runDir, absolute));
|
|
37
|
+
if (entry.isSymbolicLink())
|
|
38
|
+
throw new Error(`symlink rejected in DAG run manifest: ${relative}`);
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
await walk(absolute);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!entry.isFile())
|
|
44
|
+
throw new Error(`non-regular DAG run entry: ${relative}`);
|
|
45
|
+
const stat = await lstat(absolute);
|
|
46
|
+
if (stat.nlink !== 1)
|
|
47
|
+
throw new Error(`hardlink rejected in DAG run manifest: ${relative}`);
|
|
48
|
+
const bytes = await readFile(absolute);
|
|
49
|
+
entries.push({ path: relative, sha256: sha256Hex(bytes), bytes: bytes.byteLength });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
await walk(runDir);
|
|
53
|
+
return sha256Hex(JSON.stringify(entries));
|
|
54
|
+
}
|
|
21
55
|
async function fileExists(filePath) {
|
|
22
56
|
try {
|
|
23
57
|
await access(filePath);
|
|
@@ -30,17 +64,81 @@ async function fileExists(filePath) {
|
|
|
30
64
|
function operatorRequestPath(cwd, requestId) {
|
|
31
65
|
return path.join(cwd, DAG_RUNS_DIR, ".operator-requests", `${requestId}.json`);
|
|
32
66
|
}
|
|
67
|
+
/** Strict envelope for persisted operator-request replays (SF2). */
|
|
68
|
+
const operatorRequestEnvelopeSchema = z
|
|
69
|
+
.object({
|
|
70
|
+
schemaVersion: z.literal(1),
|
|
71
|
+
kind: z.literal("dag-rerun-from-node-request"),
|
|
72
|
+
requestId: z.string().min(1),
|
|
73
|
+
parentRunId: z.string().min(1),
|
|
74
|
+
newRunId: z.string().min(1),
|
|
75
|
+
createdAt: z.string().min(1),
|
|
76
|
+
result: z
|
|
77
|
+
.object({
|
|
78
|
+
ok: z.boolean(),
|
|
79
|
+
plan: z
|
|
80
|
+
.object({
|
|
81
|
+
parentRunId: z.string().min(1),
|
|
82
|
+
planHash: z.string().min(1),
|
|
83
|
+
})
|
|
84
|
+
.passthrough(),
|
|
85
|
+
newRunId: z.string().min(1).optional(),
|
|
86
|
+
})
|
|
87
|
+
.passthrough(),
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
33
90
|
async function readExistingOperatorRequest(cwd, requestId) {
|
|
91
|
+
const requestPath = operatorRequestPath(cwd, requestId);
|
|
34
92
|
try {
|
|
35
|
-
const raw = JSON.parse(await readFile(
|
|
36
|
-
|
|
37
|
-
|
|
93
|
+
const raw = JSON.parse(await readFile(requestPath, "utf-8"));
|
|
94
|
+
const parsed = operatorRequestEnvelopeSchema.safeParse(raw);
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope is invalid or belongs to a different request kind");
|
|
38
97
|
}
|
|
98
|
+
if (parsed.data.requestId !== requestId ||
|
|
99
|
+
parsed.data.parentRunId !== parsed.data.result.plan.parentRunId ||
|
|
100
|
+
parsed.data.newRunId !== parsed.data.result.newRunId) {
|
|
101
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope binding mismatch");
|
|
102
|
+
}
|
|
103
|
+
const result = parsed.data.result;
|
|
104
|
+
return {
|
|
105
|
+
...result,
|
|
106
|
+
plan: result.plan,
|
|
107
|
+
idempotentReplay: true,
|
|
108
|
+
};
|
|
39
109
|
}
|
|
40
|
-
catch {
|
|
41
|
-
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error &&
|
|
112
|
+
typeof error === "object" &&
|
|
113
|
+
"code" in error &&
|
|
114
|
+
error.code === "ENOENT") {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (error instanceof Error && error.message.startsWith("REQUEST_ID_REUSE_CONFLICT:")) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`REQUEST_ID_REUSE_CONFLICT: invalid persisted operator request envelope: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Idempotent replay of a previously persisted operator request, if any.
|
|
125
|
+
* When expected context is supplied, a replay that belongs to a different
|
|
126
|
+
* parent run or plan is a request-id reuse conflict rather than a valid
|
|
127
|
+
* answer.
|
|
128
|
+
*/
|
|
129
|
+
export async function readOperatorRequestReplay(cwd, requestId, expected) {
|
|
130
|
+
const replay = await readExistingOperatorRequest(cwd, requestId);
|
|
131
|
+
if (!replay)
|
|
132
|
+
return undefined;
|
|
133
|
+
if (expected?.parentRunId !== undefined &&
|
|
134
|
+
replay.plan?.parentRunId !== expected.parentRunId) {
|
|
135
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different parent run");
|
|
136
|
+
}
|
|
137
|
+
if (expected?.planHash !== undefined &&
|
|
138
|
+
replay.plan?.planHash !== expected.planHash) {
|
|
139
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different plan");
|
|
42
140
|
}
|
|
43
|
-
return
|
|
141
|
+
return replay;
|
|
44
142
|
}
|
|
45
143
|
async function persistOperatorRequest(cwd, result, requestId) {
|
|
46
144
|
await writeJsonAtomic(operatorRequestPath(cwd, requestId), {
|
|
@@ -246,6 +344,54 @@ export async function planDagRerun(input) {
|
|
|
246
344
|
bindingStatus,
|
|
247
345
|
});
|
|
248
346
|
}
|
|
347
|
+
export async function planDagArtifactOverride(input) {
|
|
348
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
349
|
+
if (!located)
|
|
350
|
+
throw new Error(`dag run not found: ${input.parentRunId}`);
|
|
351
|
+
if (located.lifecycle !== "completed") {
|
|
352
|
+
throw new Error(`artifact continuation requires completed parent: ${input.parentRunId}`);
|
|
353
|
+
}
|
|
354
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
355
|
+
const parentState = await readDagRunState(located.runDir);
|
|
356
|
+
const currentWorkspace = await captureWorkspaceCheckpoint(input.cwd).catch(() => undefined);
|
|
357
|
+
const parentTerminalWorkspace = await readWorkspaceCheckpoint(located.runDir, WORKSPACE_CHECKPOINT_TERMINAL_REL);
|
|
358
|
+
const fingerprints = await resolveControllerFingerprints(located.runDir);
|
|
359
|
+
const skillSnapshotOk = await verifyParentSkillSnapshot(located.runDir, parentState, input.parentRunId);
|
|
360
|
+
const workerHints = await detectWorkerManagedRun(located.runDir, parentState, parentSpec);
|
|
361
|
+
const liveBindings = await evaluateLiveDagRerunBindings({ cwd: input.cwd, parentSpec });
|
|
362
|
+
return evaluateArtifactOverrideRerunPlan({
|
|
363
|
+
parentRunId: input.parentRunId,
|
|
364
|
+
parentSpec,
|
|
365
|
+
parentState,
|
|
366
|
+
artifactId: input.artifactId,
|
|
367
|
+
artifactOverrides: input.artifactOverrides,
|
|
368
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
369
|
+
parentLifecycle: "completed",
|
|
370
|
+
bindings: {
|
|
371
|
+
...liveBindings,
|
|
372
|
+
controllerIdentityMatched: fingerprints.currentControllerFingerprint === undefined ||
|
|
373
|
+
fingerprints.parentControllerFingerprint === undefined ||
|
|
374
|
+
fingerprints.currentControllerFingerprint === fingerprints.parentControllerFingerprint,
|
|
375
|
+
skillSnapshotVerified: skillSnapshotOk,
|
|
376
|
+
workspaceMatched: parentTerminalWorkspace?.fingerprint !== undefined &&
|
|
377
|
+
currentWorkspace?.fingerprint !== undefined &&
|
|
378
|
+
parentTerminalWorkspace.fingerprint === currentWorkspace.fingerprint,
|
|
379
|
+
},
|
|
380
|
+
parentTerminalWorkspaceFingerprint: parentTerminalWorkspace?.fingerprint,
|
|
381
|
+
currentWorkspaceFingerprint: currentWorkspace?.fingerprint,
|
|
382
|
+
workerAssociation: workerHints.workerManaged
|
|
383
|
+
? {
|
|
384
|
+
kind: "worker-managed",
|
|
385
|
+
...(workerHints.featureId ? { featureId: workerHints.featureId } : {}),
|
|
386
|
+
...(workerHints.taskId ? { taskId: workerHints.taskId } : {}),
|
|
387
|
+
...(workerHints.workerRunId ? { workerRunId: workerHints.workerRunId } : {}),
|
|
388
|
+
}
|
|
389
|
+
: { kind: "standalone" },
|
|
390
|
+
...(input.workerContinuationAuthority
|
|
391
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
392
|
+
: {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
249
395
|
function normalizeRunRelativePath(relPath) {
|
|
250
396
|
return relPath.replace(/\\/g, "/");
|
|
251
397
|
}
|
|
@@ -359,6 +505,18 @@ function buildImportedNodeRecord(parentRecord, parentRunId, sourceNodeRecordSha2
|
|
|
359
505
|
const { origin: _origin, ...rest } = parentRecord;
|
|
360
506
|
void _origin;
|
|
361
507
|
const imported = structuredClone(rest);
|
|
508
|
+
imported.declaredArtifacts = imported.declaredArtifacts?.map((artifact) => ({
|
|
509
|
+
...artifact,
|
|
510
|
+
...(artifact.sha256
|
|
511
|
+
? {
|
|
512
|
+
origin: {
|
|
513
|
+
kind: "imported",
|
|
514
|
+
parentRunId,
|
|
515
|
+
sourceSha256: artifact.sha256,
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
: {}),
|
|
519
|
+
}));
|
|
362
520
|
imported.nodeRecordPath = path.join(newRunDir, `${parentRecord.id}.json`);
|
|
363
521
|
for (const key of [
|
|
364
522
|
"stdoutArtifactPath",
|
|
@@ -424,25 +582,204 @@ function applyPromptOverridesToSpec(input) {
|
|
|
424
582
|
}
|
|
425
583
|
return spec;
|
|
426
584
|
}
|
|
585
|
+
function collectMaterializedDynamicChildIds(spec, dynamicParentNodeIds) {
|
|
586
|
+
const parentSet = new Set(dynamicParentNodeIds);
|
|
587
|
+
const matchers = [];
|
|
588
|
+
for (const task of spec.tasks) {
|
|
589
|
+
if (!parentSet.has(task.id))
|
|
590
|
+
continue;
|
|
591
|
+
if (task.dynamicExpansion) {
|
|
592
|
+
const escaped = task.dynamicExpansion.childIdPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
593
|
+
matchers.push(new RegExp(`^${escaped}-\\d{4}$`));
|
|
594
|
+
}
|
|
595
|
+
if (task.dynamicLoopUntil) {
|
|
596
|
+
const escaped = task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
597
|
+
matchers.push(new RegExp(`^${escaped}-r\\d+-`));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return new Set(spec.tasks
|
|
601
|
+
.map((task) => task.id)
|
|
602
|
+
.filter((nodeId) => matchers.some((matcher) => matcher.test(nodeId))));
|
|
603
|
+
}
|
|
604
|
+
async function applyArtifactOverrideToStagedRun(input) {
|
|
605
|
+
const override = input.plan.artifactOverrides?.overrides[0];
|
|
606
|
+
if (!override)
|
|
607
|
+
throw new Error("artifact override missing from V2 plan");
|
|
608
|
+
if (override.parentArtifact.runId !== input.parentRunId) {
|
|
609
|
+
throw new Error("artifact override parent run drift");
|
|
610
|
+
}
|
|
611
|
+
if (!constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
612
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate hash mismatch");
|
|
613
|
+
}
|
|
614
|
+
if (input.candidateBytes.byteLength !== override.replacement.bytes) {
|
|
615
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate byte length mismatch");
|
|
616
|
+
}
|
|
617
|
+
const relativePath = assertArtifactPathWithinParentRun(input.parentRunDir, override.parentArtifact.path);
|
|
618
|
+
const parentBytes = await readFile(path.join(input.parentRunDir, ...relativePath.split("/")));
|
|
619
|
+
if (!constantTimeHashEqual(sha256Hex(parentBytes), override.parentArtifact.sha256)) {
|
|
620
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: parent artifact changed");
|
|
621
|
+
}
|
|
622
|
+
const destinationPath = path.join(input.newRunDir, ...relativePath.split("/"));
|
|
623
|
+
const resolvedRun = path.resolve(input.newRunDir);
|
|
624
|
+
if (!path.resolve(destinationPath).startsWith(`${resolvedRun}${path.sep}`)) {
|
|
625
|
+
throw new Error("ARTIFACT_REVISION_PATH_INVALID");
|
|
626
|
+
}
|
|
627
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
628
|
+
await writeFile(destinationPath, input.candidateBytes, { mode: 0o600 });
|
|
629
|
+
const producer = input.nodes[override.parentArtifact.nodeId];
|
|
630
|
+
if (!producer || producer.origin?.kind !== "imported") {
|
|
631
|
+
throw new Error("artifact producer must be imported into continuation");
|
|
632
|
+
}
|
|
633
|
+
producer.structuredArtifactPath = destinationPath;
|
|
634
|
+
producer.structuredArtifactSha256 = override.replacement.sha256;
|
|
635
|
+
producer.declaredArtifacts = producer.declaredArtifacts?.map((artifact) => artifact.path === relativePath && artifact.sha256 === override.parentArtifact.sha256
|
|
636
|
+
? {
|
|
637
|
+
...artifact,
|
|
638
|
+
sha256: override.replacement.sha256,
|
|
639
|
+
bytes: override.replacement.bytes,
|
|
640
|
+
origin: {
|
|
641
|
+
kind: "overridden",
|
|
642
|
+
parentRunId: input.parentRunId,
|
|
643
|
+
revisionId: override.revisionId,
|
|
644
|
+
supersedes: override.supersedes,
|
|
645
|
+
},
|
|
646
|
+
}
|
|
647
|
+
: artifact);
|
|
648
|
+
await writeNodeRecord(input.newRunDir, override.parentArtifact.nodeId, producer);
|
|
649
|
+
const manifestNode = input.manifestNodes.find((entry) => entry.nodeId === override.parentArtifact.nodeId);
|
|
650
|
+
if (manifestNode) {
|
|
651
|
+
manifestNode.importedArtifacts = manifestNode.importedArtifacts.filter((artifact) => artifact.destinationRelativePath !== relativePath);
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
revisionId: override.revisionId,
|
|
655
|
+
producerNodeId: override.parentArtifact.nodeId,
|
|
656
|
+
destinationRelativePath: relativePath,
|
|
657
|
+
parentSha256: override.parentArtifact.sha256,
|
|
658
|
+
replacementSha256: override.replacement.sha256,
|
|
659
|
+
bytes: override.replacement.bytes,
|
|
660
|
+
mediaType: override.replacement.mediaType,
|
|
661
|
+
supersedes: override.supersedes,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
async function captureContinuationWriterTransaction(input) {
|
|
665
|
+
const writer = input.plan.schemaVersion === 2 ? input.plan.highRiskAuthority?.writer : undefined;
|
|
666
|
+
if (!writer)
|
|
667
|
+
return;
|
|
668
|
+
const snapshot = await captureWriterBaseline(input.cwd, writer.writeSet);
|
|
669
|
+
await writeJournalFile(path.join(input.runDir, CONTINUATION_WRITER_ROLLBACK_REL_PATH), snapshot);
|
|
670
|
+
await writeJsonAtomic(path.join(input.runDir, CONTINUATION_WRITER_TRANSACTION_REL_PATH), {
|
|
671
|
+
schemaVersion: 1,
|
|
672
|
+
state: "prepared",
|
|
673
|
+
preparedAt: new Date().toISOString(),
|
|
674
|
+
writerNodeIds: writer.nodeIds,
|
|
675
|
+
writeSet: writer.writeSet,
|
|
676
|
+
writeSetSha256: writer.writeSetSha256,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
async function finalizeContinuationWriterTransaction(input) {
|
|
680
|
+
const writer = input.plan.schemaVersion === 2 ? input.plan.highRiskAuthority?.writer : undefined;
|
|
681
|
+
if (!writer)
|
|
682
|
+
return { state: "not-applicable" };
|
|
683
|
+
const transactionPath = path.join(input.runDir, CONTINUATION_WRITER_TRANSACTION_REL_PATH);
|
|
684
|
+
if (input.committed) {
|
|
685
|
+
await writeJsonAtomic(transactionPath, {
|
|
686
|
+
schemaVersion: 1,
|
|
687
|
+
state: "committed",
|
|
688
|
+
committedAt: new Date().toISOString(),
|
|
689
|
+
writerNodeIds: writer.nodeIds,
|
|
690
|
+
writeSet: writer.writeSet,
|
|
691
|
+
writeSetSha256: writer.writeSetSha256,
|
|
692
|
+
});
|
|
693
|
+
return { state: "committed" };
|
|
694
|
+
}
|
|
695
|
+
const journalPath = path.join(input.runDir, CONTINUATION_WRITER_ROLLBACK_REL_PATH);
|
|
696
|
+
const baseline = await readJournalFile(journalPath);
|
|
697
|
+
if (!baseline) {
|
|
698
|
+
const reason = "continuation writer rollback journal is missing or invalid";
|
|
699
|
+
await writeJsonAtomic(transactionPath, {
|
|
700
|
+
schemaVersion: 1,
|
|
701
|
+
state: "blocked",
|
|
702
|
+
blockedAt: new Date().toISOString(),
|
|
703
|
+
reason,
|
|
704
|
+
writerNodeIds: writer.nodeIds,
|
|
705
|
+
writeSet: writer.writeSet,
|
|
706
|
+
writeSetSha256: writer.writeSetSha256,
|
|
707
|
+
});
|
|
708
|
+
return { state: "blocked", reason };
|
|
709
|
+
}
|
|
710
|
+
const recorded = await recordWriterAttemptFromCurrent(baseline, {
|
|
711
|
+
repoRoot: input.cwd,
|
|
712
|
+
});
|
|
713
|
+
await writeJournalFile(journalPath, recorded);
|
|
714
|
+
await input.onAttemptRecorded?.();
|
|
715
|
+
const restored = await restoreJournal(recorded, { repoRoot: input.cwd });
|
|
716
|
+
await writeJournalFile(journalPath, restored.journal);
|
|
717
|
+
if (restored.state === "blocked") {
|
|
718
|
+
const reason = restored.blockedReason ?? "continuation writer rollback boundary is unprovable";
|
|
719
|
+
await writeJsonAtomic(transactionPath, {
|
|
720
|
+
schemaVersion: 1,
|
|
721
|
+
state: "blocked",
|
|
722
|
+
blockedAt: new Date().toISOString(),
|
|
723
|
+
reason,
|
|
724
|
+
restoredPaths: restored.restoredPaths,
|
|
725
|
+
writerNodeIds: writer.nodeIds,
|
|
726
|
+
writeSet: writer.writeSet,
|
|
727
|
+
writeSetSha256: writer.writeSetSha256,
|
|
728
|
+
});
|
|
729
|
+
return { state: "blocked", reason };
|
|
730
|
+
}
|
|
731
|
+
await writeJsonAtomic(transactionPath, {
|
|
732
|
+
schemaVersion: 1,
|
|
733
|
+
state: "rolled-back",
|
|
734
|
+
rolledBackAt: new Date().toISOString(),
|
|
735
|
+
restoredPaths: restored.restoredPaths,
|
|
736
|
+
writerNodeIds: writer.nodeIds,
|
|
737
|
+
writeSet: writer.writeSet,
|
|
738
|
+
writeSetSha256: writer.writeSetSha256,
|
|
739
|
+
});
|
|
740
|
+
return { state: "rolled-back" };
|
|
741
|
+
}
|
|
427
742
|
async function stageContinuationRun(input) {
|
|
428
|
-
const
|
|
743
|
+
const promptAdjustedSpec = input.promptOverrides !== undefined && input.promptOverrides.length > 0
|
|
429
744
|
? applyPromptOverridesToSpec({
|
|
430
745
|
spec: input.parentSpec,
|
|
431
746
|
plan: input.plan,
|
|
432
747
|
overrides: input.promptOverrides,
|
|
433
748
|
})
|
|
434
|
-
: input.parentSpec;
|
|
749
|
+
: structuredClone(input.parentSpec);
|
|
750
|
+
const dynamicParentNodeIds = input.plan.schemaVersion === 2
|
|
751
|
+
? (input.plan.highRiskAuthority?.dynamic?.nodeIds ?? [])
|
|
752
|
+
: [];
|
|
753
|
+
const materializedDynamicChildIds = collectMaterializedDynamicChildIds(promptAdjustedSpec, dynamicParentNodeIds);
|
|
754
|
+
const specForRun = {
|
|
755
|
+
...promptAdjustedSpec,
|
|
756
|
+
tasks: promptAdjustedSpec.tasks.filter((task) => !materializedDynamicChildIds.has(task.id)),
|
|
757
|
+
};
|
|
435
758
|
const { ranks } = topoSortToRanks(specForRun);
|
|
436
|
-
const
|
|
759
|
+
const workerContinuationAuthority = input.plan.schemaVersion === 2
|
|
760
|
+
? input.plan.workerContinuationAuthority
|
|
761
|
+
: undefined;
|
|
762
|
+
const runId = workerContinuationAuthority?.childWorkerRunId
|
|
763
|
+
?? await buildDagRunId(specForRun, { cwd: input.cwd });
|
|
764
|
+
if (workerContinuationAuthority && await locateDagRun(input.cwd, runId)) {
|
|
765
|
+
throw new Error(`WORKER_CONTINUATION_NEEDS_RECONCILE: child DAG already exists: ${runId}`);
|
|
766
|
+
}
|
|
437
767
|
const runDir = getDagRunDir(input.cwd, "active", runId);
|
|
438
768
|
await prepareActiveRunDir(runDir);
|
|
439
769
|
await writeRunSpec(runDir, specForRun);
|
|
770
|
+
await captureContinuationWriterTransaction({
|
|
771
|
+
cwd: input.cwd,
|
|
772
|
+
runDir,
|
|
773
|
+
plan: input.plan,
|
|
774
|
+
});
|
|
440
775
|
const resetSet = new Set(input.plan.resetNodeIds);
|
|
441
776
|
const importedSet = new Set(input.plan.importedNodeIds);
|
|
442
777
|
const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
|
|
443
778
|
const importManifestNodes = [];
|
|
444
779
|
const nodes = {};
|
|
445
780
|
for (const nodeId of input.plan.importedNodeIds) {
|
|
781
|
+
if (materializedDynamicChildIds.has(nodeId))
|
|
782
|
+
continue;
|
|
446
783
|
const parentRecord = input.parentState.nodes[nodeId];
|
|
447
784
|
if (!parentRecord) {
|
|
448
785
|
throw new Error(`parent node record missing for imported node: ${nodeId}`);
|
|
@@ -458,6 +795,8 @@ async function stageContinuationRun(input) {
|
|
|
458
795
|
nodes[nodeId] = importedRecord;
|
|
459
796
|
}
|
|
460
797
|
for (const nodeId of input.plan.resetNodeIds) {
|
|
798
|
+
if (materializedDynamicChildIds.has(nodeId))
|
|
799
|
+
continue;
|
|
461
800
|
const task = tasksById.get(nodeId);
|
|
462
801
|
if (!task) {
|
|
463
802
|
throw new Error(`reset node missing from spec: ${nodeId}`);
|
|
@@ -465,6 +804,8 @@ async function stageContinuationRun(input) {
|
|
|
465
804
|
nodes[nodeId] = buildPendingNodeRecord(task);
|
|
466
805
|
}
|
|
467
806
|
for (const task of input.parentSpec.tasks) {
|
|
807
|
+
if (materializedDynamicChildIds.has(task.id))
|
|
808
|
+
continue;
|
|
468
809
|
if (!resetSet.has(task.id) && !importedSet.has(task.id)) {
|
|
469
810
|
throw new Error(`node ${task.id} missing from rerun plan partition`);
|
|
470
811
|
}
|
|
@@ -483,6 +824,29 @@ async function stageContinuationRun(input) {
|
|
|
483
824
|
...(input.plan.promptOverrides
|
|
484
825
|
? { promptOverrides: input.plan.promptOverrides }
|
|
485
826
|
: {}),
|
|
827
|
+
...(input.plan.schemaVersion === 2 && input.plan.artifactOverrides
|
|
828
|
+
? { artifactOverrides: input.plan.artifactOverrides }
|
|
829
|
+
: {}),
|
|
830
|
+
...(workerContinuationAuthority
|
|
831
|
+
? { workerContinuationAuthority }
|
|
832
|
+
: {}),
|
|
833
|
+
...(input.plan.schemaVersion === 2 && input.plan.highRiskAuthority?.budget
|
|
834
|
+
? {
|
|
835
|
+
budgetLineage: {
|
|
836
|
+
parentLedgerSha256: input.plan.highRiskAuthority.budget.parentLedgerSha256,
|
|
837
|
+
inheritedAt: createdAt,
|
|
838
|
+
inheritedConsumed: structuredClone(input.plan.highRiskAuthority.budget.inheritedConsumed),
|
|
839
|
+
},
|
|
840
|
+
}
|
|
841
|
+
: {}),
|
|
842
|
+
...(input.plan.schemaVersion === 2 && input.plan.highRiskAuthority?.writer
|
|
843
|
+
? {
|
|
844
|
+
writerTransaction: {
|
|
845
|
+
writeSetSha256: input.plan.highRiskAuthority.writer.writeSetSha256,
|
|
846
|
+
writerNodeIds: input.plan.highRiskAuthority.writer.nodeIds,
|
|
847
|
+
},
|
|
848
|
+
}
|
|
849
|
+
: {}),
|
|
486
850
|
createdAt,
|
|
487
851
|
};
|
|
488
852
|
const state = {
|
|
@@ -504,6 +868,19 @@ async function stageContinuationRun(input) {
|
|
|
504
868
|
...(input.parentState.budget
|
|
505
869
|
? { budget: structuredClone(input.parentState.budget) }
|
|
506
870
|
: {}),
|
|
871
|
+
...(input.parentState.budgetLedger
|
|
872
|
+
? { budgetLedger: structuredClone(input.parentState.budgetLedger) }
|
|
873
|
+
: {}),
|
|
874
|
+
...(workerContinuationAuthority
|
|
875
|
+
? {
|
|
876
|
+
workerAssociation: {
|
|
877
|
+
schemaVersion: 1,
|
|
878
|
+
workerRunId: workerContinuationAuthority.childWorkerRunId,
|
|
879
|
+
featureId: workerContinuationAuthority.featureId,
|
|
880
|
+
taskId: workerContinuationAuthority.taskId,
|
|
881
|
+
},
|
|
882
|
+
}
|
|
883
|
+
: {}),
|
|
507
884
|
};
|
|
508
885
|
if (!input.parentState.skillSnapshotRef) {
|
|
509
886
|
throw new Error("parent run is missing skill snapshot ref");
|
|
@@ -514,30 +891,67 @@ async function stageContinuationRun(input) {
|
|
|
514
891
|
parentRunId: input.parentRunId,
|
|
515
892
|
newRunId: runId,
|
|
516
893
|
newRunDir: runDir,
|
|
894
|
+
spec: specForRun,
|
|
517
895
|
});
|
|
518
896
|
state.controllerIdentityRef = await captureControllerIdentity({ runDir });
|
|
519
897
|
try {
|
|
520
898
|
const startCheckpoint = await captureWorkspaceCheckpoint(input.cwd);
|
|
899
|
+
const highRiskWorkspace = input.plan.schemaVersion === 2
|
|
900
|
+
? input.plan.highRiskAuthority?.workspaceFingerprint
|
|
901
|
+
: undefined;
|
|
902
|
+
if (highRiskWorkspace && startCheckpoint.fingerprint !== highRiskWorkspace) {
|
|
903
|
+
throw new Error("WORKSPACE_DRIFT: continuation staging no longer matches the planned parent terminal workspace");
|
|
904
|
+
}
|
|
521
905
|
await writeWorkspaceCheckpoint(runDir, WORKSPACE_CHECKPOINT_START_REL, startCheckpoint);
|
|
522
906
|
}
|
|
523
907
|
catch (error) {
|
|
908
|
+
if (input.plan.schemaVersion === 2 && input.plan.highRiskAuthority) {
|
|
909
|
+
throw error;
|
|
910
|
+
}
|
|
524
911
|
console.warn(`[dag rerun] warning: failed to write workspace start checkpoint: ${error instanceof Error ? error.message : String(error)}`);
|
|
525
912
|
}
|
|
913
|
+
const overrideManifest = input.plan.schemaVersion === 2 && input.plan.trigger === "artifact-override"
|
|
914
|
+
? [await applyArtifactOverrideToStagedRun({
|
|
915
|
+
parentRunDir: input.parentRunDir,
|
|
916
|
+
newRunDir: runDir,
|
|
917
|
+
parentRunId: input.parentRunId,
|
|
918
|
+
plan: input.plan,
|
|
919
|
+
candidateBytes: input.artifactCandidateBytes ?? Buffer.alloc(0),
|
|
920
|
+
nodes,
|
|
921
|
+
manifestNodes: importManifestNodes,
|
|
922
|
+
})]
|
|
923
|
+
: undefined;
|
|
526
924
|
const importManifest = {
|
|
527
925
|
schemaVersion: 1,
|
|
528
926
|
parentRunId: input.parentRunId,
|
|
529
927
|
createdAt,
|
|
530
928
|
nodes: importManifestNodes,
|
|
929
|
+
...(overrideManifest ? { overrides: overrideManifest } : {}),
|
|
531
930
|
};
|
|
532
931
|
await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
|
|
533
932
|
await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
|
|
534
933
|
await writeRunState(runDir, state);
|
|
535
934
|
return { runId, runDir, state, spec: specForRun };
|
|
536
935
|
}
|
|
936
|
+
async function settleStagedContinuationWithoutExecution(input) {
|
|
937
|
+
input.staged.state.status = "failed";
|
|
938
|
+
input.staged.state.finishedAt = new Date().toISOString();
|
|
939
|
+
input.staged.state.failureCategory = input.failureCategory;
|
|
940
|
+
input.staged.state.terminalReason = input.terminalReason;
|
|
941
|
+
await writeRunState(input.staged.runDir, input.staged.state);
|
|
942
|
+
return transferDagRunDir(input.staged.runDir, getDagRunDir(input.cwd, "completed", input.staged.runId));
|
|
943
|
+
}
|
|
537
944
|
export async function executeDagRerun(input) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
945
|
+
// Replay-first with SF2 envelope validation at the core boundary (same
|
|
946
|
+
// contract as executeDagArtifactOverrideRerun): an existing operator
|
|
947
|
+
// request is only a valid idempotent answer when it matches the requested
|
|
948
|
+
// parent run and plan hash; otherwise REQUEST_ID_REUSE_CONFLICT.
|
|
949
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
950
|
+
parentRunId: input.parentRunId,
|
|
951
|
+
planHash: input.planHash,
|
|
952
|
+
});
|
|
953
|
+
if (replay) {
|
|
954
|
+
return replay;
|
|
541
955
|
}
|
|
542
956
|
const plan = await planDagRerun({
|
|
543
957
|
cwd: input.cwd,
|
|
@@ -545,7 +959,7 @@ export async function executeDagRerun(input) {
|
|
|
545
959
|
selectedNodeId: input.selectedNodeId,
|
|
546
960
|
promptOverrides: input.promptOverrides,
|
|
547
961
|
});
|
|
548
|
-
if (plan.planHash
|
|
962
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
549
963
|
return {
|
|
550
964
|
ok: false,
|
|
551
965
|
plan,
|
|
@@ -572,6 +986,20 @@ export async function executeDagRerun(input) {
|
|
|
572
986
|
}
|
|
573
987
|
const parentSpec = await readDagRunSpec(located.runDir);
|
|
574
988
|
const parentState = await readDagRunState(located.runDir);
|
|
989
|
+
const recoveryLease = await acquireDagRecoveryLease({
|
|
990
|
+
cwd: input.cwd,
|
|
991
|
+
parentRunId: input.parentRunId,
|
|
992
|
+
requestId: input.requestId,
|
|
993
|
+
kind: "rerun-from-node",
|
|
994
|
+
});
|
|
995
|
+
if (recoveryLease.kind === "held") {
|
|
996
|
+
return {
|
|
997
|
+
ok: false,
|
|
998
|
+
plan,
|
|
999
|
+
errorCode: "RECOVERY_LEASE_HELD",
|
|
1000
|
+
message: `parent run recovery is already claimed by ${recoveryLease.lease.kind} request ${recoveryLease.lease.requestId}`,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
575
1003
|
const staged = await stageContinuationRun({
|
|
576
1004
|
cwd: input.cwd,
|
|
577
1005
|
parentRunDir: located.runDir,
|
|
@@ -583,6 +1011,14 @@ export async function executeDagRerun(input) {
|
|
|
583
1011
|
requestId: input.requestId,
|
|
584
1012
|
promptOverrides: input.promptOverrides,
|
|
585
1013
|
});
|
|
1014
|
+
// Stage is non-executing; bind the child identity immediately before handing
|
|
1015
|
+
// it to the runner so no recovery DAG can start without durable parent
|
|
1016
|
+
// lineage ownership.
|
|
1017
|
+
await bindDagRecoveryLeaseExecution({
|
|
1018
|
+
leasePath: recoveryLease.leasePath,
|
|
1019
|
+
lease: recoveryLease.lease,
|
|
1020
|
+
executionRef: staged.state.runId,
|
|
1021
|
+
});
|
|
586
1022
|
const continuationOptions = {
|
|
587
1023
|
cwd: input.cwd,
|
|
588
1024
|
spec: staged.spec,
|
|
@@ -602,10 +1038,198 @@ export async function executeDagRerun(input) {
|
|
|
602
1038
|
summary,
|
|
603
1039
|
};
|
|
604
1040
|
if (await fileExists(operatorRequestPath(input.cwd, input.requestId))) {
|
|
605
|
-
const replay = await
|
|
1041
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
1042
|
+
parentRunId: input.parentRunId,
|
|
1043
|
+
planHash: input.planHash,
|
|
1044
|
+
});
|
|
606
1045
|
if (replay)
|
|
607
1046
|
return replay;
|
|
608
1047
|
}
|
|
609
1048
|
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
610
1049
|
return result;
|
|
611
1050
|
}
|
|
1051
|
+
export async function executeDagArtifactOverrideRerun(input) {
|
|
1052
|
+
// Replay-first, but with SF2 envelope validation at the core boundary: an
|
|
1053
|
+
// existing operator request is only an idempotent answer when it belongs to
|
|
1054
|
+
// the same parent run AND the same plan hash. A caller that supplies a
|
|
1055
|
+
// different planHash/parent (direct call or a race between CLI validation
|
|
1056
|
+
// and this core) must get REQUEST_ID_REUSE_CONFLICT, never an unvalidated
|
|
1057
|
+
// replay.
|
|
1058
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
1059
|
+
parentRunId: input.parentRunId,
|
|
1060
|
+
planHash: input.planHash,
|
|
1061
|
+
});
|
|
1062
|
+
if (replay)
|
|
1063
|
+
return replay;
|
|
1064
|
+
const plan = await planDagArtifactOverride({
|
|
1065
|
+
cwd: input.cwd,
|
|
1066
|
+
parentRunId: input.parentRunId,
|
|
1067
|
+
artifactId: input.artifactId,
|
|
1068
|
+
artifactOverrides: input.artifactOverrides,
|
|
1069
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
1070
|
+
...(input.workerContinuationAuthority
|
|
1071
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
1072
|
+
: {}),
|
|
1073
|
+
});
|
|
1074
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
1075
|
+
return {
|
|
1076
|
+
ok: false,
|
|
1077
|
+
plan,
|
|
1078
|
+
errorCode: "RERUN_PLAN_DRIFT",
|
|
1079
|
+
message: "plan hash mismatch; refresh impact preview before executing",
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (!plan.eligible) {
|
|
1083
|
+
return {
|
|
1084
|
+
ok: false,
|
|
1085
|
+
plan,
|
|
1086
|
+
errorCode: "INELIGIBLE",
|
|
1087
|
+
message: plan.blockedReasons.join(", ") || "artifact continuation is not eligible",
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
const override = plan.artifactOverrides?.overrides[0];
|
|
1091
|
+
if (!override || !constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
1092
|
+
return {
|
|
1093
|
+
ok: false,
|
|
1094
|
+
plan,
|
|
1095
|
+
errorCode: "ARTIFACT_REVISION_DRIFT",
|
|
1096
|
+
message: "staged revision bytes no longer match the plan",
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
1100
|
+
if (!located || located.lifecycle !== "completed") {
|
|
1101
|
+
return {
|
|
1102
|
+
ok: false,
|
|
1103
|
+
plan,
|
|
1104
|
+
errorCode: "PARENT_LIFECYCLE",
|
|
1105
|
+
message: `parent run must be completed: ${input.parentRunId}`,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
const parentManifestBefore = await snapshotDagRunManifest(located.runDir);
|
|
1109
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
1110
|
+
const parentState = await readDagRunState(located.runDir);
|
|
1111
|
+
let staged;
|
|
1112
|
+
try {
|
|
1113
|
+
staged = await stageContinuationRun({
|
|
1114
|
+
cwd: input.cwd,
|
|
1115
|
+
parentRunDir: located.runDir,
|
|
1116
|
+
parentRunId: input.parentRunId,
|
|
1117
|
+
parentSpec,
|
|
1118
|
+
parentState,
|
|
1119
|
+
plan,
|
|
1120
|
+
reason: input.reason,
|
|
1121
|
+
requestId: input.requestId,
|
|
1122
|
+
artifactCandidateBytes: input.candidateBytes,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1127
|
+
if (message.startsWith("WORKER_CONTINUATION_NEEDS_RECONCILE:")) {
|
|
1128
|
+
const result = {
|
|
1129
|
+
ok: false,
|
|
1130
|
+
plan,
|
|
1131
|
+
newRunId: input.workerContinuationAuthority?.childWorkerRunId,
|
|
1132
|
+
errorCode: "WORKER_CONTINUATION_NEEDS_RECONCILE",
|
|
1133
|
+
message,
|
|
1134
|
+
};
|
|
1135
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
1136
|
+
return result;
|
|
1137
|
+
}
|
|
1138
|
+
throw error;
|
|
1139
|
+
}
|
|
1140
|
+
const parentManifestAfterStage = await snapshotDagRunManifest(located.runDir);
|
|
1141
|
+
if (!constantTimeHashEqual(parentManifestBefore, parentManifestAfterStage)) {
|
|
1142
|
+
const settledRunDir = await settleStagedContinuationWithoutExecution({
|
|
1143
|
+
cwd: input.cwd,
|
|
1144
|
+
staged,
|
|
1145
|
+
failureCategory: "parent-immutability-violation",
|
|
1146
|
+
terminalReason: "parent run changed while staging the continuation",
|
|
1147
|
+
});
|
|
1148
|
+
return {
|
|
1149
|
+
ok: false,
|
|
1150
|
+
plan,
|
|
1151
|
+
newRunId: staged.runId,
|
|
1152
|
+
runDir: settledRunDir,
|
|
1153
|
+
errorCode: "PARENT_IMMUTABILITY_VIOLATION",
|
|
1154
|
+
message: "parent run changed while staging the child; child execution was blocked",
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
await input.onStaged?.({ runId: staged.runId, runDir: staged.runDir });
|
|
1158
|
+
const plannedWorkspace = plan.highRiskAuthority?.workspaceFingerprint;
|
|
1159
|
+
if (plannedWorkspace) {
|
|
1160
|
+
const beforeExecution = await captureWorkspaceCheckpoint(input.cwd).catch(() => undefined);
|
|
1161
|
+
if (!beforeExecution || beforeExecution.fingerprint !== plannedWorkspace) {
|
|
1162
|
+
const settledRunDir = await settleStagedContinuationWithoutExecution({
|
|
1163
|
+
cwd: input.cwd,
|
|
1164
|
+
staged,
|
|
1165
|
+
failureCategory: "workspace-drift",
|
|
1166
|
+
terminalReason: "workspace changed after continuation staging and before writer execution",
|
|
1167
|
+
});
|
|
1168
|
+
return {
|
|
1169
|
+
ok: false,
|
|
1170
|
+
plan,
|
|
1171
|
+
newRunId: staged.runId,
|
|
1172
|
+
runDir: settledRunDir,
|
|
1173
|
+
errorCode: "WORKSPACE_DRIFT",
|
|
1174
|
+
message: "workspace changed after staging; revision was not consumed and writer execution was blocked",
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (input.consumeRevision) {
|
|
1179
|
+
await input.consumeRevision({
|
|
1180
|
+
revisionId: override.revisionId,
|
|
1181
|
+
expectedSha256: override.replacement.sha256,
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
const writerTransactionRef = { current: { state: "not-applicable" } };
|
|
1185
|
+
const summary = await runDagContinuation({
|
|
1186
|
+
cwd: input.cwd,
|
|
1187
|
+
spec: staged.spec,
|
|
1188
|
+
state: staged.state,
|
|
1189
|
+
runDir: staged.runDir,
|
|
1190
|
+
...(input.maxConcurrent !== undefined ? { maxConcurrent: input.maxConcurrent } : {}),
|
|
1191
|
+
...(input.executeNode ? { executeNode: input.executeNode } : {}),
|
|
1192
|
+
beforeTerminalArchive: async ({ runDir, state }) => {
|
|
1193
|
+
writerTransactionRef.current = await finalizeContinuationWriterTransaction({
|
|
1194
|
+
cwd: input.cwd,
|
|
1195
|
+
runDir,
|
|
1196
|
+
plan,
|
|
1197
|
+
committed: state.status === "finished",
|
|
1198
|
+
...(input.onWriterAttemptRecorded
|
|
1199
|
+
? { onAttemptRecorded: input.onWriterAttemptRecorded }
|
|
1200
|
+
: {}),
|
|
1201
|
+
});
|
|
1202
|
+
if (writerTransactionRef.current.state === "blocked") {
|
|
1203
|
+
state.status = "failed";
|
|
1204
|
+
state.failureCategory = "writer-rollback-blocked";
|
|
1205
|
+
state.terminalReason = writerTransactionRef.current.reason;
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
});
|
|
1209
|
+
const writerTransaction = writerTransactionRef.current;
|
|
1210
|
+
const parentManifestAfterRun = await snapshotDagRunManifest(located.runDir);
|
|
1211
|
+
const immutable = constantTimeHashEqual(parentManifestBefore, parentManifestAfterRun);
|
|
1212
|
+
const result = immutable && writerTransaction.state !== "blocked"
|
|
1213
|
+
? {
|
|
1214
|
+
ok: summary.status === "finished",
|
|
1215
|
+
plan,
|
|
1216
|
+
newRunId: summary.runId,
|
|
1217
|
+
runDir: summary.runDir,
|
|
1218
|
+
summary,
|
|
1219
|
+
}
|
|
1220
|
+
: {
|
|
1221
|
+
ok: false,
|
|
1222
|
+
plan,
|
|
1223
|
+
newRunId: summary.runId,
|
|
1224
|
+
runDir: summary.runDir,
|
|
1225
|
+
summary,
|
|
1226
|
+
errorCode: writerTransaction.state === "blocked"
|
|
1227
|
+
? "WRITER_ROLLBACK_BLOCKED"
|
|
1228
|
+
: "PARENT_IMMUTABILITY_VIOLATION",
|
|
1229
|
+
message: writerTransaction.state === "blocked"
|
|
1230
|
+
? writerTransaction.reason
|
|
1231
|
+
: "parent run manifest changed during child execution",
|
|
1232
|
+
};
|
|
1233
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|