@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import { deterministicSha256, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
2
|
-
import { expandFrontendGlobPaths } from "./frontend-worktree-diff.js";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
/** Runtime writer admission evidence. This is the sole production authorization type. */
|
|
5
|
-
export const frontendWriterAdmissionSchema = z
|
|
6
|
-
.object({
|
|
7
|
-
schemaVersion: z.literal(1),
|
|
8
|
-
writerNodeId: z.literal("frontend-implement-pi"),
|
|
9
|
-
decision: z.enum(["authorized", "denied"]),
|
|
10
|
-
sourceArtifact: z.string().min(1),
|
|
11
|
-
artifactHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
12
|
-
checkedAt: z.string().min(1),
|
|
13
|
-
reason: z.string().nullable(),
|
|
14
|
-
})
|
|
15
|
-
.strict();
|
|
16
|
-
/** A+B (AC-006): four-state writer admission classification. `accepted` is the
|
|
17
|
-
* only authorized state; `requires-human-approval` is the High-topology
|
|
18
|
-
* high-admission-receipt gate; `blocked` is deterministic denial; `stale` is
|
|
19
|
-
* digest drift (source refresh / re-contract). */
|
|
20
|
-
export const frontendWriterAdmissionClassificationSchema = z.enum([
|
|
21
|
-
"accepted",
|
|
22
|
-
"requires-human-approval",
|
|
23
|
-
"blocked",
|
|
24
|
-
"stale",
|
|
25
|
-
// Legacy fixture alias: writeSet-external test helpers still emit `admitted`.
|
|
26
|
-
// Canonical producers always emit one of the four values above.
|
|
27
|
-
"admitted",
|
|
28
|
-
]);
|
|
29
|
-
/** A+B (AC-006): Micro topology uses `requirement-to-target-and-evidence`
|
|
30
|
-
* coverage; Small/Standard/High use `requirement-to-decision`. The two schemas
|
|
31
|
-
* are deliberately separated so a Micro evidence coverage can never be read as
|
|
32
|
-
* a decision coverage. */
|
|
33
|
-
export const frontendAdmissionCoverageSchema = z.discriminatedUnion("coverageId", [
|
|
34
|
-
z
|
|
35
|
-
.object({
|
|
36
|
-
coverageId: z.literal("requirement-to-target-and-evidence"),
|
|
37
|
-
requirementId: z.string().min(1),
|
|
38
|
-
targetPaths: z.array(z.string().min(1)),
|
|
39
|
-
evidenceRefs: z.array(z.string().min(1)),
|
|
40
|
-
})
|
|
41
|
-
.strict(),
|
|
42
|
-
z
|
|
43
|
-
.object({
|
|
44
|
-
coverageId: z.literal("requirement-to-decision"),
|
|
45
|
-
requirementId: z.string().min(1),
|
|
46
|
-
decisionRefs: z.array(z.string().min(1)),
|
|
47
|
-
})
|
|
48
|
-
.strict(),
|
|
49
|
-
]);
|
|
50
|
-
/** Failure-source values shared with frontend recovery planning (M8). */
|
|
51
|
-
export const frontendAdmissionFailureSourceSchema = z.enum([
|
|
52
|
-
"frontend-plan-pi",
|
|
53
|
-
"frontend-design-policy-shell",
|
|
54
|
-
"frontend-writer-admission-shell",
|
|
55
|
-
"frontend-implement-pi",
|
|
56
|
-
"frontend-verify-shell",
|
|
57
|
-
]);
|
|
58
|
-
/**
|
|
59
|
-
* Deterministic artifact schema for `contracts/frontend-writer-admission-result.json`.
|
|
60
|
-
* Scheduler admission gate and frontend recovery planning read this artifact
|
|
61
|
-
* (fail-closed); it replaces the legacy frontend-prewrite-result-v1 as the
|
|
62
|
-
* write authorization source.
|
|
63
|
-
*/
|
|
64
|
-
export const frontendWriterAdmissionResultV1Schema = z
|
|
65
|
-
.object({
|
|
66
|
-
schemaVersion: z.literal(1),
|
|
67
|
-
schemaId: z.literal("frontend-writer-admission-shell-v1"),
|
|
68
|
-
classification: frontendWriterAdmissionClassificationSchema,
|
|
69
|
-
writeSet: z.array(z.string().min(1)),
|
|
70
|
-
writeSetDigest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
71
|
-
baselineDigest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
72
|
-
admissionDigest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
73
|
-
baselineDebtPolicy: z.string().min(1).optional(),
|
|
74
|
-
findings: z.array(z
|
|
75
|
-
.object({
|
|
76
|
-
code: z.string().min(1),
|
|
77
|
-
message: z.string().min(1),
|
|
78
|
-
path: z.string().optional(),
|
|
79
|
-
})
|
|
80
|
-
.strict()),
|
|
81
|
-
// r12 policy change: a request_design_changes verdict no longer blocks
|
|
82
|
-
// writer admission. The verdict and its findings ride along as
|
|
83
|
-
// advisory context for the implement node; blocking is owned by the
|
|
84
|
-
// deterministic gates (prewrite, write guard) and the final code
|
|
85
|
-
// review.
|
|
86
|
-
designReviewAdvisory: z
|
|
87
|
-
.object({
|
|
88
|
-
verdict: z.enum(["approve_design", "request_design_changes"]),
|
|
89
|
-
restartPhase: z.string().min(1).optional(),
|
|
90
|
-
findingCount: z.number().int().nonnegative(),
|
|
91
|
-
})
|
|
92
|
-
.strict()
|
|
93
|
-
.optional(),
|
|
94
|
-
failureSource: frontendAdmissionFailureSourceSchema.optional(),
|
|
95
|
-
})
|
|
96
|
-
.strict();
|
|
97
|
-
/** Artifact relative path written by the frontend-writer-admission-shell. */
|
|
98
|
-
export const FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT = "contracts/frontend-writer-admission-result.json";
|
|
99
|
-
function sortFindings(findings) {
|
|
100
|
-
return [...findings].sort((a, b) => {
|
|
101
|
-
if (a.code !== b.code)
|
|
102
|
-
return a.code < b.code ? -1 : 1;
|
|
103
|
-
if (a.message !== b.message)
|
|
104
|
-
return a.message < b.message ? -1 : 1;
|
|
105
|
-
const pathA = a.path ?? "";
|
|
106
|
-
const pathB = b.path ?? "";
|
|
107
|
-
if (pathA !== pathB)
|
|
108
|
-
return pathA < pathB ? -1 : 1;
|
|
109
|
-
return 0;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/** Reject non-concrete writeSet entries: absolute paths, traversal, globstar,
|
|
113
|
-
* repo-root, and empty/duplicate-slash segments. */
|
|
114
|
-
export function isConcreteWriteSetPath(value) {
|
|
115
|
-
const path = value.trim();
|
|
116
|
-
if (path === "" || path === "." || path === "./")
|
|
117
|
-
return false;
|
|
118
|
-
if (path.startsWith("/") || path.includes("\\"))
|
|
119
|
-
return false;
|
|
120
|
-
const segments = path.split("/");
|
|
121
|
-
if (segments.some((segment) => segment === ".." || segment === "**" || segment === "")) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
/** Derive the concrete writeSet from implementation targets ∪ non-static
|
|
127
|
-
* verification target files. Entries are de-duplicated and lexicographically
|
|
128
|
-
* sorted (frozen). */
|
|
129
|
-
export function deriveWriteSet(contract, options) {
|
|
130
|
-
const findings = [];
|
|
131
|
-
const candidates = [
|
|
132
|
-
...contract.targets.files,
|
|
133
|
-
...contract.verificationTargets
|
|
134
|
-
.filter((target) => target.type !== "static")
|
|
135
|
-
.map((target) => target.file),
|
|
136
|
-
];
|
|
137
|
-
// Expand glob entries (e.g. `src/**` inherited from the skeleton) against
|
|
138
|
-
// the enumerated workspace files so the derived writeSet contains only
|
|
139
|
-
// concrete paths. Without a file list, globs are treated as concrete and
|
|
140
|
-
// rejected by isConcreteWriteSetPath below (fail-closed).
|
|
141
|
-
const expanded = options?.workspaceFiles
|
|
142
|
-
? expandFrontendGlobPaths({
|
|
143
|
-
candidates,
|
|
144
|
-
workspaceFiles: options.workspaceFiles,
|
|
145
|
-
})
|
|
146
|
-
: candidates;
|
|
147
|
-
const writeSet = new Set();
|
|
148
|
-
for (const candidate of expanded) {
|
|
149
|
-
if (!isConcreteWriteSetPath(candidate)) {
|
|
150
|
-
findings.push({
|
|
151
|
-
code: "write-set-path-not-concrete",
|
|
152
|
-
message: `writeSet entry is not a concrete path: ${candidate}`,
|
|
153
|
-
path: candidate,
|
|
154
|
-
});
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
writeSet.add(candidate);
|
|
158
|
-
}
|
|
159
|
-
return { writeSet: [...writeSet].sort(), findings };
|
|
160
|
-
}
|
|
161
|
-
/** Sort and freeze the injected baseline; duplicate paths and empty baselines
|
|
162
|
-
* are blocking (collection itself is production wiring, not this shell). */
|
|
163
|
-
export function freezeBaseline(baseline) {
|
|
164
|
-
const findings = [];
|
|
165
|
-
if (baseline.length === 0) {
|
|
166
|
-
findings.push({
|
|
167
|
-
code: "baseline-empty",
|
|
168
|
-
message: "writer admission baseline is empty",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
const seen = new Set();
|
|
172
|
-
for (const entry of baseline) {
|
|
173
|
-
if (seen.has(entry.path)) {
|
|
174
|
-
findings.push({
|
|
175
|
-
code: "baseline-duplicate-path",
|
|
176
|
-
message: `baseline contains duplicate path: ${entry.path}`,
|
|
177
|
-
path: entry.path,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
seen.add(entry.path);
|
|
181
|
-
}
|
|
182
|
-
const sorted = [...baseline].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
183
|
-
return { baselineDigest: deterministicSha256(sorted), findings };
|
|
184
|
-
}
|
|
185
|
-
/** Combine mock strategy, allowed strategies digest, frozen command digest,
|
|
186
|
-
* production-default-off flag and real-integration expectation into one
|
|
187
|
-
* deterministic admission digest. */
|
|
188
|
-
export function deriveAdmissionDigest(contract, allowedMockStrategies, frozenCommandLabels, baselineDebtPolicy) {
|
|
189
|
-
return deterministicSha256({
|
|
190
|
-
mockApiStrategy: contract.mockApi.strategy,
|
|
191
|
-
allowedMockStrategiesDigest: deterministicSha256([...allowedMockStrategies].sort()),
|
|
192
|
-
frozenCommandLabelsDigest: deterministicSha256([...frozenCommandLabels].sort()),
|
|
193
|
-
productionDefaultOff: contract.mockApi.productionDefaultOff === true,
|
|
194
|
-
hasRealIntegrationGap: Boolean(contract.realIntegrationGap),
|
|
195
|
-
baselineDebtPolicy: baselineDebtPolicy ?? "",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
/** Shape-aware deterministic admission: re-validate the typed contract, honor
|
|
199
|
-
* the design-policy result, derive/freeze the writeSet, digest the baseline,
|
|
200
|
-
* and assemble the admission digest. */
|
|
201
|
-
export function deriveFrontendWriterAdmissionShell(input) {
|
|
202
|
-
const findings = [];
|
|
203
|
-
const shape = frontendImplementationContractSchema.safeParse(input.contract);
|
|
204
|
-
if (!shape.success) {
|
|
205
|
-
findings.push({
|
|
206
|
-
code: "contract-shape-invalid",
|
|
207
|
-
message: `contract fails strict shape validation: ${shape.error.issues[0]?.message ?? "unknown issue"}`,
|
|
208
|
-
path: shape.error.issues[0]?.path.join(".") || undefined,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
if (input.designPolicyResult.classification === "blocked") {
|
|
212
|
-
findings.push({
|
|
213
|
-
code: "design-policy-blocked",
|
|
214
|
-
message: `design policy is blocked with ${input.designPolicyResult.findings.length} finding(s)`,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
const derived = deriveWriteSet(input.contract, {
|
|
218
|
-
workspaceFiles: input.workspaceFiles,
|
|
219
|
-
});
|
|
220
|
-
findings.push(...derived.findings);
|
|
221
|
-
const baseline = freezeBaseline(input.baseline);
|
|
222
|
-
findings.push(...baseline.findings);
|
|
223
|
-
const sortedFindings = sortFindings(findings);
|
|
224
|
-
// Deterministic admission: any finding blocks. High-risk contracts keep the
|
|
225
|
-
// full policy/writeSet/baseline gates but do NOT stop for a human approval
|
|
226
|
-
// step — the DAG runs unattended, so `requires-human-approval` would strand
|
|
227
|
-
// every high-risk writer. The receipt is still materialized for audit; the
|
|
228
|
-
// scheduler authorizes `accepted` directly.
|
|
229
|
-
const classification = sortedFindings.length > 0 ? "blocked" : "accepted";
|
|
230
|
-
const result = {
|
|
231
|
-
schemaId: "frontend-writer-admission-shell-v1",
|
|
232
|
-
classification,
|
|
233
|
-
writeSet: derived.writeSet,
|
|
234
|
-
writeSetDigest: deterministicSha256(derived.writeSet),
|
|
235
|
-
baselineDigest: baseline.baselineDigest,
|
|
236
|
-
admissionDigest: deriveAdmissionDigest(input.contract, input.allowedMockStrategies, input.frozenCommandLabels, input.baselineDebtPolicy),
|
|
237
|
-
...(input.baselineDebtPolicy !== undefined
|
|
238
|
-
? { baselineDebtPolicy: input.baselineDebtPolicy }
|
|
239
|
-
: {}),
|
|
240
|
-
findings: sortedFindings,
|
|
241
|
-
};
|
|
242
|
-
return result;
|
|
243
|
-
}
|
|
244
|
-
function normalizeNewVerdict(classification) {
|
|
245
|
-
return classification === "accepted" ||
|
|
246
|
-
classification === "requires-human-approval" ||
|
|
247
|
-
classification === "admitted"
|
|
248
|
-
? "accepted"
|
|
249
|
-
: "blocked";
|
|
250
|
-
}
|
|
251
|
-
function normalizeLegacyVerdict(classification) {
|
|
252
|
-
return classification === "accepted" || classification === "accepted-normalized"
|
|
253
|
-
? "accepted"
|
|
254
|
-
: "blocked";
|
|
255
|
-
}
|
|
256
|
-
export function compareAdmissionToLegacyGate(input) {
|
|
257
|
-
const newVerdict = normalizeNewVerdict(input.newClassification);
|
|
258
|
-
const legacyVerdict = normalizeLegacyVerdict(input.legacyClassification);
|
|
259
|
-
return {
|
|
260
|
-
newClassification: input.newClassification,
|
|
261
|
-
legacyClassification: input.legacyClassification,
|
|
262
|
-
newVerdict,
|
|
263
|
-
legacyVerdict,
|
|
264
|
-
match: newVerdict === legacyVerdict,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
/** Render a stable, deterministically ordered mismatch list for the evaluation
|
|
268
|
-
* set. Input order is irrelevant: rows are sorted by a canonical key. */
|
|
269
|
-
export function renderAdmissionShadowDiff(results) {
|
|
270
|
-
const indexed = results.map((result, index) => ({ ...result, index }));
|
|
271
|
-
const sorted = indexed.sort((a, b) => {
|
|
272
|
-
const keyA = `${a.legacyClassification}|${a.newClassification}`;
|
|
273
|
-
const keyB = `${b.legacyClassification}|${b.newClassification}`;
|
|
274
|
-
if (keyA !== keyB)
|
|
275
|
-
return keyA < keyB ? -1 : 1;
|
|
276
|
-
return a.index - b.index;
|
|
277
|
-
});
|
|
278
|
-
const mismatches = sorted.filter((row) => !row.match);
|
|
279
|
-
const lines = [
|
|
280
|
-
"## Admission Shadow Diff",
|
|
281
|
-
`${sorted.length} comparisons, ${mismatches.length} mismatches`,
|
|
282
|
-
...sorted.map((row) => `- ${row.legacyClassification} -> ${row.newClassification}: legacy=${row.legacyVerdict} new=${row.newVerdict}${row.match ? " match" : " MISMATCH"}`),
|
|
283
|
-
];
|
|
284
|
-
return lines.join("\n");
|
|
285
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
/**
|
|
4
|
-
* M4: fact-derived frontend writer status + bounded in-node micro-loop.
|
|
5
|
-
*
|
|
6
|
-
* `frontend-implement-pi` derives its implementation status from mechanical
|
|
7
|
-
* facts (write-tool events, run delta, cumulative diff, write guard, requirement
|
|
8
|
-
* coverage, focused-check failures) instead of parsing the
|
|
9
|
-
* `IMPLEMENTATION_OUTCOME:` first line. The legacy `implementation-outcome-v1`
|
|
10
|
-
* protocol (backend/README/module writers + `frontend-repair-pi`) is untouched.
|
|
11
|
-
*
|
|
12
|
-
* Everything here is either a pure function (deterministically testable) or an
|
|
13
|
-
* IO collector that is only reached through a dynamic import from the Pi
|
|
14
|
-
* executor, avoiding an executor↔workflows static cycle.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Write/edit-style tool names emitted by DAG writer customTools. Kept in sync
|
|
18
|
-
* with the `SDK_WRITE_TOOL_NAMES` set in pi-sdk-executor.ts so the persisted
|
|
19
|
-
* session-event count and the in-memory writeToolCallCount agree.
|
|
20
|
-
*/
|
|
21
|
-
export const FRONTEND_WRITE_TOOL_NAMES = new Set([
|
|
22
|
-
"edit",
|
|
23
|
-
"write",
|
|
24
|
-
"str_replace_editor",
|
|
25
|
-
]);
|
|
26
|
-
/**
|
|
27
|
-
* AC-001/AC-003: derive the writer status from mechanical facts only. The
|
|
28
|
-
* first-line text is never consulted. Ordering:
|
|
29
|
-
* 1. write-guard violation → blocked
|
|
30
|
-
* 2. focused-check failure → needs-repair (including trace preflight gaps)
|
|
31
|
-
* 3. non-empty diff → changed (diff is the mechanical fact source)
|
|
32
|
-
* 4. empty diff + runtime already-satisfied evidence → already-satisfied
|
|
33
|
-
* 5. empty diff, zero writes, zero diff → blocked (no progress)
|
|
34
|
-
* 6. otherwise → empty-diff (fail-closed)
|
|
35
|
-
*/
|
|
36
|
-
export function deriveFrontendWriterStatus(facts) {
|
|
37
|
-
if (!facts.writeGuardOk) {
|
|
38
|
-
return {
|
|
39
|
-
status: "blocked",
|
|
40
|
-
reason: `write guard violations: ${facts.writeGuardViolations.join(", ") || "unknown"}`,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (facts.focusedCheckFailures.length > 0) {
|
|
44
|
-
const failures = facts.focusedCheckFailures
|
|
45
|
-
.map((failure) => failure.targetId
|
|
46
|
-
? `${failure.targetId}: ${failure.detail}`
|
|
47
|
-
: failure.detail)
|
|
48
|
-
.join("; ");
|
|
49
|
-
return {
|
|
50
|
-
status: "needs-repair",
|
|
51
|
-
reason: `focused checks require repair: ${failures}`,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (facts.changedFiles.length > 0) {
|
|
55
|
-
return {
|
|
56
|
-
status: "changed",
|
|
57
|
-
reason: `non-empty run-attributed diff (${facts.changedFiles.length} file${facts.changedFiles.length === 1 ? "" : "s"})`,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
if (facts.alreadySatisfiedEvidence) {
|
|
61
|
-
return {
|
|
62
|
-
status: "already-satisfied",
|
|
63
|
-
reason: `empty diff with runtime already-satisfied evidence (${facts.alreadySatisfiedEvidence.requirementTargets.length} target${facts.alreadySatisfiedEvidence.requirementTargets.length === 1 ? "" : "s"})`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
if (facts.writeToolEvents === 0 && facts.changedFiles.length === 0) {
|
|
67
|
-
return {
|
|
68
|
-
status: "blocked",
|
|
69
|
-
reason: "zero write-tool events and zero diff: no implementation progress",
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
status: "empty-diff",
|
|
74
|
-
reason: "empty diff without runtime already-satisfied evidence (fail-closed)",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Deterministic failure fingerprint over the normalized failure signature:
|
|
79
|
-
* category + focused-check failures (target + detail) + guard status. Used for
|
|
80
|
-
* repeated-failure deduplication inside the bounded micro-loop.
|
|
81
|
-
*/
|
|
82
|
-
export function computeFailureFingerprint(facts) {
|
|
83
|
-
const signature = normalizeFailureSignature(facts);
|
|
84
|
-
return createHash("sha256").update(signature).digest("hex");
|
|
85
|
-
}
|
|
86
|
-
function normalizeFailureSignature(facts) {
|
|
87
|
-
const parts = [];
|
|
88
|
-
if (!facts.writeGuardOk) {
|
|
89
|
-
parts.push("write-guard");
|
|
90
|
-
}
|
|
91
|
-
for (const failure of facts.focusedCheckFailures) {
|
|
92
|
-
parts.push(`${failure.category}:${failure.targetId ?? ""}:${failure.detail}`);
|
|
93
|
-
}
|
|
94
|
-
if (facts.changedFiles.length === 0 && facts.writeToolEvents === 0) {
|
|
95
|
-
parts.push("no-progress");
|
|
96
|
-
}
|
|
97
|
-
return parts.sort().join("\n");
|
|
98
|
-
}
|
|
99
|
-
export const DEFAULT_FRONTEND_MICRO_LOOP_BUDGET = {
|
|
100
|
-
maxFocusedCheckRounds: 3,
|
|
101
|
-
maxWriteAttempts: 5,
|
|
102
|
-
maxWallTimeMs: 15 * 60 * 1000,
|
|
103
|
-
maxTokens: 200_000,
|
|
104
|
-
maxRepeatedFingerprints: 2,
|
|
105
|
-
};
|
|
106
|
-
/**
|
|
107
|
-
* Pure micro-loop continuation gate. Deterministic: same budget + history +
|
|
108
|
-
* timestamps always yield the same decision. The caller (executor) drives the
|
|
109
|
-
* loop by re-checking this function after each focused-check round.
|
|
110
|
-
*/
|
|
111
|
-
export function evaluateMicroLoopContinuation(budget, history, startWallTimeMs, nowWallTimeMs) {
|
|
112
|
-
const rounds = history.length;
|
|
113
|
-
const last = history[rounds - 1];
|
|
114
|
-
if (last) {
|
|
115
|
-
const resolved = (last.status === "changed" &&
|
|
116
|
-
last.writeGuardOk &&
|
|
117
|
-
last.focusedCheckFailures.length === 0) ||
|
|
118
|
-
last.status === "already-satisfied" ||
|
|
119
|
-
last.status === "blocked";
|
|
120
|
-
if (resolved) {
|
|
121
|
-
return { continue: false, reason: "status-resolved" };
|
|
122
|
-
}
|
|
123
|
-
if (last.writeAttempts >= budget.maxWriteAttempts) {
|
|
124
|
-
return { continue: false, reason: "budget-write-attempts" };
|
|
125
|
-
}
|
|
126
|
-
if (last.tokensUsed >= budget.maxTokens) {
|
|
127
|
-
return { continue: false, reason: "budget-tokens" };
|
|
128
|
-
}
|
|
129
|
-
if (nowWallTimeMs - startWallTimeMs >= budget.maxWallTimeMs) {
|
|
130
|
-
return { continue: false, reason: "budget-wall-time" };
|
|
131
|
-
}
|
|
132
|
-
const repeatCount = history.filter((round) => round.fingerprint === last.fingerprint).length;
|
|
133
|
-
if (repeatCount >= budget.maxRepeatedFingerprints) {
|
|
134
|
-
return { continue: false, reason: "budget-repeated-fingerprint" };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (rounds >= budget.maxFocusedCheckRounds) {
|
|
138
|
-
return { continue: false, reason: "budget-focused-check-rounds" };
|
|
139
|
-
}
|
|
140
|
-
return { continue: true };
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Build a deterministic per-round checkpoint payload. Persistence stays with the
|
|
144
|
-
* caller (executor writes `runDir/<nodeId>/` only, never root `artifacts/**`).
|
|
145
|
-
*/
|
|
146
|
-
export function buildFrontendMicroLoopCheckpoint(input) {
|
|
147
|
-
return {
|
|
148
|
-
schemaVersion: 1,
|
|
149
|
-
nodeId: input.nodeId,
|
|
150
|
-
round: input.round,
|
|
151
|
-
status: deriveFrontendWriterStatus(input.facts).status,
|
|
152
|
-
fingerprint: computeFailureFingerprint(input.facts),
|
|
153
|
-
changedFiles: [...input.facts.changedFiles].sort(),
|
|
154
|
-
focusedCheckFailures: input.facts.focusedCheckFailures.map((failure) => ({
|
|
155
|
-
...failure,
|
|
156
|
-
})),
|
|
157
|
-
writeGuardOk: input.facts.writeGuardOk,
|
|
158
|
-
recordedAt: input.recordedAt,
|
|
159
|
-
wallTimeMs: input.facts.wallTimeMs,
|
|
160
|
-
tokensUsed: input.facts.tokensUsed,
|
|
161
|
-
writeAttempts: input.facts.writeAttempts,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Shadow comparison between the fact-derived status and the legacy
|
|
166
|
-
* `IMPLEMENTATION_OUTCOME:` first-line parse. Never used to decide ok/fail —
|
|
167
|
-
* only persisted for the M4 shadow evaluation.
|
|
168
|
-
*/
|
|
169
|
-
export function compareFactStatusToLegacyOutcome(factStatus, legacyOutcome) {
|
|
170
|
-
const factVerdict = factStatus === "changed" || factStatus === "already-satisfied"
|
|
171
|
-
? "ok"
|
|
172
|
-
: "fail";
|
|
173
|
-
const legacyVerdict = legacyOutcome === "changed" || legacyOutcome === "already-satisfied"
|
|
174
|
-
? "ok"
|
|
175
|
-
: "fail";
|
|
176
|
-
return {
|
|
177
|
-
factStatus,
|
|
178
|
-
legacyOutcome,
|
|
179
|
-
factVerdict,
|
|
180
|
-
legacyVerdict,
|
|
181
|
-
match: factVerdict === legacyVerdict,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Pure session-event scanner: count write-tool events from the persisted
|
|
186
|
-
* session-events.jsonl content. Fail-closed: an unparseable line never counts
|
|
187
|
-
* as a write, so a corrupt/empty log cannot inflate progress.
|
|
188
|
-
*/
|
|
189
|
-
export function countWriteToolEventsFromSessionEvents(content) {
|
|
190
|
-
let count = 0;
|
|
191
|
-
for (const line of content.split("\n")) {
|
|
192
|
-
const trimmed = line.trim();
|
|
193
|
-
if (!trimmed)
|
|
194
|
-
continue;
|
|
195
|
-
let event;
|
|
196
|
-
try {
|
|
197
|
-
event = JSON.parse(trimmed);
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
if (event !== null &&
|
|
203
|
-
typeof event === "object" &&
|
|
204
|
-
event.type === "tool_execution_start" &&
|
|
205
|
-
typeof event.toolName === "string" &&
|
|
206
|
-
FRONTEND_WRITE_TOOL_NAMES.has(event.toolName)) {
|
|
207
|
-
count += 1;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return count;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* IO collector for frontend facts. Reads only the persisted session-events
|
|
214
|
-
* file; every other fact is passed in by the executor so this stays narrow and
|
|
215
|
-
* deterministic under test (tests inject session content by writing to a temp
|
|
216
|
-
* path or call the pure scanner directly).
|
|
217
|
-
*/
|
|
218
|
-
export async function collectFrontendWriterFacts(input) {
|
|
219
|
-
let writeToolEvents = 0;
|
|
220
|
-
try {
|
|
221
|
-
const content = await readFile(input.sessionEventsPath, "utf8");
|
|
222
|
-
writeToolEvents = countWriteToolEventsFromSessionEvents(content);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
// Missing/unreadable session log → fail-closed at zero, never fail-open.
|
|
226
|
-
writeToolEvents = 0;
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
changedFiles: [...input.changedFiles].sort(),
|
|
230
|
-
writeToolEvents,
|
|
231
|
-
writeGuardOk: input.writeGuard.ok,
|
|
232
|
-
writeGuardViolations: [...input.writeGuard.violations],
|
|
233
|
-
requirementTargets: [...new Set(input.requirementTargets)].sort(),
|
|
234
|
-
coveredRequirementIds: [...new Set(input.coveredRequirementIds)].sort(),
|
|
235
|
-
focusedCheckFailures: input.focusedCheckFailures.map((failure) => ({
|
|
236
|
-
...failure,
|
|
237
|
-
})),
|
|
238
|
-
...(input.alreadySatisfiedEvidence
|
|
239
|
-
? {
|
|
240
|
-
alreadySatisfiedEvidence: {
|
|
241
|
-
requirementTargets: [
|
|
242
|
-
...input.alreadySatisfiedEvidence.requirementTargets,
|
|
243
|
-
].sort(),
|
|
244
|
-
proofSha256: input.alreadySatisfiedEvidence.proofSha256,
|
|
245
|
-
},
|
|
246
|
-
}
|
|
247
|
-
: {}),
|
|
248
|
-
tokensUsed: input.tokensUsed ?? 0,
|
|
249
|
-
wallTimeMs: input.wallTimeMs ?? 0,
|
|
250
|
-
rounds: input.rounds ?? 1,
|
|
251
|
-
writeAttempts: input.writeAttempts ?? 1,
|
|
252
|
-
...(input.firstLineText !== undefined
|
|
253
|
-
? { firstLineText: input.firstLineText }
|
|
254
|
-
: {}),
|
|
255
|
-
};
|
|
256
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
|
-
import { DAG_RUNS_DIR } from "./lifecycle.js";
|
|
6
|
-
function recoveryLeasePath(cwd, parentRunId) {
|
|
7
|
-
// Never derive a filesystem name directly from an externally supplied legacy
|
|
8
|
-
// run id. The content remains auditable while the filename is traversal-safe.
|
|
9
|
-
const key = createHash("sha256").update(parentRunId).digest("hex");
|
|
10
|
-
return path.join(cwd, DAG_RUNS_DIR, ".recovery-leases", `${key}.json`);
|
|
11
|
-
}
|
|
12
|
-
function parseLease(raw, parentRunId) {
|
|
13
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14
|
-
throw new Error("invalid dag recovery lease: expected object");
|
|
15
|
-
}
|
|
16
|
-
const record = raw;
|
|
17
|
-
if (record.schemaVersion !== 1 ||
|
|
18
|
-
record.parentRunId !== parentRunId ||
|
|
19
|
-
typeof record.requestId !== "string" ||
|
|
20
|
-
record.requestId.length === 0 ||
|
|
21
|
-
(record.kind !== "auto-frontend-recovery" &&
|
|
22
|
-
record.kind !== "rerun-from-node" &&
|
|
23
|
-
record.kind !== "standalone-task-rerun") ||
|
|
24
|
-
typeof record.leaseToken !== "string" ||
|
|
25
|
-
record.leaseToken.length === 0 ||
|
|
26
|
-
typeof record.acquiredAt !== "string" ||
|
|
27
|
-
typeof record.executionRef !== "undefined" &&
|
|
28
|
-
typeof record.executionRef !== "string") {
|
|
29
|
-
throw new Error("invalid dag recovery lease: schema mismatch");
|
|
30
|
-
}
|
|
31
|
-
return record;
|
|
32
|
-
}
|
|
33
|
-
export async function acquireDagRecoveryLease(input) {
|
|
34
|
-
const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
|
|
35
|
-
const lease = {
|
|
36
|
-
schemaVersion: 1,
|
|
37
|
-
parentRunId: input.parentRunId,
|
|
38
|
-
requestId: input.requestId,
|
|
39
|
-
kind: input.kind,
|
|
40
|
-
leaseToken: randomBytes(16).toString("hex"),
|
|
41
|
-
acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
42
|
-
};
|
|
43
|
-
await mkdir(path.dirname(leasePath), { recursive: true });
|
|
44
|
-
try {
|
|
45
|
-
// `wx` is the cross-process compare-and-create primitive. The lease is
|
|
46
|
-
// deliberately immutable for a parent run: a later recovery must follow
|
|
47
|
-
// the committed child lineage rather than fork another DAG.
|
|
48
|
-
await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
|
|
49
|
-
encoding: "utf8",
|
|
50
|
-
flag: "wx",
|
|
51
|
-
});
|
|
52
|
-
return { kind: "acquired", leasePath, lease };
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
const existing = parseLease(JSON.parse(await readFile(leasePath, "utf8")), input.parentRunId);
|
|
59
|
-
return { kind: "held", leasePath, lease: existing };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/** Persist the operation/run identity before the caller is allowed to execute it. */
|
|
63
|
-
export async function bindDagRecoveryLeaseExecution(input) {
|
|
64
|
-
if (!input.executionRef.trim()) {
|
|
65
|
-
throw new Error("dag recovery lease executionRef is required");
|
|
66
|
-
}
|
|
67
|
-
const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
|
|
68
|
-
if (existing.leaseToken !== input.lease.leaseToken) {
|
|
69
|
-
throw new Error("dag recovery lease ownership drift");
|
|
70
|
-
}
|
|
71
|
-
if (existing.executionRef && existing.executionRef !== input.executionRef) {
|
|
72
|
-
throw new Error("dag recovery lease execution identity drift");
|
|
73
|
-
}
|
|
74
|
-
const bound = {
|
|
75
|
-
...existing,
|
|
76
|
-
executionRef: input.executionRef,
|
|
77
|
-
};
|
|
78
|
-
await writeJsonAtomic(input.leasePath, bound);
|
|
79
|
-
return bound;
|
|
80
|
-
}
|