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