@moretea-labs/forge 1.8.0 → 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 +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- 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-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- 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 +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- 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-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- 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/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +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 +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- 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/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +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/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- 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 +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +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/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 +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +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-pack.ts +10 -8
- 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 +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/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/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- 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 +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- 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/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -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/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +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/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- 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/stage-runtime-release.ts +0 -44
- 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/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { assertStorageHeadroom } from '../shared/storage-capacity';
|
|
5
|
+
import type { CandidateExecutionLane, StableExecutionLane } from '../root/runtime-lane';
|
|
6
|
+
import type { RuntimePublishedRelease, RuntimeReleaseAuthority } from '../root/release-store';
|
|
7
|
+
|
|
8
|
+
export const RELEASE_SESSION_PHASES = [
|
|
9
|
+
'source_frozen',
|
|
10
|
+
'built',
|
|
11
|
+
'static_verified',
|
|
12
|
+
'candidate_booted',
|
|
13
|
+
'candidate_verified',
|
|
14
|
+
'cutover_eligible',
|
|
15
|
+
'cutover_attempting',
|
|
16
|
+
'cutover_committed',
|
|
17
|
+
'soaking',
|
|
18
|
+
'known_good',
|
|
19
|
+
'rolled_back',
|
|
20
|
+
'failed',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type ReleaseSessionPhase = (typeof RELEASE_SESSION_PHASES)[number];
|
|
24
|
+
|
|
25
|
+
export interface ReleaseSessionStableRelease {
|
|
26
|
+
authorityRevision: number;
|
|
27
|
+
releaseId: string;
|
|
28
|
+
artifactIdentity: string;
|
|
29
|
+
manifestSha256: string;
|
|
30
|
+
workerProtocolVersion: number;
|
|
31
|
+
releaseFencingTokenSha256: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ReleaseSessionCandidateRelease {
|
|
35
|
+
releaseId: string;
|
|
36
|
+
manifestPath: string;
|
|
37
|
+
artifactIdentity: string;
|
|
38
|
+
manifestSha256: string;
|
|
39
|
+
treeSha256: string;
|
|
40
|
+
sourceCommit: string;
|
|
41
|
+
sourceRepositoryId?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ReleaseSessionReceipt {
|
|
45
|
+
id: string;
|
|
46
|
+
kind: 'source' | 'build' | 'static_gate' | 'candidate_canary' | 'cutover' | 'soak' | 'known_good' | 'rollback';
|
|
47
|
+
recordedAt: string;
|
|
48
|
+
summary: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Durable semantic rollback authority for one ReleaseSession after physical
|
|
53
|
+
* cutover has published Candidate B. RuntimeReleaseAuthority stores only
|
|
54
|
+
* physical active/previous release identity and backing artifacts; rollback
|
|
55
|
+
* eligibility and transaction semantics live only in this ReleaseSession.
|
|
56
|
+
*/
|
|
57
|
+
export interface ReleaseSessionTransaction {
|
|
58
|
+
schemaVersion: 1;
|
|
59
|
+
operationId: string;
|
|
60
|
+
candidateReleaseId: string;
|
|
61
|
+
cutoverAuthorityRevision: number;
|
|
62
|
+
rollbackRelease: RuntimePublishedRelease;
|
|
63
|
+
startedAt: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Runtime startup migration may persist the exact cutover transaction between
|
|
68
|
+
* activation and Recovery's post-start receipt. Operation ids and timestamps
|
|
69
|
+
* are writer-local evidence, so the semantic transaction identity excludes
|
|
70
|
+
* those fields while retaining every release/authority identity that fences a
|
|
71
|
+
* different cutover.
|
|
72
|
+
*/
|
|
73
|
+
function sameReleaseSessionTransactionIdentity(
|
|
74
|
+
left: ReleaseSessionTransaction,
|
|
75
|
+
right: ReleaseSessionTransaction,
|
|
76
|
+
): boolean {
|
|
77
|
+
return left.schemaVersion === right.schemaVersion
|
|
78
|
+
&& left.candidateReleaseId === right.candidateReleaseId
|
|
79
|
+
&& left.cutoverAuthorityRevision === right.cutoverAuthorityRevision
|
|
80
|
+
&& left.rollbackRelease.releaseId === right.rollbackRelease.releaseId
|
|
81
|
+
&& left.rollbackRelease.artifactIdentity === right.rollbackRelease.artifactIdentity
|
|
82
|
+
&& left.rollbackRelease.manifestPath === right.rollbackRelease.manifestPath
|
|
83
|
+
&& left.rollbackRelease.manifestSha256 === right.rollbackRelease.manifestSha256
|
|
84
|
+
&& left.rollbackRelease.workerProtocolVersion === right.rollbackRelease.workerProtocolVersion
|
|
85
|
+
&& left.rollbackRelease.publishedAt === right.rollbackRelease.publishedAt
|
|
86
|
+
&& left.rollbackRelease.databaseBackup?.path === right.rollbackRelease.databaseBackup?.path
|
|
87
|
+
&& left.rollbackRelease.databaseBackup?.schemaVersion === right.rollbackRelease.databaseBackup?.schemaVersion
|
|
88
|
+
&& left.rollbackRelease.databaseBackup?.createdAt === right.rollbackRelease.databaseBackup?.createdAt
|
|
89
|
+
&& left.rollbackRelease.databaseBackup?.auditEventCount === right.rollbackRelease.databaseBackup?.auditEventCount
|
|
90
|
+
&& left.rollbackRelease.databaseBackup?.recordCount === right.rollbackRelease.databaseBackup?.recordCount;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ReleaseSession {
|
|
94
|
+
/** Wire/storage schema remains readable by the previous Recovery release. */
|
|
95
|
+
schemaVersion: 1;
|
|
96
|
+
/** Current semantic model. Legacy records omit this field (or carry epoch 1). */
|
|
97
|
+
semanticEpoch: 2;
|
|
98
|
+
sessionId: string;
|
|
99
|
+
stable: StableExecutionLane;
|
|
100
|
+
stableRelease: ReleaseSessionStableRelease;
|
|
101
|
+
candidate: CandidateExecutionLane;
|
|
102
|
+
candidateRelease?: ReleaseSessionCandidateRelease;
|
|
103
|
+
transaction?: ReleaseSessionTransaction;
|
|
104
|
+
sourceRevision: string;
|
|
105
|
+
phase: ReleaseSessionPhase;
|
|
106
|
+
revision: number;
|
|
107
|
+
receipts: ReleaseSessionReceipt[];
|
|
108
|
+
createdAt: string;
|
|
109
|
+
updatedAt: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const REQUIRED_STATIC_GATES = new Set(['type', 'runtime_architecture', 'architecture_sync', 'bootstrap']);
|
|
113
|
+
const REQUIRED_CANDIDATE_CANARIES = new Set(['recovery', 'mcp', 'scheduler', 'supervisor', 'controller']);
|
|
114
|
+
const REQUIRED_KNOWN_GOOD_SOAK_GATES = new Set(['supervisor_continuation_proof']);
|
|
115
|
+
|
|
116
|
+
function sessionPath(controllerHome: string, sessionId: string): string {
|
|
117
|
+
return join(resolve(controllerHome), 'recovery', 'state', 'release-sessions', `${sessionId}.json`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validSessionId(value: string): string {
|
|
121
|
+
const sessionId = value.trim();
|
|
122
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{7,119}$/.test(sessionId)) throw new Error('RELEASE_SESSION_ID_INVALID');
|
|
123
|
+
return sessionId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function writeSession(path: string, session: ReleaseSession): void {
|
|
127
|
+
const content = `${JSON.stringify(session, null, 2)}\n`;
|
|
128
|
+
assertStorageHeadroom(path, { operation: 'release_session_write', requiredBytes: Buffer.byteLength(content), reserveBytes: 16 * 1024 * 1024 });
|
|
129
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
130
|
+
const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
131
|
+
writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o600 });
|
|
132
|
+
renameSync(temporary, path);
|
|
133
|
+
invalidateCompleteReleaseSessionInventory(dirname(path));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sameStableRelease(session: Pick<ReleaseSession, 'stableRelease'>, release: RuntimePublishedRelease): boolean {
|
|
137
|
+
return release.releaseId === session.stableRelease.releaseId
|
|
138
|
+
&& release.artifactIdentity === session.stableRelease.artifactIdentity
|
|
139
|
+
&& release.manifestSha256 === session.stableRelease.manifestSha256
|
|
140
|
+
&& release.workerProtocolVersion === session.stableRelease.workerProtocolVersion;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function validTransaction(session: ReleaseSession): boolean {
|
|
144
|
+
const transaction = session.transaction;
|
|
145
|
+
if (!transaction) return !['cutover_committed', 'soaking'].includes(session.phase);
|
|
146
|
+
const rollback = transaction.rollbackRelease;
|
|
147
|
+
return transaction.schemaVersion === 1
|
|
148
|
+
&& Boolean(transaction.operationId?.trim())
|
|
149
|
+
&& Boolean(session.candidateRelease)
|
|
150
|
+
&& transaction.candidateReleaseId === session.candidateRelease!.releaseId
|
|
151
|
+
&& Number.isInteger(transaction.cutoverAuthorityRevision)
|
|
152
|
+
&& transaction.cutoverAuthorityRevision > session.stableRelease.authorityRevision
|
|
153
|
+
&& Number.isFinite(Date.parse(transaction.startedAt))
|
|
154
|
+
&& sameStableRelease(session, rollback)
|
|
155
|
+
&& Boolean(rollback.manifestPath?.trim())
|
|
156
|
+
&& Number.isFinite(Date.parse(rollback.publishedAt))
|
|
157
|
+
&& Boolean(rollback.databaseBackup)
|
|
158
|
+
&& resolve(rollback.databaseBackup!.path) === rollback.databaseBackup!.path
|
|
159
|
+
&& Number.isInteger(rollback.databaseBackup!.schemaVersion)
|
|
160
|
+
&& rollback.databaseBackup!.schemaVersion > 0
|
|
161
|
+
&& Number.isFinite(Date.parse(rollback.databaseBackup!.createdAt));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function assertCurrentSession(value: ReleaseSession, sessionId: string): ReleaseSession {
|
|
165
|
+
if (
|
|
166
|
+
value.schemaVersion !== 1
|
|
167
|
+
|| value.semanticEpoch !== 2
|
|
168
|
+
|| value.sessionId !== sessionId
|
|
169
|
+
|| !RELEASE_SESSION_PHASES.includes(value.phase)
|
|
170
|
+
|| !Number.isInteger(value.revision)
|
|
171
|
+
|| value.revision < 1
|
|
172
|
+
|| !validTransaction(value)
|
|
173
|
+
) throw new Error('RELEASE_SESSION_INVALID');
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ReleaseSessionStateMigration {
|
|
178
|
+
migratedSessionIds: string[];
|
|
179
|
+
currentSessionIds: string[];
|
|
180
|
+
inspected: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type LegacyReleaseSessionV1 = Omit<ReleaseSession, 'semanticEpoch' | 'transaction'> & {
|
|
184
|
+
schemaVersion: 1;
|
|
185
|
+
semanticEpoch?: 1;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
type TransitionalReleaseSessionV2 = Omit<ReleaseSession, 'schemaVersion' | 'semanticEpoch'> & {
|
|
189
|
+
schemaVersion: 2;
|
|
190
|
+
semanticEpoch?: never;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
type MigratableReleaseSession = ReleaseSession | LegacyReleaseSessionV1 | TransitionalReleaseSessionV2;
|
|
194
|
+
|
|
195
|
+
function releaseIdentityKey(release: {
|
|
196
|
+
releaseId?: string;
|
|
197
|
+
artifactIdentity?: string;
|
|
198
|
+
manifestSha256?: string;
|
|
199
|
+
} | undefined): string | undefined {
|
|
200
|
+
const releaseId = release?.releaseId?.trim() || '';
|
|
201
|
+
const artifactIdentity = release?.artifactIdentity?.trim() || '';
|
|
202
|
+
const manifestSha256 = release?.manifestSha256?.trim() || '';
|
|
203
|
+
if (!releaseId || !artifactIdentity || !manifestSha256) return undefined;
|
|
204
|
+
return `${releaseId}\u0000${artifactIdentity}\u0000${manifestSha256}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function basicMigratableSessionShape(raw: MigratableReleaseSession, id: string): boolean {
|
|
208
|
+
const schemaSupported = (raw.schemaVersion === 1 && (raw.semanticEpoch === undefined || raw.semanticEpoch === 1 || raw.semanticEpoch === 2))
|
|
209
|
+
|| (raw.schemaVersion === 2 && raw.semanticEpoch === undefined);
|
|
210
|
+
return schemaSupported
|
|
211
|
+
&& raw.sessionId === id
|
|
212
|
+
&& RELEASE_SESSION_PHASES.includes(raw.phase)
|
|
213
|
+
&& Number.isFinite(Date.parse(raw.createdAt))
|
|
214
|
+
&& Number.isFinite(Date.parse(raw.updatedAt));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function historicalAcceptanceReceipt(
|
|
218
|
+
sessionId: string,
|
|
219
|
+
recordedAt: string,
|
|
220
|
+
proof: string,
|
|
221
|
+
): ReleaseSessionReceipt {
|
|
222
|
+
return {
|
|
223
|
+
id: `migration:historical-known-good:${sessionId}`,
|
|
224
|
+
kind: 'known_good',
|
|
225
|
+
recordedAt,
|
|
226
|
+
summary: `Legacy soaking state reconciled as known-good from exact durable release lineage: ${proof}.`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function migrateReleaseSessionState(
|
|
231
|
+
controllerHome: string,
|
|
232
|
+
dependencies: { readAuthority?: () => RuntimeReleaseAuthority | undefined } = {},
|
|
233
|
+
): ReleaseSessionStateMigration {
|
|
234
|
+
const root = dirname(sessionPath(controllerHome, 'release-session-migration'));
|
|
235
|
+
if (!existsSync(root)) return { migratedSessionIds: [], currentSessionIds: [], inspected: 0 };
|
|
236
|
+
const authority = dependencies.readAuthority?.();
|
|
237
|
+
const migratedSessionIds: string[] = [];
|
|
238
|
+
const currentSessionIds: string[] = [];
|
|
239
|
+
const names = readdirSync(root).filter((name) => name.endsWith('.json')).sort();
|
|
240
|
+
const parsed = names.map((name) => {
|
|
241
|
+
const id = name.slice(0, -'.json'.length);
|
|
242
|
+
const path = sessionPath(controllerHome, validSessionId(id));
|
|
243
|
+
try {
|
|
244
|
+
return { id, path, raw: JSON.parse(readFileSync(path, 'utf8')) as MigratableReleaseSession };
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(`RELEASE_SESSION_MIGRATION_INVALID_JSON: ${id}`);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const stableSuccessors = new Map<string, Array<{ sessionId: string; createdAt: string }>>();
|
|
250
|
+
for (const entry of parsed) {
|
|
251
|
+
if (!basicMigratableSessionShape(entry.raw, entry.id)) continue;
|
|
252
|
+
const stableKey = releaseIdentityKey(entry.raw.stableRelease);
|
|
253
|
+
if (!stableKey) continue;
|
|
254
|
+
const existing = stableSuccessors.get(stableKey) ?? [];
|
|
255
|
+
existing.push({ sessionId: entry.id, createdAt: entry.raw.createdAt });
|
|
256
|
+
stableSuccessors.set(stableKey, existing);
|
|
257
|
+
}
|
|
258
|
+
const activeKey = releaseIdentityKey(authority?.active);
|
|
259
|
+
const previousKey = releaseIdentityKey(authority?.previous);
|
|
260
|
+
|
|
261
|
+
for (const { id, path, raw } of parsed) {
|
|
262
|
+
if (raw.schemaVersion === 1 && raw.semanticEpoch === 2) {
|
|
263
|
+
let current = raw as ReleaseSession;
|
|
264
|
+
if (
|
|
265
|
+
!current.transaction
|
|
266
|
+
&& current.phase === 'cutover_attempting'
|
|
267
|
+
&& current.candidateRelease
|
|
268
|
+
&& authority?.active.releaseId === current.candidateRelease.releaseId
|
|
269
|
+
&& authority.active.artifactIdentity === current.candidateRelease.artifactIdentity
|
|
270
|
+
&& authority.previous?.databaseBackup
|
|
271
|
+
&& authority.previous.releaseId === current.stableRelease.releaseId
|
|
272
|
+
&& authority.previous.artifactIdentity === current.stableRelease.artifactIdentity
|
|
273
|
+
&& authority.previous.manifestSha256 === current.stableRelease.manifestSha256
|
|
274
|
+
) {
|
|
275
|
+
current = {
|
|
276
|
+
...current,
|
|
277
|
+
transaction: {
|
|
278
|
+
schemaVersion: 1,
|
|
279
|
+
operationId: `reconcile:${id}:${authority.revision}`,
|
|
280
|
+
candidateReleaseId: current.candidateRelease.releaseId,
|
|
281
|
+
cutoverAuthorityRevision: authority.revision,
|
|
282
|
+
rollbackRelease: authority.previous,
|
|
283
|
+
startedAt: authority.committedAt,
|
|
284
|
+
},
|
|
285
|
+
revision: current.revision + 1,
|
|
286
|
+
updatedAt: new Date().toISOString(),
|
|
287
|
+
};
|
|
288
|
+
assertCurrentSession(current, id);
|
|
289
|
+
writeSession(path, current);
|
|
290
|
+
migratedSessionIds.push(id);
|
|
291
|
+
} else {
|
|
292
|
+
assertCurrentSession(current, id);
|
|
293
|
+
currentSessionIds.push(id);
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const legacyV1 = raw.schemaVersion === 1
|
|
298
|
+
&& (raw.semanticEpoch === undefined || raw.semanticEpoch === 1);
|
|
299
|
+
const transitionalV2 = raw.schemaVersion === 2
|
|
300
|
+
&& raw.semanticEpoch === undefined;
|
|
301
|
+
if ((!legacyV1 && !transitionalV2) || !basicMigratableSessionShape(raw, id)) {
|
|
302
|
+
throw new Error(`RELEASE_SESSION_MIGRATION_UNSUPPORTED_SCHEMA: ${id}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let transaction: ReleaseSessionTransaction | undefined = 'transaction' in raw
|
|
306
|
+
? raw.transaction
|
|
307
|
+
: undefined;
|
|
308
|
+
let migratedPhase = raw.phase;
|
|
309
|
+
let migratedReceipts = raw.receipts;
|
|
310
|
+
const candidateKey = releaseIdentityKey(raw.candidateRelease);
|
|
311
|
+
const candidateIsActive = Boolean(candidateKey && activeKey && candidateKey === activeKey);
|
|
312
|
+
if (!transaction && ['cutover_attempting', 'cutover_committed', 'soaking', 'known_good'].includes(raw.phase)) {
|
|
313
|
+
const candidate = raw.candidateRelease;
|
|
314
|
+
const previous = authority?.previous;
|
|
315
|
+
if (
|
|
316
|
+
candidateIsActive
|
|
317
|
+
&& candidate
|
|
318
|
+
&& authority
|
|
319
|
+
&& previous?.databaseBackup
|
|
320
|
+
&& previous.releaseId === raw.stableRelease.releaseId
|
|
321
|
+
&& previous.artifactIdentity === raw.stableRelease.artifactIdentity
|
|
322
|
+
&& previous.manifestSha256 === raw.stableRelease.manifestSha256
|
|
323
|
+
) {
|
|
324
|
+
transaction = {
|
|
325
|
+
schemaVersion: 1,
|
|
326
|
+
operationId: `migration:${id}:${authority.revision}`,
|
|
327
|
+
candidateReleaseId: candidate.releaseId,
|
|
328
|
+
cutoverAuthorityRevision: authority.revision,
|
|
329
|
+
rollbackRelease: previous,
|
|
330
|
+
startedAt: raw.updatedAt,
|
|
331
|
+
};
|
|
332
|
+
} else if (raw.phase === 'known_good') {
|
|
333
|
+
// Historical terminal acceptance needs no live rollback authority. Keep
|
|
334
|
+
// exact transaction evidence when it is reconstructable above, but do
|
|
335
|
+
// not invent obsolete rollback state for already-terminal sessions.
|
|
336
|
+
} else if (raw.phase === 'soaking' && candidateKey && !candidateIsActive) {
|
|
337
|
+
const updatedAtMs = Date.parse(raw.updatedAt);
|
|
338
|
+
const successors = (stableSuccessors.get(candidateKey) ?? [])
|
|
339
|
+
.filter((entry) => entry.sessionId !== id && Date.parse(entry.createdAt) >= updatedAtMs);
|
|
340
|
+
const previousAuthorityProof = Boolean(previousKey && candidateKey === previousKey);
|
|
341
|
+
if (successors.length === 0 && !previousAuthorityProof) {
|
|
342
|
+
throw new Error(`RELEASE_SESSION_MIGRATION_HISTORICAL_ACCEPTANCE_UNPROVEN: ${id}`);
|
|
343
|
+
}
|
|
344
|
+
migratedPhase = 'known_good';
|
|
345
|
+
const proof = successors.length > 0
|
|
346
|
+
? `${successors.length} later ReleaseSession attempt(s) use exact Candidate B as Stable A; first=${successors[0]!.sessionId}`
|
|
347
|
+
: 'current RuntimeReleaseAuthority.previous references Candidate B';
|
|
348
|
+
const receiptId = `migration:historical-known-good:${id}`;
|
|
349
|
+
migratedReceipts = raw.receipts.some((receipt) => receipt.id === receiptId)
|
|
350
|
+
? raw.receipts
|
|
351
|
+
: [...raw.receipts, historicalAcceptanceReceipt(id, raw.updatedAt, proof)];
|
|
352
|
+
} else {
|
|
353
|
+
throw new Error(`RELEASE_SESSION_MIGRATION_AUTHORITY_MISMATCH: ${id}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const migrated = assertCurrentSession({
|
|
357
|
+
...raw,
|
|
358
|
+
schemaVersion: 1,
|
|
359
|
+
semanticEpoch: 2,
|
|
360
|
+
phase: migratedPhase,
|
|
361
|
+
receipts: migratedReceipts,
|
|
362
|
+
...(transaction ? { transaction } : {}),
|
|
363
|
+
} as ReleaseSession, id);
|
|
364
|
+
writeSession(path, migrated);
|
|
365
|
+
migratedSessionIds.push(id);
|
|
366
|
+
}
|
|
367
|
+
return { migratedSessionIds, currentSessionIds, inspected: names.length };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function readReleaseSession(controllerHome: string, sessionId: string): ReleaseSession | undefined {
|
|
371
|
+
const path = sessionPath(controllerHome, validSessionId(sessionId));
|
|
372
|
+
if (!existsSync(path)) return undefined;
|
|
373
|
+
try {
|
|
374
|
+
const value = JSON.parse(readFileSync(path, 'utf8')) as ReleaseSession;
|
|
375
|
+
return assertCurrentSession(value, sessionId);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof Error && error.message === 'RELEASE_SESSION_INVALID') throw error;
|
|
378
|
+
throw new Error('RELEASE_SESSION_INVALID');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface ReleaseSessionInventory {
|
|
383
|
+
sessions: ReleaseSession[];
|
|
384
|
+
invalidSessionFiles: string[];
|
|
385
|
+
inspected: number;
|
|
386
|
+
truncated: boolean;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
interface CompleteReleaseSessionInventoryCacheEntry {
|
|
390
|
+
revision: string;
|
|
391
|
+
inventory: ReleaseSessionInventory;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const completeReleaseSessionInventoryCache = new Map<string, CompleteReleaseSessionInventoryCacheEntry>();
|
|
395
|
+
|
|
396
|
+
function releaseSessionInventoryRoot(controllerHome: string): string {
|
|
397
|
+
return dirname(sessionPath(controllerHome, 'release-session-inventory'));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function invalidateCompleteReleaseSessionInventory(root: string): void {
|
|
401
|
+
completeReleaseSessionInventoryCache.delete(resolve(root));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function copyReleaseSessionInventory(inventory: ReleaseSessionInventory): ReleaseSessionInventory {
|
|
405
|
+
return {
|
|
406
|
+
sessions: [...inventory.sessions],
|
|
407
|
+
invalidSessionFiles: [...inventory.invalidSessionFiles],
|
|
408
|
+
inspected: inventory.inspected,
|
|
409
|
+
truncated: inventory.truncated,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Complete ReleaseSession authority inventory. The JSON files remain the single
|
|
415
|
+
* durable authority; this cache is process-local and is invalidated by every
|
|
416
|
+
* atomic session write. Directory metadata also invalidates cache entries when
|
|
417
|
+
* another Recovery process replaces a session file.
|
|
418
|
+
*/
|
|
419
|
+
export function listCompleteReleaseSessions(controllerHome: string): ReleaseSessionInventory {
|
|
420
|
+
const root = releaseSessionInventoryRoot(controllerHome);
|
|
421
|
+
if (!existsSync(root)) {
|
|
422
|
+
invalidateCompleteReleaseSessionInventory(root);
|
|
423
|
+
return { sessions: [], invalidSessionFiles: [], inspected: 0, truncated: false };
|
|
424
|
+
}
|
|
425
|
+
const names = readdirSync(root)
|
|
426
|
+
.filter((name) => name.endsWith('.json'))
|
|
427
|
+
.sort();
|
|
428
|
+
const directory = statSync(root);
|
|
429
|
+
const revision = [directory.mtimeMs, directory.ctimeMs, names.length, names[0] ?? '', names[names.length - 1] ?? ''].join(':');
|
|
430
|
+
const cacheKey = resolve(root);
|
|
431
|
+
const cached = completeReleaseSessionInventoryCache.get(cacheKey);
|
|
432
|
+
if (cached?.revision === revision) return copyReleaseSessionInventory(cached.inventory);
|
|
433
|
+
|
|
434
|
+
const sessions: ReleaseSession[] = [];
|
|
435
|
+
const invalidSessionFiles: string[] = [];
|
|
436
|
+
for (const name of names) {
|
|
437
|
+
const sessionId = name.slice(0, -'.json'.length);
|
|
438
|
+
try {
|
|
439
|
+
const session = readReleaseSession(controllerHome, sessionId);
|
|
440
|
+
if (session) sessions.push(session);
|
|
441
|
+
else invalidSessionFiles.push(name);
|
|
442
|
+
} catch {
|
|
443
|
+
invalidSessionFiles.push(name);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const inventory: ReleaseSessionInventory = {
|
|
447
|
+
sessions,
|
|
448
|
+
invalidSessionFiles,
|
|
449
|
+
inspected: names.length,
|
|
450
|
+
truncated: false,
|
|
451
|
+
};
|
|
452
|
+
completeReleaseSessionInventoryCache.set(cacheKey, { revision, inventory });
|
|
453
|
+
return copyReleaseSessionInventory(inventory);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Bounded read-only inventory for diagnostic/projection consumers.
|
|
458
|
+
* The release domain owns phase progression. Recovery may execute fenced
|
|
459
|
+
* Runtime mutations, but it is not the semantic owner of normal release intent.
|
|
460
|
+
*/
|
|
461
|
+
export function listReleaseSessions(
|
|
462
|
+
controllerHome: string,
|
|
463
|
+
options: { maxEntries?: number } = {},
|
|
464
|
+
): ReleaseSessionInventory {
|
|
465
|
+
const root = releaseSessionInventoryRoot(controllerHome);
|
|
466
|
+
if (!existsSync(root)) return { sessions: [], invalidSessionFiles: [], inspected: 0, truncated: false };
|
|
467
|
+
const maxEntries = Math.max(1, Math.floor(options.maxEntries ?? 512));
|
|
468
|
+
const names = readdirSync(root)
|
|
469
|
+
.filter((name) => name.endsWith('.json'))
|
|
470
|
+
.sort();
|
|
471
|
+
const selected = names.slice(0, maxEntries);
|
|
472
|
+
const sessions: ReleaseSession[] = [];
|
|
473
|
+
const invalidSessionFiles: string[] = [];
|
|
474
|
+
for (const name of selected) {
|
|
475
|
+
const sessionId = name.slice(0, -'.json'.length);
|
|
476
|
+
try {
|
|
477
|
+
const session = readReleaseSession(controllerHome, sessionId);
|
|
478
|
+
if (session) sessions.push(session);
|
|
479
|
+
else invalidSessionFiles.push(name);
|
|
480
|
+
} catch {
|
|
481
|
+
invalidSessionFiles.push(name);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
sessions,
|
|
486
|
+
invalidSessionFiles,
|
|
487
|
+
inspected: selected.length,
|
|
488
|
+
truncated: names.length > selected.length,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const RELEASE_SESSION_TERMINAL_PHASES = new Set<ReleaseSessionPhase>([
|
|
493
|
+
'known_good',
|
|
494
|
+
'rolled_back',
|
|
495
|
+
'failed',
|
|
496
|
+
]);
|
|
497
|
+
|
|
498
|
+
const RELEASE_SESSION_CANDIDATE_RETIRED_PHASES = new Set<ReleaseSessionPhase>([
|
|
499
|
+
'soaking',
|
|
500
|
+
...RELEASE_SESSION_TERMINAL_PHASES,
|
|
501
|
+
]);
|
|
502
|
+
|
|
503
|
+
export function releaseSessionIsTerminal(session: ReleaseSession): boolean {
|
|
504
|
+
return RELEASE_SESSION_TERMINAL_PHASES.has(session.phase);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* A soaking ReleaseSession may temporarily remain non-terminal while a repair
|
|
509
|
+
* successor is prepared. This relation is deliberately stricter than generic
|
|
510
|
+
* release equality: the predecessor must have durably cut over the exact
|
|
511
|
+
* release/authority revision that the successor freezes as Stable A. It
|
|
512
|
+
* therefore preserves the predecessor's rollback authority until the successor
|
|
513
|
+
* itself has committed a replacement cutover without permitting arbitrary
|
|
514
|
+
* multiple active ReleaseSessions.
|
|
515
|
+
*/
|
|
516
|
+
export function releaseSessionIsSoakingPredecessorOfStable(
|
|
517
|
+
session: ReleaseSession,
|
|
518
|
+
stableRelease: ReleaseSessionStableRelease,
|
|
519
|
+
): boolean {
|
|
520
|
+
const candidate = session.candidateRelease;
|
|
521
|
+
const transaction = session.transaction;
|
|
522
|
+
return session.phase === 'soaking'
|
|
523
|
+
&& Boolean(candidate)
|
|
524
|
+
&& Boolean(transaction)
|
|
525
|
+
&& transaction!.candidateReleaseId === candidate!.releaseId
|
|
526
|
+
&& transaction!.cutoverAuthorityRevision === stableRelease.authorityRevision
|
|
527
|
+
&& candidate!.releaseId === stableRelease.releaseId
|
|
528
|
+
&& candidate!.artifactIdentity === stableRelease.artifactIdentity
|
|
529
|
+
&& candidate!.manifestSha256 === stableRelease.manifestSha256;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Emergency Recovery may publish a later whole-Runtime authority outside the
|
|
534
|
+
* original soaking ReleaseSession. The old session is superseded only when the
|
|
535
|
+
* active authority revision has strictly advanced beyond its cutover and the
|
|
536
|
+
* active stable identity is no longer that exact candidate. Equal/older or
|
|
537
|
+
* partially-matching identities remain ambiguous and must stay fail-closed.
|
|
538
|
+
*/
|
|
539
|
+
export function releaseSessionIsSoakingSupersededByStableAuthority(
|
|
540
|
+
session: ReleaseSession,
|
|
541
|
+
stableRelease: ReleaseSessionStableRelease,
|
|
542
|
+
): boolean {
|
|
543
|
+
const candidate = session.candidateRelease;
|
|
544
|
+
const transaction = session.transaction;
|
|
545
|
+
if (
|
|
546
|
+
session.phase !== 'soaking'
|
|
547
|
+
|| !candidate
|
|
548
|
+
|| !transaction
|
|
549
|
+
|| transaction.candidateReleaseId !== candidate.releaseId
|
|
550
|
+
|| stableRelease.authorityRevision <= transaction.cutoverAuthorityRevision
|
|
551
|
+
) return false;
|
|
552
|
+
return candidate.releaseId !== stableRelease.releaseId
|
|
553
|
+
|| candidate.artifactIdentity !== stableRelease.artifactIdentity
|
|
554
|
+
|| candidate.manifestSha256 !== stableRelease.manifestSha256;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Durable semantic proof that Candidate B is no longer required as a mutable
|
|
559
|
+
* Controller Home. This does not itself authorize deletion: cleanup must still
|
|
560
|
+
* prove the exact fenced path and absence of a live Runtime owner.
|
|
561
|
+
*/
|
|
562
|
+
export function releaseSessionCandidateIsRetired(session: ReleaseSession): boolean {
|
|
563
|
+
return RELEASE_SESSION_CANDIDATE_RETIRED_PHASES.has(session.phase);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function createReleaseSession(input: {
|
|
567
|
+
controllerHome: string;
|
|
568
|
+
sessionId: string;
|
|
569
|
+
stable: StableExecutionLane;
|
|
570
|
+
stableRelease: ReleaseSessionStableRelease;
|
|
571
|
+
candidate: CandidateExecutionLane;
|
|
572
|
+
sourceRevision: string;
|
|
573
|
+
}): ReleaseSession {
|
|
574
|
+
const sessionId = validSessionId(input.sessionId);
|
|
575
|
+
const path = sessionPath(input.controllerHome, sessionId);
|
|
576
|
+
if (existsSync(path)) throw new Error('RELEASE_SESSION_ALREADY_EXISTS');
|
|
577
|
+
const inventory = listCompleteReleaseSessions(input.controllerHome);
|
|
578
|
+
if (inventory.invalidSessionFiles.length > 0) {
|
|
579
|
+
throw new Error(`RELEASE_SESSION_INVENTORY_INCOMPLETE: invalid=${inventory.invalidSessionFiles.join(',')}`);
|
|
580
|
+
}
|
|
581
|
+
const active = inventory.sessions.filter((session) => !releaseSessionIsTerminal(session));
|
|
582
|
+
const sourceRevision = input.sourceRevision.trim();
|
|
583
|
+
const allowedRepairPredecessor = active.length === 1
|
|
584
|
+
&& active[0] !== undefined
|
|
585
|
+
&& releaseSessionIsSoakingPredecessorOfStable(active[0], input.stableRelease)
|
|
586
|
+
&& active[0].sourceRevision !== sourceRevision;
|
|
587
|
+
if (active.length > 0 && !allowedRepairPredecessor) {
|
|
588
|
+
throw new Error(`RELEASE_SESSION_ACTIVE_EXISTS: ${active.map((session) => `${session.sessionId}:${session.phase}`).join(',')}`);
|
|
589
|
+
}
|
|
590
|
+
if (resolve(input.stable.controllerHome) === resolve(input.candidate.controllerHome)) throw new Error('RELEASE_SESSION_LANE_COLLISION');
|
|
591
|
+
const timestamp = new Date().toISOString();
|
|
592
|
+
const session: ReleaseSession = {
|
|
593
|
+
schemaVersion: 1,
|
|
594
|
+
semanticEpoch: 2,
|
|
595
|
+
sessionId,
|
|
596
|
+
stable: input.stable,
|
|
597
|
+
stableRelease: input.stableRelease,
|
|
598
|
+
candidate: input.candidate,
|
|
599
|
+
sourceRevision,
|
|
600
|
+
phase: 'source_frozen',
|
|
601
|
+
revision: 1,
|
|
602
|
+
receipts: [{ id: `source:${sessionId}`, kind: 'source', recordedAt: timestamp, summary: `source frozen at ${input.sourceRevision.trim()}` }],
|
|
603
|
+
createdAt: timestamp,
|
|
604
|
+
updatedAt: timestamp,
|
|
605
|
+
};
|
|
606
|
+
if (!session.sourceRevision) throw new Error('RELEASE_SESSION_SOURCE_REVISION_REQUIRED');
|
|
607
|
+
if (
|
|
608
|
+
!Number.isInteger(session.stableRelease.authorityRevision)
|
|
609
|
+
|| session.stableRelease.authorityRevision < 1
|
|
610
|
+
|| !session.stableRelease.releaseId
|
|
611
|
+
|| !session.stableRelease.artifactIdentity
|
|
612
|
+
|| !session.stableRelease.manifestSha256
|
|
613
|
+
|| !Number.isInteger(session.stableRelease.workerProtocolVersion)
|
|
614
|
+
|| session.stableRelease.workerProtocolVersion < 1
|
|
615
|
+
|| !session.stableRelease.releaseFencingTokenSha256
|
|
616
|
+
) throw new Error('RELEASE_SESSION_STABLE_RELEASE_IDENTITY_INVALID');
|
|
617
|
+
writeSession(path, session);
|
|
618
|
+
return session;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function receiptIds(session: ReleaseSession, kind: ReleaseSessionReceipt['kind']): Set<string> {
|
|
622
|
+
return new Set(session.receipts.filter((receipt) => receipt.kind === kind).map((receipt) => receipt.id));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function assertTransition(session: ReleaseSession, phase: ReleaseSessionPhase): void {
|
|
626
|
+
const order: ReleaseSessionPhase[] = ['source_frozen', 'built', 'static_verified', 'candidate_booted', 'candidate_verified', 'cutover_eligible', 'cutover_attempting', 'cutover_committed', 'soaking', 'known_good'];
|
|
627
|
+
if (session.phase === 'failed' || session.phase === 'rolled_back' || session.phase === 'known_good') {
|
|
628
|
+
throw new Error('RELEASE_SESSION_TERMINAL');
|
|
629
|
+
}
|
|
630
|
+
if (phase === 'failed') return;
|
|
631
|
+
if (phase === 'rolled_back') {
|
|
632
|
+
if (order.indexOf(session.phase) < order.indexOf('cutover_attempting')) {
|
|
633
|
+
throw new Error('RELEASE_SESSION_ROLLBACK_BEFORE_CUTOVER_ATTEMPT');
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (order.indexOf(phase) !== order.indexOf(session.phase) + 1) throw new Error('RELEASE_SESSION_TRANSITION_INVALID');
|
|
638
|
+
if ((phase === 'built' || phase === 'static_verified') && !session.candidateRelease) {
|
|
639
|
+
throw new Error('RELEASE_SESSION_CANDIDATE_RELEASE_REQUIRED');
|
|
640
|
+
}
|
|
641
|
+
if (phase === 'static_verified' && [...REQUIRED_STATIC_GATES].some((id) => !receiptIds(session, 'static_gate').has(id))) {
|
|
642
|
+
throw new Error('RELEASE_SESSION_STATIC_GATES_INCOMPLETE');
|
|
643
|
+
}
|
|
644
|
+
if (phase === 'candidate_verified' && [...REQUIRED_CANDIDATE_CANARIES].some((id) => !receiptIds(session, 'candidate_canary').has(id))) {
|
|
645
|
+
throw new Error('RELEASE_SESSION_CANDIDATE_CANARIES_INCOMPLETE');
|
|
646
|
+
}
|
|
647
|
+
if (phase === 'known_good' && [...REQUIRED_KNOWN_GOOD_SOAK_GATES].some((id) => !receiptIds(session, 'soak').has(id))) {
|
|
648
|
+
throw new Error('RELEASE_SESSION_SOAK_GATES_INCOMPLETE');
|
|
649
|
+
}
|
|
650
|
+
if (phase === 'cutover_eligible' && (session.stable.controllerHome === session.candidate.controllerHome || session.stable.port === session.candidate.port)) {
|
|
651
|
+
throw new Error('RELEASE_SESSION_LANE_COLLISION');
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* The release coordinator calls this while the executing provider holds the
|
|
657
|
+
* appropriate mutation lock. expectedRevision is a CAS fence, so a stale
|
|
658
|
+
* observer cannot advance a newer session.
|
|
659
|
+
*/
|
|
660
|
+
export function recordReleaseSessionTransaction(input: {
|
|
661
|
+
controllerHome: string;
|
|
662
|
+
sessionId: string;
|
|
663
|
+
expectedRevision: number;
|
|
664
|
+
transaction: ReleaseSessionTransaction;
|
|
665
|
+
}): ReleaseSession {
|
|
666
|
+
const current = readReleaseSession(input.controllerHome, input.sessionId);
|
|
667
|
+
if (!current) throw new Error('RELEASE_SESSION_MISSING');
|
|
668
|
+
if (current.phase !== 'cutover_attempting') throw new Error(`RELEASE_SESSION_TRANSACTION_REQUIRES_CUTOVER_ATTEMPTING: ${current.phase}`);
|
|
669
|
+
// Runtime startup migration and the Recovery activation writer are two
|
|
670
|
+
// legitimate physical writers for this one bounded transaction. If the
|
|
671
|
+
// migration already persisted the exact transaction, accept its durable
|
|
672
|
+
// result instead of treating the activation's stale local revision as a
|
|
673
|
+
// competing semantic update. Any different transaction remains fenced.
|
|
674
|
+
if (current.transaction && sameReleaseSessionTransactionIdentity(current.transaction, input.transaction)) {
|
|
675
|
+
return current;
|
|
676
|
+
}
|
|
677
|
+
if (current.revision !== input.expectedRevision) throw new Error('RELEASE_SESSION_REVISION_FENCED');
|
|
678
|
+
const next = {
|
|
679
|
+
...current,
|
|
680
|
+
transaction: input.transaction,
|
|
681
|
+
revision: current.revision + 1,
|
|
682
|
+
updatedAt: new Date().toISOString(),
|
|
683
|
+
} satisfies ReleaseSession;
|
|
684
|
+
if (!validTransaction(next)) throw new Error('RELEASE_SESSION_TRANSACTION_INVALID');
|
|
685
|
+
writeSession(sessionPath(input.controllerHome, current.sessionId), next);
|
|
686
|
+
return next;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export function advanceReleaseSession(input: {
|
|
690
|
+
controllerHome: string;
|
|
691
|
+
sessionId: string;
|
|
692
|
+
expectedRevision: number;
|
|
693
|
+
phase: ReleaseSessionPhase;
|
|
694
|
+
candidateRelease?: ReleaseSessionCandidateRelease;
|
|
695
|
+
receipts?: Array<Omit<ReleaseSessionReceipt, 'recordedAt'>>;
|
|
696
|
+
}): ReleaseSession {
|
|
697
|
+
const current = readReleaseSession(input.controllerHome, input.sessionId);
|
|
698
|
+
if (!current) throw new Error('RELEASE_SESSION_MISSING');
|
|
699
|
+
if (current.revision !== input.expectedRevision) throw new Error('RELEASE_SESSION_REVISION_FENCED');
|
|
700
|
+
const recordedAt = new Date().toISOString();
|
|
701
|
+
const receipts = [...current.receipts, ...(input.receipts ?? []).map((receipt) => ({ ...receipt, recordedAt }))].slice(-64);
|
|
702
|
+
const candidateRelease = input.candidateRelease ?? current.candidateRelease;
|
|
703
|
+
if (candidateRelease && (
|
|
704
|
+
!candidateRelease.releaseId
|
|
705
|
+
|| !candidateRelease.manifestPath
|
|
706
|
+
|| !candidateRelease.artifactIdentity
|
|
707
|
+
|| !candidateRelease.manifestSha256
|
|
708
|
+
|| !/^[a-f0-9]{64}$/i.test(candidateRelease.treeSha256)
|
|
709
|
+
|| candidateRelease.sourceCommit !== current.sourceRevision
|
|
710
|
+
)) throw new Error('RELEASE_SESSION_CANDIDATE_RELEASE_IDENTITY_INVALID');
|
|
711
|
+
const proposed: ReleaseSession = { ...current, ...(candidateRelease ? { candidateRelease } : {}), receipts };
|
|
712
|
+
assertTransition(proposed, input.phase);
|
|
713
|
+
const next: ReleaseSession = { ...proposed, phase: input.phase, revision: current.revision + 1, updatedAt: recordedAt };
|
|
714
|
+
writeSession(sessionPath(input.controllerHome, current.sessionId), next);
|
|
715
|
+
return next;
|
|
716
|
+
}
|