@tea-agent/loop-agent 0.43.0-next.9 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +31 -2309
- package/README.md +7 -0
- package/dist/application/dag/args.js +8 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +2 -0
- package/dist/cli/program.js +3 -1
- package/dist/cli/update/init-surface-notifier.js +2 -0
- package/dist/cli/update/policy.js +2 -0
- package/dist/commands/client-recovery.js +20 -55
- package/dist/commands/cursor-prompt.js +2 -0
- package/dist/commands/dag-approve.js +2 -0
- package/dist/commands/dag-final-verification.js +2 -0
- package/dist/commands/dag-init-hybrid.js +2 -0
- package/dist/commands/dag-reconcile-tasks.js +2 -0
- package/dist/commands/dag-reject.js +2 -0
- package/dist/commands/dag-report.js +2 -0
- package/dist/commands/dag-rerun.js +2 -0
- package/dist/commands/dag-resume.js +2 -0
- package/dist/commands/dag-workflow-compile.js +2 -0
- package/dist/commands/dag-workflow-plan.js +2 -0
- package/dist/commands/dag-workflow-validate.js +2 -0
- package/dist/commands/delegate.js +2 -0
- package/dist/commands/examples.js +2 -0
- package/dist/commands/import-prd.js +2 -0
- package/dist/commands/init-model-catalog.js +2 -0
- package/dist/commands/init.js +19 -13
- package/dist/commands/instructions.js +2 -0
- package/dist/commands/knowledge.js +2 -0
- package/dist/commands/loop-benchmark.js +2 -0
- package/dist/commands/pi-prompt.js +2 -0
- package/dist/commands/pi-reuse-benchmark.js +2 -0
- package/dist/commands/promote-run.js +2 -0
- package/dist/commands/stats.js +1 -1
- package/dist/commands/study-init.js +2 -0
- package/dist/commands/task-advance.js +34 -0
- package/dist/commands/task-source-prepare.js +2 -0
- package/dist/commands/worktree-create.js +2 -0
- package/dist/commands/worktree-remove.js +2 -0
- package/dist/executors/dag-pi-executor.js +1650 -198
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +93 -7
- package/dist/executors/shell-verification.js +3 -0
- package/dist/executors/shell-write-guard.js +27 -32
- package/dist/governance/exec-plans.js +6 -3
- package/dist/infrastructure/console/app-data.js +6 -0
- package/dist/shared/artifacts-core.js +2 -0
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/git-progress.js +2 -0
- package/dist/shared/one-shot-prompt-args.js +2 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/shared/package-metadata.js +6 -4
- package/dist/shared/pi-context-pressure/extension.js +26 -16
- package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
- package/dist/shared/pi-provider-recovery.js +182 -0
- package/dist/shared/pi-retry-settings.js +40 -0
- package/dist/shared/reference-context.js +2 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/recover.js +6 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/artifact-meta.js +9 -2
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/task/source-prepare/fragment-inventory.js +4 -1
- package/dist/task/source-prepare/parse-intent.js +36 -11
- package/dist/task/task-demand-routing.js +5 -2
- package/dist/worker/console/chat/browser-automation.js +523 -0
- package/dist/worker/console/chat/browser-policy.js +6 -36
- package/dist/worker/console/chat/browser-routes.js +188 -56
- package/dist/worker/console/chat/explore-tools.js +5 -2
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +208 -1138
- package/dist/worker/console/chat/routes.js +27 -7
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
- package/dist/worker/console/chat/session-mode-view.js +5 -4
- package/dist/worker/console/chat/session-mode.js +18 -12
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/tools.js +10 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/console-update-and-init.js +1 -19
- package/dist/worker/console/console-update-runtime.js +5 -20
- package/dist/worker/console/operator-actions.js +63 -1
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/routes.js +8 -0
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +24 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
- package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
- package/dist/worker/console/static-src/prd-file-import.js +3 -1
- package/dist/worker/console/workspace-context.js +22 -0
- package/dist/worker/console/workspace-initialization.js +209 -0
- package/dist/worker/delivery/git-transaction.js +2 -0
- package/dist/worker/feature/fullstack-validate.js +2 -0
- package/dist/worker/loop-agent/loop-agent-client.js +8 -2
- package/dist/worker/materialize/harness-task-materializer.js +4 -1
- package/dist/worker/metrics/projector.js +1 -1
- package/dist/worker/observability/event-store.js +4 -1
- package/dist/worker/observability/read-model.js +4 -1
- package/dist/worker/observe/node-transparency.js +23 -1
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/observe/static/styles.css +7 -4
- package/dist/worker/observe/static/views/dag-inspector.js +5 -19
- package/dist/worker/outcomes/declared-artifacts.js +2 -0
- package/dist/worker/preflight.js +3 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/worker/task-spec/validate.js +4 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/decision-envelope.js +2 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-repair.js +55 -76
- package/dist/workflows/dag/frontend-review-context.js +42 -68
- package/dist/workflows/dag/frontend-review-scopes.js +2 -2
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +33 -54
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +47 -19
- package/dist/workflows/dag/interrupt-request.js +2 -0
- package/dist/workflows/dag/lifecycle.js +11 -2
- package/dist/workflows/dag/node-execution.js +50 -13
- package/dist/workflows/dag/reconcile-run.js +2 -0
- package/dist/workflows/dag/repair-artifact.js +3 -1
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +79 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +44 -6
- package/dist/workflows/dag/validate.js +4 -0
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/governance/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/package.json +9 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +3 -1
- package/skills/frontend-plan/SKILL.md +8 -6
- package/skills/frontend-plan/references/decision-contract.md +3 -3
- package/skills/frontend-review/SKILL.md +18 -28
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/dist/commands/new-task.js +0 -5
- package/dist/commands/status.js +0 -57
- package/dist/commands/task-contract.js +0 -269
- package/dist/shared/prompts.js +0 -26
- package/dist/task/config.js +0 -2
- package/dist/task/contract/validate-draft.js +0 -106
- package/dist/task/goal.js +0 -3
- package/dist/task/index.js +0 -12
- package/dist/task/lifecycle.js +0 -1
- package/dist/task/operator/capabilities.js +0 -6
- package/dist/task/operator/envelope.js +0 -2
- package/dist/task/operator/index.js +0 -5
- package/dist/task/operator/registry.js +0 -2
- package/dist/task/operator/types.js +0 -1
- package/dist/task/paths.js +0 -1
- package/dist/task/source-state.js +0 -1
- package/dist/task/subagent-guidance.js +0 -1
- package/dist/worker/console/chat/sift-bridge.js +0 -1
- package/dist/worker/console/index.js +0 -20
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
- package/dist/worker/scheduler/index.js +0 -25
- package/dist/workflows/dag/facts.js +0 -4
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
- package/dist/workflows/dag/index.js +0 -6
- package/dist/workflows/dynamic/index.js +0 -7
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { readTypedEventStoreFromJsonl, } from "./frontend-typed-event-store.js";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function asString(value) {
|
|
6
|
+
return typeof value === "string" ? value.trim() : "";
|
|
7
|
+
}
|
|
8
|
+
function asStringArray(value) {
|
|
9
|
+
if (!Array.isArray(value))
|
|
10
|
+
return [];
|
|
11
|
+
return value
|
|
12
|
+
.map((item) => asString(item))
|
|
13
|
+
.filter((item) => item.length > 0);
|
|
14
|
+
}
|
|
15
|
+
/** Deterministic, sorted, de-duplicated string set. Lexicographic order only;
|
|
16
|
+
* never locale/Date-dependent so diff output is reproducible. */
|
|
17
|
+
function canonicalStringSet(values) {
|
|
18
|
+
return [
|
|
19
|
+
...new Set(values
|
|
20
|
+
.map((item) => item.trim())
|
|
21
|
+
.filter((item) => item.length > 0)),
|
|
22
|
+
].sort();
|
|
23
|
+
}
|
|
24
|
+
export const PLAN_LEDGER_FACT_KINDS = [
|
|
25
|
+
"target-surface",
|
|
26
|
+
"state-registry",
|
|
27
|
+
"component-choice",
|
|
28
|
+
"state-flow",
|
|
29
|
+
"data-flow",
|
|
30
|
+
"mock-api",
|
|
31
|
+
"design-deviation",
|
|
32
|
+
"dependency",
|
|
33
|
+
"plan-requirement",
|
|
34
|
+
"plan-verification-target",
|
|
35
|
+
"plan-evidence-gap",
|
|
36
|
+
"mock-endpoint",
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* A+B (AC-004): reverse of `planLedgerFactsFromPatch` for the incremental
|
|
40
|
+
* `record_*` tool flow. Reconstructs the canonical editable patch from the raw
|
|
41
|
+
* fields each `record_*` tool committed. `finalize_plan` calls this and then
|
|
42
|
+
* re-publishes the assembled patch onto a `target-surface` fact so the
|
|
43
|
+
* writeSet-external compile authority (`frontend-implementation-contract.ts`)
|
|
44
|
+
* can still restore a plain `patch` object.
|
|
45
|
+
*/
|
|
46
|
+
export function assemblePlanPatchFromCommittedFacts(records, contractRequirements) {
|
|
47
|
+
const facts = records
|
|
48
|
+
.filter((record) => record.phase === "committed")
|
|
49
|
+
.map((record) => record.fact)
|
|
50
|
+
.filter((fact) => isRecord(fact) &&
|
|
51
|
+
fact.origin === "plan" &&
|
|
52
|
+
typeof fact.kind === "string" &&
|
|
53
|
+
PLAN_LEDGER_FACT_KINDS.includes(fact.kind));
|
|
54
|
+
if (facts.length === 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
// Singleton policy facts: the LAST committed fact wins. The model corrects a
|
|
57
|
+
// rejected plan by re-recording the policy (r18). Requirements / verification
|
|
58
|
+
// targets / evidence gaps stay additive; component choices accumulate; and
|
|
59
|
+
// state-flow is merged below by item name so UX-local batches do not erase one
|
|
60
|
+
// another.
|
|
61
|
+
const lastByKind = (kind) => {
|
|
62
|
+
let found;
|
|
63
|
+
for (const fact of facts) {
|
|
64
|
+
if (fact.kind === kind)
|
|
65
|
+
found = fact;
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
};
|
|
69
|
+
// Patch snapshots (published by earlier finalize calls) carry no routes;
|
|
70
|
+
// exclude them so a snapshot cannot override the model's route selection.
|
|
71
|
+
const routeSurface = (() => {
|
|
72
|
+
let found;
|
|
73
|
+
for (const fact of facts) {
|
|
74
|
+
if (fact.kind !== "target-surface" || isRecord(fact.patch))
|
|
75
|
+
continue;
|
|
76
|
+
found = fact;
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
})();
|
|
80
|
+
const patch = {};
|
|
81
|
+
const targetSurface = routeSurface;
|
|
82
|
+
if (targetSurface) {
|
|
83
|
+
const routes = canonicalStringSet(asStringArray(targetSurface.routes));
|
|
84
|
+
if (routes.length > 0)
|
|
85
|
+
patch.targets = { routes };
|
|
86
|
+
}
|
|
87
|
+
const componentChoices = facts.filter((fact) => fact.kind === "component-choice");
|
|
88
|
+
const collectedChoices = componentChoices.flatMap((fact) => Array.isArray(fact.uiComponentChoices)
|
|
89
|
+
? fact.uiComponentChoices.filter(isRecord)
|
|
90
|
+
: []);
|
|
91
|
+
// Later corrections supersede earlier submissions per purpose: without
|
|
92
|
+
// this, a re-recorded choice (fixing a wrong specReference) would appear
|
|
93
|
+
// twice and the reviewer would still audit the stale entry (r19).
|
|
94
|
+
const choicesByPurpose = new Map();
|
|
95
|
+
const dedupedChoices = [];
|
|
96
|
+
for (const choice of collectedChoices) {
|
|
97
|
+
const purpose = asString(choice.purpose);
|
|
98
|
+
if (!purpose) {
|
|
99
|
+
dedupedChoices.push(choice);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (choicesByPurpose.has(purpose)) {
|
|
103
|
+
const at = dedupedChoices.findIndex((existing) => asString(existing.purpose) === purpose);
|
|
104
|
+
dedupedChoices[at] = choice;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
choicesByPurpose.set(purpose, choice);
|
|
108
|
+
dedupedChoices.push(choice);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const stylingStrategy = componentChoices
|
|
112
|
+
.map((fact) => asString(fact.stylingStrategy))
|
|
113
|
+
.find((value) => value.length > 0);
|
|
114
|
+
if (dedupedChoices.length > 0)
|
|
115
|
+
patch.uiComponentChoices = dedupedChoices;
|
|
116
|
+
if (stylingStrategy)
|
|
117
|
+
patch.stylingStrategy = stylingStrategy;
|
|
118
|
+
// UX-local batches can emit several state-flow facts. Aggregate by the
|
|
119
|
+
// stable state/interaction name so a later correction replaces only that
|
|
120
|
+
// item; last-wins at the whole-fact level silently discarded earlier local
|
|
121
|
+
// batches when Segment 2 was split (r21).
|
|
122
|
+
const stateFlows = facts.filter((fact) => fact.kind === "state-flow");
|
|
123
|
+
const stateByName = new Map();
|
|
124
|
+
const unnamedStates = [];
|
|
125
|
+
const interactionByName = new Map();
|
|
126
|
+
const unnamedInteractions = [];
|
|
127
|
+
for (const flow of stateFlows) {
|
|
128
|
+
for (const name of asStringArray(flow.removeUiStateNames))
|
|
129
|
+
stateByName.delete(name);
|
|
130
|
+
for (const name of asStringArray(flow.removeInteractionNames))
|
|
131
|
+
interactionByName.delete(name);
|
|
132
|
+
for (const state of Array.isArray(flow.uiStates) ? flow.uiStates : []) {
|
|
133
|
+
if (!isRecord(state))
|
|
134
|
+
continue;
|
|
135
|
+
const name = asString(state.name);
|
|
136
|
+
if (name)
|
|
137
|
+
stateByName.set(name, state);
|
|
138
|
+
else
|
|
139
|
+
unnamedStates.push(state);
|
|
140
|
+
}
|
|
141
|
+
for (const interaction of Array.isArray(flow.interactions) ? flow.interactions : []) {
|
|
142
|
+
if (!isRecord(interaction))
|
|
143
|
+
continue;
|
|
144
|
+
const name = asString(interaction.name);
|
|
145
|
+
if (name)
|
|
146
|
+
interactionByName.set(name, interaction);
|
|
147
|
+
else
|
|
148
|
+
unnamedInteractions.push(interaction);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (stateFlows.length > 0) {
|
|
152
|
+
patch.uiStates = [...stateByName.values(), ...unnamedStates];
|
|
153
|
+
patch.interactions = [...interactionByName.values(), ...unnamedInteractions];
|
|
154
|
+
}
|
|
155
|
+
const mockApi = lastByKind("mock-api");
|
|
156
|
+
if (!mockApi && facts.some(f => f.kind === "mock-endpoint" && f.removed !== true))
|
|
157
|
+
throw Error("MOCK_POLICY_MISSING: endpoint has no policy");
|
|
158
|
+
if (mockApi && isRecord(mockApi.mockApi)) {
|
|
159
|
+
const endpoints = new Map();
|
|
160
|
+
for (const endpoint of Array.isArray(mockApi.mockApi.endpoints) ? mockApi.mockApi.endpoints : []) {
|
|
161
|
+
if (isRecord(endpoint))
|
|
162
|
+
endpoints.set(`${endpoint.method}:${endpoint.path}`, endpoint);
|
|
163
|
+
}
|
|
164
|
+
for (const fact of facts)
|
|
165
|
+
if (fact.kind === "mock-endpoint" && isRecord(fact.endpoint)) {
|
|
166
|
+
const key = `${fact.endpoint.method}:${fact.endpoint.path}`;
|
|
167
|
+
if (fact.removed === true)
|
|
168
|
+
endpoints.delete(key);
|
|
169
|
+
else
|
|
170
|
+
endpoints.set(key, fact.endpoint);
|
|
171
|
+
}
|
|
172
|
+
if (mockApi.mockApi.strategy === "not-needed" && endpoints.size)
|
|
173
|
+
throw Error("MOCK_POLICY_CONFLICT: withdraw endpoints before declaring Mock/API not needed");
|
|
174
|
+
patch.mockApi = { ...mockApi.mockApi, endpoints: [...endpoints.values()] };
|
|
175
|
+
}
|
|
176
|
+
const deviation = lastByKind("design-deviation");
|
|
177
|
+
if (deviation) {
|
|
178
|
+
const conflicts = canonicalStringSet(asStringArray(deviation.conflicts));
|
|
179
|
+
if (conflicts.length > 0)
|
|
180
|
+
patch.designEvidence = { conflicts };
|
|
181
|
+
}
|
|
182
|
+
const dependency = lastByKind("dependency");
|
|
183
|
+
const dependencyPolicy = asString(dependency?.policy);
|
|
184
|
+
if (dependencyPolicy)
|
|
185
|
+
patch.dependencyPolicy = dependencyPolicy;
|
|
186
|
+
// Requirements / verification targets / evidence gaps / implementation
|
|
187
|
+
// steps arrive as individual facts (one per record_plan_* call) so a large
|
|
188
|
+
// plan never exceeds a single model output budget. Aggregate them in
|
|
189
|
+
// ledger order.
|
|
190
|
+
const targetRequirements = new Map();
|
|
191
|
+
for (const fact of facts.filter((item) => item.kind === "plan-verification-target")) {
|
|
192
|
+
const entry = isRecord(fact.entry) ? fact.entry : undefined;
|
|
193
|
+
const targetId = asString(entry?.id);
|
|
194
|
+
if (!targetId)
|
|
195
|
+
continue;
|
|
196
|
+
for (const requirementId of asStringArray(entry?.requirementIds)) {
|
|
197
|
+
const targets = targetRequirements.get(requirementId) ?? [];
|
|
198
|
+
if (!targets.includes(targetId))
|
|
199
|
+
targets.push(targetId);
|
|
200
|
+
targetRequirements.set(requirementId, targets);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const requirements = facts
|
|
204
|
+
.filter((fact) => fact.kind === "plan-requirement")
|
|
205
|
+
.map((fact) => fact.entry)
|
|
206
|
+
.filter(isRecord)
|
|
207
|
+
.map((entry) => {
|
|
208
|
+
const requirementId = asString(entry.id);
|
|
209
|
+
const derivedTargets = requirementId ? targetRequirements.get(requirementId) : undefined;
|
|
210
|
+
if (derivedTargets && derivedTargets.length > 0) {
|
|
211
|
+
return { ...entry, verificationTargetIds: [...derivedTargets].sort() };
|
|
212
|
+
}
|
|
213
|
+
return entry;
|
|
214
|
+
})
|
|
215
|
+
.map((entry) => {
|
|
216
|
+
// The canonical contract schema requires a non-empty
|
|
217
|
+
// expectedOutcome, but the plan should not be forced to re-write
|
|
218
|
+
// full outcome prose for every requirement (the contract node
|
|
219
|
+
// already captured requirement semantics). Derive a deterministic
|
|
220
|
+
// placeholder from the requirement id when omitted.
|
|
221
|
+
if (isRecord(entry) && !asString(entry.expectedOutcome)) {
|
|
222
|
+
const id = asString(entry.id) || "requirement";
|
|
223
|
+
return { ...entry, expectedOutcome: `implied by contract: ${id}` };
|
|
224
|
+
}
|
|
225
|
+
return entry;
|
|
226
|
+
})
|
|
227
|
+
// Source fidelity ledger (AC-005/AC-006): the contract node declared the
|
|
228
|
+
// authoritative sourceFragmentIds/sourceRefs for each requirement from
|
|
229
|
+
// the ledger's requirement→fragment mapping. The plan inherits them by
|
|
230
|
+
// requirement id so the compiled canonical contract carries the same
|
|
231
|
+
// provenance evidence without the plan re-deriving (or fabricating) it.
|
|
232
|
+
.map((entry) => {
|
|
233
|
+
if (!contractRequirements)
|
|
234
|
+
return entry;
|
|
235
|
+
const id = asString(entry.id);
|
|
236
|
+
if (!id)
|
|
237
|
+
return entry;
|
|
238
|
+
const inherited = contractRequirements.get(id);
|
|
239
|
+
if (!inherited)
|
|
240
|
+
return entry;
|
|
241
|
+
const merged = { ...entry, ...(inherited.text ? { expectedOutcome: inherited.text } : {}), ...(inherited.execution ? { execution: inherited.execution } : {}) };
|
|
242
|
+
// Inherit the contract-declared provenance only when the plan did
|
|
243
|
+
// not already declare it (model override wins; missing inherits).
|
|
244
|
+
if (!Array.isArray(entry.sourceFragmentIds)) {
|
|
245
|
+
if (inherited.sourceFragmentIds) {
|
|
246
|
+
merged.sourceFragmentIds = inherited.sourceFragmentIds;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!Array.isArray(entry.sourceRefs)) {
|
|
250
|
+
if (inherited.sourceRefs) {
|
|
251
|
+
merged.sourceRefs = inherited.sourceRefs;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return merged;
|
|
255
|
+
})
|
|
256
|
+
.map((entry) => {
|
|
257
|
+
if (isRecord(entry) && !isRecord(entry.evidenceGap)) {
|
|
258
|
+
const requirementId = asString(entry.id);
|
|
259
|
+
const standaloneGap = facts
|
|
260
|
+
.filter((fact) => fact.kind === "plan-evidence-gap")
|
|
261
|
+
.map((fact) => fact.entry)
|
|
262
|
+
.filter(isRecord)
|
|
263
|
+
.filter((gap) => asString(gap.requirementId) === requirementId)
|
|
264
|
+
.at(-1);
|
|
265
|
+
if (standaloneGap && asString(standaloneGap.description)) {
|
|
266
|
+
return { ...entry, evidenceGap: standaloneGap };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return entry;
|
|
270
|
+
})
|
|
271
|
+
// A requirement id is a canonical identity; drop duplicates so the
|
|
272
|
+
// compiled patch never carries the same requirement twice (the
|
|
273
|
+
// record_plan_requirement tool rejects re-records, this dedupes any
|
|
274
|
+
// duplicate edge records at the compile authority).
|
|
275
|
+
.filter((entry, index, all) => all.map((other) => asString(other.id)).lastIndexOf(asString(entry.id)) === index);
|
|
276
|
+
if (requirements.length > 0)
|
|
277
|
+
patch.requirements = requirements;
|
|
278
|
+
const verificationTargetOrder = [];
|
|
279
|
+
const verificationTargetById = new Map();
|
|
280
|
+
for (const fact of facts.filter((item) => item.kind === "plan-verification-target")) {
|
|
281
|
+
const entry = fact.entry;
|
|
282
|
+
if (!isRecord(entry))
|
|
283
|
+
continue;
|
|
284
|
+
const id = asString(entry.id);
|
|
285
|
+
if (!id)
|
|
286
|
+
continue;
|
|
287
|
+
if (!verificationTargetById.has(id))
|
|
288
|
+
verificationTargetOrder.push(id);
|
|
289
|
+
verificationTargetById.set(id, entry);
|
|
290
|
+
}
|
|
291
|
+
const verificationTargets = verificationTargetOrder
|
|
292
|
+
.map((id) => verificationTargetById.get(id))
|
|
293
|
+
.filter((entry) => Boolean(entry))
|
|
294
|
+
.map((entry) => {
|
|
295
|
+
// Coverage precedes UX. A later state-flow fact explicitly naming
|
|
296
|
+
// this VT establishes the reverse binding without another model call.
|
|
297
|
+
// No path/name heuristics: unbound or ambiguous decisions stay subject
|
|
298
|
+
// to canonical validation. Recompute from live states after removals.
|
|
299
|
+
const boundStates = [...stateByName.values()]
|
|
300
|
+
.filter((state) => state.applicable === true &&
|
|
301
|
+
asStringArray(state.verificationTargetIds).includes(asString(entry.id)))
|
|
302
|
+
.map((state) => asString(state.name));
|
|
303
|
+
if (boundStates.length === 0)
|
|
304
|
+
return entry;
|
|
305
|
+
return { ...entry, uiStates: [...new Set([
|
|
306
|
+
...asStringArray(entry.uiStates), ...boundStates,
|
|
307
|
+
])].sort() };
|
|
308
|
+
});
|
|
309
|
+
if (verificationTargets.length > 0) {
|
|
310
|
+
patch.verificationTargets = verificationTargets;
|
|
311
|
+
}
|
|
312
|
+
const evidenceGaps = facts
|
|
313
|
+
.filter((fact) => fact.kind === "plan-evidence-gap")
|
|
314
|
+
.map((fact) => fact.entry)
|
|
315
|
+
.filter(isRecord);
|
|
316
|
+
if (evidenceGaps.length > 0)
|
|
317
|
+
patch.evidenceGaps = evidenceGaps;
|
|
318
|
+
return Object.keys(patch).length > 0 ? patch : undefined;
|
|
319
|
+
}
|
|
320
|
+
/** A+B (AC-001/003): read only the committed-phase origin facts from a node's
|
|
321
|
+
* typed event store JSONL. Staged/quarantined records never leak downstream. */
|
|
322
|
+
export async function readCommittedOriginFacts(runDir, nodeId, fileName) {
|
|
323
|
+
const pathMod = await import("node:path");
|
|
324
|
+
const records = await readTypedEventStoreFromJsonl(pathMod.join(runDir, nodeId, fileName));
|
|
325
|
+
return records.filter((record) => record.phase === "committed");
|
|
326
|
+
}
|
|
327
|
+
function latestCommittedScoutTargetSurface(records) {
|
|
328
|
+
return records
|
|
329
|
+
.filter((record) => record.phase === "committed" &&
|
|
330
|
+
isRecord(record.fact) &&
|
|
331
|
+
record.fact.origin === "scout" &&
|
|
332
|
+
record.fact.kind === "target-surface")
|
|
333
|
+
.at(-1)?.fact;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Scout owns discovery. A complete fact must explicitly say so, name at least
|
|
337
|
+
* one target candidate and leave no unresolved paths. Existing surfaces must
|
|
338
|
+
* include fresh runtime evidence for a named target path. Greenfield
|
|
339
|
+
* surfaces are the one explicit exception: when runtime-injected source
|
|
340
|
+
* declarations prove that every named path was specified by the task source,
|
|
341
|
+
* all paths may be future (not yet on disk). This keeps create-new work
|
|
342
|
+
* possible without allowing Scout to invent an arbitrary path under the
|
|
343
|
+
* writer boundary. An empty test path list remains valid.
|
|
344
|
+
*/
|
|
345
|
+
export function readCompleteScoutTargetSurface(records) {
|
|
346
|
+
const fact = latestCommittedScoutTargetSurface(records);
|
|
347
|
+
if (!fact)
|
|
348
|
+
return {
|
|
349
|
+
ok: false,
|
|
350
|
+
reason: "frontend scout facts missing: no committed target-surface fact",
|
|
351
|
+
};
|
|
352
|
+
if (fact.completeness !== "complete")
|
|
353
|
+
return {
|
|
354
|
+
ok: false,
|
|
355
|
+
reason: "frontend scout target surface is not complete; resolve discovery gaps before Plan",
|
|
356
|
+
};
|
|
357
|
+
const unresolvedPaths = asStringArray(fact.unresolvedPaths);
|
|
358
|
+
if (unresolvedPaths.length > 0)
|
|
359
|
+
return {
|
|
360
|
+
ok: false,
|
|
361
|
+
reason: `frontend scout target surface has unresolved paths: ${unresolvedPaths.join(", ")}`,
|
|
362
|
+
};
|
|
363
|
+
const surface = {
|
|
364
|
+
entrypoint: asString(fact.entrypoint),
|
|
365
|
+
implementationPaths: canonicalStringSet(asStringArray(fact.implementationPaths)),
|
|
366
|
+
proposedPaths: canonicalStringSet(asStringArray(fact.proposedPaths)),
|
|
367
|
+
testPaths: canonicalStringSet(asStringArray(fact.testPaths)),
|
|
368
|
+
};
|
|
369
|
+
const namedPaths = canonicalStringSet([
|
|
370
|
+
...(surface.entrypoint ? [surface.entrypoint] : []),
|
|
371
|
+
...surface.implementationPaths,
|
|
372
|
+
...surface.proposedPaths,
|
|
373
|
+
...surface.testPaths,
|
|
374
|
+
]);
|
|
375
|
+
if (namedPaths.length === 0)
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
reason: "frontend scout target surface is incomplete: name an entrypoint, implementation path, or test path",
|
|
379
|
+
};
|
|
380
|
+
const freshPaths = new Set(Array.isArray(fact.pathEvidence)
|
|
381
|
+
? fact.pathEvidence
|
|
382
|
+
.filter(isRecord)
|
|
383
|
+
.filter((item) => item.fresh === true)
|
|
384
|
+
.map((item) => asString(item.path))
|
|
385
|
+
.filter(Boolean)
|
|
386
|
+
: []);
|
|
387
|
+
const pathEvidence = Array.isArray(fact.pathEvidence)
|
|
388
|
+
? fact.pathEvidence.filter(isRecord)
|
|
389
|
+
: [];
|
|
390
|
+
const hasSourceDeclarationLedger = Array.isArray(fact.sourceDeclaredPaths);
|
|
391
|
+
const undeclaredFuturePaths = hasSourceDeclarationLedger
|
|
392
|
+
? namedPaths.filter((candidate) => {
|
|
393
|
+
const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
|
|
394
|
+
return evidence?.fresh !== true && evidence?.sourceDeclared !== true && evidence?.proposed !== true;
|
|
395
|
+
})
|
|
396
|
+
: [];
|
|
397
|
+
if (undeclaredFuturePaths.length > 0)
|
|
398
|
+
return {
|
|
399
|
+
ok: false,
|
|
400
|
+
reason: `frontend scout target surface has future paths not declared by the task source: ${undeclaredFuturePaths.join(", ")}; test path is not declared by the task source, so do not guess test/ vs tests/ or other target directories`,
|
|
401
|
+
};
|
|
402
|
+
if (!namedPaths.some((candidate) => freshPaths.has(candidate))) {
|
|
403
|
+
const allPathsAreProposed = surface.proposedPaths.length > 0 &&
|
|
404
|
+
namedPaths.every((candidate) => surface.proposedPaths.includes(candidate) || freshPaths.has(candidate));
|
|
405
|
+
if (allPathsAreProposed)
|
|
406
|
+
return { ok: true, surface };
|
|
407
|
+
const allPathsAreSourceDeclaredFuturePaths = hasSourceDeclarationLedger &&
|
|
408
|
+
namedPaths.length > 0 &&
|
|
409
|
+
namedPaths.every((candidate) => {
|
|
410
|
+
const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
|
|
411
|
+
return evidence?.fresh !== true && evidence?.sourceDeclared === true;
|
|
412
|
+
});
|
|
413
|
+
if (allPathsAreSourceDeclaredFuturePaths)
|
|
414
|
+
return { ok: true, surface };
|
|
415
|
+
return {
|
|
416
|
+
ok: false,
|
|
417
|
+
reason: "frontend scout target surface lacks fresh runtime evidence for a named target path; greenfield exceptions require runtime-injected source declarations for every future path",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return { ok: true, surface };
|
|
421
|
+
}
|
|
422
|
+
/** Paths with Scout ownership that Plan may read for a specific decision. */
|
|
423
|
+
export function scoutConfirmedReadPaths(records) {
|
|
424
|
+
const checked = readCompleteScoutTargetSurface(records);
|
|
425
|
+
if (!checked.ok)
|
|
426
|
+
return [];
|
|
427
|
+
return canonicalStringSet([
|
|
428
|
+
...(checked.surface.entrypoint ? [checked.surface.entrypoint] : []),
|
|
429
|
+
...checked.surface.implementationPaths,
|
|
430
|
+
...checked.surface.proposedPaths,
|
|
431
|
+
...checked.surface.testPaths,
|
|
432
|
+
]);
|
|
433
|
+
}
|
|
434
|
+
/** A+B (AC-001/003): fail-closed committed contract/scout fact prerequisites.
|
|
435
|
+
* `contract` requires a committed `contract-finalized` terminal fact; `scout`
|
|
436
|
+
* requires a complete target-surface fact. Missing or incomplete facts block
|
|
437
|
+
* downstream consumption — never fall back to text parsing. */
|
|
438
|
+
export function checkCommittedOriginFacts(input) {
|
|
439
|
+
const kinds = new Set(input.records
|
|
440
|
+
.filter((record) => record.phase === "committed")
|
|
441
|
+
.map((record) => record.fact.kind)
|
|
442
|
+
.filter((kind) => typeof kind === "string"));
|
|
443
|
+
if (input.origin === "contract") {
|
|
444
|
+
return kinds.has("contract-finalized")
|
|
445
|
+
? { ok: true }
|
|
446
|
+
: {
|
|
447
|
+
ok: false,
|
|
448
|
+
reason: "frontend contract facts missing: no committed contract-finalized terminal fact",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (input.origin === "scout") {
|
|
452
|
+
if (!kinds.has("target-surface"))
|
|
453
|
+
return {
|
|
454
|
+
ok: false,
|
|
455
|
+
reason: "frontend scout facts missing: no committed target-surface fact",
|
|
456
|
+
};
|
|
457
|
+
const checked = readCompleteScoutTargetSurface(input.records);
|
|
458
|
+
return checked.ok ? { ok: true } : { ok: false, reason: checked.reason };
|
|
459
|
+
}
|
|
460
|
+
return { ok: true };
|
|
461
|
+
}
|
|
@@ -28,6 +28,9 @@ async function exclusive(key, work) {
|
|
|
28
28
|
}
|
|
29
29
|
const hash = (value) => sha256OfCanonicalJson(JSON.parse(JSON.stringify(value, (_key, v) => v instanceof Map ? [...v.entries()].sort(([a], [b]) => String(a).localeCompare(String(b))) : v)));
|
|
30
30
|
const ledgerError = (code, message) => Object.assign(new Error(message), { code });
|
|
31
|
+
/** Singleton finalize tools: their committed fact is a run terminal and their
|
|
32
|
+
* params are an acknowledgement, not domain data (see replay rule below). */
|
|
33
|
+
const FINALIZE_TERMINAL_TOOLS = new Set(["finalize_contract", "finalize_plan", "finalize_decision"]);
|
|
31
34
|
const failure = (code, error) => ({ content: [{ type: "text", text: JSON.stringify({ ok: false, code, error }) }], details: { ok: false, code, error } });
|
|
32
35
|
/** A failed rename must never fall back to copying over a live ledger. */
|
|
33
36
|
async function persist(file, records) {
|
|
@@ -98,8 +101,8 @@ export async function createDurableFrontendTools(input) {
|
|
|
98
101
|
try {
|
|
99
102
|
await restore();
|
|
100
103
|
const paramsSha256 = hash(params);
|
|
101
|
-
const singleton = new Set(["record_route_selection", "record_state_registry", "record_mock_api", "record_data_flow", "record_dependency", "record_design_deviation", "record_required_deliverables", "record_target_surface", "finalize_contract", "finalize_plan"]);
|
|
102
|
-
const naturalId = params?.id ?? params?.scopeId ?? params?.entry?.id ?? params?.requirementId ?? params?.choice?.purpose ?? (tool.name === "record_openspec_selection" ? params?.path : undefined) ?? (params?.endpoint ? `${params.endpoint.method}:${params.endpoint.path}` : undefined) ?? (singleton.has(tool.name) ? "singleton" : undefined);
|
|
104
|
+
const singleton = new Set(["record_route_selection", "record_state_registry", "record_mock_api", "record_data_flow", "record_dependency", "record_design_deviation", "record_required_deliverables", "record_target_surface", "finalize_contract", "finalize_plan", "finalize_decision"]);
|
|
105
|
+
const naturalId = params?.id ?? params?.scopeId ?? params?.entry?.id ?? params?.requirementId ?? params?.choice?.purpose ?? params?.entry?.purpose ?? params?.entry?.state ?? params?.entry?.interaction ?? params?.entry?.boundary ?? (tool.name === "record_openspec_selection" ? params?.path : undefined) ?? (params?.endpoint ? `${params.endpoint.method}:${params.endpoint.path}` : undefined) ?? (singleton.has(tool.name) ? "singleton" : undefined);
|
|
103
106
|
const identity = `${tool.name}:${typeof naturalId === "string" ? naturalId : "incremental"}`;
|
|
104
107
|
const submissions = input.store.records.flatMap(r => r.submission?.receipt ? [r.submission] : []);
|
|
105
108
|
const previousCall = submissions.find(s => s.callId === callId);
|
|
@@ -108,9 +111,18 @@ export async function createDurableFrontendTools(input) {
|
|
|
108
111
|
const previousFact = submissions.filter(s => s.identity === identity).at(-1);
|
|
109
112
|
if (tool.name !== "record_plan_group_coverage" && previousFact?.paramsSha256 === paramsSha256)
|
|
110
113
|
return replay(previousFact.receipt);
|
|
114
|
+
// A committed finalize terminal carries no domain payload in its params
|
|
115
|
+
// (r15: the model acknowledged with {replace:false}, the executor's
|
|
116
|
+
// auto-finalize sends {}) — both are acknowledgements of the same durable
|
|
117
|
+
// terminal, so any later finalize call replays the receipt instead of
|
|
118
|
+
// demanding a params match no caller can reproduce. Other singletons
|
|
119
|
+
// (state registry, data flows, ...) carry domain payloads and keep the
|
|
120
|
+
// replace:true correction path.
|
|
121
|
+
if (FINALIZE_TERMINAL_TOOLS.has(tool.name) && previousFact?.receipt)
|
|
122
|
+
return replay(previousFact.receipt);
|
|
111
123
|
if (previousFact && naturalId !== undefined && params?.replace !== true && !["record_evidence_expectation", "record_required_deliverables", "record_data_flow"].includes(tool.name))
|
|
112
124
|
return failure("FACT_IDENTITY_CONFLICT", `${identity}: changing a saved fact requires explicit replacement (replace:true)`);
|
|
113
|
-
if (input.store.records.some(r => ["contract-finalized", "finalize_plan", "approve_review", "request_review_changes", "approve_design", "request_design_changes"].includes(String(r.fact.kind))))
|
|
125
|
+
if (input.store.records.some(r => ["contract-finalized", "finalize_plan", "finalize_decision", "approve_review", "request_review_changes", "approve_design", "request_design_changes"].includes(String(r.fact.kind))))
|
|
114
126
|
return failure("TERMINAL_CONFLICT", "A successful terminal is already durable; only identical acknowledged submissions may be replayed");
|
|
115
127
|
const draft = structuredClone(input.store);
|
|
116
128
|
input.setWorkingStore(draft);
|