@moretea-labs/forge 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +73 -7
- package/CHANGELOG.md +34 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +531 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +229 -2
- package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
- package/adapters/computer/chatgpt-extension-target.ts +708 -0
- package/adapters/computer/chrome-extension/background.js +541 -0
- package/adapters/computer/chrome-extension/content.js +402 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +32 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +178 -24
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -7
- package/adapters/mcp/instructions.ts +6 -5
- package/adapters/mcp/multi-repository.ts +10 -47
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +490 -113
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +14 -54
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
- package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +19 -507
- package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +229 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +320 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
- package/adapters/mcp/server.ts +91 -26
- package/adapters/mcp/setup.ts +5 -13
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
- package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
- package/adapters/mcp/toolset-names.ts +11 -37
- package/adapters/mcp/toolset.ts +19 -92
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +377 -0
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +200 -683
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +26 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +205 -51
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +234 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +6 -5
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +16 -4
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -52
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +15 -7
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +25 -13
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +25 -25
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +348 -97
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +87 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +166 -143
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +18 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +34 -65
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +153 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/assemble-template.ts +11 -72
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +94 -201
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +921 -239
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +6 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +1 -3
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +12 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +95 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/sync-generated-authority.mjs +0 -1
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +12 -35
- package/src/cli/agent-jobs/integration.ts +20 -51
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +137 -135
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +5 -44
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +83 -102
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +107 -84
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +25 -52
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +42 -10
- package/src/cli/editing/edit-session.ts +18 -12
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +19 -14
- package/src/cli/local-bridge/console-view-models.ts +4 -13
- package/src/cli/local-bridge/facade-api.ts +64 -193
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +37 -18
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +79 -12
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +101 -5
- package/src/cli/repository/inspector.ts +185 -343
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +16 -0
- package/src/runtime/context/semantic-navigation.ts +7 -35
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +215 -12
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +616 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +197 -110
- package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +60 -34
- package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +201 -199
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
- package/src/runtime/control-plane/facade/capability-registry.ts +364 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +9 -9
- package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +256 -117
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +295 -107
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +82 -30
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +55 -18
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +3 -8
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +97 -29
- package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
- package/src/runtime/plugins/browser-adapter.ts +1124 -333
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
- package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +18 -0
- package/src/runtime/plugins/browser-runtime.ts +40 -36
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +228 -0
- package/src/runtime/plugins/computer-registration.ts +298 -20
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +189 -3
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +24 -12
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +46 -41
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -100
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +682 -435
- package/src/runtime/plugins/types.ts +24 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +4 -4
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +312 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +254 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +227 -29
- package/src/runtime/root/package-runtime-release.ts +117 -30
- package/src/runtime/root/package-runtime-service.ts +24 -24
- package/src/runtime/root/release-execution-canary.ts +57 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -40
- package/src/runtime/root/release-materialize.ts +372 -106
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +160 -29
- package/src/runtime/root/scheduled-controller-composition.ts +29 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +126 -44
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -9
- package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +5 -2
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3792 -417
- package/src/runtime/standalone-recovery/entry.ts +507 -45
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +12 -2
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -3
- package/src/runtime/workflow/schedules/engine.ts +166 -178
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +218 -0
- package/supervisor/control-plane.ts +963 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +147 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +1151 -0
- package/supervisor/native-messaging/host.ts +58 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +250 -0
- package/supervisor/server.ts +412 -0
- package/supervisor/store.ts +1870 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +261 -0
- package/SKILL.md +0 -427
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/stage-runtime-release.ts +0 -44
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
|
@@ -1,38 +1,30 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { existsSync, mkdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import { ensureControllerHome,
|
|
4
|
+
import { ensureControllerHome, scopedOperationRoot, SEMANTIC_SCOPE_KEY } from '../../../../src/cli/repositories/controller-home';
|
|
5
5
|
import { withControllerLock } from '../../../../src/cli/repositories/locks';
|
|
6
6
|
import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../../../../src/runtime/shared/json-files';
|
|
7
7
|
import {
|
|
8
|
+
listControlPlaneRecordKeys,
|
|
8
9
|
listControlPlaneRecords,
|
|
10
|
+
listControlPlaneRecordsWithinTransaction,
|
|
9
11
|
listControlPlaneRecordsExcludingPayloadTextValues,
|
|
10
12
|
initializeControlPlanePayloadTextExclusionIndex,
|
|
11
13
|
readControlPlaneRecord,
|
|
12
14
|
readControlPlaneRecordWithinTransaction,
|
|
13
15
|
withControlPlaneTransaction,
|
|
14
16
|
writeControlPlaneRecordWithinTransaction,
|
|
17
|
+
type SqliteDatabase,
|
|
15
18
|
} from '../../../../src/runtime/control-plane/persistence/sqlite-store';
|
|
16
19
|
import {
|
|
17
20
|
MAX_IMPLEMENTATION_REVIEW_HISTORY,
|
|
18
|
-
authoritativeImplementationReviewVerificationEvidence,
|
|
19
|
-
implementationReviewDecisionTarget,
|
|
20
|
-
latestImplementationReview,
|
|
21
|
-
normalizeImplementationReviewEvidence,
|
|
22
21
|
validateImplementationReviewRecord,
|
|
23
|
-
workRequiresImplementationReview,
|
|
24
22
|
type WorkImplementationReviewRecord,
|
|
25
23
|
} from '../domain/implementation-review';
|
|
26
|
-
import { phaseIndex,
|
|
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';
|
|
24
|
+
import { phaseIndex, suggestedActionsForSemanticState, transitionPhaseEvidence, validateWorkSemanticTransition, validateWorkSemantics } from '../domain/state-machine';
|
|
25
|
+
import { normalizeWorkObjectiveRelationIds, validateWorkObjectiveRelationShape } from '../domain/objective-graph';
|
|
35
26
|
import {
|
|
27
|
+
WORK_PHASES,
|
|
36
28
|
type EvidenceRef,
|
|
37
29
|
type CompletionOutcome,
|
|
38
30
|
type DispatchState,
|
|
@@ -43,7 +35,8 @@ import {
|
|
|
43
35
|
type WorkReconciliationRecord,
|
|
44
36
|
type SubmittedWorkOperation,
|
|
45
37
|
type WorkContract,
|
|
46
|
-
type
|
|
38
|
+
type WorkSemanticView,
|
|
39
|
+
type SemanticWorkState,
|
|
47
40
|
type WorkRisk,
|
|
48
41
|
type WorkKind,
|
|
49
42
|
type WorkPhase,
|
|
@@ -54,8 +47,8 @@ import {
|
|
|
54
47
|
executionPlacementForWork,
|
|
55
48
|
isDirectEditWorkCompletionReceipt,
|
|
56
49
|
isRepositoryCompletionReceipt,
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
isTerminalSemanticWorkState,
|
|
51
|
+
TERMINAL_SEMANTIC_WORK_STATES,
|
|
59
52
|
semanticScopeRefForWork,
|
|
60
53
|
} from '../domain/types';
|
|
61
54
|
|
|
@@ -64,10 +57,9 @@ export type { WorkContractStoreLocation, WorkContractStoreOptions } from '../por
|
|
|
64
57
|
|
|
65
58
|
export type CreateWorkContractInput = Omit<
|
|
66
59
|
WorkContract,
|
|
67
|
-
'schemaVersion' | '
|
|
60
|
+
'schemaVersion' | 'semanticState' | 'createdAt' | 'updatedAt' | 'risk' | 'workKind' | 'dispatchState' | 'evidenceState' | 'completionOutcome' | 'phase' | 'phaseEvidence' | 'completionReceipt' | 'evidenceRefs' | 'handoffRefs' | 'suggestedNextActions' | 'policyDecisions' | 'checkRefs' | 'implementationReviews' | 'reconciliations' | 'worktreePolicy' | 'evidencePolicy' | 'approvalPolicy' | 'recoveryPolicy'
|
|
68
61
|
> & {
|
|
69
62
|
risk?: WorkRisk;
|
|
70
|
-
status?: WorkContractStatus;
|
|
71
63
|
createdAt?: string;
|
|
72
64
|
updatedAt?: string;
|
|
73
65
|
workKind?: WorkKind;
|
|
@@ -82,16 +74,32 @@ export type CreateWorkContractInput = Omit<
|
|
|
82
74
|
policyDecisions?: PolicyDecision[];
|
|
83
75
|
checkRefs?: VerificationRecord[];
|
|
84
76
|
reconciliations?: WorkReconciliationRecord[];
|
|
85
|
-
driver?: WorkContract['driver'];
|
|
86
77
|
worktreePolicy?: WorkContract['worktreePolicy'];
|
|
87
78
|
evidencePolicy?: WorkContract['evidencePolicy'];
|
|
88
79
|
approvalPolicy?: WorkContract['approvalPolicy'];
|
|
89
80
|
recoveryPolicy?: WorkContract['recoveryPolicy'];
|
|
81
|
+
/**
|
|
82
|
+
* Legacy ingress-only fields, accepted only so a pre-20261004 frozen MCP
|
|
83
|
+
* client can still submit without the retired fields being recreated on the
|
|
84
|
+
* persisted Work contract.
|
|
85
|
+
*
|
|
86
|
+
* Replacement owner: concrete mutation scope belongs to the EditSession (and
|
|
87
|
+
* the execution resource bound to it); a Work carries semantic scope and
|
|
88
|
+
* evidence only. Bounded consumers: frozen MCP clients whose advertised
|
|
89
|
+
* `work_prepare` schema still includes `allowed_paths`. Removal condition:
|
|
90
|
+
* delete these fields (and the matching `AcceptSubmittedWorkInput` pair) as
|
|
91
|
+
* soon as no supported client sends them; a non-empty value is never
|
|
92
|
+
* enforced and must never be treated as authorization evidence.
|
|
93
|
+
*/
|
|
94
|
+
allowedPaths?: string[];
|
|
95
|
+
forbiddenPaths?: string[];
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
export interface ListWorkContractOptions extends WorkContractStoreOptions {
|
|
93
|
-
|
|
99
|
+
state?: SemanticWorkState | 'active' | 'all';
|
|
94
100
|
limit?: number;
|
|
101
|
+
/** Explicit pagination for complete internal historical inventory. */
|
|
102
|
+
offset?: number;
|
|
95
103
|
detailLevel?: 'summary' | 'detail' | 'raw';
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -112,12 +120,61 @@ export interface ActiveWorkCandidateSnapshot {
|
|
|
112
120
|
invalid: InvalidActiveWorkCandidate[];
|
|
113
121
|
}
|
|
114
122
|
|
|
123
|
+
export interface WorkContractLineageSnapshot {
|
|
124
|
+
contracts: WorkContract[];
|
|
125
|
+
invalid: Array<{
|
|
126
|
+
workId: string;
|
|
127
|
+
requirementId?: string;
|
|
128
|
+
semanticParentWorkId?: string;
|
|
129
|
+
dependsOnWorkIds?: string[];
|
|
130
|
+
parentWorkId?: string;
|
|
131
|
+
predecessorWorkId?: string;
|
|
132
|
+
supersedes: string[];
|
|
133
|
+
supersededBy?: string;
|
|
134
|
+
error: string;
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface WorkSemanticRevisionRecord extends WorkSemanticView {
|
|
139
|
+
schemaVersion: 1;
|
|
140
|
+
recordedAt: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface CreateWorkSemanticInput {
|
|
144
|
+
workId: string;
|
|
145
|
+
objective: string;
|
|
146
|
+
requestId?: string;
|
|
147
|
+
requirementId?: string;
|
|
148
|
+
requirementRevision?: number;
|
|
149
|
+
planId?: string;
|
|
150
|
+
planRevision?: number;
|
|
151
|
+
semanticParentWorkId?: string;
|
|
152
|
+
dependsOnWorkIds?: string[];
|
|
153
|
+
requestedBy?: 'chatgpt' | 'user' | 'system' | 'scheduler';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ReviseWorkSemanticInput {
|
|
157
|
+
expectedRevision: number;
|
|
158
|
+
objective?: string;
|
|
159
|
+
state?: SemanticWorkState;
|
|
160
|
+
requirementRevision?: number;
|
|
161
|
+
planRevision?: number;
|
|
162
|
+
semanticParentWorkId?: string;
|
|
163
|
+
dependsOnWorkIds?: string[];
|
|
164
|
+
resultRefs?: string[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface WorkSemanticRevisionStore {
|
|
168
|
+
schemaVersion: 1;
|
|
169
|
+
records: WorkSemanticRevisionRecord[];
|
|
170
|
+
}
|
|
171
|
+
|
|
115
172
|
export interface WorkContractSummary {
|
|
116
173
|
workId: string;
|
|
117
174
|
repoId: string;
|
|
118
|
-
|
|
175
|
+
/** The one thin authored Work state. */
|
|
176
|
+
state: SemanticWorkState;
|
|
119
177
|
phase: WorkContract['phase'];
|
|
120
|
-
status: WorkContractStatus;
|
|
121
178
|
objective: string;
|
|
122
179
|
updatedAt: string;
|
|
123
180
|
handoffCount: number;
|
|
@@ -135,15 +192,21 @@ function nowIso(options: WorkContractStoreOptions): string {
|
|
|
135
192
|
return options.now?.() ?? new Date().toISOString();
|
|
136
193
|
}
|
|
137
194
|
|
|
195
|
+
export function workContractStoreScopeKey(location: WorkContractStoreLocation): string {
|
|
196
|
+
const key = location.scopeKey?.trim() || location.repoId?.trim() || '';
|
|
197
|
+
if (!key) throw new Error('WORK_STORE_SCOPE_REQUIRED: controllerHome requires scopeKey or repoId');
|
|
198
|
+
return key;
|
|
199
|
+
}
|
|
200
|
+
|
|
138
201
|
export function workContractRoot(location: WorkContractStoreLocation): string {
|
|
139
202
|
if (location.root) {
|
|
140
203
|
mkdirSync(location.root, { recursive: true });
|
|
141
204
|
return location.root;
|
|
142
205
|
}
|
|
143
|
-
if (!location.controllerHome
|
|
144
|
-
throw new Error('work contract store requires either root or controllerHome + repoId');
|
|
206
|
+
if (!location.controllerHome) {
|
|
207
|
+
throw new Error('work contract store requires either root or controllerHome + scopeKey/repoId');
|
|
145
208
|
}
|
|
146
|
-
const root = join(
|
|
209
|
+
const root = join(scopedOperationRoot(location.controllerHome, workContractStoreScopeKey(location)), 'work-contracts');
|
|
147
210
|
mkdirSync(root, { recursive: true });
|
|
148
211
|
return root;
|
|
149
212
|
}
|
|
@@ -153,40 +216,98 @@ export function workContractStorePath(location: WorkContractStoreLocation): stri
|
|
|
153
216
|
}
|
|
154
217
|
|
|
155
218
|
export function emptyWorkContractStore(updatedAt: string): WorkContractStore {
|
|
156
|
-
return { schemaVersion:
|
|
219
|
+
return { schemaVersion: 4, updatedAt, contracts: [] };
|
|
157
220
|
}
|
|
158
221
|
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
222
|
+
function currentWorkSemanticRevision(work: WorkContract): number {
|
|
223
|
+
const revision = Number(work.semanticRevision);
|
|
224
|
+
return Number.isInteger(revision) && revision > 0 ? revision : 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Resolve the one thin semantic Work state. An explicit authored state is
|
|
229
|
+
* authoritative; a row that predates explicit semantic state derives only its
|
|
230
|
+
* terminal projection from mechanically recorded completion/cancellation.
|
|
231
|
+
* Execution/verification/review/delivery/cleanup vocabulary is never a semantic
|
|
232
|
+
* state: a mechanically blocked or failed Work stays semantically `open` until
|
|
233
|
+
* an explicit CAS close or cancellation.
|
|
234
|
+
*/
|
|
235
|
+
export function semanticWorkState(work: Pick<WorkContract, 'semanticState'>): SemanticWorkState {
|
|
236
|
+
return work.semanticState;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function workSemanticView(work: WorkContract): WorkSemanticView {
|
|
240
|
+
const resultRefs = [...new Set((work.semanticResultRefs ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
|
|
241
|
+
return {
|
|
242
|
+
workId: work.workId,
|
|
243
|
+
revision: currentWorkSemanticRevision(work),
|
|
244
|
+
semanticScope: semanticScopeRefForWork(work),
|
|
245
|
+
objective: work.objective,
|
|
246
|
+
state: semanticWorkState(work),
|
|
247
|
+
...(work.requirementId?.trim() ? { requirementId: work.requirementId.trim() } : {}),
|
|
248
|
+
...(Number.isInteger(work.requirementRevision) && Number(work.requirementRevision) > 0 ? { requirementRevision: Number(work.requirementRevision) } : {}),
|
|
249
|
+
...(work.planId?.trim() ? { planId: work.planId.trim() } : {}),
|
|
250
|
+
...(Number.isInteger(work.planRevision) && Number(work.planRevision) > 0 ? { planRevision: Number(work.planRevision) } : {}),
|
|
251
|
+
...(work.semanticParentWorkId?.trim() ? { semanticParentWorkId: work.semanticParentWorkId.trim() } : {}),
|
|
252
|
+
...(normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds).length > 0 ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds) } : {}),
|
|
253
|
+
resultRefs,
|
|
254
|
+
createdAt: work.createdAt,
|
|
255
|
+
updatedAt: work.semanticUpdatedAt ?? work.createdAt,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function workSemanticRevisionKey(workId: string, revision: number): string {
|
|
260
|
+
return `${sanitizeFileComponent(workId)}-r${revision}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function validateWorkObjectiveRelationIntegrity(candidate: WorkContract, contracts: readonly WorkContract[]): void {
|
|
264
|
+
validateWorkObjectiveRelationShape(candidate);
|
|
265
|
+
const parent = candidate.semanticParentWorkId?.trim();
|
|
266
|
+
const dependencies = normalizeWorkObjectiveRelationIds(candidate.dependsOnWorkIds);
|
|
267
|
+
if (!parent && dependencies.length === 0) return;
|
|
268
|
+
const byId = new Map(contracts.map((contract) => [contract.workId, contract] as const));
|
|
269
|
+
byId.set(candidate.workId, candidate);
|
|
270
|
+
for (const relatedWorkId of [...(parent ? [parent] : []), ...dependencies]) {
|
|
271
|
+
const related = byId.get(relatedWorkId);
|
|
272
|
+
if (!related) throw new Error(`WORK_OBJECTIVE_RELATION_NOT_FOUND: ${relatedWorkId}`);
|
|
273
|
+
if ((related.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`WORK_OBJECTIVE_RELATION_TARGET_NOT_PRIMARY: ${relatedWorkId}`);
|
|
274
|
+
}
|
|
275
|
+
const seenParents = new Set<string>();
|
|
276
|
+
let parentCursor = parent;
|
|
277
|
+
while (parentCursor) {
|
|
278
|
+
if (parentCursor === candidate.workId) throw new Error('WORK_SEMANTIC_PARENT_CYCLE');
|
|
279
|
+
if (seenParents.has(parentCursor)) throw new Error('WORK_SEMANTIC_PARENT_TARGET_CYCLE');
|
|
280
|
+
seenParents.add(parentCursor);
|
|
281
|
+
parentCursor = byId.get(parentCursor)?.semanticParentWorkId?.trim();
|
|
282
|
+
}
|
|
283
|
+
const dependencyReachesCandidate = (workId: string, visiting: Set<string>): boolean => {
|
|
284
|
+
if (workId === candidate.workId) return true;
|
|
285
|
+
if (visiting.has(workId)) return false;
|
|
286
|
+
visiting.add(workId);
|
|
287
|
+
const work = byId.get(workId);
|
|
288
|
+
if (!work) return false;
|
|
289
|
+
return normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds)
|
|
290
|
+
.some((dependency) => dependencyReachesCandidate(dependency, visiting));
|
|
291
|
+
};
|
|
292
|
+
for (const dependency of dependencies) {
|
|
293
|
+
if (dependencyReachesCandidate(dependency, new Set())) throw new Error('WORK_DEPENDENCY_CYCLE');
|
|
294
|
+
}
|
|
167
295
|
}
|
|
168
296
|
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
if (status === 'running') return 'running';
|
|
172
|
-
if (status === 'blocked') return 'blocked';
|
|
173
|
-
return 'terminal';
|
|
297
|
+
function workSemanticRevisionStorePath(options: WorkContractStoreOptions): string {
|
|
298
|
+
return join(workContractRoot(options), 'semantic-revisions.json');
|
|
174
299
|
}
|
|
175
300
|
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
if (status === 'completed' || status === 'failed' || status === 'cancelled') return 'cleanup';
|
|
179
|
-
return 'implementation';
|
|
301
|
+
function readWorkSemanticRevisionStore(options: WorkContractStoreOptions): WorkSemanticRevisionStore {
|
|
302
|
+
return readJsonFile<WorkSemanticRevisionStore>(workSemanticRevisionStorePath(options), { schemaVersion: 1, records: [] });
|
|
180
303
|
}
|
|
181
304
|
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
if (status === 'completed') return 'partial';
|
|
185
|
-
return 'none';
|
|
305
|
+
function initialLifecycleForNewWork(): Pick<WorkContract, 'phase' | 'dispatchState' | 'evidenceState'> {
|
|
306
|
+
return { phase: 'implementation', dispatchState: 'not_dispatched', evidenceState: 'none' };
|
|
186
307
|
}
|
|
187
308
|
|
|
188
309
|
function initialPhaseEvidenceForNewWork(
|
|
189
|
-
input: Pick<WorkContract, 'phase' | '
|
|
310
|
+
input: Pick<WorkContract, 'phase' | 'evidenceRefs' | 'updatedAt'> & { phaseState?: WorkPhaseEvidenceState },
|
|
190
311
|
): WorkPhaseEvidenceMap {
|
|
191
312
|
const currentIndex = phaseIndex(input.phase);
|
|
192
313
|
return Object.fromEntries((['implementation', 'verification', 'review', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => {
|
|
@@ -195,13 +316,7 @@ function initialPhaseEvidenceForNewWork(
|
|
|
195
316
|
? 'satisfied'
|
|
196
317
|
: index > currentIndex
|
|
197
318
|
? 'pending'
|
|
198
|
-
: input.
|
|
199
|
-
? 'failed'
|
|
200
|
-
: input.status === 'cancelled'
|
|
201
|
-
? 'skipped'
|
|
202
|
-
: input.status === 'blocked' || input.status === 'ready'
|
|
203
|
-
? 'blocked'
|
|
204
|
-
: 'active';
|
|
319
|
+
: input.phaseState ?? 'active';
|
|
205
320
|
return [phase, {
|
|
206
321
|
state,
|
|
207
322
|
source: 'recorded' as const,
|
|
@@ -209,7 +324,7 @@ function initialPhaseEvidenceForNewWork(
|
|
|
209
324
|
? `Canonical Work was admitted after phase ${phase}.`
|
|
210
325
|
: index > currentIndex
|
|
211
326
|
? `Waiting for Work phase ${phase}.`
|
|
212
|
-
: `Canonical Work admitted in ${phase}
|
|
327
|
+
: `Canonical Work admitted in ${phase}.`,
|
|
213
328
|
evidenceRefs: index <= currentIndex ? input.evidenceRefs.slice(0, 20) : [],
|
|
214
329
|
recordedAt: input.updatedAt,
|
|
215
330
|
} satisfies WorkPhaseEvidence];
|
|
@@ -217,7 +332,7 @@ function initialPhaseEvidenceForNewWork(
|
|
|
217
332
|
}
|
|
218
333
|
|
|
219
334
|
function legacyPhaseEvidence(
|
|
220
|
-
contract: Pick<WorkContract, 'phase' | '
|
|
335
|
+
contract: Pick<WorkContract, 'phase' | 'evidenceRefs' | 'completionReceipt' | 'updatedAt'> & { legacyStatus?: 'open' | 'running' | 'blocked' | 'ready' | 'completed' | 'failed' | 'cancelled' },
|
|
221
336
|
source: WorkPhaseEvidence['source'] = 'legacy_inferred',
|
|
222
337
|
): WorkPhaseEvidenceMap {
|
|
223
338
|
const currentIndex = phaseIndex(contract.phase);
|
|
@@ -226,8 +341,8 @@ function legacyPhaseEvidence(
|
|
|
226
341
|
const index = phaseIndex(phase);
|
|
227
342
|
let state: WorkPhaseEvidenceState = index < currentIndex ? 'satisfied' : index === currentIndex ? 'active' : 'pending';
|
|
228
343
|
if (completedByReceipt) state = phase === 'review' ? 'skipped' : 'satisfied';
|
|
229
|
-
else if (contract.
|
|
230
|
-
else if (contract.
|
|
344
|
+
else if (contract.legacyStatus === 'failed' && phase === contract.phase) state = 'failed';
|
|
345
|
+
else if (contract.legacyStatus === 'cancelled' && phase === contract.phase) state = 'skipped';
|
|
231
346
|
const receiptId = contract.completionReceipt && (phase === 'delivery' || phase === 'cleanup')
|
|
232
347
|
? contract.completionReceipt.receiptId
|
|
233
348
|
: undefined;
|
|
@@ -248,26 +363,31 @@ function legacyPhaseEvidence(
|
|
|
248
363
|
})) as WorkPhaseEvidenceMap;
|
|
249
364
|
}
|
|
250
365
|
|
|
251
|
-
function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string
|
|
252
|
-
// A caller-provided root is a test/portable compatibility store.
|
|
253
|
-
//
|
|
254
|
-
|
|
366
|
+
function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string } {
|
|
367
|
+
// A caller-provided root is a test/portable compatibility store. Runtime
|
|
368
|
+
// control-plane state uses one explicit scope key: semantic scope for authored
|
|
369
|
+
// Work, repository scope for legacy/repository execution Work.
|
|
370
|
+
return Boolean(!options.root && options.controllerHome?.trim() && (options.scopeKey?.trim() || options.repoId?.trim()));
|
|
255
371
|
}
|
|
256
372
|
|
|
257
373
|
function migrateLegacyWorkContract(legacy: WorkContract): WorkContract {
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
const
|
|
374
|
+
// One-way schema migration only: v1-v3 rows may still carry retired status.
|
|
375
|
+
// v4 strips it permanently and keeps semanticState as the only Work lifecycle state.
|
|
376
|
+
// Removal condition: once supported Controller Homes contain no schema<4 Work rows for one full release boundary, delete legacyStatus handling entirely.
|
|
377
|
+
const legacyStatus = (legacy as WorkContract & { status?: 'open' | 'running' | 'blocked' | 'ready' | 'completed' | 'failed' | 'cancelled' }).status;
|
|
378
|
+
const semanticState: SemanticWorkState = legacy.semanticState
|
|
379
|
+
?? (legacyStatus === 'completed' ? 'completed' : legacyStatus === 'cancelled' ? 'cancelled' : 'open');
|
|
380
|
+
// Phase is a mechanical checkpoint projection only. A legacy row without a
|
|
381
|
+
// persisted phase is admitted at the neutral first checkpoint; terminal
|
|
382
|
+
// status never advances phase, and status is never a phase-transition authority.
|
|
263
383
|
const phase = legacy.completionReceipt
|
|
264
384
|
? 'cleanup'
|
|
265
|
-
:
|
|
385
|
+
: semanticState === 'completed'
|
|
266
386
|
? 'delivery'
|
|
267
|
-
: legacy.phase ??
|
|
387
|
+
: legacy.phase ?? 'implementation';
|
|
268
388
|
const legacyDefaults = legacyPhaseEvidence({
|
|
269
389
|
phase,
|
|
270
|
-
|
|
390
|
+
legacyStatus,
|
|
271
391
|
evidenceRefs: legacy.evidenceRefs ?? [],
|
|
272
392
|
completionReceipt: legacy.completionReceipt,
|
|
273
393
|
updatedAt: legacy.updatedAt,
|
|
@@ -303,54 +423,92 @@ function migrateLegacyWorkContract(legacy: WorkContract): WorkContract {
|
|
|
303
423
|
},
|
|
304
424
|
}
|
|
305
425
|
: legacyDefaults;
|
|
426
|
+
const {
|
|
427
|
+
status: _retiredStatus,
|
|
428
|
+
allowedPaths: _retiredAllowedPaths,
|
|
429
|
+
forbiddenPaths: _retiredForbiddenPaths,
|
|
430
|
+
...legacyWithoutRetiredFields
|
|
431
|
+
} = legacy as WorkContract & { status?: unknown; allowedPaths?: unknown; forbiddenPaths?: unknown };
|
|
432
|
+
void _retiredStatus;
|
|
433
|
+
void _retiredAllowedPaths;
|
|
434
|
+
void _retiredForbiddenPaths;
|
|
435
|
+
// Before Work kind and completion outcome were made a single typed contract,
|
|
436
|
+
// delivery-reconciliation records retained their operational label even after
|
|
437
|
+
// a repository receipt proved that they advanced the target revision. The
|
|
438
|
+
// current contract rightly rejects that combination. Preserve the receipt and
|
|
439
|
+
// semantic terminal state, but migrate the obsolete label to the current
|
|
440
|
+
// repository-change classification so terminal history remains readable and
|
|
441
|
+
// cannot poison scheduler relay discovery forever.
|
|
442
|
+
const workKind = legacy.workKind === 'reconciliation'
|
|
443
|
+
&& legacy.completionOutcome === 'completed_changed'
|
|
444
|
+
&& legacy.completionReceipt
|
|
445
|
+
&& isRepositoryCompletionReceipt(legacy.completionReceipt)
|
|
446
|
+
? 'repository_change' as const
|
|
447
|
+
: legacy.workKind ?? 'repository_change';
|
|
306
448
|
return validateWorkSemantics({
|
|
307
|
-
...
|
|
308
|
-
schemaVersion:
|
|
449
|
+
...legacyWithoutRetiredFields,
|
|
450
|
+
schemaVersion: 4,
|
|
309
451
|
scopeRef: semanticScopeRefForWork(legacy),
|
|
310
452
|
executionPlacement: executionPlacementForWork(legacy),
|
|
311
|
-
|
|
453
|
+
semanticState,
|
|
454
|
+
semanticRevision: legacy.semanticRevision && legacy.semanticRevision > 0 ? legacy.semanticRevision : 1,
|
|
455
|
+
semanticUpdatedAt: legacy.semanticUpdatedAt ?? legacy.updatedAt,
|
|
312
456
|
phase,
|
|
313
457
|
phaseEvidence,
|
|
314
458
|
risk: legacy.risk ?? 'medium',
|
|
315
|
-
workKind
|
|
316
|
-
dispatchState: legacy.dispatchState ??
|
|
317
|
-
evidenceState: legacy.evidenceState ??
|
|
318
|
-
suggestedNextActions:
|
|
459
|
+
workKind,
|
|
460
|
+
dispatchState: legacy.dispatchState ?? 'not_dispatched',
|
|
461
|
+
evidenceState: legacy.evidenceState ?? 'none',
|
|
462
|
+
suggestedNextActions: suggestedActionsForSemanticState(semanticState, legacy.suggestedNextActions ?? []),
|
|
319
463
|
implementationReviews: legacy.implementationReviews ?? [],
|
|
320
464
|
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
465
|
});
|
|
325
466
|
}
|
|
326
467
|
|
|
327
468
|
function validateCanonicalWorkContract(contract: WorkContract): WorkContract {
|
|
328
|
-
if (contract.schemaVersion !==
|
|
469
|
+
if (contract.schemaVersion !== 4) {
|
|
329
470
|
throw new Error(`WORK_CONTRACT_SCHEMA_MIGRATION_REQUIRED: ${contract.workId}:schema=${String(contract.schemaVersion)}`);
|
|
330
471
|
}
|
|
331
472
|
return validateWorkSemantics(contract);
|
|
332
473
|
}
|
|
333
474
|
|
|
334
|
-
function storedWorkContractNeedsMigration(contract: WorkContract): boolean {
|
|
475
|
+
function storedWorkContractNeedsMigration(contract: WorkContract, persistedSchemaVersion: number = contract.schemaVersion): boolean {
|
|
335
476
|
const phaseEvidence = contract.phaseEvidence as Partial<WorkPhaseEvidenceMap> | undefined;
|
|
336
477
|
// Some pre-review-checkpoint rows were stamped with the current outer
|
|
337
478
|
// schema while omitting only `review`. That exact historical shape remains
|
|
338
479
|
// migration-compatible, but it must be persisted once rather than inferred
|
|
339
480
|
// repeatedly by the normal read path.
|
|
340
|
-
const legacyReviewGap =
|
|
481
|
+
const legacyReviewGap = persistedSchemaVersion === 3
|
|
341
482
|
&& phaseEvidence
|
|
342
483
|
&& !phaseEvidence.review
|
|
343
484
|
&& phaseEvidence.implementation
|
|
344
485
|
&& phaseEvidence.verification
|
|
345
486
|
&& phaseEvidence.delivery
|
|
346
487
|
&& phaseEvidence.cleanup;
|
|
347
|
-
return
|
|
488
|
+
return persistedSchemaVersion !== 4
|
|
489
|
+
|| contract.semanticState === undefined
|
|
490
|
+
|| Object.prototype.hasOwnProperty.call(contract as object, 'status')
|
|
491
|
+
|| Object.prototype.hasOwnProperty.call(contract as object, 'allowedPaths')
|
|
492
|
+
|| Object.prototype.hasOwnProperty.call(contract as object, 'forbiddenPaths')
|
|
493
|
+
|| Boolean(legacyReviewGap);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function workContractAtPersistedSchema(contract: WorkContract, persistedSchemaVersion: number): WorkContract {
|
|
497
|
+
return contract.schemaVersion === persistedSchemaVersion
|
|
498
|
+
? contract
|
|
499
|
+
: { ...contract, schemaVersion: persistedSchemaVersion as WorkContract['schemaVersion'] };
|
|
348
500
|
}
|
|
349
501
|
|
|
350
|
-
function canonicalizeStoredWorkContract(contract: WorkContract): WorkContract {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
502
|
+
function canonicalizeStoredWorkContract(contract: WorkContract, persistedSchemaVersion: number = contract.schemaVersion): WorkContract {
|
|
503
|
+
// The control-plane record envelope is the persisted schema authority. Some
|
|
504
|
+
// historical writers advanced the payload first, leaving it stamped v4 under
|
|
505
|
+
// a v3 envelope. Read it as the envelope declares so the one-way migration
|
|
506
|
+
// repairs the whole stored row rather than accepting a partially migrated
|
|
507
|
+
// contract indefinitely.
|
|
508
|
+
const persisted = workContractAtPersistedSchema(contract, persistedSchemaVersion);
|
|
509
|
+
return storedWorkContractNeedsMigration(persisted, persistedSchemaVersion)
|
|
510
|
+
? migrateLegacyWorkContract(persisted)
|
|
511
|
+
: validateCanonicalWorkContract(persisted);
|
|
354
512
|
}
|
|
355
513
|
|
|
356
514
|
/**
|
|
@@ -363,44 +521,45 @@ export function canonicalizeWorkContractForAuthority(contract: WorkContract): Wo
|
|
|
363
521
|
}
|
|
364
522
|
|
|
365
523
|
function normalizeWorkContractStore(store: WorkContractStore): WorkContractStore {
|
|
366
|
-
// v1
|
|
367
|
-
//
|
|
368
|
-
|
|
524
|
+
// v1-v3 rows are a one-way schema migration into v4. v4 rows are validated as-is;
|
|
525
|
+
// retired status is never inferred or exposed during normal reads.
|
|
526
|
+
// Removal condition: delete this migration branch after supported Controller Homes report zero schema<4 Work rows across one full release boundary.
|
|
527
|
+
return { schemaVersion: 4, updatedAt: store.updatedAt, contracts: store.contracts.map((contract) => canonicalizeStoredWorkContract(contract)) };
|
|
369
528
|
}
|
|
370
529
|
|
|
371
530
|
export function readWorkContractStore(options: WorkContractStoreOptions): WorkContractStore {
|
|
372
531
|
if (!sqliteBacked(options)) {
|
|
373
532
|
const raw = readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
|
|
374
533
|
const normalized = normalizeWorkContractStore(raw);
|
|
375
|
-
if (raw.schemaVersion !==
|
|
534
|
+
if (raw.schemaVersion !== 4 || raw.contracts.some((contract) => storedWorkContractNeedsMigration(contract))) {
|
|
376
535
|
writeJsonAtomic(workContractStorePath(options), normalized);
|
|
377
536
|
}
|
|
378
537
|
return normalized;
|
|
379
538
|
}
|
|
380
539
|
const records = listControlPlaneRecords<WorkContract>(options.controllerHome, {
|
|
381
540
|
namespace: 'work_contract',
|
|
382
|
-
scope: options
|
|
541
|
+
scope: workContractStoreScopeKey(options),
|
|
383
542
|
limit: 5_000,
|
|
384
543
|
});
|
|
385
544
|
if (records.length > 0) {
|
|
386
545
|
const normalized = normalizeWorkContractStore({
|
|
387
|
-
schemaVersion:
|
|
546
|
+
schemaVersion: 4,
|
|
388
547
|
updatedAt: records[0]?.updatedAt ?? nowIso(options),
|
|
389
|
-
contracts: records.map((record) => record.value),
|
|
548
|
+
contracts: records.map((record) => workContractAtPersistedSchema(record.value, record.schemaVersion)),
|
|
390
549
|
});
|
|
391
550
|
const legacyRows = records
|
|
392
551
|
.map((record, index) => ({ record, contract: normalized.contracts[index]! }))
|
|
393
|
-
.filter(({ record }) => storedWorkContractNeedsMigration(record.value));
|
|
552
|
+
.filter(({ record }) => storedWorkContractNeedsMigration(record.value, record.schemaVersion));
|
|
394
553
|
if (legacyRows.length > 0) {
|
|
395
554
|
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
396
555
|
for (const { record, contract } of legacyRows) {
|
|
397
556
|
writeControlPlaneRecordWithinTransaction(database, {
|
|
398
557
|
namespace: 'work_contract',
|
|
399
|
-
scope: options
|
|
558
|
+
scope: workContractStoreScopeKey(options),
|
|
400
559
|
key: contract.workId,
|
|
401
|
-
schemaVersion:
|
|
560
|
+
schemaVersion: 4,
|
|
402
561
|
value: contract,
|
|
403
|
-
action: '
|
|
562
|
+
action: 'work_contract_schema_v4_migrated',
|
|
404
563
|
expectedRevision: record.revision,
|
|
405
564
|
});
|
|
406
565
|
}
|
|
@@ -414,7 +573,7 @@ export function readWorkContractStore(options: WorkContractStoreOptions): WorkCo
|
|
|
414
573
|
const legacyRecord = readControlPlaneRecord<WorkContractStore>(
|
|
415
574
|
options.controllerHome,
|
|
416
575
|
'work_contract_store',
|
|
417
|
-
options
|
|
576
|
+
workContractStoreScopeKey(options),
|
|
418
577
|
'index',
|
|
419
578
|
);
|
|
420
579
|
const legacy = legacyRecord?.value
|
|
@@ -423,12 +582,12 @@ export function readWorkContractStore(options: WorkContractStoreOptions): WorkCo
|
|
|
423
582
|
if (normalized.contracts.length > 0) {
|
|
424
583
|
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
425
584
|
for (const contract of normalized.contracts) {
|
|
426
|
-
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options
|
|
585
|
+
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', workContractStoreScopeKey(options), contract.workId)) continue;
|
|
427
586
|
writeControlPlaneRecordWithinTransaction(database, {
|
|
428
587
|
namespace: 'work_contract',
|
|
429
|
-
scope: options
|
|
588
|
+
scope: workContractStoreScopeKey(options),
|
|
430
589
|
key: contract.workId,
|
|
431
|
-
schemaVersion:
|
|
590
|
+
schemaVersion: 4,
|
|
432
591
|
value: contract,
|
|
433
592
|
action: 'work_contract_legacy_import',
|
|
434
593
|
expectedRevision: null,
|
|
@@ -439,6 +598,65 @@ export function readWorkContractStore(options: WorkContractStoreOptions): WorkCo
|
|
|
439
598
|
return normalized;
|
|
440
599
|
}
|
|
441
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Read valid Work rows without allowing an unrelated corrupt historical row to
|
|
603
|
+
* poison a lineage-scoped ControllerRound projection. Invalid rows retain only
|
|
604
|
+
* their identity and explicit lineage references so callers can fail closed if
|
|
605
|
+
* the malformed row may belong to the requested lineage.
|
|
606
|
+
*/
|
|
607
|
+
export function readWorkContractLineageSnapshot(options: WorkContractStoreOptions): WorkContractLineageSnapshot {
|
|
608
|
+
if (!sqliteBacked(options)) {
|
|
609
|
+
const store = readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
|
|
610
|
+
const contracts: WorkContract[] = [];
|
|
611
|
+
const invalid: WorkContractLineageSnapshot['invalid'] = [];
|
|
612
|
+
for (const raw of store.contracts) {
|
|
613
|
+
try {
|
|
614
|
+
contracts.push(canonicalizeStoredWorkContract(raw));
|
|
615
|
+
} catch (error) {
|
|
616
|
+
invalid.push(lineageInvalidWork(raw, error));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return { contracts, invalid };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const records = listControlPlaneRecords<WorkContract>(options.controllerHome, {
|
|
623
|
+
namespace: 'work_contract',
|
|
624
|
+
scope: workContractStoreScopeKey(options),
|
|
625
|
+
limit: 5_000,
|
|
626
|
+
});
|
|
627
|
+
if (records.length === 0) return { contracts: readWorkContractStore(options).contracts, invalid: [] };
|
|
628
|
+
|
|
629
|
+
const contracts: WorkContract[] = [];
|
|
630
|
+
const invalid: WorkContractLineageSnapshot['invalid'] = [];
|
|
631
|
+
for (const record of records) {
|
|
632
|
+
try {
|
|
633
|
+
contracts.push(canonicalizeStoredWorkContract(record.value, record.schemaVersion));
|
|
634
|
+
} catch (error) {
|
|
635
|
+
invalid.push(lineageInvalidWork(record.value, error, record.key));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { contracts, invalid };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function lineageInvalidWork(
|
|
642
|
+
raw: WorkContract,
|
|
643
|
+
error: unknown,
|
|
644
|
+
recordKey?: string,
|
|
645
|
+
): WorkContractLineageSnapshot['invalid'][number] {
|
|
646
|
+
const clean = (id: unknown): string | undefined => typeof id === 'string' && id.trim() ? id.trim() : undefined;
|
|
647
|
+
return {
|
|
648
|
+
workId: clean(raw.workId) ?? recordKey ?? '',
|
|
649
|
+
...(clean(raw.requirementId) ? { requirementId: clean(raw.requirementId) } : {}),
|
|
650
|
+
...(clean(raw.semanticParentWorkId) ? { semanticParentWorkId: clean(raw.semanticParentWorkId) } : {}),
|
|
651
|
+
dependsOnWorkIds: Array.isArray(raw.dependsOnWorkIds) ? raw.dependsOnWorkIds.map(clean).filter((id): id is string => Boolean(id)) : [],
|
|
652
|
+
...(clean(raw.parentWorkId) ? { parentWorkId: clean(raw.parentWorkId) } : {}),
|
|
653
|
+
...(clean(raw.predecessorWorkId) ? { predecessorWorkId: clean(raw.predecessorWorkId) } : {}),
|
|
654
|
+
supersedes: Array.isArray(raw.supersedes) ? raw.supersedes.map(clean).filter((id): id is string => Boolean(id)) : [],
|
|
655
|
+
...(clean(raw.supersededBy) ? { supersededBy: clean(raw.supersededBy) } : {}),
|
|
656
|
+
error: error instanceof Error ? error.message : String(error),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
442
660
|
export function writeWorkContractStore(options: WorkContractStoreOptions, store: WorkContractStore): WorkContractStore {
|
|
443
661
|
if (!sqliteBacked(options)) {
|
|
444
662
|
writeJsonAtomic(workContractStorePath(options), store);
|
|
@@ -450,7 +668,7 @@ export function writeWorkContractStore(options: WorkContractStoreOptions, store:
|
|
|
450
668
|
const current = readControlPlaneRecordWithinTransaction<WorkContract>(
|
|
451
669
|
database,
|
|
452
670
|
'work_contract',
|
|
453
|
-
options
|
|
671
|
+
workContractStoreScopeKey(options),
|
|
454
672
|
contract.workId,
|
|
455
673
|
);
|
|
456
674
|
// SQLite is authoritative per Work row. Aggregate-store callers may still
|
|
@@ -459,9 +677,9 @@ export function writeWorkContractStore(options: WorkContractStoreOptions, store:
|
|
|
459
677
|
if (current && JSON.stringify(current.value) === JSON.stringify(value)) continue;
|
|
460
678
|
writeControlPlaneRecordWithinTransaction(database, {
|
|
461
679
|
namespace: 'work_contract',
|
|
462
|
-
scope: options
|
|
680
|
+
scope: workContractStoreScopeKey(options),
|
|
463
681
|
key: contract.workId,
|
|
464
|
-
schemaVersion:
|
|
682
|
+
schemaVersion: 4,
|
|
465
683
|
value,
|
|
466
684
|
action: 'work_contract_write',
|
|
467
685
|
expectedRevision: current?.revision ?? null,
|
|
@@ -475,44 +693,65 @@ function withWorkContractStoreWrite<T>(options: WorkContractStoreOptions, operat
|
|
|
475
693
|
if (!sqliteBacked(options)) return operation();
|
|
476
694
|
return withControllerLock(
|
|
477
695
|
options.controllerHome,
|
|
478
|
-
{ scope: 'global', resource: `work-contract-store-${sanitizeFileComponent(options
|
|
479
|
-
`work-contract-store:${options
|
|
696
|
+
{ scope: 'global', resource: `work-contract-store-${sanitizeFileComponent(workContractStoreScopeKey(options))}` },
|
|
697
|
+
`work-contract-store:${workContractStoreScopeKey(options)}`,
|
|
480
698
|
operation,
|
|
481
699
|
undefined,
|
|
482
700
|
5_000,
|
|
483
701
|
);
|
|
484
702
|
}
|
|
485
703
|
|
|
486
|
-
|
|
487
|
-
function assertCanonicalWorkAdmissionAllowed(
|
|
704
|
+
function withExactWorkContractWrite<T>(
|
|
488
705
|
options: WorkContractStoreOptions,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
706
|
+
workIdInput: string,
|
|
707
|
+
operation: () => T,
|
|
708
|
+
): T {
|
|
709
|
+
if (!sqliteBacked(options)) return operation();
|
|
710
|
+
const workId = sanitizeFileComponent(workIdInput);
|
|
711
|
+
return withControllerLock(
|
|
493
712
|
options.controllerHome,
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
713
|
+
{ scope: 'global', resource: `work-contract-${workId}` },
|
|
714
|
+
`work-contract:${workId}`,
|
|
715
|
+
operation,
|
|
716
|
+
undefined,
|
|
717
|
+
5_000,
|
|
718
|
+
);
|
|
499
719
|
}
|
|
500
720
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
721
|
+
|
|
722
|
+
export function createWorkSemanticContext(options: WorkContractStoreOptions, input: CreateWorkSemanticInput): WorkContract {
|
|
723
|
+
const semanticOptions = options.scopeKey?.trim() || !options.repoId?.trim()
|
|
724
|
+
? options
|
|
725
|
+
: { ...options, scopeKey: options.repoId.trim() };
|
|
726
|
+
return createWorkContract(semanticOptions, {
|
|
727
|
+
workId: input.workId,
|
|
728
|
+
repoId: options.repoId?.trim() ?? '',
|
|
729
|
+
objective: input.objective,
|
|
730
|
+
acceptanceCriteria: [],
|
|
731
|
+
constraints: {},
|
|
732
|
+
workKind: 'investigation',
|
|
733
|
+
lifecycleRole: 'primary',
|
|
734
|
+
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
735
|
+
checks: [],
|
|
736
|
+
...(input.requirementId?.trim() ? { requirementId: input.requirementId.trim() } : {}),
|
|
737
|
+
...(Number.isInteger(input.requirementRevision) ? { requirementRevision: input.requirementRevision } : {}),
|
|
738
|
+
...(input.planId?.trim() ? { planId: input.planId.trim() } : {}),
|
|
739
|
+
...(Number.isInteger(input.planRevision) ? { planRevision: input.planRevision } : {}),
|
|
740
|
+
...(input.semanticParentWorkId !== undefined ? { semanticParentWorkId: input.semanticParentWorkId.trim() || undefined } : {}),
|
|
741
|
+
...(input.dependsOnWorkIds !== undefined ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds) } : {}),
|
|
742
|
+
...(input.requestId?.trim() ? { requestId: input.requestId.trim() } : {}),
|
|
743
|
+
});
|
|
509
744
|
}
|
|
510
745
|
|
|
511
746
|
export function createWorkContract(options: WorkContractStoreOptions, input: CreateWorkContractInput): WorkContract {
|
|
512
|
-
|
|
513
|
-
|
|
747
|
+
// Thin semantic Work is authored context. Repository execution and concurrency
|
|
748
|
+
// are fenced by placement/resource ownership rather than a global migration gate.
|
|
749
|
+
const runtimeInput = input as CreateWorkContractInput & Record<string, unknown>;
|
|
750
|
+
if (Object.prototype.hasOwnProperty.call(runtimeInput, 'status')) throw new Error('WORK_RETIRED_FIELD_REJECTED: status');
|
|
751
|
+
for (const field of ['semanticState', 'semanticRevision', 'semanticUpdatedAt']) {
|
|
752
|
+
if (Object.prototype.hasOwnProperty.call(runtimeInput, field)) throw new Error(`WORK_SEMANTIC_FIELDS_REQUIRE_REVISION_API: ${field}`);
|
|
514
753
|
}
|
|
515
|
-
if (input.
|
|
754
|
+
if (input.completionReceipt || input.completionOutcome) {
|
|
516
755
|
throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
517
756
|
}
|
|
518
757
|
const create = (): WorkContract => {
|
|
@@ -521,7 +760,7 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
521
760
|
const predecessorWorkId = input.predecessorWorkId ? sanitizeFileComponent(input.predecessorWorkId) : undefined;
|
|
522
761
|
if (predecessorWorkId === workId) throw new Error('WORK_PREDECESSOR_SELF_REFERENCE');
|
|
523
762
|
const contract: WorkContract = validateWorkSemantics({
|
|
524
|
-
schemaVersion:
|
|
763
|
+
schemaVersion: 4,
|
|
525
764
|
workId,
|
|
526
765
|
scopeRef: semanticScopeRefForWork({
|
|
527
766
|
workId,
|
|
@@ -530,7 +769,8 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
530
769
|
planId: input.planId,
|
|
531
770
|
planStepId: input.planStepId,
|
|
532
771
|
}),
|
|
533
|
-
executionPlacement: input.executionPlacement
|
|
772
|
+
executionPlacement: input.executionPlacement
|
|
773
|
+
?? (input.repoId?.trim() ? executionPlacementForWork({ repoId: input.repoId, checkoutId: input.checkoutId }) : undefined),
|
|
534
774
|
repoId: input.repoId,
|
|
535
775
|
checkoutId: input.checkoutId,
|
|
536
776
|
principalId: input.principalId,
|
|
@@ -538,39 +778,41 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
538
778
|
baseRevision: input.baseRevision,
|
|
539
779
|
repositoryBaseState: input.repositoryBaseState,
|
|
540
780
|
workspaceFingerprint: input.workspaceFingerprint,
|
|
541
|
-
routeDecisionFingerprint: input.routeDecisionFingerprint,
|
|
542
|
-
routeDecision: input.routeDecision,
|
|
543
|
-
mode: input.mode,
|
|
544
781
|
objective: input.objective.slice(0, 2_000),
|
|
782
|
+
semanticRevision: 1,
|
|
783
|
+
semanticUpdatedAt: input.updatedAt ?? at,
|
|
784
|
+
semanticState: 'open',
|
|
545
785
|
acceptanceCriteria: (input.acceptanceCriteria ?? []).slice(0, 20).map((item) => item.slice(0, 500)),
|
|
546
|
-
constraints: input.constraints ?? {
|
|
786
|
+
constraints: input.constraints ?? {},
|
|
547
787
|
risk: input.risk ?? 'medium',
|
|
548
788
|
engineeringContext: input.engineeringContext,
|
|
549
789
|
workKind: input.workKind ?? 'repository_change',
|
|
550
790
|
lifecycleRole: input.lifecycleRole ?? 'primary',
|
|
551
791
|
parentWorkId: input.parentWorkId?.trim() || undefined,
|
|
792
|
+
semanticParentWorkId: input.semanticParentWorkId?.trim() || undefined,
|
|
793
|
+
dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds),
|
|
552
794
|
predecessorWorkId: predecessorWorkId && predecessorWorkId !== 'unknown' ? predecessorWorkId : undefined,
|
|
553
795
|
supersedes: input.supersedes?.map((value) => sanitizeFileComponent(value)).filter((value) => value !== 'unknown').slice(0, 50),
|
|
554
796
|
supersededBy: input.supersededBy ? sanitizeFileComponent(input.supersededBy) : undefined,
|
|
555
797
|
supersessionReason: input.supersessionReason?.trim().slice(0, 500),
|
|
556
|
-
dispatchState: input.dispatchState ?? initialLifecycleForNewWork(
|
|
557
|
-
evidenceState: input.evidenceState ?? initialLifecycleForNewWork(
|
|
798
|
+
dispatchState: input.dispatchState ?? initialLifecycleForNewWork().dispatchState,
|
|
799
|
+
evidenceState: input.evidenceState ?? initialLifecycleForNewWork().evidenceState,
|
|
558
800
|
completionOutcome: input.completionOutcome,
|
|
559
|
-
phase: input.phase ?? initialLifecycleForNewWork(
|
|
801
|
+
phase: input.phase ?? initialLifecycleForNewWork().phase,
|
|
560
802
|
phaseEvidence: initialPhaseEvidenceForNewWork({
|
|
561
|
-
phase: input.phase ?? initialLifecycleForNewWork(
|
|
562
|
-
status: input.status ?? 'open',
|
|
803
|
+
phase: input.phase ?? initialLifecycleForNewWork().phase,
|
|
563
804
|
evidenceRefs: input.evidenceRefs ?? [],
|
|
564
805
|
updatedAt: input.updatedAt ?? at,
|
|
565
806
|
}),
|
|
566
807
|
completionReceipt: input.completionReceipt,
|
|
567
|
-
status: input.status ?? 'open',
|
|
568
808
|
createdAt: at,
|
|
569
809
|
updatedAt: input.updatedAt ?? at,
|
|
570
810
|
issueId: input.issueId,
|
|
571
811
|
taskId: input.taskId,
|
|
572
812
|
requirementId: input.requirementId,
|
|
813
|
+
requirementRevision: input.requirementRevision,
|
|
573
814
|
planId: input.planId,
|
|
815
|
+
planRevision: input.planRevision,
|
|
574
816
|
planStepId: input.planStepId,
|
|
575
817
|
planSourceRevision: input.planSourceRevision,
|
|
576
818
|
scopeSummary: input.scopeSummary?.slice(0, 1_000),
|
|
@@ -580,13 +822,12 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
580
822
|
actualChangedPaths: [...new Set(input.scopeEvidence.actualChangedPaths)].slice(0, 500),
|
|
581
823
|
recordedAt: input.scopeEvidence.recordedAt,
|
|
582
824
|
} : undefined,
|
|
583
|
-
allowedPaths: (input.allowedPaths ?? []).slice(0, 50),
|
|
584
|
-
forbiddenPaths: (input.forbiddenPaths ?? []).slice(0, 50),
|
|
585
825
|
checks: (input.checks ?? []).slice(0, 30),
|
|
586
|
-
driver: input.driver ?? defaultDriver(input.mode),
|
|
587
826
|
worktreePolicy: input.worktreePolicy ?? {
|
|
588
|
-
required: input.
|
|
589
|
-
reason: input.
|
|
827
|
+
required: input.constraints?.workspaceMode === 'isolated',
|
|
828
|
+
reason: input.constraints?.workspaceMode === 'isolated'
|
|
829
|
+
? 'Typed workspace placement requires an isolated worktree.'
|
|
830
|
+
: undefined,
|
|
590
831
|
},
|
|
591
832
|
evidencePolicy: input.evidencePolicy ?? {
|
|
592
833
|
defaultDetailLevel: 'summary',
|
|
@@ -616,14 +857,21 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
616
857
|
|
|
617
858
|
if (sqliteBacked(options)) {
|
|
618
859
|
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
619
|
-
|
|
860
|
+
const scope = workContractStoreScopeKey(options);
|
|
861
|
+
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', scope, contract.workId)) {
|
|
620
862
|
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
621
863
|
}
|
|
864
|
+
if (contract.semanticParentWorkId?.trim() || contract.dependsOnWorkIds?.length) {
|
|
865
|
+
const relationContracts = listControlPlaneRecordsWithinTransaction<WorkContract>(database, {
|
|
866
|
+
namespace: 'work_contract', scope, limit: 5_000,
|
|
867
|
+
}).map((record) => canonicalizeStoredWorkContract(record.value));
|
|
868
|
+
validateWorkObjectiveRelationIntegrity(contract, relationContracts);
|
|
869
|
+
}
|
|
622
870
|
writeControlPlaneRecordWithinTransaction(database, {
|
|
623
871
|
namespace: 'work_contract',
|
|
624
|
-
scope
|
|
872
|
+
scope,
|
|
625
873
|
key: contract.workId,
|
|
626
|
-
schemaVersion:
|
|
874
|
+
schemaVersion: 4,
|
|
627
875
|
value: contract,
|
|
628
876
|
action: 'work_contract_created',
|
|
629
877
|
expectedRevision: null,
|
|
@@ -635,15 +883,19 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
635
883
|
if (store.contracts.some((existing) => existing.workId === contract.workId)) {
|
|
636
884
|
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
637
885
|
}
|
|
886
|
+
if (contract.semanticParentWorkId?.trim() || contract.dependsOnWorkIds?.length) {
|
|
887
|
+
validateWorkObjectiveRelationIntegrity(contract, store.contracts);
|
|
888
|
+
}
|
|
638
889
|
const nextStore: WorkContractStore = {
|
|
639
|
-
schemaVersion:
|
|
890
|
+
schemaVersion: 4,
|
|
640
891
|
updatedAt: contract.updatedAt,
|
|
641
892
|
contracts: [contract, ...store.contracts],
|
|
642
893
|
};
|
|
643
894
|
writeWorkContractStore(options, nextStore);
|
|
644
895
|
return contract;
|
|
645
896
|
};
|
|
646
|
-
|
|
897
|
+
const relationAware = Boolean(input.semanticParentWorkId?.trim() || input.dependsOnWorkIds?.length);
|
|
898
|
+
return withExactWorkContractWrite(options, input.workId, () => relationAware ? withWorkContractStoreWrite(options, create) : create());
|
|
647
899
|
}
|
|
648
900
|
|
|
649
901
|
interface WorkRequestIndexRecord {
|
|
@@ -700,7 +952,7 @@ export function acceptSubmittedWorkContract(
|
|
|
700
952
|
if (parentWorkId) {
|
|
701
953
|
const parent = getWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, parentWorkId);
|
|
702
954
|
if (!parent) throw new Error(`PARENT_WORK_NOT_FOUND: ${parentWorkId}`);
|
|
703
|
-
if (
|
|
955
|
+
if (semanticWorkState(parent) !== 'open') throw new Error(`PARENT_WORK_TERMINAL: ${parentWorkId}:${semanticWorkState(parent)}`);
|
|
704
956
|
if ((parent.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`PARENT_WORK_NOT_PRIMARY: ${parentWorkId}`);
|
|
705
957
|
}
|
|
706
958
|
const lockId = createHash('sha256').update(requestId).digest('hex').slice(0, 24);
|
|
@@ -719,30 +971,18 @@ export function acceptSubmittedWorkContract(
|
|
|
719
971
|
repoId: input.repoId,
|
|
720
972
|
principalId: input.principalId,
|
|
721
973
|
controllerInstanceId: input.controllerInstanceId,
|
|
722
|
-
mode: input.mode ?? 'direct_control',
|
|
723
974
|
lifecycleRole: 'execution_child',
|
|
724
975
|
parentWorkId,
|
|
725
976
|
objective: (input.objective ?? `Typed operation ${input.operation.name}`).slice(0, 2_000),
|
|
726
977
|
acceptanceCriteria: input.acceptanceCriteria ?? [],
|
|
727
978
|
workKind: input.workKind,
|
|
728
979
|
risk: input.risk,
|
|
729
|
-
constraints: input.constraints ?? {
|
|
730
|
-
allowedPaths: input.allowedPaths ?? [],
|
|
731
|
-
forbiddenPaths: input.forbiddenPaths ?? [],
|
|
980
|
+
constraints: input.constraints ?? {},
|
|
732
981
|
checks: input.checks ?? [],
|
|
733
982
|
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
734
|
-
status: 'open',
|
|
735
983
|
requestId,
|
|
736
984
|
submittedOperation: input.operation,
|
|
737
|
-
|
|
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
|
-
}],
|
|
985
|
+
suggestedNextActions: [],
|
|
746
986
|
});
|
|
747
987
|
writeJsonAtomic(recordPath, { requestId, repoId: input.repoId, workId: contract.workId, semanticKey, createdAt: contract.createdAt } satisfies WorkRequestIndexRecord);
|
|
748
988
|
return { contract, deduplicated: false };
|
|
@@ -750,24 +990,36 @@ export function acceptSubmittedWorkContract(
|
|
|
750
990
|
}
|
|
751
991
|
|
|
752
992
|
export function isCurrentWorkContract(contract: WorkContract): boolean {
|
|
753
|
-
return
|
|
993
|
+
return semanticWorkState(contract) === 'open'
|
|
754
994
|
&& !contract.supersededBy?.trim()
|
|
755
995
|
&& contract.workKind !== 'superseded'
|
|
756
996
|
&& contract.completionOutcome !== 'superseded';
|
|
757
997
|
}
|
|
758
998
|
|
|
759
999
|
export function listWorkContracts(options: ListWorkContractOptions): WorkContract[] {
|
|
760
|
-
const
|
|
761
|
-
const status = options.status ?? 'active';
|
|
1000
|
+
const state = options.state ?? 'active';
|
|
762
1001
|
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 50), 100));
|
|
1002
|
+
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
1003
|
+
if (state === 'active' && offset > 0) throw new Error('WORK_ACTIVE_PAGINATION_UNSUPPORTED');
|
|
1004
|
+
if (state === 'active' && sqliteBacked(options)) {
|
|
1005
|
+
const active = readActiveWorkCandidates({ ...options, limit });
|
|
1006
|
+
if (active.invalid.length > 0) {
|
|
1007
|
+
// Preserve the aggregate list API's fail-closed semantics. Callers that
|
|
1008
|
+
// intentionally need row-isolated corruption diagnostics use
|
|
1009
|
+
// readActiveWorkCandidates() directly and can inspect every invalid row.
|
|
1010
|
+
throw new Error(active.invalid[0]!.error);
|
|
1011
|
+
}
|
|
1012
|
+
return active.contracts;
|
|
1013
|
+
}
|
|
1014
|
+
const store = readWorkContractStore(options);
|
|
763
1015
|
return store.contracts
|
|
764
1016
|
.filter((contract) => {
|
|
765
|
-
if (
|
|
766
|
-
if (
|
|
767
|
-
return contract
|
|
1017
|
+
if (state === 'all') return true;
|
|
1018
|
+
if (state === 'active') return isCurrentWorkContract(contract);
|
|
1019
|
+
return semanticWorkState(contract) === state;
|
|
768
1020
|
})
|
|
769
1021
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
770
|
-
.slice(
|
|
1022
|
+
.slice(offset, offset + limit);
|
|
771
1023
|
}
|
|
772
1024
|
|
|
773
1025
|
function workExecutionSemanticScopeKeys(contract: WorkContract): string[] {
|
|
@@ -784,13 +1036,12 @@ function workExecutionSemanticScopeKeys(contract: WorkContract): string[] {
|
|
|
784
1036
|
function workExecutionIsolation(contract: WorkContract): 'shared' | 'isolated' {
|
|
785
1037
|
return contract.worktreePolicy?.required === true
|
|
786
1038
|
|| contract.constraints?.workspaceMode === 'isolated'
|
|
787
|
-
|| contract.constraints?.requireWorktree === true
|
|
788
1039
|
? 'isolated'
|
|
789
1040
|
: 'shared';
|
|
790
1041
|
}
|
|
791
1042
|
|
|
792
1043
|
function rawWorkMayBeCurrent(contract: WorkContract): boolean {
|
|
793
|
-
return
|
|
1044
|
+
return semanticWorkState(contract) === 'open'
|
|
794
1045
|
&& !contract.supersededBy?.trim()
|
|
795
1046
|
&& contract.workKind !== 'superseded'
|
|
796
1047
|
&& contract.completionOutcome !== 'superseded';
|
|
@@ -805,7 +1056,7 @@ function rawWorkMayBeCurrent(contract: WorkContract): boolean {
|
|
|
805
1056
|
*/
|
|
806
1057
|
export function initializeWorkCandidateIndex(controllerHome: string): void {
|
|
807
1058
|
initializeControlPlanePayloadTextExclusionIndex(controllerHome, {
|
|
808
|
-
namespace: 'work_contract', field: '
|
|
1059
|
+
namespace: 'work_contract', field: 'semanticState', excludedValues: TERMINAL_SEMANTIC_WORK_STATES,
|
|
809
1060
|
});
|
|
810
1061
|
}
|
|
811
1062
|
|
|
@@ -814,24 +1065,36 @@ export function readActiveWorkCandidates(
|
|
|
814
1065
|
): ActiveWorkCandidateSnapshot {
|
|
815
1066
|
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 1_000), 1_000));
|
|
816
1067
|
if (!sqliteBacked(options)) {
|
|
817
|
-
return { contracts: listWorkContracts({ ...options,
|
|
1068
|
+
return { contracts: listWorkContracts({ ...options, state: 'active', limit }), invalid: [] };
|
|
818
1069
|
}
|
|
1070
|
+
const requestedScope = workContractStoreScopeKey(options);
|
|
819
1071
|
const records = listControlPlaneRecordsExcludingPayloadTextValues<WorkContract>(options.controllerHome, {
|
|
820
1072
|
namespace: 'work_contract',
|
|
821
|
-
scope:
|
|
822
|
-
field: '
|
|
823
|
-
excludedValues:
|
|
1073
|
+
scope: requestedScope,
|
|
1074
|
+
field: 'semanticState',
|
|
1075
|
+
excludedValues: TERMINAL_SEMANTIC_WORK_STATES,
|
|
824
1076
|
limit: 5_000,
|
|
825
1077
|
});
|
|
1078
|
+
const semanticAuthorityWorkIds = requestedScope === SEMANTIC_SCOPE_KEY
|
|
1079
|
+
? new Set<string>()
|
|
1080
|
+
: new Set(listControlPlaneRecordKeys(options.controllerHome, {
|
|
1081
|
+
namespace: 'work_contract',
|
|
1082
|
+
scope: SEMANTIC_SCOPE_KEY,
|
|
1083
|
+
limit: 5_000,
|
|
1084
|
+
}));
|
|
826
1085
|
const contracts: WorkContract[] = [];
|
|
827
1086
|
const invalid: InvalidActiveWorkCandidate[] = [];
|
|
828
1087
|
const migrations: Array<{ record: (typeof records)[number]; contract: WorkContract }> = [];
|
|
829
1088
|
for (const record of records) {
|
|
1089
|
+
// Exact Work reads already prefer semantic scope over repository-scoped
|
|
1090
|
+
// compatibility rows. Collection reads must preserve the same one-authority
|
|
1091
|
+
// rule or a stale open compatibility shadow can resurrect terminal Work.
|
|
1092
|
+
if (semanticAuthorityWorkIds.has(record.key)) continue;
|
|
830
1093
|
const raw = record.value;
|
|
831
1094
|
if (!rawWorkMayBeCurrent(raw)) continue;
|
|
832
1095
|
try {
|
|
833
|
-
const normalized = canonicalizeStoredWorkContract(raw);
|
|
834
|
-
if (storedWorkContractNeedsMigration(raw)) migrations.push({ record, contract: normalized });
|
|
1096
|
+
const normalized = canonicalizeStoredWorkContract(raw, record.schemaVersion);
|
|
1097
|
+
if (storedWorkContractNeedsMigration(raw, record.schemaVersion)) migrations.push({ record, contract: normalized });
|
|
835
1098
|
if (isCurrentWorkContract(normalized)) contracts.push(normalized);
|
|
836
1099
|
} catch (error) {
|
|
837
1100
|
invalid.push({
|
|
@@ -852,11 +1115,11 @@ export function readActiveWorkCandidates(
|
|
|
852
1115
|
for (const { record, contract } of migrations) {
|
|
853
1116
|
writeControlPlaneRecordWithinTransaction(database, {
|
|
854
1117
|
namespace: 'work_contract',
|
|
855
|
-
scope: options
|
|
1118
|
+
scope: workContractStoreScopeKey(options),
|
|
856
1119
|
key: contract.workId,
|
|
857
|
-
schemaVersion:
|
|
1120
|
+
schemaVersion: 4,
|
|
858
1121
|
value: contract,
|
|
859
|
-
action: '
|
|
1122
|
+
action: 'work_contract_schema_v4_migrated',
|
|
860
1123
|
expectedRevision: record.revision,
|
|
861
1124
|
});
|
|
862
1125
|
}
|
|
@@ -867,35 +1130,201 @@ export function readActiveWorkCandidates(
|
|
|
867
1130
|
return { contracts: contracts.slice(0, limit), invalid };
|
|
868
1131
|
}
|
|
869
1132
|
|
|
1133
|
+
export function listWorkSemanticRevisionRecords(
|
|
1134
|
+
options: WorkContractStoreOptions,
|
|
1135
|
+
workId?: string,
|
|
1136
|
+
limit = 200,
|
|
1137
|
+
): WorkSemanticRevisionRecord[] {
|
|
1138
|
+
const normalizedId = workId ? sanitizeFileComponent(workId) : undefined;
|
|
1139
|
+
const boundedLimit = Math.max(1, Math.min(Math.trunc(limit), 1000));
|
|
1140
|
+
const authoritativeOptions = normalizedId
|
|
1141
|
+
? authoritativeExistingWorkStoreOptions(options, normalizedId)
|
|
1142
|
+
: options;
|
|
1143
|
+
const records = sqliteBacked(authoritativeOptions)
|
|
1144
|
+
? listControlPlaneRecords<WorkSemanticRevisionRecord>(authoritativeOptions.controllerHome, {
|
|
1145
|
+
namespace: 'work_semantic_revision', scope: workContractStoreScopeKey(authoritativeOptions), limit: 5_000,
|
|
1146
|
+
}).map((record) => record.value)
|
|
1147
|
+
: readWorkSemanticRevisionStore(authoritativeOptions).records;
|
|
1148
|
+
return records
|
|
1149
|
+
.filter((record) => !normalizedId || record.workId === normalizedId)
|
|
1150
|
+
.map((record) => ({
|
|
1151
|
+
...record,
|
|
1152
|
+
semanticScope: record.semanticScope ?? semanticScopeRefForWork({
|
|
1153
|
+
workId: record.workId,
|
|
1154
|
+
requirementId: record.requirementId,
|
|
1155
|
+
planId: record.planId,
|
|
1156
|
+
planStepId: undefined,
|
|
1157
|
+
}),
|
|
1158
|
+
}))
|
|
1159
|
+
.sort((left, right) => right.revision - left.revision)
|
|
1160
|
+
.slice(0, boundedLimit);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function authoritativeExistingWorkStoreOptions(
|
|
1164
|
+
options: WorkContractStoreOptions,
|
|
1165
|
+
workIdInput: string,
|
|
1166
|
+
): WorkContractStoreOptions {
|
|
1167
|
+
if (!sqliteBacked(options)) return options;
|
|
1168
|
+
const requestedScope = workContractStoreScopeKey(options);
|
|
1169
|
+
if (requestedScope === SEMANTIC_SCOPE_KEY) return options;
|
|
1170
|
+
const workId = sanitizeFileComponent(workIdInput);
|
|
1171
|
+
const semantic = readControlPlaneRecord<WorkContract>(
|
|
1172
|
+
options.controllerHome,
|
|
1173
|
+
'work_contract',
|
|
1174
|
+
SEMANTIC_SCOPE_KEY,
|
|
1175
|
+
workId,
|
|
1176
|
+
);
|
|
1177
|
+
return semantic ? { ...options, scopeKey: SEMANTIC_SCOPE_KEY } : options;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
export function reviseWorkSemanticContext(
|
|
1181
|
+
options: WorkContractStoreOptions,
|
|
1182
|
+
workIdInput: string,
|
|
1183
|
+
input: ReviseWorkSemanticInput,
|
|
1184
|
+
): WorkContract {
|
|
1185
|
+
if (!Number.isInteger(input.expectedRevision) || input.expectedRevision < 1) throw new Error('WORK_EXPECTED_REVISION_INVALID');
|
|
1186
|
+
return withExactWorkContractWrite(options, workIdInput, () => {
|
|
1187
|
+
const authoritativeOptions = authoritativeExistingWorkStoreOptions(options, workIdInput);
|
|
1188
|
+
return withWorkContractStoreWrite(authoritativeOptions, () => {
|
|
1189
|
+
const workId = sanitizeFileComponent(workIdInput);
|
|
1190
|
+
const relationRevisionRequested = input.semanticParentWorkId !== undefined || input.dependsOnWorkIds !== undefined;
|
|
1191
|
+
const applyRevision = (current: WorkContract, at: string, relationContracts: readonly WorkContract[] = []): WorkContract => {
|
|
1192
|
+
const semanticRevision = currentWorkSemanticRevision(current);
|
|
1193
|
+
if (semanticRevision !== input.expectedRevision) {
|
|
1194
|
+
throw new Error(`WORK_REVISION_CONFLICT:${workId}:expected=${input.expectedRevision}:actual=${semanticRevision}`);
|
|
1195
|
+
}
|
|
1196
|
+
const currentSemanticState = semanticWorkState(current);
|
|
1197
|
+
if (currentSemanticState !== 'open' && input.state === 'open') {
|
|
1198
|
+
throw new Error(`WORK_SEMANTIC_REOPEN_FORBIDDEN:${workId}:${currentSemanticState}`);
|
|
1199
|
+
}
|
|
1200
|
+
const objective = input.objective === undefined ? current.objective : String(input.objective).trim().slice(0, 2_000);
|
|
1201
|
+
if (!objective) throw new Error('WORK_OBJECTIVE_REQUIRED');
|
|
1202
|
+
const positiveRevision = (value: number | undefined, code: string): number | undefined => {
|
|
1203
|
+
if (value === undefined) return undefined;
|
|
1204
|
+
if (!Number.isInteger(value) || value < 1) throw new Error(code);
|
|
1205
|
+
return value;
|
|
1206
|
+
};
|
|
1207
|
+
const nextSemanticState = input.state ?? currentSemanticState;
|
|
1208
|
+
if (currentSemanticState !== 'open' && (input.semanticParentWorkId !== undefined || input.dependsOnWorkIds !== undefined)) {
|
|
1209
|
+
throw new Error(`WORK_OBJECTIVE_RELATION_TERMINAL_IMMUTABLE:${workId}:${currentSemanticState}`);
|
|
1210
|
+
}
|
|
1211
|
+
const next = validateWorkSemantics({
|
|
1212
|
+
...current,
|
|
1213
|
+
objective,
|
|
1214
|
+
semanticRevision: semanticRevision + 1,
|
|
1215
|
+
semanticUpdatedAt: at,
|
|
1216
|
+
semanticState: nextSemanticState,
|
|
1217
|
+
...(input.requirementRevision !== undefined ? { requirementRevision: positiveRevision(input.requirementRevision, 'WORK_REQUIREMENT_REVISION_INVALID') } : {}),
|
|
1218
|
+
...(input.planRevision !== undefined ? { planRevision: positiveRevision(input.planRevision, 'WORK_PLAN_REVISION_INVALID') } : {}),
|
|
1219
|
+
...(input.semanticParentWorkId !== undefined ? { semanticParentWorkId: input.semanticParentWorkId.trim() || undefined } : {}),
|
|
1220
|
+
...(input.dependsOnWorkIds !== undefined ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds) } : {}),
|
|
1221
|
+
...(input.resultRefs !== undefined ? { semanticResultRefs: [...new Set(input.resultRefs.map(String).map((value) => value.trim()).filter(Boolean))].slice(0, 100) } : {}),
|
|
1222
|
+
updatedAt: at,
|
|
1223
|
+
});
|
|
1224
|
+
if (relationRevisionRequested) validateWorkObjectiveRelationIntegrity(next, relationContracts);
|
|
1225
|
+
return next;
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
if (sqliteBacked(authoritativeOptions)) {
|
|
1229
|
+
return withControlPlaneTransaction(authoritativeOptions.controllerHome, (database) => {
|
|
1230
|
+
const scope = workContractStoreScopeKey(authoritativeOptions);
|
|
1231
|
+
const currentRecord = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', scope, workId);
|
|
1232
|
+
if (!currentRecord) throw new Error(`work contract not found: ${workId}`);
|
|
1233
|
+
const current = canonicalizeStoredWorkContract(currentRecord.value);
|
|
1234
|
+
const at = nowIso(authoritativeOptions);
|
|
1235
|
+
const relationContracts = relationRevisionRequested
|
|
1236
|
+
? listControlPlaneRecordsWithinTransaction<WorkContract>(database, {
|
|
1237
|
+
namespace: 'work_contract', scope, limit: 5_000,
|
|
1238
|
+
}).map((record) => canonicalizeStoredWorkContract(record.value))
|
|
1239
|
+
: [];
|
|
1240
|
+
const next = applyRevision(current, at, relationContracts);
|
|
1241
|
+
const semanticRevision = currentWorkSemanticRevision(current);
|
|
1242
|
+
const revisionKey = workSemanticRevisionKey(workId, semanticRevision);
|
|
1243
|
+
if (!readControlPlaneRecordWithinTransaction<WorkSemanticRevisionRecord>(database, 'work_semantic_revision', scope, revisionKey)) {
|
|
1244
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
1245
|
+
namespace: 'work_semantic_revision', scope, key: revisionKey, schemaVersion: 1,
|
|
1246
|
+
value: { schemaVersion: 1, ...workSemanticView(current), recordedAt: at },
|
|
1247
|
+
action: 'work_semantic_revision_archived', expectedRevision: null,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
return writeControlPlaneRecordWithinTransaction(database, {
|
|
1251
|
+
namespace: 'work_contract', scope, key: workId, schemaVersion: 4,
|
|
1252
|
+
value: next, action: 'work_semantic_revised', expectedRevision: currentRecord.revision,
|
|
1253
|
+
}).value;
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Semantic CAS validation must be side-effect free. The public read path may
|
|
1258
|
+
// persist a one-way legacy migration, so normalize the file-backed store only
|
|
1259
|
+
// in memory here and write it exactly once after the requested revision has
|
|
1260
|
+
// been validated successfully.
|
|
1261
|
+
const rawStore = readJsonFile<WorkContractStore>(
|
|
1262
|
+
workContractStorePath(authoritativeOptions),
|
|
1263
|
+
emptyWorkContractStore(nowIso(authoritativeOptions)),
|
|
1264
|
+
);
|
|
1265
|
+
const store = normalizeWorkContractStore(rawStore);
|
|
1266
|
+
const index = store.contracts.findIndex((contract) => contract.workId === workId);
|
|
1267
|
+
if (index < 0) throw new Error(`work contract not found: ${workId}`);
|
|
1268
|
+
const current = store.contracts[index]!;
|
|
1269
|
+
const at = nowIso(authoritativeOptions);
|
|
1270
|
+
const next = applyRevision(current, at, relationRevisionRequested ? store.contracts : []);
|
|
1271
|
+
const archived = { schemaVersion: 1 as const, ...workSemanticView(current), recordedAt: at };
|
|
1272
|
+
const history = readWorkSemanticRevisionStore(authoritativeOptions);
|
|
1273
|
+
if (!history.records.some((record) => record.workId === workId && record.revision === archived.revision)) {
|
|
1274
|
+
writeJsonAtomic(workSemanticRevisionStorePath(authoritativeOptions), { schemaVersion: 1, records: [...history.records, archived].slice(-5_000) });
|
|
1275
|
+
}
|
|
1276
|
+
const contracts = [...store.contracts];
|
|
1277
|
+
contracts[index] = next;
|
|
1278
|
+
writeWorkContractStore(authoritativeOptions, { schemaVersion: 4, updatedAt: at, contracts });
|
|
1279
|
+
return next;
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function readExactSqliteWorkContract(
|
|
1285
|
+
options: WorkContractStoreOptions & { controllerHome: string },
|
|
1286
|
+
scope: string,
|
|
1287
|
+
sanitizedId: string,
|
|
1288
|
+
): WorkContract | undefined {
|
|
1289
|
+
const exact = readControlPlaneRecord<WorkContract>(options.controllerHome, 'work_contract', scope, sanitizedId);
|
|
1290
|
+
if (!exact) return undefined;
|
|
1291
|
+
const canonical = canonicalizeStoredWorkContract(exact.value, exact.schemaVersion);
|
|
1292
|
+
if (storedWorkContractNeedsMigration(exact.value, exact.schemaVersion)) {
|
|
1293
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
1294
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
1295
|
+
namespace: 'work_contract',
|
|
1296
|
+
scope,
|
|
1297
|
+
key: canonical.workId,
|
|
1298
|
+
schemaVersion: 4,
|
|
1299
|
+
value: canonical,
|
|
1300
|
+
action: 'work_contract_schema_v4_migrated',
|
|
1301
|
+
expectedRevision: exact.revision,
|
|
1302
|
+
});
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return canonical;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
870
1308
|
export function getWorkContract(options: WorkContractStoreOptions, workId: string): WorkContract | undefined {
|
|
871
1309
|
const sanitizedId = sanitizeFileComponent(workId);
|
|
872
1310
|
if (!sqliteBacked(options)) {
|
|
873
1311
|
return readWorkContractStore(options).contracts.find((contract) => contract.workId === sanitizedId);
|
|
874
1312
|
}
|
|
875
|
-
const
|
|
876
|
-
if (
|
|
877
|
-
const
|
|
878
|
-
if (
|
|
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;
|
|
1313
|
+
const requestedScope = workContractStoreScopeKey(options);
|
|
1314
|
+
if (requestedScope !== SEMANTIC_SCOPE_KEY) {
|
|
1315
|
+
const semantic = readExactSqliteWorkContract(options, SEMANTIC_SCOPE_KEY, sanitizedId);
|
|
1316
|
+
if (semantic) return semantic;
|
|
892
1317
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1318
|
+
const exact = readExactSqliteWorkContract(options, requestedScope, sanitizedId);
|
|
1319
|
+
if (exact) return exact;
|
|
1320
|
+
|
|
1321
|
+
// Preserve the one-time legacy import path only while this requested scope has
|
|
1322
|
+
// no per-Work rows. Canonical semantic rows are checked first and never copied
|
|
1323
|
+
// into repository scope; repository-scoped rows remain a read-only compatibility
|
|
1324
|
+
// fallback until their consumers are retired.
|
|
896
1325
|
const hasPerWorkRows = listControlPlaneRecords<WorkContract>(options.controllerHome, {
|
|
897
1326
|
namespace: 'work_contract',
|
|
898
|
-
scope:
|
|
1327
|
+
scope: requestedScope,
|
|
899
1328
|
limit: 1,
|
|
900
1329
|
}).length > 0;
|
|
901
1330
|
if (hasPerWorkRows) return undefined;
|
|
@@ -949,13 +1378,13 @@ export function supersedeWorkContract(
|
|
|
949
1378
|
contracts[predecessorIndex] = predecessorNext;
|
|
950
1379
|
contracts[successorIndex] = successorNext;
|
|
951
1380
|
if (!sqliteBacked(options)) {
|
|
952
|
-
writeJsonAtomic(workContractStorePath(options), { schemaVersion:
|
|
1381
|
+
writeJsonAtomic(workContractStorePath(options), { schemaVersion: 4, updatedAt: at, contracts });
|
|
953
1382
|
} else {
|
|
954
1383
|
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
955
1384
|
for (const contract of [predecessorNext, successorNext]) {
|
|
956
|
-
const current = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options
|
|
1385
|
+
const current = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', workContractStoreScopeKey(options), contract.workId);
|
|
957
1386
|
if (!current) throw new Error(`WORK_LINEAGE_RECORD_MISSING: ${contract.workId}`);
|
|
958
|
-
writeControlPlaneRecordWithinTransaction(database, { namespace: 'work_contract', scope: options
|
|
1387
|
+
writeControlPlaneRecordWithinTransaction(database, { namespace: 'work_contract', scope: workContractStoreScopeKey(options), key: contract.workId, schemaVersion: 4, value: contract, action: 'work_contract_supersession_linked', expectedRevision: current.revision });
|
|
959
1388
|
}
|
|
960
1389
|
});
|
|
961
1390
|
}
|
|
@@ -967,9 +1396,8 @@ export function summarizeWorkContract(contract: WorkContract): WorkContractSumma
|
|
|
967
1396
|
return {
|
|
968
1397
|
workId: contract.workId,
|
|
969
1398
|
repoId: contract.repoId,
|
|
970
|
-
|
|
1399
|
+
state: semanticWorkState(contract),
|
|
971
1400
|
phase: contract.phase,
|
|
972
|
-
status: contract.status,
|
|
973
1401
|
objective: contract.objective.slice(0, 240),
|
|
974
1402
|
updatedAt: contract.updatedAt,
|
|
975
1403
|
handoffCount: contract.handoffRefs.length,
|
|
@@ -995,32 +1423,62 @@ function updateWorkContractInternal(
|
|
|
995
1423
|
mutation: WorkContractMutation,
|
|
996
1424
|
allowCompletionWrite: boolean,
|
|
997
1425
|
allowLifecycleWrite = false,
|
|
998
|
-
allowRetainedCancelledResume = false,
|
|
999
1426
|
allowImplementationReviewWrite = false,
|
|
1000
1427
|
allowPhaseRegression = false,
|
|
1428
|
+
atomicPlacementEffect?: (database: SqliteDatabase, contract: WorkContract) => void,
|
|
1001
1429
|
): WorkContract {
|
|
1002
|
-
return
|
|
1430
|
+
return withExactWorkContractWrite(options, workId, () => {
|
|
1431
|
+
const authoritativeOptions = authoritativeExistingWorkStoreOptions(options, workId);
|
|
1432
|
+
return withWorkContractStoreWrite(authoritativeOptions, () => {
|
|
1003
1433
|
const sanitizedId = sanitizeFileComponent(workId);
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1434
|
+
const exact = sqliteBacked(authoritativeOptions)
|
|
1435
|
+
? readControlPlaneRecord<WorkContract>(
|
|
1436
|
+
authoritativeOptions.controllerHome,
|
|
1437
|
+
'work_contract',
|
|
1438
|
+
workContractStoreScopeKey(authoritativeOptions),
|
|
1439
|
+
sanitizedId,
|
|
1440
|
+
)
|
|
1441
|
+
: undefined;
|
|
1442
|
+
const store = exact ? undefined : readWorkContractStore(authoritativeOptions);
|
|
1443
|
+
const index = store?.contracts.findIndex((contract) => contract.workId === sanitizedId) ?? -1;
|
|
1444
|
+
if (!exact && index < 0) throw new Error(`work contract not found: ${sanitizedId}`);
|
|
1445
|
+
const at = nowIso(authoritativeOptions);
|
|
1446
|
+
// A per-Work SQLite row is the mutable authority. Do not normalize every
|
|
1447
|
+
// sibling just to update this row: malformed historical evidence must stay
|
|
1448
|
+
// visible and fenced, but cannot prevent a different terminal Work from
|
|
1449
|
+
// recording its physical cleanup.
|
|
1450
|
+
const current = exact
|
|
1451
|
+
? canonicalizeStoredWorkContract(exact.value)
|
|
1452
|
+
: store!.contracts[index]!;
|
|
1453
|
+
const mutationPatch = typeof mutation === 'function' ? mutation(current, at) : mutation;
|
|
1454
|
+
if (!mutationPatch) return current;
|
|
1455
|
+
const placementChanged = (
|
|
1456
|
+
Object.prototype.hasOwnProperty.call(mutationPatch, 'checkoutId')
|
|
1457
|
+
&& mutationPatch.checkoutId !== current.checkoutId
|
|
1458
|
+
) || (
|
|
1459
|
+
Object.prototype.hasOwnProperty.call(mutationPatch, 'worktreeRef')
|
|
1460
|
+
&& mutationPatch.worktreeRef !== current.worktreeRef
|
|
1461
|
+
) || (
|
|
1462
|
+
Object.prototype.hasOwnProperty.call(mutationPatch, 'executionPlacement')
|
|
1463
|
+
&& JSON.stringify(mutationPatch.executionPlacement) !== JSON.stringify(current.executionPlacement)
|
|
1464
|
+
);
|
|
1465
|
+
// executionConcurrency is only a projection of the concrete placement that
|
|
1466
|
+
// existed when an execution attempt was admitted. A checkout/worktree move
|
|
1467
|
+
// makes that projection stale; clear it centrally unless the same atomic
|
|
1468
|
+
// mutation explicitly supplies a replacement projection for the new target.
|
|
1469
|
+
const patch = placementChanged && !Object.prototype.hasOwnProperty.call(mutationPatch, 'executionConcurrency')
|
|
1470
|
+
? { ...mutationPatch, executionConcurrency: undefined }
|
|
1471
|
+
: mutationPatch;
|
|
1472
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'status')) throw new Error('WORK_RETIRED_FIELD_REJECTED: status');
|
|
1473
|
+
for (const field of ['semanticState', 'semanticRevision', 'semanticUpdatedAt']) {
|
|
1474
|
+
if (!allowLifecycleWrite && Object.prototype.hasOwnProperty.call(patch, field)) {
|
|
1475
|
+
throw new Error(`WORK_SEMANTIC_FIELDS_REQUIRE_REVISION_API: ${field}`);
|
|
1476
|
+
}
|
|
1018
1477
|
}
|
|
1019
1478
|
const writesCompletionReceipt = Object.prototype.hasOwnProperty.call(patch, 'completionReceipt');
|
|
1020
1479
|
const changesCompletionOutcome = patch.completionOutcome !== undefined && patch.completionOutcome !== current.completionOutcome;
|
|
1021
1480
|
const writesPhase = Object.prototype.hasOwnProperty.call(patch, 'phase') || Object.prototype.hasOwnProperty.call(patch, 'phaseEvidence');
|
|
1022
1481
|
const writesLifecycle = writesPhase
|
|
1023
|
-
|| Object.prototype.hasOwnProperty.call(patch, 'status')
|
|
1024
1482
|
|| Object.prototype.hasOwnProperty.call(patch, 'dispatchState')
|
|
1025
1483
|
|| Object.prototype.hasOwnProperty.call(patch, 'evidenceState')
|
|
1026
1484
|
|| Object.prototype.hasOwnProperty.call(patch, 'workKind');
|
|
@@ -1029,19 +1487,16 @@ function updateWorkContractInternal(
|
|
|
1029
1487
|
if (!allowImplementationReviewWrite && writesImplementationReviews) throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRES_RECORD_API');
|
|
1030
1488
|
const projectedPhase = patch.phase ?? current.phase;
|
|
1031
1489
|
const projectedPhaseEvidence = patch.phaseEvidence ?? current.phaseEvidence;
|
|
1032
|
-
if (!allowCompletionWrite && (writesCompletionReceipt || changesCompletionOutcome
|
|
1490
|
+
if (!allowCompletionWrite && (writesCompletionReceipt || changesCompletionOutcome)) {
|
|
1033
1491
|
throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
1034
1492
|
}
|
|
1035
|
-
if (patch.status === 'completed' && !current.completionReceipt && !patch.completionReceipt) {
|
|
1036
|
-
throw new Error('WORK_COMPLETION_RECEIPT_REQUIRED');
|
|
1037
|
-
}
|
|
1038
1493
|
if (writesCompletionReceipt && patch.completionReceipt === undefined && current.completionReceipt) {
|
|
1039
1494
|
throw new Error('WORK_COMPLETION_RECEIPT_IMMUTABLE');
|
|
1040
1495
|
}
|
|
1041
1496
|
const next: WorkContract = validateWorkSemanticTransition(current, validateWorkSemantics({
|
|
1042
1497
|
...current,
|
|
1043
1498
|
...patch,
|
|
1044
|
-
schemaVersion:
|
|
1499
|
+
schemaVersion: 4,
|
|
1045
1500
|
workId: current.workId,
|
|
1046
1501
|
repoId: current.repoId,
|
|
1047
1502
|
createdAt: current.createdAt,
|
|
@@ -1055,206 +1510,85 @@ function updateWorkContractInternal(
|
|
|
1055
1510
|
completionOutcome: patch.completionOutcome ?? current.completionOutcome,
|
|
1056
1511
|
evidenceRefs: (patch.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs),
|
|
1057
1512
|
handoffRefs: (patch.handoffRefs ?? current.handoffRefs).slice(0, 20),
|
|
1058
|
-
suggestedNextActions:
|
|
1513
|
+
suggestedNextActions: suggestedActionsForSemanticState(current.semanticState, patch.suggestedNextActions ?? current.suggestedNextActions),
|
|
1059
1514
|
policyDecisions: (patch.policyDecisions ?? current.policyDecisions).slice(0, 20),
|
|
1060
1515
|
checkRefs: (patch.checkRefs ?? current.checkRefs).slice(0, 50),
|
|
1061
1516
|
implementationReviews: (patch.implementationReviews ?? current.implementationReviews ?? []).slice(-MAX_IMPLEMENTATION_REVIEW_HISTORY),
|
|
1062
1517
|
reconciliations: (patch.reconciliations ?? current.reconciliations ?? []).slice(0, 20),
|
|
1063
1518
|
objective: (patch.objective ?? current.objective).slice(0, 2_000),
|
|
1064
1519
|
continuationPrompt: (patch.continuationPrompt ?? current.continuationPrompt)?.slice(0, 2_000),
|
|
1065
|
-
}), {
|
|
1066
|
-
|
|
1520
|
+
}), { allowPhaseRegression });
|
|
1521
|
+
if (exact && sqliteBacked(authoritativeOptions)) {
|
|
1522
|
+
withControlPlaneTransaction(authoritativeOptions.controllerHome, (database) => {
|
|
1523
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
1524
|
+
namespace: 'work_contract',
|
|
1525
|
+
scope: workContractStoreScopeKey(authoritativeOptions),
|
|
1526
|
+
key: next.workId,
|
|
1527
|
+
schemaVersion: 4,
|
|
1528
|
+
value: next,
|
|
1529
|
+
action: 'work_contract_updated',
|
|
1530
|
+
expectedRevision: exact.revision,
|
|
1531
|
+
});
|
|
1532
|
+
atomicPlacementEffect?.(database, next);
|
|
1533
|
+
});
|
|
1534
|
+
return next;
|
|
1535
|
+
}
|
|
1536
|
+
if (atomicPlacementEffect) throw new Error('WORK_PLACEMENT_ATOMIC_SQLITE_REQUIRED');
|
|
1537
|
+
const contracts = [...store!.contracts];
|
|
1067
1538
|
contracts[index] = next;
|
|
1068
|
-
writeWorkContractStore(
|
|
1539
|
+
writeWorkContractStore(authoritativeOptions, { schemaVersion: 4, updatedAt: at, contracts });
|
|
1069
1540
|
return next;
|
|
1541
|
+
});
|
|
1070
1542
|
});
|
|
1071
1543
|
}
|
|
1072
1544
|
|
|
1073
1545
|
|
|
1074
1546
|
/**
|
|
1075
1547
|
* Build the only permitted in-place Plan rebind for an active Work: the same
|
|
1076
|
-
|
|
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(
|
|
1548
|
+
function cancellationPhaseEvidence(
|
|
1152
1549
|
current: WorkContract,
|
|
1153
|
-
input: {
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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);
|
|
1550
|
+
input: { summary: string; evidenceRefs?: EvidenceRef[]; recordedAt: string },
|
|
1551
|
+
): WorkPhaseEvidenceMap {
|
|
1552
|
+
const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
|
|
1553
|
+
const existing = current.phaseEvidence[current.phase];
|
|
1554
|
+
if (!['pending', 'active'].includes(existing.state)) return current.phaseEvidence;
|
|
1555
|
+
return {
|
|
1556
|
+
...current.phaseEvidence,
|
|
1557
|
+
[current.phase]: {
|
|
1558
|
+
state: 'skipped',
|
|
1559
|
+
source: 'recorded',
|
|
1560
|
+
summary: input.summary.trim().slice(0, 1_000) || 'Work cancelled.',
|
|
1561
|
+
evidenceRefs,
|
|
1562
|
+
recordedAt: input.recordedAt,
|
|
1563
|
+
},
|
|
1564
|
+
};
|
|
1204
1565
|
}
|
|
1205
1566
|
|
|
1206
1567
|
/**
|
|
1207
1568
|
* Retire technical Work authority when its owning Plan is no longer current.
|
|
1208
1569
|
*
|
|
1209
1570
|
* This is authority retirement, not history deletion. The cancelled Work and
|
|
1210
|
-
* its evidence stay durable for audit/recovery/retention, while
|
|
1571
|
+
* its evidence stay durable for audit/recovery/retention, while semantic state and
|
|
1211
1572
|
* dispatch become terminal immediately so schedulers, concurrency admission,
|
|
1212
1573
|
* and UI current-state projections cannot keep executing an obsolete Plan.
|
|
1213
1574
|
*/
|
|
1214
|
-
|
|
1575
|
+
function cancellationPhaseEvidence(
|
|
1215
1576
|
current: WorkContract,
|
|
1216
|
-
input: {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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,
|
|
1577
|
+
input: { summary: string; evidenceRefs?: EvidenceRef[]; recordedAt: string },
|
|
1578
|
+
): WorkPhaseEvidenceMap {
|
|
1579
|
+
const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
|
|
1580
|
+
const existing = current.phaseEvidence[current.phase];
|
|
1581
|
+
if (!['pending', 'active'].includes(existing.state)) return current.phaseEvidence;
|
|
1582
|
+
return {
|
|
1583
|
+
...current.phaseEvidence,
|
|
1584
|
+
[current.phase]: {
|
|
1585
|
+
state: 'skipped',
|
|
1586
|
+
source: 'recorded',
|
|
1587
|
+
summary: input.summary.trim().slice(0, 1_000) || 'Work cancelled.',
|
|
1249
1588
|
evidenceRefs,
|
|
1250
1589
|
recordedAt: input.recordedAt,
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
evidenceRefs,
|
|
1254
|
-
suggestedNextActions: [],
|
|
1255
|
-
updatedAt: input.recordedAt,
|
|
1256
|
-
});
|
|
1257
|
-
return validateWorkSemanticTransition(canonicalCurrent, next);
|
|
1590
|
+
},
|
|
1591
|
+
};
|
|
1258
1592
|
}
|
|
1259
1593
|
|
|
1260
1594
|
export type WorkContractMetadataPatch = Partial<Omit<
|
|
@@ -1264,7 +1598,9 @@ export type WorkContractMetadataPatch = Partial<Omit<
|
|
|
1264
1598
|
| 'repoId'
|
|
1265
1599
|
| 'createdAt'
|
|
1266
1600
|
| 'updatedAt'
|
|
1267
|
-
| '
|
|
1601
|
+
| 'semanticState'
|
|
1602
|
+
| 'semanticRevision'
|
|
1603
|
+
| 'semanticUpdatedAt'
|
|
1268
1604
|
| 'phase'
|
|
1269
1605
|
| 'phaseEvidence'
|
|
1270
1606
|
| 'dispatchState'
|
|
@@ -1282,18 +1618,31 @@ export function updateWorkContract(
|
|
|
1282
1618
|
): WorkContract {
|
|
1283
1619
|
return updateWorkContractInternal(options, workId, patch, false);
|
|
1284
1620
|
}
|
|
1621
|
+
export function updateWorkContractPlacementAtomically(
|
|
1622
|
+
options: WorkContractStoreOptions,
|
|
1623
|
+
workId: string,
|
|
1624
|
+
patch: Pick<WorkContractMetadataPatch, 'checkoutId' | 'worktreeRef' | 'executionPlacement' | 'worktreePolicy'>,
|
|
1625
|
+
effect: (database: SqliteDatabase, contract: WorkContract) => void,
|
|
1626
|
+
): WorkContract {
|
|
1627
|
+
// The WorkContract writer remains the sole semantic metadata authority.
|
|
1628
|
+
// An execution WorkHandle is committed inside the same SQLite transaction,
|
|
1629
|
+
// never as a second step that could leave conflicting checkout ownership.
|
|
1630
|
+
return updateWorkContractInternal(options, workId, patch, false, false, false, false, effect);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
|
|
1285
1634
|
|
|
1286
|
-
/** Explicit
|
|
1635
|
+
/** Explicit execution-classification transition when an open Work begins governed repository mutation. */
|
|
1287
1636
|
export function promoteWorkToRepositoryChange(
|
|
1288
1637
|
options: WorkContractStoreOptions,
|
|
1289
1638
|
workId: string,
|
|
1290
1639
|
): WorkContract {
|
|
1291
1640
|
return updateWorkContractInternal(options, workId, (current) => {
|
|
1292
|
-
if (
|
|
1641
|
+
if (semanticWorkState(current) !== 'open' || current.completionReceipt || current.completionOutcome) {
|
|
1293
1642
|
throw new Error(`WORK_KIND_PROMOTION_TERMINAL: ${workId}`);
|
|
1294
1643
|
}
|
|
1295
1644
|
if (current.workKind === 'repository_change') return undefined;
|
|
1296
|
-
if (
|
|
1645
|
+
if (!['investigation', 'local_effect', 'remote_effect'].includes(current.workKind)) {
|
|
1297
1646
|
throw new Error(`WORK_KIND_PROMOTION_INVALID: ${workId}:${current.workKind}`);
|
|
1298
1647
|
}
|
|
1299
1648
|
return { workKind: 'repository_change' };
|
|
@@ -1319,12 +1668,12 @@ export function activateWorkContract(
|
|
|
1319
1668
|
},
|
|
1320
1669
|
): WorkContract {
|
|
1321
1670
|
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1322
|
-
if (current.
|
|
1323
|
-
throw new Error(`WORK_ACTIVATION_TERMINAL: ${workId}:${current.
|
|
1671
|
+
if (current.semanticState !== 'open') {
|
|
1672
|
+
throw new Error(`WORK_ACTIVATION_TERMINAL: ${workId}:${current.semanticState}`);
|
|
1324
1673
|
}
|
|
1325
1674
|
const phase = input.phase ?? current.phase;
|
|
1326
1675
|
const phaseEvidence = transitionPhaseEvidence(current, phase, {
|
|
1327
|
-
|
|
1676
|
+
state: 'active',
|
|
1328
1677
|
summary: input.summary,
|
|
1329
1678
|
recordedAt: at,
|
|
1330
1679
|
source: 'recorded',
|
|
@@ -1332,14 +1681,13 @@ export function activateWorkContract(
|
|
|
1332
1681
|
const evidenceState = input.evidenceState
|
|
1333
1682
|
?? (current.evidenceState === 'failed' ? 'partial' : current.evidenceState);
|
|
1334
1683
|
return {
|
|
1335
|
-
status: 'running',
|
|
1336
1684
|
phase,
|
|
1337
1685
|
phaseEvidence,
|
|
1338
1686
|
dispatchState: 'running',
|
|
1339
1687
|
evidenceState,
|
|
1340
1688
|
worktreeRef: input.worktreeRef ?? current.worktreeRef,
|
|
1341
1689
|
};
|
|
1342
|
-
}, false, true, false,
|
|
1690
|
+
}, false, true, false, true);
|
|
1343
1691
|
}
|
|
1344
1692
|
|
|
1345
1693
|
export function failWorkContract(
|
|
@@ -1348,24 +1696,23 @@ export function failWorkContract(
|
|
|
1348
1696
|
input: { phase: WorkPhase; summary: string; evidenceRefs?: EvidenceRef[] },
|
|
1349
1697
|
): WorkContract {
|
|
1350
1698
|
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1351
|
-
if (current.
|
|
1352
|
-
throw new Error(`WORK_FAILURE_TERMINAL: ${workId}:${current.
|
|
1699
|
+
if (current.semanticState !== 'open') {
|
|
1700
|
+
throw new Error(`WORK_FAILURE_TERMINAL: ${workId}:${current.semanticState}`);
|
|
1353
1701
|
}
|
|
1354
1702
|
if (input.phase !== current.phase) {
|
|
1355
1703
|
throw new Error(`WORK_FAILURE_PHASE_MISMATCH: ${workId}:expected=${current.phase}:actual=${input.phase}`);
|
|
1356
1704
|
}
|
|
1357
1705
|
const phaseEvidence = transitionPhaseEvidence(current, current.phase, {
|
|
1358
|
-
|
|
1706
|
+
state: 'failed',
|
|
1359
1707
|
summary: input.summary,
|
|
1360
1708
|
evidenceRefs: input.evidenceRefs,
|
|
1361
1709
|
recordedAt: at,
|
|
1362
1710
|
source: 'recorded',
|
|
1363
1711
|
});
|
|
1364
1712
|
return {
|
|
1365
|
-
status: 'failed',
|
|
1366
1713
|
phase: current.phase,
|
|
1367
1714
|
phaseEvidence,
|
|
1368
|
-
dispatchState: '
|
|
1715
|
+
dispatchState: 'blocked',
|
|
1369
1716
|
evidenceState: 'failed',
|
|
1370
1717
|
...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
|
|
1371
1718
|
};
|
|
@@ -1377,19 +1724,27 @@ export function cancelWorkContract(
|
|
|
1377
1724
|
workId: string,
|
|
1378
1725
|
input: { summary: string; evidenceRefs?: EvidenceRef[] },
|
|
1379
1726
|
): WorkContract {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1727
|
+
const current = getWorkContract(options, workId);
|
|
1728
|
+
if (!current) throw new Error(`work contract not found: ${sanitizeFileComponent(workId)}`);
|
|
1729
|
+
if (current.semanticState === 'completed') throw new Error(`WORK_CANCEL_COMPLETED: ${workId}`);
|
|
1730
|
+
if (current.semanticState === 'cancelled') return current;
|
|
1731
|
+
|
|
1732
|
+
reviseWorkSemanticContext(options, workId, {
|
|
1733
|
+
expectedRevision: currentWorkSemanticRevision(current),
|
|
1734
|
+
state: 'cancelled',
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
return updateWorkContractInternal(options, workId, (cancelled, at) => {
|
|
1738
|
+
if (cancelled.semanticState !== 'cancelled') {
|
|
1739
|
+
throw new Error(`WORK_CANCEL_SEMANTIC_STATE_REQUIRED: ${workId}:${cancelled.semanticState}`);
|
|
1740
|
+
}
|
|
1741
|
+
const phaseEvidence = cancellationPhaseEvidence(cancelled, {
|
|
1385
1742
|
summary: input.summary,
|
|
1386
1743
|
evidenceRefs: input.evidenceRefs,
|
|
1387
1744
|
recordedAt: at,
|
|
1388
|
-
source: 'recorded',
|
|
1389
1745
|
});
|
|
1390
1746
|
return {
|
|
1391
|
-
|
|
1392
|
-
phase: current.phase,
|
|
1747
|
+
phase: cancelled.phase,
|
|
1393
1748
|
phaseEvidence,
|
|
1394
1749
|
dispatchState: 'terminal',
|
|
1395
1750
|
...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
|
|
@@ -1399,58 +1754,54 @@ export function cancelWorkContract(
|
|
|
1399
1754
|
}
|
|
1400
1755
|
|
|
1401
1756
|
/**
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1757
|
+
* Record verified physical cleanup for an already-cancelled Work without
|
|
1758
|
+
* inventing semantic success. Phases that never ran are skipped, while prior
|
|
1759
|
+
* satisfied/skipped/blocked/failed evidence remains immutable. The cleanup
|
|
1760
|
+
* receipt is the authority for the only newly satisfied phase.
|
|
1405
1761
|
*/
|
|
1406
|
-
export function
|
|
1762
|
+
export function recordCancelledWorkCleanupCompleted(
|
|
1407
1763
|
options: WorkContractStoreOptions,
|
|
1408
1764
|
workId: string,
|
|
1409
|
-
input: {
|
|
1410
|
-
principalId: string;
|
|
1411
|
-
controllerInstanceId: string;
|
|
1412
|
-
summary: string;
|
|
1413
|
-
checkoutId?: string;
|
|
1414
|
-
worktreeRef?: string;
|
|
1415
|
-
},
|
|
1765
|
+
input: { summary: string; receiptId: string; evidenceRefs?: EvidenceRef[] },
|
|
1416
1766
|
): WorkContract {
|
|
1417
1767
|
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1418
|
-
if (current.
|
|
1419
|
-
throw new Error(`
|
|
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}`);
|
|
1768
|
+
if (current.semanticState !== 'cancelled') {
|
|
1769
|
+
throw new Error(`WORK_CANCELLED_CLEANUP_STATE_REQUIRED: ${workId}:${current.semanticState}`);
|
|
1425
1770
|
}
|
|
1426
|
-
const
|
|
1427
|
-
if (!
|
|
1428
|
-
|
|
1771
|
+
const summary = input.summary.trim().slice(0, 1_000);
|
|
1772
|
+
if (!summary) throw new Error('WORK_CANCELLED_CLEANUP_SUMMARY_REQUIRED');
|
|
1773
|
+
const receiptId = input.receiptId.trim();
|
|
1774
|
+
if (!receiptId) throw new Error('WORK_CANCELLED_CLEANUP_RECEIPT_REQUIRED');
|
|
1775
|
+
const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
|
|
1776
|
+
const phaseEvidence: WorkPhaseEvidenceMap = { ...current.phaseEvidence };
|
|
1777
|
+
for (const phase of WORK_PHASES) {
|
|
1778
|
+
if (phase === 'cleanup') continue;
|
|
1779
|
+
const existing = phaseEvidence[phase];
|
|
1780
|
+
if (!['pending', 'active'].includes(existing.state)) continue;
|
|
1781
|
+
phaseEvidence[phase] = {
|
|
1782
|
+
state: 'skipped',
|
|
1783
|
+
source: 'recorded',
|
|
1784
|
+
summary: `Skipped after Work cancellation: ${summary}`.slice(0, 1_000),
|
|
1785
|
+
evidenceRefs,
|
|
1786
|
+
recordedAt: at,
|
|
1787
|
+
};
|
|
1429
1788
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
}].slice(-current.evidencePolicy.maxEvidenceRefs);
|
|
1435
|
-
const phaseEvidence = transitionPhaseEvidence({ ...current, evidenceRefs }, 'implementation', {
|
|
1436
|
-
status: 'running',
|
|
1437
|
-
summary: input.summary,
|
|
1789
|
+
phaseEvidence.cleanup = {
|
|
1790
|
+
state: 'satisfied',
|
|
1791
|
+
source: 'recorded',
|
|
1792
|
+
summary,
|
|
1438
1793
|
evidenceRefs,
|
|
1439
1794
|
recordedAt: at,
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1795
|
+
receiptId,
|
|
1796
|
+
};
|
|
1442
1797
|
return {
|
|
1443
|
-
|
|
1444
|
-
phase: 'implementation',
|
|
1798
|
+
phase: 'cleanup',
|
|
1445
1799
|
phaseEvidence,
|
|
1446
|
-
dispatchState: '
|
|
1800
|
+
dispatchState: 'terminal',
|
|
1447
1801
|
evidenceRefs,
|
|
1448
|
-
|
|
1449
|
-
worktreeRef: input.worktreeRef ?? current.worktreeRef,
|
|
1450
|
-
controllerInstanceId: input.controllerInstanceId,
|
|
1451
|
-
continuationPrompt: input.summary,
|
|
1802
|
+
suggestedNextActions: [],
|
|
1452
1803
|
};
|
|
1453
|
-
}, false, true
|
|
1804
|
+
}, false, true);
|
|
1454
1805
|
}
|
|
1455
1806
|
|
|
1456
1807
|
/** Merge non-authoritative discovery/change evidence without changing policy fences. */
|
|
@@ -1479,7 +1830,6 @@ export function transitionWorkContractPhase(
|
|
|
1479
1830
|
workId: string,
|
|
1480
1831
|
input: {
|
|
1481
1832
|
phase: WorkPhase;
|
|
1482
|
-
status: WorkContractStatus;
|
|
1483
1833
|
state?: Exclude<WorkPhaseEvidenceState, 'pending'>;
|
|
1484
1834
|
summary: string;
|
|
1485
1835
|
evidenceRefs?: EvidenceRef[];
|
|
@@ -1488,56 +1838,25 @@ export function transitionWorkContractPhase(
|
|
|
1488
1838
|
},
|
|
1489
1839
|
): WorkContract {
|
|
1490
1840
|
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
1841
|
const phaseEvidence = transitionPhaseEvidence(current, input.phase, {
|
|
1500
|
-
|
|
1842
|
+
state: input.state,
|
|
1501
1843
|
summary: input.summary,
|
|
1502
1844
|
evidenceRefs: input.evidenceRefs,
|
|
1503
1845
|
recordedAt: at,
|
|
1504
1846
|
});
|
|
1505
|
-
if (input.state) phaseEvidence[input.phase] = { ...phaseEvidence[input.phase], state: input.state };
|
|
1506
1847
|
return {
|
|
1507
1848
|
phase: input.phase,
|
|
1508
1849
|
phaseEvidence,
|
|
1509
|
-
status: input.status,
|
|
1510
1850
|
dispatchState: input.dispatchState ?? current.dispatchState,
|
|
1511
1851
|
evidenceState: input.evidenceState ?? current.evidenceState,
|
|
1512
1852
|
};
|
|
1513
|
-
}, false, true, false,
|
|
1853
|
+
}, false, true, false, true);
|
|
1514
1854
|
}
|
|
1515
1855
|
|
|
1516
|
-
/**
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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. */
|
|
1856
|
+
/**
|
|
1857
|
+
* Append immutable historical review evidence. This is evidence storage only:
|
|
1858
|
+
* the decision never changes semantic state or phase and never authorizes delivery.
|
|
1859
|
+
*/
|
|
1541
1860
|
export function recordWorkImplementationReview(
|
|
1542
1861
|
options: WorkContractStoreOptions,
|
|
1543
1862
|
workId: string,
|
|
@@ -1548,198 +1867,14 @@ export function recordWorkImplementationReview(
|
|
|
1548
1867
|
throw new Error('WORK_IMPLEMENTATION_REVIEW_DERIVATION_REQUIRES_TRANSFER_API');
|
|
1549
1868
|
}
|
|
1550
1869
|
return updateWorkContractInternal(options, workId, (current, at) => {
|
|
1551
|
-
if (
|
|
1870
|
+
if (semanticWorkState(current) !== 'open') throw new Error(`WORK_IMPLEMENTATION_REVIEW_TERMINAL: ${workId}`);
|
|
1552
1871
|
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
1872
|
const history = [...(current.implementationReviews ?? []), review];
|
|
1558
1873
|
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
1874
|
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
1875
|
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
1876
|
};
|
|
1742
|
-
}, false, true);
|
|
1877
|
+
}, false, false, true, false);
|
|
1743
1878
|
}
|
|
1744
1879
|
|
|
1745
1880
|
export function appendWorkEvidence(
|
|
@@ -1797,63 +1932,45 @@ export function appendVerificationRecord(
|
|
|
1797
1932
|
}
|
|
1798
1933
|
|
|
1799
1934
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1935
|
+
* Legacy field name retained for storage compatibility. This records durable
|
|
1936
|
+
* delivery/effect evidence only. It MUST NOT complete semantic Work, advance a
|
|
1937
|
+
* Work lifecycle/phase, satisfy review policy, or infer the next action.
|
|
1938
|
+
* Semantic completion is exclusively reviseWorkSemanticContext/complete CAS.
|
|
1803
1939
|
*/
|
|
1804
1940
|
export function recordWorkCompletionReceipt(
|
|
1805
1941
|
options: WorkContractStoreOptions,
|
|
1806
1942
|
workId: string,
|
|
1807
1943
|
receipt: NonNullable<WorkContract['completionReceipt']>,
|
|
1808
1944
|
completionOutcome: NonNullable<WorkContract['completionOutcome']>,
|
|
1809
|
-
|
|
1945
|
+
_completionWorkKind?: WorkKind,
|
|
1810
1946
|
): WorkContract {
|
|
1811
1947
|
return updateWorkContractInternal(options, workId, (current) => {
|
|
1812
1948
|
if (receipt.workId !== current.workId) throw new Error('WORK_COMPLETION_RECEIPT_IDENTITY_MISMATCH');
|
|
1813
1949
|
if (current.completionReceipt) {
|
|
1814
|
-
if (current.completionReceipt.receiptId
|
|
1815
|
-
|
|
1950
|
+
if (current.completionReceipt.receiptId === receipt.receiptId) return undefined;
|
|
1951
|
+
// Legacy field names store current mechanical delivery evidence. An open
|
|
1952
|
+
// semantic Work may legitimately deliver again after a retained production
|
|
1953
|
+
// canary repair. Terminal semantic Work keeps its delivery receipt immutable.
|
|
1954
|
+
if (semanticWorkState(current) !== 'open') throw new Error('WORK_COMPLETION_RECEIPT_ALREADY_RECORDED');
|
|
1955
|
+
if ((isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)) && current.evidenceState !== 'valid') {
|
|
1956
|
+
throw new Error('WORK_DELIVERY_RECEIPT_REPLACEMENT_VALIDATION_REQUIRED');
|
|
1957
|
+
}
|
|
1816
1958
|
}
|
|
1817
1959
|
const recordedAt = receipt.recordedAt;
|
|
1818
1960
|
const receiptChangedPaths = isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)
|
|
1819
1961
|
? receipt.changedPaths
|
|
1820
1962
|
: [];
|
|
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
1963
|
return {
|
|
1841
|
-
phase: 'cleanup',
|
|
1842
|
-
phaseEvidence,
|
|
1843
|
-
status: 'completed',
|
|
1844
|
-
dispatchState: 'terminal',
|
|
1845
|
-
evidenceState: 'valid',
|
|
1846
1964
|
completionOutcome,
|
|
1847
|
-
...(completionWorkKind ? { workKind: completionWorkKind } : {}),
|
|
1848
1965
|
completionReceipt: receipt,
|
|
1849
1966
|
scopeEvidence: {
|
|
1850
|
-
initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ??
|
|
1967
|
+
initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ?? [],
|
|
1851
1968
|
inspectedPaths: current.scopeEvidence?.inspectedPaths ?? [],
|
|
1852
1969
|
actualChangedPaths: [...new Set(receiptChangedPaths)].slice(0, 500),
|
|
1853
1970
|
recordedAt,
|
|
1854
1971
|
},
|
|
1855
1972
|
};
|
|
1856
|
-
}, true,
|
|
1973
|
+
}, true, false);
|
|
1857
1974
|
}
|
|
1858
1975
|
export interface AcceptSubmittedWorkInput {
|
|
1859
1976
|
requestId: string;
|
|
@@ -1862,13 +1979,14 @@ export interface AcceptSubmittedWorkInput {
|
|
|
1862
1979
|
semanticKey: string;
|
|
1863
1980
|
operation: SubmittedWorkOperation;
|
|
1864
1981
|
objective?: string;
|
|
1865
|
-
mode?: WorkContract['mode'];
|
|
1866
1982
|
requestedBy?: WorkContract['requestedBy'];
|
|
1867
1983
|
principalId?: string;
|
|
1868
1984
|
controllerInstanceId?: string;
|
|
1869
1985
|
workKind?: WorkKind;
|
|
1870
1986
|
risk?: WorkRisk;
|
|
1987
|
+
/** Compatibility input only; execution scope is owned by EditSession/resources. */
|
|
1871
1988
|
allowedPaths?: string[];
|
|
1989
|
+
/** Compatibility input only; execution scope is owned by EditSession/resources. */
|
|
1872
1990
|
forbiddenPaths?: string[];
|
|
1873
1991
|
checks?: string[];
|
|
1874
1992
|
acceptanceCriteria?: string[];
|