@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
|
@@ -14,19 +14,19 @@ export const FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH = ".runtime/import-manif
|
|
|
14
14
|
/** Staging directory prefix under `active/` for in-flight child materialization. */
|
|
15
15
|
export const FRONTEND_RECOVERY_STAGING_PREFIX = ".frontend-recovery-staging-";
|
|
16
16
|
export function deriveFrontendRecoveryFailureSource(result) {
|
|
17
|
-
if (result.
|
|
18
|
-
|
|
19
|
-
result.failureSource === "frontend-implement-pi" ||
|
|
20
|
-
result.failureSource === "frontend-verify-shell") {
|
|
21
|
-
return result.failureSource;
|
|
17
|
+
if (result.selectedPlanNodeId === "frontend-plan-revision-pi") {
|
|
18
|
+
return "frontend-plan-revision-pi";
|
|
22
19
|
}
|
|
23
|
-
|
|
20
|
+
if (result.selectedPlanNodeId === "frontend-plan-pi") {
|
|
21
|
+
return "frontend-plan-pi";
|
|
22
|
+
}
|
|
23
|
+
return "frontend-prewrite-gate-shell";
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Compute the frontend candidate-continuation reset partition:
|
|
27
|
-
* - `frontend-plan-pi` failure → reset plan + design-review +
|
|
28
|
-
* - `frontend-
|
|
29
|
-
* -
|
|
27
|
+
* - `frontend-plan-pi` failure → reset plan + design-review + prewrite (+ descendants);
|
|
28
|
+
* - `frontend-plan-revision-pi` failure → reset revision + final-review + prewrite (+ descendants);
|
|
29
|
+
* - prewrite-only materialization → reset prewrite (+ descendants), reuse verified plan/review.
|
|
30
30
|
*
|
|
31
31
|
* Everything upstream of the failure source is imported as verified parent facts.
|
|
32
32
|
*/
|
|
@@ -55,14 +55,7 @@ export function computeFrontendWriterRecoveryPlan(input) {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
function computeFrontendRecoveryPlanForSource(input) {
|
|
58
|
-
|
|
59
|
-
// verification commands / trace. Resetting only verify + its descendants
|
|
60
|
-
// would re-run the same failing checks against the same implementation, so
|
|
61
|
-
// reset from implement: the recovery re-implements and re-verifies.
|
|
62
|
-
const resetRoot = input.failureSource === "frontend-verify-shell"
|
|
63
|
-
? "frontend-implement-pi"
|
|
64
|
-
: input.failureSource;
|
|
65
|
-
const resetNodeIds = computeResetClosure(input.spec, resetRoot);
|
|
58
|
+
const resetNodeIds = computeResetClosure(input.spec, input.failureSource);
|
|
66
59
|
const resetSet = new Set(resetNodeIds);
|
|
67
60
|
const importedNodeIds = input.spec.tasks
|
|
68
61
|
.map((task) => task.id)
|
|
@@ -6,7 +6,7 @@ import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_RE
|
|
|
6
6
|
import { dagRunDirExists, getDagRunDir, readDagRunSpec, readDagRunState, transferDagRunDir, writeDagRunState, } from "./lifecycle.js";
|
|
7
7
|
import { buildDagRunId } from "./runner.js";
|
|
8
8
|
import { prepareRunDir, writeNodeRecord } from "./run-store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
10
10
|
import { topoSortToRanks } from "./topo.js";
|
|
11
11
|
/**
|
|
12
12
|
* Candidate-continuation child creation kernel (phase 3b).
|
|
@@ -19,8 +19,8 @@ import { topoSortToRanks } from "./topo.js";
|
|
|
19
19
|
* phase=child-running. Scheduling/execution of the child is phase 3c and out
|
|
20
20
|
* of scope here.
|
|
21
21
|
*/
|
|
22
|
-
/** Canonical contract artifact default location; overridden by the
|
|
23
|
-
*
|
|
22
|
+
/** Canonical contract artifact default location; overridden by the prewrite
|
|
23
|
+
* gate's configured outputDir/artifactName when present. */
|
|
24
24
|
const FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH = "contracts/frontend-implementation-contract.json";
|
|
25
25
|
function resolveDeps(deps) {
|
|
26
26
|
return {
|
|
@@ -62,9 +62,6 @@ function isValidFrontendRecoveryMarker(raw) {
|
|
|
62
62
|
* Read + validate the activation marker. Missing or invalid markers return
|
|
63
63
|
* `undefined` so the phase-3c gate and tests can treat an unmarked active
|
|
64
64
|
* child as not executable (AC-2 / AC-5d).
|
|
65
|
-
*
|
|
66
|
-
* @deprecated Read-only/scheduler compatibility only; the runner no longer
|
|
67
|
-
* uses activation markers as the authoritative recovery entry point.
|
|
68
65
|
*/
|
|
69
66
|
export async function readFrontendRecoveryMarker(parentRunDir, requestId) {
|
|
70
67
|
let raw;
|
|
@@ -82,8 +79,8 @@ export async function readFrontendRecoveryMarker(parentRunDir, requestId) {
|
|
|
82
79
|
return raw;
|
|
83
80
|
}
|
|
84
81
|
function classifyFrontendFactKind(nodeId) {
|
|
85
|
-
if (/
|
|
86
|
-
return "
|
|
82
|
+
if (/prewrite/i.test(nodeId))
|
|
83
|
+
return "prewrite";
|
|
87
84
|
if (/scout/i.test(nodeId))
|
|
88
85
|
return "scout";
|
|
89
86
|
if (/contract/i.test(nodeId))
|
|
@@ -94,9 +91,9 @@ function classifyFrontendFactKind(nodeId) {
|
|
|
94
91
|
}
|
|
95
92
|
function resolveCanonicalContractRelPath(spec) {
|
|
96
93
|
for (const task of spec.tasks) {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
return path.posix.join(
|
|
94
|
+
const gate = task.shell?.frontendPrewriteGate;
|
|
95
|
+
if (gate) {
|
|
96
|
+
return path.posix.join(gate.outputDir, gate.artifactName);
|
|
100
97
|
}
|
|
101
98
|
}
|
|
102
99
|
return FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH;
|
|
@@ -289,15 +286,15 @@ async function materializeChildStaging(input) {
|
|
|
289
286
|
});
|
|
290
287
|
}
|
|
291
288
|
else {
|
|
292
|
-
const
|
|
293
|
-
if (!
|
|
294
|
-
throw new Error(
|
|
289
|
+
const prewriteRead = await readFrontendPrewriteResult(parentRunDir);
|
|
290
|
+
if (!prewriteRead.ok) {
|
|
291
|
+
throw new Error(prewriteRead.reason);
|
|
295
292
|
}
|
|
296
293
|
plan = computeFrontendRecoveryPlan({
|
|
297
294
|
spec: parentSpec,
|
|
298
295
|
parentRunId,
|
|
299
296
|
requestId,
|
|
300
|
-
result:
|
|
297
|
+
result: prewriteRead.result,
|
|
301
298
|
});
|
|
302
299
|
}
|
|
303
300
|
const { ranks } = topoSortToRanks(parentSpec);
|
|
@@ -333,17 +330,17 @@ async function materializeChildStaging(input) {
|
|
|
333
330
|
throw new Error(`node ${task.id} missing from frontend recovery plan partition`);
|
|
334
331
|
}
|
|
335
332
|
}
|
|
336
|
-
// Canonical contract +
|
|
333
|
+
// Canonical contract + prewrite-result identity facts are copied alongside
|
|
337
334
|
// the per-node facts so reset nodes can re-read them (AC-4).
|
|
338
335
|
const canonicalContract = await copyArtifactVerified({
|
|
339
336
|
parentRunDir,
|
|
340
337
|
newRunDir: stagingDir,
|
|
341
338
|
relativePath: resolveCanonicalContractRelPath(parentSpec),
|
|
342
339
|
});
|
|
343
|
-
const
|
|
340
|
+
const prewriteResult = await copyArtifactVerified({
|
|
344
341
|
parentRunDir,
|
|
345
342
|
newRunDir: stagingDir,
|
|
346
|
-
relativePath:
|
|
343
|
+
relativePath: FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT,
|
|
347
344
|
});
|
|
348
345
|
const childState = {
|
|
349
346
|
version: 1,
|
|
@@ -380,9 +377,9 @@ async function materializeChildStaging(input) {
|
|
|
380
377
|
relativePath: canonicalContract.destinationRelativePath,
|
|
381
378
|
sha256: canonicalContract.sha256,
|
|
382
379
|
},
|
|
383
|
-
|
|
384
|
-
relativePath:
|
|
385
|
-
sha256:
|
|
380
|
+
prewriteResult: {
|
|
381
|
+
relativePath: prewriteResult.destinationRelativePath,
|
|
382
|
+
sha256: prewriteResult.sha256,
|
|
386
383
|
},
|
|
387
384
|
importedFacts,
|
|
388
385
|
};
|
|
@@ -420,9 +417,6 @@ async function verifyThreeWayConsistency(input) {
|
|
|
420
417
|
* intent. Single-writer: the recovery coordinator is the only caller.
|
|
421
418
|
* Fail-closed on revision drift, missing intent, unreadable prewrite result,
|
|
422
419
|
* hash mismatch, marker-write failure, or consistency failure.
|
|
423
|
-
*
|
|
424
|
-
* @deprecated The M6 operation controller is the authoritative recovery loop;
|
|
425
|
-
* this kernel is retained only for read-only/scheduler compatibility.
|
|
426
420
|
*/
|
|
427
421
|
export async function stageRecoveryChild(options) {
|
|
428
422
|
const { cwd, parentRunId } = options;
|
|
@@ -458,15 +452,8 @@ export async function stageRecoveryChild(options) {
|
|
|
458
452
|
}
|
|
459
453
|
}
|
|
460
454
|
// Derive the childRunId exactly once; replay always reuses a reserved id.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// staging path aliases the parent and the required import manifest is absent.
|
|
464
|
-
const childRunId = recovery.childRunId ?? await (async () => {
|
|
465
|
-
const generatedChildRunId = await (options.buildChildRunId ?? buildDagRunId)(parentSpec, cwd);
|
|
466
|
-
return generatedChildRunId === parentRunId
|
|
467
|
-
? `${generatedChildRunId}-recovery-${requestId.slice(0, 8)}`
|
|
468
|
-
: generatedChildRunId;
|
|
469
|
-
})();
|
|
455
|
+
const childRunId = recovery.childRunId ??
|
|
456
|
+
(await (options.buildChildRunId ?? buildDagRunId)(parentSpec, cwd));
|
|
470
457
|
// Orphan staging cleanup (D4.3): remove any in-flight staging dir for this id.
|
|
471
458
|
const activeRootDir = getDagRunDir(cwd, "active", "");
|
|
472
459
|
const stagingDir = path.join(activeRootDir, `${FRONTEND_RECOVERY_STAGING_PREFIX}${childRunId}`);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
4
|
+
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
5
|
+
import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
6
|
+
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE, } from "./frontend-verification-trace.js";
|
|
7
|
+
export const FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID = "frontend-repair-assessment-v1";
|
|
3
8
|
export const frontendRepairFailureClassSchema = z.enum([
|
|
4
9
|
"typecheck",
|
|
5
10
|
"build",
|
|
@@ -24,6 +29,55 @@ export const frontendCommandCategorySchema = z.enum([
|
|
|
24
29
|
"unit-test",
|
|
25
30
|
"trace",
|
|
26
31
|
]);
|
|
32
|
+
const REPAIRABLE = new Set([
|
|
33
|
+
"typecheck",
|
|
34
|
+
"build",
|
|
35
|
+
"lint",
|
|
36
|
+
"component-test",
|
|
37
|
+
"unit-test",
|
|
38
|
+
"trace",
|
|
39
|
+
]);
|
|
40
|
+
export function isFrontendRepairable(failureClass) {
|
|
41
|
+
return REPAIRABLE.has(failureClass);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Deterministic commandLabel → commandCategory mapping (AC-3).
|
|
45
|
+
* `targetType` comes from the contract verificationTargets[].type
|
|
46
|
+
* (static | unit | component | integration | mock); the literal commandLabel
|
|
47
|
+
* wins over type so a `static` typecheck target is never misread as a test.
|
|
48
|
+
*/
|
|
49
|
+
export function mapCommandLabelToCategory(commandLabel, targetType) {
|
|
50
|
+
const label = commandLabel.toLowerCase();
|
|
51
|
+
if (/(?:typecheck|check-types|type-check|\btsc\b)/.test(label))
|
|
52
|
+
return "typecheck";
|
|
53
|
+
if (/(?:vite\s+build|next\s+build|webpack|rollup|\bbuild\b)/.test(label))
|
|
54
|
+
return "build";
|
|
55
|
+
if (/(?:eslint|\blint\b)/.test(label))
|
|
56
|
+
return "lint";
|
|
57
|
+
if (targetType === "component")
|
|
58
|
+
return "component-test";
|
|
59
|
+
if (targetType === "unit")
|
|
60
|
+
return "unit-test";
|
|
61
|
+
if (/(?:component|testing-library|render\()/.test(label))
|
|
62
|
+
return "component-test";
|
|
63
|
+
return "unit-test";
|
|
64
|
+
}
|
|
65
|
+
export function isReviewRepairable(finding, implementWriteSet) {
|
|
66
|
+
const hasFile = Boolean(finding.file && finding.file.trim().length > 0);
|
|
67
|
+
const hasLocation = Boolean((finding.line !== undefined && finding.line > 0) ||
|
|
68
|
+
(finding.symbol && finding.symbol.trim().length > 0));
|
|
69
|
+
const hasRequirement = Boolean((finding.requirementId && finding.requirementId.trim().length > 0) ||
|
|
70
|
+
(finding.acceptanceCriteriaId &&
|
|
71
|
+
finding.acceptanceCriteriaId.trim().length > 0));
|
|
72
|
+
const fixScope = finding.fixScope ?? (hasFile ? [finding.file] : []);
|
|
73
|
+
const fixScopeSubset = fixScope.length > 0 &&
|
|
74
|
+
fixScope.every((entry) => isPathWithinWriteSet(entry, implementWriteSet));
|
|
75
|
+
return (hasFile &&
|
|
76
|
+
hasLocation &&
|
|
77
|
+
hasRequirement &&
|
|
78
|
+
fixScopeSubset &&
|
|
79
|
+
finding.requiresContractReplan !== true);
|
|
80
|
+
}
|
|
27
81
|
export function classifyFrontendFailure(input) {
|
|
28
82
|
// AC-2: structured evidence takes priority over string-blob heuristics.
|
|
29
83
|
if (input.failureCategory === "write-guard") {
|
|
@@ -122,3 +176,380 @@ export function classifyFrontendFailure(input) {
|
|
|
122
176
|
return "path";
|
|
123
177
|
return "unknown";
|
|
124
178
|
}
|
|
179
|
+
export const frontendRepairAssessmentSchema = z
|
|
180
|
+
.object({
|
|
181
|
+
schemaVersion: z.literal(1),
|
|
182
|
+
schemaId: z.literal(FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID),
|
|
183
|
+
contract: z
|
|
184
|
+
.object({
|
|
185
|
+
relativePath: z.string().min(1),
|
|
186
|
+
schemaId: z.string().min(1),
|
|
187
|
+
})
|
|
188
|
+
.strict(),
|
|
189
|
+
failedNodeIds: z.array(z.string().min(1)),
|
|
190
|
+
failureClass: frontendRepairFailureClassSchema,
|
|
191
|
+
eligible: z.boolean(),
|
|
192
|
+
reason: z.string().min(1),
|
|
193
|
+
attempt: z.number().int().min(1).max(2),
|
|
194
|
+
maxAttempts: z.literal(1),
|
|
195
|
+
allowedRepairWriteSet: z.array(z.string().min(1)),
|
|
196
|
+
evidenceRefs: z.array(z.string().min(1)),
|
|
197
|
+
browserStatus: z.literal("not-run"),
|
|
198
|
+
// AC-5 structured evidence fields (all optional so existing minimal
|
|
199
|
+
// fixtures and frontend-review-context safeParse stay compatible).
|
|
200
|
+
verificationTargetId: z.string().min(1).optional(),
|
|
201
|
+
commandLabel: z.string().min(1).optional(),
|
|
202
|
+
exitCode: z.number().int().optional(),
|
|
203
|
+
commandCategory: frontendCommandCategorySchema.optional(),
|
|
204
|
+
requirementIds: z.array(z.string().min(1)).optional(),
|
|
205
|
+
acceptanceCriteriaIds: z.array(z.string().min(1)).optional(),
|
|
206
|
+
fixScope: z.array(z.string().min(1)).optional(),
|
|
207
|
+
changedPaths: z.array(z.string().min(1)).optional(),
|
|
208
|
+
traceRef: z
|
|
209
|
+
.object({
|
|
210
|
+
relativePath: z.string().min(1),
|
|
211
|
+
schemaId: z.string().min(1),
|
|
212
|
+
})
|
|
213
|
+
.strict()
|
|
214
|
+
.optional(),
|
|
215
|
+
})
|
|
216
|
+
.strict();
|
|
217
|
+
function nodeHadFailure(record) {
|
|
218
|
+
if (record.status === "ERROR")
|
|
219
|
+
return true;
|
|
220
|
+
if (recordIndicatesCommandFailure(record))
|
|
221
|
+
return true;
|
|
222
|
+
const results = record.commandResults ?? [];
|
|
223
|
+
if (results.some((item) => item.ok === false))
|
|
224
|
+
return true;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
function normalize(value) {
|
|
228
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Whether a concrete path or glob entry is contained by the implement writeSet.
|
|
232
|
+
* Exact glob equality is the norm (repair writeSet === implement writeSet);
|
|
233
|
+
* concrete (non-glob) paths may also be covered by an implement glob.
|
|
234
|
+
*/
|
|
235
|
+
export function isPathWithinWriteSet(entry, implementWriteSet) {
|
|
236
|
+
const normalizedEntry = normalize(entry);
|
|
237
|
+
const normalizedImplement = implementWriteSet.map(normalize);
|
|
238
|
+
if (normalizedImplement.includes(normalizedEntry))
|
|
239
|
+
return true;
|
|
240
|
+
if (!normalizedEntry.includes("*")) {
|
|
241
|
+
return implementWriteSet.some((pattern) => pathMatchesPattern(normalizedEntry, pattern));
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
export function assertWriteSetSubset(repairWriteSet, implementWriteSet) {
|
|
246
|
+
for (const entry of repairWriteSet) {
|
|
247
|
+
if (!isPathWithinWriteSet(entry, implementWriteSet)) {
|
|
248
|
+
throw new Error(`repair writeSet entry "${entry}" is not ⊆ implement writeSet`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function loadImplementWriteSet(runDir, fallback) {
|
|
253
|
+
if (fallback.length > 0)
|
|
254
|
+
return fallback;
|
|
255
|
+
const candidates = ["run.json", "dag.json", "spec.json"];
|
|
256
|
+
for (const name of candidates) {
|
|
257
|
+
try {
|
|
258
|
+
const raw = JSON.parse(await readFile(path.join(runDir, name), "utf8"));
|
|
259
|
+
const tasks = raw.spec?.tasks ?? raw.tasks ?? [];
|
|
260
|
+
const implement = tasks.find((task) => task.id === "frontend-implement-pi");
|
|
261
|
+
if (implement?.writeSet?.length)
|
|
262
|
+
return implement.writeSet;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// continue
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return fallback;
|
|
269
|
+
}
|
|
270
|
+
function matchVerificationTarget(contract, commandLabel, command) {
|
|
271
|
+
for (const target of contract.verificationTargets) {
|
|
272
|
+
if (commandLabel && target.commandLabel === commandLabel)
|
|
273
|
+
return target;
|
|
274
|
+
if (command &&
|
|
275
|
+
(command.includes(target.commandLabel) ||
|
|
276
|
+
target.commandLabel.includes(command))) {
|
|
277
|
+
return target;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
function deriveFailureEvidence(input) {
|
|
283
|
+
const evidence = {};
|
|
284
|
+
for (const { record } of input.failed) {
|
|
285
|
+
const results = record.commandResults ?? [];
|
|
286
|
+
const failedResult = results.find((item) => item.ok === false) ?? results[0];
|
|
287
|
+
if (failedResult &&
|
|
288
|
+
evidence.exitCode === undefined &&
|
|
289
|
+
typeof failedResult.exitCode === "number") {
|
|
290
|
+
evidence.exitCode = failedResult.exitCode;
|
|
291
|
+
}
|
|
292
|
+
if (failedResult?.commandLabel && !evidence.commandLabel) {
|
|
293
|
+
evidence.commandLabel = failedResult.commandLabel;
|
|
294
|
+
}
|
|
295
|
+
// Structured trace facts passed by the verification bundle / trace gate.
|
|
296
|
+
const failedTraceTarget = (record.traceTargets ?? []).find((target) => target.status === "failed");
|
|
297
|
+
if (failedTraceTarget && !evidence.verificationTargetId) {
|
|
298
|
+
evidence.verificationTargetId = failedTraceTarget.id;
|
|
299
|
+
if (!evidence.commandLabel)
|
|
300
|
+
evidence.commandLabel = failedTraceTarget.commandLabel;
|
|
301
|
+
evidence.traceTarget = failedTraceTarget;
|
|
302
|
+
evidence.traceRef = {
|
|
303
|
+
relativePath: "contracts/frontend-verification-trace.json",
|
|
304
|
+
schemaId: "frontend-verification-trace-v1",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (record.changedPaths?.length && !evidence.changedPaths) {
|
|
308
|
+
evidence.changedPaths = [...record.changedPaths];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Resolve commandLabel → contract verification target → category + scope.
|
|
312
|
+
const target = matchVerificationTarget(input.contract, evidence.commandLabel);
|
|
313
|
+
if (target) {
|
|
314
|
+
if (!evidence.verificationTargetId)
|
|
315
|
+
evidence.verificationTargetId = target.id;
|
|
316
|
+
if (!evidence.commandLabel)
|
|
317
|
+
evidence.commandLabel = target.commandLabel;
|
|
318
|
+
evidence.requirementIds ??= [...target.requirementIds];
|
|
319
|
+
evidence.acceptanceCriteriaIds ??= [...target.requirementIds];
|
|
320
|
+
evidence.fixScope ??= [target.file];
|
|
321
|
+
}
|
|
322
|
+
if (evidence.commandLabel) {
|
|
323
|
+
evidence.commandCategory ??= mapCommandLabelToCategory(evidence.commandLabel, target?.type);
|
|
324
|
+
}
|
|
325
|
+
return evidence;
|
|
326
|
+
}
|
|
327
|
+
export async function runFrontendFailureAssessGate(input) {
|
|
328
|
+
const attempt = input.attempt ?? 1;
|
|
329
|
+
const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
|
|
330
|
+
if (implementWriteSet.length === 0) {
|
|
331
|
+
throw new Error("assess: implement writeSet unavailable");
|
|
332
|
+
}
|
|
333
|
+
const contractRel = "contracts/frontend-implementation-contract.json";
|
|
334
|
+
const contractPath = path.join(input.runDir, contractRel);
|
|
335
|
+
let contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
|
|
336
|
+
let contract;
|
|
337
|
+
try {
|
|
338
|
+
const raw = JSON.parse(await readFile(contractPath, "utf8"));
|
|
339
|
+
const parsed = frontendImplementationContractSchema.safeParse(raw);
|
|
340
|
+
if (!parsed.success) {
|
|
341
|
+
throw new Error("invalid frontend implementation contract");
|
|
342
|
+
}
|
|
343
|
+
contract = parsed.data;
|
|
344
|
+
contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// still allow assessment of failures without contract? fail-closed to contract
|
|
348
|
+
throw new Error("assess: missing or invalid contracts/frontend-implementation-contract.json");
|
|
349
|
+
}
|
|
350
|
+
const candidateNodeIds = [
|
|
351
|
+
"frontend-verify-assess-shell",
|
|
352
|
+
"frontend-reverify-shell",
|
|
353
|
+
"frontend-static-verify-shell",
|
|
354
|
+
"frontend-behavior-verify-shell",
|
|
355
|
+
"frontend-verification-trace-shell",
|
|
356
|
+
"frontend-static-reverify-shell",
|
|
357
|
+
"frontend-behavior-reverify-shell",
|
|
358
|
+
"frontend-verification-retrace-shell",
|
|
359
|
+
];
|
|
360
|
+
const failed = [
|
|
361
|
+
...(input.failureFacts ?? []).filter(({ record }) => nodeHadFailure(record) || recordIndicatesCommandFailure(record)),
|
|
362
|
+
];
|
|
363
|
+
for (const nodeId of candidateNodeIds) {
|
|
364
|
+
if (failed.some((item) => item.nodeId === nodeId))
|
|
365
|
+
continue;
|
|
366
|
+
const nodePath = path.join(input.runDir, `${nodeId}.json`);
|
|
367
|
+
try {
|
|
368
|
+
const record = JSON.parse(await readFile(nodePath, "utf8"));
|
|
369
|
+
if (nodeHadFailure(record) || recordIndicatesCommandFailure(record)) {
|
|
370
|
+
failed.push({ nodeId, record });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// missing optional nodes ok
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (failed.length === 0) {
|
|
378
|
+
const assessment = {
|
|
379
|
+
schemaVersion: 1,
|
|
380
|
+
schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
|
|
381
|
+
contract: {
|
|
382
|
+
relativePath: contractRel,
|
|
383
|
+
schemaId: contractSchemaId,
|
|
384
|
+
},
|
|
385
|
+
failedNodeIds: [],
|
|
386
|
+
failureClass: "unknown",
|
|
387
|
+
eligible: false,
|
|
388
|
+
reason: "no failed frontend verify/trace nodes in current run",
|
|
389
|
+
attempt,
|
|
390
|
+
maxAttempts: 1,
|
|
391
|
+
allowedRepairWriteSet: [...implementWriteSet],
|
|
392
|
+
evidenceRefs: [contractRel],
|
|
393
|
+
browserStatus: "not-run",
|
|
394
|
+
};
|
|
395
|
+
await writeAssessment(input.runDir, assessment);
|
|
396
|
+
return assessment;
|
|
397
|
+
}
|
|
398
|
+
const primary = failed[0];
|
|
399
|
+
const evidence = deriveFailureEvidence({ contract, failed });
|
|
400
|
+
const failureClass = classifyFrontendFailure({
|
|
401
|
+
nodeId: primary.nodeId,
|
|
402
|
+
failureCategory: primary.record.failureCategory,
|
|
403
|
+
stdout: primary.record.stdout,
|
|
404
|
+
stderr: primary.record.stderr,
|
|
405
|
+
commandLabel: evidence.commandLabel,
|
|
406
|
+
commandCategory: evidence.commandCategory,
|
|
407
|
+
exitCode: evidence.exitCode,
|
|
408
|
+
traceTarget: evidence.traceTarget ?? null,
|
|
409
|
+
changedPaths: evidence.changedPaths,
|
|
410
|
+
});
|
|
411
|
+
// AC-5: repairable=true requires structured evidence (a resolved exitCode,
|
|
412
|
+
// commandCategory, or trace-bound verification target). review has its own
|
|
413
|
+
// stricter gate (AC-4) and is never unconditionally repairable.
|
|
414
|
+
const baseEligible = isFrontendRepairable(failureClass) && attempt <= 1;
|
|
415
|
+
let eligible;
|
|
416
|
+
let reason;
|
|
417
|
+
if (failureClass === "review") {
|
|
418
|
+
const finding = primary.record.reviewFinding ?? {};
|
|
419
|
+
eligible = attempt <= 1 && isReviewRepairable(finding, implementWriteSet);
|
|
420
|
+
reason = eligible
|
|
421
|
+
? `repairable review finding on ${primary.nodeId}`
|
|
422
|
+
: `review finding lacks file/line-symbol/requirement evidence or is outside writeSet on ${primary.nodeId}`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const hasStructuredEvidence = evidence.exitCode !== undefined ||
|
|
426
|
+
evidence.commandCategory !== undefined ||
|
|
427
|
+
Boolean(evidence.verificationTargetId);
|
|
428
|
+
eligible = baseEligible && hasStructuredEvidence;
|
|
429
|
+
reason = eligible
|
|
430
|
+
? `repairable failureClass=${failureClass} on ${primary.nodeId}`
|
|
431
|
+
: !baseEligible
|
|
432
|
+
? `non-repairable or ineligible: failureClass=${failureClass} attempt=${attempt}`
|
|
433
|
+
: `missing structured evidence for failureClass=${failureClass} on ${primary.nodeId}`;
|
|
434
|
+
if (!eligible &&
|
|
435
|
+
`${primary.record.stderr ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
|
|
436
|
+
// Surface the actionable unavailable-verification reason in the
|
|
437
|
+
// assessment (and the non-repairable gate error) instead of a
|
|
438
|
+
// generic class label.
|
|
439
|
+
reason = FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// fixScope and changedPaths must stay inside the implement writeSet.
|
|
443
|
+
if (evidence.fixScope?.length) {
|
|
444
|
+
assertWriteSetSubset(evidence.fixScope, implementWriteSet);
|
|
445
|
+
}
|
|
446
|
+
if (evidence.changedPaths?.length) {
|
|
447
|
+
assertWriteSetSubset(evidence.changedPaths, implementWriteSet);
|
|
448
|
+
}
|
|
449
|
+
const assessment = {
|
|
450
|
+
schemaVersion: 1,
|
|
451
|
+
schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
|
|
452
|
+
contract: {
|
|
453
|
+
relativePath: contractRel,
|
|
454
|
+
schemaId: contractSchemaId,
|
|
455
|
+
},
|
|
456
|
+
failedNodeIds: failed.map((item) => item.nodeId),
|
|
457
|
+
failureClass,
|
|
458
|
+
eligible,
|
|
459
|
+
reason,
|
|
460
|
+
attempt,
|
|
461
|
+
maxAttempts: 1,
|
|
462
|
+
allowedRepairWriteSet: [...implementWriteSet],
|
|
463
|
+
evidenceRefs: [contractRel, ...failed.map((item) => `${item.nodeId}.json`)],
|
|
464
|
+
browserStatus: "not-run",
|
|
465
|
+
...(evidence.verificationTargetId
|
|
466
|
+
? { verificationTargetId: evidence.verificationTargetId }
|
|
467
|
+
: {}),
|
|
468
|
+
...(evidence.commandLabel ? { commandLabel: evidence.commandLabel } : {}),
|
|
469
|
+
...(evidence.exitCode !== undefined ? { exitCode: evidence.exitCode } : {}),
|
|
470
|
+
...(evidence.commandCategory
|
|
471
|
+
? { commandCategory: evidence.commandCategory }
|
|
472
|
+
: {}),
|
|
473
|
+
...(evidence.requirementIds?.length
|
|
474
|
+
? { requirementIds: evidence.requirementIds }
|
|
475
|
+
: {}),
|
|
476
|
+
...(evidence.acceptanceCriteriaIds?.length
|
|
477
|
+
? { acceptanceCriteriaIds: evidence.acceptanceCriteriaIds }
|
|
478
|
+
: {}),
|
|
479
|
+
...(evidence.fixScope?.length ? { fixScope: evidence.fixScope } : {}),
|
|
480
|
+
...(evidence.changedPaths?.length
|
|
481
|
+
? { changedPaths: evidence.changedPaths }
|
|
482
|
+
: {}),
|
|
483
|
+
...(evidence.traceRef ? { traceRef: evidence.traceRef } : {}),
|
|
484
|
+
};
|
|
485
|
+
await writeAssessment(input.runDir, assessment);
|
|
486
|
+
return assessment;
|
|
487
|
+
}
|
|
488
|
+
function recordIndicatesCommandFailure(record) {
|
|
489
|
+
// Prefer explicit commandResults when present
|
|
490
|
+
if (record.commandResults?.some((item) => item.ok === false))
|
|
491
|
+
return true;
|
|
492
|
+
// Node may be FINISHED with nonZeroExitPolicy=record; look for harness markers
|
|
493
|
+
const stdout = record.stdout ?? "";
|
|
494
|
+
if (/nonZeroExitPolicy=record/.test(stdout) &&
|
|
495
|
+
/exitCode=[1-9]/.test(stdout)) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
if (record.failureCategory && record.failureCategory !== "success") {
|
|
499
|
+
// recorded failures may still be FINISHED
|
|
500
|
+
if (record.status === "FINISHED") {
|
|
501
|
+
return (record.failureCategory === "nonzero-exit" ||
|
|
502
|
+
record.failureCategory === "invalid-output" ||
|
|
503
|
+
record.failureCategory === "timeout");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
async function writeAssessment(runDir, assessment) {
|
|
509
|
+
const relative = "contracts/frontend-repair-assessment.json";
|
|
510
|
+
const absolute = path.join(runDir, relative);
|
|
511
|
+
await mkdir(path.dirname(absolute), { recursive: true });
|
|
512
|
+
await writeFile(absolute, `${JSON.stringify(assessment, null, 2)}\n`, "utf8");
|
|
513
|
+
return absolute;
|
|
514
|
+
}
|
|
515
|
+
export async function runFrontendRepairContractGate(input) {
|
|
516
|
+
const relative = "contracts/frontend-repair-assessment.json";
|
|
517
|
+
const absolute = path.join(input.runDir, relative);
|
|
518
|
+
const raw = JSON.parse(await readFile(absolute, "utf8"));
|
|
519
|
+
const parsed = frontendRepairAssessmentSchema.safeParse(raw);
|
|
520
|
+
if (!parsed.success) {
|
|
521
|
+
throw new Error(`repair-contract: invalid assessment: ${parsed.error.issues
|
|
522
|
+
.map((i) => i.message)
|
|
523
|
+
.join("; ")}`);
|
|
524
|
+
}
|
|
525
|
+
const assessment = parsed.data;
|
|
526
|
+
const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
|
|
527
|
+
assertWriteSetSubset(assessment.allowedRepairWriteSet, implementWriteSet.length
|
|
528
|
+
? implementWriteSet
|
|
529
|
+
: assessment.allowedRepairWriteSet);
|
|
530
|
+
if (assessment.attempt > assessment.maxAttempts) {
|
|
531
|
+
throw new Error("repair-contract: attempt exceeds maxAttempts");
|
|
532
|
+
}
|
|
533
|
+
if (assessment.failedNodeIds.length === 0) {
|
|
534
|
+
return { ok: true, assessment };
|
|
535
|
+
}
|
|
536
|
+
if (!assessment.eligible) {
|
|
537
|
+
throw new Error(`repair-contract: non-repairable failure (${assessment.failureClass}): ${assessment.reason}`);
|
|
538
|
+
}
|
|
539
|
+
if (!isFrontendRepairable(assessment.failureClass) &&
|
|
540
|
+
assessment.failureClass !== "review") {
|
|
541
|
+
throw new Error(`repair-contract: failureClass ${assessment.failureClass} is not repairable`);
|
|
542
|
+
}
|
|
543
|
+
return { ok: true, assessment };
|
|
544
|
+
}
|
|
545
|
+
export function formatFrontendFailureAssessStdout(assessment) {
|
|
546
|
+
return JSON.stringify(assessment);
|
|
547
|
+
}
|
|
548
|
+
export function formatFrontendRepairContractStdout(assessment) {
|
|
549
|
+
return [
|
|
550
|
+
"Frontend repair contract: pass",
|
|
551
|
+
`eligible=${assessment.eligible}`,
|
|
552
|
+
`failureClass=${assessment.failureClass}`,
|
|
553
|
+
`writeSetSize=${assessment.allowedRepairWriteSet.length}`,
|
|
554
|
+
].join("\n");
|
|
555
|
+
}
|