sdd-agent-platform 0.4.1 → 0.5.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/README.md +24 -28
- package/node_modules/@sdd-agent-platform/core/dist/ai-tools.js +84 -103
- package/node_modules/@sdd-agent-platform/core/dist/ai-tools.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/config/init-project.d.ts +10 -6
- package/node_modules/@sdd-agent-platform/core/dist/config/init-project.js +7 -8
- package/node_modules/@sdd-agent-platform/core/dist/config/init-project.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/config/project-config.d.ts +3 -1
- package/node_modules/@sdd-agent-platform/core/dist/config/project-config.js +7 -3
- package/node_modules/@sdd-agent-platform/core/dist/config/project-config.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.d.ts +0 -1
- package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js +374 -421
- package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/context/build-package.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/context/build-package.js +7 -19
- package/node_modules/@sdd-agent-platform/core/dist/context/build-package.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/contracts.d.ts +7 -1
- package/node_modules/@sdd-agent-platform/core/dist/contracts.js +6 -0
- package/node_modules/@sdd-agent-platform/core/dist/contracts.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/document-chain.js +2 -12
- package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/document-chain.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js +1 -18
- package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/contracts.d.ts +0 -1
- package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/coordination.js +110 -0
- package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/coordination.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/execution/host-invocation.js +83 -83
- package/node_modules/@sdd-agent-platform/core/dist/instructions.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/instructions.js +37 -80
- package/node_modules/@sdd-agent-platform/core/dist/instructions.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js +58 -68
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.d.ts +159 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.js +7 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.d.ts +16 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.js +461 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.d.ts +2 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.js +3 -0
- package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/orchestration/contracts.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/orchestration/runtime.js +21 -28
- package/node_modules/@sdd-agent-platform/core/dist/orchestration/runtime.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js +124 -40
- package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.js +6 -13
- package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.d.ts +13 -0
- package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.js +76 -0
- package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/registries/skill-capabilities.js +7 -7
- package/node_modules/@sdd-agent-platform/core/dist/registries/skill-capabilities.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/tool-capabilities.js +6 -6
- package/node_modules/@sdd-agent-platform/core/dist/registries/tool-capabilities.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js +18 -18
- package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/risk/consumer-diagnostics.js +2 -1
- package/node_modules/@sdd-agent-platform/core/dist/risk/consumer-diagnostics.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/risk/contracts.d.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/dist/risk/kernel.js +7 -7
- package/node_modules/@sdd-agent-platform/core/dist/risk/kernel.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/risk/legacy-adapters.js +12 -27
- package/node_modules/@sdd-agent-platform/core/dist/risk/legacy-adapters.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/risk/workflow-gates.js +6 -6
- package/node_modules/@sdd-agent-platform/core/dist/risk/workflow-gates.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/router/routing.js +2 -4
- package/node_modules/@sdd-agent-platform/core/dist/router/routing.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.d.ts +28 -0
- package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.js +383 -0
- package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.d.ts +37 -0
- package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.js +227 -0
- package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/router.d.ts +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/router.js +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/router.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.d.ts +16 -0
- package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js +6 -0
- package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/run-state/model.d.ts +20 -0
- package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js +7 -7
- package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.d.ts +1 -2
- package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js +2 -9
- package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.d.ts +8 -0
- package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.js +131 -0
- package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/build.js +1 -4
- package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/build.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/findings.js +0 -39
- package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/findings.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/model.d.ts +1 -17
- package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.d.ts +10 -0
- package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js +65 -0
- package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.d.ts +64 -0
- package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.js +211 -0
- package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.d.ts +14 -0
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.js +179 -0
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.d.ts +5 -1
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js +60 -22
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-rendering.js +2 -2
- package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-rendering.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/spec-entry.js +40 -0
- package/node_modules/@sdd-agent-platform/core/dist/spec-entry.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.d.ts +12 -0
- package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.js +2 -0
- package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.d.ts +55 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.js +315 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.d.ts +55 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.js +238 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.d.ts +736 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.js +4018 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/stage-runtime/runtime.js +8 -1
- package/node_modules/@sdd-agent-platform/core/dist/stage-runtime/runtime.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js +25 -1
- package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.d.ts +170 -18
- package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js +597 -85
- package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.d.ts +1 -17
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js +1 -242
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.d.ts +1 -110
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js +1 -496
- package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/sync-back.d.ts +1 -2
- package/node_modules/@sdd-agent-platform/core/dist/sync-back.js +1 -2
- package/node_modules/@sdd-agent-platform/core/dist/sync-back.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.d.ts +167 -0
- package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.js +377 -0
- package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js +329 -314
- package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.d.ts +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js +31 -0
- package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.d.ts +44 -0
- package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.js +135 -0
- package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.d.ts +0 -49
- package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js +1 -545
- package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.d.ts +5 -7
- package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js +15 -55
- package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js +1 -40
- package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.d.ts +49 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.js +521 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.d.ts +12 -2
- package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js +247 -112
- package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.d.ts +26 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.js +73 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.d.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js +49 -72
- package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/verification.d.ts +3 -3
- package/node_modules/@sdd-agent-platform/core/dist/verification.js +2 -2
- package/node_modules/@sdd-agent-platform/core/dist/verification.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js +2 -7
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js +0 -7
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js +2 -4
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/types.d.ts +3 -5
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js +30 -4
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.d.ts +40 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.js +110 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.d.ts +12 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.js +63 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.d.ts +21 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.js +95 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.d.ts +55 -5
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js +518 -36
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js.map +1 -1
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.d.ts +228 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.js +452 -0
- package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.js.map +1 -0
- package/node_modules/@sdd-agent-platform/core/package.json +6 -3
- package/node_modules/@sdd-agent-platform/core/src/ai-tools.test.ts +238 -137
- package/node_modules/@sdd-agent-platform/core/src/ai-tools.ts +84 -103
- package/node_modules/@sdd-agent-platform/core/src/artifacts/ingestion.test.ts +189 -189
- package/node_modules/@sdd-agent-platform/core/src/artifacts/ingestion.ts +222 -222
- package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-evidence.test.ts +28 -28
- package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-evidence.ts +302 -302
- package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-result.test.ts +181 -181
- package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-result.ts +231 -231
- package/node_modules/@sdd-agent-platform/core/src/artifacts/templates.ts +99 -99
- package/node_modules/@sdd-agent-platform/core/src/artifacts.ts +4 -4
- package/node_modules/@sdd-agent-platform/core/src/coding-facts/contracts.ts +79 -79
- package/node_modules/@sdd-agent-platform/core/src/coding-facts.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/src/config/init-project.test.ts +314 -306
- package/node_modules/@sdd-agent-platform/core/src/config/init-project.ts +128 -120
- package/node_modules/@sdd-agent-platform/core/src/config/project-config.ts +265 -259
- package/node_modules/@sdd-agent-platform/core/src/config/project-detection.ts +147 -147
- package/node_modules/@sdd-agent-platform/core/src/config/starter-documents.ts +400 -445
- package/node_modules/@sdd-agent-platform/core/src/context/budget.ts +30 -30
- package/node_modules/@sdd-agent-platform/core/src/context/build-package.ts +305 -317
- package/node_modules/@sdd-agent-platform/core/src/context/command-summary.ts +45 -45
- package/node_modules/@sdd-agent-platform/core/src/context/context-build.test.ts +188 -188
- package/node_modules/@sdd-agent-platform/core/src/context/evidence-summary.ts +144 -144
- package/node_modules/@sdd-agent-platform/core/src/context/log-worker.ts +48 -48
- package/node_modules/@sdd-agent-platform/core/src/context/source-refs.ts +41 -41
- package/node_modules/@sdd-agent-platform/core/src/context-offload/contracts.ts +47 -47
- package/node_modules/@sdd-agent-platform/core/src/context-offload/runtime.test.ts +71 -71
- package/node_modules/@sdd-agent-platform/core/src/context-offload/runtime.ts +178 -178
- package/node_modules/@sdd-agent-platform/core/src/context-offload.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/context.ts +6 -6
- package/node_modules/@sdd-agent-platform/core/src/contracts/issues.ts +13 -13
- package/node_modules/@sdd-agent-platform/core/src/contracts.test.ts +9 -9
- package/node_modules/@sdd-agent-platform/core/src/contracts.ts +121 -115
- package/node_modules/@sdd-agent-platform/core/src/delegation/delegation.test.ts +183 -183
- package/node_modules/@sdd-agent-platform/core/src/delegation/model.ts +23 -23
- package/node_modules/@sdd-agent-platform/core/src/delegation/queue.ts +58 -58
- package/node_modules/@sdd-agent-platform/core/src/delegation/run-state.ts +14 -14
- package/node_modules/@sdd-agent-platform/core/src/delegation/state-machine.ts +90 -90
- package/node_modules/@sdd-agent-platform/core/src/delegation/validation.ts +124 -124
- package/node_modules/@sdd-agent-platform/core/src/delegation.ts +26 -26
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/ai-entries.ts +28 -28
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/document-chain.ts +104 -112
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/local-run-index.ts +27 -27
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/project.ts +84 -84
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/registries.ts +252 -252
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-evidence.ts +330 -330
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-records.ts +79 -79
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-trust.ts +128 -128
- package/node_modules/@sdd-agent-platform/core/src/doctor/checks/runtime-contracts.ts +300 -300
- package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.test.ts +627 -657
- package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.ts +301 -318
- package/node_modules/@sdd-agent-platform/core/src/doctor/model.ts +13 -13
- package/node_modules/@sdd-agent-platform/core/src/doctor/summary.ts +11 -11
- package/node_modules/@sdd-agent-platform/core/src/doctor.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/evidence/lookup.ts +80 -80
- package/node_modules/@sdd-agent-platform/core/src/evidence-runtime/contracts.ts +48 -49
- package/node_modules/@sdd-agent-platform/core/src/evidence-runtime.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/src/execution/agent-execution-records.ts +195 -195
- package/node_modules/@sdd-agent-platform/core/src/execution/background-executor.test.ts +187 -187
- package/node_modules/@sdd-agent-platform/core/src/execution/background-executor.ts +305 -305
- package/node_modules/@sdd-agent-platform/core/src/execution/foreground-subagents.test.ts +97 -97
- package/node_modules/@sdd-agent-platform/core/src/execution/foreground-subagents.ts +453 -453
- package/node_modules/@sdd-agent-platform/core/src/execution/host-invocation.ts +225 -225
- package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.test.ts +132 -132
- package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.ts +436 -436
- package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.test.ts +102 -102
- package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.ts +271 -271
- package/node_modules/@sdd-agent-platform/core/src/execution/wave-executor.test.ts +111 -111
- package/node_modules/@sdd-agent-platform/core/src/execution/wave-executor.ts +231 -231
- package/node_modules/@sdd-agent-platform/core/src/execution.ts +5 -5
- package/node_modules/@sdd-agent-platform/core/src/governance/policy.test.ts +57 -57
- package/node_modules/@sdd-agent-platform/core/src/governance/policy.ts +175 -175
- package/node_modules/@sdd-agent-platform/core/src/governance.ts +1 -1
- package/node_modules/@sdd-agent-platform/core/src/instructions.test.ts +80 -49
- package/node_modules/@sdd-agent-platform/core/src/instructions.ts +38 -81
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/decision-gate.test.ts +174 -174
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/decision-gate.ts +373 -373
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/rendering.ts +29 -29
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/risk-signals.ts +146 -146
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/ship.test.ts +47 -0
- package/node_modules/@sdd-agent-platform/core/src/lifecycle/ship.ts +255 -263
- package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph/contracts.ts +179 -0
- package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph/kernel.ts +522 -0
- package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph.ts +2 -0
- package/node_modules/@sdd-agent-platform/core/src/lifecycle.ts +4 -4
- package/node_modules/@sdd-agent-platform/core/src/orchestration/contracts.ts +50 -50
- package/node_modules/@sdd-agent-platform/core/src/orchestration/index.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/orchestration/runtime.ts +331 -342
- package/node_modules/@sdd-agent-platform/core/src/path-safety.test.ts +22 -22
- package/node_modules/@sdd-agent-platform/core/src/phase8-contracts.test.ts +243 -243
- package/node_modules/@sdd-agent-platform/core/src/phase8-projection-compat.test.ts +152 -153
- package/node_modules/@sdd-agent-platform/core/src/phase8-risk-kernel.test.ts +277 -277
- package/node_modules/@sdd-agent-platform/core/src/phase9-lifecycle-graph.test.ts +103 -0
- package/node_modules/@sdd-agent-platform/core/src/planning/task-graph.test.ts +88 -88
- package/node_modules/@sdd-agent-platform/core/src/planning/task-graph.ts +222 -222
- package/node_modules/@sdd-agent-platform/core/src/planning/wave-plan.test.ts +79 -79
- package/node_modules/@sdd-agent-platform/core/src/planning/wave-plan.ts +160 -160
- package/node_modules/@sdd-agent-platform/core/src/planning.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/registries/agent-capability-catalog.ts +426 -426
- package/node_modules/@sdd-agent-platform/core/src/registries/agent-registry.ts +230 -146
- package/node_modules/@sdd-agent-platform/core/src/registries/agent-runtime-static.ts +142 -142
- package/node_modules/@sdd-agent-platform/core/src/registries/capability-sources.ts +253 -253
- package/node_modules/@sdd-agent-platform/core/src/registries/command-team-runtime.ts +302 -309
- package/node_modules/@sdd-agent-platform/core/src/registries/eval-learning-context.ts +246 -246
- package/node_modules/@sdd-agent-platform/core/src/registries/plan-scout-domains.ts +89 -0
- package/node_modules/@sdd-agent-platform/core/src/registries/query-status.ts +119 -119
- package/node_modules/@sdd-agent-platform/core/src/registries/registries.test.ts +454 -429
- package/node_modules/@sdd-agent-platform/core/src/registries/skill-capabilities.ts +37 -37
- package/node_modules/@sdd-agent-platform/core/src/registries/tool-capabilities.ts +135 -135
- package/node_modules/@sdd-agent-platform/core/src/registries/tool-plugins.ts +132 -132
- package/node_modules/@sdd-agent-platform/core/src/registries/worker-adapters.ts +144 -144
- package/node_modules/@sdd-agent-platform/core/src/registries/workflow-gates.ts +111 -111
- package/node_modules/@sdd-agent-platform/core/src/registries.ts +42 -42
- package/node_modules/@sdd-agent-platform/core/src/risk/consumer-diagnostics.ts +98 -97
- package/node_modules/@sdd-agent-platform/core/src/risk/contracts.ts +63 -63
- package/node_modules/@sdd-agent-platform/core/src/risk/kernel.ts +233 -233
- package/node_modules/@sdd-agent-platform/core/src/risk/legacy-adapters.ts +251 -266
- package/node_modules/@sdd-agent-platform/core/src/risk/workflow-gates.ts +203 -203
- package/node_modules/@sdd-agent-platform/core/src/risk.ts +5 -5
- package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime-config.ts +327 -327
- package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime.ts +388 -388
- package/node_modules/@sdd-agent-platform/core/src/router/profile-resolution.ts +154 -154
- package/node_modules/@sdd-agent-platform/core/src/router/risk-policy.ts +33 -33
- package/node_modules/@sdd-agent-platform/core/src/router/route-cache.ts +100 -100
- package/node_modules/@sdd-agent-platform/core/src/router/route-projection.ts +356 -356
- package/node_modules/@sdd-agent-platform/core/src/router/route-sdd-task.test.ts +428 -428
- package/node_modules/@sdd-agent-platform/core/src/router/route-sdd-task.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/router/routing-rules.ts +73 -73
- package/node_modules/@sdd-agent-platform/core/src/router/routing.ts +189 -191
- package/node_modules/@sdd-agent-platform/core/src/router/runtime-import.ts +464 -0
- package/node_modules/@sdd-agent-platform/core/src/router/runtime-inspection.ts +124 -124
- package/node_modules/@sdd-agent-platform/core/src/router/runtime-registry.ts +123 -123
- package/node_modules/@sdd-agent-platform/core/src/router/runtime-validation.ts +277 -277
- package/node_modules/@sdd-agent-platform/core/src/router/stage-route-binding.ts +273 -0
- package/node_modules/@sdd-agent-platform/core/src/router/team-mode.ts +170 -170
- package/node_modules/@sdd-agent-platform/core/src/router.ts +5 -4
- package/node_modules/@sdd-agent-platform/core/src/run-state/artifacts.ts +126 -118
- package/node_modules/@sdd-agent-platform/core/src/run-state/events.ts +27 -27
- package/node_modules/@sdd-agent-platform/core/src/run-state/inspect-run.ts +172 -172
- package/node_modules/@sdd-agent-platform/core/src/run-state/invocation-ledger.ts +109 -109
- package/node_modules/@sdd-agent-platform/core/src/run-state/model.ts +252 -230
- package/node_modules/@sdd-agent-platform/core/src/run-state/run-index.test.ts +52 -52
- package/node_modules/@sdd-agent-platform/core/src/run-state/run-index.ts +356 -356
- package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.test.ts +70 -70
- package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.ts +406 -406
- package/node_modules/@sdd-agent-platform/core/src/run-state/task-evidence.ts +198 -206
- package/node_modules/@sdd-agent-platform/core/src/run-state/timing.ts +146 -0
- package/node_modules/@sdd-agent-platform/core/src/run-state.ts +8 -8
- package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/build.ts +60 -63
- package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/findings.ts +257 -296
- package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/model.ts +140 -152
- package/node_modules/@sdd-agent-platform/core/src/runtime-analysis.test.ts +66 -68
- package/node_modules/@sdd-agent-platform/core/src/runtime-analysis.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/runtime-paths.ts +253 -176
- package/node_modules/@sdd-agent-platform/core/src/runtime-projection-p0.test.ts +101 -0
- package/node_modules/@sdd-agent-platform/core/src/runtime-projection-p0.ts +314 -0
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/artifact-depth.test.ts +380 -0
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/artifact-depth.ts +207 -0
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/context.ts +111 -111
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/document-hashes.ts +207 -207
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/run-binding.ts +95 -95
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-inspection.ts +39 -39
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.test.ts +467 -401
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.ts +738 -694
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-rendering.ts +81 -81
- package/node_modules/@sdd-agent-platform/core/src/sdd-docs.ts +5 -5
- package/node_modules/@sdd-agent-platform/core/src/spec-manager-contracts.ts +13 -0
- package/node_modules/@sdd-agent-platform/core/src/stage-artifacts.ts +435 -0
- package/node_modules/@sdd-agent-platform/core/src/stage-collaboration-contracts.ts +316 -0
- package/node_modules/@sdd-agent-platform/core/src/stage-collaboration.test.ts +2964 -0
- package/node_modules/@sdd-agent-platform/core/src/stage-collaboration.ts +5856 -0
- package/node_modules/@sdd-agent-platform/core/src/stage-runtime/contracts.ts +40 -40
- package/node_modules/@sdd-agent-platform/core/src/stage-runtime/runtime.test.ts +209 -209
- package/node_modules/@sdd-agent-platform/core/src/stage-runtime/runtime.ts +360 -352
- package/node_modules/@sdd-agent-platform/core/src/stage-runtime.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/status/project-status.test.ts +288 -288
- package/node_modules/@sdd-agent-platform/core/src/status/project-status.ts +651 -625
- package/node_modules/@sdd-agent-platform/core/src/status.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/storage/json-io.ts +10 -10
- package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.test.ts +489 -489
- package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.ts +1981 -1175
- package/node_modules/@sdd-agent-platform/core/src/subagents/contracts.ts +45 -45
- package/node_modules/@sdd-agent-platform/core/src/subagents/runtime.test.ts +232 -232
- package/node_modules/@sdd-agent-platform/core/src/subagents/runtime.ts +307 -307
- package/node_modules/@sdd-agent-platform/core/src/subagents.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/task-execution-contract.test.ts +141 -0
- package/node_modules/@sdd-agent-platform/core/src/task-execution-contract.ts +566 -0
- package/node_modules/@sdd-agent-platform/core/src/task-risk-profile.ts +193 -193
- package/node_modules/@sdd-agent-platform/core/src/test-support/fixtures.ts +413 -398
- package/node_modules/@sdd-agent-platform/core/src/test-support/run-state.ts +102 -70
- package/node_modules/@sdd-agent-platform/core/src/test-support.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/truth-reconciliation.test.ts +72 -0
- package/node_modules/@sdd-agent-platform/core/src/truth-reconciliation.ts +174 -0
- package/node_modules/@sdd-agent-platform/core/src/verification/rendering.ts +137 -181
- package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.test.ts +77 -77
- package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.ts +77 -77
- package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.ts +455 -494
- package/node_modules/@sdd-agent-platform/core/src/verification/{goal-verify.test.ts → task-evidence-judgment.test.ts} +261 -335
- package/node_modules/@sdd-agent-platform/core/src/verification/{goal-verify.ts → task-evidence-judgment.ts} +619 -648
- package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.ts +1190 -1032
- package/node_modules/@sdd-agent-platform/core/src/verification/validation-cache.ts +106 -0
- package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.ts +513 -513
- package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.ts +334 -358
- package/node_modules/@sdd-agent-platform/core/src/verification.ts +8 -8
- package/node_modules/@sdd-agent-platform/core/src/work-units/contracts.ts +26 -26
- package/node_modules/@sdd-agent-platform/core/src/work-units/runtime.test.ts +88 -88
- package/node_modules/@sdd-agent-platform/core/src/work-units/runtime.ts +112 -112
- package/node_modules/@sdd-agent-platform/core/src/work-units.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/evidence-packet.ts +190 -196
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.test.ts +169 -171
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.ts +136 -143
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.test.ts +135 -137
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.ts +153 -155
- package/node_modules/@sdd-agent-platform/core/src/workflow-gate/types.ts +111 -114
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/affected-file-conflicts.ts +95 -95
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.test.ts +32 -32
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.ts +114 -114
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/latest-eligible-run.ts +184 -156
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/migration-recovery.ts +158 -0
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/repair-contract.ts +77 -0
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve-task-run.ts +114 -0
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.test.ts +970 -464
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.ts +967 -363
- package/node_modules/@sdd-agent-platform/core/src/workflow-state/runtime-projections.ts +712 -0
- package/node_modules/@sdd-agent-platform/core/src/workflow-state.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/src/worktree/isolation.ts +130 -130
- package/node_modules/@sdd-agent-platform/core/src/worktree/lifecycle.ts +269 -269
- package/node_modules/@sdd-agent-platform/core/src/worktree/worktree.test.ts +150 -150
- package/node_modules/@sdd-agent-platform/core/src/worktree.ts +2 -2
- package/node_modules/@sdd-agent-platform/core/tsconfig.json +15 -15
- package/package.json +2 -2
- package/packages/cli/dist/args.js +1 -1
- package/packages/cli/dist/args.js.map +1 -1
- package/packages/cli/dist/commands/context.js +1 -1
- package/packages/cli/dist/commands/context.js.map +1 -1
- package/packages/cli/dist/commands/evidence.js.map +1 -0
- package/packages/cli/dist/commands/execution.js +126 -0
- package/packages/cli/dist/commands/execution.js.map +1 -1
- package/packages/cli/dist/commands/instructions.d.ts +1 -1
- package/packages/cli/dist/commands/instructions.js +15 -1
- package/packages/cli/dist/commands/instructions.js.map +1 -1
- package/packages/cli/dist/commands/registry/runtime.js +70 -1
- package/packages/cli/dist/commands/registry/runtime.js.map +1 -1
- package/packages/cli/dist/commands/run.js +12 -1
- package/packages/cli/dist/commands/run.js.map +1 -1
- package/packages/cli/dist/commands/stage-close.d.ts +66 -0
- package/packages/cli/dist/commands/stage-close.js +524 -0
- package/packages/cli/dist/commands/stage-close.js.map +1 -0
- package/packages/cli/dist/commands/status.js +8 -1
- package/packages/cli/dist/commands/status.js.map +1 -1
- package/packages/cli/dist/commands/tasks.js.map +1 -1
- package/packages/cli/dist/dispatch.js +6 -31
- package/packages/cli/dist/dispatch.js.map +1 -1
- package/packages/cli/dist/help.js +153 -158
- package/packages/cli/dist/help.js.map +1 -1
- package/packages/cli/dist/renderers/workflow.d.ts +51 -2
- package/packages/cli/dist/renderers/workflow.js.map +1 -1
- package/packages/cli/dist/skill-import-args.d.ts +10 -0
- package/packages/cli/dist/skill-import-args.js +47 -0
- package/packages/cli/dist/skill-import-args.js.map +1 -0
- package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/cli/package.json +2 -2
- package/packages/core/dist/ai-tools.js +84 -103
- package/packages/core/dist/ai-tools.js.map +1 -1
- package/packages/core/dist/config/init-project.d.ts +10 -6
- package/packages/core/dist/config/init-project.js +7 -8
- package/packages/core/dist/config/init-project.js.map +1 -1
- package/packages/core/dist/config/project-config.d.ts +3 -1
- package/packages/core/dist/config/project-config.js +7 -3
- package/packages/core/dist/config/project-config.js.map +1 -1
- package/packages/core/dist/config/starter-documents.d.ts +0 -1
- package/packages/core/dist/config/starter-documents.js +374 -421
- package/packages/core/dist/config/starter-documents.js.map +1 -1
- package/packages/core/dist/context/build-package.d.ts +1 -1
- package/packages/core/dist/context/build-package.js +7 -19
- package/packages/core/dist/context/build-package.js.map +1 -1
- package/packages/core/dist/contracts.d.ts +7 -1
- package/packages/core/dist/contracts.js +6 -0
- package/packages/core/dist/contracts.js.map +1 -1
- package/packages/core/dist/doctor/checks/document-chain.js +2 -12
- package/packages/core/dist/doctor/checks/document-chain.js.map +1 -1
- package/packages/core/dist/doctor/doctor.js +1 -18
- package/packages/core/dist/doctor/doctor.js.map +1 -1
- package/packages/core/dist/evidence/lookup.d.ts +1 -1
- package/packages/core/dist/evidence/lookup.js +1 -1
- package/packages/core/dist/evidence/lookup.js.map +1 -1
- package/packages/core/dist/evidence-runtime/contracts.d.ts +0 -1
- package/packages/core/dist/evidence-runtime/coordination.js +110 -0
- package/packages/core/dist/evidence-runtime/coordination.js.map +1 -0
- package/packages/core/dist/execution/host-invocation.js +83 -83
- package/packages/core/dist/instructions.d.ts +1 -1
- package/packages/core/dist/instructions.js +37 -80
- package/packages/core/dist/instructions.js.map +1 -1
- package/packages/core/dist/lifecycle/ship.js +58 -68
- package/packages/core/dist/lifecycle/ship.js.map +1 -1
- package/packages/core/dist/lifecycle-graph/contracts.d.ts +159 -0
- package/packages/core/dist/lifecycle-graph/contracts.js +7 -0
- package/packages/core/dist/lifecycle-graph/contracts.js.map +1 -0
- package/packages/core/dist/lifecycle-graph/kernel.d.ts +16 -0
- package/packages/core/dist/lifecycle-graph/kernel.js +461 -0
- package/packages/core/dist/lifecycle-graph/kernel.js.map +1 -0
- package/packages/core/dist/lifecycle-graph.d.ts +2 -0
- package/packages/core/dist/lifecycle-graph.js +3 -0
- package/packages/core/dist/lifecycle-graph.js.map +1 -0
- package/packages/core/dist/orchestration/contracts.d.ts +1 -1
- package/packages/core/dist/orchestration/runtime.js +21 -28
- package/packages/core/dist/orchestration/runtime.js.map +1 -1
- package/packages/core/dist/registries/agent-registry.js +124 -40
- package/packages/core/dist/registries/agent-registry.js.map +1 -1
- package/packages/core/dist/registries/command-team-runtime.d.ts +1 -1
- package/packages/core/dist/registries/command-team-runtime.js +6 -13
- package/packages/core/dist/registries/command-team-runtime.js.map +1 -1
- package/packages/core/dist/registries/plan-scout-domains.d.ts +13 -0
- package/packages/core/dist/registries/plan-scout-domains.js +76 -0
- package/packages/core/dist/registries/plan-scout-domains.js.map +1 -0
- package/packages/core/dist/registries/skill-capabilities.js +7 -7
- package/packages/core/dist/registries/skill-capabilities.js.map +1 -1
- package/packages/core/dist/registries/tool-capabilities.js +6 -6
- package/packages/core/dist/registries/tool-capabilities.js.map +1 -1
- package/packages/core/dist/registries/workflow-gates.d.ts +1 -1
- package/packages/core/dist/registries/workflow-gates.js +18 -18
- package/packages/core/dist/registries/workflow-gates.js.map +1 -1
- package/packages/core/dist/risk/consumer-diagnostics.js +2 -1
- package/packages/core/dist/risk/consumer-diagnostics.js.map +1 -1
- package/packages/core/dist/risk/contracts.d.ts +2 -2
- package/packages/core/dist/risk/kernel.js +7 -7
- package/packages/core/dist/risk/kernel.js.map +1 -1
- package/packages/core/dist/risk/legacy-adapters.js +12 -27
- package/packages/core/dist/risk/legacy-adapters.js.map +1 -1
- package/packages/core/dist/risk/workflow-gates.js +6 -6
- package/packages/core/dist/risk/workflow-gates.js.map +1 -1
- package/packages/core/dist/router/agent-runtime-config.js +1 -1
- package/packages/core/dist/router/agent-runtime-config.js.map +1 -1
- package/packages/core/dist/router/routing.js +2 -4
- package/packages/core/dist/router/routing.js.map +1 -1
- package/packages/core/dist/router/runtime-import.d.ts +28 -0
- package/packages/core/dist/router/runtime-import.js +383 -0
- package/packages/core/dist/router/runtime-import.js.map +1 -0
- package/packages/core/dist/router/stage-route-binding.d.ts +37 -0
- package/packages/core/dist/router/stage-route-binding.js +227 -0
- package/packages/core/dist/router/stage-route-binding.js.map +1 -0
- package/packages/core/dist/router.d.ts +1 -0
- package/packages/core/dist/router.js +1 -0
- package/packages/core/dist/router.js.map +1 -1
- package/packages/core/dist/run-state/artifacts.d.ts +16 -0
- package/packages/core/dist/run-state/artifacts.js +6 -0
- package/packages/core/dist/run-state/artifacts.js.map +1 -1
- package/packages/core/dist/run-state/model.d.ts +20 -0
- package/packages/core/dist/run-state/run-state.js +7 -7
- package/packages/core/dist/run-state/run-state.js.map +1 -1
- package/packages/core/dist/run-state/task-evidence.d.ts +1 -2
- package/packages/core/dist/run-state/task-evidence.js +2 -9
- package/packages/core/dist/run-state/task-evidence.js.map +1 -1
- package/packages/core/dist/run-state/timing.d.ts +8 -0
- package/packages/core/dist/run-state/timing.js +131 -0
- package/packages/core/dist/run-state/timing.js.map +1 -0
- package/packages/core/dist/runtime-analysis/build.js +1 -4
- package/packages/core/dist/runtime-analysis/build.js.map +1 -1
- package/packages/core/dist/runtime-analysis/findings.js +0 -39
- package/packages/core/dist/runtime-analysis/findings.js.map +1 -1
- package/packages/core/dist/runtime-analysis/model.d.ts +1 -17
- package/packages/core/dist/runtime-paths.d.ts +10 -0
- package/packages/core/dist/runtime-paths.js +65 -0
- package/packages/core/dist/runtime-paths.js.map +1 -1
- package/packages/core/dist/runtime-projection-p0.d.ts +64 -0
- package/packages/core/dist/runtime-projection-p0.js +211 -0
- package/packages/core/dist/runtime-projection-p0.js.map +1 -0
- package/packages/core/dist/sdd-docs/artifact-depth.d.ts +14 -0
- package/packages/core/dist/sdd-docs/artifact-depth.js +179 -0
- package/packages/core/dist/sdd-docs/artifact-depth.js.map +1 -0
- package/packages/core/dist/sdd-docs/task-parser.d.ts +5 -1
- package/packages/core/dist/sdd-docs/task-parser.js +60 -22
- package/packages/core/dist/sdd-docs/task-parser.js.map +1 -1
- package/packages/core/dist/sdd-docs/task-rendering.js +2 -2
- package/packages/core/dist/sdd-docs/task-rendering.js.map +1 -1
- package/packages/core/dist/spec-entry.js +40 -0
- package/packages/core/dist/spec-entry.js.map +1 -0
- package/packages/core/dist/spec-manager-contracts.d.ts +12 -0
- package/packages/core/dist/spec-manager-contracts.js +2 -0
- package/packages/core/dist/spec-manager-contracts.js.map +1 -0
- package/packages/core/dist/stage-artifacts.d.ts +55 -0
- package/packages/core/dist/stage-artifacts.js +315 -0
- package/packages/core/dist/stage-artifacts.js.map +1 -0
- package/packages/core/dist/stage-collaboration-contracts.d.ts +55 -0
- package/packages/core/dist/stage-collaboration-contracts.js +238 -0
- package/packages/core/dist/stage-collaboration-contracts.js.map +1 -0
- package/packages/core/dist/stage-collaboration.d.ts +736 -0
- package/packages/core/dist/stage-collaboration.js +4018 -0
- package/packages/core/dist/stage-collaboration.js.map +1 -0
- package/packages/core/dist/stage-runtime/runtime.js +8 -1
- package/packages/core/dist/stage-runtime/runtime.js.map +1 -1
- package/packages/core/dist/status/project-status.js +25 -1
- package/packages/core/dist/status/project-status.js.map +1 -1
- package/packages/core/dist/storage/runtime-store.d.ts +170 -18
- package/packages/core/dist/storage/runtime-store.js +597 -85
- package/packages/core/dist/storage/runtime-store.js.map +1 -1
- package/packages/core/dist/sync-back/apply.d.ts +1 -17
- package/packages/core/dist/sync-back/apply.js +1 -242
- package/packages/core/dist/sync-back/apply.js.map +1 -1
- package/packages/core/dist/sync-back/inspect.d.ts +1 -110
- package/packages/core/dist/sync-back/inspect.js +1 -496
- package/packages/core/dist/sync-back/inspect.js.map +1 -1
- package/packages/core/dist/sync-back.d.ts +1 -2
- package/packages/core/dist/sync-back.js +1 -2
- package/packages/core/dist/sync-back.js.map +1 -1
- package/packages/core/dist/task-execution-contract.d.ts +167 -0
- package/packages/core/dist/task-execution-contract.js +377 -0
- package/packages/core/dist/task-execution-contract.js.map +1 -0
- package/packages/core/dist/test-support/fixtures.js +329 -314
- package/packages/core/dist/test-support/fixtures.js.map +1 -1
- package/packages/core/dist/test-support/run-state.d.ts +1 -0
- package/packages/core/dist/test-support/run-state.js +31 -0
- package/packages/core/dist/test-support/run-state.js.map +1 -1
- package/packages/core/dist/truth-reconciliation.d.ts +44 -0
- package/packages/core/dist/truth-reconciliation.js +135 -0
- package/packages/core/dist/truth-reconciliation.js.map +1 -0
- package/packages/core/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/core/dist/verification/goal-verify.d.ts +0 -49
- package/packages/core/dist/verification/goal-verify.js +1 -545
- package/packages/core/dist/verification/goal-verify.js.map +1 -1
- package/packages/core/dist/verification/rendering.d.ts +5 -7
- package/packages/core/dist/verification/rendering.js +15 -55
- package/packages/core/dist/verification/rendering.js.map +1 -1
- package/packages/core/dist/verification/single-task-loop.js +1 -40
- package/packages/core/dist/verification/single-task-loop.js.map +1 -1
- package/packages/core/dist/verification/task-evidence-judgment.d.ts +49 -0
- package/packages/core/dist/verification/task-evidence-judgment.js +521 -0
- package/packages/core/dist/verification/task-evidence-judgment.js.map +1 -0
- package/packages/core/dist/verification/test-runtime.d.ts +12 -2
- package/packages/core/dist/verification/test-runtime.js +247 -112
- package/packages/core/dist/verification/test-runtime.js.map +1 -1
- package/packages/core/dist/verification/validation-cache.d.ts +26 -0
- package/packages/core/dist/verification/validation-cache.js +73 -0
- package/packages/core/dist/verification/validation-cache.js.map +1 -0
- package/packages/core/dist/verification/verify-contract.d.ts +1 -1
- package/packages/core/dist/verification/verify-contract.js +49 -72
- package/packages/core/dist/verification/verify-contract.js.map +1 -1
- package/packages/core/dist/verification.d.ts +3 -3
- package/packages/core/dist/verification.js +2 -2
- package/packages/core/dist/verification.js.map +1 -1
- package/packages/core/dist/workflow-gate/evidence-packet.js +2 -7
- package/packages/core/dist/workflow-gate/evidence-packet.js.map +1 -1
- package/packages/core/dist/workflow-gate/hard-checks.js +0 -7
- package/packages/core/dist/workflow-gate/hard-checks.js.map +1 -1
- package/packages/core/dist/workflow-gate/policy.js +2 -4
- package/packages/core/dist/workflow-gate/policy.js.map +1 -1
- package/packages/core/dist/workflow-gate/types.d.ts +3 -5
- package/packages/core/dist/workflow-state/latest-eligible-run.js +30 -4
- package/packages/core/dist/workflow-state/latest-eligible-run.js.map +1 -1
- package/packages/core/dist/workflow-state/migration-recovery.d.ts +40 -0
- package/packages/core/dist/workflow-state/migration-recovery.js +110 -0
- package/packages/core/dist/workflow-state/migration-recovery.js.map +1 -0
- package/packages/core/dist/workflow-state/repair-contract.d.ts +12 -0
- package/packages/core/dist/workflow-state/repair-contract.js +63 -0
- package/packages/core/dist/workflow-state/repair-contract.js.map +1 -0
- package/packages/core/dist/workflow-state/resolve-task-run.d.ts +21 -0
- package/packages/core/dist/workflow-state/resolve-task-run.js +95 -0
- package/packages/core/dist/workflow-state/resolve-task-run.js.map +1 -0
- package/packages/core/dist/workflow-state/resolve.d.ts +55 -5
- package/packages/core/dist/workflow-state/resolve.js +518 -36
- package/packages/core/dist/workflow-state/resolve.js.map +1 -1
- package/packages/core/dist/workflow-state/runtime-projections.d.ts +228 -0
- package/packages/core/dist/workflow-state/runtime-projections.js +452 -0
- package/packages/core/dist/workflow-state/runtime-projections.js.map +1 -0
- package/packages/core/package.json +6 -3
- package/tsconfig.build.json +6 -7
- package/node_modules/@sdd-agent-platform/core/dist/doctor/render.d.ts +0 -2
- package/node_modules/@sdd-agent-platform/core/dist/doctor/render.js +0 -44
- package/node_modules/@sdd-agent-platform/core/dist/doctor/render.js.map +0 -1
- package/node_modules/@sdd-agent-platform/core/src/sync-back/apply.ts +0 -270
- package/node_modules/@sdd-agent-platform/core/src/sync-back/inspect.ts +0 -655
- package/node_modules/@sdd-agent-platform/core/src/sync-back/sync-back.test.ts +0 -569
- package/node_modules/@sdd-agent-platform/core/src/sync-back.ts +0 -2
- package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.test.ts +0 -255
- package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.test.ts +0 -439
- package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.test.ts +0 -341
- package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.test.ts +0 -204
- package/packages/cli/dist/commands/lifecycle.d.ts +0 -6
- package/packages/cli/dist/commands/lifecycle.js +0 -112
- package/packages/cli/dist/commands/lifecycle.js.map +0 -1
- package/packages/cli/dist/commands/sync-back.d.ts +0 -6
- package/packages/cli/dist/commands/sync-back.js +0 -82
- package/packages/cli/dist/commands/sync-back.js.map +0 -1
- package/packages/cli/dist/commands/test.d.ts +0 -6
- package/packages/cli/dist/commands/test.js +0 -195
- package/packages/cli/dist/commands/test.js.map +0 -1
- package/packages/cli/dist/commands/verifies.d.ts +0 -6
- package/packages/cli/dist/commands/verifies.js +0 -85
- package/packages/cli/dist/commands/verifies.js.map +0 -1
- package/packages/cli/dist/commands/verify.d.ts +0 -6
- package/packages/cli/dist/commands/verify.js +0 -134
- package/packages/cli/dist/commands/verify.js.map +0 -1
- package/packages/core/dist/doctor/render.d.ts +0 -2
- package/packages/core/dist/doctor/render.js +0 -44
- package/packages/core/dist/doctor/render.js.map +0 -1
|
@@ -0,0 +1,4018 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { LIFECYCLE_DECISION_CONTRACT, LIFECYCLE_DECISION_VERSION, STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION, STAGE_RUN_CONTRACT_VERSION, WORKFLOW_HANDOFF_CONTRACT_VERSION } from './contracts.js';
|
|
5
|
+
import { LIFECYCLE_RISK_DECISION_PROJECTION_TYPE, lifecycleRiskDecisionScopeKey, recordLifecycleRiskDecisionProjection } from './risk/kernel.js';
|
|
6
|
+
import { createRun } from './run-state/run-state.js';
|
|
7
|
+
import { normalizePortablePath } from './path-safety.js';
|
|
8
|
+
import { getBranchStageEvidenceDir, normalizeBranchStageEvidenceRef, toBranchStageEvidenceRef } from './runtime-paths.js';
|
|
9
|
+
import { resolveSddContext } from './sdd-docs/context.js';
|
|
10
|
+
import { evaluatePlanArtifactDepth, evaluateSpecArtifactDepth, formatArtifactDepthBlockingIssues } from './sdd-docs/artifact-depth.js';
|
|
11
|
+
import { hashDocumentContent, hashSemanticDocument, hashTasksContract } from './sdd-docs/document-hashes.js';
|
|
12
|
+
import { parseSddTasksMarkdown } from './sdd-docs/task-parser.js';
|
|
13
|
+
import { EXECUTION_LANE_PROJECTION_TYPE, TASK_DEPENDENCY_GRAPH_PROJECTION_TYPE, TASK_UNIT_PROJECTION_TYPE, executionLaneScopeKey, readTaskDependencyGraphProjection, taskDependencyGraphScopeKey } from './task-execution-contract.js';
|
|
14
|
+
import { readWorkflowHandoffProjection, recordStageRunProjection, recordWorkflowHandoffProjection, STAGE_RUN_PROJECTION_TYPE, stageRunScopeKey, WORKFLOW_HANDOFF_PROJECTION_TYPE, workflowHandoffScopeKey } from './stage-runtime/runtime.js';
|
|
15
|
+
import { listRuntimeProjections, recordRuntimeArtifactPayload, recordRuntimeProjectionEnvelope, recordRuntimeStageArtifact, recordRuntimeStageCollaborationContract, runtimeScopedId } from './storage/runtime-store.js';
|
|
16
|
+
import { readMarkdownArtifact, validateStageArtifactFrontmatter } from './stage-artifacts.js';
|
|
17
|
+
import { readStageCollaborationContract, validateStageCollaborationContractFrontmatter } from './stage-collaboration-contracts.js';
|
|
18
|
+
export const SPEC_STAGE_MANAGER = 'spec-manager';
|
|
19
|
+
export const SPEC_STAGE_REVIEW_AGENT = 'spec-reviewer';
|
|
20
|
+
export const SPEC_STAGE_SCOUT_AGENT = 'scout';
|
|
21
|
+
export const SPEC_STAGE_AGENT_TEAM = [SPEC_STAGE_SCOUT_AGENT, SPEC_STAGE_REVIEW_AGENT];
|
|
22
|
+
export const SPEC_STAGE_REQUIRED_CAPABILITIES = ['norm_discovery', 'uncertainty_resolution'];
|
|
23
|
+
export const SPEC_STAGE_OPTIONAL_CAPABILITIES = ['context_curation', 'solution-design', 'frontend-engineering', 'security-engineering', 'ui-ux-product-design'];
|
|
24
|
+
export const SPEC_STAGE_MATERIAL_PACKS = ['project-norms', 'uncertainty-map', 'baseline-solution-design', 'baseline-frontend-engineering', 'baseline-security-engineering', 'baseline-ui-ux-product-design'];
|
|
25
|
+
export const STAGE_COLLABORATION_RUNTIME_PRODUCER_VERSION = 'phase9.1-stage-collaboration-runtime-v1';
|
|
26
|
+
export const SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_1_spec_collaboration_adjudication';
|
|
27
|
+
export const PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_2_plan_collaboration_adjudication';
|
|
28
|
+
export const PLAN_STAGE_MANAGER = 'plan-manager';
|
|
29
|
+
export const PLAN_STAGE_SCOUT_AGENT = 'plan-scout';
|
|
30
|
+
export const PLAN_STAGE_REVIEW_AGENT = 'plan-review-agent';
|
|
31
|
+
export const PLAN_STAGE_AGENT_TEAM = [PLAN_STAGE_SCOUT_AGENT, PLAN_STAGE_REVIEW_AGENT];
|
|
32
|
+
export const PLAN_SCOUT_DOMAINS = ['architecture-runtime', 'backend-api', 'frontend-ui', 'database-migration', 'security', 'performance', 'testing-validation'];
|
|
33
|
+
export const PLAN_STAGE_REQUIRED_CAPABILITIES = ['plan-strategy', 'plan-pressure-review'];
|
|
34
|
+
export const PLAN_STAGE_OPTIONAL_CAPABILITIES = [...PLAN_SCOUT_DOMAINS];
|
|
35
|
+
export const PLAN_STAGE_MATERIAL_PACKS = ['project-norms', 'plan-section-rubric', 'plan-scout-domain-packs'];
|
|
36
|
+
export const TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_3_tasks_collaboration_adjudication';
|
|
37
|
+
export const TASKS_STAGE_MANAGER = 'tasks-manager';
|
|
38
|
+
export const TASKS_STAGE_REVIEW_AGENT = 'task-review-agent';
|
|
39
|
+
export const TASKS_STAGE_SLICER_AGENT = 'task-slicer';
|
|
40
|
+
export const EXECUTE_IMPLEMENTER_AGENT = 'implementer';
|
|
41
|
+
export const EXECUTE_CODE_REVIEW_AGENT = 'code-reviewer';
|
|
42
|
+
export const EXECUTE_DEBUGGER_AGENT = 'debugger';
|
|
43
|
+
export const EXECUTE_TEST_RUNNER_AGENT = 'test-runner';
|
|
44
|
+
export const EXECUTE_VALIDATOR_AGENT = 'validator';
|
|
45
|
+
export const EXECUTE_TEST_REVIEW_AGENT = 'test-reviewer';
|
|
46
|
+
export const EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase10_execute_collaboration_adjudication';
|
|
47
|
+
export const EXECUTE_EVIDENCE_JUDGMENT_VALIDATOR_AGENT = 'evidence-validator';
|
|
48
|
+
export const EXECUTE_EVIDENCE_JUDGMENT_REVIEW_AGENT = 'evidence-reviewer';
|
|
49
|
+
export const TRUTH_ALIGNMENT_PROJECTION_TYPE = 'phase9_12_truth_alignment';
|
|
50
|
+
export const TRUTH_ALIGNMENT_CONTRACT = 'sdd-truth-alignment-v1';
|
|
51
|
+
export const SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_9_ship_collaboration_adjudication';
|
|
52
|
+
export const SHIP_STAGE_MANAGER = 'ship-manager';
|
|
53
|
+
export const SHIP_STAGE_VALIDATOR_AGENT = 'ship-validator';
|
|
54
|
+
export const SHIP_STAGE_REVIEW_AGENT = 'release-reviewer';
|
|
55
|
+
const TRUTH_ALIGNMENT_STATUSES = ['aligned', 'drift_detected', 'update_required', 'blocked'];
|
|
56
|
+
const TRUTH_ALIGNMENT_SEMANTIC_IMPACTS = ['none', 'bounded', 'material'];
|
|
57
|
+
const TRUTH_ALIGNMENT_OWNER_STAGES = ['spec', 'plan', 'tasks', 'execute', 'ship'];
|
|
58
|
+
export function deriveSpecCollaborationProfile(decision, generatedAt = new Date().toISOString()) {
|
|
59
|
+
const required = decision.requiredStages.includes('spec');
|
|
60
|
+
const intensity = specIntensity(decision, required);
|
|
61
|
+
return {
|
|
62
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
63
|
+
stage: 'spec',
|
|
64
|
+
scope: decision.scope,
|
|
65
|
+
lifecycleProfile: decision.profile,
|
|
66
|
+
intensity,
|
|
67
|
+
required,
|
|
68
|
+
requiresStageClosure: intensity !== 'noop' && intensity !== 'blocked',
|
|
69
|
+
allowedProposalKinds: intensity === 'noop' ? [] : ['advisory_finding', 'blocking_ambiguity', 'capability_suggestion', 'handoff_proposal'],
|
|
70
|
+
stageManager: intensity === 'noop' || intensity === 'blocked' ? null : SPEC_STAGE_MANAGER,
|
|
71
|
+
agentTeam: specMemberAgents(intensity),
|
|
72
|
+
requiredCapabilities: specRequiredCapabilities(intensity),
|
|
73
|
+
optionalCapabilities: specOptionalCapabilities(intensity),
|
|
74
|
+
materialPackIds: specMaterialPackIds(intensity),
|
|
75
|
+
collaborationPlan: specCollaborationPlan(intensity),
|
|
76
|
+
inputRefs: decision.inputRefs,
|
|
77
|
+
reasons: specProfileReasons(decision, required, intensity),
|
|
78
|
+
generatedAt
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function buildSpecStageWorkOrder(profile, profileRef, generatedAt = new Date().toISOString()) {
|
|
82
|
+
return {
|
|
83
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
84
|
+
workOrderId: stableId('spec-work-order', profile.scope),
|
|
85
|
+
stage: 'spec',
|
|
86
|
+
scope: profile.scope,
|
|
87
|
+
profileRef,
|
|
88
|
+
authorityCeiling: 'proposal_input',
|
|
89
|
+
requiredOutputKinds: specRequiredOutputKinds(profile.intensity),
|
|
90
|
+
forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
|
|
91
|
+
stageManager: SPEC_STAGE_MANAGER,
|
|
92
|
+
agentTeam: profile.agentTeam,
|
|
93
|
+
requiredCapabilities: profile.requiredCapabilities,
|
|
94
|
+
optionalCapabilities: profile.optionalCapabilities,
|
|
95
|
+
materialPackIds: profile.materialPackIds,
|
|
96
|
+
collaborationPlan: profile.collaborationPlan,
|
|
97
|
+
inputRefs: profile.inputRefs,
|
|
98
|
+
generatedAt
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function planCollaborationScopeKey(scope) {
|
|
102
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'plan'].join(':');
|
|
103
|
+
}
|
|
104
|
+
export function buildPlanStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString(), intensity = 'scout-first') {
|
|
105
|
+
const collaborationPlan = planCollaborationPlan(intensity);
|
|
106
|
+
return {
|
|
107
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
108
|
+
workOrderId: stableId('plan-work-order', scope),
|
|
109
|
+
stage: 'plan',
|
|
110
|
+
scope,
|
|
111
|
+
profileRef,
|
|
112
|
+
authorityCeiling: 'proposal_input',
|
|
113
|
+
requiredOutputKinds: planRequiredOutputKinds(intensity),
|
|
114
|
+
forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
|
|
115
|
+
inputRefs,
|
|
116
|
+
stageManager: PLAN_STAGE_MANAGER,
|
|
117
|
+
agentTeam: planMemberAgents(intensity),
|
|
118
|
+
planScoutDomains: planScoutDomains(intensity),
|
|
119
|
+
requiredCapabilities: planRequiredCapabilities(intensity),
|
|
120
|
+
optionalCapabilities: planOptionalCapabilities(intensity),
|
|
121
|
+
materialPackIds: planMaterialPackIds(intensity),
|
|
122
|
+
collaborationPlan,
|
|
123
|
+
generatedAt
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function tasksCollaborationScopeKey(scope) {
|
|
127
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'tasks'].join(':');
|
|
128
|
+
}
|
|
129
|
+
export function buildTasksStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
|
|
130
|
+
return {
|
|
131
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
132
|
+
workOrderId: stableId('tasks-work-order', scope),
|
|
133
|
+
stage: 'tasks',
|
|
134
|
+
scope,
|
|
135
|
+
profileRef,
|
|
136
|
+
authorityCeiling: 'proposal_input',
|
|
137
|
+
requiredOutputKinds: ['tasks_context', 'tasks_review', 'manager_closure_request'],
|
|
138
|
+
forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
|
|
139
|
+
inputRefs,
|
|
140
|
+
stageManager: TASKS_STAGE_MANAGER,
|
|
141
|
+
agentTeam: [TASKS_STAGE_SLICER_AGENT, TASKS_STAGE_REVIEW_AGENT],
|
|
142
|
+
requiredCapabilities: ['task-decomposition', 'acceptance-mapping'],
|
|
143
|
+
optionalCapabilities: ['dependency-analysis', 'parallelization-planning'],
|
|
144
|
+
materialPackIds: ['project-norms', 'baseline-task-slicing', 'baseline-acceptance-mapping'],
|
|
145
|
+
generatedAt
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function executeCollaborationScopeKey(scope) {
|
|
149
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'execute'].join(':');
|
|
150
|
+
}
|
|
151
|
+
export function buildExecuteStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
|
|
152
|
+
return {
|
|
153
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
154
|
+
workOrderId: stableId('execute-work-order', scope),
|
|
155
|
+
stage: 'execute',
|
|
156
|
+
scope,
|
|
157
|
+
profileRef,
|
|
158
|
+
authorityCeiling: 'proposal_input',
|
|
159
|
+
requiredOutputKinds: ['implementation_evidence', 'code_review', 'validation_evidence', 'checkpoint_decision', 'evidence_judgment', 'manager_closure_request'],
|
|
160
|
+
forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
|
|
161
|
+
inputRefs,
|
|
162
|
+
stageManager: 'execute-manager',
|
|
163
|
+
agentTeam: [EXECUTE_IMPLEMENTER_AGENT, EXECUTE_CODE_REVIEW_AGENT, EXECUTE_DEBUGGER_AGENT, EXECUTE_TEST_RUNNER_AGENT, EXECUTE_VALIDATOR_AGENT, EXECUTE_TEST_REVIEW_AGENT, EXECUTE_EVIDENCE_JUDGMENT_VALIDATOR_AGENT, EXECUTE_EVIDENCE_JUDGMENT_REVIEW_AGENT],
|
|
164
|
+
requiredCapabilities: ['implementation', 'code-review', 'validation', 'evidence-judgment'],
|
|
165
|
+
optionalCapabilities: ['debugging', 'checkpoint-review', 'frontend-engineering', 'security-engineering'],
|
|
166
|
+
materialPackIds: ['accepted-task-contract', 'execute-lane-contract', 'validation-contract', 'checkpoint-policy', 'evidence-judgment-policy'],
|
|
167
|
+
generatedAt
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function truthAlignmentScopeKey(scope) {
|
|
171
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'truth-alignment'].join(':');
|
|
172
|
+
}
|
|
173
|
+
function buildTruthAlignmentProjection(scope, input) {
|
|
174
|
+
const acceptedRealityRefs = uniqueRuntimeRefs([input.acceptedEvidenceJudgmentRef, ...input.artifactRefs].filter((ref) => ref !== null));
|
|
175
|
+
return {
|
|
176
|
+
contract: TRUTH_ALIGNMENT_CONTRACT,
|
|
177
|
+
branch: scope.branch,
|
|
178
|
+
sourceStage: 'execute',
|
|
179
|
+
declaredTruthRefs: uniqueRuntimeRefs(input.declaredTruthRefs),
|
|
180
|
+
acceptedRealityRefs,
|
|
181
|
+
status: input.status,
|
|
182
|
+
ownerStage: input.ownerStage,
|
|
183
|
+
semanticImpact: input.semanticImpact,
|
|
184
|
+
staleRefs: uniqueRuntimeRefs(input.staleRefs),
|
|
185
|
+
invalidatesStages: [...new Set(input.invalidatesStages)],
|
|
186
|
+
reasons: input.reasons,
|
|
187
|
+
createdAt: input.generatedAt
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function shipCollaborationScopeKey(scope) {
|
|
191
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'ship'].join(':');
|
|
192
|
+
}
|
|
193
|
+
export function buildShipStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
|
|
194
|
+
return {
|
|
195
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
196
|
+
workOrderId: stableId('ship-work-order', scope),
|
|
197
|
+
stage: 'ship',
|
|
198
|
+
scope,
|
|
199
|
+
profileRef,
|
|
200
|
+
authorityCeiling: 'proposal_input',
|
|
201
|
+
requiredOutputKinds: ['ship_readiness', 'release_review', 'manager_closure_request'],
|
|
202
|
+
forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
|
|
203
|
+
inputRefs,
|
|
204
|
+
stageManager: SHIP_STAGE_MANAGER,
|
|
205
|
+
agentTeam: [SHIP_STAGE_VALIDATOR_AGENT, SHIP_STAGE_REVIEW_AGENT],
|
|
206
|
+
requiredCapabilities: ['ship-readiness', 'release-readiness'],
|
|
207
|
+
optionalCapabilities: ['release-note-review', 'migration-readiness', 'operational-readiness'],
|
|
208
|
+
materialPackIds: ['project-norms', 'baseline-ship-readiness', 'baseline-release-review'],
|
|
209
|
+
generatedAt
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function specCollaborationScopeKey(scope) {
|
|
213
|
+
return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'spec'].join(':');
|
|
214
|
+
}
|
|
215
|
+
export async function recordSpecCollaborationAdjudicationProjection(projectRoot, result) {
|
|
216
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
217
|
+
projectionType: SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
|
|
218
|
+
scopeKey: specCollaborationScopeKey(result.scope),
|
|
219
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
220
|
+
producer: 'phase9.1-stage-collaboration-runtime',
|
|
221
|
+
producerVersion: STAGE_COLLABORATION_RUNTIME_PRODUCER_VERSION,
|
|
222
|
+
generatedAt: result.createdAt,
|
|
223
|
+
payload: result
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
export async function recordPlanCollaborationAdjudicationProjection(projectRoot, result) {
|
|
227
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
228
|
+
projectionType: PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
|
|
229
|
+
scopeKey: planCollaborationScopeKey(result.scope),
|
|
230
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
231
|
+
producer: 'phase9.2-plan-stage-collaboration-runtime',
|
|
232
|
+
producerVersion: 'phase9.2-plan-stage-collaboration-runtime-v1',
|
|
233
|
+
generatedAt: result.createdAt,
|
|
234
|
+
payload: result
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export async function recordTasksCollaborationAdjudicationProjection(projectRoot, result) {
|
|
238
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
239
|
+
projectionType: TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
|
|
240
|
+
scopeKey: tasksCollaborationScopeKey(result.scope),
|
|
241
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
242
|
+
producer: 'phase9.3-tasks-stage-collaboration-runtime',
|
|
243
|
+
producerVersion: 'phase9.3-tasks-stage-collaboration-runtime-v1',
|
|
244
|
+
generatedAt: result.createdAt,
|
|
245
|
+
payload: result
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
export async function recordExecuteCollaborationAdjudicationProjection(projectRoot, result) {
|
|
249
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
250
|
+
projectionType: EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
|
|
251
|
+
scopeKey: executeCollaborationScopeKey(result.scope),
|
|
252
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
253
|
+
producer: 'phase10-execute-stage-collaboration-runtime',
|
|
254
|
+
producerVersion: 'phase10-execute-stage-collaboration-runtime-v1',
|
|
255
|
+
generatedAt: result.createdAt,
|
|
256
|
+
payload: result
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
export async function recordTruthAlignmentProjection(projectRoot, result) {
|
|
260
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
261
|
+
projectionType: TRUTH_ALIGNMENT_PROJECTION_TYPE,
|
|
262
|
+
scopeKey: truthAlignmentScopeKey({ branch: result.branch }),
|
|
263
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
264
|
+
producer: 'phase9.12-truth-alignment-runtime-projection',
|
|
265
|
+
producerVersion: 'phase9.12-truth-alignment-runtime-projection-v1',
|
|
266
|
+
generatedAt: result.createdAt,
|
|
267
|
+
payload: result
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
export async function readTruthAlignmentProjection(projectRoot, scope) {
|
|
271
|
+
const scopeKey = truthAlignmentScopeKey({ branch: scope.branch });
|
|
272
|
+
const projections = await listRuntimeProjections(projectRoot, [TRUTH_ALIGNMENT_PROJECTION_TYPE]);
|
|
273
|
+
return projections
|
|
274
|
+
.map((projection) => projection.payload)
|
|
275
|
+
.filter((envelope) => envelope?.projectionType === TRUTH_ALIGNMENT_PROJECTION_TYPE && (envelope.scopeKey === scopeKey || envelope.payload?.branch === scope.branch))
|
|
276
|
+
.sort((left, right) => right.generatedAt.localeCompare(left.generatedAt))[0] ?? null;
|
|
277
|
+
}
|
|
278
|
+
export async function recordShipCollaborationAdjudicationProjection(projectRoot, result) {
|
|
279
|
+
return recordRuntimeProjectionEnvelope(projectRoot, {
|
|
280
|
+
projectionType: SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
|
|
281
|
+
scopeKey: shipCollaborationScopeKey(result.scope),
|
|
282
|
+
inputHash: stableHash(JSON.stringify(result)),
|
|
283
|
+
producer: 'phase9.9-ship-stage-collaboration-runtime',
|
|
284
|
+
producerVersion: 'phase9.9-ship-stage-collaboration-runtime-v1',
|
|
285
|
+
generatedAt: result.createdAt,
|
|
286
|
+
payload: result
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
export async function reconcileSpecCollaborationClosure(projectRoot, input) {
|
|
290
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
291
|
+
const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
|
|
292
|
+
const decision = {
|
|
293
|
+
...input.decision,
|
|
294
|
+
scope: { ...input.decision.scope, branch: context.rawBranch }
|
|
295
|
+
};
|
|
296
|
+
const run = await createRun(projectRoot, {
|
|
297
|
+
runId: input.runId,
|
|
298
|
+
branch: context.rawBranch,
|
|
299
|
+
lifecycleDecision: runLifecycleDecisionRecord(decision)
|
|
300
|
+
});
|
|
301
|
+
const runRef = { kind: 'run', ref: run.runId };
|
|
302
|
+
const profile = deriveSpecCollaborationProfile(decision, generatedAt);
|
|
303
|
+
const profileRef = { kind: 'projection', ref: `${SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${specCollaborationScopeKey(profile.scope)}:profile` };
|
|
304
|
+
const workOrder = profile.stageManager ? buildSpecStageWorkOrder(profile, profileRef, generatedAt) : null;
|
|
305
|
+
const registeredCollaborationContracts = input.collaborationContractRefs && input.collaborationContractRefs.length > 0
|
|
306
|
+
? await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'spec', workOrder, input.collaborationContractRefs, generatedAt)
|
|
307
|
+
: [];
|
|
308
|
+
const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
|
|
309
|
+
const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
|
|
310
|
+
const registeredArtifacts = await registerSpecStageArtifacts(projectRoot, context.rawBranch, profile, input.artifactRefs, run.runId, generatedAt);
|
|
311
|
+
const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
|
|
312
|
+
const outputClosure = await buildOutputCenteredSpecClosureRequest(projectRoot, profile, workOrder, context.partition, input.outputCloseRequest, generatedAt);
|
|
313
|
+
const closureRequest = input.closureRequest ?? outputClosure;
|
|
314
|
+
const coordination = input.coordination ?? deriveRegisteredSpecManagerCoordination(profile, workOrder, registeredArtifacts, generatedAt);
|
|
315
|
+
const candidate = closureRequest?.candidate ?? null;
|
|
316
|
+
const reviews = closureRequest?.reviewResults ?? [];
|
|
317
|
+
const capabilityFindings = closureRequest?.capabilityFindings ?? [];
|
|
318
|
+
let adjudication = adjudicateSpecStageClosureRequest({
|
|
319
|
+
profile,
|
|
320
|
+
closureRequest,
|
|
321
|
+
answerRefs: input.answerRefs,
|
|
322
|
+
generatedAt
|
|
323
|
+
});
|
|
324
|
+
const specAcceptance = await validateAcceptedSpecArtifact(projectRoot, {
|
|
325
|
+
partition: context.partition,
|
|
326
|
+
adjudication,
|
|
327
|
+
closureRequest,
|
|
328
|
+
registeredArtifacts,
|
|
329
|
+
validatedCollaborationContract
|
|
330
|
+
});
|
|
331
|
+
if (specAcceptance.rejectionIssue) {
|
|
332
|
+
adjudication = rejected({ profile, closureRequest, answerRefs: input.answerRefs }, specAcceptance.rejectionIssue.reasonCode, specAcceptance.rejectionIssue.explanation, specAcceptance.rejectionIssue.requiredNextAction, specAcceptance.rejectionIssue.fallbackRoute, generatedAt, true);
|
|
333
|
+
}
|
|
334
|
+
const adjudicationProjectionRef = specAdjudicationProjectionRef(adjudication.scope);
|
|
335
|
+
const lifecycleRiskProjectionRef = {
|
|
336
|
+
kind: 'projection',
|
|
337
|
+
ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
|
|
338
|
+
};
|
|
339
|
+
const outputRefs = [specAcceptance.acceptedSpecRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
|
|
340
|
+
const stageRun = buildClosureStageRun(profile, adjudication, {
|
|
341
|
+
runId: run.runId,
|
|
342
|
+
outputRefs,
|
|
343
|
+
decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
|
|
344
|
+
generatedAt
|
|
345
|
+
});
|
|
346
|
+
const stageRunProjectionRef = {
|
|
347
|
+
kind: 'projection',
|
|
348
|
+
ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
|
|
349
|
+
};
|
|
350
|
+
const handoff = specAcceptance.specAcceptanceStatus === 'accepted' && adjudication.health === 'ready_for_plan'
|
|
351
|
+
? buildClosureWorkflowHandoff(profile, decision, adjudication, {
|
|
352
|
+
outputRefs,
|
|
353
|
+
requiredInputRefs: specAcceptance.acceptedSpecRef ? [specAcceptance.acceptedSpecRef] : [],
|
|
354
|
+
evidenceRefs: artifactRefs,
|
|
355
|
+
riskDecisionRef: lifecycleRiskProjectionRef,
|
|
356
|
+
generatedAt
|
|
357
|
+
})
|
|
358
|
+
: null;
|
|
359
|
+
const workflowHandoffProjectionRef = handoff
|
|
360
|
+
? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
|
|
361
|
+
: undefined;
|
|
362
|
+
const closureRefs = {
|
|
363
|
+
runRef,
|
|
364
|
+
acceptedSpecRef: specAcceptance.acceptedSpecRef,
|
|
365
|
+
specAcceptanceStatus: specAcceptance.specAcceptanceStatus,
|
|
366
|
+
specHash: specAcceptance.specHash,
|
|
367
|
+
specContractHash: specAcceptance.specContractHash,
|
|
368
|
+
artifactRefs,
|
|
369
|
+
collaborationContractRef,
|
|
370
|
+
lifecycleRiskProjectionRef,
|
|
371
|
+
adjudicationProjectionRef,
|
|
372
|
+
stageRunProjectionRef,
|
|
373
|
+
workflowHandoffProjectionRef,
|
|
374
|
+
reasons: specAcceptance.reasons
|
|
375
|
+
};
|
|
376
|
+
const enrichedAdjudication = { ...adjudication, closureRefs };
|
|
377
|
+
await recordLifecycleRiskDecisionProjection(projectRoot, decision);
|
|
378
|
+
await recordStageRunProjection(projectRoot, stageRun);
|
|
379
|
+
if (handoff) {
|
|
380
|
+
await recordWorkflowHandoffProjection(projectRoot, handoff);
|
|
381
|
+
}
|
|
382
|
+
const projection = await recordSpecCollaborationAdjudicationProjection(projectRoot, enrichedAdjudication);
|
|
383
|
+
return {
|
|
384
|
+
runId: run.runId,
|
|
385
|
+
branch: context.rawBranch,
|
|
386
|
+
profile,
|
|
387
|
+
workOrder,
|
|
388
|
+
coordination,
|
|
389
|
+
candidate,
|
|
390
|
+
reviews,
|
|
391
|
+
capabilityFindings,
|
|
392
|
+
closureRequest,
|
|
393
|
+
adjudication: enrichedAdjudication,
|
|
394
|
+
projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
|
|
395
|
+
artifactRefs,
|
|
396
|
+
registeredArtifacts,
|
|
397
|
+
registeredCollaborationContracts,
|
|
398
|
+
acceptedSpecRef: specAcceptance.acceptedSpecRef
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
export async function reconcilePlanCollaborationClosure(projectRoot, input) {
|
|
402
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
403
|
+
const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
|
|
404
|
+
const decision = {
|
|
405
|
+
...input.decision,
|
|
406
|
+
scope: { ...input.decision.scope, branch: context.rawBranch }
|
|
407
|
+
};
|
|
408
|
+
const run = await createRun(projectRoot, {
|
|
409
|
+
runId: input.runId,
|
|
410
|
+
branch: context.rawBranch,
|
|
411
|
+
lifecycleDecision: runLifecycleDecisionRecord(decision)
|
|
412
|
+
});
|
|
413
|
+
const runRef = { kind: 'run', ref: run.runId };
|
|
414
|
+
const planRequired = decision.requiredStages.includes('plan');
|
|
415
|
+
const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('plan');
|
|
416
|
+
const acceptedSpecHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'spec', 'plan');
|
|
417
|
+
const workOrderInputRefs = acceptedSpecHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
|
|
418
|
+
const profileRef = { kind: 'projection', ref: `${PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${planCollaborationScopeKey(decision.scope)}:profile` };
|
|
419
|
+
const workOrder = planRequired && !blocked ? buildPlanStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
|
|
420
|
+
const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'plan', null, input.collaborationContractRefs, generatedAt);
|
|
421
|
+
const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
|
|
422
|
+
const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
|
|
423
|
+
const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'plan', input.artifactRefs, false, run.runId, generatedAt);
|
|
424
|
+
const closureRequest = await buildOutputCenteredPlanClosureRequest(projectRoot, decision.scope, workOrder, context.partition, acceptedSpecHandoffEnvelope?.payload ?? null, input.outputCloseRequest, generatedAt);
|
|
425
|
+
const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
|
|
426
|
+
const lifecycleRiskProjectionRef = {
|
|
427
|
+
kind: 'projection',
|
|
428
|
+
ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
|
|
429
|
+
};
|
|
430
|
+
const adjudicationProjectionRef = planAdjudicationProjectionRef(decision.scope);
|
|
431
|
+
const planAcceptance = await validateAcceptedPlanArtifact(projectRoot, {
|
|
432
|
+
partition: context.partition,
|
|
433
|
+
required: planRequired,
|
|
434
|
+
blocked,
|
|
435
|
+
registeredArtifacts,
|
|
436
|
+
registeredCollaborationContracts,
|
|
437
|
+
acceptedSpecHandoff: acceptedSpecHandoffEnvelope?.payload ?? null,
|
|
438
|
+
closureRequest,
|
|
439
|
+
});
|
|
440
|
+
const health = planAcceptance.planAcceptanceStatus === 'accepted'
|
|
441
|
+
? 'ready_for_tasks'
|
|
442
|
+
: !planRequired
|
|
443
|
+
? 'no-op'
|
|
444
|
+
: blocked
|
|
445
|
+
? 'blocked'
|
|
446
|
+
: 'rejected';
|
|
447
|
+
const outputRefs = [planAcceptance.acceptedPlanRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
|
|
448
|
+
const stageRun = buildPlanClosureStageRun(decision.scope, run.runId, workOrder, health, {
|
|
449
|
+
outputRefs,
|
|
450
|
+
decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
|
|
451
|
+
inputRefs: acceptedSpecHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
|
|
452
|
+
generatedAt,
|
|
453
|
+
rejectionReason: planAcceptance.rejectionIssue?.explanation ?? null
|
|
454
|
+
});
|
|
455
|
+
const stageRunProjectionRef = {
|
|
456
|
+
kind: 'projection',
|
|
457
|
+
ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
|
|
458
|
+
};
|
|
459
|
+
const acceptedSpecRef = acceptedSpecRefFromHandoff(context.partition, acceptedSpecHandoffEnvelope?.payload ?? null);
|
|
460
|
+
const handoffOutputRefs = [planAcceptance.acceptedPlanRef].filter((ref) => ref !== null);
|
|
461
|
+
const handoffRequiredInputRefs = [acceptedSpecRef, planAcceptance.acceptedPlanRef].filter((ref) => ref !== null);
|
|
462
|
+
const handoff = planAcceptance.planAcceptanceStatus === 'accepted'
|
|
463
|
+
? buildPlanWorkflowHandoff(decision.scope, decision, {
|
|
464
|
+
outputRefs: handoffOutputRefs,
|
|
465
|
+
requiredInputRefs: handoffRequiredInputRefs,
|
|
466
|
+
evidenceRefs: [],
|
|
467
|
+
riskDecisionRef: lifecycleRiskProjectionRef,
|
|
468
|
+
generatedAt
|
|
469
|
+
})
|
|
470
|
+
: null;
|
|
471
|
+
const workflowHandoffProjectionRef = handoff
|
|
472
|
+
? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
|
|
473
|
+
: undefined;
|
|
474
|
+
const rejection = planAcceptance.rejectionIssue
|
|
475
|
+
? buildStageRejection('plan', decision.scope, null, planAcceptance.rejectionIssue.reasonCode, planAcceptance.rejectionIssue.explanation, planAcceptance.rejectionIssue.requiredNextAction, planAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
|
|
476
|
+
: null;
|
|
477
|
+
const closureRefs = {
|
|
478
|
+
runRef,
|
|
479
|
+
acceptedPlanRef: planAcceptance.acceptedPlanRef,
|
|
480
|
+
planAcceptanceStatus: planAcceptance.planAcceptanceStatus,
|
|
481
|
+
planHash: planAcceptance.planHash,
|
|
482
|
+
planContractHash: planAcceptance.planContractHash,
|
|
483
|
+
acceptedSpecHandoffRef: acceptedSpecHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'spec', 'plan')}` } : null,
|
|
484
|
+
artifactRefs,
|
|
485
|
+
collaborationContractRef,
|
|
486
|
+
lifecycleRiskProjectionRef,
|
|
487
|
+
adjudicationProjectionRef,
|
|
488
|
+
stageRunProjectionRef,
|
|
489
|
+
workflowHandoffProjectionRef,
|
|
490
|
+
reasons: planAcceptance.reasons
|
|
491
|
+
};
|
|
492
|
+
const adjudication = {
|
|
493
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
494
|
+
adjudicationId: stableId('plan-adjudication', decision.scope, health, generatedAt),
|
|
495
|
+
stage: 'plan',
|
|
496
|
+
scope: decision.scope,
|
|
497
|
+
health,
|
|
498
|
+
stageDecision: {
|
|
499
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
500
|
+
decisionId: stableId('plan-stage-decision', decision.scope, run.runId, generatedAt),
|
|
501
|
+
stage: 'plan',
|
|
502
|
+
scope: decision.scope,
|
|
503
|
+
status: health === 'ready_for_tasks' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
|
|
504
|
+
health,
|
|
505
|
+
acceptedDecisionRefs: [planAcceptance.acceptedPlanRef, adjudicationProjectionRef].filter((ref) => ref !== null),
|
|
506
|
+
advisoryRefs: [],
|
|
507
|
+
capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('context') || ref.ref.includes('capability')),
|
|
508
|
+
blockingReasons: health === 'ready_for_tasks' || health === 'no-op' ? [] : planAcceptance.reasons,
|
|
509
|
+
createdAt: generatedAt
|
|
510
|
+
},
|
|
511
|
+
handoffPacket: handoff,
|
|
512
|
+
rejection,
|
|
513
|
+
nextActions: buildStageRuntimeNextActions('plan', decision.scope, generatedAt, health === 'ready_for_tasks' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
|
|
514
|
+
closureRefs,
|
|
515
|
+
createdAt: generatedAt
|
|
516
|
+
};
|
|
517
|
+
await recordLifecycleRiskDecisionProjection(projectRoot, decision);
|
|
518
|
+
await recordStageRunProjection(projectRoot, stageRun);
|
|
519
|
+
if (handoff) {
|
|
520
|
+
await recordWorkflowHandoffProjection(projectRoot, handoff);
|
|
521
|
+
}
|
|
522
|
+
const projection = await recordPlanCollaborationAdjudicationProjection(projectRoot, adjudication);
|
|
523
|
+
return {
|
|
524
|
+
runId: run.runId,
|
|
525
|
+
branch: context.rawBranch,
|
|
526
|
+
workOrder,
|
|
527
|
+
adjudication,
|
|
528
|
+
projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
|
|
529
|
+
artifactRefs,
|
|
530
|
+
registeredArtifacts,
|
|
531
|
+
registeredCollaborationContracts,
|
|
532
|
+
acceptedPlanRef: planAcceptance.acceptedPlanRef
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
export async function reconcileTasksCollaborationClosure(projectRoot, input) {
|
|
536
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
537
|
+
const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
|
|
538
|
+
const decision = {
|
|
539
|
+
...input.decision,
|
|
540
|
+
scope: { ...input.decision.scope, branch: context.rawBranch }
|
|
541
|
+
};
|
|
542
|
+
const run = await createRun(projectRoot, {
|
|
543
|
+
runId: input.runId,
|
|
544
|
+
branch: context.rawBranch,
|
|
545
|
+
lifecycleDecision: runLifecycleDecisionRecord(decision)
|
|
546
|
+
});
|
|
547
|
+
const runRef = { kind: 'run', ref: run.runId };
|
|
548
|
+
const tasksRequired = decision.requiredStages.includes('tasks');
|
|
549
|
+
const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('tasks');
|
|
550
|
+
const acceptedPlanHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'plan', 'tasks');
|
|
551
|
+
const workOrderInputRefs = acceptedPlanHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
|
|
552
|
+
const profileRef = { kind: 'projection', ref: `${TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${tasksCollaborationScopeKey(decision.scope)}:profile` };
|
|
553
|
+
const workOrder = tasksRequired && !blocked ? buildTasksStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
|
|
554
|
+
const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'tasks', null, input.collaborationContractRefs, generatedAt);
|
|
555
|
+
const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
|
|
556
|
+
const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
|
|
557
|
+
const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'tasks', input.artifactRefs, false, run.runId, generatedAt);
|
|
558
|
+
const closureRequest = await buildOutputCenteredTasksClosureRequest(projectRoot, decision.scope, workOrder, context.partition, acceptedPlanHandoffEnvelope?.payload ?? null, input.outputCloseRequest, generatedAt);
|
|
559
|
+
const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
|
|
560
|
+
const lifecycleRiskProjectionRef = {
|
|
561
|
+
kind: 'projection',
|
|
562
|
+
ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
|
|
563
|
+
};
|
|
564
|
+
const adjudicationProjectionRef = tasksAdjudicationProjectionRef(decision.scope);
|
|
565
|
+
const tasksAcceptance = await validateAcceptedTasksArtifact(projectRoot, {
|
|
566
|
+
partition: context.partition,
|
|
567
|
+
required: tasksRequired,
|
|
568
|
+
blocked,
|
|
569
|
+
registeredArtifacts,
|
|
570
|
+
registeredCollaborationContracts,
|
|
571
|
+
acceptedPlanHandoff: acceptedPlanHandoffEnvelope?.payload ?? null,
|
|
572
|
+
closureRequest
|
|
573
|
+
});
|
|
574
|
+
const health = tasksAcceptance.tasksAcceptanceStatus === 'accepted'
|
|
575
|
+
? 'ready_for_execute'
|
|
576
|
+
: !tasksRequired
|
|
577
|
+
? 'no-op'
|
|
578
|
+
: blocked
|
|
579
|
+
? 'blocked'
|
|
580
|
+
: 'rejected';
|
|
581
|
+
const outputRefs = [tasksAcceptance.acceptedTasksRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
|
|
582
|
+
const stageRun = buildTasksClosureStageRun(decision.scope, run.runId, workOrder, health, {
|
|
583
|
+
outputRefs,
|
|
584
|
+
decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
|
|
585
|
+
inputRefs: acceptedPlanHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
|
|
586
|
+
generatedAt,
|
|
587
|
+
rejectionReason: tasksAcceptance.rejectionIssue?.explanation ?? null
|
|
588
|
+
});
|
|
589
|
+
const stageRunProjectionRef = {
|
|
590
|
+
kind: 'projection',
|
|
591
|
+
ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
|
|
592
|
+
};
|
|
593
|
+
const handoff = tasksAcceptance.tasksAcceptanceStatus === 'accepted'
|
|
594
|
+
? buildTasksWorkflowHandoff(decision.scope, decision, {
|
|
595
|
+
outputRefs,
|
|
596
|
+
requiredInputRefs: tasksAcceptance.acceptedTasksRef ? [tasksAcceptance.acceptedTasksRef] : [],
|
|
597
|
+
evidenceRefs: artifactRefs,
|
|
598
|
+
riskDecisionRef: lifecycleRiskProjectionRef,
|
|
599
|
+
generatedAt
|
|
600
|
+
})
|
|
601
|
+
: null;
|
|
602
|
+
const workflowHandoffProjectionRef = handoff
|
|
603
|
+
? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
|
|
604
|
+
: undefined;
|
|
605
|
+
const rejection = tasksAcceptance.rejectionIssue
|
|
606
|
+
? buildStageRejection('tasks', decision.scope, null, tasksAcceptance.rejectionIssue.reasonCode, tasksAcceptance.rejectionIssue.explanation, tasksAcceptance.rejectionIssue.requiredNextAction, tasksAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
|
|
607
|
+
: null;
|
|
608
|
+
const closureRefs = {
|
|
609
|
+
runRef,
|
|
610
|
+
acceptedTasksRef: tasksAcceptance.acceptedTasksRef,
|
|
611
|
+
tasksAcceptanceStatus: tasksAcceptance.tasksAcceptanceStatus,
|
|
612
|
+
tasksHash: tasksAcceptance.tasksHash,
|
|
613
|
+
tasksContractHash: tasksAcceptance.tasksContractHash,
|
|
614
|
+
acceptedPlanHandoffRef: acceptedPlanHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'plan', 'tasks')}` } : null,
|
|
615
|
+
artifactRefs,
|
|
616
|
+
collaborationContractRef,
|
|
617
|
+
lifecycleRiskProjectionRef,
|
|
618
|
+
adjudicationProjectionRef,
|
|
619
|
+
stageRunProjectionRef,
|
|
620
|
+
workflowHandoffProjectionRef,
|
|
621
|
+
reasons: tasksAcceptance.reasons
|
|
622
|
+
};
|
|
623
|
+
const adjudication = {
|
|
624
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
625
|
+
adjudicationId: stableId('tasks-adjudication', decision.scope, health, generatedAt),
|
|
626
|
+
stage: 'tasks',
|
|
627
|
+
scope: decision.scope,
|
|
628
|
+
health,
|
|
629
|
+
stageDecision: {
|
|
630
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
631
|
+
decisionId: stableId('tasks-stage-decision', decision.scope, run.runId, generatedAt),
|
|
632
|
+
stage: 'tasks',
|
|
633
|
+
scope: decision.scope,
|
|
634
|
+
status: health === 'ready_for_execute' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
|
|
635
|
+
health,
|
|
636
|
+
acceptedDecisionRefs: outputRefs,
|
|
637
|
+
advisoryRefs: [],
|
|
638
|
+
capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('context') || ref.ref.includes('capability')),
|
|
639
|
+
blockingReasons: health === 'ready_for_execute' || health === 'no-op' ? [] : tasksAcceptance.reasons,
|
|
640
|
+
createdAt: generatedAt
|
|
641
|
+
},
|
|
642
|
+
handoffPacket: handoff,
|
|
643
|
+
rejection,
|
|
644
|
+
nextActions: buildStageRuntimeNextActions('tasks', decision.scope, generatedAt, health === 'ready_for_execute' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
|
|
645
|
+
closureRefs,
|
|
646
|
+
createdAt: generatedAt
|
|
647
|
+
};
|
|
648
|
+
await recordLifecycleRiskDecisionProjection(projectRoot, decision);
|
|
649
|
+
await recordStageRunProjection(projectRoot, stageRun);
|
|
650
|
+
if (handoff) {
|
|
651
|
+
await recordWorkflowHandoffProjection(projectRoot, handoff);
|
|
652
|
+
}
|
|
653
|
+
const projection = await recordTasksCollaborationAdjudicationProjection(projectRoot, adjudication);
|
|
654
|
+
return {
|
|
655
|
+
runId: run.runId,
|
|
656
|
+
branch: context.rawBranch,
|
|
657
|
+
workOrder,
|
|
658
|
+
adjudication,
|
|
659
|
+
projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
|
|
660
|
+
artifactRefs,
|
|
661
|
+
registeredArtifacts,
|
|
662
|
+
registeredCollaborationContracts,
|
|
663
|
+
acceptedTasksRef: tasksAcceptance.acceptedTasksRef
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
export async function reconcileExecuteCollaborationClosure(projectRoot, input) {
|
|
667
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
668
|
+
const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
|
|
669
|
+
const decision = {
|
|
670
|
+
...input.decision,
|
|
671
|
+
scope: { ...input.decision.scope, branch: context.rawBranch }
|
|
672
|
+
};
|
|
673
|
+
const run = await createRun(projectRoot, {
|
|
674
|
+
runId: input.runId,
|
|
675
|
+
branch: context.rawBranch,
|
|
676
|
+
lifecycleDecision: runLifecycleDecisionRecord(decision)
|
|
677
|
+
});
|
|
678
|
+
const runRef = { kind: 'run', ref: run.runId };
|
|
679
|
+
const executeRequired = decision.requiredStages.includes('execute');
|
|
680
|
+
const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('execute');
|
|
681
|
+
const acceptedTasksHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'tasks', 'execute');
|
|
682
|
+
const workOrderInputRefs = acceptedTasksHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
|
|
683
|
+
const profileRef = { kind: 'projection', ref: `${EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${executeCollaborationScopeKey(decision.scope)}:profile` };
|
|
684
|
+
const workOrder = executeRequired && !blocked ? buildExecuteStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
|
|
685
|
+
const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'execute', workOrder, input.collaborationContractRefs, generatedAt);
|
|
686
|
+
const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
|
|
687
|
+
const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
|
|
688
|
+
const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'execute', input.artifactRefs, Boolean(workOrder), run.runId, generatedAt);
|
|
689
|
+
const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
|
|
690
|
+
const lifecycleRiskProjectionRef = {
|
|
691
|
+
kind: 'projection',
|
|
692
|
+
ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
|
|
693
|
+
};
|
|
694
|
+
const adjudicationProjectionRef = executeAdjudicationProjectionRef(decision.scope);
|
|
695
|
+
const executeAcceptance = await validateAcceptedExecuteArtifact(projectRoot, {
|
|
696
|
+
partition: context.partition,
|
|
697
|
+
required: executeRequired,
|
|
698
|
+
blocked,
|
|
699
|
+
registeredArtifacts,
|
|
700
|
+
registeredCollaborationContracts,
|
|
701
|
+
acceptedTasksHandoff: acceptedTasksHandoffEnvelope?.payload ?? null
|
|
702
|
+
});
|
|
703
|
+
const health = executeAcceptance.executeAcceptanceStatus === 'accepted'
|
|
704
|
+
? 'ready_for_ship'
|
|
705
|
+
: !executeRequired
|
|
706
|
+
? 'no-op'
|
|
707
|
+
: blocked
|
|
708
|
+
? 'blocked'
|
|
709
|
+
: 'rejected';
|
|
710
|
+
const acceptedEvidenceJudgmentArtifact = latestStageArtifact(registeredArtifacts, 'evidence_judgment');
|
|
711
|
+
const acceptedEvidenceJudgmentRef = executeAcceptance.executeAcceptanceStatus === 'accepted' && acceptedEvidenceJudgmentArtifact
|
|
712
|
+
? { kind: 'artifact', ref: acceptedEvidenceJudgmentArtifact.ref, hash: acceptedEvidenceJudgmentArtifact.hash }
|
|
713
|
+
: null;
|
|
714
|
+
const truthAlignmentProjection = acceptedEvidenceJudgmentRef
|
|
715
|
+
? buildTruthAlignmentProjection(decision.scope, {
|
|
716
|
+
acceptedEvidenceJudgmentRef: acceptedEvidenceJudgmentRef,
|
|
717
|
+
declaredTruthRefs: await collectTruthAlignmentDeclaredTruthRefs(projectRoot, context.rawBranch, acceptedTasksHandoffEnvelope?.payload ?? null),
|
|
718
|
+
artifactRefs,
|
|
719
|
+
status: 'aligned',
|
|
720
|
+
ownerStage: null,
|
|
721
|
+
semanticImpact: 'none',
|
|
722
|
+
staleRefs: [],
|
|
723
|
+
invalidatesStages: [],
|
|
724
|
+
reasons: ['Accepted execute evidence judgment is structurally aligned with declared upstream truth refs.'],
|
|
725
|
+
generatedAt
|
|
726
|
+
})
|
|
727
|
+
: null;
|
|
728
|
+
const truthAlignmentProjectionRef = truthAlignmentProjection
|
|
729
|
+
? { kind: 'projection', ref: `${TRUTH_ALIGNMENT_PROJECTION_TYPE}:${truthAlignmentScopeKey({ branch: decision.scope.branch })}` }
|
|
730
|
+
: null;
|
|
731
|
+
const outputRefs = [executeAcceptance.acceptedExecuteRef, truthAlignmentProjectionRef, adjudicationProjectionRef, collaborationContractRef, ...executeAcceptance.laneEvidenceRefs, ...artifactRefs].filter((ref) => ref !== null);
|
|
732
|
+
const stageRun = buildExecuteClosureStageRun(decision.scope, run.runId, workOrder, health, {
|
|
733
|
+
outputRefs,
|
|
734
|
+
decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
|
|
735
|
+
inputRefs: acceptedTasksHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
|
|
736
|
+
generatedAt,
|
|
737
|
+
rejectionReason: executeAcceptance.rejectionIssue?.explanation ?? null
|
|
738
|
+
});
|
|
739
|
+
const stageRunProjectionRef = {
|
|
740
|
+
kind: 'projection',
|
|
741
|
+
ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
|
|
742
|
+
};
|
|
743
|
+
const handoff = executeAcceptance.executeAcceptanceStatus === 'accepted'
|
|
744
|
+
? buildExecuteWorkflowHandoff(decision.scope, decision, {
|
|
745
|
+
outputRefs,
|
|
746
|
+
requiredInputRefs: executeAcceptance.acceptedExecuteRef ? [executeAcceptance.acceptedExecuteRef, ...executeAcceptance.laneEvidenceRefs] : executeAcceptance.laneEvidenceRefs,
|
|
747
|
+
evidenceRefs: artifactRefs,
|
|
748
|
+
riskDecisionRef: lifecycleRiskProjectionRef,
|
|
749
|
+
generatedAt
|
|
750
|
+
})
|
|
751
|
+
: null;
|
|
752
|
+
const workflowHandoffProjectionRef = handoff
|
|
753
|
+
? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
|
|
754
|
+
: undefined;
|
|
755
|
+
const rejection = executeAcceptance.rejectionIssue
|
|
756
|
+
? buildStageRejection('execute', decision.scope, null, executeAcceptance.rejectionIssue.reasonCode, executeAcceptance.rejectionIssue.explanation, executeAcceptance.rejectionIssue.requiredNextAction, executeAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
|
|
757
|
+
: null;
|
|
758
|
+
const closureRefs = {
|
|
759
|
+
runRef,
|
|
760
|
+
acceptedExecuteRef: executeAcceptance.acceptedExecuteRef,
|
|
761
|
+
executeAcceptanceStatus: executeAcceptance.executeAcceptanceStatus,
|
|
762
|
+
acceptedTasksHandoffRef: acceptedTasksHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'tasks', 'execute')}` } : null,
|
|
763
|
+
truthAlignmentProjectionRef,
|
|
764
|
+
laneEvidenceRefs: executeAcceptance.laneEvidenceRefs,
|
|
765
|
+
artifactRefs,
|
|
766
|
+
collaborationContractRef,
|
|
767
|
+
lifecycleRiskProjectionRef,
|
|
768
|
+
adjudicationProjectionRef,
|
|
769
|
+
stageRunProjectionRef,
|
|
770
|
+
workflowHandoffProjectionRef,
|
|
771
|
+
reasons: executeAcceptance.reasons
|
|
772
|
+
};
|
|
773
|
+
const adjudication = {
|
|
774
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
775
|
+
adjudicationId: stableId('execute-adjudication', decision.scope, health, generatedAt),
|
|
776
|
+
stage: 'execute',
|
|
777
|
+
scope: decision.scope,
|
|
778
|
+
health,
|
|
779
|
+
stageDecision: {
|
|
780
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
781
|
+
decisionId: stableId('execute-stage-decision', decision.scope, run.runId, generatedAt),
|
|
782
|
+
stage: 'execute',
|
|
783
|
+
scope: decision.scope,
|
|
784
|
+
status: health === 'ready_for_ship' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
|
|
785
|
+
health,
|
|
786
|
+
acceptedDecisionRefs: outputRefs,
|
|
787
|
+
advisoryRefs: [],
|
|
788
|
+
capabilityRefs: executeAcceptance.laneEvidenceRefs,
|
|
789
|
+
blockingReasons: health === 'ready_for_ship' || health === 'no-op' ? [] : executeAcceptance.reasons,
|
|
790
|
+
createdAt: generatedAt
|
|
791
|
+
},
|
|
792
|
+
handoffPacket: handoff,
|
|
793
|
+
rejection,
|
|
794
|
+
nextActions: buildStageRuntimeNextActions('execute', decision.scope, generatedAt, health === 'ready_for_ship' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
|
|
795
|
+
closureRefs,
|
|
796
|
+
createdAt: generatedAt
|
|
797
|
+
};
|
|
798
|
+
await recordLifecycleRiskDecisionProjection(projectRoot, decision);
|
|
799
|
+
await recordStageRunProjection(projectRoot, stageRun);
|
|
800
|
+
if (truthAlignmentProjection) {
|
|
801
|
+
await recordTruthAlignmentProjection(projectRoot, truthAlignmentProjection);
|
|
802
|
+
}
|
|
803
|
+
if (handoff) {
|
|
804
|
+
await recordWorkflowHandoffProjection(projectRoot, handoff);
|
|
805
|
+
}
|
|
806
|
+
const projection = await recordExecuteCollaborationAdjudicationProjection(projectRoot, adjudication);
|
|
807
|
+
return {
|
|
808
|
+
runId: run.runId,
|
|
809
|
+
branch: context.rawBranch,
|
|
810
|
+
workOrder,
|
|
811
|
+
adjudication,
|
|
812
|
+
projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
|
|
813
|
+
artifactRefs,
|
|
814
|
+
registeredArtifacts,
|
|
815
|
+
registeredCollaborationContracts,
|
|
816
|
+
acceptedExecuteRef: executeAcceptance.acceptedExecuteRef
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
export async function reconcileShipCollaborationClosure(projectRoot, input) {
|
|
820
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
821
|
+
const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
|
|
822
|
+
const decision = {
|
|
823
|
+
...input.decision,
|
|
824
|
+
scope: { ...input.decision.scope, branch: context.rawBranch }
|
|
825
|
+
};
|
|
826
|
+
const run = await createRun(projectRoot, {
|
|
827
|
+
runId: input.runId,
|
|
828
|
+
branch: context.rawBranch,
|
|
829
|
+
lifecycleDecision: runLifecycleDecisionRecord(decision)
|
|
830
|
+
});
|
|
831
|
+
const runRef = { kind: 'run', ref: run.runId };
|
|
832
|
+
const shipRequired = decision.requiredStages.includes('ship');
|
|
833
|
+
const openShipBlockers = shipBlockerCount(decision);
|
|
834
|
+
const blockedReason = openShipBlockers > 0
|
|
835
|
+
? 'Lifecycle risk decision has open ship blockers.'
|
|
836
|
+
: 'Lifecycle risk decision blocks ship closure.';
|
|
837
|
+
const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('ship') || openShipBlockers > 0;
|
|
838
|
+
const truthAlignmentEnvelope = await readTruthAlignmentProjection(projectRoot, decision.scope);
|
|
839
|
+
const workOrderInputRefs = truthAlignmentEnvelope?.payload.acceptedRealityRefs ?? decision.inputRefs;
|
|
840
|
+
const profileRef = { kind: 'projection', ref: `${SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${shipCollaborationScopeKey(decision.scope)}:profile` };
|
|
841
|
+
const workOrder = shipRequired && !blocked ? buildShipStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
|
|
842
|
+
const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'ship', workOrder, input.collaborationContractRefs, generatedAt);
|
|
843
|
+
const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
|
|
844
|
+
const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
|
|
845
|
+
const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'ship', input.artifactRefs, Boolean(workOrder), run.runId, generatedAt);
|
|
846
|
+
const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
|
|
847
|
+
const lifecycleRiskProjectionRef = {
|
|
848
|
+
kind: 'projection',
|
|
849
|
+
ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
|
|
850
|
+
};
|
|
851
|
+
const adjudicationProjectionRef = shipAdjudicationProjectionRef(decision.scope);
|
|
852
|
+
const shipReadiness = await validateAcceptedShipReadinessArtifact(projectRoot, {
|
|
853
|
+
partition: context.partition,
|
|
854
|
+
required: shipRequired,
|
|
855
|
+
blocked,
|
|
856
|
+
blockedReason,
|
|
857
|
+
registeredArtifacts,
|
|
858
|
+
registeredCollaborationContracts,
|
|
859
|
+
truthAlignment: truthAlignmentEnvelope?.payload ?? null
|
|
860
|
+
});
|
|
861
|
+
const health = shipReadiness.shipReadinessStatus === 'accepted'
|
|
862
|
+
? 'ship_ready'
|
|
863
|
+
: !shipRequired
|
|
864
|
+
? 'no-op'
|
|
865
|
+
: blocked
|
|
866
|
+
? 'blocked'
|
|
867
|
+
: 'rejected';
|
|
868
|
+
const truthAlignmentProjectionRef = truthAlignmentEnvelope ? { kind: 'projection', ref: `${TRUTH_ALIGNMENT_PROJECTION_TYPE}:${truthAlignmentScopeKey({ branch: decision.scope.branch })}` } : null;
|
|
869
|
+
const outputRefs = [shipReadiness.acceptedShipReadinessRef, shipReadiness.releaseDocumentRef, truthAlignmentProjectionRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
|
|
870
|
+
const stageRun = buildShipClosureStageRun(decision.scope, run.runId, workOrder, health, {
|
|
871
|
+
outputRefs,
|
|
872
|
+
decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
|
|
873
|
+
inputRefs: truthAlignmentEnvelope?.payload.acceptedRealityRefs ?? decision.inputRefs,
|
|
874
|
+
generatedAt,
|
|
875
|
+
rejectionReason: shipReadiness.rejectionIssue?.explanation ?? null
|
|
876
|
+
});
|
|
877
|
+
const stageRunProjectionRef = {
|
|
878
|
+
kind: 'projection',
|
|
879
|
+
ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
|
|
880
|
+
};
|
|
881
|
+
const rejection = shipReadiness.rejectionIssue
|
|
882
|
+
? buildStageRejection('ship', decision.scope, null, shipReadiness.rejectionIssue.reasonCode, shipReadiness.rejectionIssue.explanation, shipReadiness.rejectionIssue.requiredNextAction, shipReadiness.rejectionIssue.fallbackRoute, generatedAt, true)
|
|
883
|
+
: null;
|
|
884
|
+
const closureRefs = {
|
|
885
|
+
runRef,
|
|
886
|
+
acceptedShipReadinessRef: shipReadiness.acceptedShipReadinessRef,
|
|
887
|
+
shipReadinessStatus: shipReadiness.shipReadinessStatus,
|
|
888
|
+
shipReadinessHash: shipReadiness.shipReadinessHash,
|
|
889
|
+
releaseDocumentRef: shipReadiness.releaseDocumentRef,
|
|
890
|
+
truthAlignmentProjectionRef,
|
|
891
|
+
artifactRefs,
|
|
892
|
+
collaborationContractRef,
|
|
893
|
+
lifecycleRiskProjectionRef,
|
|
894
|
+
adjudicationProjectionRef,
|
|
895
|
+
stageRunProjectionRef,
|
|
896
|
+
reasons: shipReadiness.reasons
|
|
897
|
+
};
|
|
898
|
+
const adjudication = {
|
|
899
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
900
|
+
adjudicationId: stableId('ship-adjudication', decision.scope, health, generatedAt),
|
|
901
|
+
stage: 'ship',
|
|
902
|
+
scope: decision.scope,
|
|
903
|
+
health,
|
|
904
|
+
stageDecision: {
|
|
905
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
906
|
+
decisionId: stableId('ship-stage-decision', decision.scope, run.runId, generatedAt),
|
|
907
|
+
stage: 'ship',
|
|
908
|
+
scope: decision.scope,
|
|
909
|
+
status: health === 'ship_ready' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
|
|
910
|
+
health,
|
|
911
|
+
acceptedDecisionRefs: outputRefs,
|
|
912
|
+
advisoryRefs: [],
|
|
913
|
+
capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('ship') || ref.ref.includes('release') || ref.ref.includes('capability')),
|
|
914
|
+
blockingReasons: health === 'ship_ready' || health === 'no-op' ? [] : shipReadiness.reasons,
|
|
915
|
+
createdAt: generatedAt
|
|
916
|
+
},
|
|
917
|
+
handoffPacket: null,
|
|
918
|
+
rejection,
|
|
919
|
+
nextActions: buildStageRuntimeNextActions('ship', decision.scope, generatedAt, health === 'ship_ready' ? ['stage_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
|
|
920
|
+
closureRefs,
|
|
921
|
+
createdAt: generatedAt
|
|
922
|
+
};
|
|
923
|
+
await recordLifecycleRiskDecisionProjection(projectRoot, decision);
|
|
924
|
+
await recordStageRunProjection(projectRoot, stageRun);
|
|
925
|
+
const projection = await recordShipCollaborationAdjudicationProjection(projectRoot, adjudication);
|
|
926
|
+
return {
|
|
927
|
+
runId: run.runId,
|
|
928
|
+
branch: context.rawBranch,
|
|
929
|
+
workOrder,
|
|
930
|
+
adjudication,
|
|
931
|
+
projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
|
|
932
|
+
artifactRefs,
|
|
933
|
+
registeredArtifacts,
|
|
934
|
+
registeredCollaborationContracts,
|
|
935
|
+
acceptedShipReadinessRef: shipReadiness.acceptedShipReadinessRef,
|
|
936
|
+
releaseDocumentRef: shipReadiness.releaseDocumentRef
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
const FULL_STAGE_CHAIN = [
|
|
940
|
+
{ stage: 'spec', expectedHealth: 'ready_for_plan', projectionType: SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
|
|
941
|
+
{ stage: 'plan', expectedHealth: 'ready_for_tasks', projectionType: PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
|
|
942
|
+
{ stage: 'tasks', expectedHealth: 'ready_for_execute', projectionType: TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
|
|
943
|
+
{ stage: 'execute', expectedHealth: 'ready_for_ship', projectionType: EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
|
|
944
|
+
{ stage: 'ship', expectedHealth: 'ship_ready', projectionType: SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE }
|
|
945
|
+
];
|
|
946
|
+
export async function inspectFullStageChain(projectRoot, branch) {
|
|
947
|
+
const projections = await listRuntimeProjections(projectRoot, FULL_STAGE_CHAIN.map((item) => item.projectionType));
|
|
948
|
+
const envelopes = projections
|
|
949
|
+
.map((projection) => projection.payload)
|
|
950
|
+
.filter((envelope) => envelope?.payload?.scope?.branch === branch);
|
|
951
|
+
const stages = FULL_STAGE_CHAIN.map((item) => fullStageChainStage(item, latestStageChainEnvelope(envelopes, item.projectionType, item.stage)));
|
|
952
|
+
const completedStages = stages.filter((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth).length;
|
|
953
|
+
const validatedCollaborationContracts = stages.filter((stage) => stage.collaborationContractStatus === 'validated').length;
|
|
954
|
+
const handoffs = stages.filter((stage) => stage.handoffProjectionRef !== null).length;
|
|
955
|
+
const status = fullStageChainStatus(stages);
|
|
956
|
+
return {
|
|
957
|
+
contract: 'sdd-full-stage-chain-diagnostic-v1',
|
|
958
|
+
branch,
|
|
959
|
+
status,
|
|
960
|
+
stages,
|
|
961
|
+
projectionCounts: {
|
|
962
|
+
adjudications: stages.filter((stage) => stage.projectionRef !== null).length,
|
|
963
|
+
completedStages,
|
|
964
|
+
validatedCollaborationContracts,
|
|
965
|
+
handoffs
|
|
966
|
+
},
|
|
967
|
+
finalHealth: fullStageChainFinalHealth(stages),
|
|
968
|
+
reasons: fullStageChainReasons(status, stages)
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
async function collectTruthAlignmentDeclaredTruthRefs(projectRoot, branch, testHandoff) {
|
|
972
|
+
const projectionTypes = FULL_STAGE_CHAIN
|
|
973
|
+
.filter((item) => item.stage !== 'ship')
|
|
974
|
+
.map((item) => item.projectionType);
|
|
975
|
+
const projections = await listRuntimeProjections(projectRoot, projectionTypes);
|
|
976
|
+
const envelopes = projections
|
|
977
|
+
.map((projection) => projection.payload)
|
|
978
|
+
.filter((envelope) => envelope?.payload?.scope?.branch === branch);
|
|
979
|
+
const acceptedRefs = FULL_STAGE_CHAIN
|
|
980
|
+
.filter((item) => item.stage !== 'ship')
|
|
981
|
+
.map((item) => latestStageChainEnvelope(envelopes, item.projectionType, item.stage))
|
|
982
|
+
.map((envelope) => envelope ? acceptedRefFromAdjudication(envelope.payload.stage, envelope.payload) : null)
|
|
983
|
+
.filter((ref) => ref !== null);
|
|
984
|
+
return uniqueRuntimeRefs([...acceptedRefs, ...(testHandoff?.requiredInputRefs ?? [])]);
|
|
985
|
+
}
|
|
986
|
+
function latestStageChainEnvelope(envelopes, projectionType, stage) {
|
|
987
|
+
return envelopes
|
|
988
|
+
.filter((envelope) => envelope.projectionType === projectionType && envelope.payload.stage === stage)
|
|
989
|
+
.sort((left, right) => right.generatedAt.localeCompare(left.generatedAt))[0] ?? null;
|
|
990
|
+
}
|
|
991
|
+
function fullStageChainStage(item, envelope) {
|
|
992
|
+
const adjudication = envelope?.payload ?? null;
|
|
993
|
+
const closureRefs = (adjudication?.closureRefs ?? {});
|
|
994
|
+
const collaborationContractRef = runtimeRefValue(closureRefs.collaborationContractRef);
|
|
995
|
+
const handoff = adjudication?.handoffPacket ? {
|
|
996
|
+
fromStage: adjudication.handoffPacket.fromStage,
|
|
997
|
+
toStage: adjudication.handoffPacket.toStage,
|
|
998
|
+
status: adjudication.handoffPacket.status
|
|
999
|
+
} : null;
|
|
1000
|
+
const acceptedRef = adjudication ? acceptedRefFromAdjudication(item.stage, adjudication) : null;
|
|
1001
|
+
return {
|
|
1002
|
+
stage: item.stage,
|
|
1003
|
+
expectedHealth: item.expectedHealth,
|
|
1004
|
+
projectionType: item.projectionType,
|
|
1005
|
+
projectionRef: envelope ? { kind: 'projection', ref: `${envelope.projectionType}:${envelope.scopeKey}` } : null,
|
|
1006
|
+
health: adjudication?.health ?? null,
|
|
1007
|
+
status: adjudication?.stageDecision?.status ?? null,
|
|
1008
|
+
acceptedRef,
|
|
1009
|
+
acceptedHash: acceptedRef?.hash ?? null,
|
|
1010
|
+
collaborationContractRef,
|
|
1011
|
+
collaborationContractStatus: collaborationContractRef ? 'validated' : 'missing',
|
|
1012
|
+
handoff,
|
|
1013
|
+
handoffProjectionRef: runtimeRefValue(closureRefs.workflowHandoffProjectionRef),
|
|
1014
|
+
reasons: Array.isArray(closureRefs.reasons) ? closureRefs.reasons.filter((reason) => typeof reason === 'string') : adjudication?.rejection ? [adjudication.rejection.explanation] : []
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function acceptedRefFromAdjudication(stage, adjudication) {
|
|
1018
|
+
const closureRefs = (adjudication.closureRefs ?? {});
|
|
1019
|
+
const keyByStage = {
|
|
1020
|
+
spec: 'acceptedSpecRef',
|
|
1021
|
+
plan: 'acceptedPlanRef',
|
|
1022
|
+
tasks: 'acceptedTasksRef',
|
|
1023
|
+
execute: 'acceptedExecuteRef',
|
|
1024
|
+
ship: 'acceptedShipReadinessRef'
|
|
1025
|
+
};
|
|
1026
|
+
return runtimeRefValue(closureRefs[keyByStage[stage]]) ?? adjudication.stageDecision?.acceptedDecisionRefs[0] ?? null;
|
|
1027
|
+
}
|
|
1028
|
+
function runtimeRefValue(value) {
|
|
1029
|
+
if (!value || typeof value !== 'object') {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
const candidate = value;
|
|
1033
|
+
if (typeof candidate.kind !== 'string' || typeof candidate.ref !== 'string') {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
kind: candidate.kind,
|
|
1038
|
+
ref: candidate.ref,
|
|
1039
|
+
hash: typeof candidate.hash === 'string' ? candidate.hash : undefined
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function fullStageChainFinalHealth(stages) {
|
|
1043
|
+
return [...stages]
|
|
1044
|
+
.reverse()
|
|
1045
|
+
.find((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth)?.health ?? null;
|
|
1046
|
+
}
|
|
1047
|
+
function fullStageChainStatus(stages) {
|
|
1048
|
+
if (stages.every((stage) => stage.projectionRef === null)) {
|
|
1049
|
+
return 'missing';
|
|
1050
|
+
}
|
|
1051
|
+
if (stages.some((stage) => stage.status === 'rejected' || stage.health === 'rejected')) {
|
|
1052
|
+
return 'rejected';
|
|
1053
|
+
}
|
|
1054
|
+
if (stages.some((stage) => stage.status === 'blocked' || stage.health === 'blocked')) {
|
|
1055
|
+
return 'blocked';
|
|
1056
|
+
}
|
|
1057
|
+
if (stages.every((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth && stage.collaborationContractStatus === 'validated')) {
|
|
1058
|
+
return 'ready';
|
|
1059
|
+
}
|
|
1060
|
+
return 'partial';
|
|
1061
|
+
}
|
|
1062
|
+
function fullStageChainReasons(status, stages) {
|
|
1063
|
+
if (status === 'missing') {
|
|
1064
|
+
return ['No Phase 9 stage collaboration adjudication projections found.'];
|
|
1065
|
+
}
|
|
1066
|
+
if (status === 'ready') {
|
|
1067
|
+
return ['Active Phase 9 stage chain is closed through ship_ready with validated collaboration contracts.'];
|
|
1068
|
+
}
|
|
1069
|
+
return stages
|
|
1070
|
+
.filter((stage) => stage.status !== 'completed' || stage.health !== stage.expectedHealth || stage.collaborationContractStatus !== 'validated')
|
|
1071
|
+
.map((stage) => `${stage.stage} is ${stage.health ?? 'missing'}; expected ${stage.expectedHealth}; collaboration_contract=${stage.collaborationContractStatus}.`);
|
|
1072
|
+
}
|
|
1073
|
+
export async function inspectSpecCollaborationHealth(projectRoot, branch) {
|
|
1074
|
+
const projections = await listRuntimeProjections(projectRoot, [SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE]);
|
|
1075
|
+
const envelopes = projections
|
|
1076
|
+
.map((projection) => projection.payload)
|
|
1077
|
+
.filter((envelope) => envelope?.payload?.scope?.branch === branch);
|
|
1078
|
+
const latest = envelopes[0]?.payload ?? null;
|
|
1079
|
+
if (!latest) {
|
|
1080
|
+
return {
|
|
1081
|
+
status: 'missing',
|
|
1082
|
+
branch,
|
|
1083
|
+
latest: null,
|
|
1084
|
+
projectionCount: envelopes.length,
|
|
1085
|
+
latestClarificationGateId: null,
|
|
1086
|
+
latestHandoffId: null,
|
|
1087
|
+
latestRejectionReason: null,
|
|
1088
|
+
reasons: ['No spec collaboration adjudication projection found.']
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
if (latest.contract !== STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION) {
|
|
1092
|
+
return {
|
|
1093
|
+
status: 'incompatible',
|
|
1094
|
+
branch,
|
|
1095
|
+
latest,
|
|
1096
|
+
projectionCount: envelopes.length,
|
|
1097
|
+
latestClarificationGateId: latest.clarificationGate?.gateId ?? null,
|
|
1098
|
+
latestHandoffId: latest.handoffPacket?.handoffId ?? null,
|
|
1099
|
+
latestRejectionReason: latest.rejection?.reasonCode ?? null,
|
|
1100
|
+
reasons: ['Latest spec collaboration projection has an incompatible contract.']
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
return {
|
|
1104
|
+
status: latest.health,
|
|
1105
|
+
branch,
|
|
1106
|
+
latest,
|
|
1107
|
+
projectionCount: envelopes.length,
|
|
1108
|
+
latestClarificationGateId: latest.clarificationGate?.gateId ?? null,
|
|
1109
|
+
latestHandoffId: latest.handoffPacket?.handoffId ?? null,
|
|
1110
|
+
latestRejectionReason: latest.rejection?.reasonCode ?? null,
|
|
1111
|
+
reasons: specCollaborationDiagnosticReasons(latest)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
export function adjudicateSpecStageClosureRequest(input) {
|
|
1115
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
1116
|
+
if (input.profile.intensity === 'blocked') {
|
|
1117
|
+
return rejected(input, 'blocked_lifecycle', 'Lifecycle risk decision blocks spec collaboration.', 'Resolve lifecycle blockers before retrying spec collaboration.', 'runtime-blocked', generatedAt, false);
|
|
1118
|
+
}
|
|
1119
|
+
if (!input.profile.required) {
|
|
1120
|
+
return noOpResult(input.profile, generatedAt);
|
|
1121
|
+
}
|
|
1122
|
+
const closureIssue = validateStageClosureRequest(input);
|
|
1123
|
+
if (closureIssue) {
|
|
1124
|
+
return rejected(input, closureIssue.reasonCode, closureIssue.explanation, closureIssue.requiredNextAction, closureIssue.fallbackRoute, generatedAt, closureIssue.retryAllowed);
|
|
1125
|
+
}
|
|
1126
|
+
const closureRequest = input.closureRequest;
|
|
1127
|
+
const acceptedItems = closureRequest.candidate?.acceptedItems ?? [];
|
|
1128
|
+
const blockingItems = acceptedItems.filter((item) => item.kind === 'blocking_ambiguity' || closureRequest.unresolvedAmbiguityIds.includes(item.id));
|
|
1129
|
+
if (blockingItems.length > 0 && (input.answerRefs ?? []).length === 0) {
|
|
1130
|
+
const clarificationGate = {
|
|
1131
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1132
|
+
gateId: stableId('spec-clarification', input.profile.scope, closureRequest.closureRequestId, generatedAt),
|
|
1133
|
+
stage: 'spec',
|
|
1134
|
+
scope: input.profile.scope,
|
|
1135
|
+
status: 'needs_clarification',
|
|
1136
|
+
questions: blockingItems.map((item) => item.summary),
|
|
1137
|
+
blockingItemIds: blockingItems.map((item) => item.id),
|
|
1138
|
+
requiredResponder: 'user',
|
|
1139
|
+
createdAt: generatedAt,
|
|
1140
|
+
answerRefs: []
|
|
1141
|
+
};
|
|
1142
|
+
return baseResult(input.profile, generatedAt, {
|
|
1143
|
+
health: 'needs_clarification',
|
|
1144
|
+
acceptedItemIds: acceptedItems.filter((item) => item.kind !== 'blocking_ambiguity').map((item) => item.id),
|
|
1145
|
+
clarificationGate,
|
|
1146
|
+
nextActions: buildRuntimeNextActions(input.profile, generatedAt, ['unresolved_ambiguity'])
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
const acceptedItemIds = acceptedItems.map((item) => item.id);
|
|
1150
|
+
const decisionRecord = buildSpecDecisionRecord(input.profile, closureRequest, input.answerRefs ?? [], acceptedItemIds, generatedAt);
|
|
1151
|
+
const stageDecision = buildStageRuntimeDecision(input.profile, closureRequest, decisionRecord, generatedAt);
|
|
1152
|
+
const handoffPacket = buildStageHandoffPacket(input.profile, closureRequest, decisionRecord, generatedAt);
|
|
1153
|
+
return baseResult(input.profile, generatedAt, {
|
|
1154
|
+
health: 'ready_for_plan',
|
|
1155
|
+
acceptedItemIds,
|
|
1156
|
+
specDecisionRecord: decisionRecord,
|
|
1157
|
+
stageDecision,
|
|
1158
|
+
handoffPacket,
|
|
1159
|
+
nextActions: buildRuntimeNextActions(input.profile, generatedAt, ['stage_ready', 'handoff_ready'])
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function planMemberAgents(intensity) {
|
|
1163
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1164
|
+
return [];
|
|
1165
|
+
}
|
|
1166
|
+
if (intensity === 'lightweight') {
|
|
1167
|
+
return [PLAN_STAGE_REVIEW_AGENT];
|
|
1168
|
+
}
|
|
1169
|
+
return [PLAN_STAGE_SCOUT_AGENT, PLAN_STAGE_REVIEW_AGENT];
|
|
1170
|
+
}
|
|
1171
|
+
function planScoutDomains(intensity) {
|
|
1172
|
+
if (intensity === 'noop' || intensity === 'blocked' || intensity === 'lightweight') {
|
|
1173
|
+
return [];
|
|
1174
|
+
}
|
|
1175
|
+
if (intensity === 'scout-first') {
|
|
1176
|
+
return ['architecture-runtime', 'testing-validation'];
|
|
1177
|
+
}
|
|
1178
|
+
return ['architecture-runtime', 'backend-api', 'database-migration', 'security', 'testing-validation'];
|
|
1179
|
+
}
|
|
1180
|
+
function planRequiredOutputKinds(intensity) {
|
|
1181
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1182
|
+
return [];
|
|
1183
|
+
}
|
|
1184
|
+
if (intensity === 'lightweight') {
|
|
1185
|
+
return ['plan_review', 'manager_closure_request'];
|
|
1186
|
+
}
|
|
1187
|
+
return ['plan_context', 'plan_review', 'manager_closure_request'];
|
|
1188
|
+
}
|
|
1189
|
+
function planRequiredCapabilities(intensity) {
|
|
1190
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1191
|
+
return [];
|
|
1192
|
+
}
|
|
1193
|
+
if (intensity === 'lightweight') {
|
|
1194
|
+
return ['plan-strategy', 'plan-pressure-review'];
|
|
1195
|
+
}
|
|
1196
|
+
return ['plan-strategy', 'plan-pressure-review', ...planScoutDomains(intensity)];
|
|
1197
|
+
}
|
|
1198
|
+
function planOptionalCapabilities(intensity) {
|
|
1199
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1200
|
+
return [];
|
|
1201
|
+
}
|
|
1202
|
+
return PLAN_SCOUT_DOMAINS.filter((domain) => !planScoutDomains(intensity).includes(domain));
|
|
1203
|
+
}
|
|
1204
|
+
function planMaterialPackIds(intensity) {
|
|
1205
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1206
|
+
return [];
|
|
1207
|
+
}
|
|
1208
|
+
if (intensity === 'lightweight') {
|
|
1209
|
+
return ['project-norms', 'plan-section-rubric'];
|
|
1210
|
+
}
|
|
1211
|
+
return [...PLAN_STAGE_MATERIAL_PACKS];
|
|
1212
|
+
}
|
|
1213
|
+
function planCollaborationPlan(intensity) {
|
|
1214
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1215
|
+
return { topology: 'none', participants: [], maxParallelism: 0, fanIn: 'runtime_adjudication' };
|
|
1216
|
+
}
|
|
1217
|
+
const requiredCapabilities = planRequiredCapabilities(intensity);
|
|
1218
|
+
const optionalCapabilities = planOptionalCapabilities(intensity);
|
|
1219
|
+
const materialPackIds = planMaterialPackIds(intensity);
|
|
1220
|
+
const participants = [
|
|
1221
|
+
{ id: PLAN_STAGE_MANAGER, kind: 'agent', role: 'plan-stage-manager', required: true, capabilityDomain: 'plan-strategy', parallelGroup: null },
|
|
1222
|
+
...planMemberAgents(intensity).map((agent) => ({
|
|
1223
|
+
id: agent,
|
|
1224
|
+
kind: 'subagent',
|
|
1225
|
+
role: planAgentRole(agent),
|
|
1226
|
+
required: agent === PLAN_STAGE_REVIEW_AGENT,
|
|
1227
|
+
capabilityDomain: planAgentCapability(agent),
|
|
1228
|
+
parallelGroup: planAgentParallelGroup(agent)
|
|
1229
|
+
})),
|
|
1230
|
+
...requiredCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'required-capability-review', required: true, capabilityDomain: capability, parallelGroup: 'capability-review' })),
|
|
1231
|
+
...optionalCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'optional-capability-review', required: false, capabilityDomain: capability, parallelGroup: 'capability-review' })),
|
|
1232
|
+
...materialPackIds.map((packId) => ({ id: packId, kind: 'material-pack', role: 'capability-material', required: false, parallelGroup: 'material-pack' }))
|
|
1233
|
+
];
|
|
1234
|
+
const topology = intensity === 'lightweight' ? 'team-lite' : intensity === 'scout-first' ? 'parallel-research' : 'team-required';
|
|
1235
|
+
return { topology, participants, maxParallelism: topology === 'team-lite' ? 2 : 3, fanIn: 'runtime_adjudication' };
|
|
1236
|
+
}
|
|
1237
|
+
function planAgentRole(agent) {
|
|
1238
|
+
switch (agent) {
|
|
1239
|
+
case PLAN_STAGE_SCOUT_AGENT:
|
|
1240
|
+
return 'domain-evidence-scout';
|
|
1241
|
+
case PLAN_STAGE_REVIEW_AGENT:
|
|
1242
|
+
return 'plan-pressure-reviewer';
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function planAgentCapability(agent) {
|
|
1246
|
+
switch (agent) {
|
|
1247
|
+
case PLAN_STAGE_SCOUT_AGENT:
|
|
1248
|
+
return 'architecture-runtime';
|
|
1249
|
+
case PLAN_STAGE_REVIEW_AGENT:
|
|
1250
|
+
return 'plan-pressure-review';
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function planAgentParallelGroup(agent) {
|
|
1254
|
+
switch (agent) {
|
|
1255
|
+
case PLAN_STAGE_SCOUT_AGENT:
|
|
1256
|
+
return 'domain-scouting';
|
|
1257
|
+
case PLAN_STAGE_REVIEW_AGENT:
|
|
1258
|
+
return 'review';
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function specIntensity(decision, required) {
|
|
1262
|
+
if (decision.profile === 'blocked' || decision.blockedStages.includes('spec')) {
|
|
1263
|
+
return 'blocked';
|
|
1264
|
+
}
|
|
1265
|
+
if (!required || decision.profile === 'direct') {
|
|
1266
|
+
return 'noop';
|
|
1267
|
+
}
|
|
1268
|
+
if (decision.profile === 'compact') {
|
|
1269
|
+
return 'lightweight';
|
|
1270
|
+
}
|
|
1271
|
+
if (decision.profile === 'research') {
|
|
1272
|
+
return 'scout-first';
|
|
1273
|
+
}
|
|
1274
|
+
return 'team-required';
|
|
1275
|
+
}
|
|
1276
|
+
function specProfileReasons(decision, required, intensity) {
|
|
1277
|
+
if (intensity === 'blocked') {
|
|
1278
|
+
return ['Lifecycle risk decision blocks spec collaboration.', ...decision.reasons];
|
|
1279
|
+
}
|
|
1280
|
+
if (!required) {
|
|
1281
|
+
return ['Spec stage is not required by lifecycle risk decision.', ...decision.reasons];
|
|
1282
|
+
}
|
|
1283
|
+
if (intensity === 'scout-first') {
|
|
1284
|
+
return ['Research profile requires spec-stage uncertainty discovery before plan handoff.', ...decision.reasons];
|
|
1285
|
+
}
|
|
1286
|
+
if (intensity === 'team-required') {
|
|
1287
|
+
return ['Full lifecycle requires runtime-adjudicated spec collaboration before plan handoff.', ...decision.reasons];
|
|
1288
|
+
}
|
|
1289
|
+
return ['Spec collaboration can remain lightweight for this lifecycle decision.', ...decision.reasons];
|
|
1290
|
+
}
|
|
1291
|
+
function specMemberAgents(intensity) {
|
|
1292
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1293
|
+
return [];
|
|
1294
|
+
}
|
|
1295
|
+
if (intensity === 'lightweight') {
|
|
1296
|
+
return [SPEC_STAGE_REVIEW_AGENT];
|
|
1297
|
+
}
|
|
1298
|
+
return [SPEC_STAGE_SCOUT_AGENT, SPEC_STAGE_REVIEW_AGENT];
|
|
1299
|
+
}
|
|
1300
|
+
function specRequiredOutputKinds(intensity) {
|
|
1301
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1302
|
+
return [];
|
|
1303
|
+
}
|
|
1304
|
+
if (intensity === 'lightweight') {
|
|
1305
|
+
return ['spec_review', 'manager_closure_request'];
|
|
1306
|
+
}
|
|
1307
|
+
return ['scout_context', 'spec_review', 'manager_closure_request'];
|
|
1308
|
+
}
|
|
1309
|
+
function specRequiredCapabilities(intensity) {
|
|
1310
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
if (intensity === 'lightweight') {
|
|
1314
|
+
return ['norm_discovery'];
|
|
1315
|
+
}
|
|
1316
|
+
return SPEC_STAGE_REQUIRED_CAPABILITIES;
|
|
1317
|
+
}
|
|
1318
|
+
function specOptionalCapabilities(intensity) {
|
|
1319
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
if (intensity === 'lightweight') {
|
|
1323
|
+
return ['uncertainty_resolution', 'context_curation'];
|
|
1324
|
+
}
|
|
1325
|
+
return SPEC_STAGE_OPTIONAL_CAPABILITIES;
|
|
1326
|
+
}
|
|
1327
|
+
function specMaterialPackIds(intensity) {
|
|
1328
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1329
|
+
return [];
|
|
1330
|
+
}
|
|
1331
|
+
if (intensity === 'lightweight') {
|
|
1332
|
+
return ['project-norms', 'uncertainty-map'];
|
|
1333
|
+
}
|
|
1334
|
+
return [...SPEC_STAGE_MATERIAL_PACKS];
|
|
1335
|
+
}
|
|
1336
|
+
function specCollaborationPlan(intensity) {
|
|
1337
|
+
if (intensity === 'noop' || intensity === 'blocked') {
|
|
1338
|
+
return { topology: 'none', participants: [], maxParallelism: 0, fanIn: 'runtime_adjudication' };
|
|
1339
|
+
}
|
|
1340
|
+
const requiredCapabilities = specRequiredCapabilities(intensity);
|
|
1341
|
+
const optionalCapabilities = specOptionalCapabilities(intensity);
|
|
1342
|
+
const materialPackIds = specMaterialPackIds(intensity);
|
|
1343
|
+
const participants = [
|
|
1344
|
+
{ id: SPEC_STAGE_MANAGER, kind: 'agent', role: 'spec-stage-manager', required: true, capabilityDomain: 'uncertainty_resolution', parallelGroup: null },
|
|
1345
|
+
...specMemberAgents(intensity).map((agent) => ({
|
|
1346
|
+
id: agent,
|
|
1347
|
+
kind: 'subagent',
|
|
1348
|
+
role: agent === SPEC_STAGE_SCOUT_AGENT ? 'bounded-context-scout' : 'spec-document-reviewer',
|
|
1349
|
+
required: true,
|
|
1350
|
+
capabilityDomain: agent === SPEC_STAGE_SCOUT_AGENT ? 'context_curation' : 'uncertainty_resolution',
|
|
1351
|
+
parallelGroup: agent === SPEC_STAGE_SCOUT_AGENT ? 'discovery' : 'review'
|
|
1352
|
+
})),
|
|
1353
|
+
...requiredCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'required-capability-review', required: true, capabilityDomain: capability, parallelGroup: 'capability-review' })),
|
|
1354
|
+
...optionalCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'optional-capability-review', required: false, capabilityDomain: capability, parallelGroup: 'capability-review' })),
|
|
1355
|
+
...materialPackIds.map((packId) => ({ id: packId, kind: 'material-pack', role: 'capability-material', required: false, parallelGroup: 'material-pack' }))
|
|
1356
|
+
];
|
|
1357
|
+
const topology = intensity === 'lightweight' ? 'team-lite' : intensity === 'scout-first' ? 'parallel-research' : 'team-required';
|
|
1358
|
+
return { topology, participants, maxParallelism: topology === 'team-lite' ? 2 : 4, fanIn: 'runtime_adjudication' };
|
|
1359
|
+
}
|
|
1360
|
+
function validateStageClosureRequest(input) {
|
|
1361
|
+
const request = input.closureRequest;
|
|
1362
|
+
if (!request) {
|
|
1363
|
+
return { reasonCode: 'invalid_proposal', explanation: 'Spec stage closure requires a StageClosureRequest from spec-manager.', requiredNextAction: 'Submit spec-manager closure request with final spec ref/hash and runtime close boundary facts.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1364
|
+
}
|
|
1365
|
+
if (request.stage !== 'spec') {
|
|
1366
|
+
return { reasonCode: 'unsupported_stage', explanation: `Spec runtime cannot close ${request.stage} closure requests.`, requiredNextAction: 'Route the closure request to the matching stage runtime.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1367
|
+
}
|
|
1368
|
+
if (request.authorityAttempts.length > 0) {
|
|
1369
|
+
return { reasonCode: 'authority_violation', explanation: `Spec stage closure attempted workflow authority: ${request.authorityAttempts.join(', ')}.`, requiredNextAction: 'Remove workflow-authority attempts from manager and agent-team outputs.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1370
|
+
}
|
|
1371
|
+
if (request.candidate && request.candidate.stage !== 'spec') {
|
|
1372
|
+
return { reasonCode: 'unsupported_stage', explanation: `Spec runtime cannot close ${request.candidate.stage} candidates.`, requiredNextAction: 'Route the candidate to the matching stage runtime.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1373
|
+
}
|
|
1374
|
+
const unsupported = request.candidate?.acceptedItems.find((item) => !input.profile.allowedProposalKinds.includes(item.kind));
|
|
1375
|
+
if (unsupported) {
|
|
1376
|
+
return { reasonCode: 'invalid_proposal', explanation: `Spec candidate item ${unsupported.id} uses unsupported kind ${unsupported.kind}.`, requiredNextAction: 'Submit only allowed spec candidate item kinds for this profile.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1377
|
+
}
|
|
1378
|
+
const missingItemRef = request.candidate?.acceptedItems.find((item) => item.refs.length === 0);
|
|
1379
|
+
if (missingItemRef) {
|
|
1380
|
+
return { reasonCode: 'missing_refs', explanation: `Spec candidate item ${missingItemRef.id} has no refs.`, requiredNextAction: 'Attach source refs to every candidate item.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1381
|
+
}
|
|
1382
|
+
if (request.managerRecommendation === 'close_stage' && !request.runtimeCloseBoundaryFacts) {
|
|
1383
|
+
return { reasonCode: 'missing_refs', explanation: 'Spec stage close request is missing runtime close boundary facts for the final spec.md V3.', requiredNextAction: 'Ask spec-manager to submit finalSpecRef/finalSpecHash, section close declaration, review signal presence, and blockingBeforePlanCount=0.', fallbackRoute: 'revise-proposal', retryAllowed: true };
|
|
1384
|
+
}
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
function noOpResult(profile, generatedAt) {
|
|
1388
|
+
const decisionRecord = {
|
|
1389
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1390
|
+
decisionId: stableId('spec-decision', profile.scope, 'noop', generatedAt),
|
|
1391
|
+
stage: 'spec',
|
|
1392
|
+
scope: profile.scope,
|
|
1393
|
+
source: 'runtime-noop',
|
|
1394
|
+
acceptedItemIds: [],
|
|
1395
|
+
answerRefs: [],
|
|
1396
|
+
decisionRefs: profile.inputRefs,
|
|
1397
|
+
mustNotAssume: [],
|
|
1398
|
+
createdAt: generatedAt
|
|
1399
|
+
};
|
|
1400
|
+
const stageDecision = {
|
|
1401
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1402
|
+
decisionId: stableId('spec-stage-decision', profile.scope, 'noop', generatedAt),
|
|
1403
|
+
stage: 'spec',
|
|
1404
|
+
scope: profile.scope,
|
|
1405
|
+
status: 'skipped',
|
|
1406
|
+
health: 'no-op',
|
|
1407
|
+
acceptedDecisionRefs: decisionRecord.decisionRefs,
|
|
1408
|
+
advisoryRefs: [],
|
|
1409
|
+
capabilityRefs: [],
|
|
1410
|
+
blockingReasons: [],
|
|
1411
|
+
createdAt: generatedAt
|
|
1412
|
+
};
|
|
1413
|
+
return baseResult(profile, generatedAt, { health: 'no-op', specDecisionRecord: decisionRecord, stageDecision });
|
|
1414
|
+
}
|
|
1415
|
+
function buildSpecDecisionRecord(profile, closureRequest, answerRefs, acceptedItemIds, generatedAt) {
|
|
1416
|
+
return {
|
|
1417
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1418
|
+
decisionId: stableId('spec-decision', profile.scope, closureRequest.closureRequestId, generatedAt),
|
|
1419
|
+
stage: 'spec',
|
|
1420
|
+
scope: profile.scope,
|
|
1421
|
+
source: answerRefs.length > 0 ? 'clarification-answer' : 'stage-closure-request',
|
|
1422
|
+
acceptedItemIds,
|
|
1423
|
+
answerRefs,
|
|
1424
|
+
decisionRefs: closureDecisionRefs(closureRequest, answerRefs),
|
|
1425
|
+
mustNotAssume: closureRequest.candidate?.acceptedItems.filter((item) => item.kind === 'blocking_ambiguity').map((item) => item.summary) ?? [],
|
|
1426
|
+
createdAt: generatedAt
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
function buildStageRuntimeDecision(profile, closureRequest, decisionRecord, generatedAt) {
|
|
1430
|
+
return {
|
|
1431
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1432
|
+
decisionId: stableId('spec-stage-decision', profile.scope, closureRequest.closureRequestId, generatedAt),
|
|
1433
|
+
stage: 'spec',
|
|
1434
|
+
scope: profile.scope,
|
|
1435
|
+
status: 'completed',
|
|
1436
|
+
health: 'ready_for_plan',
|
|
1437
|
+
acceptedDecisionRefs: decisionRecord.decisionRefs,
|
|
1438
|
+
advisoryRefs: refsByCandidateKind(closureRequest, 'advisory_finding'),
|
|
1439
|
+
capabilityRefs: refsByCandidateKind(closureRequest, 'capability_suggestion'),
|
|
1440
|
+
blockingReasons: [],
|
|
1441
|
+
createdAt: generatedAt
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
function buildStageHandoffPacket(profile, closureRequest, decisionRecord, generatedAt) {
|
|
1445
|
+
return {
|
|
1446
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1447
|
+
handoffId: stableId('spec-plan-handoff', profile.scope, closureRequest.closureRequestId, generatedAt),
|
|
1448
|
+
scope: profile.scope,
|
|
1449
|
+
fromStage: 'spec',
|
|
1450
|
+
toStage: 'plan',
|
|
1451
|
+
status: 'proposed',
|
|
1452
|
+
acceptedSpecDecisionRefs: decisionRecord.decisionRefs,
|
|
1453
|
+
advisoryRefs: refsByCandidateKind(closureRequest, 'advisory_finding'),
|
|
1454
|
+
capabilityRefs: refsByCandidateKind(closureRequest, 'capability_suggestion'),
|
|
1455
|
+
unresolvedRisks: [],
|
|
1456
|
+
mustNotAssume: decisionRecord.mustNotAssume,
|
|
1457
|
+
recommendedNextStageRoles: recommendedPlanRoles(profile),
|
|
1458
|
+
createdAt: generatedAt
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function refsByCandidateKind(closureRequest, kind) {
|
|
1462
|
+
return closureRequest.candidate?.acceptedItems.filter((item) => item.kind === kind).flatMap((item) => item.refs) ?? [];
|
|
1463
|
+
}
|
|
1464
|
+
function closureDecisionRefs(closureRequest, answerRefs) {
|
|
1465
|
+
return [
|
|
1466
|
+
closureRequest.workOrderRef,
|
|
1467
|
+
closureRequest.coordinationRef,
|
|
1468
|
+
closureRequest.candidateRef,
|
|
1469
|
+
...closureRequest.reviewRefs,
|
|
1470
|
+
...closureRequest.capabilityFindingRefs,
|
|
1471
|
+
...closureRequest.evidenceRefs,
|
|
1472
|
+
...answerRefs
|
|
1473
|
+
].filter((ref) => ref !== null);
|
|
1474
|
+
}
|
|
1475
|
+
function recommendedPlanRoles(profile) {
|
|
1476
|
+
if (profile.intensity === 'scout-first') {
|
|
1477
|
+
return ['role.norm-scout', 'role.uncertainty-reviewer'];
|
|
1478
|
+
}
|
|
1479
|
+
if (profile.intensity === 'team-required') {
|
|
1480
|
+
return ['role.norm-scout', 'role.performance-planner', 'role.verification-designer'];
|
|
1481
|
+
}
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1484
|
+
function rejected(input, reasonCode, explanation, requiredNextAction, fallbackRoute, generatedAt, retryAllowed) {
|
|
1485
|
+
const closureRequestId = input.closureRequest?.closureRequestId ?? null;
|
|
1486
|
+
const rejection = {
|
|
1487
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1488
|
+
rejectionId: stableId('spec-rejection', input.profile.scope, closureRequestId ?? reasonCode, generatedAt),
|
|
1489
|
+
stage: 'spec',
|
|
1490
|
+
scope: input.profile.scope,
|
|
1491
|
+
closureRequestId,
|
|
1492
|
+
reasonCode,
|
|
1493
|
+
explanation,
|
|
1494
|
+
requiredNextAction,
|
|
1495
|
+
retryAllowed,
|
|
1496
|
+
retryBudgetRemaining: retryAllowed ? input.retryBudgetRemaining ?? 1 : 0,
|
|
1497
|
+
fallbackRoute,
|
|
1498
|
+
createdAt: generatedAt
|
|
1499
|
+
};
|
|
1500
|
+
return baseResult(input.profile, generatedAt, {
|
|
1501
|
+
health: reasonCode === 'blocked_lifecycle' ? 'blocked' : 'rejected',
|
|
1502
|
+
rejection,
|
|
1503
|
+
nextActions: buildRuntimeNextActions(input.profile, generatedAt, [rejectionReasonToNextActionReason(reasonCode)])
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
function baseResult(profile, generatedAt, overrides) {
|
|
1507
|
+
return {
|
|
1508
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1509
|
+
adjudicationId: stableId('spec-adjudication', profile.scope, overrides.health ?? 'unknown', generatedAt),
|
|
1510
|
+
stage: 'spec',
|
|
1511
|
+
scope: profile.scope,
|
|
1512
|
+
health: overrides.health ?? 'rejected',
|
|
1513
|
+
acceptedItemIds: overrides.acceptedItemIds ?? [],
|
|
1514
|
+
clarificationGate: overrides.clarificationGate ?? null,
|
|
1515
|
+
specDecisionRecord: overrides.specDecisionRecord ?? null,
|
|
1516
|
+
stageDecision: overrides.stageDecision ?? null,
|
|
1517
|
+
handoffPacket: overrides.handoffPacket ?? null,
|
|
1518
|
+
rejection: overrides.rejection ?? null,
|
|
1519
|
+
nextActions: overrides.nextActions ?? [],
|
|
1520
|
+
createdAt: generatedAt
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
function rejectionReasonToNextActionReason(reasonCode) {
|
|
1524
|
+
if (reasonCode === 'authority_violation') {
|
|
1525
|
+
return 'authority_violation';
|
|
1526
|
+
}
|
|
1527
|
+
if (reasonCode === 'missing_required_review') {
|
|
1528
|
+
return 'missing_required_review';
|
|
1529
|
+
}
|
|
1530
|
+
if (reasonCode === 'missing_refs') {
|
|
1531
|
+
return 'insufficient_evidence';
|
|
1532
|
+
}
|
|
1533
|
+
if (reasonCode === 'blocked_lifecycle') {
|
|
1534
|
+
return 'graph_illegal';
|
|
1535
|
+
}
|
|
1536
|
+
return 'manager_rework_required';
|
|
1537
|
+
}
|
|
1538
|
+
function buildRuntimeNextActions(profile, generatedAt, reasonCodes) {
|
|
1539
|
+
return reasonCodes
|
|
1540
|
+
.map((reasonCode, index) => ({
|
|
1541
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1542
|
+
actionId: stableId('spec-next-action', profile.scope, reasonCode, generatedAt),
|
|
1543
|
+
stage: 'spec',
|
|
1544
|
+
scope: profile.scope,
|
|
1545
|
+
kind: nextActionKind(reasonCode),
|
|
1546
|
+
owner: nextActionOwner(reasonCode),
|
|
1547
|
+
actionScope: nextActionScope(reasonCode),
|
|
1548
|
+
reasonCode,
|
|
1549
|
+
priority: {
|
|
1550
|
+
reasonRank: reasonCodeRank(reasonCode),
|
|
1551
|
+
upstreamDistance: 0,
|
|
1552
|
+
actionRank: actionKindRank(nextActionKind(reasonCode)),
|
|
1553
|
+
stableOrder: index
|
|
1554
|
+
},
|
|
1555
|
+
requiredInputs: nextActionRequiredInputs(reasonCode),
|
|
1556
|
+
blockedBy: [],
|
|
1557
|
+
createdAt: generatedAt
|
|
1558
|
+
}))
|
|
1559
|
+
.sort(compareRuntimeNextAction);
|
|
1560
|
+
}
|
|
1561
|
+
function compareRuntimeNextAction(left, right) {
|
|
1562
|
+
return left.priority.reasonRank - right.priority.reasonRank
|
|
1563
|
+
|| left.priority.upstreamDistance - right.priority.upstreamDistance
|
|
1564
|
+
|| left.priority.actionRank - right.priority.actionRank
|
|
1565
|
+
|| left.priority.stableOrder - right.priority.stableOrder;
|
|
1566
|
+
}
|
|
1567
|
+
function reasonCodeRank(reasonCode) {
|
|
1568
|
+
const order = ['authority_violation', 'graph_illegal', 'scope_unresolved', 'ambiguous_target', 'missing_canonical_artifact', 'stale_upstream', 'missing_handoff', 'missing_required_review', 'unresolved_conflict', 'unresolved_ambiguity', 'insufficient_evidence', 'manager_rework_required', 'human_required', 'stage_ready', 'handoff_ready', 'report_only'];
|
|
1569
|
+
return order.indexOf(reasonCode);
|
|
1570
|
+
}
|
|
1571
|
+
function nextActionKind(reasonCode) {
|
|
1572
|
+
if (reasonCode === 'authority_violation' || reasonCode === 'graph_illegal') {
|
|
1573
|
+
return 'reject_proposal';
|
|
1574
|
+
}
|
|
1575
|
+
if (reasonCode === 'missing_required_review') {
|
|
1576
|
+
return 'collect_required_review';
|
|
1577
|
+
}
|
|
1578
|
+
if (reasonCode === 'unresolved_ambiguity') {
|
|
1579
|
+
return 'ask_user_clarification';
|
|
1580
|
+
}
|
|
1581
|
+
if (reasonCode === 'handoff_ready') {
|
|
1582
|
+
return 'create_handoff';
|
|
1583
|
+
}
|
|
1584
|
+
if (reasonCode === 'stage_ready') {
|
|
1585
|
+
return 'commit_stage_closure';
|
|
1586
|
+
}
|
|
1587
|
+
if (reasonCode === 'stale_upstream') {
|
|
1588
|
+
return 'refresh_upstream_stage';
|
|
1589
|
+
}
|
|
1590
|
+
if (reasonCode === 'report_only') {
|
|
1591
|
+
return 'report_status';
|
|
1592
|
+
}
|
|
1593
|
+
return 'request_manager_rework';
|
|
1594
|
+
}
|
|
1595
|
+
function nextActionOwner(reasonCode) {
|
|
1596
|
+
if (reasonCode === 'unresolved_ambiguity' || reasonCode === 'human_required') {
|
|
1597
|
+
return 'user';
|
|
1598
|
+
}
|
|
1599
|
+
if (reasonCode === 'stage_ready' || reasonCode === 'handoff_ready' || reasonCode === 'report_only') {
|
|
1600
|
+
return 'runtime';
|
|
1601
|
+
}
|
|
1602
|
+
if (reasonCode === 'scope_unresolved' || reasonCode === 'ambiguous_target') {
|
|
1603
|
+
return 'orchestrator';
|
|
1604
|
+
}
|
|
1605
|
+
return 'stage-manager';
|
|
1606
|
+
}
|
|
1607
|
+
function nextActionScope(reasonCode) {
|
|
1608
|
+
if (reasonCode === 'unresolved_ambiguity' || reasonCode === 'human_required') {
|
|
1609
|
+
return 'human';
|
|
1610
|
+
}
|
|
1611
|
+
if (reasonCode === 'stage_ready' || reasonCode === 'handoff_ready') {
|
|
1612
|
+
return 'runtime';
|
|
1613
|
+
}
|
|
1614
|
+
if (reasonCode === 'scope_unresolved' || reasonCode === 'ambiguous_target') {
|
|
1615
|
+
return 'global';
|
|
1616
|
+
}
|
|
1617
|
+
return 'stage';
|
|
1618
|
+
}
|
|
1619
|
+
function actionKindRank(kind) {
|
|
1620
|
+
const order = ['reject_proposal', 'refresh_upstream_stage', 'collect_required_review', 'resolve_ambiguity', 'request_manager_rework', 'ask_user_clarification', 'commit_stage_closure', 'create_handoff', 'report_status'];
|
|
1621
|
+
return order.indexOf(kind);
|
|
1622
|
+
}
|
|
1623
|
+
function nextActionRequiredInputs(reasonCode) {
|
|
1624
|
+
if (reasonCode === 'missing_required_review') {
|
|
1625
|
+
return ['SpecReviewResult'];
|
|
1626
|
+
}
|
|
1627
|
+
if (reasonCode === 'unresolved_ambiguity') {
|
|
1628
|
+
return ['ClarificationGate answer'];
|
|
1629
|
+
}
|
|
1630
|
+
if (reasonCode === 'insufficient_evidence') {
|
|
1631
|
+
return ['runtime evidence refs'];
|
|
1632
|
+
}
|
|
1633
|
+
return [];
|
|
1634
|
+
}
|
|
1635
|
+
function specCollaborationDiagnosticReasons(result) {
|
|
1636
|
+
if (result.health === 'no-op') {
|
|
1637
|
+
return ['Spec collaboration is not required for this lifecycle decision.'];
|
|
1638
|
+
}
|
|
1639
|
+
if (result.health === 'needs_clarification') {
|
|
1640
|
+
return result.clarificationGate?.questions ?? ['Spec collaboration is waiting for clarification.'];
|
|
1641
|
+
}
|
|
1642
|
+
if (result.health === 'ready_for_plan') {
|
|
1643
|
+
return [`Spec collaboration is ready for plan handoff ${result.handoffPacket?.handoffId ?? 'none'}.`];
|
|
1644
|
+
}
|
|
1645
|
+
if (result.health === 'blocked') {
|
|
1646
|
+
return [result.rejection?.explanation ?? 'Spec collaboration is blocked.'];
|
|
1647
|
+
}
|
|
1648
|
+
return [result.rejection?.explanation ?? 'Spec collaboration proposal was rejected.'];
|
|
1649
|
+
}
|
|
1650
|
+
async function registerStageCollaborationContracts(projectRoot, branch, stage, workOrder, contractRefs, registeredAt) {
|
|
1651
|
+
const refs = await collectStageCollaborationContractRefs(projectRoot, branch, stage, contractRefs, Boolean(workOrder));
|
|
1652
|
+
const records = [];
|
|
1653
|
+
for (const ref of refs) {
|
|
1654
|
+
const artifact = await readStageCollaborationContract(projectRoot, ref);
|
|
1655
|
+
const record = validateStageCollaborationContractFrontmatter({
|
|
1656
|
+
branch,
|
|
1657
|
+
stage,
|
|
1658
|
+
ref: artifact.ref,
|
|
1659
|
+
hash: artifact.hash,
|
|
1660
|
+
frontmatter: artifact.frontmatter,
|
|
1661
|
+
workOrder,
|
|
1662
|
+
registeredAt
|
|
1663
|
+
});
|
|
1664
|
+
records.push(await recordRuntimeStageCollaborationContract(projectRoot, record));
|
|
1665
|
+
}
|
|
1666
|
+
return records;
|
|
1667
|
+
}
|
|
1668
|
+
async function collectStageCollaborationContractRefs(projectRoot, branch, stage, contractRefs, required) {
|
|
1669
|
+
if (contractRefs && contractRefs.length > 0) {
|
|
1670
|
+
return [...new Set(contractRefs.map(normalizeBranchStageEvidenceRef))];
|
|
1671
|
+
}
|
|
1672
|
+
if (!required) {
|
|
1673
|
+
return [];
|
|
1674
|
+
}
|
|
1675
|
+
const dir = getBranchStageEvidenceDir(projectRoot, branch, stage);
|
|
1676
|
+
let files;
|
|
1677
|
+
try {
|
|
1678
|
+
files = await readdir(dir);
|
|
1679
|
+
}
|
|
1680
|
+
catch (error) {
|
|
1681
|
+
if (error.code === 'ENOENT') {
|
|
1682
|
+
return [];
|
|
1683
|
+
}
|
|
1684
|
+
throw error;
|
|
1685
|
+
}
|
|
1686
|
+
return files
|
|
1687
|
+
.filter((fileName) => isStageCollaborationContractFile(stage, fileName))
|
|
1688
|
+
.sort(compareStageEvidenceFileNames)
|
|
1689
|
+
.map((fileName) => toBranchStageEvidenceRef(branch, stage, fileName));
|
|
1690
|
+
}
|
|
1691
|
+
async function registerSpecStageArtifacts(projectRoot, branch, profile, artifactRefs, runId, registeredAt) {
|
|
1692
|
+
const refs = await collectSpecStageEvidenceRefs(projectRoot, branch, profile, artifactRefs);
|
|
1693
|
+
const records = [];
|
|
1694
|
+
for (const ref of refs) {
|
|
1695
|
+
const artifact = await readMarkdownArtifact(projectRoot, ref);
|
|
1696
|
+
const record = validateStageArtifactFrontmatter({
|
|
1697
|
+
branch,
|
|
1698
|
+
stage: 'spec',
|
|
1699
|
+
ref: artifact.ref,
|
|
1700
|
+
hash: artifact.hash,
|
|
1701
|
+
frontmatter: artifact.frontmatter,
|
|
1702
|
+
registeredAt
|
|
1703
|
+
});
|
|
1704
|
+
records.push(await recordRuntimeStageArtifact(projectRoot, record));
|
|
1705
|
+
await recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record);
|
|
1706
|
+
}
|
|
1707
|
+
return records;
|
|
1708
|
+
}
|
|
1709
|
+
async function collectSpecStageEvidenceRefs(projectRoot, branch, profile, artifactRefs) {
|
|
1710
|
+
void projectRoot;
|
|
1711
|
+
void branch;
|
|
1712
|
+
void profile;
|
|
1713
|
+
if (artifactRefs && artifactRefs.length > 0) {
|
|
1714
|
+
return [...new Set(artifactRefs.map(normalizeBranchStageEvidenceRef))];
|
|
1715
|
+
}
|
|
1716
|
+
return [];
|
|
1717
|
+
}
|
|
1718
|
+
async function registerBranchStageArtifacts(projectRoot, branch, stage, artifactRefs, required, runId, registeredAt) {
|
|
1719
|
+
const refs = await collectBranchStageEvidenceRefs(projectRoot, branch, stage, artifactRefs, required);
|
|
1720
|
+
const records = [];
|
|
1721
|
+
for (const ref of refs) {
|
|
1722
|
+
const artifact = await readMarkdownArtifact(projectRoot, ref);
|
|
1723
|
+
const record = validateStageArtifactFrontmatter({
|
|
1724
|
+
branch,
|
|
1725
|
+
stage,
|
|
1726
|
+
ref: artifact.ref,
|
|
1727
|
+
hash: artifact.hash,
|
|
1728
|
+
frontmatter: artifact.frontmatter,
|
|
1729
|
+
registeredAt
|
|
1730
|
+
});
|
|
1731
|
+
records.push(await recordRuntimeStageArtifact(projectRoot, record));
|
|
1732
|
+
await recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record);
|
|
1733
|
+
}
|
|
1734
|
+
return records;
|
|
1735
|
+
}
|
|
1736
|
+
async function recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record) {
|
|
1737
|
+
const physicalPayloadPath = `runtime.sqlite:${runId}:${artifact.ref}`;
|
|
1738
|
+
await recordRuntimeArtifactPayload(projectRoot, {
|
|
1739
|
+
payloadId: runtimeScopedId(runId, artifact.ref, artifact.hash),
|
|
1740
|
+
runId,
|
|
1741
|
+
sourceRunId: runId,
|
|
1742
|
+
branchSlug: record.branch,
|
|
1743
|
+
taskId: null,
|
|
1744
|
+
logicalRef: artifact.ref,
|
|
1745
|
+
physicalPayloadPath,
|
|
1746
|
+
artifactRole: record.kind,
|
|
1747
|
+
digest: artifact.hash,
|
|
1748
|
+
status: 'active',
|
|
1749
|
+
payload: { logicalRef: artifact.ref, physicalPayloadPath, storage: 'runtime.sqlite', source: 'branch_stage_evidence', content: artifact.content }
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
async function collectBranchStageEvidenceRefs(projectRoot, branch, stage, artifactRefs, required) {
|
|
1753
|
+
if (artifactRefs && artifactRefs.length > 0) {
|
|
1754
|
+
return [...new Set(artifactRefs.map(normalizeBranchStageEvidenceRef).filter((ref) => !isStageCollaborationContractFile(stage, ref.split('/').pop() ?? ref)))];
|
|
1755
|
+
}
|
|
1756
|
+
if (!required) {
|
|
1757
|
+
return [];
|
|
1758
|
+
}
|
|
1759
|
+
const dir = getBranchStageEvidenceDir(projectRoot, branch, stage);
|
|
1760
|
+
let files;
|
|
1761
|
+
try {
|
|
1762
|
+
files = await readdir(dir);
|
|
1763
|
+
}
|
|
1764
|
+
catch (error) {
|
|
1765
|
+
if (error.code === 'ENOENT') {
|
|
1766
|
+
return [];
|
|
1767
|
+
}
|
|
1768
|
+
throw error;
|
|
1769
|
+
}
|
|
1770
|
+
const candidateRefs = files
|
|
1771
|
+
.filter((fileName) => fileName.endsWith('.md') && !isStageCollaborationContractFile(stage, fileName))
|
|
1772
|
+
.sort(compareStageEvidenceFileNames)
|
|
1773
|
+
.map((fileName) => toBranchStageEvidenceRef(branch, stage, fileName));
|
|
1774
|
+
const checkedRefs = await Promise.all(candidateRefs.map(async (ref) => (await isMarkdownStageArtifactCandidate(projectRoot, ref)) ? ref : null));
|
|
1775
|
+
return checkedRefs.filter((ref) => ref !== null);
|
|
1776
|
+
}
|
|
1777
|
+
async function isMarkdownStageArtifactCandidate(projectRoot, ref) {
|
|
1778
|
+
const content = await readFile(path.join(projectRoot, normalizeBranchStageEvidenceRef(ref)), 'utf8');
|
|
1779
|
+
return /^---\r?\n/.test(content);
|
|
1780
|
+
}
|
|
1781
|
+
function isSpecStageEvidenceFile(fileName) {
|
|
1782
|
+
return fileName === 'scout.md' || /^spec-review-v\d+\.md$/.test(fileName) || /^spec-manager-v\d+\.md$/.test(fileName);
|
|
1783
|
+
}
|
|
1784
|
+
function isStageCollaborationContractFile(stage, fileName) {
|
|
1785
|
+
return fileName === `${stage}-collaboration-contract.md` || new RegExp(`^${escapeRegex(stage)}-collaboration-contract-v\\d+\\.md$`).test(fileName);
|
|
1786
|
+
}
|
|
1787
|
+
function escapeRegex(value) {
|
|
1788
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1789
|
+
}
|
|
1790
|
+
function compareStageEvidenceFileNames(left, right) {
|
|
1791
|
+
return stageEvidenceVersion(left) - stageEvidenceVersion(right) || left.localeCompare(right);
|
|
1792
|
+
}
|
|
1793
|
+
function stageEvidenceVersion(fileNameOrRef) {
|
|
1794
|
+
const fileName = fileNameOrRef.split('/').pop() ?? fileNameOrRef;
|
|
1795
|
+
const match = /-v(\d+)\.md$/.exec(fileName);
|
|
1796
|
+
return match ? Number(match[1]) : 0;
|
|
1797
|
+
}
|
|
1798
|
+
function runtimeRefForStageArtifact(record) {
|
|
1799
|
+
return { kind: 'artifact', ref: record.ref, hash: record.hash };
|
|
1800
|
+
}
|
|
1801
|
+
function runtimeRefForStageCollaborationContract(record) {
|
|
1802
|
+
return { kind: 'artifact', ref: record.ref, hash: record.hash };
|
|
1803
|
+
}
|
|
1804
|
+
async function buildOutputCenteredSpecClosureRequest(projectRoot, profile, workOrder, partition, request, generatedAt) {
|
|
1805
|
+
if (!request) {
|
|
1806
|
+
return null;
|
|
1807
|
+
}
|
|
1808
|
+
const expectedSpecRef = `specs/${partition}/spec.md`;
|
|
1809
|
+
if (request.finalSpecRef && request.finalSpecRef !== expectedSpecRef) {
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
if (request.sectionCloseDeclaration !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforePlanCount !== 0) {
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
|
|
1816
|
+
if (content === null) {
|
|
1817
|
+
return null;
|
|
1818
|
+
}
|
|
1819
|
+
const specHash = hashDocumentContent(content);
|
|
1820
|
+
const finalSpecRef = { kind: 'document', ref: expectedSpecRef, hash: specHash };
|
|
1821
|
+
const facts = {
|
|
1822
|
+
contract: 'sdd-spec-runtime-close-boundary-facts-v1',
|
|
1823
|
+
scope: profile.scope,
|
|
1824
|
+
finalSpecRef,
|
|
1825
|
+
finalSpecHash: specHash,
|
|
1826
|
+
templateContract: 'sdd-spec-doc-v3',
|
|
1827
|
+
blockingBeforePlanCount: 0,
|
|
1828
|
+
sectionCloseDeclaration: 'present',
|
|
1829
|
+
reviewSignal: 'present',
|
|
1830
|
+
runtimeTruthOwner: 'Runtime Kernel'
|
|
1831
|
+
};
|
|
1832
|
+
const acceptedItems = [{
|
|
1833
|
+
id: 'accepted-spec',
|
|
1834
|
+
kind: 'handoff_proposal',
|
|
1835
|
+
summary: 'Spec-manager submitted final spec.md V3 for runtime ref/hash acceptance.',
|
|
1836
|
+
refs: [finalSpecRef]
|
|
1837
|
+
}];
|
|
1838
|
+
const candidate = {
|
|
1839
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1840
|
+
candidateId: stableId('spec-document-candidate', profile.scope, expectedSpecRef, specHash, generatedAt),
|
|
1841
|
+
stage: 'spec',
|
|
1842
|
+
scope: profile.scope,
|
|
1843
|
+
producedBy: SPEC_STAGE_MANAGER,
|
|
1844
|
+
inputRefs: profile.inputRefs,
|
|
1845
|
+
evidenceRefs: [],
|
|
1846
|
+
acceptedItems,
|
|
1847
|
+
generatedAt
|
|
1848
|
+
};
|
|
1849
|
+
return {
|
|
1850
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1851
|
+
closureRequestId: stableId('spec-closure-request', profile.scope, expectedSpecRef, specHash, generatedAt),
|
|
1852
|
+
stage: 'spec',
|
|
1853
|
+
scope: profile.scope,
|
|
1854
|
+
submittedBy: 'host-adapter',
|
|
1855
|
+
workOrderRef: workOrder ? { kind: 'projection', ref: `spec-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `spec-work-order:${specCollaborationScopeKey(profile.scope)}` },
|
|
1856
|
+
coordinationRef: null,
|
|
1857
|
+
candidateRef: finalSpecRef,
|
|
1858
|
+
reviewRefs: [],
|
|
1859
|
+
capabilityFindingRefs: [],
|
|
1860
|
+
evidenceRefs: [],
|
|
1861
|
+
candidate,
|
|
1862
|
+
reviewResults: [],
|
|
1863
|
+
capabilityFindings: [],
|
|
1864
|
+
unresolvedAmbiguityIds: [],
|
|
1865
|
+
authorityAttempts: [],
|
|
1866
|
+
managerRecommendation: 'close_stage',
|
|
1867
|
+
runtimeCloseBoundaryFacts: facts,
|
|
1868
|
+
generatedAt
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
async function buildOutputCenteredPlanClosureRequest(projectRoot, scope, workOrder, partition, acceptedSpecHandoff, request, generatedAt) {
|
|
1872
|
+
if (!request) {
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
const expectedPlanRef = `specs/${partition}/plan.md`;
|
|
1876
|
+
if (request.finalPlanRef && request.finalPlanRef !== expectedPlanRef) {
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
if (request.planCloseQualityEvidence !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforeTasksCount !== 0) {
|
|
1880
|
+
return null;
|
|
1881
|
+
}
|
|
1882
|
+
const acceptedSpecRef = acceptedSpecRefFromHandoff(partition, acceptedSpecHandoff);
|
|
1883
|
+
if (!acceptedSpecRef) {
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'plan.md'));
|
|
1887
|
+
if (content === null) {
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
const planHash = hashDocumentContent(content);
|
|
1891
|
+
const finalPlanRef = { kind: 'document', ref: expectedPlanRef, hash: planHash };
|
|
1892
|
+
const facts = {
|
|
1893
|
+
contract: 'sdd-plan-runtime-close-boundary-facts-v1',
|
|
1894
|
+
scope,
|
|
1895
|
+
acceptedSpecRef,
|
|
1896
|
+
finalPlanRef,
|
|
1897
|
+
finalPlanHash: planHash,
|
|
1898
|
+
templateContract: 'sdd-plan-doc-v3',
|
|
1899
|
+
blockingBeforeTasksCount: 0,
|
|
1900
|
+
planCloseQualityEvidence: 'present',
|
|
1901
|
+
reviewSignal: 'present',
|
|
1902
|
+
runtimeTruthOwner: 'Runtime Kernel'
|
|
1903
|
+
};
|
|
1904
|
+
return {
|
|
1905
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1906
|
+
closureRequestId: stableId('plan-closure-request', scope, expectedPlanRef, planHash, generatedAt),
|
|
1907
|
+
stage: 'plan',
|
|
1908
|
+
scope,
|
|
1909
|
+
submittedBy: 'host-adapter',
|
|
1910
|
+
workOrderRef: workOrder ? { kind: 'projection', ref: `plan-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `plan-work-order:${planCollaborationScopeKey(scope)}` },
|
|
1911
|
+
coordinationRef: null,
|
|
1912
|
+
candidateRef: finalPlanRef,
|
|
1913
|
+
reviewRefs: [],
|
|
1914
|
+
capabilityFindingRefs: [],
|
|
1915
|
+
evidenceRefs: [],
|
|
1916
|
+
candidate: null,
|
|
1917
|
+
reviewResults: [],
|
|
1918
|
+
capabilityFindings: [],
|
|
1919
|
+
unresolvedAmbiguityIds: [],
|
|
1920
|
+
authorityAttempts: [],
|
|
1921
|
+
managerRecommendation: 'close_stage',
|
|
1922
|
+
runtimeCloseBoundaryFacts: facts,
|
|
1923
|
+
generatedAt
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
async function buildOutputCenteredTasksClosureRequest(projectRoot, scope, workOrder, partition, acceptedPlanHandoff, request, generatedAt) {
|
|
1927
|
+
if (!request) {
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
const expectedTasksRef = `specs/${partition}/tasks.md`;
|
|
1931
|
+
if (request.finalTasksRef && request.finalTasksRef !== expectedTasksRef) {
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1934
|
+
if (request.dependencyGraphStatus !== 'present' || request.validationSignalsStatus !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforeExecuteCount !== 0) {
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
const acceptedPlanRef = acceptedPlanRefFromHandoff(partition, acceptedPlanHandoff);
|
|
1938
|
+
const acceptedSpecRef = acceptedSpecRefFromPlanHandoff(acceptedPlanHandoff);
|
|
1939
|
+
if (!acceptedPlanRef || !acceptedSpecRef) {
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
|
|
1943
|
+
if (content === null) {
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
const taskModel = parseSddTasksMarkdown(content, { tasksPath: expectedTasksRef });
|
|
1947
|
+
const tasksHash = hashDocumentContent(content);
|
|
1948
|
+
const finalTasksRef = { kind: 'document', ref: expectedTasksRef, hash: tasksHash };
|
|
1949
|
+
const facts = {
|
|
1950
|
+
contract: 'sdd-tasks-runtime-close-boundary-facts-v1',
|
|
1951
|
+
scope,
|
|
1952
|
+
acceptedSpecRef,
|
|
1953
|
+
acceptedPlanRef,
|
|
1954
|
+
finalTasksRef,
|
|
1955
|
+
finalTasksHash: tasksHash,
|
|
1956
|
+
templateContract: 'sdd-tasks-doc-v2',
|
|
1957
|
+
taskUnitCount: taskModel.tasks.length,
|
|
1958
|
+
blockingBeforeExecuteCount: 0,
|
|
1959
|
+
dependencyGraphStatus: 'present',
|
|
1960
|
+
validationSignalsStatus: 'present',
|
|
1961
|
+
reviewSignal: 'present',
|
|
1962
|
+
runtimeTruthOwner: 'Runtime Kernel'
|
|
1963
|
+
};
|
|
1964
|
+
return {
|
|
1965
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
1966
|
+
closureRequestId: stableId('tasks-closure-request', scope, expectedTasksRef, tasksHash, generatedAt),
|
|
1967
|
+
stage: 'tasks',
|
|
1968
|
+
scope,
|
|
1969
|
+
submittedBy: 'host-adapter',
|
|
1970
|
+
workOrderRef: workOrder ? { kind: 'projection', ref: `tasks-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `tasks-work-order:${tasksCollaborationScopeKey(scope)}` },
|
|
1971
|
+
coordinationRef: null,
|
|
1972
|
+
candidateRef: finalTasksRef,
|
|
1973
|
+
reviewRefs: [],
|
|
1974
|
+
capabilityFindingRefs: [],
|
|
1975
|
+
evidenceRefs: [],
|
|
1976
|
+
candidate: null,
|
|
1977
|
+
reviewResults: [],
|
|
1978
|
+
capabilityFindings: [],
|
|
1979
|
+
unresolvedAmbiguityIds: [],
|
|
1980
|
+
authorityAttempts: [],
|
|
1981
|
+
managerRecommendation: 'close_stage',
|
|
1982
|
+
runtimeCloseBoundaryFacts: facts,
|
|
1983
|
+
generatedAt
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
function acceptedSpecRefFromHandoff(partition, handoff) {
|
|
1987
|
+
const expectedSpecRef = `specs/${partition}/spec.md`;
|
|
1988
|
+
return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
|
|
1989
|
+
?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef)
|
|
1990
|
+
?? null;
|
|
1991
|
+
}
|
|
1992
|
+
function acceptedPlanRefFromHandoff(partition, handoff) {
|
|
1993
|
+
const expectedPlanRef = `specs/${partition}/plan.md`;
|
|
1994
|
+
return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedPlanRef)
|
|
1995
|
+
?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedPlanRef)
|
|
1996
|
+
?? null;
|
|
1997
|
+
}
|
|
1998
|
+
function acceptedSpecRefFromPlanHandoff(handoff) {
|
|
1999
|
+
return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref.endsWith('/spec.md'))
|
|
2000
|
+
?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref.endsWith('/spec.md'))
|
|
2001
|
+
?? null;
|
|
2002
|
+
}
|
|
2003
|
+
function latestValidatedStageCollaborationContract(records) {
|
|
2004
|
+
return records
|
|
2005
|
+
.filter((record) => record.status === 'validated')
|
|
2006
|
+
.sort((left, right) => stageEvidenceVersion(right.ref) - stageEvidenceVersion(left.ref) || right.registeredAt.localeCompare(left.registeredAt))[0] ?? null;
|
|
2007
|
+
}
|
|
2008
|
+
function deriveRegisteredSpecManagerCoordination(profile, workOrder, records, generatedAt) {
|
|
2009
|
+
const manager = latestStageArtifact(records, 'manager_closure_request');
|
|
2010
|
+
if (!manager || !workOrder) {
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
2015
|
+
coordinationId: stableId('spec-manager-coordination', profile.scope, manager.ref, manager.hash, generatedAt),
|
|
2016
|
+
stage: 'spec',
|
|
2017
|
+
scope: profile.scope,
|
|
2018
|
+
stageManager: SPEC_STAGE_MANAGER,
|
|
2019
|
+
workOrderRef: { kind: 'projection', ref: `spec-work-order:${workOrder.workOrderId}` },
|
|
2020
|
+
agentTeamRefs: records.filter((record) => record.ref !== manager.ref).map(runtimeRefForStageArtifact),
|
|
2021
|
+
recommendation: manager.recommendation ?? 'blocked',
|
|
2022
|
+
generatedAt
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
function latestStageArtifact(records, kind) {
|
|
2026
|
+
return records
|
|
2027
|
+
.filter((record) => record.kind === kind)
|
|
2028
|
+
.sort((left, right) => stageEvidenceVersion(right.ref) - stageEvidenceVersion(left.ref) || right.registeredAt.localeCompare(left.registeredAt))[0] ?? null;
|
|
2029
|
+
}
|
|
2030
|
+
async function validateAcceptedSpecArtifact(projectRoot, input) {
|
|
2031
|
+
if (input.adjudication.health !== 'ready_for_plan') {
|
|
2032
|
+
return {
|
|
2033
|
+
acceptedSpecRef: null,
|
|
2034
|
+
specAcceptanceStatus: specArtifactStatusForHealth(input.adjudication.health),
|
|
2035
|
+
specHash: null,
|
|
2036
|
+
specContractHash: null,
|
|
2037
|
+
reasons: [`Spec artifact was not accepted because adjudication health is ${input.adjudication.health}.`]
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
const expectedRef = `specs/${input.partition}/spec.md`;
|
|
2041
|
+
const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts ?? null;
|
|
2042
|
+
if (!boundaryFacts || boundaryFacts.contract !== 'sdd-spec-runtime-close-boundary-facts-v1') {
|
|
2043
|
+
return rejectedSpecArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready spec closure must include runtime close boundary facts for the final spec.md V3.', 'Submit finalSpecRef/finalSpecHash, section close declaration, review signal presence, and blockingBeforePlanCount=0 before closure.');
|
|
2044
|
+
}
|
|
2045
|
+
if (!validSpecRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef)) {
|
|
2046
|
+
return rejectedSpecArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Spec runtime close boundary facts do not match the final spec.md V3 acceptance contract.', 'Refresh spec close facts so finalSpecRef/finalSpecHash, template contract, section close declaration, review signal, and blocking count match runtime requirements.');
|
|
2047
|
+
}
|
|
2048
|
+
const specPath = path.join(projectRoot, 'specs', input.partition, 'spec.md');
|
|
2049
|
+
const content = await readOptionalText(specPath);
|
|
2050
|
+
if (content === null) {
|
|
2051
|
+
return rejectedSpecArtifact('not_accepted_missing_spec', 'missing_refs', `Final spec.md V3 artifact is missing at ${expectedRef}.`, 'Produce specs/<branch>/spec.md before requesting runtime closure.');
|
|
2052
|
+
}
|
|
2053
|
+
if (isManagedStarterSpec(content)) {
|
|
2054
|
+
return rejectedSpecArtifact('not_accepted_starter_spec', 'invalid_proposal', `Canonical spec artifact at ${expectedRef} is still the managed starter spec.`, 'Replace starter spec.md with final semantic spec.md V3 content before closure.');
|
|
2055
|
+
}
|
|
2056
|
+
const specHash = hashDocumentContent(content);
|
|
2057
|
+
const expectedHashes = [boundaryFacts.finalSpecHash, boundaryFacts.finalSpecRef.hash].filter((hash) => Boolean(hash));
|
|
2058
|
+
const mismatchedHash = expectedHashes.find((hash) => hash !== specHash);
|
|
2059
|
+
if (mismatchedHash) {
|
|
2060
|
+
return rejectedSpecArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final spec.md V3 hash mismatch for ${expectedRef}: expected ${mismatchedHash}, actual ${specHash}.`, 'Refresh spec close facts with the current spec.md hash before closure.');
|
|
2061
|
+
}
|
|
2062
|
+
const specV3Errors = validateSpecDocumentV3(content);
|
|
2063
|
+
if (specV3Errors.length > 0) {
|
|
2064
|
+
return rejectedSpecArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Final spec.md does not satisfy sdd-spec-doc-v3: ${specV3Errors.join('; ')}.`, 'Rewrite specs/<branch>/spec.md as final spec.md V3 with required semantic sections before closure.');
|
|
2065
|
+
}
|
|
2066
|
+
return {
|
|
2067
|
+
acceptedSpecRef: { kind: 'document', ref: expectedRef, hash: specHash },
|
|
2068
|
+
specAcceptanceStatus: 'accepted',
|
|
2069
|
+
specHash,
|
|
2070
|
+
specContractHash: hashSemanticDocument(content),
|
|
2071
|
+
reasons: [`Runtime accepted final spec.md V3 artifact ${expectedRef}.`]
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
function validSpecRuntimeCloseBoundaryFacts(facts, partition, expectedSpecRef) {
|
|
2075
|
+
return facts.contract === 'sdd-spec-runtime-close-boundary-facts-v1'
|
|
2076
|
+
&& facts.scope.branch === partition
|
|
2077
|
+
&& facts.finalSpecRef.kind === 'document'
|
|
2078
|
+
&& facts.finalSpecRef.ref === expectedSpecRef
|
|
2079
|
+
&& Boolean(facts.finalSpecRef.hash)
|
|
2080
|
+
&& facts.finalSpecHash === facts.finalSpecRef.hash
|
|
2081
|
+
&& facts.templateContract === 'sdd-spec-doc-v3'
|
|
2082
|
+
&& facts.blockingBeforePlanCount === 0
|
|
2083
|
+
&& facts.sectionCloseDeclaration === 'present'
|
|
2084
|
+
&& facts.reviewSignal === 'present'
|
|
2085
|
+
&& facts.runtimeTruthOwner === 'Runtime Kernel';
|
|
2086
|
+
}
|
|
2087
|
+
function validPlanRuntimeCloseBoundaryFacts(facts, partition, expectedPlanRef, acceptedSpecRef) {
|
|
2088
|
+
return facts.contract === 'sdd-plan-runtime-close-boundary-facts-v1'
|
|
2089
|
+
&& facts.scope.branch === partition
|
|
2090
|
+
&& facts.acceptedSpecRef.kind === acceptedSpecRef.kind
|
|
2091
|
+
&& facts.acceptedSpecRef.ref === acceptedSpecRef.ref
|
|
2092
|
+
&& facts.acceptedSpecRef.hash === acceptedSpecRef.hash
|
|
2093
|
+
&& facts.finalPlanRef.kind === 'document'
|
|
2094
|
+
&& facts.finalPlanRef.ref === expectedPlanRef
|
|
2095
|
+
&& Boolean(facts.finalPlanRef.hash)
|
|
2096
|
+
&& facts.finalPlanHash === facts.finalPlanRef.hash
|
|
2097
|
+
&& facts.templateContract === 'sdd-plan-doc-v3'
|
|
2098
|
+
&& facts.blockingBeforeTasksCount === 0
|
|
2099
|
+
&& facts.planCloseQualityEvidence === 'present'
|
|
2100
|
+
&& facts.reviewSignal === 'present'
|
|
2101
|
+
&& facts.runtimeTruthOwner === 'Runtime Kernel';
|
|
2102
|
+
}
|
|
2103
|
+
function validTasksRuntimeCloseBoundaryFacts(facts, partition, expectedTasksRef, acceptedSpecRef, acceptedPlanRef, taskUnitCount) {
|
|
2104
|
+
return facts.contract === 'sdd-tasks-runtime-close-boundary-facts-v1'
|
|
2105
|
+
&& facts.scope.branch === partition
|
|
2106
|
+
&& facts.acceptedSpecRef.kind === acceptedSpecRef.kind
|
|
2107
|
+
&& facts.acceptedSpecRef.ref === acceptedSpecRef.ref
|
|
2108
|
+
&& facts.acceptedSpecRef.hash === acceptedSpecRef.hash
|
|
2109
|
+
&& facts.acceptedPlanRef.kind === acceptedPlanRef.kind
|
|
2110
|
+
&& facts.acceptedPlanRef.ref === acceptedPlanRef.ref
|
|
2111
|
+
&& facts.acceptedPlanRef.hash === acceptedPlanRef.hash
|
|
2112
|
+
&& facts.finalTasksRef.kind === 'document'
|
|
2113
|
+
&& facts.finalTasksRef.ref === expectedTasksRef
|
|
2114
|
+
&& Boolean(facts.finalTasksRef.hash)
|
|
2115
|
+
&& facts.finalTasksHash === facts.finalTasksRef.hash
|
|
2116
|
+
&& facts.templateContract === 'sdd-tasks-doc-v2'
|
|
2117
|
+
&& facts.taskUnitCount === taskUnitCount
|
|
2118
|
+
&& facts.taskUnitCount > 0
|
|
2119
|
+
&& facts.blockingBeforeExecuteCount === 0
|
|
2120
|
+
&& facts.dependencyGraphStatus === 'present'
|
|
2121
|
+
&& facts.validationSignalsStatus === 'present'
|
|
2122
|
+
&& facts.reviewSignal === 'present'
|
|
2123
|
+
&& facts.runtimeTruthOwner === 'Runtime Kernel';
|
|
2124
|
+
}
|
|
2125
|
+
function rejectedSpecArtifact(specAcceptanceStatus, reasonCode, explanation, requiredNextAction) {
|
|
2126
|
+
return {
|
|
2127
|
+
acceptedSpecRef: null,
|
|
2128
|
+
specAcceptanceStatus,
|
|
2129
|
+
specHash: null,
|
|
2130
|
+
specContractHash: null,
|
|
2131
|
+
reasons: [explanation],
|
|
2132
|
+
rejectionIssue: {
|
|
2133
|
+
reasonCode,
|
|
2134
|
+
explanation,
|
|
2135
|
+
requiredNextAction,
|
|
2136
|
+
fallbackRoute: 'revise-proposal'
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
async function validateAcceptedPlanArtifact(projectRoot, input) {
|
|
2141
|
+
if (!input.required) {
|
|
2142
|
+
return {
|
|
2143
|
+
acceptedPlanRef: null,
|
|
2144
|
+
planAcceptanceStatus: 'not_required_noop',
|
|
2145
|
+
planHash: null,
|
|
2146
|
+
planContractHash: null,
|
|
2147
|
+
reasons: ['Plan artifact was not required by this lifecycle decision.']
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
if (input.blocked) {
|
|
2151
|
+
return rejectedPlanArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks plan closure.', 'Resolve lifecycle blockers before requesting plan-stage closure.', 'runtime-blocked');
|
|
2152
|
+
}
|
|
2153
|
+
const upstreamIssue = await validateAcceptedSpecHandoff(projectRoot, input.partition, input.acceptedSpecHandoff);
|
|
2154
|
+
if (upstreamIssue) {
|
|
2155
|
+
return rejectedPlanArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh spec-stage closure and provide a fresh spec -> plan handoff before plan closure.');
|
|
2156
|
+
}
|
|
2157
|
+
const expectedRef = `specs/${input.partition}/plan.md`;
|
|
2158
|
+
const acceptedSpecRef = acceptedSpecRefFromHandoff(input.partition, input.acceptedSpecHandoff);
|
|
2159
|
+
if (!acceptedSpecRef) {
|
|
2160
|
+
return rejectedPlanArtifact('not_accepted_missing_upstream', 'missing_refs', 'Plan closure requires accepted spec ref/hash from runtime handoff.', 'Refresh spec-stage closure and provide a fresh spec -> plan handoff before plan closure.');
|
|
2161
|
+
}
|
|
2162
|
+
const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts;
|
|
2163
|
+
if (!boundaryFacts || boundaryFacts.contract !== 'sdd-plan-runtime-close-boundary-facts-v1') {
|
|
2164
|
+
return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Plan close request is missing runtime boundary facts for final plan.md V3.', 'Submit finalPlanRef/finalPlanHash, acceptedSpecRef, plan close quality evidence, review signal, and blockingBeforeTasksCount=0.');
|
|
2165
|
+
}
|
|
2166
|
+
if (!validPlanRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef, acceptedSpecRef)) {
|
|
2167
|
+
return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Plan close boundary facts must reference accepted spec and final ${expectedRef}.`, 'Refresh the output close request from current runtime handoff and specs/<branch>/plan.md.');
|
|
2168
|
+
}
|
|
2169
|
+
const planPath = path.join(projectRoot, 'specs', input.partition, 'plan.md');
|
|
2170
|
+
const content = await readOptionalText(planPath);
|
|
2171
|
+
if (content === null) {
|
|
2172
|
+
return rejectedPlanArtifact('not_accepted_missing_plan', 'missing_refs', `Canonical plan artifact is missing at ${expectedRef}.`, 'Produce specs/<branch>/plan.md before requesting runtime closure.');
|
|
2173
|
+
}
|
|
2174
|
+
if (isManagedStarterSpec(content)) {
|
|
2175
|
+
return rejectedPlanArtifact('not_accepted_starter_plan', 'invalid_proposal', `Canonical plan artifact at ${expectedRef} is still the managed starter document.`, 'Replace starter plan.md with semantic plan content before closure.');
|
|
2176
|
+
}
|
|
2177
|
+
const documentErrors = validatePlanDocumentV3(content, boundaryFacts);
|
|
2178
|
+
if (documentErrors.length > 0) {
|
|
2179
|
+
return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Canonical plan artifact at ${expectedRef} is not a valid sdd-plan-doc-v3: ${documentErrors.join('; ')}.`, 'Rewrite specs/<branch>/plan.md as plan.md V3 before requesting runtime closure.');
|
|
2180
|
+
}
|
|
2181
|
+
const planHash = hashDocumentContent(content);
|
|
2182
|
+
if (boundaryFacts.finalPlanHash !== planHash || boundaryFacts.finalPlanRef.hash !== planHash) {
|
|
2183
|
+
return rejectedPlanArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final plan hash mismatch for ${expectedRef}: expected ${boundaryFacts.finalPlanHash}, actual ${planHash}.`, 'Refresh plan close request with the current plan.md hash before closure.');
|
|
2184
|
+
}
|
|
2185
|
+
return {
|
|
2186
|
+
acceptedPlanRef: { kind: 'document', ref: expectedRef, hash: planHash },
|
|
2187
|
+
planAcceptanceStatus: 'accepted',
|
|
2188
|
+
planHash,
|
|
2189
|
+
planContractHash: hashSemanticDocument(content),
|
|
2190
|
+
reasons: [`Runtime accepted reviewed canonical plan artifact ${expectedRef}.`]
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
async function validateAcceptedTasksArtifact(projectRoot, input) {
|
|
2194
|
+
if (!input.required) {
|
|
2195
|
+
return {
|
|
2196
|
+
acceptedTasksRef: null,
|
|
2197
|
+
tasksAcceptanceStatus: 'not_required_noop',
|
|
2198
|
+
tasksHash: null,
|
|
2199
|
+
tasksContractHash: null,
|
|
2200
|
+
reasons: ['Tasks artifact was not required by this lifecycle decision.']
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
if (input.blocked) {
|
|
2204
|
+
return rejectedTasksArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks tasks closure.', 'Resolve lifecycle blockers before requesting tasks-stage closure.', 'runtime-blocked');
|
|
2205
|
+
}
|
|
2206
|
+
const upstreamIssue = await validateAcceptedPlanHandoff(projectRoot, input.partition, input.acceptedPlanHandoff);
|
|
2207
|
+
if (upstreamIssue) {
|
|
2208
|
+
return rejectedTasksArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh plan-stage closure and provide a fresh plan -> tasks handoff before tasks closure.');
|
|
2209
|
+
}
|
|
2210
|
+
const expectedRef = `specs/${input.partition}/tasks.md`;
|
|
2211
|
+
const acceptedPlanRef = acceptedPlanRefFromHandoff(input.partition, input.acceptedPlanHandoff);
|
|
2212
|
+
const acceptedSpecRef = acceptedSpecRefFromPlanHandoff(input.acceptedPlanHandoff);
|
|
2213
|
+
if (!acceptedPlanRef || !acceptedSpecRef) {
|
|
2214
|
+
return rejectedTasksArtifact('not_accepted_missing_upstream', 'missing_refs', 'Tasks closure requires accepted spec and plan ref/hash from runtime handoff.', 'Refresh plan-stage closure and provide a fresh plan -> tasks handoff before tasks closure.');
|
|
2215
|
+
}
|
|
2216
|
+
const tasksPath = path.join(projectRoot, 'specs', input.partition, 'tasks.md');
|
|
2217
|
+
const content = await readOptionalText(tasksPath);
|
|
2218
|
+
const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts;
|
|
2219
|
+
if (!boundaryFacts || boundaryFacts.contract !== 'sdd-tasks-runtime-close-boundary-facts-v1') {
|
|
2220
|
+
const closeEvidenceErrors = [];
|
|
2221
|
+
if (content !== null) {
|
|
2222
|
+
validateTasksDepthSignals(content, closeEvidenceErrors);
|
|
2223
|
+
}
|
|
2224
|
+
const closeEvidenceExplanation = closeEvidenceErrors.join('\n');
|
|
2225
|
+
return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', closeEvidenceExplanation || 'Tasks close request is missing runtime boundary facts for final tasks.md V2.', 'Submit finalTasksRef/finalTasksHash, acceptedSpecRef, acceptedPlanRef, taskUnitCount, dependencyGraphStatus, validationSignalsStatus, reviewSignal, and blockingBeforeExecuteCount=0.');
|
|
2226
|
+
}
|
|
2227
|
+
if (content === null) {
|
|
2228
|
+
return rejectedTasksArtifact('not_accepted_missing_tasks', 'missing_refs', `Reviewed canonical tasks artifact is missing at ${expectedRef}.`, 'Produce and review specs/<branch>/tasks.md before requesting runtime closure.');
|
|
2229
|
+
}
|
|
2230
|
+
if (isManagedStarterSpec(content)) {
|
|
2231
|
+
return rejectedTasksArtifact('not_accepted_starter_tasks', 'invalid_proposal', `Canonical tasks artifact at ${expectedRef} is still the managed starter document.`, 'Replace starter tasks.md with reviewed executable task contracts before closure.');
|
|
2232
|
+
}
|
|
2233
|
+
const tasksV2Errors = validateTasksDocumentV2(content, expectedRef);
|
|
2234
|
+
if (tasksV2Errors.length > 0) {
|
|
2235
|
+
return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Final tasks.md does not satisfy sdd-tasks-doc-v2: ${tasksV2Errors.join('; ')}.`, 'Rewrite specs/<branch>/tasks.md as final tasks.md V2 with required execution topology sections before closure.');
|
|
2236
|
+
}
|
|
2237
|
+
const taskModel = parseSddTasksMarkdown(content, { tasksPath: expectedRef });
|
|
2238
|
+
const duplicateIdGap = taskModel.gaps.find((gap) => gap.field === 'id' && /Duplicate task id/i.test(gap.message));
|
|
2239
|
+
if (duplicateIdGap) {
|
|
2240
|
+
return rejectedTasksArtifact('not_accepted_duplicate_task_ids', 'invalid_proposal', duplicateIdGap.message, duplicateIdGap.recommendation);
|
|
2241
|
+
}
|
|
2242
|
+
const blockingTaskGap = taskModel.tasks.length === 0
|
|
2243
|
+
? { message: `Reviewed tasks artifact ${expectedRef} contains no executable sdd-task blocks.`, recommendation: 'Add at least one reviewed sdd-task block before closure.' }
|
|
2244
|
+
: taskModel.gaps.find((gap) => gap.severity === 'blocking') ?? null;
|
|
2245
|
+
if (blockingTaskGap) {
|
|
2246
|
+
return rejectedTasksArtifact('not_accepted_invalid_task_ids', 'invalid_proposal', blockingTaskGap.message, blockingTaskGap.recommendation);
|
|
2247
|
+
}
|
|
2248
|
+
const tasksHash = hashDocumentContent(content);
|
|
2249
|
+
if (!validTasksRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef, acceptedSpecRef, acceptedPlanRef, taskModel.tasks.length)) {
|
|
2250
|
+
return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Tasks close boundary facts must reference accepted spec, accepted plan, final ${expectedRef}, and parsed task unit count.`, 'Refresh the output close request from current runtime handoff and specs/<branch>/tasks.md.');
|
|
2251
|
+
}
|
|
2252
|
+
if (boundaryFacts.finalTasksHash !== tasksHash || boundaryFacts.finalTasksRef.hash !== tasksHash) {
|
|
2253
|
+
return rejectedTasksArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final tasks hash mismatch for ${expectedRef}: expected ${boundaryFacts.finalTasksHash}, actual ${tasksHash}.`, 'Refresh tasks close request with the current tasks.md hash before closure.');
|
|
2254
|
+
}
|
|
2255
|
+
const tasksContractHash = hashTasksContract(content);
|
|
2256
|
+
if (!tasksContractHash) {
|
|
2257
|
+
return rejectedTasksArtifact('not_accepted_invalid_task_ids', 'invalid_proposal', `Reviewed tasks artifact ${expectedRef} does not produce a task contract hash.`, 'Refresh tasks.md with reviewed executable task contracts before closure.');
|
|
2258
|
+
}
|
|
2259
|
+
return {
|
|
2260
|
+
acceptedTasksRef: { kind: 'document', ref: expectedRef, hash: tasksHash },
|
|
2261
|
+
tasksAcceptanceStatus: 'accepted',
|
|
2262
|
+
tasksHash,
|
|
2263
|
+
tasksContractHash,
|
|
2264
|
+
reasons: [`Runtime accepted reviewed canonical tasks artifact ${expectedRef}.`]
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
async function validateAcceptedExecuteArtifact(projectRoot, input) {
|
|
2268
|
+
if (!input.required) {
|
|
2269
|
+
return {
|
|
2270
|
+
acceptedExecuteRef: null,
|
|
2271
|
+
executeAcceptanceStatus: 'not_required_noop',
|
|
2272
|
+
laneEvidenceRefs: [],
|
|
2273
|
+
reasons: ['Execute evidence was not required by this lifecycle decision.']
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
if (input.blocked) {
|
|
2277
|
+
return rejectedExecuteArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute closure.', 'Resolve lifecycle blockers before requesting execute-stage closure.', 'runtime-blocked');
|
|
2278
|
+
}
|
|
2279
|
+
const upstreamIssue = await validateAcceptedExecuteTasksHandoff(projectRoot, input.partition, input.acceptedTasksHandoff);
|
|
2280
|
+
if (upstreamIssue) {
|
|
2281
|
+
return rejectedExecuteArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh tasks-stage closure and provide a fresh tasks -> execute handoff before execute closure.');
|
|
2282
|
+
}
|
|
2283
|
+
return validateExecuteProjectionReadiness(projectRoot, input.partition, input.acceptedTasksHandoff);
|
|
2284
|
+
}
|
|
2285
|
+
async function validateExecuteProjectionReadiness(projectRoot, partition, acceptedTasksHandoff) {
|
|
2286
|
+
const acceptedTasksRef = acceptedTasksRefFromExecuteHandoff(acceptedTasksHandoff);
|
|
2287
|
+
if (!acceptedTasksRef?.hash) {
|
|
2288
|
+
return rejectedExecuteArtifact('not_accepted_missing_upstream', 'missing_refs', 'Execute close requires a tasks -> execute handoff carrying accepted tasks ref/hash.', 'Refresh tasks-stage closure before requesting execute close.');
|
|
2289
|
+
}
|
|
2290
|
+
const [projectionRecords, dependencyGraphEnvelope] = await Promise.all([
|
|
2291
|
+
listRuntimeProjections(projectRoot, [TASK_UNIT_PROJECTION_TYPE, EXECUTION_LANE_PROJECTION_TYPE]),
|
|
2292
|
+
readTaskDependencyGraphProjection(projectRoot, partition)
|
|
2293
|
+
]);
|
|
2294
|
+
if (!dependencyGraphEnvelope || dependencyGraphEnvelope.payload.tasksHash !== acceptedTasksRef.hash) {
|
|
2295
|
+
return rejectedExecuteArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute dependency graph must be derived from accepted ${acceptedTasksRef.ref}.`, 'Rebuild execute task projections from the accepted tasks ref/hash before closing execute.');
|
|
2296
|
+
}
|
|
2297
|
+
const taskUnits = projectionRecords
|
|
2298
|
+
.filter((record) => record.projectionType === TASK_UNIT_PROJECTION_TYPE && record.scopeKey.startsWith(`${partition}:`))
|
|
2299
|
+
.map((record) => runtimeProjectionPayload(record.payload))
|
|
2300
|
+
.filter((unit) => Boolean(unit))
|
|
2301
|
+
.filter((unit) => unit.sourceRef.ref === acceptedTasksRef.ref && unit.sourceRef.hash === acceptedTasksRef.hash);
|
|
2302
|
+
if (taskUnits.length === 0) {
|
|
2303
|
+
return rejectedExecuteArtifact('not_accepted_wrong_ref', 'missing_refs', `No TaskUnitProjection records match accepted ${acceptedTasksRef.ref}.`, 'Rebuild execute task projections from the accepted tasks ref/hash before closing execute.');
|
|
2304
|
+
}
|
|
2305
|
+
const executionLanes = projectionRecords
|
|
2306
|
+
.filter((record) => record.projectionType === EXECUTION_LANE_PROJECTION_TYPE && record.scopeKey.startsWith(`${partition}:`))
|
|
2307
|
+
.map((record) => runtimeProjectionPayload(record.payload))
|
|
2308
|
+
.filter((lane) => Boolean(lane));
|
|
2309
|
+
const laneEvidenceRefs = executionLanes.map((lane) => ({ kind: 'projection', ref: `${EXECUTION_LANE_PROJECTION_TYPE}:${executionLaneScopeKey(partition, lane.laneId)}` }));
|
|
2310
|
+
const invalidTaskIds = uniqueStrings([...dependencyGraphEnvelope.payload.invalidTaskIds, ...taskUnits.filter((unit) => unit.dependencyState === 'invalid').map((unit) => unit.taskId)]);
|
|
2311
|
+
if (invalidTaskIds.length > 0) {
|
|
2312
|
+
return rejectedExecuteArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Execute task projections contain invalid task units: ${invalidTaskIds.join(', ')}.`, 'Repair accepted tasks.md and rebuild task projections before closing execute.');
|
|
2313
|
+
}
|
|
2314
|
+
const implementationUnits = taskUnits.filter((unit) => unit.unit.agentRouting.board === 'implementation');
|
|
2315
|
+
const validationUnits = taskUnits.filter((unit) => unit.unit.agentRouting.board === 'validation');
|
|
2316
|
+
if (implementationUnits.length === 0) {
|
|
2317
|
+
return rejectedExecuteArtifact('not_accepted_missing_implementation', 'missing_refs', 'Execute close requires at least one implementation-board task unit from accepted tasks.md.', 'Route accepted implementation tasks before closing execute.');
|
|
2318
|
+
}
|
|
2319
|
+
if (validationUnits.length === 0) {
|
|
2320
|
+
return rejectedExecuteArtifact('not_accepted_missing_validation', 'missing_refs', 'Execute close requires at least one validation-board task unit from accepted tasks.md.', 'Route implementation validation and goal validation tasks before closing execute.');
|
|
2321
|
+
}
|
|
2322
|
+
const incompleteImplementation = implementationUnits.filter((unit) => !isImplementationComplete(unit)).map((unit) => unit.taskId);
|
|
2323
|
+
if (incompleteImplementation.length > 0) {
|
|
2324
|
+
return rejectedExecuteArtifact('not_accepted_missing_implementation', 'missing_refs', `Implementation board is not complete for task units: ${incompleteImplementation.join(', ')}.`, 'Run implementation/review/debug lanes until implementation task units are implemented or validated before execute close.');
|
|
2325
|
+
}
|
|
2326
|
+
const openCheckpointIds = taskUnits.filter((unit) => unit.checkpointState === 'open' || unit.checkpointState === 'locked').map((unit) => `${unit.taskId}:checkpoint`);
|
|
2327
|
+
if (openCheckpointIds.length > 0) {
|
|
2328
|
+
return rejectedExecuteArtifact('not_accepted_missing_checkpoint', 'invalid_proposal', `Execute checkpoints are still open: ${openCheckpointIds.join(', ')}.`, 'Satisfy or resolve execute checkpoints before closing execute.');
|
|
2329
|
+
}
|
|
2330
|
+
const incompleteValidation = validationUnits.filter((unit) => !isValidationComplete(unit)).map((unit) => unit.taskId);
|
|
2331
|
+
if (incompleteValidation.length > 0) {
|
|
2332
|
+
return rejectedExecuteArtifact('not_accepted_missing_validation', 'missing_refs', `Validation board is not complete for task units: ${incompleteValidation.join(', ')}.`, 'Run validation and goal-validation lanes until validation task units are validated before execute close.');
|
|
2333
|
+
}
|
|
2334
|
+
const acceptedExecuteRef = {
|
|
2335
|
+
kind: 'projection',
|
|
2336
|
+
ref: `${TASK_DEPENDENCY_GRAPH_PROJECTION_TYPE}:${taskDependencyGraphScopeKey(partition)}`,
|
|
2337
|
+
hash: dependencyGraphEnvelope.payload.tasksHash
|
|
2338
|
+
};
|
|
2339
|
+
return {
|
|
2340
|
+
acceptedExecuteRef,
|
|
2341
|
+
executeAcceptanceStatus: 'accepted',
|
|
2342
|
+
laneEvidenceRefs,
|
|
2343
|
+
reasons: [`Runtime accepted execute task/lane projections for ${acceptedTasksRef.ref}.`]
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
async function validateAcceptedDoArtifact(projectRoot, input) {
|
|
2347
|
+
if (!input.required) {
|
|
2348
|
+
return {
|
|
2349
|
+
acceptedImplementationRef: null,
|
|
2350
|
+
implementationAcceptanceStatus: 'not_required_noop',
|
|
2351
|
+
implementationHash: null,
|
|
2352
|
+
changedFileRefs: [],
|
|
2353
|
+
reasons: ['Implementation artifact was not required by this lifecycle decision.']
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
if (input.blocked) {
|
|
2357
|
+
return rejectedDoArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute closure.', 'Resolve lifecycle blockers before requesting execute closure.', 'runtime-blocked');
|
|
2358
|
+
}
|
|
2359
|
+
if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
|
|
2360
|
+
return rejectedDoArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready execute implementation closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/execute-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
|
|
2361
|
+
}
|
|
2362
|
+
const upstreamIssue = await validateAcceptedTasksExecuteHandoff(projectRoot, input.partition, input.acceptedTasksHandoff);
|
|
2363
|
+
if (upstreamIssue) {
|
|
2364
|
+
return rejectedDoArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh tasks closure and provide a fresh tasks -> execute handoff before execute closure.');
|
|
2365
|
+
}
|
|
2366
|
+
const implementation = latestStageArtifact(input.registeredArtifacts, 'implementation_evidence');
|
|
2367
|
+
if (!implementation) {
|
|
2368
|
+
return rejectedDoArtifact('not_accepted_missing_implementation', 'missing_refs', 'Ready execute implementation closure must be backed by a registered implementer evidence artifact.', 'Ask implementer to write .sdd/runs/<branch>/execute/implementation-vN.md and register it before closure.');
|
|
2369
|
+
}
|
|
2370
|
+
const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
|
|
2371
|
+
if (!manager) {
|
|
2372
|
+
return rejectedDoArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready execute implementation closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/execute-manager-vN.md and register it before closure.');
|
|
2373
|
+
}
|
|
2374
|
+
if (manager.recommendation !== 'close_stage') {
|
|
2375
|
+
return rejectedDoArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept implementation state for execute validation handoff.`, 'Ask execute-manager to submit a close_stage recommendation after review blockers are resolved.');
|
|
2376
|
+
}
|
|
2377
|
+
const reviewByRef = manager.reviewRef
|
|
2378
|
+
? input.registeredArtifacts.find((record) => record.kind === 'code_review' && record.ref === manager.reviewRef?.ref) ?? null
|
|
2379
|
+
: null;
|
|
2380
|
+
if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
|
|
2381
|
+
return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered code review artifact.');
|
|
2382
|
+
}
|
|
2383
|
+
const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'code_review');
|
|
2384
|
+
if (!approvedReview) {
|
|
2385
|
+
return rejectedDoArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute implementation closure must include a registered code-reviewer artifact referenced by execute-manager.', 'Ask code-reviewer to write .sdd/runs/<branch>/execute/code-review-vN.md and ask execute-manager to reference that review hash.');
|
|
2386
|
+
}
|
|
2387
|
+
if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
|
|
2388
|
+
return rejectedDoArtifact('not_accepted_missing_review', 'missing_required_review', `Code-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept implementation state.`, 'Resolve review blockers and register an approved code-reviewer artifact before closure.');
|
|
2389
|
+
}
|
|
2390
|
+
if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== implementation.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== implementation.ref) {
|
|
2391
|
+
return rejectedDoArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute implementation closure must reference reviewed ${implementation.ref} as the implementation evidence artifact.`, 'Refresh code-reviewer and execute-manager artifacts so targetRef points at the implementation evidence artifact.');
|
|
2392
|
+
}
|
|
2393
|
+
const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
|
|
2394
|
+
const mismatchedHash = expectedHashes.find((hash) => hash !== implementation.hash);
|
|
2395
|
+
if (mismatchedHash) {
|
|
2396
|
+
return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed implementation evidence hash mismatch for ${implementation.ref}: expected ${mismatchedHash}, actual ${implementation.hash}.`, 'Refresh code-reviewer and execute-manager artifacts with the current implementation evidence hash before closure.');
|
|
2397
|
+
}
|
|
2398
|
+
const changedFileParse = parseImplementationChangedFileRefs(implementation);
|
|
2399
|
+
if (changedFileParse.issue) {
|
|
2400
|
+
return rejectedDoArtifact('not_accepted_boundary_violation', 'invalid_proposal', changedFileParse.issue, 'Refresh implementation evidence with changedFiles entries that include safe refs and current hashes.');
|
|
2401
|
+
}
|
|
2402
|
+
const changedFileRefs = changedFileParse.refs;
|
|
2403
|
+
const boundaryIssue = validateAllowedChangedFileRefs(changedFileRefs, input.allowedChangedFileRefs);
|
|
2404
|
+
if (boundaryIssue) {
|
|
2405
|
+
return rejectedDoArtifact('not_accepted_boundary_violation', 'invalid_proposal', boundaryIssue, 'Restrict execute implementation evidence to the task boundary or update the allowed changed-file refs.');
|
|
2406
|
+
}
|
|
2407
|
+
const changedFileHashIssue = await validateChangedFileHashes(projectRoot, changedFileRefs);
|
|
2408
|
+
if (changedFileHashIssue) {
|
|
2409
|
+
return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', changedFileHashIssue, 'Refresh implementation, code-reviewer, and execute-manager artifacts with current changed-file hashes before closure.');
|
|
2410
|
+
}
|
|
2411
|
+
return {
|
|
2412
|
+
acceptedImplementationRef: { kind: 'artifact', ref: implementation.ref, hash: implementation.hash },
|
|
2413
|
+
implementationAcceptanceStatus: 'accepted',
|
|
2414
|
+
implementationHash: implementation.hash,
|
|
2415
|
+
changedFileRefs,
|
|
2416
|
+
reasons: [`Runtime accepted reviewed implementation evidence ${implementation.ref}.`]
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
async function validateAcceptedTestArtifact(projectRoot, input) {
|
|
2420
|
+
if (!input.required) {
|
|
2421
|
+
return {
|
|
2422
|
+
acceptedTestEvidenceRef: null,
|
|
2423
|
+
testAcceptanceStatus: 'not_required_noop',
|
|
2424
|
+
testEvidenceHash: null,
|
|
2425
|
+
reasons: ['Test evidence was not required by this lifecycle decision.']
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
if (input.blocked) {
|
|
2429
|
+
return rejectedTestArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute validation closure.', 'Resolve lifecycle blockers before requesting execute validation closure.', 'runtime-blocked');
|
|
2430
|
+
}
|
|
2431
|
+
if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
|
|
2432
|
+
return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute validation closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/validation-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
|
|
2433
|
+
}
|
|
2434
|
+
const upstreamIssue = await validateAcceptedDoHandoff(projectRoot, input.partition, input.acceptedDoHandoff);
|
|
2435
|
+
if (upstreamIssue) {
|
|
2436
|
+
return rejectedTestArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh execute implementation closure before execute validation closure.');
|
|
2437
|
+
}
|
|
2438
|
+
const execution = latestStageArtifact(input.registeredArtifacts, 'test_execution');
|
|
2439
|
+
if (!execution) {
|
|
2440
|
+
return rejectedTestArtifact('not_accepted_missing_execution', 'missing_refs', 'Ready execute validation closure must be backed by a registered validation execution artifact.', 'Ask validator to write .sdd/runs/<branch>/execute/test-execution-vN.md and register it before closure.');
|
|
2441
|
+
}
|
|
2442
|
+
const validation = latestStageArtifact(input.registeredArtifacts, 'validation_evidence');
|
|
2443
|
+
if (!validation) {
|
|
2444
|
+
return rejectedTestArtifact('not_accepted_missing_validation', 'missing_refs', 'Ready execute validation closure must be backed by a registered validator evidence artifact.', 'Ask validator to write .sdd/runs/<branch>/execute/validation-vN.md and register it before closure.');
|
|
2445
|
+
}
|
|
2446
|
+
const executionStatus = execution.frontmatter.status;
|
|
2447
|
+
const executionExitCode = execution.frontmatter.exitCode;
|
|
2448
|
+
if (executionStatus !== 'passed' || executionExitCode !== 0) {
|
|
2449
|
+
return rejectedTestArtifact('not_accepted_failed_tests', 'invalid_proposal', `Validation execution ${execution.ref} status ${String(executionStatus)} with exitCode ${String(executionExitCode)} cannot close execute validation.`, 'Rerun failing validation commands and register passing validation execution evidence before closure.');
|
|
2450
|
+
}
|
|
2451
|
+
const validationExecutionRef = executionRefFromValidation(validation);
|
|
2452
|
+
if (validationExecutionRef !== execution.ref) {
|
|
2453
|
+
return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Validation evidence ${validation.ref} must reference execution evidence ${execution.ref}.`, 'Refresh validation evidence so executionRef points at the registered validation execution artifact.');
|
|
2454
|
+
}
|
|
2455
|
+
const validationExecutionHash = validation.frontmatter.executionHash;
|
|
2456
|
+
if (validationExecutionHash !== execution.hash) {
|
|
2457
|
+
return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Validation evidence execution hash mismatch for ${execution.ref}: expected ${String(validationExecutionHash)}, actual ${execution.hash}.`, 'Refresh validation evidence with the current validation execution hash before closure.');
|
|
2458
|
+
}
|
|
2459
|
+
if (validation.frontmatter.status !== 'passed') {
|
|
2460
|
+
return rejectedTestArtifact('not_accepted_failed_tests', 'invalid_proposal', `Validation evidence ${validation.ref} status ${String(validation.frontmatter.status)} cannot close execute validation.`, 'Resolve validation failures and register passing validation evidence before closure.');
|
|
2461
|
+
}
|
|
2462
|
+
if (validation.frontmatter.acceptanceMapped !== true) {
|
|
2463
|
+
return rejectedTestArtifact('not_accepted_unmapped_acceptance', 'invalid_proposal', `Validation evidence ${validation.ref} must declare acceptanceMapped: true.`, 'Map validation evidence to accepted task/verify criteria before requesting execute validation closure.');
|
|
2464
|
+
}
|
|
2465
|
+
const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
|
|
2466
|
+
if (!manager) {
|
|
2467
|
+
return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute validation closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/validation-manager-vN.md and register it before closure.');
|
|
2468
|
+
}
|
|
2469
|
+
if (manager.recommendation !== 'close_stage') {
|
|
2470
|
+
return rejectedTestArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept validation evidence for execute evidence judgment.`, 'Ask execute-manager to submit a close_stage recommendation after validation blockers are resolved.');
|
|
2471
|
+
}
|
|
2472
|
+
const reviewByRef = manager.reviewRef
|
|
2473
|
+
? input.registeredArtifacts.find((record) => record.kind === 'test_review' && record.ref === manager.reviewRef?.ref) ?? null
|
|
2474
|
+
: null;
|
|
2475
|
+
if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
|
|
2476
|
+
return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered validation review artifact.');
|
|
2477
|
+
}
|
|
2478
|
+
const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'test_review');
|
|
2479
|
+
if (!approvedReview) {
|
|
2480
|
+
return rejectedTestArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute validation closure must include a registered validation-reviewer artifact referenced by execute-manager.', 'Ask validation-reviewer to write .sdd/runs/<branch>/execute/validation-review-vN.md and ask execute-manager to reference that review hash.');
|
|
2481
|
+
}
|
|
2482
|
+
if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
|
|
2483
|
+
return rejectedTestArtifact('not_accepted_missing_review', 'missing_required_review', `Validation-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept validation evidence.`, 'Resolve review blockers and register an approved validation-reviewer artifact before closure.');
|
|
2484
|
+
}
|
|
2485
|
+
if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== validation.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== validation.ref) {
|
|
2486
|
+
return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute validation closure must reference reviewed ${validation.ref} as the validation evidence artifact.`, 'Refresh validation-reviewer and execute-manager artifacts so targetRef points at the validation evidence artifact.');
|
|
2487
|
+
}
|
|
2488
|
+
const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
|
|
2489
|
+
const mismatchedHash = expectedHashes.find((hash) => hash !== validation.hash);
|
|
2490
|
+
if (mismatchedHash) {
|
|
2491
|
+
return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed validation evidence hash mismatch for ${validation.ref}: expected ${mismatchedHash}, actual ${validation.hash}.`, 'Refresh validation-reviewer and execute-manager artifacts with the current validation evidence hash before closure.');
|
|
2492
|
+
}
|
|
2493
|
+
return {
|
|
2494
|
+
acceptedTestEvidenceRef: { kind: 'artifact', ref: validation.ref, hash: validation.hash },
|
|
2495
|
+
testAcceptanceStatus: 'accepted',
|
|
2496
|
+
testEvidenceHash: validation.hash,
|
|
2497
|
+
reasons: [`Runtime accepted reviewed execute validation evidence ${validation.ref}.`]
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
async function validateAcceptedEvidenceJudgmentArtifact(projectRoot, input) {
|
|
2501
|
+
if (!input.required) {
|
|
2502
|
+
return {
|
|
2503
|
+
acceptedEvidenceJudgmentRef: null,
|
|
2504
|
+
evidenceJudgmentAcceptanceStatus: 'not_required_noop',
|
|
2505
|
+
evidenceJudgmentHash: null,
|
|
2506
|
+
reasons: ['Evidence judgment was not required by this lifecycle decision.'],
|
|
2507
|
+
truthAlignment: null
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
if (input.blocked) {
|
|
2511
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute evidence judgment closure.', 'Resolve lifecycle blockers before requesting execute evidence judgment closure.', 'runtime-blocked');
|
|
2512
|
+
}
|
|
2513
|
+
if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
|
|
2514
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute evidence judgment closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/evidence-judgment-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
|
|
2515
|
+
}
|
|
2516
|
+
const upstreamIssue = await validateAcceptedTestHandoff(projectRoot, input.partition, input.acceptedTestHandoff);
|
|
2517
|
+
if (upstreamIssue) {
|
|
2518
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh execute validation closure before execute evidence judgment closure.');
|
|
2519
|
+
}
|
|
2520
|
+
const verification = latestStageArtifact(input.registeredArtifacts, 'evidence_judgment');
|
|
2521
|
+
if (!verification) {
|
|
2522
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_missing_verification', 'missing_refs', 'Ready execute evidence judgment closure must be backed by a registered evidence-judgment artifact.', 'Ask evidence-judgment agent to write .sdd/runs/<branch>/execute/evidence-judgment-vN.md and register it before closure.');
|
|
2523
|
+
}
|
|
2524
|
+
if (verification.frontmatter.status !== 'passed') {
|
|
2525
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_failed_verification', 'invalid_proposal', `Evidence judgment ${verification.ref} status ${String(verification.frontmatter.status)} cannot close execute evidence judgment.`, 'Resolve evidence judgment failures and register passing evidence judgment evidence before closure.');
|
|
2526
|
+
}
|
|
2527
|
+
if (verification.frontmatter.coverageComplete !== true) {
|
|
2528
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_incomplete_coverage', 'invalid_proposal', `Evidence judgment ${verification.ref} must declare coverageComplete: true.`, 'Complete acceptance coverage before requesting execute evidence judgment closure.');
|
|
2529
|
+
}
|
|
2530
|
+
const durableGapCount = verification.frontmatter.durableGapCount;
|
|
2531
|
+
const openGapCount = verification.frontmatter.openGapCount;
|
|
2532
|
+
if ((typeof durableGapCount === 'number' && durableGapCount > 0) || (typeof openGapCount === 'number' && openGapCount > 0)) {
|
|
2533
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_open_gap', 'invalid_proposal', `Evidence judgment ${verification.ref} still declares open durable gaps.`, 'Resolve durable gaps before requesting execute evidence judgment closure.');
|
|
2534
|
+
}
|
|
2535
|
+
const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
|
|
2536
|
+
if (!manager) {
|
|
2537
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute evidence judgment closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/evidence-judgment-manager-vN.md and register it before closure.');
|
|
2538
|
+
}
|
|
2539
|
+
if (manager.recommendation !== 'close_stage') {
|
|
2540
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept evidence judgment for truthAlignment and ship readiness.`, 'Ask execute-manager to submit a close_stage recommendation after evidence judgment blockers are resolved.');
|
|
2541
|
+
}
|
|
2542
|
+
const reviewByRef = manager.reviewRef
|
|
2543
|
+
? input.registeredArtifacts.find((record) => record.kind === 'evidence_review' && record.ref === manager.reviewRef?.ref) ?? null
|
|
2544
|
+
: null;
|
|
2545
|
+
if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
|
|
2546
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered evidence review artifact.');
|
|
2547
|
+
}
|
|
2548
|
+
const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'evidence_review');
|
|
2549
|
+
if (!approvedReview) {
|
|
2550
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute evidence judgment closure must include a registered evidence-reviewer artifact referenced by execute-manager.', 'Ask evidence-reviewer to write .sdd/runs/<branch>/execute/evidence-review-vN.md and ask execute-manager to reference that review hash.');
|
|
2551
|
+
}
|
|
2552
|
+
if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
|
|
2553
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_missing_review', 'missing_required_review', `Evidence-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept evidence judgment.`, 'Resolve review blockers and register an approved evidence-reviewer artifact before closure.');
|
|
2554
|
+
}
|
|
2555
|
+
if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== verification.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== verification.ref) {
|
|
2556
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute evidence judgment closure must reference reviewed ${verification.ref} as the evidence judgment artifact.`, 'Refresh evidence-reviewer and execute-manager artifacts so targetRef points at the evidence judgment artifact.');
|
|
2557
|
+
}
|
|
2558
|
+
const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
|
|
2559
|
+
const mismatchedHash = expectedHashes.find((hash) => hash !== verification.hash);
|
|
2560
|
+
if (mismatchedHash) {
|
|
2561
|
+
return rejectedEvidenceJudgmentArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed evidence judgment hash mismatch for ${verification.ref}: expected ${mismatchedHash}, actual ${verification.hash}.`, 'Refresh evidence-reviewer and execute-manager artifacts with the current evidence judgment hash before closure.');
|
|
2562
|
+
}
|
|
2563
|
+
return {
|
|
2564
|
+
acceptedEvidenceJudgmentRef: { kind: 'artifact', ref: verification.ref, hash: verification.hash },
|
|
2565
|
+
evidenceJudgmentAcceptanceStatus: 'accepted',
|
|
2566
|
+
evidenceJudgmentHash: verification.hash,
|
|
2567
|
+
reasons: [`Runtime accepted reviewed evidence judgment evidence ${verification.ref}.`],
|
|
2568
|
+
truthAlignment: parseEvidenceJudgmentTruthAlignment(verification)
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
async function validateAcceptedShipReadinessArtifact(projectRoot, input) {
|
|
2572
|
+
if (!input.required) {
|
|
2573
|
+
return {
|
|
2574
|
+
acceptedShipReadinessRef: null,
|
|
2575
|
+
shipReadinessStatus: 'not_required_noop',
|
|
2576
|
+
shipReadinessHash: null,
|
|
2577
|
+
releaseDocumentRef: null,
|
|
2578
|
+
reasons: ['Ship was not required by this lifecycle decision.']
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
if (input.blocked) {
|
|
2582
|
+
return rejectedShipReadinessArtifact('not_accepted_blocked', 'blocked_lifecycle', input.blockedReason ?? 'Lifecycle risk decision blocks ship closure.', 'Resolve ship blockers before requesting ship-stage closure.', 'runtime-blocked');
|
|
2583
|
+
}
|
|
2584
|
+
if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
|
|
2585
|
+
return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready ship closure must be backed by a validated ship StageCollaborationContract.', 'Ask ship-manager to write .sdd/runs/<branch>/ship/ship-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
|
|
2586
|
+
}
|
|
2587
|
+
const truthAlignmentIssue = await validateAcceptedTruthAlignment(projectRoot, input.partition, input.truthAlignment);
|
|
2588
|
+
if (truthAlignmentIssue) {
|
|
2589
|
+
return rejectedShipReadinessArtifact('not_accepted_missing_upstream', 'missing_refs', truthAlignmentIssue, 'Refresh execute evidence judgment closure so runtime records an aligned truthAlignment projection before ship closure.');
|
|
2590
|
+
}
|
|
2591
|
+
const authorityIssue = validateNoRuntimeAuthorityAttempts(input.registeredArtifacts);
|
|
2592
|
+
if (authorityIssue) {
|
|
2593
|
+
return rejectedShipReadinessArtifact('not_accepted_authority_violation', 'authority_violation', authorityIssue, 'Remove workflow-authority attempts from ship evidence before requesting runtime closure.');
|
|
2594
|
+
}
|
|
2595
|
+
const readiness = latestStageArtifact(input.registeredArtifacts, 'ship_readiness');
|
|
2596
|
+
if (!readiness) {
|
|
2597
|
+
return rejectedShipReadinessArtifact('not_accepted_missing_readiness', 'missing_refs', 'Ready ship closure must be backed by a registered ship-validator readiness artifact.', 'Ask ship-validator to write .sdd/runs/<branch>/ship/ship-readiness-vN.md and register it before closure.');
|
|
2598
|
+
}
|
|
2599
|
+
if (readiness.frontmatter.status !== 'ready') {
|
|
2600
|
+
return rejectedShipReadinessArtifact('not_accepted_rejected', 'invalid_proposal', `Ship readiness ${readiness.ref} status ${String(readiness.frontmatter.status)} cannot mark the project ship-ready.`, 'Resolve ship readiness failures and register ready ship evidence before closure.');
|
|
2601
|
+
}
|
|
2602
|
+
const openBlockerCount = readinessBlockerCount(readiness);
|
|
2603
|
+
if (openBlockerCount > 0) {
|
|
2604
|
+
return rejectedShipReadinessArtifact('not_accepted_open_blocker', 'invalid_proposal', `Ship readiness ${readiness.ref} still declares ${openBlockerCount} open blockers.`, 'Resolve doctor, capability, repair, gap, and migration blockers before requesting ship closure.');
|
|
2605
|
+
}
|
|
2606
|
+
const releaseDocument = await parseShipReleaseDocumentRef(projectRoot, input.partition, readiness);
|
|
2607
|
+
if (releaseDocument.issue) {
|
|
2608
|
+
return rejectedShipReadinessArtifact('not_accepted_release_drift', 'invalid_proposal', releaseDocument.issue, 'Refresh release documentation and ship readiness releaseRef/releaseHash before requesting ship closure.');
|
|
2609
|
+
}
|
|
2610
|
+
const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
|
|
2611
|
+
if (!manager) {
|
|
2612
|
+
return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready ship closure must be backed by a registered ship-manager closure artifact.', 'Ask ship-manager to write .sdd/runs/<branch>/ship/ship-manager-vN.md and register it before closure.');
|
|
2613
|
+
}
|
|
2614
|
+
if (manager.recommendation !== 'close_stage') {
|
|
2615
|
+
return rejectedShipReadinessArtifact('not_accepted_rejected', 'invalid_proposal', `Ship-manager recommendation ${manager.recommendation ?? 'none'} cannot mark the project ship-ready.`, 'Ask ship-manager to submit a close_stage recommendation after ship blockers are resolved.');
|
|
2616
|
+
}
|
|
2617
|
+
const reviewByRef = manager.reviewRef
|
|
2618
|
+
? input.registeredArtifacts.find((record) => record.kind === 'release_review' && record.ref === manager.reviewRef?.ref) ?? null
|
|
2619
|
+
: null;
|
|
2620
|
+
if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
|
|
2621
|
+
return rejectedShipReadinessArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Ship-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh ship-manager closure artifact so reviewRef/reviewHash point at the registered release review artifact.');
|
|
2622
|
+
}
|
|
2623
|
+
const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'release_review');
|
|
2624
|
+
if (!approvedReview) {
|
|
2625
|
+
return rejectedShipReadinessArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready ship closure must include a registered release-reviewer artifact referenced by ship-manager.', 'Ask release-reviewer to write .sdd/runs/<branch>/ship/release-review-vN.md and ask ship-manager to reference that review hash.');
|
|
2626
|
+
}
|
|
2627
|
+
if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
|
|
2628
|
+
return rejectedShipReadinessArtifact('not_accepted_missing_review', 'missing_required_review', `Release-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot mark the project ship-ready.`, 'Resolve release review blockers and register an approved release-reviewer artifact before closure.');
|
|
2629
|
+
}
|
|
2630
|
+
if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== readiness.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== readiness.ref) {
|
|
2631
|
+
return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready ship closure must reference reviewed ${readiness.ref} as the ship readiness artifact.`, 'Refresh release-reviewer and ship-manager artifacts so targetRef points at the ship readiness artifact.');
|
|
2632
|
+
}
|
|
2633
|
+
const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
|
|
2634
|
+
const mismatchedHash = expectedHashes.find((hash) => hash !== readiness.hash);
|
|
2635
|
+
if (mismatchedHash) {
|
|
2636
|
+
return rejectedShipReadinessArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed ship readiness hash mismatch for ${readiness.ref}: expected ${mismatchedHash}, actual ${readiness.hash}.`, 'Refresh release-reviewer and ship-manager artifacts with the current ship readiness hash before closure.');
|
|
2637
|
+
}
|
|
2638
|
+
return {
|
|
2639
|
+
acceptedShipReadinessRef: { kind: 'artifact', ref: readiness.ref, hash: readiness.hash },
|
|
2640
|
+
shipReadinessStatus: 'accepted',
|
|
2641
|
+
shipReadinessHash: readiness.hash,
|
|
2642
|
+
releaseDocumentRef: releaseDocument.ref,
|
|
2643
|
+
reasons: [`Runtime accepted reviewed ship readiness evidence ${readiness.ref}.`]
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
async function validateAcceptedPlanHandoff(projectRoot, partition, handoff) {
|
|
2647
|
+
const expectedSpecRef = `specs/${partition}/spec.md`;
|
|
2648
|
+
const expectedPlanRef = `specs/${partition}/plan.md`;
|
|
2649
|
+
if (!handoff) {
|
|
2650
|
+
return 'Tasks closure requires a recorded plan -> tasks handoff.';
|
|
2651
|
+
}
|
|
2652
|
+
if (handoff.fromStage !== 'plan' || handoff.toStage !== 'tasks' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2653
|
+
return 'Tasks closure requires a non-blocking plan -> tasks handoff.';
|
|
2654
|
+
}
|
|
2655
|
+
const acceptedSpecRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
|
|
2656
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef);
|
|
2657
|
+
if (!acceptedSpecRef?.hash) {
|
|
2658
|
+
return `Plan -> tasks handoff must carry accepted ${expectedSpecRef} with a content hash.`;
|
|
2659
|
+
}
|
|
2660
|
+
const specContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
|
|
2661
|
+
if (specContent === null) {
|
|
2662
|
+
return `Accepted upstream spec artifact is missing at ${expectedSpecRef}.`;
|
|
2663
|
+
}
|
|
2664
|
+
const currentSpecHash = hashDocumentContent(specContent);
|
|
2665
|
+
if (acceptedSpecRef.hash !== currentSpecHash) {
|
|
2666
|
+
return `Accepted upstream spec hash is stale for ${expectedSpecRef}: expected ${acceptedSpecRef.hash}, actual ${currentSpecHash}.`;
|
|
2667
|
+
}
|
|
2668
|
+
const acceptedPlanRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedPlanRef)
|
|
2669
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedPlanRef);
|
|
2670
|
+
if (!acceptedPlanRef?.hash) {
|
|
2671
|
+
return `Plan -> tasks handoff must carry accepted ${expectedPlanRef} with a content hash.`;
|
|
2672
|
+
}
|
|
2673
|
+
const planContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'plan.md'));
|
|
2674
|
+
if (planContent === null) {
|
|
2675
|
+
return `Accepted upstream plan artifact is missing at ${expectedPlanRef}.`;
|
|
2676
|
+
}
|
|
2677
|
+
const currentPlanHash = hashDocumentContent(planContent);
|
|
2678
|
+
if (acceptedPlanRef.hash !== currentPlanHash) {
|
|
2679
|
+
return `Accepted upstream plan hash is stale for ${expectedPlanRef}: expected ${acceptedPlanRef.hash}, actual ${currentPlanHash}.`;
|
|
2680
|
+
}
|
|
2681
|
+
return null;
|
|
2682
|
+
}
|
|
2683
|
+
async function validateAcceptedExecuteTasksHandoff(projectRoot, partition, handoff) {
|
|
2684
|
+
const expectedTasksRef = `specs/${partition}/tasks.md`;
|
|
2685
|
+
if (!handoff) {
|
|
2686
|
+
return 'Execute closure requires a recorded tasks -> execute handoff.';
|
|
2687
|
+
}
|
|
2688
|
+
if (handoff.fromStage !== 'tasks' || handoff.toStage !== 'execute' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2689
|
+
return 'Execute closure requires a non-blocking tasks -> execute handoff.';
|
|
2690
|
+
}
|
|
2691
|
+
const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
|
|
2692
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
|
|
2693
|
+
if (!acceptedTasksRef?.hash) {
|
|
2694
|
+
return `Tasks -> execute handoff must carry accepted ${expectedTasksRef} with a content hash.`;
|
|
2695
|
+
}
|
|
2696
|
+
const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
|
|
2697
|
+
if (tasksContent === null) {
|
|
2698
|
+
return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
|
|
2699
|
+
}
|
|
2700
|
+
const currentTasksHash = hashDocumentContent(tasksContent);
|
|
2701
|
+
if (acceptedTasksRef.hash !== currentTasksHash) {
|
|
2702
|
+
return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
|
|
2703
|
+
}
|
|
2704
|
+
return null;
|
|
2705
|
+
}
|
|
2706
|
+
function acceptedTasksRefFromExecuteHandoff(handoff) {
|
|
2707
|
+
if (!handoff) {
|
|
2708
|
+
return null;
|
|
2709
|
+
}
|
|
2710
|
+
return handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref.endsWith('/tasks.md'))
|
|
2711
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref.endsWith('/tasks.md'))
|
|
2712
|
+
?? null;
|
|
2713
|
+
}
|
|
2714
|
+
function isImplementationComplete(unit) {
|
|
2715
|
+
return unit.executionState === 'implemented' || unit.executionState === 'validated' || unit.evidenceState === 'accepted';
|
|
2716
|
+
}
|
|
2717
|
+
function isValidationComplete(unit) {
|
|
2718
|
+
return unit.executionState === 'validated' || unit.evidenceState === 'accepted';
|
|
2719
|
+
}
|
|
2720
|
+
async function validateAcceptedTasksHandoff(projectRoot, partition, handoff) {
|
|
2721
|
+
const expectedTasksRef = `specs/${partition}/tasks.md`;
|
|
2722
|
+
if (!handoff) {
|
|
2723
|
+
return 'Verification-design closure requires a recorded plan -> tasks handoff.';
|
|
2724
|
+
}
|
|
2725
|
+
if (handoff.fromStage !== 'plan' || handoff.toStage !== 'tasks' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2726
|
+
return 'Verification-design closure requires a non-blocking plan -> tasks handoff.';
|
|
2727
|
+
}
|
|
2728
|
+
const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
|
|
2729
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
|
|
2730
|
+
if (!acceptedTasksRef?.hash) {
|
|
2731
|
+
return `Plan -> tasks handoff must carry accepted ${expectedTasksRef} with a content hash.`;
|
|
2732
|
+
}
|
|
2733
|
+
const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
|
|
2734
|
+
if (tasksContent === null) {
|
|
2735
|
+
return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
|
|
2736
|
+
}
|
|
2737
|
+
const currentTasksHash = hashDocumentContent(tasksContent);
|
|
2738
|
+
if (acceptedTasksRef.hash !== currentTasksHash) {
|
|
2739
|
+
return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
|
|
2740
|
+
}
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
async function validateAcceptedTasksExecuteHandoff(projectRoot, partition, handoff) {
|
|
2744
|
+
const expectedTasksRef = `specs/${partition}/tasks.md`;
|
|
2745
|
+
if (!handoff) {
|
|
2746
|
+
return 'Execute closure requires a recorded tasks -> execute handoff.';
|
|
2747
|
+
}
|
|
2748
|
+
if (handoff.fromStage !== 'tasks' || handoff.toStage !== 'execute' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2749
|
+
return 'Execute closure requires a non-blocking tasks -> execute handoff.';
|
|
2750
|
+
}
|
|
2751
|
+
const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
|
|
2752
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
|
|
2753
|
+
if (!acceptedTasksRef?.hash) {
|
|
2754
|
+
return `Tasks -> execute handoff must carry accepted ${expectedTasksRef} with a content hash.`;
|
|
2755
|
+
}
|
|
2756
|
+
const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
|
|
2757
|
+
if (tasksContent === null) {
|
|
2758
|
+
return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
|
|
2759
|
+
}
|
|
2760
|
+
const currentTasksHash = hashDocumentContent(tasksContent);
|
|
2761
|
+
if (acceptedTasksRef.hash !== currentTasksHash) {
|
|
2762
|
+
return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
|
|
2763
|
+
}
|
|
2764
|
+
return null;
|
|
2765
|
+
}
|
|
2766
|
+
async function validateAcceptedDoHandoff(projectRoot, partition, handoff) {
|
|
2767
|
+
if (!handoff) {
|
|
2768
|
+
return 'Validation lane closure requires a recorded execute -> ship handoff.';
|
|
2769
|
+
}
|
|
2770
|
+
if (handoff.fromStage !== 'execute' || handoff.toStage !== 'ship' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2771
|
+
return 'Validation lane closure requires a non-blocking execute -> ship handoff.';
|
|
2772
|
+
}
|
|
2773
|
+
const acceptedImplementationRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'artifact' && inputRef.ref.startsWith(`.sdd/runs/${partition}/execute/implementation-`))
|
|
2774
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'artifact' && outputRef.ref.startsWith(`.sdd/runs/${partition}/execute/implementation-`));
|
|
2775
|
+
if (!acceptedImplementationRef?.hash) {
|
|
2776
|
+
return `Execute -> ship handoff must carry accepted .sdd/runs/${partition}/execute/implementation-vN.md with a content hash.`;
|
|
2777
|
+
}
|
|
2778
|
+
return validateHandoffInputHashes(projectRoot, handoff.requiredInputRefs);
|
|
2779
|
+
}
|
|
2780
|
+
async function validateAcceptedTestHandoff(projectRoot, partition, handoff) {
|
|
2781
|
+
if (!handoff) {
|
|
2782
|
+
return 'Evidence-judgment lane closure requires a recorded execute -> ship handoff.';
|
|
2783
|
+
}
|
|
2784
|
+
if (handoff.fromStage !== 'execute' || handoff.toStage !== 'ship' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2785
|
+
return 'Evidence-judgment lane closure requires a non-blocking execute -> ship handoff.';
|
|
2786
|
+
}
|
|
2787
|
+
const acceptedTestRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'artifact' && inputRef.ref.startsWith(`.sdd/runs/${partition}/execute/validation-`))
|
|
2788
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'artifact' && outputRef.ref.startsWith(`.sdd/runs/${partition}/execute/validation-`));
|
|
2789
|
+
if (!acceptedTestRef?.hash) {
|
|
2790
|
+
return `Execute -> ship handoff must carry accepted .sdd/runs/${partition}/execute/validation-vN.md with a content hash.`;
|
|
2791
|
+
}
|
|
2792
|
+
return validateHandoffInputHashes(projectRoot, handoff.requiredInputRefs);
|
|
2793
|
+
}
|
|
2794
|
+
async function validateAcceptedSpecHandoff(projectRoot, partition, handoff) {
|
|
2795
|
+
const expectedSpecRef = `specs/${partition}/spec.md`;
|
|
2796
|
+
if (!handoff) {
|
|
2797
|
+
return 'Plan closure requires a recorded spec -> plan handoff.';
|
|
2798
|
+
}
|
|
2799
|
+
if (handoff.fromStage !== 'spec' || handoff.toStage !== 'plan' || handoff.status === 'blocked' || handoff.status === 'rejected') {
|
|
2800
|
+
return 'Plan closure requires a non-blocking spec -> plan handoff.';
|
|
2801
|
+
}
|
|
2802
|
+
const acceptedSpecRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
|
|
2803
|
+
?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef);
|
|
2804
|
+
if (!acceptedSpecRef?.hash) {
|
|
2805
|
+
return `Spec -> plan handoff must carry accepted ${expectedSpecRef} with a content hash.`;
|
|
2806
|
+
}
|
|
2807
|
+
const specContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
|
|
2808
|
+
if (specContent === null) {
|
|
2809
|
+
return `Accepted upstream spec artifact is missing at ${expectedSpecRef}.`;
|
|
2810
|
+
}
|
|
2811
|
+
const currentSpecHash = hashDocumentContent(specContent);
|
|
2812
|
+
if (acceptedSpecRef.hash !== currentSpecHash) {
|
|
2813
|
+
return `Accepted upstream spec hash is stale for ${expectedSpecRef}: expected ${acceptedSpecRef.hash}, actual ${currentSpecHash}.`;
|
|
2814
|
+
}
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
function rejectedPlanArtifact(planAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2818
|
+
return {
|
|
2819
|
+
acceptedPlanRef: null,
|
|
2820
|
+
planAcceptanceStatus,
|
|
2821
|
+
planHash: null,
|
|
2822
|
+
planContractHash: null,
|
|
2823
|
+
reasons: [explanation],
|
|
2824
|
+
rejectionIssue: {
|
|
2825
|
+
reasonCode,
|
|
2826
|
+
explanation,
|
|
2827
|
+
requiredNextAction,
|
|
2828
|
+
fallbackRoute
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
function rejectedTasksArtifact(tasksAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2833
|
+
return {
|
|
2834
|
+
acceptedTasksRef: null,
|
|
2835
|
+
tasksAcceptanceStatus,
|
|
2836
|
+
tasksHash: null,
|
|
2837
|
+
tasksContractHash: null,
|
|
2838
|
+
reasons: [explanation],
|
|
2839
|
+
rejectionIssue: {
|
|
2840
|
+
reasonCode,
|
|
2841
|
+
explanation,
|
|
2842
|
+
requiredNextAction,
|
|
2843
|
+
fallbackRoute
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
function rejectedExecuteArtifact(executeAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2848
|
+
return {
|
|
2849
|
+
acceptedExecuteRef: null,
|
|
2850
|
+
executeAcceptanceStatus,
|
|
2851
|
+
laneEvidenceRefs: [],
|
|
2852
|
+
reasons: [explanation],
|
|
2853
|
+
rejectionIssue: {
|
|
2854
|
+
reasonCode,
|
|
2855
|
+
explanation,
|
|
2856
|
+
requiredNextAction,
|
|
2857
|
+
fallbackRoute
|
|
2858
|
+
}
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
function rejectedDoArtifact(implementationAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2862
|
+
return {
|
|
2863
|
+
acceptedImplementationRef: null,
|
|
2864
|
+
implementationAcceptanceStatus,
|
|
2865
|
+
implementationHash: null,
|
|
2866
|
+
changedFileRefs: [],
|
|
2867
|
+
reasons: [explanation],
|
|
2868
|
+
rejectionIssue: {
|
|
2869
|
+
reasonCode,
|
|
2870
|
+
explanation,
|
|
2871
|
+
requiredNextAction,
|
|
2872
|
+
fallbackRoute
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
function rejectedTestArtifact(testAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2877
|
+
return {
|
|
2878
|
+
acceptedTestEvidenceRef: null,
|
|
2879
|
+
testAcceptanceStatus,
|
|
2880
|
+
testEvidenceHash: null,
|
|
2881
|
+
reasons: [explanation],
|
|
2882
|
+
rejectionIssue: {
|
|
2883
|
+
reasonCode,
|
|
2884
|
+
explanation,
|
|
2885
|
+
requiredNextAction,
|
|
2886
|
+
fallbackRoute
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
function rejectedEvidenceJudgmentArtifact(evidenceJudgmentAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2891
|
+
return {
|
|
2892
|
+
acceptedEvidenceJudgmentRef: null,
|
|
2893
|
+
evidenceJudgmentAcceptanceStatus,
|
|
2894
|
+
evidenceJudgmentHash: null,
|
|
2895
|
+
reasons: [explanation],
|
|
2896
|
+
truthAlignment: null,
|
|
2897
|
+
rejectionIssue: {
|
|
2898
|
+
reasonCode,
|
|
2899
|
+
explanation,
|
|
2900
|
+
requiredNextAction,
|
|
2901
|
+
fallbackRoute
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
function parseEvidenceJudgmentTruthAlignment(verification) {
|
|
2906
|
+
const status = enumFrontmatter(verification.frontmatter, 'truthAlignmentStatus', TRUTH_ALIGNMENT_STATUSES);
|
|
2907
|
+
const semanticImpact = enumFrontmatter(verification.frontmatter, 'semanticImpact', TRUTH_ALIGNMENT_SEMANTIC_IMPACTS);
|
|
2908
|
+
const ownerStage = enumFrontmatter(verification.frontmatter, 'ownerStage', TRUTH_ALIGNMENT_OWNER_STAGES);
|
|
2909
|
+
const invalidatesStages = stageListFrontmatter(verification.frontmatter, 'invalidatesStages');
|
|
2910
|
+
const staleRefs = runtimeRefListFrontmatter(verification.frontmatter, 'staleRefs');
|
|
2911
|
+
const reasons = stringListFrontmatter(verification.frontmatter, 'truthAlignmentReasons');
|
|
2912
|
+
return {
|
|
2913
|
+
status,
|
|
2914
|
+
ownerStage,
|
|
2915
|
+
semanticImpact,
|
|
2916
|
+
staleRefs,
|
|
2917
|
+
invalidatesStages,
|
|
2918
|
+
reasons
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
function enumFrontmatter(frontmatter, key, allowed) {
|
|
2922
|
+
const value = frontmatter[key];
|
|
2923
|
+
if (value === undefined || value === null || value === '') {
|
|
2924
|
+
return null;
|
|
2925
|
+
}
|
|
2926
|
+
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
2927
|
+
return null;
|
|
2928
|
+
}
|
|
2929
|
+
return value;
|
|
2930
|
+
}
|
|
2931
|
+
function stringListFrontmatter(frontmatter, key) {
|
|
2932
|
+
const value = frontmatter[key];
|
|
2933
|
+
if (!Array.isArray(value)) {
|
|
2934
|
+
return [];
|
|
2935
|
+
}
|
|
2936
|
+
return value.filter((item) => typeof item === 'string' && item.length > 0);
|
|
2937
|
+
}
|
|
2938
|
+
function stageListFrontmatter(frontmatter, key) {
|
|
2939
|
+
const values = stringListFrontmatter(frontmatter, key);
|
|
2940
|
+
if (values.length === 0) {
|
|
2941
|
+
return null;
|
|
2942
|
+
}
|
|
2943
|
+
const allowed = new Set(['spec', 'plan', 'tasks', 'execute', 'ship']);
|
|
2944
|
+
return values.filter((value) => allowed.has(value));
|
|
2945
|
+
}
|
|
2946
|
+
function runtimeRefListFrontmatter(frontmatter, key) {
|
|
2947
|
+
const value = frontmatter[key];
|
|
2948
|
+
if (!Array.isArray(value)) {
|
|
2949
|
+
return [];
|
|
2950
|
+
}
|
|
2951
|
+
const refs = [];
|
|
2952
|
+
for (const item of value) {
|
|
2953
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
const entry = item;
|
|
2957
|
+
if (typeof entry.ref !== 'string' || entry.ref.length === 0) {
|
|
2958
|
+
continue;
|
|
2959
|
+
}
|
|
2960
|
+
const kind = typeof entry.kind === 'string' ? entry.kind : entry.ref.startsWith('.sdd/runs/') ? 'artifact' : entry.ref.startsWith('specs/') ? 'document' : 'external';
|
|
2961
|
+
refs.push({ kind, ref: entry.ref, hash: typeof entry.hash === 'string' && entry.hash.length > 0 ? entry.hash : undefined });
|
|
2962
|
+
}
|
|
2963
|
+
return refs;
|
|
2964
|
+
}
|
|
2965
|
+
function rejectedShipReadinessArtifact(shipReadinessStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
|
|
2966
|
+
return {
|
|
2967
|
+
acceptedShipReadinessRef: null,
|
|
2968
|
+
shipReadinessStatus,
|
|
2969
|
+
shipReadinessHash: null,
|
|
2970
|
+
releaseDocumentRef: null,
|
|
2971
|
+
reasons: [explanation],
|
|
2972
|
+
rejectionIssue: {
|
|
2973
|
+
reasonCode,
|
|
2974
|
+
explanation,
|
|
2975
|
+
requiredNextAction,
|
|
2976
|
+
fallbackRoute
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
async function parseShipReleaseDocumentRef(projectRoot, partition, readiness) {
|
|
2981
|
+
const rawRef = readiness.frontmatter.releaseRef;
|
|
2982
|
+
const hash = readiness.frontmatter.releaseHash;
|
|
2983
|
+
if (rawRef === undefined || rawRef === null || rawRef === '') {
|
|
2984
|
+
return { ref: null, issue: null };
|
|
2985
|
+
}
|
|
2986
|
+
if (typeof rawRef !== 'string' || typeof hash !== 'string' || hash.length === 0) {
|
|
2987
|
+
return { ref: null, issue: `Ship readiness ${readiness.ref} releaseRef requires a non-empty releaseHash.` };
|
|
2988
|
+
}
|
|
2989
|
+
const normalizedRef = normalizePortablePath(rawRef);
|
|
2990
|
+
if (!normalizedRef || normalizedRef === '.' || normalizedRef === '..' || normalizedRef.includes('..') || path.isAbsolute(rawRef) || !normalizedRef.startsWith(`specs/${partition}/`)) {
|
|
2991
|
+
return { ref: null, issue: `Ship readiness ${readiness.ref} release document ref is unsafe or outside the branch docs: ${rawRef}` };
|
|
2992
|
+
}
|
|
2993
|
+
const filePath = path.resolve(projectRoot, normalizedRef);
|
|
2994
|
+
const projectPath = path.resolve(projectRoot);
|
|
2995
|
+
if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
|
|
2996
|
+
return { ref: null, issue: `Ship readiness ${readiness.ref} release document ref escapes project root: ${rawRef}` };
|
|
2997
|
+
}
|
|
2998
|
+
const content = await readOptionalText(filePath);
|
|
2999
|
+
if (content === null) {
|
|
3000
|
+
return { ref: null, issue: `Release document is missing at ${normalizedRef}.` };
|
|
3001
|
+
}
|
|
3002
|
+
const currentHash = hashDocumentContent(content);
|
|
3003
|
+
if (hash !== currentHash) {
|
|
3004
|
+
return { ref: null, issue: `Release document hash is stale for ${normalizedRef}: expected ${hash}, actual ${currentHash}.` };
|
|
3005
|
+
}
|
|
3006
|
+
return { ref: { kind: 'document', ref: normalizedRef, hash }, issue: null };
|
|
3007
|
+
}
|
|
3008
|
+
async function validateAcceptedTruthAlignment(projectRoot, partition, truthAlignment) {
|
|
3009
|
+
if (!truthAlignment) {
|
|
3010
|
+
return 'Ship closure requires a recorded aligned truthAlignment projection from execute.';
|
|
3011
|
+
}
|
|
3012
|
+
if (truthAlignment.contract !== TRUTH_ALIGNMENT_CONTRACT || truthAlignment.sourceStage !== 'execute') {
|
|
3013
|
+
return 'Ship closure requires a valid execute truthAlignment projection.';
|
|
3014
|
+
}
|
|
3015
|
+
if (truthAlignment.status !== 'aligned') {
|
|
3016
|
+
const reasons = truthAlignment.reasons.length > 0 ? `: ${truthAlignment.reasons.join('; ')}` : '';
|
|
3017
|
+
return `Ship closure requires aligned truthAlignment; current status is ${truthAlignment.status}${reasons}.`;
|
|
3018
|
+
}
|
|
3019
|
+
if (truthAlignment.semanticImpact === 'material') {
|
|
3020
|
+
return 'Ship closure requires material truth drift to be reconciled before ship.';
|
|
3021
|
+
}
|
|
3022
|
+
if (truthAlignment.staleRefs.length > 0) {
|
|
3023
|
+
return `Ship closure requires fresh truthAlignment refs; stale refs: ${truthAlignment.staleRefs.map((ref) => ref.ref).join(', ')}.`;
|
|
3024
|
+
}
|
|
3025
|
+
if (truthAlignment.invalidatesStages.includes('ship')) {
|
|
3026
|
+
return 'Ship closure requires truthAlignment that does not invalidate ship.';
|
|
3027
|
+
}
|
|
3028
|
+
const acceptedEvidenceJudgmentRef = truthAlignment.acceptedRealityRefs.find((ref) => ref.kind === 'artifact' && ref.ref.startsWith(`.sdd/runs/${partition}/execute/evidence-judgment-`));
|
|
3029
|
+
if (!acceptedEvidenceJudgmentRef?.hash) {
|
|
3030
|
+
return `TruthAlignment must carry accepted .sdd/runs/${partition}/execute/evidence-judgment-vN.md with a content hash.`;
|
|
3031
|
+
}
|
|
3032
|
+
return validateTruthAlignmentRefHashes(projectRoot, [...truthAlignment.declaredTruthRefs, ...truthAlignment.acceptedRealityRefs]);
|
|
3033
|
+
}
|
|
3034
|
+
async function validateTruthAlignmentRefHashes(projectRoot, refs) {
|
|
3035
|
+
for (const ref of refs) {
|
|
3036
|
+
if (!ref.hash || ref.kind === 'projection' || ref.kind === 'run') {
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
const normalized = normalizePortablePath(ref.ref);
|
|
3040
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(ref.ref)) {
|
|
3041
|
+
return `TruthAlignment accepted reality ref is unsafe: ${ref.ref}`;
|
|
3042
|
+
}
|
|
3043
|
+
const filePath = path.resolve(projectRoot, normalized);
|
|
3044
|
+
const projectPath = path.resolve(projectRoot);
|
|
3045
|
+
if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
|
|
3046
|
+
return `TruthAlignment accepted reality ref escapes project root: ${ref.ref}`;
|
|
3047
|
+
}
|
|
3048
|
+
const content = await readOptionalText(filePath);
|
|
3049
|
+
if (content === null) {
|
|
3050
|
+
return `TruthAlignment accepted reality ref is missing at ${ref.ref}.`;
|
|
3051
|
+
}
|
|
3052
|
+
const currentHash = hashDocumentContent(content);
|
|
3053
|
+
if (ref.hash !== currentHash) {
|
|
3054
|
+
return `TruthAlignment accepted reality ref hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return null;
|
|
3058
|
+
}
|
|
3059
|
+
function validateNoRuntimeAuthorityAttempts(records) {
|
|
3060
|
+
const forbidden = new Set(['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved']);
|
|
3061
|
+
for (const record of records) {
|
|
3062
|
+
const value = record.frontmatter.authorityAttempts;
|
|
3063
|
+
if (!Array.isArray(value)) {
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3066
|
+
const attempts = value.filter((attempt) => typeof attempt === 'string' && forbidden.has(attempt));
|
|
3067
|
+
if (attempts.length > 0) {
|
|
3068
|
+
return `Ship artifact ${record.ref} attempted workflow authority: ${attempts.join(', ')}.`;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
return null;
|
|
3072
|
+
}
|
|
3073
|
+
function shipBlockerCount(decision) {
|
|
3074
|
+
return decision.requiredEvidence.filter((evidence) => evidence.requiredBefore === 'ship' && evidence.refs.length === 0).length;
|
|
3075
|
+
}
|
|
3076
|
+
function readinessBlockerCount(readiness) {
|
|
3077
|
+
return numericFrontmatter(readiness.frontmatter, 'doctorBlockerCount')
|
|
3078
|
+
+ numericFrontmatter(readiness.frontmatter, 'capabilityBlockerCount')
|
|
3079
|
+
+ numericFrontmatter(readiness.frontmatter, 'repairBlockerCount')
|
|
3080
|
+
+ numericFrontmatter(readiness.frontmatter, 'gapBlockerCount')
|
|
3081
|
+
+ numericFrontmatter(readiness.frontmatter, 'migrationBlockerCount')
|
|
3082
|
+
+ numericFrontmatter(readiness.frontmatter, 'openBlockerCount');
|
|
3083
|
+
}
|
|
3084
|
+
function numericFrontmatter(frontmatter, key) {
|
|
3085
|
+
const value = frontmatter[key];
|
|
3086
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : 0;
|
|
3087
|
+
}
|
|
3088
|
+
function executionRefFromValidation(validation) {
|
|
3089
|
+
const value = validation.frontmatter.executionRef;
|
|
3090
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
3091
|
+
return null;
|
|
3092
|
+
}
|
|
3093
|
+
try {
|
|
3094
|
+
return normalizeBranchStageEvidenceRef(value);
|
|
3095
|
+
}
|
|
3096
|
+
catch {
|
|
3097
|
+
return null;
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
async function validateHandoffInputHashes(projectRoot, inputRefs) {
|
|
3101
|
+
for (const ref of inputRefs) {
|
|
3102
|
+
if (!ref.hash) {
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
const normalized = normalizePortablePath(ref.ref);
|
|
3106
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(ref.ref)) {
|
|
3107
|
+
return `Handoff input ref is unsafe: ${ref.ref}`;
|
|
3108
|
+
}
|
|
3109
|
+
const filePath = path.resolve(projectRoot, normalized);
|
|
3110
|
+
const projectPath = path.resolve(projectRoot);
|
|
3111
|
+
if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
|
|
3112
|
+
return `Handoff input ref escapes project root: ${ref.ref}`;
|
|
3113
|
+
}
|
|
3114
|
+
const content = await readOptionalText(filePath);
|
|
3115
|
+
if (content === null) {
|
|
3116
|
+
return `Accepted do handoff input is missing at ${ref.ref}.`;
|
|
3117
|
+
}
|
|
3118
|
+
const currentHash = hashDocumentContent(content);
|
|
3119
|
+
if (ref.hash !== currentHash) {
|
|
3120
|
+
return `Accepted do handoff input hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
return null;
|
|
3124
|
+
}
|
|
3125
|
+
function findStageReviewForManager(records, manager, reviewKind) {
|
|
3126
|
+
if (manager.reviewRef) {
|
|
3127
|
+
return records.find((record) => record.kind === reviewKind && record.ref === manager.reviewRef?.ref && record.hash === manager.reviewHash) ?? null;
|
|
3128
|
+
}
|
|
3129
|
+
return latestStageArtifact(records, reviewKind);
|
|
3130
|
+
}
|
|
3131
|
+
function parseImplementationChangedFileRefs(implementation) {
|
|
3132
|
+
const value = implementation.frontmatter.changedFiles;
|
|
3133
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
3134
|
+
return { refs: [], issue: `Implementation evidence ${implementation.ref} must declare non-empty changedFiles frontmatter.` };
|
|
3135
|
+
}
|
|
3136
|
+
const refs = [];
|
|
3137
|
+
for (const item of value) {
|
|
3138
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
3139
|
+
return { refs: [], issue: `Implementation evidence ${implementation.ref} has an invalid changedFiles entry.` };
|
|
3140
|
+
}
|
|
3141
|
+
const entry = item;
|
|
3142
|
+
const rawRef = entry.ref;
|
|
3143
|
+
const hash = entry.hash;
|
|
3144
|
+
if (typeof rawRef !== 'string' || rawRef.length === 0 || typeof hash !== 'string' || hash.length === 0) {
|
|
3145
|
+
return { refs: [], issue: `Implementation evidence ${implementation.ref} changedFiles entries must include ref and hash.` };
|
|
3146
|
+
}
|
|
3147
|
+
const normalizedRef = normalizeChangedFileRef(rawRef);
|
|
3148
|
+
if (!normalizedRef) {
|
|
3149
|
+
return { refs: [], issue: `Implementation evidence ${implementation.ref} changed file ref is unsafe: ${rawRef}` };
|
|
3150
|
+
}
|
|
3151
|
+
refs.push({ kind: 'evidence', ref: normalizedRef, hash });
|
|
3152
|
+
}
|
|
3153
|
+
return { refs, issue: null };
|
|
3154
|
+
}
|
|
3155
|
+
function normalizeChangedFileRef(value) {
|
|
3156
|
+
const normalized = normalizePortablePath(value);
|
|
3157
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(value)) {
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
if (normalized.startsWith('.sdd/runs/') || normalized.startsWith('runs/') || isCanonicalSddDocumentRef(normalized)) {
|
|
3161
|
+
return null;
|
|
3162
|
+
}
|
|
3163
|
+
return normalized;
|
|
3164
|
+
}
|
|
3165
|
+
function validateAllowedChangedFileRefs(changedFileRefs, allowedChangedFileRefs) {
|
|
3166
|
+
if (!allowedChangedFileRefs || allowedChangedFileRefs.length === 0) {
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
const allowedExact = new Set();
|
|
3170
|
+
const allowedPatterns = [];
|
|
3171
|
+
for (const value of allowedChangedFileRefs) {
|
|
3172
|
+
const normalized = value.includes('*') ? normalizeChangedFilePattern(value) : normalizeChangedFileRef(value);
|
|
3173
|
+
if (!normalized) {
|
|
3174
|
+
return `Allowed changed-file ref is unsafe: ${value}`;
|
|
3175
|
+
}
|
|
3176
|
+
if (normalized.includes('*')) {
|
|
3177
|
+
allowedPatterns.push(changedFilePatternRegex(normalized));
|
|
3178
|
+
}
|
|
3179
|
+
else {
|
|
3180
|
+
allowedExact.add(normalized);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
const outsideBoundary = changedFileRefs.find((ref) => !allowedExact.has(ref.ref) && !allowedPatterns.some((pattern) => pattern.test(ref.ref)));
|
|
3184
|
+
return outsideBoundary ? `Changed file ${outsideBoundary.ref} is outside the allowed execute task boundary.` : null;
|
|
3185
|
+
}
|
|
3186
|
+
function normalizeChangedFilePattern(value) {
|
|
3187
|
+
const normalized = normalizePortablePath(value);
|
|
3188
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(value)) {
|
|
3189
|
+
return null;
|
|
3190
|
+
}
|
|
3191
|
+
if (normalized.startsWith('.sdd/runs/') || normalized.startsWith('runs/') || isCanonicalSddDocumentRef(normalized)) {
|
|
3192
|
+
return null;
|
|
3193
|
+
}
|
|
3194
|
+
return normalized;
|
|
3195
|
+
}
|
|
3196
|
+
function isCanonicalSddDocumentRef(normalizedRef) {
|
|
3197
|
+
return /^specs\/[^/]+\/(spec|plan|tasks|verify)\.md$/.test(normalizedRef);
|
|
3198
|
+
}
|
|
3199
|
+
function changedFilePatternRegex(pattern) {
|
|
3200
|
+
return new RegExp(`^${escapeRegex(pattern).replace(/\\\*/g, '[^/]*')}$`);
|
|
3201
|
+
}
|
|
3202
|
+
async function validateChangedFileHashes(projectRoot, changedFileRefs) {
|
|
3203
|
+
for (const ref of changedFileRefs) {
|
|
3204
|
+
const filePath = path.resolve(projectRoot, ref.ref);
|
|
3205
|
+
const projectPath = path.resolve(projectRoot);
|
|
3206
|
+
if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
|
|
3207
|
+
return `Changed file ref escapes project root: ${ref.ref}`;
|
|
3208
|
+
}
|
|
3209
|
+
const content = await readOptionalText(filePath);
|
|
3210
|
+
if (content === null) {
|
|
3211
|
+
return `Changed file ${ref.ref} is missing from the workspace.`;
|
|
3212
|
+
}
|
|
3213
|
+
const currentHash = hashDocumentContent(content);
|
|
3214
|
+
if (ref.hash !== currentHash) {
|
|
3215
|
+
return `Changed file hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
function buildPlanClosureStageRun(scope, runId, workOrder, health, input) {
|
|
3221
|
+
const status = health === 'ready_for_tasks'
|
|
3222
|
+
? 'completed'
|
|
3223
|
+
: health === 'no-op'
|
|
3224
|
+
? 'skipped'
|
|
3225
|
+
: 'blocked';
|
|
3226
|
+
return {
|
|
3227
|
+
contract: STAGE_RUN_CONTRACT_VERSION,
|
|
3228
|
+
id: stableId('stage-run-plan', scope, runId, input.generatedAt),
|
|
3229
|
+
scope,
|
|
3230
|
+
stage: 'plan',
|
|
3231
|
+
ownerAgent: workOrder?.stageManager ?? 'runtime',
|
|
3232
|
+
coMainAgents: workOrder?.agentTeam ?? [],
|
|
3233
|
+
status,
|
|
3234
|
+
inputRefs: input.inputRefs,
|
|
3235
|
+
outputRefs: input.outputRefs,
|
|
3236
|
+
decisionRefs: input.decisionRefs,
|
|
3237
|
+
blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Plan adjudication health is ${health}.`] : [],
|
|
3238
|
+
createdAt: input.generatedAt,
|
|
3239
|
+
updatedAt: input.generatedAt
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
function buildPlanWorkflowHandoff(scope, decision, input) {
|
|
3243
|
+
return {
|
|
3244
|
+
contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
|
|
3245
|
+
id: stableId('plan-tasks-handoff', scope, 'closure', input.generatedAt),
|
|
3246
|
+
scope,
|
|
3247
|
+
fromStage: 'plan',
|
|
3248
|
+
toStage: 'tasks',
|
|
3249
|
+
fromAgent: PLAN_STAGE_MANAGER,
|
|
3250
|
+
toAgent: 'tasks-stage-runtime',
|
|
3251
|
+
status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
|
|
3252
|
+
outputRefs: input.outputRefs,
|
|
3253
|
+
requiredInputRefs: input.requiredInputRefs,
|
|
3254
|
+
riskDecisionRef: input.riskDecisionRef,
|
|
3255
|
+
evidenceRefs: input.evidenceRefs,
|
|
3256
|
+
openQuestions: [],
|
|
3257
|
+
blockingGaps: [],
|
|
3258
|
+
createdAt: input.generatedAt
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
function buildTasksClosureStageRun(scope, runId, workOrder, health, input) {
|
|
3262
|
+
const status = health === 'ready_for_execute'
|
|
3263
|
+
? 'completed'
|
|
3264
|
+
: health === 'no-op'
|
|
3265
|
+
? 'skipped'
|
|
3266
|
+
: 'blocked';
|
|
3267
|
+
return {
|
|
3268
|
+
contract: STAGE_RUN_CONTRACT_VERSION,
|
|
3269
|
+
id: stableId('stage-run-tasks', scope, runId, input.generatedAt),
|
|
3270
|
+
scope,
|
|
3271
|
+
stage: 'tasks',
|
|
3272
|
+
ownerAgent: workOrder?.stageManager ?? 'runtime',
|
|
3273
|
+
coMainAgents: workOrder?.agentTeam ?? [],
|
|
3274
|
+
status,
|
|
3275
|
+
inputRefs: input.inputRefs,
|
|
3276
|
+
outputRefs: input.outputRefs,
|
|
3277
|
+
decisionRefs: input.decisionRefs,
|
|
3278
|
+
blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Tasks adjudication health is ${health}.`] : [],
|
|
3279
|
+
createdAt: input.generatedAt,
|
|
3280
|
+
updatedAt: input.generatedAt
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
function buildTasksWorkflowHandoff(scope, decision, input) {
|
|
3284
|
+
return {
|
|
3285
|
+
contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
|
|
3286
|
+
id: stableId('tasks-execute-handoff', scope, 'closure', input.generatedAt),
|
|
3287
|
+
scope,
|
|
3288
|
+
fromStage: 'tasks',
|
|
3289
|
+
toStage: 'execute',
|
|
3290
|
+
fromAgent: TASKS_STAGE_MANAGER,
|
|
3291
|
+
toAgent: 'execute-stage-runtime',
|
|
3292
|
+
status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
|
|
3293
|
+
outputRefs: input.outputRefs,
|
|
3294
|
+
requiredInputRefs: input.requiredInputRefs,
|
|
3295
|
+
riskDecisionRef: input.riskDecisionRef,
|
|
3296
|
+
evidenceRefs: input.evidenceRefs,
|
|
3297
|
+
openQuestions: [],
|
|
3298
|
+
blockingGaps: [],
|
|
3299
|
+
createdAt: input.generatedAt
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
function buildExecuteClosureStageRun(scope, runId, workOrder, health, input) {
|
|
3303
|
+
const status = health === 'ready_for_ship'
|
|
3304
|
+
? 'completed'
|
|
3305
|
+
: health === 'no-op'
|
|
3306
|
+
? 'skipped'
|
|
3307
|
+
: 'blocked';
|
|
3308
|
+
return {
|
|
3309
|
+
contract: STAGE_RUN_CONTRACT_VERSION,
|
|
3310
|
+
id: stableId('stage-run-execute', scope, runId, input.generatedAt),
|
|
3311
|
+
scope,
|
|
3312
|
+
stage: 'execute',
|
|
3313
|
+
ownerAgent: workOrder?.stageManager ?? 'runtime',
|
|
3314
|
+
coMainAgents: workOrder?.agentTeam ?? [],
|
|
3315
|
+
status,
|
|
3316
|
+
inputRefs: input.inputRefs,
|
|
3317
|
+
outputRefs: input.outputRefs,
|
|
3318
|
+
decisionRefs: input.decisionRefs,
|
|
3319
|
+
blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Execute adjudication health is ${health}.`] : [],
|
|
3320
|
+
createdAt: input.generatedAt,
|
|
3321
|
+
updatedAt: input.generatedAt
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
function buildExecuteWorkflowHandoff(scope, decision, input) {
|
|
3325
|
+
return {
|
|
3326
|
+
contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
|
|
3327
|
+
id: stableId('execute-ship-handoff', scope, 'closure', input.generatedAt),
|
|
3328
|
+
scope,
|
|
3329
|
+
fromStage: 'execute',
|
|
3330
|
+
toStage: 'ship',
|
|
3331
|
+
fromAgent: 'execute-manager',
|
|
3332
|
+
toAgent: 'ship-stage-runtime',
|
|
3333
|
+
status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
|
|
3334
|
+
outputRefs: input.outputRefs,
|
|
3335
|
+
requiredInputRefs: input.requiredInputRefs,
|
|
3336
|
+
riskDecisionRef: input.riskDecisionRef,
|
|
3337
|
+
evidenceRefs: input.evidenceRefs,
|
|
3338
|
+
openQuestions: [],
|
|
3339
|
+
blockingGaps: [],
|
|
3340
|
+
createdAt: input.generatedAt
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
function buildShipClosureStageRun(scope, runId, workOrder, health, input) {
|
|
3344
|
+
const status = health === 'ship_ready'
|
|
3345
|
+
? 'completed'
|
|
3346
|
+
: health === 'no-op'
|
|
3347
|
+
? 'skipped'
|
|
3348
|
+
: 'blocked';
|
|
3349
|
+
return {
|
|
3350
|
+
contract: STAGE_RUN_CONTRACT_VERSION,
|
|
3351
|
+
id: stableId('stage-run-ship', scope, runId, input.generatedAt),
|
|
3352
|
+
scope,
|
|
3353
|
+
stage: 'ship',
|
|
3354
|
+
ownerAgent: workOrder?.stageManager ?? 'runtime',
|
|
3355
|
+
coMainAgents: workOrder?.agentTeam ?? [],
|
|
3356
|
+
status,
|
|
3357
|
+
inputRefs: input.inputRefs,
|
|
3358
|
+
outputRefs: input.outputRefs,
|
|
3359
|
+
decisionRefs: input.decisionRefs,
|
|
3360
|
+
blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Ship adjudication health is ${health}.`] : [],
|
|
3361
|
+
createdAt: input.generatedAt,
|
|
3362
|
+
updatedAt: input.generatedAt
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
function buildStageRejection(stage, scope, closureRequestId, reasonCode, explanation, requiredNextAction, fallbackRoute, generatedAt, retryAllowed) {
|
|
3366
|
+
return {
|
|
3367
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
3368
|
+
rejectionId: stableId(`${stage}-rejection`, scope, closureRequestId ?? reasonCode, generatedAt),
|
|
3369
|
+
stage,
|
|
3370
|
+
scope,
|
|
3371
|
+
closureRequestId,
|
|
3372
|
+
reasonCode,
|
|
3373
|
+
explanation,
|
|
3374
|
+
requiredNextAction,
|
|
3375
|
+
retryAllowed,
|
|
3376
|
+
retryBudgetRemaining: retryAllowed ? 1 : 0,
|
|
3377
|
+
fallbackRoute,
|
|
3378
|
+
createdAt: generatedAt
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
function buildStageRuntimeNextActions(stage, scope, generatedAt, reasonCodes) {
|
|
3382
|
+
return reasonCodes
|
|
3383
|
+
.map((reasonCode, index) => ({
|
|
3384
|
+
contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
|
|
3385
|
+
actionId: stableId(`${stage}-next-action`, scope, reasonCode, generatedAt),
|
|
3386
|
+
stage,
|
|
3387
|
+
scope,
|
|
3388
|
+
kind: nextActionKind(reasonCode),
|
|
3389
|
+
owner: nextActionOwner(reasonCode),
|
|
3390
|
+
actionScope: nextActionScope(reasonCode),
|
|
3391
|
+
reasonCode,
|
|
3392
|
+
priority: {
|
|
3393
|
+
reasonRank: reasonCodeRank(reasonCode),
|
|
3394
|
+
upstreamDistance: stage === 'plan' && (reasonCode === 'missing_handoff' || reasonCode === 'stale_upstream') ? 1 : 0,
|
|
3395
|
+
actionRank: actionKindRank(nextActionKind(reasonCode)),
|
|
3396
|
+
stableOrder: index
|
|
3397
|
+
},
|
|
3398
|
+
requiredInputs: nextActionRequiredInputs(reasonCode),
|
|
3399
|
+
blockedBy: [],
|
|
3400
|
+
createdAt: generatedAt
|
|
3401
|
+
}))
|
|
3402
|
+
.sort(compareRuntimeNextAction);
|
|
3403
|
+
}
|
|
3404
|
+
function buildClosureStageRun(profile, adjudication, input) {
|
|
3405
|
+
const status = adjudication.health === 'ready_for_plan'
|
|
3406
|
+
? 'completed'
|
|
3407
|
+
: adjudication.health === 'no-op'
|
|
3408
|
+
? 'skipped'
|
|
3409
|
+
: 'blocked';
|
|
3410
|
+
return {
|
|
3411
|
+
contract: STAGE_RUN_CONTRACT_VERSION,
|
|
3412
|
+
id: stableId('stage-run-spec', profile.scope, input.runId, input.generatedAt),
|
|
3413
|
+
scope: profile.scope,
|
|
3414
|
+
stage: 'spec',
|
|
3415
|
+
ownerAgent: profile.stageManager ?? 'runtime',
|
|
3416
|
+
coMainAgents: profile.agentTeam,
|
|
3417
|
+
status,
|
|
3418
|
+
inputRefs: profile.inputRefs,
|
|
3419
|
+
outputRefs: input.outputRefs,
|
|
3420
|
+
decisionRefs: input.decisionRefs,
|
|
3421
|
+
blockingReasons: status === 'blocked' ? stageBlockingReasons(adjudication) : [],
|
|
3422
|
+
createdAt: input.generatedAt,
|
|
3423
|
+
updatedAt: input.generatedAt
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function buildClosureWorkflowHandoff(profile, decision, adjudication, input) {
|
|
3427
|
+
return {
|
|
3428
|
+
contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
|
|
3429
|
+
id: adjudication.handoffPacket?.handoffId ?? stableId('spec-plan-handoff', profile.scope, 'closure', input.generatedAt),
|
|
3430
|
+
scope: profile.scope,
|
|
3431
|
+
fromStage: 'spec',
|
|
3432
|
+
toStage: 'plan',
|
|
3433
|
+
fromAgent: profile.stageManager ?? 'runtime',
|
|
3434
|
+
toAgent: recommendedPlanRoles(profile)[0] ?? 'plan-stage-runtime',
|
|
3435
|
+
status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
|
|
3436
|
+
outputRefs: input.outputRefs,
|
|
3437
|
+
requiredInputRefs: input.requiredInputRefs,
|
|
3438
|
+
riskDecisionRef: input.riskDecisionRef,
|
|
3439
|
+
evidenceRefs: input.evidenceRefs,
|
|
3440
|
+
openQuestions: [],
|
|
3441
|
+
blockingGaps: [],
|
|
3442
|
+
createdAt: input.generatedAt
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
function runLifecycleDecisionRecord(decision) {
|
|
3446
|
+
return {
|
|
3447
|
+
contract: LIFECYCLE_DECISION_CONTRACT,
|
|
3448
|
+
version: LIFECYCLE_DECISION_VERSION,
|
|
3449
|
+
model_version: decision.policyVersion,
|
|
3450
|
+
input_summary: { scope: decision.scope, inputHash: decision.inputHash },
|
|
3451
|
+
decision: {
|
|
3452
|
+
profile: decision.profile,
|
|
3453
|
+
confidence: decision.confidence,
|
|
3454
|
+
hard_gate_hits: decision.blockedStages,
|
|
3455
|
+
required_stages: decision.requiredStages,
|
|
3456
|
+
skipped_stages: decision.skippedStages,
|
|
3457
|
+
human_checkpoint_required: decision.humanCheckpointRequired
|
|
3458
|
+
},
|
|
3459
|
+
reasons: decision.reasons,
|
|
3460
|
+
escalation_triggers: decision.requiredReviews.map((review) => review.id),
|
|
3461
|
+
downgrade_reason: null,
|
|
3462
|
+
audit: {
|
|
3463
|
+
decided_at: decision.generatedAt,
|
|
3464
|
+
decided_by: 'phase9.1-stage-collaboration-runtime',
|
|
3465
|
+
policy_version: decision.policyVersion,
|
|
3466
|
+
source_artifacts: decision.inputRefs.map((ref) => ref.ref)
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
function stageBlockingReasons(adjudication) {
|
|
3471
|
+
if (adjudication.clarificationGate) {
|
|
3472
|
+
return adjudication.clarificationGate.questions;
|
|
3473
|
+
}
|
|
3474
|
+
if (adjudication.rejection) {
|
|
3475
|
+
return [adjudication.rejection.explanation];
|
|
3476
|
+
}
|
|
3477
|
+
return [`Spec adjudication health is ${adjudication.health}.`];
|
|
3478
|
+
}
|
|
3479
|
+
function specArtifactStatusForHealth(health) {
|
|
3480
|
+
if (health === 'no-op') {
|
|
3481
|
+
return 'not_required_noop';
|
|
3482
|
+
}
|
|
3483
|
+
if (health === 'needs_clarification') {
|
|
3484
|
+
return 'not_accepted_needs_clarification';
|
|
3485
|
+
}
|
|
3486
|
+
if (health === 'blocked') {
|
|
3487
|
+
return 'not_accepted_blocked';
|
|
3488
|
+
}
|
|
3489
|
+
return 'not_accepted_rejected';
|
|
3490
|
+
}
|
|
3491
|
+
function isManagedStarterSpec(content) {
|
|
3492
|
+
return /^sdd_managed_starter:\s*true\s*$/m.test(content);
|
|
3493
|
+
}
|
|
3494
|
+
function validateSpecDocumentV3(content) {
|
|
3495
|
+
const errors = [];
|
|
3496
|
+
if (!/contract:\s*sdd-spec-doc-v3\b/.test(content)) {
|
|
3497
|
+
errors.push('missing contract: sdd-spec-doc-v3');
|
|
3498
|
+
}
|
|
3499
|
+
const requiredSections = [
|
|
3500
|
+
'Problem Reframing / Intent Discovery',
|
|
3501
|
+
'Change Delta',
|
|
3502
|
+
'Scope',
|
|
3503
|
+
'Requirements',
|
|
3504
|
+
'Acceptance Criteria / Evidence Targets',
|
|
3505
|
+
'Definitions / Rules',
|
|
3506
|
+
'Planning Constraints / Signals',
|
|
3507
|
+
'Open Questions / Ambiguity Ledger',
|
|
3508
|
+
'Close Quality Evidence'
|
|
3509
|
+
];
|
|
3510
|
+
for (const section of requiredSections) {
|
|
3511
|
+
const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
|
|
3512
|
+
if (!new RegExp(`^##\\s+(?:\\d+\\.\\s*)?${escaped}\\s*$`, 'im').test(content)) {
|
|
3513
|
+
errors.push(`missing section: ${section}`);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
const forbiddenSections = [/^##\s+Implementation Plan\s*$/im, /^##\s+Task List\s*$/im, /^##\s+Validation Plan\s*$/im, /^##\s+Runtime Gate Metadata\s*$/im, /^##\s+Subagent Trace\s*$/im];
|
|
3517
|
+
if (forbiddenSections.some((pattern) => pattern.test(content))) {
|
|
3518
|
+
errors.push('contains forbidden implementation/runtime/subagent section');
|
|
3519
|
+
}
|
|
3520
|
+
errors.push(...formatArtifactDepthBlockingIssues(evaluateSpecArtifactDepth(content)));
|
|
3521
|
+
return errors;
|
|
3522
|
+
}
|
|
3523
|
+
function validateSpecDepthSignals(content, errors) {
|
|
3524
|
+
const problem = extractMarkdownSection(content, 'Problem Reframing / Intent Discovery') ?? '';
|
|
3525
|
+
const changeDelta = extractMarkdownSection(content, 'Change Delta') ?? '';
|
|
3526
|
+
const requirements = extractMarkdownSection(content, 'Requirements') ?? '';
|
|
3527
|
+
const acceptance = extractMarkdownSection(content, 'Acceptance Criteria / Evidence Targets') ?? '';
|
|
3528
|
+
const definitions = extractMarkdownSection(content, 'Definitions / Rules') ?? '';
|
|
3529
|
+
const ambiguity = extractMarkdownSection(content, 'Open Questions / Ambiguity Ledger') ?? '';
|
|
3530
|
+
const closeEvidence = extractMarkdownSection(content, 'Close Quality Evidence') ?? '';
|
|
3531
|
+
if (!hasProblemReframingDepth(problem)) {
|
|
3532
|
+
errors.push('spec lacks problem reframing depth in Problem Reframing / Intent Discovery');
|
|
3533
|
+
}
|
|
3534
|
+
if (!hasChangeDeltaDepth(changeDelta)) {
|
|
3535
|
+
errors.push('spec lacks explicit current/target/delta/unchanged/non-goal depth in Change Delta');
|
|
3536
|
+
}
|
|
3537
|
+
if (!hasRequirementReasoningDepth(requirements, definitions)) {
|
|
3538
|
+
errors.push('spec lacks requirement reasoning or domain rule depth');
|
|
3539
|
+
}
|
|
3540
|
+
if (!hasAcceptanceEvidenceDepth(acceptance)) {
|
|
3541
|
+
errors.push('spec lacks acceptance evidence targets tied to requirements');
|
|
3542
|
+
}
|
|
3543
|
+
if (!hasAmbiguityRoutingDepth(ambiguity)) {
|
|
3544
|
+
errors.push('spec lacks explicit ambiguity routing for blocking decisions, researchable gaps, assumptions, or deferred items');
|
|
3545
|
+
}
|
|
3546
|
+
if (!closeEvidence) {
|
|
3547
|
+
errors.push('missing spec close quality evidence content');
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
const requiredEvidence = [
|
|
3551
|
+
['requirement_review_sufficient', 'spec close quality evidence must declare requirement_review_sufficient: true'],
|
|
3552
|
+
['problem_reframed', 'spec close quality evidence must declare problem_reframed: true'],
|
|
3553
|
+
['domain_rules_confirmed_or_routed', 'spec close quality evidence must declare domain_rules_confirmed_or_routed: true'],
|
|
3554
|
+
['acceptance_evidence_targets_defined', 'spec close quality evidence must declare acceptance_evidence_targets_defined: true'],
|
|
3555
|
+
['ambiguity_routed', 'spec close quality evidence must declare ambiguity_routed: true'],
|
|
3556
|
+
['ready_for_plan', 'spec close quality evidence must declare ready_for_plan: true']
|
|
3557
|
+
];
|
|
3558
|
+
for (const [key, message] of requiredEvidence) {
|
|
3559
|
+
if (!new RegExp(`${key}\\s*:\\s*` + '`?' + 'true' + '`?', 'i').test(closeEvidence)) {
|
|
3560
|
+
errors.push(message);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
if (!/downstream_business_guesswork_remaining\s*:\s*\[\s*\]/i.test(closeEvidence)) {
|
|
3564
|
+
errors.push('spec close quality evidence must declare no downstream_business_guesswork_remaining');
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
function hasProblemReframingDepth(section) {
|
|
3568
|
+
return /surface request/i.test(section)
|
|
3569
|
+
&& /reframed problem/i.test(section)
|
|
3570
|
+
&& /inferred real intent|real intent/i.test(section)
|
|
3571
|
+
&& /observable success/i.test(section);
|
|
3572
|
+
}
|
|
3573
|
+
function hasChangeDeltaDepth(section) {
|
|
3574
|
+
return /current behavior/i.test(section)
|
|
3575
|
+
&& /target behavior|desired behavior/i.test(section)
|
|
3576
|
+
&& /delta/i.test(section)
|
|
3577
|
+
&& /unchanged behavior/i.test(section)
|
|
3578
|
+
&& /non-goals?/i.test(section);
|
|
3579
|
+
}
|
|
3580
|
+
function hasRequirementReasoningDepth(requirements, definitions) {
|
|
3581
|
+
const hasRequirement = /\bREQ-\d+\b/i.test(requirements);
|
|
3582
|
+
const hasReasoning = /reasoning|basis|source|constraint|because|why|rule|invariant|domain/i.test(`${requirements}\n${definitions}`);
|
|
3583
|
+
return hasRequirement && hasReasoning;
|
|
3584
|
+
}
|
|
3585
|
+
function hasAcceptanceEvidenceDepth(section) {
|
|
3586
|
+
return /\bAC-\d+\b/i.test(section)
|
|
3587
|
+
&& /evidence target|api|ui|sql|test|manual|runtime|stage close/i.test(section)
|
|
3588
|
+
&& /REQ-\d+/i.test(section);
|
|
3589
|
+
}
|
|
3590
|
+
function hasAmbiguityRoutingDepth(section) {
|
|
3591
|
+
return /blocking user decisions?|blocking before plan|no blocking/i.test(section)
|
|
3592
|
+
&& /researchable|resolved by scout|scout|safe assumptions?|assumptions?|deferred/i.test(section);
|
|
3593
|
+
}
|
|
3594
|
+
function validatePlanDocumentV3(content, boundaryFacts) {
|
|
3595
|
+
const errors = [];
|
|
3596
|
+
if (!/contract:\s*sdd-plan-doc-v3\b/.test(content)) {
|
|
3597
|
+
errors.push('missing contract: sdd-plan-doc-v3');
|
|
3598
|
+
}
|
|
3599
|
+
const requiredSections = [
|
|
3600
|
+
'Metadata',
|
|
3601
|
+
'Upstream Spec Trace',
|
|
3602
|
+
'Planning Problem / Strategy Framing',
|
|
3603
|
+
'Current Implementation Map',
|
|
3604
|
+
'Target Design Overview',
|
|
3605
|
+
'Change Topology / Responsibility Boundaries',
|
|
3606
|
+
'Interface / API / Schema Design',
|
|
3607
|
+
'State / Data / Concurrency Design',
|
|
3608
|
+
'Key Design Decisions',
|
|
3609
|
+
'Alternatives Considered',
|
|
3610
|
+
'Risk Controls',
|
|
3611
|
+
'Validation Strategy',
|
|
3612
|
+
'Rollout / Rollback / Compatibility',
|
|
3613
|
+
'Task-stage Constraints',
|
|
3614
|
+
'Open Questions / User Decisions',
|
|
3615
|
+
'Plan Close Quality Evidence'
|
|
3616
|
+
];
|
|
3617
|
+
for (const section of requiredSections) {
|
|
3618
|
+
const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
|
|
3619
|
+
if (!new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content)) {
|
|
3620
|
+
errors.push(`missing section: ${section}`);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
const forbiddenSections = ['Task Graph', 'Task Units', 'Task Breakdown Rationale', 'Implementation Diff', 'Validation Results', 'Runtime Gate Metadata', 'Subagent Trace'];
|
|
3624
|
+
const hasForbiddenSection = forbiddenSections.some((section) => {
|
|
3625
|
+
const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
|
|
3626
|
+
return new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content);
|
|
3627
|
+
});
|
|
3628
|
+
const taskLeakagePatterns = [/```sdd-task\b/i, /^###\s+T\d+\b/im, /^#{3,}\s+Task\s+Units?\b/im, /^\s*sourceRequirements\s*:/im, /^\s*sourceAcceptanceCriteria\s*:/im, /^\s*taskClass\s*:/im, /^\s*suggestedExecutionLane\s*:/im, /^\s*wave\s*:/im, /^\s*dependencies\s*:/im];
|
|
3629
|
+
if (hasForbiddenSection || taskLeakagePatterns.some((pattern) => pattern.test(content))) {
|
|
3630
|
+
errors.push('contains forbidden task/runtime/subagent section');
|
|
3631
|
+
}
|
|
3632
|
+
const closeEvidence = extractMarkdownSection(content, 'Plan Close Quality Evidence');
|
|
3633
|
+
if (!closeEvidence) {
|
|
3634
|
+
errors.push('missing plan close quality evidence content');
|
|
3635
|
+
}
|
|
3636
|
+
else if (/ready_for_tasks\s*:\s*`?false`?/i.test(closeEvidence) || !/ready_for_tasks\s*:\s*`?true`?/i.test(closeEvidence)) {
|
|
3637
|
+
errors.push('plan close quality evidence must declare ready_for_tasks: true');
|
|
3638
|
+
}
|
|
3639
|
+
validatePlanDepthSignals(content, boundaryFacts, errors);
|
|
3640
|
+
errors.push(...formatArtifactDepthBlockingIssues(evaluatePlanArtifactDepth(content)));
|
|
3641
|
+
return errors;
|
|
3642
|
+
}
|
|
3643
|
+
function validatePlanDepthSignals(_content, boundaryFacts, errors) {
|
|
3644
|
+
if (boundaryFacts?.blockingBeforeTasksCount !== undefined && boundaryFacts.blockingBeforeTasksCount !== 0) {
|
|
3645
|
+
errors.push('plan close quality evidence conflicts with runtime blockingBeforeTasksCount');
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
function hasConcreteImplementationSurface(section) {
|
|
3649
|
+
if (/scout gap|needs scout|unresolved gap|not applicable|no existing surface/i.test(section)) {
|
|
3650
|
+
return true;
|
|
3651
|
+
}
|
|
3652
|
+
const namedSurfaces = section.match(/\b[A-Z][A-Za-z0-9_]*(?:Controller|Service|ServiceImpl|Mapper|Repository|Entity|DTO|VO|Request|Response|Component|Page|Config|Test)\b/g) ?? [];
|
|
3653
|
+
const fileRefs = section.match(/`[^`]+\.(?:ts|tsx|js|jsx|java|kt|go|py|rs|xml|sql|jsp|vue|md)`/g) ?? [];
|
|
3654
|
+
return namedSurfaces.length + fileRefs.length >= 2 && /reuse|change|do-not-reuse|do not reuse|owner|ownership|convention|boundary|surface/i.test(section);
|
|
3655
|
+
}
|
|
3656
|
+
function hasFieldApiSqlMapping(section) {
|
|
3657
|
+
if (/not applicable|no interface|no api|no schema/i.test(section) && /because|checked|reason/i.test(section)) {
|
|
3658
|
+
return true;
|
|
3659
|
+
}
|
|
3660
|
+
const backtickFields = section.match(/`[a-zA-Z_][\w.]*`/g) ?? [];
|
|
3661
|
+
const identifierFields = section.match(/\b[A-Za-z_][A-Za-z0-9_]*(?:Id|IDs|Hash|Ref|Refs|Count|Status|Type|Key|Keys|At|Path|Command|Query|DTO|VO|Request|Response|Mapper|Controller|Service)\b/g) ?? [];
|
|
3662
|
+
const hasApiShape = /\b(query|request|response|DTO|VO|endpoint|controller|service|mapper|SQL|MyBatis|分页|权限|错误|响应包装)\b/i.test(section);
|
|
3663
|
+
const hasMapping = /\b(source|table|mapper|SQL|field|column|join|group|aggregate|映射|字段|表|聚合|查询参数|返回字段)\b/i.test(section);
|
|
3664
|
+
return backtickFields.length + identifierFields.length >= 6 && hasApiShape && hasMapping;
|
|
3665
|
+
}
|
|
3666
|
+
function hasStateDataFailureDepth(section) {
|
|
3667
|
+
if (/not applicable|no persisted|no state/i.test(section) && /failure|rollback|stale|dirty|because|reason/i.test(section)) {
|
|
3668
|
+
return true;
|
|
3669
|
+
}
|
|
3670
|
+
const hasDataSources = /\b(table|source|asset|state|flow|idempot|transaction|rollback|failure|partial|dirty|stale|performance|limit|join|dedup|grain|表|状态|流程|失败|回滚|脏数据|性能|去重|粒度|资产池)\b/i.test(section);
|
|
3671
|
+
const identifierRules = section.match(/\b[A-Za-z_][A-Za-z0-9_]*(?:Id|Hash|Ref|Status|Type|Key|At|Path|Projection|Handoff|Stage|State|Scope|Owner|Content)\b/g) ?? [];
|
|
3672
|
+
const hasSpecificRules = (section.match(/`[^`]+`/g) ?? []).length >= 5 || identifierRules.length >= 5 || /@startuml|\|.*\|.*\|/m.test(section);
|
|
3673
|
+
const hasFailureOrLimit = /\b(fail|failure|rollback|partial|dirty|stale|limit|timeout|fallback|异常|失败|回滚|部分|脏|限制|超时|降级)\b/i.test(section);
|
|
3674
|
+
return hasDataSources && hasSpecificRules && hasFailureOrLimit;
|
|
3675
|
+
}
|
|
3676
|
+
function hasScenarioValidationMatrix(validationStrategy, riskControls) {
|
|
3677
|
+
const combined = `${validationStrategy}\n${riskControls}`;
|
|
3678
|
+
const hasScenario = /scenario|input data|user action|expected|proves|does not prove|场景|输入|预期|证明/i.test(combined);
|
|
3679
|
+
const hasEvidence = /command|evidence|manual|test|api|ui|sql|hook|artifact|证据|检查/i.test(combined);
|
|
3680
|
+
const hasRiskLink = /risk|impact|control|validation hook|task-stage constraint|风险|影响|控制/i.test(combined);
|
|
3681
|
+
return hasScenario && hasEvidence && hasRiskLink;
|
|
3682
|
+
}
|
|
3683
|
+
function hasTaskStageConstraintDepth(section) {
|
|
3684
|
+
return /boundary|constraint|rollback|ordering|dependency|validation coverage|risky seam|task-stage|atomic|边界|约束|回滚|依赖|验证/i.test(section)
|
|
3685
|
+
&& !/```sdd-task\b/i.test(section);
|
|
3686
|
+
}
|
|
3687
|
+
function hasScoutResolutionSignal(implementationMap, openQuestions, closeEvidence) {
|
|
3688
|
+
return /scout|evidence|observed|grounded|resolved|logged|no existing surface|not applicable/i.test(`${implementationMap}\n${openQuestions}\n${closeEvidence}`);
|
|
3689
|
+
}
|
|
3690
|
+
function extractMarkdownSection(content, section) {
|
|
3691
|
+
for (const sectionName of Array.isArray(section) ? section : [section]) {
|
|
3692
|
+
const escaped = escapeRegex(sectionName).replace(/\s+/g, '\\s+');
|
|
3693
|
+
const match = new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').exec(content);
|
|
3694
|
+
if (!match) {
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
const start = match.index + match[0].length;
|
|
3698
|
+
const rest = content.slice(start);
|
|
3699
|
+
const next = /^##\s+/im.exec(rest);
|
|
3700
|
+
return (next ? rest.slice(0, next.index) : rest).trim();
|
|
3701
|
+
}
|
|
3702
|
+
return null;
|
|
3703
|
+
}
|
|
3704
|
+
function validateTasksDocumentV2(content, tasksPath = 'tasks.md') {
|
|
3705
|
+
const errors = [];
|
|
3706
|
+
if (!/contract:\s*sdd-tasks-doc-v2\b/.test(content)) {
|
|
3707
|
+
errors.push('missing contract: sdd-tasks-doc-v2');
|
|
3708
|
+
}
|
|
3709
|
+
const requiredSections = [
|
|
3710
|
+
['Split Basis', 'Task Split Basis', 'Work Unit Split Basis'],
|
|
3711
|
+
['Execution Order Overview'],
|
|
3712
|
+
['Implementation Tasks', 'Implementation Work Units', 'Implementation Task Units'],
|
|
3713
|
+
['Validation Tasks', 'Validation Work Units', 'Validation Task Units'],
|
|
3714
|
+
['Task Dependencies and Handoff', 'Dependencies and Validation Handoff', 'Task Dependency and Validation Handoff'],
|
|
3715
|
+
['Coverage Check'],
|
|
3716
|
+
['Open Execution Questions', 'Open Questions', 'Execution Questions'],
|
|
3717
|
+
['Tasks Close Quality Evidence', 'Close Quality Evidence']
|
|
3718
|
+
];
|
|
3719
|
+
for (const sectionAliases of requiredSections) {
|
|
3720
|
+
if (!hasMarkdownSection(content, sectionAliases)) {
|
|
3721
|
+
errors.push(`missing section: ${sectionAliases[0]}`);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
const forbiddenSections = [/^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Implementation Diff\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Validation Results\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Runtime Gate Metadata\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Subagent Trace\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Long-Term Backlog\s*$/im];
|
|
3725
|
+
if (forbiddenSections.some((pattern) => pattern.test(content)) || /\b(?:obligationMatrix|projectionPayload|runtimeGateMetadata)\b/i.test(content)) {
|
|
3726
|
+
errors.push('contains forbidden implementation/runtime/subagent section');
|
|
3727
|
+
}
|
|
3728
|
+
const parsed = parseSddTasksMarkdown(content, { tasksPath });
|
|
3729
|
+
if (parsed.tasks.length === 0) {
|
|
3730
|
+
errors.push('missing executable task units');
|
|
3731
|
+
}
|
|
3732
|
+
const taskIds = new Set(parsed.tasks.map((task) => task.id));
|
|
3733
|
+
const taskById = new Map(parsed.tasks.map((task) => [task.id, task]));
|
|
3734
|
+
let hasImplementationTask = false;
|
|
3735
|
+
let hasValidationTask = false;
|
|
3736
|
+
for (const task of parsed.tasks) {
|
|
3737
|
+
const metadata = task.rawMetadata;
|
|
3738
|
+
const taskClass = task.taskClass;
|
|
3739
|
+
const unitType = task.unitType;
|
|
3740
|
+
const dependencies = task.dependsOn;
|
|
3741
|
+
if (taskClass !== 'implementation' && taskClass !== 'validation') {
|
|
3742
|
+
errors.push(`task ${task.id} missing taskClass implementation|validation`);
|
|
3743
|
+
}
|
|
3744
|
+
if (!unitType) {
|
|
3745
|
+
errors.push(`task ${task.id} missing unitType`);
|
|
3746
|
+
}
|
|
3747
|
+
if (!Number.isInteger(task.wave) || (task.wave ?? 0) <= 0) {
|
|
3748
|
+
errors.push(`task ${task.id} missing execution wave`);
|
|
3749
|
+
}
|
|
3750
|
+
if (task.affectedFiles.length === 0) {
|
|
3751
|
+
errors.push(`task ${task.id} missing affected areas`);
|
|
3752
|
+
}
|
|
3753
|
+
for (const dependency of dependencies) {
|
|
3754
|
+
if (!taskIds.has(dependency)) {
|
|
3755
|
+
errors.push(`task ${task.id} references unknown dependency ${dependency}`);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
if (taskClass === 'implementation') {
|
|
3759
|
+
hasImplementationTask = true;
|
|
3760
|
+
const handoffTasks = task.validationHandoff;
|
|
3761
|
+
if (handoffTasks.length === 0) {
|
|
3762
|
+
errors.push(`task ${task.id} missing validation handoff`);
|
|
3763
|
+
}
|
|
3764
|
+
for (const handoffTaskId of handoffTasks) {
|
|
3765
|
+
const handoffTask = taskById.get(handoffTaskId);
|
|
3766
|
+
if (!handoffTask) {
|
|
3767
|
+
errors.push(`task ${task.id} hands off to unknown validation task ${handoffTaskId}`);
|
|
3768
|
+
}
|
|
3769
|
+
else if (handoffTask.taskClass !== 'validation') {
|
|
3770
|
+
errors.push(`task ${task.id} hands off to non-validation task ${handoffTaskId}`);
|
|
3771
|
+
}
|
|
3772
|
+
else if (Number.isInteger(task.wave) && Number.isInteger(handoffTask.wave) && (handoffTask.wave ?? 0) <= (task.wave ?? 0)) {
|
|
3773
|
+
errors.push(`task ${task.id} validation handoff ${handoffTaskId} must be in a later wave`);
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
if (taskClass === 'validation') {
|
|
3778
|
+
hasValidationTask = true;
|
|
3779
|
+
const validatesTasks = metadataNamedListValue(metadata, ['validatesImplementationTasks', 'validates_implementation_tasks']);
|
|
3780
|
+
if (validatesTasks.length === 0) {
|
|
3781
|
+
errors.push(`task ${task.id} missing validatesImplementationTasks`);
|
|
3782
|
+
}
|
|
3783
|
+
for (const validatedTask of validatesTasks) {
|
|
3784
|
+
if (!taskIds.has(validatedTask)) {
|
|
3785
|
+
errors.push(`task ${task.id} validates unknown implementation task ${validatedTask}`);
|
|
3786
|
+
}
|
|
3787
|
+
const validated = taskById.get(validatedTask);
|
|
3788
|
+
if (validated && validated.taskClass !== 'implementation') {
|
|
3789
|
+
errors.push(`task ${task.id} validates non-implementation task ${validatedTask}`);
|
|
3790
|
+
}
|
|
3791
|
+
if (validated && Number.isInteger(task.wave) && Number.isInteger(validated.wave) && (task.wave ?? 0) <= (validated.wave ?? 0)) {
|
|
3792
|
+
errors.push(`task ${task.id} must run after validated implementation task ${validatedTask}`);
|
|
3793
|
+
}
|
|
3794
|
+
if (!dependencies.includes(validatedTask)) {
|
|
3795
|
+
errors.push(`task ${task.id} must depend on validated implementation task ${validatedTask}`);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
if (!hasImplementationTask) {
|
|
3801
|
+
errors.push('missing implementation task');
|
|
3802
|
+
}
|
|
3803
|
+
if (!hasValidationTask) {
|
|
3804
|
+
errors.push('missing validation task');
|
|
3805
|
+
}
|
|
3806
|
+
errors.push(...validateTaskValidationTopology(parsed.tasks));
|
|
3807
|
+
validateTasksDepthSignals(content, errors);
|
|
3808
|
+
errors.push(...taskDependencyCycleErrors(parsed.tasks));
|
|
3809
|
+
return errors;
|
|
3810
|
+
}
|
|
3811
|
+
function validateTasksDepthSignals(content, errors) {
|
|
3812
|
+
const closeEvidence = extractMarkdownSection(content, ['Tasks Close Quality Evidence', 'Close Quality Evidence']) ?? '';
|
|
3813
|
+
if (!hasTaskReviewVerdict(closeEvidence) || !hasTaskReviewReconciliation(closeEvidence)) {
|
|
3814
|
+
errors.push('tasks close quality evidence must include task-review-agent verdict and tasks-manager review reconciliation');
|
|
3815
|
+
}
|
|
3816
|
+
if (!/downstream_execution_guesswork_remaining\s*:\s*\[\s*\]/i.test(closeEvidence)) {
|
|
3817
|
+
errors.push('tasks close quality evidence must declare downstream_execution_guesswork_remaining: []');
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function hasTaskReviewVerdict(section) {
|
|
3821
|
+
return /task-review-agent\s+(?:verdict|review)|review(?:_|\s+)signal|reviewed by task-review-agent/i.test(section);
|
|
3822
|
+
}
|
|
3823
|
+
function hasTaskReviewReconciliation(section) {
|
|
3824
|
+
return /review(?:_|\s+)reconciliation|tasks-manager\s+(?:review\s+)?reconciliation|tasks-manager\s+(?:resolved|adjudicated)|review findings? reconciled|blocking findings resolved|task-review-agent findings resolved/i.test(section);
|
|
3825
|
+
}
|
|
3826
|
+
function hasWorkUnitContextDepth(section) {
|
|
3827
|
+
return /why this task exists|work-unit context|plan decision|design part|must not reinterpret|risk/i.test(section);
|
|
3828
|
+
}
|
|
3829
|
+
function hasUpstreamTraceDepth(section) {
|
|
3830
|
+
return /REQ-\d+/i.test(section) && /AC-\d+/i.test(section) && /planRefs|plan section|plan decision|§\d+/i.test(section);
|
|
3831
|
+
}
|
|
3832
|
+
function hasModificationBoundaryDepth(section) {
|
|
3833
|
+
return /allowed files|allowed code|allowed .*changes|forbidden files|forbidden .*scope|what not to do|modification boundary|scope exclusions/i.test(section);
|
|
3834
|
+
}
|
|
3835
|
+
function hasRollbackUnitDepth(implementationTasks, dependencies) {
|
|
3836
|
+
return /rollback unit|reverted together|rollback boundary|shared rollback/i.test(`${implementationTasks}\n${dependencies}`);
|
|
3837
|
+
}
|
|
3838
|
+
function hasDependencyRationaleDepth(dependencies, implementationTasks) {
|
|
3839
|
+
return /because|why ordering matters|dependency rationale|unlocks|must complete before|parallel/i.test(`${dependencies}\n${implementationTasks}`);
|
|
3840
|
+
}
|
|
3841
|
+
function hasCompletionEvidenceDepth(section) {
|
|
3842
|
+
return /completion evidence|done criteria|observable completion|what does not count|code review|basic code sanity/i.test(section);
|
|
3843
|
+
}
|
|
3844
|
+
function hasValidationExpectedResultDepth(section) {
|
|
3845
|
+
return /expected result|pass criteria|evidence is enough|does not prove|acceptance checks|manual inspection|test/i.test(section);
|
|
3846
|
+
}
|
|
3847
|
+
function hasFailureRouteDepth(section) {
|
|
3848
|
+
return /failure routing|failure route|return to|plan repair|spec\/user decision|environment diagnostic|tasks repair/i.test(section);
|
|
3849
|
+
}
|
|
3850
|
+
function hasMarkdownSection(content, sectionAliases) {
|
|
3851
|
+
return sectionAliases.some((section) => {
|
|
3852
|
+
const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
|
|
3853
|
+
return new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content);
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
function validateTaskValidationTopology(tasks) {
|
|
3857
|
+
const taskIds = new Set(tasks.map((task) => task.id));
|
|
3858
|
+
if (taskIds.size !== tasks.length) {
|
|
3859
|
+
return [];
|
|
3860
|
+
}
|
|
3861
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
3862
|
+
const errors = [];
|
|
3863
|
+
for (const task of tasks) {
|
|
3864
|
+
if (task.taskClass === 'implementation') {
|
|
3865
|
+
if (task.validationHandoff.length !== 1) {
|
|
3866
|
+
errors.push(`task ${task.id} must hand off to exactly one validation task`);
|
|
3867
|
+
continue;
|
|
3868
|
+
}
|
|
3869
|
+
const validationTask = taskById.get(task.validationHandoff[0]);
|
|
3870
|
+
const validatesTasks = validationTask ? metadataNamedListValue(validationTask.rawMetadata, ['validatesImplementationTasks', 'validates_implementation_tasks']) : [];
|
|
3871
|
+
if (validationTask && (validatesTasks.length !== 1 || validatesTasks[0] !== task.id)) {
|
|
3872
|
+
errors.push(`task ${task.id} validation handoff ${validationTask.id} must validate exactly ${task.id}`);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
if (task.taskClass === 'validation') {
|
|
3876
|
+
const validatesTasks = metadataNamedListValue(task.rawMetadata, ['validatesImplementationTasks', 'validates_implementation_tasks']);
|
|
3877
|
+
if (validatesTasks.length !== 1) {
|
|
3878
|
+
errors.push(`task ${task.id} must validate exactly one implementation task`);
|
|
3879
|
+
continue;
|
|
3880
|
+
}
|
|
3881
|
+
const implementationTask = taskById.get(validatesTasks[0]);
|
|
3882
|
+
if (implementationTask?.taskClass === 'implementation' && (implementationTask.validationHandoff.length !== 1 || implementationTask.validationHandoff[0] !== task.id)) {
|
|
3883
|
+
errors.push(`task ${task.id} validates ${implementationTask.id} but ${implementationTask.id} must hand off only to ${task.id}`);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
return errors;
|
|
3888
|
+
}
|
|
3889
|
+
function taskDependencyCycleErrors(tasks) {
|
|
3890
|
+
const taskIds = new Set(tasks.map((task) => task.id));
|
|
3891
|
+
const dependencies = new Map(tasks.map((task) => [task.id, task.dependsOn.filter((dependency) => taskIds.has(dependency))]));
|
|
3892
|
+
const visiting = new Set();
|
|
3893
|
+
const visited = new Set();
|
|
3894
|
+
const errors = [];
|
|
3895
|
+
const visit = (taskId, path) => {
|
|
3896
|
+
if (visited.has(taskId)) {
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
if (visiting.has(taskId)) {
|
|
3900
|
+
errors.push(`task dependency cycle: ${[...path, taskId].join(' -> ')}`);
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
visiting.add(taskId);
|
|
3904
|
+
for (const dependency of dependencies.get(taskId) ?? []) {
|
|
3905
|
+
visit(dependency, [...path, taskId]);
|
|
3906
|
+
}
|
|
3907
|
+
visiting.delete(taskId);
|
|
3908
|
+
visited.add(taskId);
|
|
3909
|
+
};
|
|
3910
|
+
for (const task of tasks) {
|
|
3911
|
+
visit(task.id, []);
|
|
3912
|
+
}
|
|
3913
|
+
return Array.from(new Set(errors));
|
|
3914
|
+
}
|
|
3915
|
+
function metadataNamedListValue(metadata, names) {
|
|
3916
|
+
for (const name of names) {
|
|
3917
|
+
const value = metadataListValue(metadata[name]);
|
|
3918
|
+
if (value.length > 0) {
|
|
3919
|
+
return value;
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
return [];
|
|
3923
|
+
}
|
|
3924
|
+
function metadataNamedScalarValue(metadata, names) {
|
|
3925
|
+
for (const name of names) {
|
|
3926
|
+
const value = metadataScalarValue(metadata[name]);
|
|
3927
|
+
if (value !== null) {
|
|
3928
|
+
return value;
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
return null;
|
|
3932
|
+
}
|
|
3933
|
+
function metadataListValue(value) {
|
|
3934
|
+
if (Array.isArray(value)) {
|
|
3935
|
+
return value.map((item) => String(item).trim()).filter((item) => item.length > 0);
|
|
3936
|
+
}
|
|
3937
|
+
const scalar = metadataScalarValue(value);
|
|
3938
|
+
return scalar ? [scalar] : [];
|
|
3939
|
+
}
|
|
3940
|
+
function metadataScalarValue(value) {
|
|
3941
|
+
if (typeof value === 'string') {
|
|
3942
|
+
const trimmed = value.trim();
|
|
3943
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3944
|
+
}
|
|
3945
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
3946
|
+
return String(value);
|
|
3947
|
+
}
|
|
3948
|
+
return null;
|
|
3949
|
+
}
|
|
3950
|
+
async function readOptionalText(filePath) {
|
|
3951
|
+
try {
|
|
3952
|
+
return await readFile(filePath, 'utf8');
|
|
3953
|
+
}
|
|
3954
|
+
catch (error) {
|
|
3955
|
+
if (error.code === 'ENOENT') {
|
|
3956
|
+
return null;
|
|
3957
|
+
}
|
|
3958
|
+
throw error;
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
function specAdjudicationProjectionRef(scope) {
|
|
3962
|
+
return {
|
|
3963
|
+
kind: 'projection',
|
|
3964
|
+
ref: `${SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${specCollaborationScopeKey(scope)}`
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
function planAdjudicationProjectionRef(scope) {
|
|
3968
|
+
return {
|
|
3969
|
+
kind: 'projection',
|
|
3970
|
+
ref: `${PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${planCollaborationScopeKey(scope)}`
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
function tasksAdjudicationProjectionRef(scope) {
|
|
3974
|
+
return {
|
|
3975
|
+
kind: 'projection',
|
|
3976
|
+
ref: `${TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${tasksCollaborationScopeKey(scope)}`
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
function executeAdjudicationProjectionRef(scope) {
|
|
3980
|
+
return {
|
|
3981
|
+
kind: 'projection',
|
|
3982
|
+
ref: `${EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${executeCollaborationScopeKey(scope)}`
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
function shipAdjudicationProjectionRef(scope) {
|
|
3986
|
+
return {
|
|
3987
|
+
kind: 'projection',
|
|
3988
|
+
ref: `${SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${shipCollaborationScopeKey(scope)}`
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
function stableHash(value) {
|
|
3992
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
3993
|
+
}
|
|
3994
|
+
function uniqueStrings(values) {
|
|
3995
|
+
return Array.from(new Set(values.filter((value) => value.length > 0)));
|
|
3996
|
+
}
|
|
3997
|
+
function uniqueRuntimeRefs(refs) {
|
|
3998
|
+
const seen = new Set();
|
|
3999
|
+
return refs.filter((ref) => {
|
|
4000
|
+
const key = `${ref.kind}:${ref.ref}:${ref.hash ?? ''}`;
|
|
4001
|
+
if (seen.has(key)) {
|
|
4002
|
+
return false;
|
|
4003
|
+
}
|
|
4004
|
+
seen.add(key);
|
|
4005
|
+
return true;
|
|
4006
|
+
});
|
|
4007
|
+
}
|
|
4008
|
+
function runtimeProjectionPayload(payload) {
|
|
4009
|
+
if (!payload || typeof payload !== 'object' || !('payload' in payload)) {
|
|
4010
|
+
return null;
|
|
4011
|
+
}
|
|
4012
|
+
return payload.payload;
|
|
4013
|
+
}
|
|
4014
|
+
function stableId(prefix, scope, ...parts) {
|
|
4015
|
+
const hash = createHash('sha256').update(JSON.stringify([scope, parts]), 'utf8').digest('hex').slice(0, 16);
|
|
4016
|
+
return `${prefix}-${hash}`;
|
|
4017
|
+
}
|
|
4018
|
+
//# sourceMappingURL=stage-collaboration.js.map
|