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