@moretea-labs/forge 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +58 -62
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +42 -62
- package/README.md +2 -2
- package/SKILL.md +0 -1
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/adapters/browser/index.ts +2 -0
- package/adapters/chatgpt/browser-delivery-host.ts +71 -0
- package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
- package/adapters/chatgpt/controller-binding-store.ts +55 -0
- package/adapters/chatgpt/controller-host.ts +119 -0
- package/adapters/chatgpt/controller-round-host.ts +78 -0
- package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
- package/adapters/chatgpt/provider-delivery.ts +113 -0
- package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
- package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
- package/adapters/computer/desktop-operator-contract.ts +7 -0
- package/adapters/computer/desktop-operator-discovery.ts +201 -0
- package/adapters/computer/desktop-operator-negotiation.ts +174 -0
- package/adapters/computer/desktop-operator-provider.ts +312 -0
- package/adapters/computer/index.ts +1 -0
- package/adapters/computer/interaction-target-authority.ts +944 -0
- package/adapters/controller-process/binding-store.ts +34 -0
- package/adapters/controller-process/controller-host.ts +26 -0
- package/adapters/mcp/access-mode.ts +132 -0
- package/adapters/mcp/audit.ts +32 -0
- package/adapters/mcp/auth.ts +407 -0
- package/adapters/mcp/controller-round-compatibility.ts +184 -0
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
- package/adapters/mcp/instructions.ts +36 -0
- package/adapters/mcp/legacy-context.ts +18 -0
- package/adapters/mcp/multi-repository.ts +437 -0
- package/adapters/mcp/oauth.ts +391 -0
- package/adapters/mcp/paths.ts +140 -0
- package/adapters/mcp/policy.ts +232 -0
- package/adapters/mcp/proxy-env.ts +62 -0
- package/adapters/mcp/readiness-projection.ts +24 -0
- package/adapters/mcp/redaction.ts +21 -0
- package/adapters/mcp/repo.ts +24 -0
- package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
- package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
- package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
- package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
- package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
- package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
- package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
- package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
- package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
- package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
- package/adapters/mcp/runtime-gateway/router.ts +728 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
- package/adapters/mcp/server.ts +1048 -0
- package/adapters/mcp/setup.ts +791 -0
- package/adapters/mcp/tool-capability-cache.ts +87 -0
- package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
- package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
- package/adapters/mcp/tool-mapping/tools.ts +9 -0
- package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
- package/adapters/mcp/toolset-names.ts +74 -0
- package/adapters/mcp/toolset-selection.ts +41 -0
- package/adapters/mcp/toolset.ts +336 -0
- package/adapters/mcp/transports/http.ts +1456 -0
- package/adapters/mcp/transports/session-registry.ts +513 -0
- package/adapters/mcp/transports/stdio.ts +8 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
- package/adapters/mcp/types.ts +41 -0
- package/assets/AGENTS.md +2 -16
- package/assets/CLAUDE.md +2 -16
- package/assets/hooks/AGENTS.md +3 -14
- package/assets/hooks/CLAUDE.md +3 -14
- package/assets/hooks/hook-input.sh +9 -2
- package/assets/hooks/lib/workflow-state.sh +28 -10
- package/assets/hooks/prompt-guard.sh +63 -37
- package/assets/hooks/security-sentinel.sh +2 -1
- package/assets/hooks/session-start-context.sh +7 -5
- package/assets/hooks/stop-orchestrator.sh +13 -3
- package/assets/initializer-question-pack.v4.json +11 -7
- package/assets/partials/05-workflow.partial.md +18 -73
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -2
- package/assets/partials-agents/04-task-protocol.partial.md +14 -59
- package/assets/partials-agents/06-quality-safety.partial.md +11 -16
- package/assets/partials-agents/08-deep-docs.partial.md +1 -2
- package/assets/plan-map.json +11 -11
- package/assets/plugin-registry.v1.json +9 -3
- package/assets/plugin-trust-policy.v1.json +6 -0
- package/assets/recovery/ForgeRecovery.ps1 +100 -0
- package/assets/recovery/forge-wsl-rescue +271 -0
- package/assets/reference-configs/development-protocol.md +11 -15
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/global-working-rules.md +9 -1
- package/assets/reference-configs/harness-overview.md +22 -112
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +2 -2
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/architecture-event.ts +3 -34
- package/assets/templates/helpers/architecture-queue.sh +61 -10
- package/assets/templates/helpers/archive-workflow.sh +90 -180
- package/assets/templates/helpers/capability-config.ts +8 -11
- package/assets/templates/helpers/capability-resolver.ts +37 -19
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +1 -9
- package/assets/templates/helpers/check-task-sync.sh +82 -11
- package/assets/templates/helpers/check-task-workflow.sh +1 -3
- package/assets/templates/helpers/context-contract-sync.sh +1 -12
- package/assets/templates/helpers/contract-worktree.sh +26 -24
- package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
- package/assets/templates/helpers/inspect-project-state.ts +1 -1
- package/assets/templates/helpers/migrate-project-template.sh +1 -1
- package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
- package/assets/templates/helpers/new-plan.sh +2 -2
- package/assets/templates/helpers/plan-to-todo.sh +3 -31
- package/assets/templates/helpers/sprint-backlog.sh +1 -1
- package/assets/templates/helpers/switch-plan.sh +1 -1
- package/assets/templates/helpers/workflow-contract.ts +1 -0
- package/assets/workflow-contract.v1.json +63 -171
- package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
- package/docs/architecture/CURRENT.md +148 -13
- package/docs/architecture/EVOLUTION.md +13 -0
- package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
- package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
- package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
- package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
- package/docs/architecture/domains/verification.md +7 -0
- package/docs/architecture/domains/workflow-engine.md +1 -1
- package/docs/architecture/index.md +3 -3
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
- package/docs/forge-chatgpt-mcp-setup.md +5 -3
- package/docs/forge-plugin-management.md +20 -4
- package/docs/forge-runtime-self-healing-loop.md +2 -0
- package/docs/operations/benchmark-cross-version-freeze.md +38 -0
- package/docs/operations/controller-browser-plugin.md +3 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/platform-support.md +16 -8
- package/docs/operations/platform-support.zh-CN.md +2 -2
- package/docs/operations/standalone-disaster-recovery.md +26 -1
- package/docs/operations/uu-remote-rescue.md +41 -0
- package/docs/operations/windows-wsl-host-rescue.md +133 -0
- package/docs/public-usage-guide.md +1 -1
- package/docs/public-usage-guide.zh-CN.md +1 -1
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/02-connect-chatgpt.md +4 -4
- package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +23 -14
- package/packages/kernel/controller/api/index.ts +2 -0
- package/packages/kernel/controller/application/controller-round-service.ts +29 -0
- package/packages/kernel/controller/application/controller-service.ts +30 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
- package/packages/kernel/controller/domain/controller-round.ts +107 -0
- package/packages/kernel/controller/domain/execution-quality.ts +133 -0
- package/packages/kernel/controller/domain/types.ts +56 -0
- package/packages/kernel/controller/index.ts +1 -0
- package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
- package/packages/kernel/controller/ports/controller-host.ts +16 -0
- package/packages/kernel/goal/api/index.ts +1 -0
- package/packages/kernel/goal/domain/types.ts +40 -0
- package/packages/kernel/goal/index.ts +1 -0
- package/packages/kernel/identity/api/index.ts +5 -0
- package/packages/kernel/identity/application/identity-service.ts +67 -0
- package/packages/kernel/identity/domain/scope.ts +63 -0
- package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
- package/packages/kernel/identity/domain/types.ts +44 -0
- package/packages/kernel/identity/domain/workspace.ts +56 -0
- package/packages/kernel/identity/index.ts +1 -0
- package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
- package/packages/kernel/memory/api/index.ts +5 -0
- package/packages/kernel/memory/application/experience-service.ts +118 -0
- package/packages/kernel/memory/application/outcome-service.ts +77 -0
- package/packages/kernel/memory/domain/experience.ts +103 -0
- package/packages/kernel/memory/domain/operational-prior.ts +92 -0
- package/packages/kernel/memory/index.ts +1 -0
- package/packages/kernel/progression/api/index.ts +2 -0
- package/packages/kernel/progression/application/projector.ts +211 -0
- package/packages/kernel/progression/domain/types.ts +116 -0
- package/packages/kernel/progression/index.ts +1 -0
- package/packages/kernel/scheduler/api/index.ts +6 -0
- package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
- package/packages/kernel/scheduler/application/eligibility.ts +252 -0
- package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
- package/packages/kernel/scheduler/application/settlement.ts +197 -0
- package/packages/kernel/scheduler/domain/continuation.ts +38 -0
- package/packages/kernel/scheduler/domain/schedule.ts +149 -0
- package/packages/kernel/scheduler/index.ts +1 -0
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
- package/packages/kernel/work/api/index.ts +15 -0
- package/packages/kernel/work/application/work-service.ts +148 -0
- package/packages/kernel/work/domain/admission-policy.ts +34 -0
- package/packages/kernel/work/domain/check-receipt.ts +43 -0
- package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
- package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
- package/packages/kernel/work/domain/engineering-design.ts +281 -0
- package/packages/kernel/work/domain/engineering-profile.ts +242 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
- package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
- package/packages/kernel/work/domain/implementation-review.ts +510 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
- package/packages/kernel/work/domain/state-machine.ts +259 -0
- package/packages/kernel/work/domain/types.ts +528 -0
- package/packages/kernel/work/index.ts +1 -0
- package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
- package/packages/kernel/work/ports/work-contract-store.ts +18 -0
- package/packages/plugin-runtime/browser/index.ts +3 -0
- package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
- package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
- package/packages/plugin-runtime/browser/session-authority.ts +61 -0
- package/packages/plugin-runtime/computer/index.ts +6 -0
- package/packages/plugin-runtime/computer/provider-error.ts +26 -0
- package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
- package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
- package/packages/plugin-runtime/computer/provider.ts +19 -0
- package/packages/plugin-runtime/computer/target-authority.ts +190 -0
- package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
- package/packages/plugin-runtime/external/index.ts +1 -0
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
- package/packages/plugin-runtime/index.ts +3 -0
- package/packages/protocols/browser/index.ts +1 -0
- package/packages/protocols/browser/session.ts +98 -0
- package/packages/protocols/computer/contract.ts +199 -0
- package/packages/protocols/computer/index.ts +1 -0
- package/packages/protocols/failure/contract.ts +158 -0
- package/packages/protocols/failure/index.ts +1 -0
- package/packages/protocols/handoff/index.ts +1 -0
- package/packages/protocols/handoff/status.ts +22 -0
- package/packages/protocols/mcp/execution-context.ts +11 -0
- package/packages/protocols/mcp/index.ts +2 -0
- package/packages/protocols/mcp/tool-contract.ts +42 -0
- package/packages/workflow-runtime/api/index.ts +2 -0
- package/packages/workflow-runtime/application/interpreter.ts +342 -0
- package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
- package/packages/workflow-runtime/index.ts +1 -0
- package/scripts/assemble-template.ts +1 -1
- package/scripts/benchmark-computer-hotpath.ts +255 -0
- package/scripts/benchmark-controller-round2.ts +54 -2
- package/scripts/benchmark-ios-agent-device.ts +3 -6
- package/scripts/benchmark-mcp-transport.ts +3 -4
- package/scripts/benchmark-operational-memory-activation.ts +235 -0
- package/scripts/benchmark-operational-shadow.ts +28 -0
- package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
- package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
- package/scripts/canary-controller-semantic-wait.ts +195 -0
- package/scripts/canary-global-hook.sh +2 -2
- package/scripts/check-mcp-compatibility.ts +173 -7
- package/scripts/check-platform-support.mjs +28 -2
- package/scripts/check-public-docs.sh +1 -1
- package/scripts/check-release-readiness.sh +1 -1
- package/scripts/check-repository-hygiene.mjs +92 -0
- package/scripts/check-runtime-architecture.mjs +1206 -95
- package/scripts/check-source-duplication.mjs +9 -1
- package/scripts/check-tarball-install-smoke.sh +98 -15
- package/scripts/controller-ngrok-rotation.sh +5 -4
- package/scripts/create-project-dirs.sh +10 -74
- package/scripts/forge-local-recovery-helper.mjs +204 -0
- package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
- package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
- package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
- package/scripts/hook-shim.sh +2 -2
- package/scripts/init-project.sh +12 -70
- package/scripts/install-desktop-operator-registration.ts +4 -2
- package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
- package/scripts/install-standalone-recovery.ts +122 -10
- package/scripts/install-uu-remote-rescue-registration.ts +74 -0
- package/scripts/install-windows-host-recovery-registration.ts +22 -0
- package/scripts/lib/controller-home-env.sh +6 -3
- package/scripts/lib/project-init-lib.sh +39 -582
- package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
- package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
- package/scripts/open-source-audit-allowlist.txt +12 -1
- package/scripts/portability-bindings.mjs +70 -0
- package/scripts/run-bounded-command.mjs +55 -0
- package/scripts/run-bun-test-file.ts +22 -6
- package/scripts/run-governed-gate.ts +4 -1
- package/scripts/smoke-mcp-http-runtime.ts +1 -1
- package/scripts/smoke-runtime-recovery.ts +34 -1
- package/scripts/smoke-schedule-engine.ts +1 -1
- package/scripts/stage-runtime-release.ts +6 -1
- package/scripts/test-governance.ts +51 -2
- package/src/cli/agent-jobs/job-manager.ts +10 -1
- package/src/cli/chatgpt-browser/bind-server.ts +2 -1
- package/src/cli/chatgpt-browser/binding.ts +20 -10
- package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
- package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
- package/src/cli/chatgpt-browser/engine.ts +10 -11
- package/src/cli/chatgpt-browser/native-provider.ts +24 -83
- package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
- package/src/cli/chatgpt-browser/types.ts +1 -0
- package/src/cli/commands/bootstrap-control.ts +334 -0
- package/src/cli/commands/brain-assistant.ts +38 -0
- package/src/cli/commands/brain.ts +193 -40
- package/src/cli/commands/chatgpt.ts +127 -0
- package/src/cli/commands/computer.ts +437 -0
- package/src/cli/commands/doctor.ts +18 -1
- package/src/cli/commands/init-hook.ts +71 -61
- package/src/cli/commands/init.ts +3 -1
- package/src/cli/commands/mcp.ts +16 -10
- package/src/cli/commands/plugin.ts +38 -12
- package/src/cli/commands/recovery.ts +493 -80
- package/src/cli/commands/runtime.ts +9 -4
- package/src/cli/commands/setup-profile.ts +89 -39
- package/src/cli/commands/status.ts +2 -1
- package/src/cli/controller/check-runner.ts +487 -98
- package/src/cli/controller/context/types.ts +7 -1
- package/src/cli/controller/context-pack.ts +23 -3
- package/src/cli/controller/issue-store.ts +1 -1
- package/src/cli/controller/systemd-user.ts +110 -0
- package/src/cli/controller/types.ts +10 -70
- package/src/cli/controller/worklog.ts +67 -13
- package/src/cli/editing/edit-operation-contract.ts +92 -0
- package/src/cli/editing/edit-session.ts +289 -13
- package/src/cli/github/plugin.ts +17 -12
- package/src/cli/hook/route-registry.ts +3 -5
- package/src/cli/hook/runtime.ts +49 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/local-bridge/connector-freshness.ts +2 -2
- package/src/cli/local-bridge/console-automations.ts +2 -2
- package/src/cli/local-bridge/facade-api.ts +41 -14
- package/src/cli/local-bridge/job-store.ts +1 -1
- package/src/cli/local-bridge/server.ts +24 -27
- package/src/cli/mcp/access-mode.ts +2 -132
- package/src/cli/mcp/access-tools.ts +2 -387
- package/src/cli/mcp/audit.ts +2 -32
- package/src/cli/mcp/auth.ts +2 -404
- package/src/cli/mcp/instructions.ts +2 -34
- package/src/cli/mcp/legacy-context.ts +2 -16
- package/src/cli/mcp/legacy-tool-service.ts +2 -5308
- package/src/cli/mcp/multi-repository.ts +2 -436
- package/src/cli/mcp/oauth.ts +2 -375
- package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
- package/src/cli/mcp/paths.ts +2 -140
- package/src/cli/mcp/policy.ts +2 -232
- package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
- package/src/cli/mcp/proxy-env.ts +2 -62
- package/src/cli/mcp/readiness-projection.ts +2 -24
- package/src/cli/mcp/redaction.ts +2 -21
- package/src/cli/mcp/repo.ts +2 -24
- package/src/cli/mcp/repository-tools.ts +2 -1613
- package/src/cli/mcp/server.ts +2 -951
- package/src/cli/mcp/setup.ts +2 -737
- package/src/cli/mcp/tool-capability-cache.ts +2 -87
- package/src/cli/mcp/tool-contract.ts +2 -14
- package/src/cli/mcp/tools.ts +2 -9
- package/src/cli/mcp/toolset-names.ts +2 -70
- package/src/cli/mcp/toolset-selection.ts +2 -41
- package/src/cli/mcp/toolset.ts +2 -336
- package/src/cli/mcp/transports/AGENTS.md +1 -10
- package/src/cli/mcp/transports/CLAUDE.md +1 -10
- package/src/cli/mcp/transports/http.ts +2 -1414
- package/src/cli/mcp/transports/session-registry.ts +2 -490
- package/src/cli/mcp/transports/stdio.ts +2 -8
- package/src/cli/mcp/types.ts +2 -41
- package/src/cli/mcp/umbrella-tools.ts +2 -117
- package/src/cli/repositories/command-executor.ts +64 -18
- package/src/cli/repositories/command-scope.ts +117 -1
- package/src/cli/repositories/controller-home.ts +119 -1
- package/src/cli/repositories/identity.ts +13 -0
- package/src/cli/repositories/registry.ts +68 -49
- package/src/cli/repositories/repository-snapshot.ts +53 -7
- package/src/cli/repositories/runtime-storage.ts +27 -2
- package/src/cli/repositories/safe-patch.ts +34 -38
- package/src/cli/repositories/selected-path-actions.ts +22 -2
- package/src/cli/repositories/structured-git.ts +105 -25
- package/src/cli/runtime/helper-runner.ts +7 -0
- package/src/cli/runtime-invocation.ts +8 -1
- package/src/cli/tools/codegraph.ts +44 -25
- package/src/core/adoption/manifest-templates.ts +1 -1
- package/src/core/adoption/plan.ts +3 -26
- package/src/core/adoption/workflow-contract-plan.ts +17 -9
- package/src/effects/process-runner.ts +8 -14
- package/src/runtime/context/assistant-context.ts +154 -0
- package/src/runtime/context/assistant-work-context.ts +147 -0
- package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
- package/src/runtime/context/codegraph-read-provider.ts +55 -22
- package/src/runtime/context/codegraph-sidecar.cjs +8 -1
- package/src/runtime/context/context-closure.ts +268 -0
- package/src/runtime/context/context-plane.ts +211 -0
- package/src/runtime/context/project-engineering-contract.ts +60 -0
- package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
- package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
- package/src/runtime/control-plane/bootstrap/index.ts +5 -0
- package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
- package/src/runtime/control-plane/bootstrap/store.ts +21 -0
- package/src/runtime/control-plane/bootstrap/types.ts +87 -0
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
- package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +369 -99
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
- package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
- package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
- package/src/runtime/control-plane/execution/validation.ts +28 -3
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
- package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
- package/src/runtime/control-plane/execution/work-handle-authority.ts +219 -22
- package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
- package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
- package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
- package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
- package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
- package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
- package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
- package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
- package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
- package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
- package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
- package/src/runtime/control-plane/facade/index.ts +4 -2
- package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
- package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
- package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
- package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
- package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
- package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
- package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
- package/src/runtime/control-plane/facade/types.ts +189 -532
- package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
- package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
- package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
- package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
- package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
- package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
- package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
- package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
- package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
- package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
- package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
- package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
- package/src/runtime/control-plane/release-retention.ts +56 -16
- package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
- package/src/runtime/control-plane/routing/route-policy.ts +19 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
- package/src/runtime/control-plane/runtime-generation.ts +6 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
- package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
- package/src/runtime/diagnostics/incident-repair.ts +406 -0
- package/src/runtime/diagnostics/mcp-timing.ts +125 -6
- package/src/runtime/diagnostics/performance.ts +6 -14
- package/src/runtime/diagnostics/read-only-tool.ts +1 -1
- package/src/runtime/evidence/artifact-store.ts +65 -2
- package/src/runtime/evidence/operational-shadow.ts +38 -0
- package/src/runtime/evidence/process-check-receipt.ts +5 -43
- package/src/runtime/evidence/sensitive-output.ts +4 -4
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +121 -2
- package/src/runtime/execution/managed-workspace.ts +55 -5
- package/src/runtime/execution/process-runtime/canary.ts +19 -5
- package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
- package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
- package/src/runtime/execution/process-runtime/check-result.ts +73 -2
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
- package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
- package/src/runtime/execution/process-runtime/index.ts +1 -0
- package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
- package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
- package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
- package/src/runtime/execution/process-runtime/runtime.ts +132 -15
- package/src/runtime/execution/process-runtime/types.ts +22 -0
- package/src/runtime/execution/workers/executor.ts +16 -13
- package/src/runtime/execution/workers/worker-entry.ts +1 -1
- package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
- package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
- package/src/runtime/gateway/mcp/process-tools.ts +2 -289
- package/src/runtime/gateway/mcp/router.ts +2 -1125
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
- package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
- package/src/runtime/maintenance/cleanup.ts +38 -24
- package/src/runtime/platform/browser-product-discovery.ts +174 -0
- package/src/runtime/platform/computer-platform.ts +14 -0
- package/src/runtime/platform/executable-discovery.ts +54 -0
- package/src/runtime/platform/fetch-http-server.ts +90 -0
- package/src/runtime/platform/service-manager.ts +138 -0
- package/src/runtime/platform/windows-host-discovery.ts +118 -0
- package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
- package/src/runtime/plugins/action-application.ts +135 -0
- package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
- package/src/runtime/plugins/browser-adapter.ts +140 -100
- package/src/runtime/plugins/browser-automation-service.ts +15 -36
- package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
- package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
- package/src/runtime/plugins/browser-native-session-service.ts +1 -1
- package/src/runtime/plugins/browser-provider-registry.ts +2 -109
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
- package/src/runtime/plugins/browser-runtime.ts +41 -9
- package/src/runtime/plugins/browser-session-authority.ts +101 -291
- package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
- package/src/runtime/plugins/browser-session-store.ts +229 -36
- package/src/runtime/plugins/browser-session-types.ts +2 -98
- package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
- package/src/runtime/plugins/catalog-trust.ts +82 -0
- package/src/runtime/plugins/computer-registration.ts +839 -0
- package/src/runtime/plugins/config-store.ts +119 -0
- package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
- package/src/runtime/plugins/errors.ts +8 -1
- package/src/runtime/plugins/external-adapter.ts +45 -322
- package/src/runtime/plugins/external-provider-policy.ts +48 -0
- package/src/runtime/plugins/external-registration.ts +9 -1
- package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
- package/src/runtime/plugins/external-unix-socket.ts +64 -193
- package/src/runtime/plugins/first-party-external-registration.ts +163 -0
- package/src/runtime/plugins/first-party-registry.ts +3 -1
- package/src/runtime/plugins/github-adapter.ts +1 -2
- package/src/runtime/plugins/gmail-adapter.ts +6 -6
- package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
- package/src/runtime/plugins/google-shared.ts +31 -46
- package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
- package/src/runtime/plugins/ios-adapter.ts +1 -2
- package/src/runtime/plugins/local-system-adapter.ts +180 -0
- package/src/runtime/plugins/macos-capability-broker.ts +15 -105
- package/src/runtime/plugins/resend-adapter.ts +15 -24
- package/src/runtime/plugins/store.ts +493 -51
- package/src/runtime/plugins/types.ts +8 -1
- package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
- package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
- package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
- package/src/runtime/projections/controller-context.ts +12 -6
- package/src/runtime/projections/git-status-sampler.ts +73 -22
- package/src/runtime/projections/materialized-view.ts +14 -8
- package/src/runtime/recovery/capability-action-application.ts +228 -0
- package/src/runtime/recovery/index.ts +3 -0
- package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
- package/src/runtime/recovery/local-jobs-repair.ts +14 -14
- package/src/runtime/recovery/maintenance-executor.ts +358 -76
- package/src/runtime/recovery/quarantine-retention.ts +256 -0
- package/src/runtime/release/identity.ts +6 -0
- package/src/runtime/resources/leases/store.ts +1 -1
- package/src/runtime/root/active-execution-power-assertion.ts +139 -0
- package/src/runtime/root/assistant-learning-loop.ts +45 -0
- package/src/runtime/root/browser-session-composition.ts +59 -0
- package/src/runtime/root/computer-composition.ts +191 -0
- package/src/runtime/root/computer-target-composition.ts +16 -0
- package/src/runtime/root/computer-target-persistence.ts +39 -0
- package/src/runtime/root/controller-round-composition.ts +107 -0
- package/src/runtime/root/controller-services.ts +2 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/gateway-adapter.ts +3 -3
- package/src/runtime/root/local-bridge.ts +5 -2
- package/src/runtime/root/mcp-transport.ts +5 -5
- package/src/runtime/root/package-connector-service.ts +108 -56
- package/src/runtime/root/package-runtime-release.ts +85 -18
- package/src/runtime/root/package-runtime-service.ts +60 -97
- package/src/runtime/root/release-execution-canary.ts +71 -0
- package/src/runtime/root/release-loader.ts +68 -0
- package/src/runtime/root/release-manifest.ts +106 -2
- package/src/runtime/root/release-materialize.ts +103 -4
- package/src/runtime/root/release-store.ts +31 -2
- package/src/runtime/root/runtime.ts +240 -25
- package/src/runtime/root/scheduled-controller-composition.ts +51 -0
- package/src/runtime/root/scheduler.ts +12 -4
- package/src/runtime/root/service-runner.ts +1 -1
- package/src/runtime/root/service.ts +41 -16
- package/src/runtime/root/status.ts +1 -0
- package/src/runtime/root/types.ts +24 -1
- package/src/runtime/shared/local-bridge-surface.ts +1 -1
- package/src/runtime/shared/process-environment.ts +19 -4
- package/src/runtime/shared/storage-capacity.ts +68 -0
- package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
- package/src/runtime/standalone-recovery/core.ts +984 -230
- package/src/runtime/standalone-recovery/entry.ts +182 -51
- package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
- package/src/runtime/standalone-recovery/installer.ts +212 -104
- package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
- package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
- package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
- package/src/runtime/standalone-recovery/performance.ts +135 -0
- package/src/runtime/standalone-recovery/release.ts +5 -0
- package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +191 -343
- package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
- package/src/runtime/workflow/schedules/settlement.ts +2 -191
- package/src/runtime/workflow/schedules/store.ts +2 -607
- package/src/runtime/workflow/schedules/types.ts +2 -127
- package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
- package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
- package/src/runtime/workflows/runtime.ts +486 -0
- package/src/testing/test-governance.ts +90 -29
- package/assets/templates/helpers/archive-architecture-request.sh +0 -244
- package/docs/architecture/history/README.md +0 -7
- package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
- package/docs/architecture/history/global-hook-runtime.md +0 -166
- package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
- package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
- package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
- package/docs/architecture/requests/archive/2026/root.md +0 -68
- package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
- package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
- package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
- package/scripts/archive-architecture-request.sh +0 -4
- package/scripts/run-skill-evals.ts +0 -1022
|
@@ -0,0 +1,1876 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { ensureControllerHome, repositoryControllerRoot } from '../../../../src/cli/repositories/controller-home';
|
|
5
|
+
import { withControllerLock } from '../../../../src/cli/repositories/locks';
|
|
6
|
+
import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../../../../src/runtime/shared/json-files';
|
|
7
|
+
import {
|
|
8
|
+
listControlPlaneRecords,
|
|
9
|
+
listControlPlaneRecordsExcludingPayloadTextValues,
|
|
10
|
+
initializeControlPlanePayloadTextExclusionIndex,
|
|
11
|
+
readControlPlaneRecord,
|
|
12
|
+
readControlPlaneRecordWithinTransaction,
|
|
13
|
+
withControlPlaneTransaction,
|
|
14
|
+
writeControlPlaneRecordWithinTransaction,
|
|
15
|
+
} from '../../../../src/runtime/control-plane/persistence/sqlite-store';
|
|
16
|
+
import {
|
|
17
|
+
MAX_IMPLEMENTATION_REVIEW_HISTORY,
|
|
18
|
+
authoritativeImplementationReviewVerificationEvidence,
|
|
19
|
+
implementationReviewDecisionTarget,
|
|
20
|
+
latestImplementationReview,
|
|
21
|
+
normalizeImplementationReviewEvidence,
|
|
22
|
+
validateImplementationReviewRecord,
|
|
23
|
+
workRequiresImplementationReview,
|
|
24
|
+
type WorkImplementationReviewRecord,
|
|
25
|
+
} from '../domain/implementation-review';
|
|
26
|
+
import { phaseIndex, suggestedActionsForStatus, transitionPhaseEvidence, validateWorkSemanticTransition, validateWorkSemantics } from '../domain/state-machine';
|
|
27
|
+
import {
|
|
28
|
+
WORK_ADMISSION_POLICY_KEY,
|
|
29
|
+
WORK_ADMISSION_POLICY_NAMESPACE,
|
|
30
|
+
WORK_ADMISSION_POLICY_SCOPE,
|
|
31
|
+
assertWorkAdmissionPolicyAllows,
|
|
32
|
+
normalWorkAdmissionPolicy,
|
|
33
|
+
type WorkAdmissionPolicy,
|
|
34
|
+
} from '../domain/admission-policy';
|
|
35
|
+
import {
|
|
36
|
+
type EvidenceRef,
|
|
37
|
+
type CompletionOutcome,
|
|
38
|
+
type DispatchState,
|
|
39
|
+
type EvidenceState,
|
|
40
|
+
type PolicyDecision,
|
|
41
|
+
type SuggestedNextAction,
|
|
42
|
+
type VerificationRecord,
|
|
43
|
+
type WorkReconciliationRecord,
|
|
44
|
+
type SubmittedWorkOperation,
|
|
45
|
+
type WorkContract,
|
|
46
|
+
type WorkContractStatus,
|
|
47
|
+
type WorkRisk,
|
|
48
|
+
type WorkKind,
|
|
49
|
+
type WorkPhase,
|
|
50
|
+
type WorkPhaseEvidence,
|
|
51
|
+
type WorkPhaseEvidenceMap,
|
|
52
|
+
type WorkPhaseEvidenceState,
|
|
53
|
+
type WorkContractStore,
|
|
54
|
+
executionPlacementForWork,
|
|
55
|
+
isDirectEditWorkCompletionReceipt,
|
|
56
|
+
isRepositoryCompletionReceipt,
|
|
57
|
+
isTerminalWorkContractStatus,
|
|
58
|
+
TERMINAL_WORK_CONTRACT_STATUSES,
|
|
59
|
+
semanticScopeRefForWork,
|
|
60
|
+
} from '../domain/types';
|
|
61
|
+
|
|
62
|
+
import type { WorkContractStoreLocation, WorkContractStoreOptions } from '../ports/work-contract-store';
|
|
63
|
+
export type { WorkContractStoreLocation, WorkContractStoreOptions } from '../ports/work-contract-store';
|
|
64
|
+
|
|
65
|
+
export type CreateWorkContractInput = Omit<
|
|
66
|
+
WorkContract,
|
|
67
|
+
'schemaVersion' | 'status' | 'createdAt' | 'updatedAt' | 'risk' | 'workKind' | 'dispatchState' | 'evidenceState' | 'completionOutcome' | 'phase' | 'phaseEvidence' | 'completionReceipt' | 'evidenceRefs' | 'handoffRefs' | 'suggestedNextActions' | 'policyDecisions' | 'checkRefs' | 'implementationReviews' | 'reconciliations' | 'driver' | 'worktreePolicy' | 'evidencePolicy' | 'approvalPolicy' | 'recoveryPolicy'
|
|
68
|
+
> & {
|
|
69
|
+
risk?: WorkRisk;
|
|
70
|
+
status?: WorkContractStatus;
|
|
71
|
+
createdAt?: string;
|
|
72
|
+
updatedAt?: string;
|
|
73
|
+
workKind?: WorkKind;
|
|
74
|
+
dispatchState?: DispatchState;
|
|
75
|
+
evidenceState?: EvidenceState;
|
|
76
|
+
completionOutcome?: CompletionOutcome;
|
|
77
|
+
phase?: WorkContract['phase'];
|
|
78
|
+
completionReceipt?: WorkContract['completionReceipt'];
|
|
79
|
+
evidenceRefs?: EvidenceRef[];
|
|
80
|
+
handoffRefs?: string[];
|
|
81
|
+
suggestedNextActions?: SuggestedNextAction[];
|
|
82
|
+
policyDecisions?: PolicyDecision[];
|
|
83
|
+
checkRefs?: VerificationRecord[];
|
|
84
|
+
reconciliations?: WorkReconciliationRecord[];
|
|
85
|
+
driver?: WorkContract['driver'];
|
|
86
|
+
worktreePolicy?: WorkContract['worktreePolicy'];
|
|
87
|
+
evidencePolicy?: WorkContract['evidencePolicy'];
|
|
88
|
+
approvalPolicy?: WorkContract['approvalPolicy'];
|
|
89
|
+
recoveryPolicy?: WorkContract['recoveryPolicy'];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export interface ListWorkContractOptions extends WorkContractStoreOptions {
|
|
93
|
+
status?: WorkContractStatus | 'active' | 'all';
|
|
94
|
+
limit?: number;
|
|
95
|
+
detailLevel?: 'summary' | 'detail' | 'raw';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface InvalidActiveWorkCandidate {
|
|
99
|
+
workId: string;
|
|
100
|
+
updatedAt: string;
|
|
101
|
+
checkoutId?: string;
|
|
102
|
+
requirementId?: string;
|
|
103
|
+
planId?: string;
|
|
104
|
+
planStepId?: string;
|
|
105
|
+
semanticScopeKeys: string[];
|
|
106
|
+
isolation: 'shared' | 'isolated';
|
|
107
|
+
error: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ActiveWorkCandidateSnapshot {
|
|
111
|
+
contracts: WorkContract[];
|
|
112
|
+
invalid: InvalidActiveWorkCandidate[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface WorkContractSummary {
|
|
116
|
+
workId: string;
|
|
117
|
+
repoId: string;
|
|
118
|
+
mode: WorkContract['mode'];
|
|
119
|
+
phase: WorkContract['phase'];
|
|
120
|
+
status: WorkContractStatus;
|
|
121
|
+
objective: string;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
handoffCount: number;
|
|
124
|
+
evidenceCount: number;
|
|
125
|
+
checkCount: number;
|
|
126
|
+
engineering?: {
|
|
127
|
+
profileVersion: string;
|
|
128
|
+
riskClass: string;
|
|
129
|
+
missingAdmissionEvidence: string[];
|
|
130
|
+
missingCompletionEvidence: string[];
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function nowIso(options: WorkContractStoreOptions): string {
|
|
135
|
+
return options.now?.() ?? new Date().toISOString();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function workContractRoot(location: WorkContractStoreLocation): string {
|
|
139
|
+
if (location.root) {
|
|
140
|
+
mkdirSync(location.root, { recursive: true });
|
|
141
|
+
return location.root;
|
|
142
|
+
}
|
|
143
|
+
if (!location.controllerHome || !location.repoId) {
|
|
144
|
+
throw new Error('work contract store requires either root or controllerHome + repoId');
|
|
145
|
+
}
|
|
146
|
+
const root = join(repositoryControllerRoot(location.controllerHome, location.repoId), 'work-contracts');
|
|
147
|
+
mkdirSync(root, { recursive: true });
|
|
148
|
+
return root;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function workContractStorePath(location: WorkContractStoreLocation): string {
|
|
152
|
+
return join(workContractRoot(location), 'index.json');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function emptyWorkContractStore(updatedAt: string): WorkContractStore {
|
|
156
|
+
return { schemaVersion: 3, updatedAt, contracts: [] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function initialLifecycleForNewWork(status: WorkContractStatus): Pick<WorkContract, 'phase' | 'dispatchState' | 'evidenceState'> {
|
|
160
|
+
if (status === 'completed') throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
161
|
+
if (status === 'running') return { phase: 'implementation', dispatchState: 'running', evidenceState: 'none' };
|
|
162
|
+
if (status === 'ready') return { phase: 'verification', dispatchState: 'not_dispatched', evidenceState: 'none' };
|
|
163
|
+
if (status === 'blocked') return { phase: 'implementation', dispatchState: 'blocked', evidenceState: 'none' };
|
|
164
|
+
if (status === 'failed') return { phase: 'implementation', dispatchState: 'terminal', evidenceState: 'failed' };
|
|
165
|
+
if (status === 'cancelled') return { phase: 'implementation', dispatchState: 'terminal', evidenceState: 'none' };
|
|
166
|
+
return { phase: 'implementation', dispatchState: 'not_dispatched', evidenceState: 'none' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function legacyInferredDispatchState(status: WorkContractStatus): DispatchState {
|
|
170
|
+
if (status === 'open' || status === 'ready') return 'not_dispatched';
|
|
171
|
+
if (status === 'running') return 'running';
|
|
172
|
+
if (status === 'blocked') return 'blocked';
|
|
173
|
+
return 'terminal';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function legacyInferredPhase(status: WorkContractStatus): WorkContract['phase'] {
|
|
177
|
+
if (status === 'ready') return 'verification';
|
|
178
|
+
if (status === 'completed' || status === 'failed' || status === 'cancelled') return 'cleanup';
|
|
179
|
+
return 'implementation';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function legacyInferredEvidenceState(status: WorkContractStatus): EvidenceState {
|
|
183
|
+
if (status === 'failed') return 'failed';
|
|
184
|
+
if (status === 'completed') return 'partial';
|
|
185
|
+
return 'none';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function initialPhaseEvidenceForNewWork(
|
|
189
|
+
input: Pick<WorkContract, 'phase' | 'status' | 'evidenceRefs' | 'updatedAt'>,
|
|
190
|
+
): WorkPhaseEvidenceMap {
|
|
191
|
+
const currentIndex = phaseIndex(input.phase);
|
|
192
|
+
return Object.fromEntries((['implementation', 'verification', 'review', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => {
|
|
193
|
+
const index = phaseIndex(phase);
|
|
194
|
+
const state: WorkPhaseEvidenceState = index < currentIndex
|
|
195
|
+
? 'satisfied'
|
|
196
|
+
: index > currentIndex
|
|
197
|
+
? 'pending'
|
|
198
|
+
: input.status === 'failed'
|
|
199
|
+
? 'failed'
|
|
200
|
+
: input.status === 'cancelled'
|
|
201
|
+
? 'skipped'
|
|
202
|
+
: input.status === 'blocked' || input.status === 'ready'
|
|
203
|
+
? 'blocked'
|
|
204
|
+
: 'active';
|
|
205
|
+
return [phase, {
|
|
206
|
+
state,
|
|
207
|
+
source: 'recorded' as const,
|
|
208
|
+
summary: index < currentIndex
|
|
209
|
+
? `Canonical Work was admitted after phase ${phase}.`
|
|
210
|
+
: index > currentIndex
|
|
211
|
+
? `Waiting for Work phase ${phase}.`
|
|
212
|
+
: `Canonical Work admitted in ${phase} with status ${input.status}.`,
|
|
213
|
+
evidenceRefs: index <= currentIndex ? input.evidenceRefs.slice(0, 20) : [],
|
|
214
|
+
recordedAt: input.updatedAt,
|
|
215
|
+
} satisfies WorkPhaseEvidence];
|
|
216
|
+
})) as WorkPhaseEvidenceMap;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function legacyPhaseEvidence(
|
|
220
|
+
contract: Pick<WorkContract, 'phase' | 'status' | 'evidenceRefs' | 'completionReceipt' | 'updatedAt'>,
|
|
221
|
+
source: WorkPhaseEvidence['source'] = 'legacy_inferred',
|
|
222
|
+
): WorkPhaseEvidenceMap {
|
|
223
|
+
const currentIndex = phaseIndex(contract.phase);
|
|
224
|
+
const completedByReceipt = Boolean(contract.completionReceipt);
|
|
225
|
+
return Object.fromEntries((['implementation', 'verification', 'review', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => {
|
|
226
|
+
const index = phaseIndex(phase);
|
|
227
|
+
let state: WorkPhaseEvidenceState = index < currentIndex ? 'satisfied' : index === currentIndex ? 'active' : 'pending';
|
|
228
|
+
if (completedByReceipt) state = phase === 'review' ? 'skipped' : 'satisfied';
|
|
229
|
+
else if (contract.status === 'failed' && phase === contract.phase) state = 'failed';
|
|
230
|
+
else if (contract.status === 'cancelled' && phase === contract.phase) state = 'skipped';
|
|
231
|
+
const receiptId = contract.completionReceipt && (phase === 'delivery' || phase === 'cleanup')
|
|
232
|
+
? contract.completionReceipt.receiptId
|
|
233
|
+
: undefined;
|
|
234
|
+
return [phase, {
|
|
235
|
+
state,
|
|
236
|
+
source: completedByReceipt ? 'recorded' : source,
|
|
237
|
+
summary: completedByReceipt
|
|
238
|
+
? phase === 'review'
|
|
239
|
+
? `Legacy completed Work predates first-class implementation review; review phase is compatibility-skipped without synthesizing approval.`
|
|
240
|
+
: `Phase ${phase} satisfied by Work completion receipt ${contract.completionReceipt!.receiptId}.`
|
|
241
|
+
: index < currentIndex
|
|
242
|
+
? `Legacy Work advanced beyond ${phase}.`
|
|
243
|
+
: `Legacy Work phase ${phase} is ${state}.`,
|
|
244
|
+
evidenceRefs: contract.evidenceRefs.slice(0, 20),
|
|
245
|
+
recordedAt: contract.updatedAt,
|
|
246
|
+
...(receiptId ? { receiptId } : {}),
|
|
247
|
+
} satisfies WorkPhaseEvidence];
|
|
248
|
+
})) as WorkPhaseEvidenceMap;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string; repoId: string } {
|
|
252
|
+
// A caller-provided root is a test/portable compatibility store. Runtime
|
|
253
|
+
// controller state always carries controllerHome + repoId and is SQLite.
|
|
254
|
+
return Boolean(!options.root && options.controllerHome?.trim() && options.repoId?.trim());
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function migrateLegacyWorkContract(legacy: WorkContract): WorkContract {
|
|
258
|
+
const mappedStatus = ({ pending: 'open', waiting_for_review: 'ready', succeeded: 'completed' } as Record<string, WorkContractStatus>)[String(legacy.status)] ?? legacy.status;
|
|
259
|
+
// Historical terminal labels without the Work-owned receipt are not
|
|
260
|
+
// completion authority. Reopen them at delivery so callers can obtain an
|
|
261
|
+
// exact receipt instead of projecting an unproven success.
|
|
262
|
+
const status = mappedStatus === 'completed' && !legacy.completionReceipt ? 'ready' : mappedStatus;
|
|
263
|
+
const phase = legacy.completionReceipt
|
|
264
|
+
? 'cleanup'
|
|
265
|
+
: mappedStatus === 'completed'
|
|
266
|
+
? 'delivery'
|
|
267
|
+
: legacy.phase ?? legacyInferredPhase(status);
|
|
268
|
+
const legacyDefaults = legacyPhaseEvidence({
|
|
269
|
+
phase,
|
|
270
|
+
status,
|
|
271
|
+
evidenceRefs: legacy.evidenceRefs ?? [],
|
|
272
|
+
completionReceipt: legacy.completionReceipt,
|
|
273
|
+
updatedAt: legacy.updatedAt,
|
|
274
|
+
});
|
|
275
|
+
const existingPhaseEvidence = legacy.phaseEvidence as Partial<WorkPhaseEvidenceMap> | undefined;
|
|
276
|
+
const existingReview = existingPhaseEvidence?.review;
|
|
277
|
+
const legacyReviewAdvancedPastCheckpoint = Boolean(
|
|
278
|
+
existingReview
|
|
279
|
+
&& existingReview.source === 'legacy_inferred'
|
|
280
|
+
&& existingReview.state === 'pending'
|
|
281
|
+
&& phaseIndex(phase) > phaseIndex('review'),
|
|
282
|
+
);
|
|
283
|
+
const phaseEvidence: WorkPhaseEvidenceMap = existingPhaseEvidence
|
|
284
|
+
? {
|
|
285
|
+
...legacyDefaults,
|
|
286
|
+
...existingPhaseEvidence,
|
|
287
|
+
review: existingReview
|
|
288
|
+
? legacyReviewAdvancedPastCheckpoint
|
|
289
|
+
? {
|
|
290
|
+
...existingReview,
|
|
291
|
+
state: 'skipped' as const,
|
|
292
|
+
summary: 'Legacy Work advanced beyond first-class implementation review before that checkpoint existed; compatibility-skipped without synthesizing Controller approval.',
|
|
293
|
+
}
|
|
294
|
+
: existingReview
|
|
295
|
+
: {
|
|
296
|
+
...legacyDefaults.review,
|
|
297
|
+
...(phaseIndex(phase) > phaseIndex('review')
|
|
298
|
+
? {
|
|
299
|
+
state: 'skipped' as const,
|
|
300
|
+
summary: 'Legacy Work advanced beyond first-class implementation review; compatibility-skipped without synthesizing Controller approval.',
|
|
301
|
+
}
|
|
302
|
+
: {}),
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
: legacyDefaults;
|
|
306
|
+
return validateWorkSemantics({
|
|
307
|
+
...legacy,
|
|
308
|
+
schemaVersion: 3,
|
|
309
|
+
scopeRef: semanticScopeRefForWork(legacy),
|
|
310
|
+
executionPlacement: executionPlacementForWork(legacy),
|
|
311
|
+
status,
|
|
312
|
+
phase,
|
|
313
|
+
phaseEvidence,
|
|
314
|
+
risk: legacy.risk ?? 'medium',
|
|
315
|
+
workKind: legacy.workKind ?? 'repository_change',
|
|
316
|
+
dispatchState: legacy.dispatchState ?? legacyInferredDispatchState(status),
|
|
317
|
+
evidenceState: legacy.evidenceState ?? legacyInferredEvidenceState(status),
|
|
318
|
+
suggestedNextActions: suggestedActionsForStatus(status, legacy.suggestedNextActions ?? []),
|
|
319
|
+
implementationReviews: legacy.implementationReviews ?? [],
|
|
320
|
+
reconciliations: legacy.reconciliations ?? [],
|
|
321
|
+
driver: (legacy.driver as unknown as { preferred?: string } | undefined)?.preferred === 'codex_worker'
|
|
322
|
+
? { ...legacy.driver, preferred: 'external_controller', allowWorker: false }
|
|
323
|
+
: legacy.driver,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function validateCanonicalWorkContract(contract: WorkContract): WorkContract {
|
|
328
|
+
if (contract.schemaVersion !== 3) {
|
|
329
|
+
throw new Error(`WORK_CONTRACT_SCHEMA_MIGRATION_REQUIRED: ${contract.workId}:schema=${String(contract.schemaVersion)}`);
|
|
330
|
+
}
|
|
331
|
+
return validateWorkSemantics(contract);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function storedWorkContractNeedsMigration(contract: WorkContract): boolean {
|
|
335
|
+
const phaseEvidence = contract.phaseEvidence as Partial<WorkPhaseEvidenceMap> | undefined;
|
|
336
|
+
// Some pre-review-checkpoint rows were stamped with the current outer
|
|
337
|
+
// schema while omitting only `review`. That exact historical shape remains
|
|
338
|
+
// migration-compatible, but it must be persisted once rather than inferred
|
|
339
|
+
// repeatedly by the normal read path.
|
|
340
|
+
const legacyReviewGap = contract.schemaVersion === 3
|
|
341
|
+
&& phaseEvidence
|
|
342
|
+
&& !phaseEvidence.review
|
|
343
|
+
&& phaseEvidence.implementation
|
|
344
|
+
&& phaseEvidence.verification
|
|
345
|
+
&& phaseEvidence.delivery
|
|
346
|
+
&& phaseEvidence.cleanup;
|
|
347
|
+
return contract.schemaVersion !== 3 || Boolean(legacyReviewGap);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function canonicalizeStoredWorkContract(contract: WorkContract): WorkContract {
|
|
351
|
+
return storedWorkContractNeedsMigration(contract)
|
|
352
|
+
? migrateLegacyWorkContract(contract)
|
|
353
|
+
: validateCanonicalWorkContract(contract);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Normalize one row at a transaction boundary without reading or mutating a
|
|
358
|
+
* second Work authority. Higher-level control-plane transactions use this to
|
|
359
|
+
* validate legacy rows against the same canonical Work semantics as reads.
|
|
360
|
+
*/
|
|
361
|
+
export function canonicalizeWorkContractForAuthority(contract: WorkContract): WorkContract {
|
|
362
|
+
return canonicalizeStoredWorkContract(contract);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function normalizeWorkContractStore(store: WorkContractStore): WorkContractStore {
|
|
366
|
+
// v1/v2 compatibility is a one-way cutover. v3 records are validated as-is;
|
|
367
|
+
// no current lifecycle field is inferred from status during normal reads.
|
|
368
|
+
return { schemaVersion: 3, updatedAt: store.updatedAt, contracts: store.contracts.map(canonicalizeStoredWorkContract) };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function readWorkContractStore(options: WorkContractStoreOptions): WorkContractStore {
|
|
372
|
+
if (!sqliteBacked(options)) {
|
|
373
|
+
const raw = readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
|
|
374
|
+
const normalized = normalizeWorkContractStore(raw);
|
|
375
|
+
if (raw.schemaVersion !== 3 || raw.contracts.some(storedWorkContractNeedsMigration)) {
|
|
376
|
+
writeJsonAtomic(workContractStorePath(options), normalized);
|
|
377
|
+
}
|
|
378
|
+
return normalized;
|
|
379
|
+
}
|
|
380
|
+
const records = listControlPlaneRecords<WorkContract>(options.controllerHome, {
|
|
381
|
+
namespace: 'work_contract',
|
|
382
|
+
scope: options.repoId,
|
|
383
|
+
limit: 5_000,
|
|
384
|
+
});
|
|
385
|
+
if (records.length > 0) {
|
|
386
|
+
const normalized = normalizeWorkContractStore({
|
|
387
|
+
schemaVersion: 3,
|
|
388
|
+
updatedAt: records[0]?.updatedAt ?? nowIso(options),
|
|
389
|
+
contracts: records.map((record) => record.value),
|
|
390
|
+
});
|
|
391
|
+
const legacyRows = records
|
|
392
|
+
.map((record, index) => ({ record, contract: normalized.contracts[index]! }))
|
|
393
|
+
.filter(({ record }) => storedWorkContractNeedsMigration(record.value));
|
|
394
|
+
if (legacyRows.length > 0) {
|
|
395
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
396
|
+
for (const { record, contract } of legacyRows) {
|
|
397
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
398
|
+
namespace: 'work_contract',
|
|
399
|
+
scope: options.repoId,
|
|
400
|
+
key: contract.workId,
|
|
401
|
+
schemaVersion: 3,
|
|
402
|
+
value: contract,
|
|
403
|
+
action: 'work_contract_schema_v3_migrated',
|
|
404
|
+
expectedRevision: record.revision,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return normalized;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// One-time import only. Once a per-Work row exists, legacy index/file data is
|
|
413
|
+
// never consulted again and is never written back.
|
|
414
|
+
const legacyRecord = readControlPlaneRecord<WorkContractStore>(
|
|
415
|
+
options.controllerHome,
|
|
416
|
+
'work_contract_store',
|
|
417
|
+
options.repoId,
|
|
418
|
+
'index',
|
|
419
|
+
);
|
|
420
|
+
const legacy = legacyRecord?.value
|
|
421
|
+
?? readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
|
|
422
|
+
const normalized = normalizeWorkContractStore(legacy);
|
|
423
|
+
if (normalized.contracts.length > 0) {
|
|
424
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
425
|
+
for (const contract of normalized.contracts) {
|
|
426
|
+
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options.repoId, contract.workId)) continue;
|
|
427
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
428
|
+
namespace: 'work_contract',
|
|
429
|
+
scope: options.repoId,
|
|
430
|
+
key: contract.workId,
|
|
431
|
+
schemaVersion: 3,
|
|
432
|
+
value: contract,
|
|
433
|
+
action: 'work_contract_legacy_import',
|
|
434
|
+
expectedRevision: null,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return normalized;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function writeWorkContractStore(options: WorkContractStoreOptions, store: WorkContractStore): WorkContractStore {
|
|
443
|
+
if (!sqliteBacked(options)) {
|
|
444
|
+
writeJsonAtomic(workContractStorePath(options), store);
|
|
445
|
+
return store;
|
|
446
|
+
}
|
|
447
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
448
|
+
for (const contract of store.contracts) {
|
|
449
|
+
const value = validateCanonicalWorkContract(contract);
|
|
450
|
+
const current = readControlPlaneRecordWithinTransaction<WorkContract>(
|
|
451
|
+
database,
|
|
452
|
+
'work_contract',
|
|
453
|
+
options.repoId,
|
|
454
|
+
contract.workId,
|
|
455
|
+
);
|
|
456
|
+
// SQLite is authoritative per Work row. Aggregate-store callers may still
|
|
457
|
+
// construct a complete compatibility snapshot, but unchanged siblings are
|
|
458
|
+
// not authoritative mutations and must not advance revision/audit history.
|
|
459
|
+
if (current && JSON.stringify(current.value) === JSON.stringify(value)) continue;
|
|
460
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
461
|
+
namespace: 'work_contract',
|
|
462
|
+
scope: options.repoId,
|
|
463
|
+
key: contract.workId,
|
|
464
|
+
schemaVersion: 3,
|
|
465
|
+
value,
|
|
466
|
+
action: 'work_contract_write',
|
|
467
|
+
expectedRevision: current?.revision ?? null,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
return store;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function withWorkContractStoreWrite<T>(options: WorkContractStoreOptions, operation: () => T): T {
|
|
475
|
+
if (!sqliteBacked(options)) return operation();
|
|
476
|
+
return withControllerLock(
|
|
477
|
+
options.controllerHome,
|
|
478
|
+
{ scope: 'global', resource: `work-contract-store-${sanitizeFileComponent(options.repoId)}` },
|
|
479
|
+
`work-contract-store:${options.repoId}`,
|
|
480
|
+
operation,
|
|
481
|
+
undefined,
|
|
482
|
+
5_000,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
function assertCanonicalWorkAdmissionAllowed(
|
|
488
|
+
options: WorkContractStoreOptions,
|
|
489
|
+
input: { operation: 'create' | 'continue' | 'maintenance'; workId?: string },
|
|
490
|
+
): WorkAdmissionPolicy {
|
|
491
|
+
if (!options.controllerHome) return normalWorkAdmissionPolicy(nowIso(options));
|
|
492
|
+
const policy = readControlPlaneRecord<WorkAdmissionPolicy>(
|
|
493
|
+
options.controllerHome,
|
|
494
|
+
WORK_ADMISSION_POLICY_NAMESPACE,
|
|
495
|
+
WORK_ADMISSION_POLICY_SCOPE,
|
|
496
|
+
WORK_ADMISSION_POLICY_KEY,
|
|
497
|
+
)?.value ?? normalWorkAdmissionPolicy(nowIso(options));
|
|
498
|
+
return assertWorkAdmissionPolicyAllows(policy, input);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function defaultDriver(mode: WorkContract['mode']): WorkContract['driver'] {
|
|
502
|
+
if (mode === 'direct_control') {
|
|
503
|
+
return { preferred: 'direct_edit', allowWorker: false, allowDirectEdit: true };
|
|
504
|
+
}
|
|
505
|
+
if (mode === 'handoff_only') {
|
|
506
|
+
return { preferred: 'handoff_only', allowWorker: false, allowDirectEdit: false };
|
|
507
|
+
}
|
|
508
|
+
return { preferred: 'direct_edit', allowWorker: false, allowDirectEdit: true };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function createWorkContract(options: WorkContractStoreOptions, input: CreateWorkContractInput): WorkContract {
|
|
512
|
+
if (options.controllerHome) {
|
|
513
|
+
assertCanonicalWorkAdmissionAllowed(options, { operation: 'create', workId: input.workId });
|
|
514
|
+
}
|
|
515
|
+
if (input.status === 'completed' || input.completionReceipt || input.completionOutcome) {
|
|
516
|
+
throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
517
|
+
}
|
|
518
|
+
const create = (): WorkContract => {
|
|
519
|
+
const at = input.createdAt ?? input.updatedAt ?? nowIso(options);
|
|
520
|
+
const workId = sanitizeFileComponent(input.workId);
|
|
521
|
+
const predecessorWorkId = input.predecessorWorkId ? sanitizeFileComponent(input.predecessorWorkId) : undefined;
|
|
522
|
+
if (predecessorWorkId === workId) throw new Error('WORK_PREDECESSOR_SELF_REFERENCE');
|
|
523
|
+
const contract: WorkContract = validateWorkSemantics({
|
|
524
|
+
schemaVersion: 3,
|
|
525
|
+
workId,
|
|
526
|
+
scopeRef: semanticScopeRefForWork({
|
|
527
|
+
workId,
|
|
528
|
+
scopeRef: input.scopeRef,
|
|
529
|
+
requirementId: input.requirementId,
|
|
530
|
+
planId: input.planId,
|
|
531
|
+
planStepId: input.planStepId,
|
|
532
|
+
}),
|
|
533
|
+
executionPlacement: input.executionPlacement ?? executionPlacementForWork({ repoId: input.repoId, checkoutId: input.checkoutId }),
|
|
534
|
+
repoId: input.repoId,
|
|
535
|
+
checkoutId: input.checkoutId,
|
|
536
|
+
principalId: input.principalId,
|
|
537
|
+
controllerInstanceId: input.controllerInstanceId,
|
|
538
|
+
baseRevision: input.baseRevision,
|
|
539
|
+
repositoryBaseState: input.repositoryBaseState,
|
|
540
|
+
workspaceFingerprint: input.workspaceFingerprint,
|
|
541
|
+
routeDecisionFingerprint: input.routeDecisionFingerprint,
|
|
542
|
+
routeDecision: input.routeDecision,
|
|
543
|
+
mode: input.mode,
|
|
544
|
+
objective: input.objective.slice(0, 2_000),
|
|
545
|
+
acceptanceCriteria: (input.acceptanceCriteria ?? []).slice(0, 20).map((item) => item.slice(0, 500)),
|
|
546
|
+
constraints: input.constraints ?? { requireHandoffOnAmbiguity: true },
|
|
547
|
+
risk: input.risk ?? 'medium',
|
|
548
|
+
engineeringContext: input.engineeringContext,
|
|
549
|
+
workKind: input.workKind ?? 'repository_change',
|
|
550
|
+
lifecycleRole: input.lifecycleRole ?? 'primary',
|
|
551
|
+
parentWorkId: input.parentWorkId?.trim() || undefined,
|
|
552
|
+
predecessorWorkId: predecessorWorkId && predecessorWorkId !== 'unknown' ? predecessorWorkId : undefined,
|
|
553
|
+
supersedes: input.supersedes?.map((value) => sanitizeFileComponent(value)).filter((value) => value !== 'unknown').slice(0, 50),
|
|
554
|
+
supersededBy: input.supersededBy ? sanitizeFileComponent(input.supersededBy) : undefined,
|
|
555
|
+
supersessionReason: input.supersessionReason?.trim().slice(0, 500),
|
|
556
|
+
dispatchState: input.dispatchState ?? initialLifecycleForNewWork(input.status ?? 'open').dispatchState,
|
|
557
|
+
evidenceState: input.evidenceState ?? initialLifecycleForNewWork(input.status ?? 'open').evidenceState,
|
|
558
|
+
completionOutcome: input.completionOutcome,
|
|
559
|
+
phase: input.phase ?? initialLifecycleForNewWork(input.status ?? 'open').phase,
|
|
560
|
+
phaseEvidence: initialPhaseEvidenceForNewWork({
|
|
561
|
+
phase: input.phase ?? initialLifecycleForNewWork(input.status ?? 'open').phase,
|
|
562
|
+
status: input.status ?? 'open',
|
|
563
|
+
evidenceRefs: input.evidenceRefs ?? [],
|
|
564
|
+
updatedAt: input.updatedAt ?? at,
|
|
565
|
+
}),
|
|
566
|
+
completionReceipt: input.completionReceipt,
|
|
567
|
+
status: input.status ?? 'open',
|
|
568
|
+
createdAt: at,
|
|
569
|
+
updatedAt: input.updatedAt ?? at,
|
|
570
|
+
issueId: input.issueId,
|
|
571
|
+
taskId: input.taskId,
|
|
572
|
+
requirementId: input.requirementId,
|
|
573
|
+
planId: input.planId,
|
|
574
|
+
planStepId: input.planStepId,
|
|
575
|
+
planSourceRevision: input.planSourceRevision,
|
|
576
|
+
scopeSummary: input.scopeSummary?.slice(0, 1_000),
|
|
577
|
+
scopeEvidence: input.scopeEvidence ? {
|
|
578
|
+
initialLikelyPaths: [...new Set(input.scopeEvidence.initialLikelyPaths)].slice(0, 100),
|
|
579
|
+
inspectedPaths: [...new Set(input.scopeEvidence.inspectedPaths)].slice(0, 500),
|
|
580
|
+
actualChangedPaths: [...new Set(input.scopeEvidence.actualChangedPaths)].slice(0, 500),
|
|
581
|
+
recordedAt: input.scopeEvidence.recordedAt,
|
|
582
|
+
} : undefined,
|
|
583
|
+
allowedPaths: (input.allowedPaths ?? []).slice(0, 50),
|
|
584
|
+
forbiddenPaths: (input.forbiddenPaths ?? []).slice(0, 50),
|
|
585
|
+
checks: (input.checks ?? []).slice(0, 30),
|
|
586
|
+
driver: input.driver ?? defaultDriver(input.mode),
|
|
587
|
+
worktreePolicy: input.worktreePolicy ?? {
|
|
588
|
+
required: input.mode === 'goal_workloop',
|
|
589
|
+
reason: input.mode === 'goal_workloop' ? 'Goal workloop defaults to isolated worktree execution.' : undefined,
|
|
590
|
+
},
|
|
591
|
+
evidencePolicy: input.evidencePolicy ?? {
|
|
592
|
+
defaultDetailLevel: 'summary',
|
|
593
|
+
allowRawOptIn: true,
|
|
594
|
+
maxEvidenceRefs: 20,
|
|
595
|
+
},
|
|
596
|
+
approvalPolicy: input.approvalPolicy ?? { required: false, reasons: [], confirmed: false },
|
|
597
|
+
recoveryPolicy: input.recoveryPolicy ?? {
|
|
598
|
+
allowSelfHealing: false,
|
|
599
|
+
maxInfrastructureRetries: 0,
|
|
600
|
+
handoffOnAmbiguity: true,
|
|
601
|
+
},
|
|
602
|
+
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
603
|
+
evidenceRefs: (input.evidenceRefs ?? []).slice(0, 20),
|
|
604
|
+
handoffRefs: (input.handoffRefs ?? []).slice(0, 20),
|
|
605
|
+
suggestedNextActions: (input.suggestedNextActions ?? []).slice(0, 8),
|
|
606
|
+
policyDecisions: (input.policyDecisions ?? []).slice(0, 20),
|
|
607
|
+
checkRefs: (input.checkRefs ?? []).slice(0, 50),
|
|
608
|
+
implementationReviews: [],
|
|
609
|
+
reconciliations: (input.reconciliations ?? []).slice(0, 20),
|
|
610
|
+
continuationPrompt: input.continuationPrompt?.slice(0, 2_000),
|
|
611
|
+
worktreeRef: input.worktreeRef,
|
|
612
|
+
workerRef: input.workerRef,
|
|
613
|
+
requestId: input.requestId?.trim() || undefined,
|
|
614
|
+
submittedOperation: input.submittedOperation,
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
if (sqliteBacked(options)) {
|
|
618
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
619
|
+
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options.repoId, contract.workId)) {
|
|
620
|
+
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
621
|
+
}
|
|
622
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
623
|
+
namespace: 'work_contract',
|
|
624
|
+
scope: options.repoId,
|
|
625
|
+
key: contract.workId,
|
|
626
|
+
schemaVersion: 3,
|
|
627
|
+
value: contract,
|
|
628
|
+
action: 'work_contract_created',
|
|
629
|
+
expectedRevision: null,
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
return contract;
|
|
633
|
+
}
|
|
634
|
+
const store = readWorkContractStore(options);
|
|
635
|
+
if (store.contracts.some((existing) => existing.workId === contract.workId)) {
|
|
636
|
+
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
637
|
+
}
|
|
638
|
+
const nextStore: WorkContractStore = {
|
|
639
|
+
schemaVersion: 3,
|
|
640
|
+
updatedAt: contract.updatedAt,
|
|
641
|
+
contracts: [contract, ...store.contracts],
|
|
642
|
+
};
|
|
643
|
+
writeWorkContractStore(options, nextStore);
|
|
644
|
+
return contract;
|
|
645
|
+
};
|
|
646
|
+
return create();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
interface WorkRequestIndexRecord {
|
|
650
|
+
requestId: string;
|
|
651
|
+
repoId: string;
|
|
652
|
+
workId: string;
|
|
653
|
+
semanticKey: string;
|
|
654
|
+
createdAt: string;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function workRequestIndexPath(controllerHome: string, requestId: string): string {
|
|
658
|
+
const hash = createHash('sha256').update(requestId).digest('hex');
|
|
659
|
+
const root = join(ensureControllerHome(controllerHome), 'indexes', 'work-contracts', 'requests');
|
|
660
|
+
mkdirSync(root, { recursive: true });
|
|
661
|
+
return join(root, `${hash}.json`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function getWorkContractByRequestId(
|
|
665
|
+
controllerHome: string,
|
|
666
|
+
requestId: string,
|
|
667
|
+
expectedRepoId?: string,
|
|
668
|
+
): WorkContract | undefined {
|
|
669
|
+
const normalizedRequestId = requestId.trim();
|
|
670
|
+
if (!normalizedRequestId) return undefined;
|
|
671
|
+
const recordPath = workRequestIndexPath(controllerHome, normalizedRequestId);
|
|
672
|
+
if (!existsSync(recordPath)) return undefined;
|
|
673
|
+
try {
|
|
674
|
+
const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
|
|
675
|
+
if (record.requestId !== normalizedRequestId) return undefined;
|
|
676
|
+
if (expectedRepoId && record.repoId !== expectedRepoId) return undefined;
|
|
677
|
+
return getWorkContract({ controllerHome, repoId: record.repoId }, record.workId);
|
|
678
|
+
} catch {
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Create or reuse the execution-child Work used by typed callers. This is not
|
|
685
|
+
* a generic MCP submission authority and never creates an ExecutionJob.
|
|
686
|
+
*/
|
|
687
|
+
export function acceptSubmittedWorkContract(
|
|
688
|
+
controllerHome: string,
|
|
689
|
+
input: AcceptSubmittedWorkInput,
|
|
690
|
+
options: WorkContractStoreOptions = {},
|
|
691
|
+
): { contract: WorkContract; deduplicated: boolean } {
|
|
692
|
+
const home = ensureControllerHome(controllerHome);
|
|
693
|
+
const requestId = input.requestId.trim();
|
|
694
|
+
const semanticKey = input.semanticKey.trim();
|
|
695
|
+
if (!requestId) throw new Error('INVALID_ARGUMENT: typed execution is missing request_id');
|
|
696
|
+
if (!semanticKey) throw new Error('INVALID_ARGUMENT: typed execution requires a semantic operation key');
|
|
697
|
+
if (!input.repoId.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing repo_id');
|
|
698
|
+
if (!input.operation?.name?.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing operation');
|
|
699
|
+
const parentWorkId = input.parentWorkId?.trim() || undefined;
|
|
700
|
+
if (parentWorkId) {
|
|
701
|
+
const parent = getWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, parentWorkId);
|
|
702
|
+
if (!parent) throw new Error(`PARENT_WORK_NOT_FOUND: ${parentWorkId}`);
|
|
703
|
+
if (isTerminalWorkContractStatus(parent.status)) throw new Error(`PARENT_WORK_TERMINAL: ${parentWorkId}:${parent.status}`);
|
|
704
|
+
if ((parent.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`PARENT_WORK_NOT_PRIMARY: ${parentWorkId}`);
|
|
705
|
+
}
|
|
706
|
+
const lockId = createHash('sha256').update(requestId).digest('hex').slice(0, 24);
|
|
707
|
+
return withControllerLock(home, { scope: 'global', resource: `work-request-${lockId}` }, `accept-work:${requestId}`, () => {
|
|
708
|
+
const recordPath = workRequestIndexPath(home, requestId);
|
|
709
|
+
if (existsSync(recordPath)) {
|
|
710
|
+
const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
|
|
711
|
+
if (record.repoId !== input.repoId) throw new Error(`REQUEST_ID_REPO_CONFLICT: ${requestId} already belongs to repository ${record.repoId}`);
|
|
712
|
+
if (record.semanticKey !== semanticKey) throw new Error(`REQUEST_ID_CONFLICT: ${requestId} already belongs to ${record.semanticKey}`);
|
|
713
|
+
const existing = getWorkContract({ controllerHome: home, repoId: record.repoId, now: options.now }, record.workId);
|
|
714
|
+
if (!existing) throw new Error(`WORK_ACCEPTANCE_LOST: request ${requestId} is indexed without a readable WorkContract`);
|
|
715
|
+
return { contract: existing, deduplicated: true };
|
|
716
|
+
}
|
|
717
|
+
const contract = createWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, {
|
|
718
|
+
workId: `WORK-${Date.now()}-${randomUUID().slice(0, 8)}`,
|
|
719
|
+
repoId: input.repoId,
|
|
720
|
+
principalId: input.principalId,
|
|
721
|
+
controllerInstanceId: input.controllerInstanceId,
|
|
722
|
+
mode: input.mode ?? 'direct_control',
|
|
723
|
+
lifecycleRole: 'execution_child',
|
|
724
|
+
parentWorkId,
|
|
725
|
+
objective: (input.objective ?? `Typed operation ${input.operation.name}`).slice(0, 2_000),
|
|
726
|
+
acceptanceCriteria: input.acceptanceCriteria ?? [],
|
|
727
|
+
workKind: input.workKind,
|
|
728
|
+
risk: input.risk,
|
|
729
|
+
constraints: input.constraints ?? { requireHandoffOnAmbiguity: true },
|
|
730
|
+
allowedPaths: input.allowedPaths ?? [],
|
|
731
|
+
forbiddenPaths: input.forbiddenPaths ?? [],
|
|
732
|
+
checks: input.checks ?? [],
|
|
733
|
+
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
734
|
+
status: 'open',
|
|
735
|
+
requestId,
|
|
736
|
+
submittedOperation: input.operation,
|
|
737
|
+
driver: { preferred: 'external_controller', allowWorker: false, allowDirectEdit: input.operation.mode === 'readonly' || input.operation.mode === 'mutating' },
|
|
738
|
+
suggestedNextActions: [{
|
|
739
|
+
label: 'Claim controller ownership',
|
|
740
|
+
tool: 'rh_work',
|
|
741
|
+
operation: 'controller_claim',
|
|
742
|
+
risk: 'readonly',
|
|
743
|
+
confidence: 'high',
|
|
744
|
+
reason: 'Claim this Work before launching an external SuperController or Process Runtime command.',
|
|
745
|
+
}],
|
|
746
|
+
});
|
|
747
|
+
writeJsonAtomic(recordPath, { requestId, repoId: input.repoId, workId: contract.workId, semanticKey, createdAt: contract.createdAt } satisfies WorkRequestIndexRecord);
|
|
748
|
+
return { contract, deduplicated: false };
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function isCurrentWorkContract(contract: WorkContract): boolean {
|
|
753
|
+
return !isTerminalWorkContractStatus(contract.status)
|
|
754
|
+
&& !contract.supersededBy?.trim()
|
|
755
|
+
&& contract.workKind !== 'superseded'
|
|
756
|
+
&& contract.completionOutcome !== 'superseded';
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export function listWorkContracts(options: ListWorkContractOptions): WorkContract[] {
|
|
760
|
+
const store = readWorkContractStore(options);
|
|
761
|
+
const status = options.status ?? 'active';
|
|
762
|
+
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 50), 100));
|
|
763
|
+
return store.contracts
|
|
764
|
+
.filter((contract) => {
|
|
765
|
+
if (status === 'all') return true;
|
|
766
|
+
if (status === 'active') return isCurrentWorkContract(contract);
|
|
767
|
+
return contract.status === status;
|
|
768
|
+
})
|
|
769
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
770
|
+
.slice(0, limit);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function workExecutionSemanticScopeKeys(contract: WorkContract): string[] {
|
|
774
|
+
const declared = contract.engineeringContext?.semanticScope?.keys
|
|
775
|
+
?.map((value) => value.trim())
|
|
776
|
+
.filter(Boolean) ?? [];
|
|
777
|
+
if (declared.length > 0) return [...new Set(declared)].sort();
|
|
778
|
+
if (contract.planId?.trim() && contract.planStepId?.trim()) {
|
|
779
|
+
return [`plan-step:${contract.planId.trim()}:${contract.planStepId.trim()}`];
|
|
780
|
+
}
|
|
781
|
+
return [`work:${contract.workId}`];
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function workExecutionIsolation(contract: WorkContract): 'shared' | 'isolated' {
|
|
785
|
+
return contract.worktreePolicy?.required === true
|
|
786
|
+
|| contract.constraints?.workspaceMode === 'isolated'
|
|
787
|
+
|| contract.constraints?.requireWorktree === true
|
|
788
|
+
? 'isolated'
|
|
789
|
+
: 'shared';
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function rawWorkMayBeCurrent(contract: WorkContract): boolean {
|
|
793
|
+
return !isTerminalWorkContractStatus(contract.status)
|
|
794
|
+
&& !contract.supersededBy?.trim()
|
|
795
|
+
&& contract.workKind !== 'superseded'
|
|
796
|
+
&& contract.completionOutcome !== 'superseded';
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Row-isolated active Work admission projection. Canonical aggregate Work reads
|
|
801
|
+
* remain strict. Each SQLite row is normalized independently so one malformed
|
|
802
|
+
* legacy sibling cannot erase otherwise valid Work authority from control-plane
|
|
803
|
+
* admission. Invalid active rows stay explicit and conservative by identity,
|
|
804
|
+
* lineage, scope and isolation; they are never mutated or silently accepted.
|
|
805
|
+
*/
|
|
806
|
+
export function initializeWorkCandidateIndex(controllerHome: string): void {
|
|
807
|
+
initializeControlPlanePayloadTextExclusionIndex(controllerHome, {
|
|
808
|
+
namespace: 'work_contract', field: 'status', excludedValues: TERMINAL_WORK_CONTRACT_STATUSES,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export function readActiveWorkCandidates(
|
|
813
|
+
options: WorkContractStoreOptions & { limit?: number },
|
|
814
|
+
): ActiveWorkCandidateSnapshot {
|
|
815
|
+
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 1_000), 1_000));
|
|
816
|
+
if (!sqliteBacked(options)) {
|
|
817
|
+
return { contracts: listWorkContracts({ ...options, status: 'active', limit }), invalid: [] };
|
|
818
|
+
}
|
|
819
|
+
const records = listControlPlaneRecordsExcludingPayloadTextValues<WorkContract>(options.controllerHome, {
|
|
820
|
+
namespace: 'work_contract',
|
|
821
|
+
scope: options.repoId,
|
|
822
|
+
field: 'status',
|
|
823
|
+
excludedValues: TERMINAL_WORK_CONTRACT_STATUSES,
|
|
824
|
+
limit: 5_000,
|
|
825
|
+
});
|
|
826
|
+
const contracts: WorkContract[] = [];
|
|
827
|
+
const invalid: InvalidActiveWorkCandidate[] = [];
|
|
828
|
+
const migrations: Array<{ record: (typeof records)[number]; contract: WorkContract }> = [];
|
|
829
|
+
for (const record of records) {
|
|
830
|
+
const raw = record.value;
|
|
831
|
+
if (!rawWorkMayBeCurrent(raw)) continue;
|
|
832
|
+
try {
|
|
833
|
+
const normalized = canonicalizeStoredWorkContract(raw);
|
|
834
|
+
if (storedWorkContractNeedsMigration(raw)) migrations.push({ record, contract: normalized });
|
|
835
|
+
if (isCurrentWorkContract(normalized)) contracts.push(normalized);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
invalid.push({
|
|
838
|
+
workId: raw.workId,
|
|
839
|
+
updatedAt: raw.updatedAt,
|
|
840
|
+
...(raw.checkoutId?.trim() ? { checkoutId: raw.checkoutId.trim() } : {}),
|
|
841
|
+
...(raw.requirementId?.trim() ? { requirementId: raw.requirementId.trim() } : {}),
|
|
842
|
+
...(raw.planId?.trim() ? { planId: raw.planId.trim() } : {}),
|
|
843
|
+
...(raw.planStepId?.trim() ? { planStepId: raw.planStepId.trim() } : {}),
|
|
844
|
+
semanticScopeKeys: workExecutionSemanticScopeKeys(raw),
|
|
845
|
+
isolation: workExecutionIsolation(raw),
|
|
846
|
+
error: error instanceof Error ? error.message : String(error),
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (migrations.length > 0) {
|
|
851
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
852
|
+
for (const { record, contract } of migrations) {
|
|
853
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
854
|
+
namespace: 'work_contract',
|
|
855
|
+
scope: options.repoId,
|
|
856
|
+
key: contract.workId,
|
|
857
|
+
schemaVersion: 3,
|
|
858
|
+
value: contract,
|
|
859
|
+
action: 'work_contract_schema_v3_migrated',
|
|
860
|
+
expectedRevision: record.revision,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
contracts.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
866
|
+
invalid.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
867
|
+
return { contracts: contracts.slice(0, limit), invalid };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export function getWorkContract(options: WorkContractStoreOptions, workId: string): WorkContract | undefined {
|
|
871
|
+
const sanitizedId = sanitizeFileComponent(workId);
|
|
872
|
+
if (!sqliteBacked(options)) {
|
|
873
|
+
return readWorkContractStore(options).contracts.find((contract) => contract.workId === sanitizedId);
|
|
874
|
+
}
|
|
875
|
+
const exact = readControlPlaneRecord<WorkContract>(options.controllerHome, 'work_contract', options.repoId, sanitizedId);
|
|
876
|
+
if (exact) {
|
|
877
|
+
const canonical = canonicalizeStoredWorkContract(exact.value);
|
|
878
|
+
if (storedWorkContractNeedsMigration(exact.value)) {
|
|
879
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
880
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
881
|
+
namespace: 'work_contract',
|
|
882
|
+
scope: options.repoId,
|
|
883
|
+
key: canonical.workId,
|
|
884
|
+
schemaVersion: 3,
|
|
885
|
+
value: canonical,
|
|
886
|
+
action: 'work_contract_schema_v3_migrated',
|
|
887
|
+
expectedRevision: exact.revision,
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
return canonical;
|
|
892
|
+
}
|
|
893
|
+
// Preserve the one-time legacy import path only while this repository has no
|
|
894
|
+
// per-Work rows. Once any per-Work row exists, absence of this exact key is
|
|
895
|
+
// authoritative and unrelated rows must never be normalized for an exact get.
|
|
896
|
+
const hasPerWorkRows = listControlPlaneRecords<WorkContract>(options.controllerHome, {
|
|
897
|
+
namespace: 'work_contract',
|
|
898
|
+
scope: options.repoId,
|
|
899
|
+
limit: 1,
|
|
900
|
+
}).length > 0;
|
|
901
|
+
if (hasPerWorkRows) return undefined;
|
|
902
|
+
return readWorkContractStore(options).contracts.find((contract) => contract.workId === sanitizedId);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export interface SupersedeWorkContractInput {
|
|
906
|
+
workId: string;
|
|
907
|
+
supersededBy: string;
|
|
908
|
+
reason: string;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function workSupersessionWouldCycle(contracts: WorkContract[], predecessorId: string, successorId: string): boolean {
|
|
912
|
+
const byId = new Map(contracts.map((contract) => [contract.workId, contract]));
|
|
913
|
+
const visited = new Set<string>();
|
|
914
|
+
let cursorId: string | undefined = successorId;
|
|
915
|
+
while (cursorId) {
|
|
916
|
+
if (cursorId === predecessorId) return true;
|
|
917
|
+
if (visited.has(cursorId)) return true;
|
|
918
|
+
visited.add(cursorId);
|
|
919
|
+
cursorId = byId.get(cursorId)?.supersededBy?.trim() || undefined;
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/** Persist one explicit Work supersession edge without deleting or terminalizing either side. */
|
|
925
|
+
export function supersedeWorkContract(
|
|
926
|
+
options: WorkContractStoreOptions,
|
|
927
|
+
input: SupersedeWorkContractInput,
|
|
928
|
+
): { predecessor: WorkContract; successor: WorkContract } {
|
|
929
|
+
return withWorkContractStoreWrite(options, () => {
|
|
930
|
+
const predecessorId = sanitizeFileComponent(input.workId);
|
|
931
|
+
const successorId = sanitizeFileComponent(input.supersededBy);
|
|
932
|
+
if (!predecessorId || predecessorId === 'unknown' || !successorId || successorId === 'unknown') throw new Error('WORK_SUPERSESSION_IDS_REQUIRED');
|
|
933
|
+
if (predecessorId === successorId) throw new Error('WORK_SUCCESSOR_ID_MUST_CHANGE');
|
|
934
|
+
const store = readWorkContractStore(options);
|
|
935
|
+
const predecessorIndex = store.contracts.findIndex((contract) => contract.workId === predecessorId);
|
|
936
|
+
const successorIndex = store.contracts.findIndex((contract) => contract.workId === successorId);
|
|
937
|
+
if (predecessorIndex < 0) throw new Error(`WORK_PREDECESSOR_NOT_FOUND: ${predecessorId}`);
|
|
938
|
+
if (successorIndex < 0) throw new Error(`WORK_SUCCESSOR_NOT_FOUND: ${successorId}`);
|
|
939
|
+
const predecessor = store.contracts[predecessorIndex]!;
|
|
940
|
+
const successor = store.contracts[successorIndex]!;
|
|
941
|
+
if (predecessor.supersededBy && predecessor.supersededBy !== successorId) throw new Error(`WORK_SUPERSESSION_CONFLICT: ${predecessorId}:existing=${predecessor.supersededBy}:requested=${successorId}`);
|
|
942
|
+
if ((predecessor.supersedes ?? []).includes(successorId) || workSupersessionWouldCycle(store.contracts, predecessorId, successorId)) throw new Error(`WORK_SUPERSESSION_CYCLE: ${predecessorId}:${successorId}`);
|
|
943
|
+
const at = nowIso(options);
|
|
944
|
+
const reason = String(input.reason ?? '').trim().slice(0, 500);
|
|
945
|
+
if (!reason) throw new Error('WORK_SUPERSESSION_REASON_REQUIRED');
|
|
946
|
+
const predecessorNext = validateWorkSemantics({ ...predecessor, supersededBy: successorId, supersessionReason: reason, updatedAt: at });
|
|
947
|
+
const successorNext = validateWorkSemantics({ ...successor, supersedes: [...new Set([...(successor.supersedes ?? []), predecessorId])], updatedAt: at });
|
|
948
|
+
const contracts = [...store.contracts];
|
|
949
|
+
contracts[predecessorIndex] = predecessorNext;
|
|
950
|
+
contracts[successorIndex] = successorNext;
|
|
951
|
+
if (!sqliteBacked(options)) {
|
|
952
|
+
writeJsonAtomic(workContractStorePath(options), { schemaVersion: 3, updatedAt: at, contracts });
|
|
953
|
+
} else {
|
|
954
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
955
|
+
for (const contract of [predecessorNext, successorNext]) {
|
|
956
|
+
const current = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options.repoId, contract.workId);
|
|
957
|
+
if (!current) throw new Error(`WORK_LINEAGE_RECORD_MISSING: ${contract.workId}`);
|
|
958
|
+
writeControlPlaneRecordWithinTransaction(database, { namespace: 'work_contract', scope: options.repoId, key: contract.workId, schemaVersion: 3, value: contract, action: 'work_contract_supersession_linked', expectedRevision: current.revision });
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return { predecessor: predecessorNext, successor: successorNext };
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
export function summarizeWorkContract(contract: WorkContract): WorkContractSummary {
|
|
967
|
+
return {
|
|
968
|
+
workId: contract.workId,
|
|
969
|
+
repoId: contract.repoId,
|
|
970
|
+
mode: contract.mode,
|
|
971
|
+
phase: contract.phase,
|
|
972
|
+
status: contract.status,
|
|
973
|
+
objective: contract.objective.slice(0, 240),
|
|
974
|
+
updatedAt: contract.updatedAt,
|
|
975
|
+
handoffCount: contract.handoffRefs.length,
|
|
976
|
+
evidenceCount: contract.evidenceRefs.length,
|
|
977
|
+
checkCount: contract.checkRefs.length,
|
|
978
|
+
...(contract.engineeringContext ? {
|
|
979
|
+
engineering: {
|
|
980
|
+
profileVersion: contract.engineeringContext.profileVersion,
|
|
981
|
+
riskClass: contract.engineeringContext.riskClass,
|
|
982
|
+
missingAdmissionEvidence: contract.engineeringContext.missingAdmissionEvidence,
|
|
983
|
+
missingCompletionEvidence: contract.engineeringContext.missingCompletionEvidence,
|
|
984
|
+
},
|
|
985
|
+
} : {}),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
type WorkContractMutationPatch = Partial<Omit<WorkContract, 'schemaVersion' | 'workId' | 'repoId' | 'createdAt'>>;
|
|
990
|
+
type WorkContractMutation = WorkContractMutationPatch | ((current: WorkContract, recordedAt: string) => WorkContractMutationPatch | undefined);
|
|
991
|
+
|
|
992
|
+
function updateWorkContractInternal(
|
|
993
|
+
options: WorkContractStoreOptions,
|
|
994
|
+
workId: string,
|
|
995
|
+
mutation: WorkContractMutation,
|
|
996
|
+
allowCompletionWrite: boolean,
|
|
997
|
+
allowLifecycleWrite = false,
|
|
998
|
+
allowRetainedCancelledResume = false,
|
|
999
|
+
allowImplementationReviewWrite = false,
|
|
1000
|
+
allowPhaseRegression = false,
|
|
1001
|
+
): WorkContract {
|
|
1002
|
+
return withWorkContractStoreWrite(options, () => {
|
|
1003
|
+
const sanitizedId = sanitizeFileComponent(workId);
|
|
1004
|
+
const store = readWorkContractStore(options);
|
|
1005
|
+
const index = store.contracts.findIndex((contract) => contract.workId === sanitizedId);
|
|
1006
|
+
if (index < 0) throw new Error(`work contract not found: ${sanitizedId}`);
|
|
1007
|
+
const at = nowIso(options);
|
|
1008
|
+
const current = store.contracts[index];
|
|
1009
|
+
const patch = typeof mutation === 'function' ? mutation(current, at) : mutation;
|
|
1010
|
+
if (!patch) return current;
|
|
1011
|
+
if (options.controllerHome) {
|
|
1012
|
+
assertCanonicalWorkAdmissionAllowed(options, {
|
|
1013
|
+
operation: patch.status !== undefined && isTerminalWorkContractStatus(patch.status)
|
|
1014
|
+
? 'maintenance'
|
|
1015
|
+
: 'continue',
|
|
1016
|
+
workId: sanitizedId,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const writesCompletionReceipt = Object.prototype.hasOwnProperty.call(patch, 'completionReceipt');
|
|
1020
|
+
const changesCompletionOutcome = patch.completionOutcome !== undefined && patch.completionOutcome !== current.completionOutcome;
|
|
1021
|
+
const writesPhase = Object.prototype.hasOwnProperty.call(patch, 'phase') || Object.prototype.hasOwnProperty.call(patch, 'phaseEvidence');
|
|
1022
|
+
const writesLifecycle = writesPhase
|
|
1023
|
+
|| Object.prototype.hasOwnProperty.call(patch, 'status')
|
|
1024
|
+
|| Object.prototype.hasOwnProperty.call(patch, 'dispatchState')
|
|
1025
|
+
|| Object.prototype.hasOwnProperty.call(patch, 'evidenceState')
|
|
1026
|
+
|| Object.prototype.hasOwnProperty.call(patch, 'workKind');
|
|
1027
|
+
const writesImplementationReviews = Object.prototype.hasOwnProperty.call(patch, 'implementationReviews');
|
|
1028
|
+
if (!allowLifecycleWrite && writesLifecycle) throw new Error('WORK_LIFECYCLE_REQUIRES_TRANSITION_API');
|
|
1029
|
+
if (!allowImplementationReviewWrite && writesImplementationReviews) throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRES_RECORD_API');
|
|
1030
|
+
const projectedPhase = patch.phase ?? current.phase;
|
|
1031
|
+
const projectedPhaseEvidence = patch.phaseEvidence ?? current.phaseEvidence;
|
|
1032
|
+
if (!allowCompletionWrite && (writesCompletionReceipt || changesCompletionOutcome || (patch.status === 'completed' && current.status !== 'completed'))) {
|
|
1033
|
+
throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
1034
|
+
}
|
|
1035
|
+
if (patch.status === 'completed' && !current.completionReceipt && !patch.completionReceipt) {
|
|
1036
|
+
throw new Error('WORK_COMPLETION_RECEIPT_REQUIRED');
|
|
1037
|
+
}
|
|
1038
|
+
if (writesCompletionReceipt && patch.completionReceipt === undefined && current.completionReceipt) {
|
|
1039
|
+
throw new Error('WORK_COMPLETION_RECEIPT_IMMUTABLE');
|
|
1040
|
+
}
|
|
1041
|
+
const next: WorkContract = validateWorkSemanticTransition(current, validateWorkSemantics({
|
|
1042
|
+
...current,
|
|
1043
|
+
...patch,
|
|
1044
|
+
schemaVersion: 3,
|
|
1045
|
+
workId: current.workId,
|
|
1046
|
+
repoId: current.repoId,
|
|
1047
|
+
createdAt: current.createdAt,
|
|
1048
|
+
updatedAt: at,
|
|
1049
|
+
risk: patch.risk ?? current.risk,
|
|
1050
|
+
workKind: patch.workKind ?? current.workKind,
|
|
1051
|
+
phase: projectedPhase,
|
|
1052
|
+
phaseEvidence: projectedPhaseEvidence,
|
|
1053
|
+
dispatchState: patch.dispatchState ?? current.dispatchState,
|
|
1054
|
+
evidenceState: patch.evidenceState ?? current.evidenceState,
|
|
1055
|
+
completionOutcome: patch.completionOutcome ?? current.completionOutcome,
|
|
1056
|
+
evidenceRefs: (patch.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs),
|
|
1057
|
+
handoffRefs: (patch.handoffRefs ?? current.handoffRefs).slice(0, 20),
|
|
1058
|
+
suggestedNextActions: suggestedActionsForStatus(patch.status ?? current.status, patch.suggestedNextActions ?? current.suggestedNextActions),
|
|
1059
|
+
policyDecisions: (patch.policyDecisions ?? current.policyDecisions).slice(0, 20),
|
|
1060
|
+
checkRefs: (patch.checkRefs ?? current.checkRefs).slice(0, 50),
|
|
1061
|
+
implementationReviews: (patch.implementationReviews ?? current.implementationReviews ?? []).slice(-MAX_IMPLEMENTATION_REVIEW_HISTORY),
|
|
1062
|
+
reconciliations: (patch.reconciliations ?? current.reconciliations ?? []).slice(0, 20),
|
|
1063
|
+
objective: (patch.objective ?? current.objective).slice(0, 2_000),
|
|
1064
|
+
continuationPrompt: (patch.continuationPrompt ?? current.continuationPrompt)?.slice(0, 2_000),
|
|
1065
|
+
}), { allowRetainedCancelledResume, allowPhaseRegression });
|
|
1066
|
+
const contracts = [...store.contracts];
|
|
1067
|
+
contracts[index] = next;
|
|
1068
|
+
writeWorkContractStore(options, { schemaVersion: 3, updatedAt: at, contracts });
|
|
1069
|
+
return next;
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Build the only permitted in-place Plan rebind for an active Work: the same
|
|
1076
|
+
* Work and Plan step may move to an explicit successor Plan after a scope-only
|
|
1077
|
+
* replan. This is deliberately pure so the Plan store can persist predecessor,
|
|
1078
|
+
* successor, and Work in one SQLite transaction.
|
|
1079
|
+
*/
|
|
1080
|
+
export function rebindPlanBoundWorkContract(
|
|
1081
|
+
current: WorkContract,
|
|
1082
|
+
input: {
|
|
1083
|
+
predecessorPlanId: string;
|
|
1084
|
+
successorPlanId: string;
|
|
1085
|
+
planStepId: string;
|
|
1086
|
+
planSourceRevision: string;
|
|
1087
|
+
allowedPaths: string[];
|
|
1088
|
+
forbiddenPaths: string[];
|
|
1089
|
+
checks: string[];
|
|
1090
|
+
recordedAt: string;
|
|
1091
|
+
reason: string;
|
|
1092
|
+
},
|
|
1093
|
+
): WorkContract {
|
|
1094
|
+
const canonicalCurrent = validateCanonicalWorkContract(current);
|
|
1095
|
+
if (isTerminalWorkContractStatus(canonicalCurrent.status) || canonicalCurrent.completionReceipt || canonicalCurrent.completionOutcome) {
|
|
1096
|
+
throw new Error(`WORK_PLAN_REBIND_TERMINAL: ${canonicalCurrent.workId}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (canonicalCurrent.planId !== input.predecessorPlanId || canonicalCurrent.planStepId !== input.planStepId) {
|
|
1099
|
+
throw new Error(`WORK_PLAN_REBIND_SOURCE_MISMATCH: ${canonicalCurrent.workId}`);
|
|
1100
|
+
}
|
|
1101
|
+
const successorPlanId = sanitizeFileComponent(input.successorPlanId);
|
|
1102
|
+
const planStepId = sanitizeFileComponent(input.planStepId);
|
|
1103
|
+
if (!successorPlanId || successorPlanId === 'unknown' || successorPlanId === canonicalCurrent.planId) {
|
|
1104
|
+
throw new Error('WORK_PLAN_REBIND_SUCCESSOR_INVALID');
|
|
1105
|
+
}
|
|
1106
|
+
const planSourceRevision = input.planSourceRevision.trim();
|
|
1107
|
+
if (!planSourceRevision) throw new Error('WORK_PLAN_REBIND_SOURCE_REVISION_REQUIRED');
|
|
1108
|
+
const allowedPaths = [...new Set(input.allowedPaths.map((value) => value.trim()).filter(Boolean))].slice(0, 50);
|
|
1109
|
+
const forbiddenPaths = [...new Set(input.forbiddenPaths.map((value) => value.trim()).filter(Boolean))].slice(0, 50);
|
|
1110
|
+
const checks = [...new Set(input.checks.map((value) => value.trim()).filter(Boolean))].slice(0, 30);
|
|
1111
|
+
for (const path of canonicalCurrent.allowedPaths) {
|
|
1112
|
+
if (!allowedPaths.includes(path)) throw new Error(`WORK_PLAN_REBIND_SCOPE_NARROWING_FORBIDDEN: ${path}`);
|
|
1113
|
+
}
|
|
1114
|
+
if (JSON.stringify(forbiddenPaths) !== JSON.stringify(canonicalCurrent.forbiddenPaths)) {
|
|
1115
|
+
throw new Error('WORK_PLAN_REBIND_FORBIDDEN_SCOPE_CHANGE');
|
|
1116
|
+
}
|
|
1117
|
+
if (JSON.stringify(checks) !== JSON.stringify(canonicalCurrent.checks)) {
|
|
1118
|
+
throw new Error('WORK_PLAN_REBIND_CHECK_CHANGE');
|
|
1119
|
+
}
|
|
1120
|
+
const evidenceRefs = [{
|
|
1121
|
+
title: 'Plan-bound Work scope replanned',
|
|
1122
|
+
summary: `${canonicalCurrent.planId} -> ${successorPlanId}: ${input.reason.trim().slice(0, 1_000)}`,
|
|
1123
|
+
detailLevel: 'summary' as const,
|
|
1124
|
+
}, ...canonicalCurrent.evidenceRefs].slice(0, canonicalCurrent.evidencePolicy.maxEvidenceRefs);
|
|
1125
|
+
const next = validateWorkSemantics({
|
|
1126
|
+
...canonicalCurrent,
|
|
1127
|
+
scopeRef: semanticScopeRefForWork({
|
|
1128
|
+
workId: canonicalCurrent.workId,
|
|
1129
|
+
requirementId: canonicalCurrent.requirementId,
|
|
1130
|
+
planId: successorPlanId,
|
|
1131
|
+
planStepId,
|
|
1132
|
+
}),
|
|
1133
|
+
planId: successorPlanId,
|
|
1134
|
+
planStepId,
|
|
1135
|
+
planSourceRevision,
|
|
1136
|
+
allowedPaths,
|
|
1137
|
+
forbiddenPaths,
|
|
1138
|
+
checks,
|
|
1139
|
+
evidenceRefs,
|
|
1140
|
+
updatedAt: input.recordedAt,
|
|
1141
|
+
});
|
|
1142
|
+
return validateWorkSemanticTransition(canonicalCurrent, next);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Refresh one active Work against a newer revision of the same stable Plan.
|
|
1147
|
+
* This deliberately does not change Plan identity or semantic scope. Only the
|
|
1148
|
+
* source fence and an allowed-path widening may change; forbidden paths and
|
|
1149
|
+
* machine checks remain exact.
|
|
1150
|
+
*/
|
|
1151
|
+
export function refreshPlanBoundWorkRevision(
|
|
1152
|
+
current: WorkContract,
|
|
1153
|
+
input: {
|
|
1154
|
+
planId: string;
|
|
1155
|
+
planStepId: string;
|
|
1156
|
+
planSourceRevision: string;
|
|
1157
|
+
allowedPaths: string[];
|
|
1158
|
+
forbiddenPaths: string[];
|
|
1159
|
+
checks: string[];
|
|
1160
|
+
recordedAt: string;
|
|
1161
|
+
reason: string;
|
|
1162
|
+
},
|
|
1163
|
+
): WorkContract {
|
|
1164
|
+
const canonicalCurrent = validateCanonicalWorkContract(current);
|
|
1165
|
+
if (isTerminalWorkContractStatus(canonicalCurrent.status) || canonicalCurrent.completionReceipt || canonicalCurrent.completionOutcome) {
|
|
1166
|
+
throw new Error(`WORK_PLAN_REVISION_REFRESH_TERMINAL: ${canonicalCurrent.workId}`);
|
|
1167
|
+
}
|
|
1168
|
+
const planId = sanitizeFileComponent(input.planId);
|
|
1169
|
+
const planStepId = sanitizeFileComponent(input.planStepId);
|
|
1170
|
+
if (!planId || planId === 'unknown' || canonicalCurrent.planId !== planId || canonicalCurrent.planStepId !== planStepId) {
|
|
1171
|
+
throw new Error(`WORK_PLAN_REVISION_REFRESH_SOURCE_MISMATCH: ${canonicalCurrent.workId}`);
|
|
1172
|
+
}
|
|
1173
|
+
const planSourceRevision = input.planSourceRevision.trim();
|
|
1174
|
+
if (!planSourceRevision) throw new Error('WORK_PLAN_REVISION_REFRESH_SOURCE_REVISION_REQUIRED');
|
|
1175
|
+
const allowedPaths = [...new Set(input.allowedPaths.map((value) => value.trim()).filter(Boolean))].slice(0, 50);
|
|
1176
|
+
const forbiddenPaths = [...new Set(input.forbiddenPaths.map((value) => value.trim()).filter(Boolean))].slice(0, 50);
|
|
1177
|
+
const checks = [...new Set(input.checks.map((value) => value.trim()).filter(Boolean))].slice(0, 30);
|
|
1178
|
+
for (const path of canonicalCurrent.allowedPaths) {
|
|
1179
|
+
if (!allowedPaths.includes(path)) throw new Error(`WORK_PLAN_REVISION_REFRESH_SCOPE_NARROWING_FORBIDDEN: ${path}`);
|
|
1180
|
+
}
|
|
1181
|
+
if (JSON.stringify(forbiddenPaths) !== JSON.stringify(canonicalCurrent.forbiddenPaths)) {
|
|
1182
|
+
throw new Error('WORK_PLAN_REVISION_REFRESH_FORBIDDEN_SCOPE_CHANGE');
|
|
1183
|
+
}
|
|
1184
|
+
if (JSON.stringify(checks) !== JSON.stringify(canonicalCurrent.checks)) {
|
|
1185
|
+
throw new Error('WORK_PLAN_REVISION_REFRESH_CHECK_CHANGE');
|
|
1186
|
+
}
|
|
1187
|
+
const reason = input.reason.trim().slice(0, 1_000);
|
|
1188
|
+
if (!reason) throw new Error('WORK_PLAN_REVISION_REFRESH_REASON_REQUIRED');
|
|
1189
|
+
const evidenceRefs = [{
|
|
1190
|
+
title: 'Plan revision refreshed Work authority',
|
|
1191
|
+
summary: `${planId}@${canonicalCurrent.planSourceRevision ?? 'unknown'} -> ${planId}@${planSourceRevision}: ${reason}`,
|
|
1192
|
+
detailLevel: 'summary' as const,
|
|
1193
|
+
}, ...canonicalCurrent.evidenceRefs].slice(0, canonicalCurrent.evidencePolicy.maxEvidenceRefs);
|
|
1194
|
+
const next = validateWorkSemantics({
|
|
1195
|
+
...canonicalCurrent,
|
|
1196
|
+
planSourceRevision,
|
|
1197
|
+
allowedPaths,
|
|
1198
|
+
forbiddenPaths,
|
|
1199
|
+
checks,
|
|
1200
|
+
evidenceRefs,
|
|
1201
|
+
updatedAt: input.recordedAt,
|
|
1202
|
+
});
|
|
1203
|
+
return validateWorkSemanticTransition(canonicalCurrent, next);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Retire technical Work authority when its owning Plan is no longer current.
|
|
1208
|
+
*
|
|
1209
|
+
* This is authority retirement, not history deletion. The cancelled Work and
|
|
1210
|
+
* its evidence stay durable for audit/recovery/retention, while status and
|
|
1211
|
+
* dispatch become terminal immediately so schedulers, concurrency admission,
|
|
1212
|
+
* and UI current-state projections cannot keep executing an obsolete Plan.
|
|
1213
|
+
*/
|
|
1214
|
+
export function retirePlanBoundWorkContract(
|
|
1215
|
+
current: WorkContract,
|
|
1216
|
+
input: {
|
|
1217
|
+
predecessorPlanId: string;
|
|
1218
|
+
successorPlanId?: string;
|
|
1219
|
+
recordedAt: string;
|
|
1220
|
+
reason: string;
|
|
1221
|
+
},
|
|
1222
|
+
): WorkContract {
|
|
1223
|
+
const canonicalCurrent = validateCanonicalWorkContract(current);
|
|
1224
|
+
if (isTerminalWorkContractStatus(canonicalCurrent.status)) return canonicalCurrent;
|
|
1225
|
+
if (canonicalCurrent.planId !== input.predecessorPlanId) {
|
|
1226
|
+
throw new Error(`WORK_PLAN_RETIRE_SOURCE_MISMATCH: ${canonicalCurrent.workId}:expected=${input.predecessorPlanId}:actual=${canonicalCurrent.planId ?? 'none'}`);
|
|
1227
|
+
}
|
|
1228
|
+
if (canonicalCurrent.completionReceipt || canonicalCurrent.completionOutcome) {
|
|
1229
|
+
throw new Error(`WORK_PLAN_RETIRE_COMPLETION_CONFLICT: ${canonicalCurrent.workId}`);
|
|
1230
|
+
}
|
|
1231
|
+
const reason = input.reason.trim().slice(0, 1_000);
|
|
1232
|
+
if (!reason) throw new Error('WORK_PLAN_RETIRE_REASON_REQUIRED');
|
|
1233
|
+
const successor = input.successorPlanId?.trim();
|
|
1234
|
+
const summary = successor
|
|
1235
|
+
? `Plan authority retired ${canonicalCurrent.planId} -> ${successor}: ${reason}`
|
|
1236
|
+
: `Plan authority retired ${canonicalCurrent.planId}: ${reason}`;
|
|
1237
|
+
const evidenceRefs = [{
|
|
1238
|
+
title: 'Plan-bound Work authority retired',
|
|
1239
|
+
summary,
|
|
1240
|
+
detailLevel: 'summary' as const,
|
|
1241
|
+
}, ...canonicalCurrent.evidenceRefs].slice(0, canonicalCurrent.evidencePolicy.maxEvidenceRefs);
|
|
1242
|
+
const next = validateWorkSemantics({
|
|
1243
|
+
...canonicalCurrent,
|
|
1244
|
+
status: 'cancelled',
|
|
1245
|
+
phase: 'cleanup',
|
|
1246
|
+
phaseEvidence: transitionPhaseEvidence(canonicalCurrent, 'cleanup', {
|
|
1247
|
+
status: 'cancelled',
|
|
1248
|
+
summary,
|
|
1249
|
+
evidenceRefs,
|
|
1250
|
+
recordedAt: input.recordedAt,
|
|
1251
|
+
}),
|
|
1252
|
+
dispatchState: 'terminal',
|
|
1253
|
+
evidenceRefs,
|
|
1254
|
+
suggestedNextActions: [],
|
|
1255
|
+
updatedAt: input.recordedAt,
|
|
1256
|
+
});
|
|
1257
|
+
return validateWorkSemanticTransition(canonicalCurrent, next);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
export type WorkContractMetadataPatch = Partial<Omit<
|
|
1261
|
+
WorkContract,
|
|
1262
|
+
| 'schemaVersion'
|
|
1263
|
+
| 'workId'
|
|
1264
|
+
| 'repoId'
|
|
1265
|
+
| 'createdAt'
|
|
1266
|
+
| 'updatedAt'
|
|
1267
|
+
| 'status'
|
|
1268
|
+
| 'phase'
|
|
1269
|
+
| 'phaseEvidence'
|
|
1270
|
+
| 'dispatchState'
|
|
1271
|
+
| 'evidenceState'
|
|
1272
|
+
| 'completionReceipt'
|
|
1273
|
+
| 'completionOutcome'
|
|
1274
|
+
| 'implementationReviews'
|
|
1275
|
+
| 'workKind'
|
|
1276
|
+
>>;
|
|
1277
|
+
|
|
1278
|
+
export function updateWorkContract(
|
|
1279
|
+
options: WorkContractStoreOptions,
|
|
1280
|
+
workId: string,
|
|
1281
|
+
patch: WorkContractMetadataPatch,
|
|
1282
|
+
): WorkContract {
|
|
1283
|
+
return updateWorkContractInternal(options, workId, patch, false);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/** Explicit semantic transition used only when an effect Work begins governed repository mutation. */
|
|
1287
|
+
export function promoteWorkToRepositoryChange(
|
|
1288
|
+
options: WorkContractStoreOptions,
|
|
1289
|
+
workId: string,
|
|
1290
|
+
): WorkContract {
|
|
1291
|
+
return updateWorkContractInternal(options, workId, (current) => {
|
|
1292
|
+
if (isTerminalWorkContractStatus(current.status) || current.completionReceipt || current.completionOutcome) {
|
|
1293
|
+
throw new Error(`WORK_KIND_PROMOTION_TERMINAL: ${workId}`);
|
|
1294
|
+
}
|
|
1295
|
+
if (current.workKind === 'repository_change') return undefined;
|
|
1296
|
+
if (current.workKind !== 'local_effect' && current.workKind !== 'remote_effect') {
|
|
1297
|
+
throw new Error(`WORK_KIND_PROMOTION_INVALID: ${workId}:${current.workKind}`);
|
|
1298
|
+
}
|
|
1299
|
+
return { workKind: 'repository_change' };
|
|
1300
|
+
}, false, true);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
export function recordWorkEvidenceState(
|
|
1304
|
+
options: WorkContractStoreOptions,
|
|
1305
|
+
workId: string,
|
|
1306
|
+
evidenceState: EvidenceState,
|
|
1307
|
+
): WorkContract {
|
|
1308
|
+
return updateWorkContractInternal(options, workId, { evidenceState }, false, true);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
export function activateWorkContract(
|
|
1312
|
+
options: WorkContractStoreOptions,
|
|
1313
|
+
workId: string,
|
|
1314
|
+
input: {
|
|
1315
|
+
summary: string;
|
|
1316
|
+
phase?: WorkPhase;
|
|
1317
|
+
worktreeRef?: string;
|
|
1318
|
+
evidenceState?: EvidenceState;
|
|
1319
|
+
},
|
|
1320
|
+
): WorkContract {
|
|
1321
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1322
|
+
if (current.status === 'completed' || current.status === 'cancelled') {
|
|
1323
|
+
throw new Error(`WORK_ACTIVATION_TERMINAL: ${workId}:${current.status}`);
|
|
1324
|
+
}
|
|
1325
|
+
const phase = input.phase ?? current.phase;
|
|
1326
|
+
const phaseEvidence = transitionPhaseEvidence(current, phase, {
|
|
1327
|
+
status: 'running',
|
|
1328
|
+
summary: input.summary,
|
|
1329
|
+
recordedAt: at,
|
|
1330
|
+
source: 'recorded',
|
|
1331
|
+
});
|
|
1332
|
+
const evidenceState = input.evidenceState
|
|
1333
|
+
?? (current.evidenceState === 'failed' ? 'partial' : current.evidenceState);
|
|
1334
|
+
return {
|
|
1335
|
+
status: 'running',
|
|
1336
|
+
phase,
|
|
1337
|
+
phaseEvidence,
|
|
1338
|
+
dispatchState: 'running',
|
|
1339
|
+
evidenceState,
|
|
1340
|
+
worktreeRef: input.worktreeRef ?? current.worktreeRef,
|
|
1341
|
+
};
|
|
1342
|
+
}, false, true, false, false, true);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export function failWorkContract(
|
|
1346
|
+
options: WorkContractStoreOptions,
|
|
1347
|
+
workId: string,
|
|
1348
|
+
input: { phase: WorkPhase; summary: string; evidenceRefs?: EvidenceRef[] },
|
|
1349
|
+
): WorkContract {
|
|
1350
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1351
|
+
if (current.status === 'completed' || current.status === 'cancelled') {
|
|
1352
|
+
throw new Error(`WORK_FAILURE_TERMINAL: ${workId}:${current.status}`);
|
|
1353
|
+
}
|
|
1354
|
+
if (input.phase !== current.phase) {
|
|
1355
|
+
throw new Error(`WORK_FAILURE_PHASE_MISMATCH: ${workId}:expected=${current.phase}:actual=${input.phase}`);
|
|
1356
|
+
}
|
|
1357
|
+
const phaseEvidence = transitionPhaseEvidence(current, current.phase, {
|
|
1358
|
+
status: 'failed',
|
|
1359
|
+
summary: input.summary,
|
|
1360
|
+
evidenceRefs: input.evidenceRefs,
|
|
1361
|
+
recordedAt: at,
|
|
1362
|
+
source: 'recorded',
|
|
1363
|
+
});
|
|
1364
|
+
return {
|
|
1365
|
+
status: 'failed',
|
|
1366
|
+
phase: current.phase,
|
|
1367
|
+
phaseEvidence,
|
|
1368
|
+
dispatchState: 'terminal',
|
|
1369
|
+
evidenceState: 'failed',
|
|
1370
|
+
...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
|
|
1371
|
+
};
|
|
1372
|
+
}, false, true);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
export function cancelWorkContract(
|
|
1376
|
+
options: WorkContractStoreOptions,
|
|
1377
|
+
workId: string,
|
|
1378
|
+
input: { summary: string; evidenceRefs?: EvidenceRef[] },
|
|
1379
|
+
): WorkContract {
|
|
1380
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1381
|
+
if (current.status === 'completed') throw new Error(`WORK_CANCEL_COMPLETED: ${workId}`);
|
|
1382
|
+
if (current.status === 'cancelled') return undefined;
|
|
1383
|
+
const phaseEvidence = transitionPhaseEvidence(current, current.phase, {
|
|
1384
|
+
status: 'cancelled',
|
|
1385
|
+
summary: input.summary,
|
|
1386
|
+
evidenceRefs: input.evidenceRefs,
|
|
1387
|
+
recordedAt: at,
|
|
1388
|
+
source: 'recorded',
|
|
1389
|
+
});
|
|
1390
|
+
return {
|
|
1391
|
+
status: 'cancelled',
|
|
1392
|
+
phase: current.phase,
|
|
1393
|
+
phaseEvidence,
|
|
1394
|
+
dispatchState: 'terminal',
|
|
1395
|
+
...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
|
|
1396
|
+
suggestedNextActions: [],
|
|
1397
|
+
};
|
|
1398
|
+
}, false, true);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Reopen one explicitly retained cancelled repository Work after the facade has
|
|
1403
|
+
* reauthenticated the same principal and revalidated physical Work ownership.
|
|
1404
|
+
* Generic mutation APIs intentionally cannot perform terminal -> running.
|
|
1405
|
+
*/
|
|
1406
|
+
export function resumeRetainedCancelledWorkContract(
|
|
1407
|
+
options: WorkContractStoreOptions,
|
|
1408
|
+
workId: string,
|
|
1409
|
+
input: {
|
|
1410
|
+
principalId: string;
|
|
1411
|
+
controllerInstanceId: string;
|
|
1412
|
+
summary: string;
|
|
1413
|
+
checkoutId?: string;
|
|
1414
|
+
worktreeRef?: string;
|
|
1415
|
+
},
|
|
1416
|
+
): WorkContract {
|
|
1417
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1418
|
+
if (current.status !== 'cancelled' || current.dispatchState !== 'terminal') {
|
|
1419
|
+
throw new Error(`WORK_CANCELLED_RESUME_STATUS_INVALID: ${workId}`);
|
|
1420
|
+
}
|
|
1421
|
+
if (current.workKind !== 'repository_change') throw new Error(`WORK_CANCELLED_RESUME_KIND_INVALID: ${workId}`);
|
|
1422
|
+
if (current.completionReceipt || current.completionOutcome) throw new Error(`WORK_CANCELLED_RESUME_COMPLETION_CONFLICT: ${workId}`);
|
|
1423
|
+
if (current.phaseEvidence[current.phase].state !== 'skipped' || current.phaseEvidence[current.phase].source !== 'recorded') {
|
|
1424
|
+
throw new Error(`WORK_CANCELLED_RESUME_HISTORY_AMBIGUOUS: ${workId}`);
|
|
1425
|
+
}
|
|
1426
|
+
const originalPrincipal = current.principalId?.trim();
|
|
1427
|
+
if (!originalPrincipal || originalPrincipal !== input.principalId.trim()) {
|
|
1428
|
+
throw new Error(`WORK_CANCELLED_RESUME_PRINCIPAL_MISMATCH: ${workId}`);
|
|
1429
|
+
}
|
|
1430
|
+
const evidenceRefs = [...current.evidenceRefs, {
|
|
1431
|
+
title: 'explicit current-user Work reauthorization',
|
|
1432
|
+
summary: input.summary.trim().slice(0, 1_000),
|
|
1433
|
+
detailLevel: 'summary' as const,
|
|
1434
|
+
}].slice(-current.evidencePolicy.maxEvidenceRefs);
|
|
1435
|
+
const phaseEvidence = transitionPhaseEvidence({ ...current, evidenceRefs }, 'implementation', {
|
|
1436
|
+
status: 'running',
|
|
1437
|
+
summary: input.summary,
|
|
1438
|
+
evidenceRefs,
|
|
1439
|
+
recordedAt: at,
|
|
1440
|
+
source: 'recorded',
|
|
1441
|
+
});
|
|
1442
|
+
return {
|
|
1443
|
+
status: 'running',
|
|
1444
|
+
phase: 'implementation',
|
|
1445
|
+
phaseEvidence,
|
|
1446
|
+
dispatchState: 'running',
|
|
1447
|
+
evidenceRefs,
|
|
1448
|
+
checkoutId: input.checkoutId ?? current.checkoutId,
|
|
1449
|
+
worktreeRef: input.worktreeRef ?? current.worktreeRef,
|
|
1450
|
+
controllerInstanceId: input.controllerInstanceId,
|
|
1451
|
+
continuationPrompt: input.summary,
|
|
1452
|
+
};
|
|
1453
|
+
}, false, true, true, false, true);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/** Merge non-authoritative discovery/change evidence without changing policy fences. */
|
|
1457
|
+
export function recordWorkScopeEvidence(
|
|
1458
|
+
options: WorkContractStoreOptions,
|
|
1459
|
+
workId: string,
|
|
1460
|
+
input: { initialLikelyPaths?: string[]; inspectedPaths?: string[]; actualChangedPaths?: string[] },
|
|
1461
|
+
): WorkContract {
|
|
1462
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1463
|
+
const previous = current.scopeEvidence ?? {
|
|
1464
|
+
initialLikelyPaths: [], inspectedPaths: [], actualChangedPaths: [], recordedAt: current.createdAt,
|
|
1465
|
+
};
|
|
1466
|
+
return {
|
|
1467
|
+
scopeEvidence: {
|
|
1468
|
+
initialLikelyPaths: [...new Set([...previous.initialLikelyPaths, ...(input.initialLikelyPaths ?? [])])].slice(0, 100),
|
|
1469
|
+
inspectedPaths: [...new Set([...previous.inspectedPaths, ...(input.inspectedPaths ?? [])])].slice(0, 500),
|
|
1470
|
+
actualChangedPaths: [...new Set([...previous.actualChangedPaths, ...(input.actualChangedPaths ?? [])])].slice(0, 500),
|
|
1471
|
+
recordedAt: at,
|
|
1472
|
+
},
|
|
1473
|
+
};
|
|
1474
|
+
}, false);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
export function transitionWorkContractPhase(
|
|
1478
|
+
options: WorkContractStoreOptions,
|
|
1479
|
+
workId: string,
|
|
1480
|
+
input: {
|
|
1481
|
+
phase: WorkPhase;
|
|
1482
|
+
status: WorkContractStatus;
|
|
1483
|
+
state?: Exclude<WorkPhaseEvidenceState, 'pending'>;
|
|
1484
|
+
summary: string;
|
|
1485
|
+
evidenceRefs?: EvidenceRef[];
|
|
1486
|
+
dispatchState?: DispatchState;
|
|
1487
|
+
evidenceState?: EvidenceState;
|
|
1488
|
+
},
|
|
1489
|
+
): WorkContract {
|
|
1490
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1491
|
+
if (input.phase === 'review') {
|
|
1492
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRES_REQUEST_API');
|
|
1493
|
+
}
|
|
1494
|
+
if (input.phase === 'delivery'
|
|
1495
|
+
&& current.phase !== 'delivery'
|
|
1496
|
+
&& workRequiresImplementationReview(current.workKind, current.scopeEvidence?.actualChangedPaths ?? [])) {
|
|
1497
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRES_RECORD_API');
|
|
1498
|
+
}
|
|
1499
|
+
const phaseEvidence = transitionPhaseEvidence(current, input.phase, {
|
|
1500
|
+
status: input.status,
|
|
1501
|
+
summary: input.summary,
|
|
1502
|
+
evidenceRefs: input.evidenceRefs,
|
|
1503
|
+
recordedAt: at,
|
|
1504
|
+
});
|
|
1505
|
+
if (input.state) phaseEvidence[input.phase] = { ...phaseEvidence[input.phase], state: input.state };
|
|
1506
|
+
return {
|
|
1507
|
+
phase: input.phase,
|
|
1508
|
+
phaseEvidence,
|
|
1509
|
+
status: input.status,
|
|
1510
|
+
dispatchState: input.dispatchState ?? current.dispatchState,
|
|
1511
|
+
evidenceState: input.evidenceState ?? current.evidenceState,
|
|
1512
|
+
};
|
|
1513
|
+
}, false, true, false, false, true);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/** Enter the first-class implementation-review phase without recording a decision. */
|
|
1517
|
+
export function requestWorkImplementationReview(
|
|
1518
|
+
options: WorkContractStoreOptions,
|
|
1519
|
+
workId: string,
|
|
1520
|
+
summary: string,
|
|
1521
|
+
): WorkContract {
|
|
1522
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1523
|
+
if (isTerminalWorkContractStatus(current.status)) throw new Error(`WORK_IMPLEMENTATION_REVIEW_TERMINAL: ${workId}`);
|
|
1524
|
+
if (current.phase !== 'verification' || current.phaseEvidence.verification.state !== 'satisfied') {
|
|
1525
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_VERIFIED_CANDIDATE_REQUIRED');
|
|
1526
|
+
}
|
|
1527
|
+
const phaseEvidence = transitionPhaseEvidence(current, 'review', {
|
|
1528
|
+
status: 'running',
|
|
1529
|
+
summary,
|
|
1530
|
+
recordedAt: at,
|
|
1531
|
+
});
|
|
1532
|
+
return {
|
|
1533
|
+
phase: 'review',
|
|
1534
|
+
phaseEvidence,
|
|
1535
|
+
status: 'running',
|
|
1536
|
+
};
|
|
1537
|
+
}, false, true);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/** The only writer for durable Controller implementation-review authority. */
|
|
1541
|
+
export function recordWorkImplementationReview(
|
|
1542
|
+
options: WorkContractStoreOptions,
|
|
1543
|
+
workId: string,
|
|
1544
|
+
review: WorkImplementationReviewRecord,
|
|
1545
|
+
): WorkContract {
|
|
1546
|
+
validateImplementationReviewRecord(review);
|
|
1547
|
+
if (review.derivation === 'content_equivalent_commit') {
|
|
1548
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_DERIVATION_REQUIRES_TRANSFER_API');
|
|
1549
|
+
}
|
|
1550
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1551
|
+
if (isTerminalWorkContractStatus(current.status)) throw new Error(`WORK_IMPLEMENTATION_REVIEW_TERMINAL: ${workId}`);
|
|
1552
|
+
if (review.workId !== current.workId) throw new Error('WORK_IMPLEMENTATION_REVIEW_WORK_ID_MISMATCH');
|
|
1553
|
+
if (current.phase !== 'review') {
|
|
1554
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_PHASE_REQUIRED');
|
|
1555
|
+
}
|
|
1556
|
+
const target = implementationReviewDecisionTarget(review.decision);
|
|
1557
|
+
const history = [...(current.implementationReviews ?? []), review];
|
|
1558
|
+
if (history.length > MAX_IMPLEMENTATION_REVIEW_HISTORY) throw new Error('WORK_IMPLEMENTATION_REVIEW_HISTORY_LIMIT');
|
|
1559
|
+
const phaseEvidence = transitionPhaseEvidence(current, target.phase, {
|
|
1560
|
+
status: target.status,
|
|
1561
|
+
summary: `Implementation review ${review.reviewId}: ${review.decision}. ${review.rationale}`,
|
|
1562
|
+
recordedAt: at,
|
|
1563
|
+
});
|
|
1564
|
+
if (review.decision === 'approved') {
|
|
1565
|
+
phaseEvidence.review = {
|
|
1566
|
+
state: 'satisfied',
|
|
1567
|
+
source: 'recorded',
|
|
1568
|
+
summary: `Controller approved exact implementation candidate in ${review.reviewId}.`,
|
|
1569
|
+
evidenceRefs: current.evidenceRefs.slice(0, 20),
|
|
1570
|
+
recordedAt: review.recordedAt,
|
|
1571
|
+
};
|
|
1572
|
+
} else if (review.decision === 'blocked') {
|
|
1573
|
+
phaseEvidence.review = { ...phaseEvidence.review, state: 'blocked', recordedAt: review.recordedAt };
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
phase: target.phase,
|
|
1577
|
+
phaseEvidence,
|
|
1578
|
+
status: target.status,
|
|
1579
|
+
dispatchState: target.status === 'blocked'
|
|
1580
|
+
? 'blocked'
|
|
1581
|
+
: current.dispatchState === 'blocked'
|
|
1582
|
+
? 'running'
|
|
1583
|
+
: current.dispatchState,
|
|
1584
|
+
implementationReviews: history,
|
|
1585
|
+
};
|
|
1586
|
+
}, false, true, false, true, review.decision === 'changes_required');
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
export interface ContentEquivalentCommitAuthorityTransferInput {
|
|
1590
|
+
transferredVerificationRecords: VerificationRecord[];
|
|
1591
|
+
derivedReview: WorkImplementationReviewRecord;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Persist the representation-only commit authority transfer in one Work-store
|
|
1596
|
+
* transaction. The immutable parent review plus exact post-commit verification
|
|
1597
|
+
* evidence authorize this write; mutable lifecycle phase is only a projection.
|
|
1598
|
+
*/
|
|
1599
|
+
export function recordContentEquivalentCommitAuthorityTransfer(
|
|
1600
|
+
options: WorkContractStoreOptions,
|
|
1601
|
+
workId: string,
|
|
1602
|
+
input: ContentEquivalentCommitAuthorityTransferInput,
|
|
1603
|
+
): WorkContract {
|
|
1604
|
+
return withWorkContractStoreWrite(options, () => {
|
|
1605
|
+
const sanitizedId = sanitizeFileComponent(workId);
|
|
1606
|
+
if (options.controllerHome) assertCanonicalWorkAdmissionAllowed(options, { operation: 'continue', workId: sanitizedId });
|
|
1607
|
+
const store = readWorkContractStore(options);
|
|
1608
|
+
const index = store.contracts.findIndex((contract) => contract.workId === sanitizedId);
|
|
1609
|
+
if (index < 0) throw new Error(`work contract not found: ${sanitizedId}`);
|
|
1610
|
+
const current = store.contracts[index]!;
|
|
1611
|
+
if (current.completionReceipt || isTerminalWorkContractStatus(current.status)) {
|
|
1612
|
+
throw new Error(`WORK_IMPLEMENTATION_REVIEW_TRANSFER_TERMINAL: ${sanitizedId}`);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const review = input.derivedReview;
|
|
1616
|
+
validateImplementationReviewRecord(review);
|
|
1617
|
+
if (review.workId !== current.workId) throw new Error('WORK_IMPLEMENTATION_REVIEW_WORK_ID_MISMATCH');
|
|
1618
|
+
if (review.derivation !== 'content_equivalent_commit'
|
|
1619
|
+
|| review.decision !== 'approved'
|
|
1620
|
+
|| !review.derivedFromReviewId) {
|
|
1621
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_TRANSFER_DERIVATION_REQUIRED');
|
|
1622
|
+
}
|
|
1623
|
+
const parent = latestImplementationReview(current.implementationReviews);
|
|
1624
|
+
if (!parent || parent.reviewId !== review.derivedFromReviewId || parent.decision !== 'approved') {
|
|
1625
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_TRANSFER_PARENT_REQUIRED');
|
|
1626
|
+
}
|
|
1627
|
+
if (!['verification', 'review', 'delivery'].includes(current.phase)) {
|
|
1628
|
+
throw new Error(`WORK_IMPLEMENTATION_REVIEW_TRANSFER_PHASE_INVALID: ${current.phase}`);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const requestedCheckIds = new Set(current.checks);
|
|
1632
|
+
for (const record of input.transferredVerificationRecords) {
|
|
1633
|
+
if (!requestedCheckIds.has(record.checkId)) {
|
|
1634
|
+
throw new Error(`WORK_VERIFICATION_TRANSFER_UNDECLARED_CHECK: ${record.checkId}`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
const transferredVerificationRecords = input.transferredVerificationRecords.filter((record) =>
|
|
1638
|
+
!current.checkRefs.some((existing) => sameSuccessfulVerificationExecutionFact(existing, record)));
|
|
1639
|
+
const checkRefs = [...transferredVerificationRecords, ...current.checkRefs].slice(0, 50);
|
|
1640
|
+
const postVerification = authoritativeImplementationReviewVerificationEvidence({
|
|
1641
|
+
repoId: current.repoId,
|
|
1642
|
+
workId: current.workId,
|
|
1643
|
+
requiredCheckIds: current.checks,
|
|
1644
|
+
records: checkRefs,
|
|
1645
|
+
sourceRevision: review.sourceRevision,
|
|
1646
|
+
workspaceFingerprint: review.verificationWorkspaceFingerprint,
|
|
1647
|
+
});
|
|
1648
|
+
if (postVerification.missingCheckIds.length > 0) {
|
|
1649
|
+
throw new Error(`WORK_IMPLEMENTATION_REVIEW_TRANSFER_VERIFICATION_REQUIRED: ${postVerification.missingCheckIds.join(', ')}`);
|
|
1650
|
+
}
|
|
1651
|
+
const expectedEvidence = normalizeImplementationReviewEvidence(review.verificationEvidence);
|
|
1652
|
+
if (JSON.stringify(postVerification.evidence) !== JSON.stringify(expectedEvidence)) {
|
|
1653
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_TRANSFER_VERIFICATION_IDENTITY_MISMATCH');
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const history = [...current.implementationReviews, review];
|
|
1657
|
+
if (history.length > MAX_IMPLEMENTATION_REVIEW_HISTORY) throw new Error('WORK_IMPLEMENTATION_REVIEW_HISTORY_LIMIT');
|
|
1658
|
+
const at = nowIso(options);
|
|
1659
|
+
const summary = `Transferred verification and implementation-review authority to content-equivalent commit ${review.sourceRevision}.`;
|
|
1660
|
+
const phaseEvidence = transitionPhaseEvidence(current, 'delivery', {
|
|
1661
|
+
status: 'running',
|
|
1662
|
+
summary,
|
|
1663
|
+
evidenceRefs: current.evidenceRefs,
|
|
1664
|
+
recordedAt: at,
|
|
1665
|
+
});
|
|
1666
|
+
phaseEvidence.verification = {
|
|
1667
|
+
...phaseEvidence.verification,
|
|
1668
|
+
state: 'satisfied',
|
|
1669
|
+
source: 'recorded',
|
|
1670
|
+
summary: `Exact post-commit verification authority supports derived review ${review.reviewId}.`,
|
|
1671
|
+
evidenceRefs: current.evidenceRefs.slice(0, 20),
|
|
1672
|
+
recordedAt: review.recordedAt,
|
|
1673
|
+
};
|
|
1674
|
+
phaseEvidence.review = {
|
|
1675
|
+
state: 'satisfied',
|
|
1676
|
+
source: 'recorded',
|
|
1677
|
+
summary: `Controller approval ${parent.reviewId} was transferred as ${review.reviewId} after content-equivalence proof.`,
|
|
1678
|
+
evidenceRefs: current.evidenceRefs.slice(0, 20),
|
|
1679
|
+
recordedAt: review.recordedAt,
|
|
1680
|
+
};
|
|
1681
|
+
const next = validateWorkSemanticTransition(current, validateWorkSemantics({
|
|
1682
|
+
...current,
|
|
1683
|
+
updatedAt: at,
|
|
1684
|
+
status: 'running',
|
|
1685
|
+
phase: 'delivery',
|
|
1686
|
+
phaseEvidence,
|
|
1687
|
+
evidenceState: 'valid',
|
|
1688
|
+
checkRefs,
|
|
1689
|
+
implementationReviews: history,
|
|
1690
|
+
}));
|
|
1691
|
+
const contracts = [...store.contracts];
|
|
1692
|
+
contracts[index] = next;
|
|
1693
|
+
writeWorkContractStore(options, { schemaVersion: 3, updatedAt: at, contracts });
|
|
1694
|
+
return next;
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Repair only the lifecycle projection for an already-durable exact approved
|
|
1700
|
+
* implementation review. This never appends or derives review authority. The
|
|
1701
|
+
* caller must first independently prove that the immutable delivery candidate
|
|
1702
|
+
* still matches the durable review/check boundary.
|
|
1703
|
+
*/
|
|
1704
|
+
export function reconcileApprovedWorkImplementationReviewProjection(
|
|
1705
|
+
options: WorkContractStoreOptions,
|
|
1706
|
+
workId: string,
|
|
1707
|
+
expected: { reviewId: string; sourceRevision: string; changedPathDigest: string },
|
|
1708
|
+
): WorkContract {
|
|
1709
|
+
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1710
|
+
if (current.completionReceipt) return undefined;
|
|
1711
|
+
if (isTerminalWorkContractStatus(current.status)) throw new Error(`WORK_IMPLEMENTATION_REVIEW_PROJECTION_TERMINAL: ${workId}`);
|
|
1712
|
+
const review = latestImplementationReview(current.implementationReviews);
|
|
1713
|
+
if (!review || review.decision !== 'approved') throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRED');
|
|
1714
|
+
if (review.reviewId !== expected.reviewId
|
|
1715
|
+
|| review.sourceRevision !== expected.sourceRevision
|
|
1716
|
+
|| review.changedPathDigest !== expected.changedPathDigest) {
|
|
1717
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_PROJECTION_IDENTITY_MISMATCH');
|
|
1718
|
+
}
|
|
1719
|
+
if (current.phase === 'delivery' && current.phaseEvidence.review.state === 'satisfied') return undefined;
|
|
1720
|
+
if (!['verification', 'review', 'delivery'].includes(current.phase)) {
|
|
1721
|
+
throw new Error(`WORK_IMPLEMENTATION_REVIEW_PROJECTION_PHASE_INVALID: ${current.phase}`);
|
|
1722
|
+
}
|
|
1723
|
+
const summary = `Recovered lifecycle projection from durable approved implementation review ${review.reviewId}; review authority was not rewritten.`;
|
|
1724
|
+
const phaseEvidence = transitionPhaseEvidence(current, 'delivery', {
|
|
1725
|
+
status: 'running',
|
|
1726
|
+
summary,
|
|
1727
|
+
evidenceRefs: current.evidenceRefs,
|
|
1728
|
+
recordedAt: at,
|
|
1729
|
+
});
|
|
1730
|
+
phaseEvidence.review = {
|
|
1731
|
+
state: 'satisfied',
|
|
1732
|
+
source: 'recorded',
|
|
1733
|
+
summary: `Controller approved exact implementation candidate in ${review.reviewId}.`,
|
|
1734
|
+
evidenceRefs: current.evidenceRefs.slice(0, 20),
|
|
1735
|
+
recordedAt: review.recordedAt,
|
|
1736
|
+
};
|
|
1737
|
+
return {
|
|
1738
|
+
phase: 'delivery',
|
|
1739
|
+
phaseEvidence,
|
|
1740
|
+
status: 'running',
|
|
1741
|
+
};
|
|
1742
|
+
}, false, true);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
export function appendWorkEvidence(
|
|
1746
|
+
options: WorkContractStoreOptions,
|
|
1747
|
+
workId: string,
|
|
1748
|
+
evidence: EvidenceRef,
|
|
1749
|
+
): WorkContract {
|
|
1750
|
+
return updateWorkContractInternal(options, workId, (current) => ({
|
|
1751
|
+
evidenceRefs: [evidence, ...current.evidenceRefs].slice(0, current.evidencePolicy.maxEvidenceRefs),
|
|
1752
|
+
}), false);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
export function appendWorkHandoffRef(
|
|
1756
|
+
options: WorkContractStoreOptions,
|
|
1757
|
+
workId: string,
|
|
1758
|
+
handoffId: string,
|
|
1759
|
+
): WorkContract {
|
|
1760
|
+
const sanitizedHandoffId = sanitizeFileComponent(handoffId);
|
|
1761
|
+
return updateWorkContractInternal(options, workId, (current) => ({
|
|
1762
|
+
handoffRefs: [sanitizedHandoffId, ...current.handoffRefs.filter((id) => id !== sanitizedHandoffId)].slice(0, 20),
|
|
1763
|
+
}), false);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function sameSuccessfulVerificationExecutionFact(left: VerificationRecord, right: VerificationRecord): boolean {
|
|
1767
|
+
const a = left.receipt;
|
|
1768
|
+
const b = right.receipt;
|
|
1769
|
+
if (left.outcome !== 'valid_pass' || right.outcome !== 'valid_pass' || !a || !b) return false;
|
|
1770
|
+
if (!left.sourceRevision || !right.sourceRevision || left.sourceRevision !== right.sourceRevision) return false;
|
|
1771
|
+
if (!left.workspaceFingerprint || !right.workspaceFingerprint || left.workspaceFingerprint !== right.workspaceFingerprint) return false;
|
|
1772
|
+
if (left.checkId !== right.checkId || a.processId !== b.processId) return false;
|
|
1773
|
+
if (!a.checkCacheKey || !a.checkRevision || !a.checkDefinitionDigest || !a.checkEnvironmentFingerprint) return false;
|
|
1774
|
+
if (!b.checkCacheKey || !b.checkRevision || !b.checkDefinitionDigest || !b.checkEnvironmentFingerprint) return false;
|
|
1775
|
+
return a.checkCacheKey === b.checkCacheKey
|
|
1776
|
+
&& a.checkRevision === b.checkRevision
|
|
1777
|
+
&& a.checkDefinitionDigest === b.checkDefinitionDigest
|
|
1778
|
+
&& a.checkEnvironmentFingerprint === b.checkEnvironmentFingerprint;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
export function appendVerificationRecord(
|
|
1782
|
+
options: WorkContractStoreOptions,
|
|
1783
|
+
workId: string,
|
|
1784
|
+
record: VerificationRecord,
|
|
1785
|
+
): WorkContract {
|
|
1786
|
+
return updateWorkContractInternal(options, workId, (current) => {
|
|
1787
|
+
// Re-consuming the exact same successful Process against the exact same
|
|
1788
|
+
// source/workspace is request provenance, not a new Work verification fact.
|
|
1789
|
+
// Keep the original durable VerificationRecord so an approved review remains
|
|
1790
|
+
// bound to one stable evidence identity. A changed source/workspace, Process,
|
|
1791
|
+
// Check definition, environment, or cache identity still appends normally.
|
|
1792
|
+
if (current.checkRefs.some((existing) => sameSuccessfulVerificationExecutionFact(existing, record))) {
|
|
1793
|
+
return undefined;
|
|
1794
|
+
}
|
|
1795
|
+
return { checkRefs: [record, ...current.checkRefs].slice(0, 50) };
|
|
1796
|
+
}, false);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* The only supported path for turning Work evidence into a completion receipt.
|
|
1801
|
+
* Task/Run/Process callers may supply evidence, but they cannot manufacture a
|
|
1802
|
+
* receipt or a terminal Work projection without this identity and cleanup gate.
|
|
1803
|
+
*/
|
|
1804
|
+
export function recordWorkCompletionReceipt(
|
|
1805
|
+
options: WorkContractStoreOptions,
|
|
1806
|
+
workId: string,
|
|
1807
|
+
receipt: NonNullable<WorkContract['completionReceipt']>,
|
|
1808
|
+
completionOutcome: NonNullable<WorkContract['completionOutcome']>,
|
|
1809
|
+
completionWorkKind?: WorkKind,
|
|
1810
|
+
): WorkContract {
|
|
1811
|
+
return updateWorkContractInternal(options, workId, (current) => {
|
|
1812
|
+
if (receipt.workId !== current.workId) throw new Error('WORK_COMPLETION_RECEIPT_IDENTITY_MISMATCH');
|
|
1813
|
+
if (current.completionReceipt) {
|
|
1814
|
+
if (current.completionReceipt.receiptId !== receipt.receiptId) throw new Error('WORK_COMPLETION_RECEIPT_ALREADY_RECORDED');
|
|
1815
|
+
return undefined;
|
|
1816
|
+
}
|
|
1817
|
+
const recordedAt = receipt.recordedAt;
|
|
1818
|
+
const receiptChangedPaths = isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)
|
|
1819
|
+
? receipt.changedPaths
|
|
1820
|
+
: [];
|
|
1821
|
+
const historicalReconciliationException = isDirectEditWorkCompletionReceipt(receipt)
|
|
1822
|
+
&& Boolean(receipt.reconciliationId?.trim())
|
|
1823
|
+
&& current.reconciliations.some((entry) => entry.reconciliationId === receipt.reconciliationId && entry.outcome === 'accepted_equivalence');
|
|
1824
|
+
const reviewRequired = workRequiresImplementationReview(completionWorkKind ?? current.workKind, receiptChangedPaths);
|
|
1825
|
+
if (reviewRequired && !historicalReconciliationException && !['satisfied', 'skipped'].includes(current.phaseEvidence.review.state)) {
|
|
1826
|
+
throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRED');
|
|
1827
|
+
}
|
|
1828
|
+
const phaseEvidence: WorkPhaseEvidenceMap = {
|
|
1829
|
+
...current.phaseEvidence,
|
|
1830
|
+
implementation: { ...current.phaseEvidence.implementation, state: 'satisfied' },
|
|
1831
|
+
verification: { ...current.phaseEvidence.verification, state: 'satisfied' },
|
|
1832
|
+
review: reviewRequired
|
|
1833
|
+
? historicalReconciliationException
|
|
1834
|
+
? { state: 'skipped', source: 'recorded', summary: `Historical reviewed reconciliation ${receipt.reconciliationId} is the narrow compatibility authority for this already-delivered Direct Edit Work.`, evidenceRefs: current.evidenceRefs.slice(0, 20), recordedAt }
|
|
1835
|
+
: { ...current.phaseEvidence.review, state: 'satisfied' }
|
|
1836
|
+
: { state: 'skipped', source: 'recorded', summary: 'Implementation review is not required for this source-free Work completion.', evidenceRefs: [], recordedAt },
|
|
1837
|
+
delivery: { state: 'satisfied', source: 'recorded', summary: `Phase delivery accepted by Work completion receipt ${receipt.receiptId}.`, evidenceRefs: current.evidenceRefs.slice(0, 20), recordedAt, receiptId: receipt.receiptId },
|
|
1838
|
+
cleanup: { state: 'satisfied', source: 'recorded', summary: `Phase cleanup accepted by Work completion receipt ${receipt.receiptId}.`, evidenceRefs: current.evidenceRefs.slice(0, 20), recordedAt, receiptId: receipt.receiptId },
|
|
1839
|
+
};
|
|
1840
|
+
return {
|
|
1841
|
+
phase: 'cleanup',
|
|
1842
|
+
phaseEvidence,
|
|
1843
|
+
status: 'completed',
|
|
1844
|
+
dispatchState: 'terminal',
|
|
1845
|
+
evidenceState: 'valid',
|
|
1846
|
+
completionOutcome,
|
|
1847
|
+
...(completionWorkKind ? { workKind: completionWorkKind } : {}),
|
|
1848
|
+
completionReceipt: receipt,
|
|
1849
|
+
scopeEvidence: {
|
|
1850
|
+
initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ?? current.allowedPaths,
|
|
1851
|
+
inspectedPaths: current.scopeEvidence?.inspectedPaths ?? [],
|
|
1852
|
+
actualChangedPaths: [...new Set(receiptChangedPaths)].slice(0, 500),
|
|
1853
|
+
recordedAt,
|
|
1854
|
+
},
|
|
1855
|
+
};
|
|
1856
|
+
}, true, true);
|
|
1857
|
+
}
|
|
1858
|
+
export interface AcceptSubmittedWorkInput {
|
|
1859
|
+
requestId: string;
|
|
1860
|
+
repoId: string;
|
|
1861
|
+
parentWorkId?: string;
|
|
1862
|
+
semanticKey: string;
|
|
1863
|
+
operation: SubmittedWorkOperation;
|
|
1864
|
+
objective?: string;
|
|
1865
|
+
mode?: WorkContract['mode'];
|
|
1866
|
+
requestedBy?: WorkContract['requestedBy'];
|
|
1867
|
+
principalId?: string;
|
|
1868
|
+
controllerInstanceId?: string;
|
|
1869
|
+
workKind?: WorkKind;
|
|
1870
|
+
risk?: WorkRisk;
|
|
1871
|
+
allowedPaths?: string[];
|
|
1872
|
+
forbiddenPaths?: string[];
|
|
1873
|
+
checks?: string[];
|
|
1874
|
+
acceptanceCriteria?: string[];
|
|
1875
|
+
constraints?: WorkContract['constraints'];
|
|
1876
|
+
}
|