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