@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
|
@@ -1,32 +1,52 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
2
3
|
import { createServer, type IncomingMessage, type ServerResponse } from 'http';
|
|
3
4
|
import { basename, join, resolve } from 'path';
|
|
4
5
|
import { pathToFileURL } from 'url';
|
|
5
|
-
import { readMcpServiceOAuthPassphrase } from '
|
|
6
|
+
import { readMcpServiceOAuthPassphrase } from '../../../adapters/mcp/auth';
|
|
6
7
|
import { FORGE_VERSION } from '../../version';
|
|
8
|
+
import type { Tool } from '@modelcontextprotocol/server';
|
|
9
|
+
import { RecoveryMcpServer } from './mcp-server';
|
|
7
10
|
import {
|
|
8
11
|
activateRuntimeRelease,
|
|
12
|
+
activatePinnedRuntimeRelease,
|
|
13
|
+
bootAndVerifyConfiguredRuntimeReleaseSessionCandidate,
|
|
14
|
+
cancelConfiguredRuntimeReleaseSession,
|
|
15
|
+
cutoverConfiguredRuntimeReleaseSession,
|
|
16
|
+
assertRecoveryMutationIdentity,
|
|
9
17
|
attestKnownGood,
|
|
18
|
+
configuredRuntimeReleaseSourceState,
|
|
19
|
+
measureConfiguredRuntimePerformance,
|
|
20
|
+
RECOVERY_INTERNAL_PERFORMANCE_COMMAND,
|
|
10
21
|
diagnose,
|
|
11
22
|
gatewayToken,
|
|
23
|
+
listRecoveryHistory,
|
|
12
24
|
listReleases,
|
|
25
|
+
pinRuntimeRelease,
|
|
26
|
+
prepareConfiguredRuntimeReleaseSession,
|
|
27
|
+
promoteConfiguredRuntimeReleaseSessionKnownGood,
|
|
28
|
+
rollbackConfiguredRuntimeReleaseSession,
|
|
13
29
|
loadRecoveryConfig,
|
|
14
30
|
loadWatchdogState,
|
|
15
31
|
saveWatchdogState,
|
|
16
32
|
reconnectMain,
|
|
17
33
|
recoverPrimaryRuntime,
|
|
34
|
+
recoveryMachineIdentity,
|
|
18
35
|
repairPublicTunnel,
|
|
19
36
|
restartPrimaryConnector,
|
|
20
37
|
restartPrimaryRuntime,
|
|
21
|
-
restartRecoveryWatchdog,
|
|
22
38
|
stageAndActivateConfiguredRuntimeRelease,
|
|
39
|
+
unpinRuntimeRelease,
|
|
23
40
|
rollbackPrevious,
|
|
24
41
|
secureEqual,
|
|
25
42
|
runtimeStatus,
|
|
26
43
|
verifyStableRuntime,
|
|
44
|
+
verifyConfiguredRuntimeReleaseSessionStaticGates,
|
|
45
|
+
probeReleaseCutoverReadiness,
|
|
27
46
|
watchdogTick,
|
|
28
47
|
type WatchdogState,
|
|
29
48
|
type RecoveryConfig,
|
|
49
|
+
type RecoveryMachineIdentity,
|
|
30
50
|
} from './core';
|
|
31
51
|
import {
|
|
32
52
|
createRecoveryWatchdogHeartbeat,
|
|
@@ -36,10 +56,40 @@ import {
|
|
|
36
56
|
} from './watchdog-heartbeat';
|
|
37
57
|
import {
|
|
38
58
|
RECOVERY_RELEASE_ROLE_CANARY_ARG,
|
|
59
|
+
readCurrentRecoveryRelease,
|
|
60
|
+
recoveryGatewayIsDraining,
|
|
39
61
|
writeRecoveryRuntimeIdentity,
|
|
40
62
|
type RecoveryRuntimeIdentity,
|
|
41
63
|
type RecoveryRuntimeRole,
|
|
42
64
|
} from './release';
|
|
65
|
+
import { RECOVERY_MUTATION_IDENTITY_CONTRACT, RECOVERY_MUTATION_IDENTITY_FIELDS } from './mutation-identity-contract';
|
|
66
|
+
import { readReleaseSession } from '../release/release-session';
|
|
67
|
+
import { migrateReleaseDurableState } from '../release/release-state-migration';
|
|
68
|
+
import {
|
|
69
|
+
advanceConfiguredRuntimeRelease,
|
|
70
|
+
advanceConfiguredRuntimeReleaseStep,
|
|
71
|
+
decideConfiguredRuntimeReleaseReconciliation,
|
|
72
|
+
type RuntimeReleaseProvider,
|
|
73
|
+
} from '../release/release-coordinator';
|
|
74
|
+
import { runBoundedChild } from '../shared/bounded-child-supervisor';
|
|
75
|
+
import { runtimeAuthorityFreeEnvironment } from '../shared/process-environment';
|
|
76
|
+
|
|
77
|
+
const RECOVERY_RUNTIME_RELEASE_PROVIDER: RuntimeReleaseProvider<RecoveryConfig> = {
|
|
78
|
+
prepare: (config, requestId) => prepareConfiguredRuntimeReleaseSession(config, {}, requestId),
|
|
79
|
+
verifyStatic: (config, sessionId, requestId) => verifyConfiguredRuntimeReleaseSessionStaticGates(config, sessionId, requestId),
|
|
80
|
+
verifyCandidate: (config, sessionId, requestId) => bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, sessionId, requestId),
|
|
81
|
+
cutover: (config, sessionId, requestId) => cutoverConfiguredRuntimeReleaseSession(config, sessionId, requestId),
|
|
82
|
+
promoteKnownGood: (config, sessionId, requestId) => promoteConfiguredRuntimeReleaseSessionKnownGood(config, sessionId, {}, requestId),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND = '__reconcile-runtime-release-step';
|
|
86
|
+
const RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS = 15_000;
|
|
87
|
+
/**
|
|
88
|
+
* Upper bound for retrying one non-converging release step. A stuck release must
|
|
89
|
+
* still be retried, but never at a cadence that can starve the daemon that owns it.
|
|
90
|
+
*/
|
|
91
|
+
const RECOVERY_AUTOMATIC_RELEASE_FAILURE_BACKOFF_MAX_MS = 15 * 60_000;
|
|
92
|
+
const RECOVERY_AUTOMATIC_RELEASE_STEP_TIMEOUT_MS = 15 * 60_000;
|
|
43
93
|
|
|
44
94
|
function option(name: string): string | undefined {
|
|
45
95
|
const index = process.argv.indexOf(name);
|
|
@@ -54,8 +104,11 @@ function controllerHome(): string {
|
|
|
54
104
|
|
|
55
105
|
function output(value: unknown): void { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); }
|
|
56
106
|
|
|
107
|
+
import { runRecoveryControllerHomeMigrationWorker, scheduleRecoveryControllerHomeMigration } from './controller-home-migration';
|
|
108
|
+
|
|
57
109
|
export const RECOVERY_CLI_COMMANDS = [
|
|
58
110
|
'status',
|
|
111
|
+
'daemon',
|
|
59
112
|
'verify',
|
|
60
113
|
'verify-external',
|
|
61
114
|
'list-releases',
|
|
@@ -66,6 +119,16 @@ export const RECOVERY_CLI_COMMANDS = [
|
|
|
66
119
|
'recover-primary-runtime',
|
|
67
120
|
'activate-runtime-release',
|
|
68
121
|
'stage-and-activate-runtime-release',
|
|
122
|
+
'release-session-status',
|
|
123
|
+
'release-session-advance',
|
|
124
|
+
'release-session-prepare',
|
|
125
|
+
'release-session-static-verify',
|
|
126
|
+
'release-session-candidate-verify',
|
|
127
|
+
'release-session-cutover',
|
|
128
|
+
'release-session-cancel',
|
|
129
|
+
'release-session-rollback',
|
|
130
|
+
'release-session-known-good',
|
|
131
|
+
'migrate-controller-home-worker',
|
|
69
132
|
'restart-public-tunnel',
|
|
70
133
|
'diagnose',
|
|
71
134
|
'reconnect-main',
|
|
@@ -85,6 +148,10 @@ export function recoveryRuntimeRoleFromExecutable(executable = process.execPath)
|
|
|
85
148
|
async function cli(): Promise<void> {
|
|
86
149
|
const command = process.argv.find((value, index) => index >= 2 && !value.startsWith('-') && process.argv[index - 1] !== '--controller-home') ?? 'status';
|
|
87
150
|
const config = loadRecoveryConfig(controllerHome(), option('--config'));
|
|
151
|
+
// Internal durable state has one current schema. Every Recovery entrypoint,
|
|
152
|
+
// including gateway/watchdog startup, crosses the same migration boundary as
|
|
153
|
+
// Canonical Runtime before reading or mutating release semantics.
|
|
154
|
+
migrateReleaseDurableState(config.controllerHome);
|
|
88
155
|
const executableRole = recoveryRuntimeRoleFromExecutable();
|
|
89
156
|
if (executableRole) {
|
|
90
157
|
if (command !== executableRole) {
|
|
@@ -98,8 +165,52 @@ async function cli(): Promise<void> {
|
|
|
98
165
|
else await startWatchdog(config);
|
|
99
166
|
return;
|
|
100
167
|
}
|
|
168
|
+
if (command === RECOVERY_INTERNAL_PERFORMANCE_COMMAND) {
|
|
169
|
+
try {
|
|
170
|
+
output({ schemaVersion: 1, ok: true, evidence: await measureConfiguredRuntimePerformance(config) });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
173
|
+
output({
|
|
174
|
+
schemaVersion: 1,
|
|
175
|
+
ok: false,
|
|
176
|
+
error: /^RECOVERY_PERFORMANCE_(?:UNKNOWN|REJECTED):/.test(detail)
|
|
177
|
+
? detail
|
|
178
|
+
: 'RECOVERY_PERFORMANCE_UNKNOWN: isolated sampler failed',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (command === RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND) {
|
|
184
|
+
try {
|
|
185
|
+
const decision = decideConfiguredRuntimeReleaseReconciliation(
|
|
186
|
+
config.controllerHome,
|
|
187
|
+
() => configuredRuntimeReleaseSourceState(config),
|
|
188
|
+
);
|
|
189
|
+
if (!decision.required) {
|
|
190
|
+
output({ schemaVersion: 1, ok: true, attempted: false, noOp: true, decision });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const result = await advanceConfiguredRuntimeReleaseStep(
|
|
194
|
+
config,
|
|
195
|
+
RECOVERY_RUNTIME_RELEASE_PROVIDER,
|
|
196
|
+
`recovery-auto-release:${process.pid}:${Date.now()}`,
|
|
197
|
+
);
|
|
198
|
+
output({ schemaVersion: 1, ok: result.ok, attempted: result.attempted, decision, result });
|
|
199
|
+
} catch (error) {
|
|
200
|
+
output({ schemaVersion: 1, ok: false, attempted: false, error: error instanceof Error ? error.message : String(error) });
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
101
204
|
switch (command) {
|
|
102
205
|
case 'status': output(await runtimeStatus(config)); return;
|
|
206
|
+
case 'daemon': {
|
|
207
|
+
if (process.argv.includes(RECOVERY_RELEASE_ROLE_CANARY_ARG)) {
|
|
208
|
+
output({ status: 'ok', role: 'daemon', executable: basename(process.execPath) });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
await startRecoveryDaemon(config);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
103
214
|
case 'verify': output(await verifyStableRuntime(config)); return;
|
|
104
215
|
case 'verify-external': {
|
|
105
216
|
const verified = await verifyStableRuntime(config);
|
|
@@ -109,7 +220,7 @@ async function cli(): Promise<void> {
|
|
|
109
220
|
case 'list-releases': output(await listReleases(config)); return;
|
|
110
221
|
case 'attest-known-good': output(await attestKnownGood(config)); return;
|
|
111
222
|
case 'rollback-previous': output(await rollbackPrevious(config)); return;
|
|
112
|
-
case 'restart-primary-runtime': output(await restartPrimaryRuntime(config)); return;
|
|
223
|
+
case 'restart-primary-runtime': output(await restartPrimaryRuntime(config, { forceHealthyRestart: true })); return;
|
|
113
224
|
case 'restart-primary-connector': output(await restartPrimaryConnector(config)); return;
|
|
114
225
|
case 'recover-primary-runtime': output(await recoverPrimaryRuntime(config)); return;
|
|
115
226
|
case 'activate-runtime-release': {
|
|
@@ -128,6 +239,64 @@ async function cli(): Promise<void> {
|
|
|
128
239
|
return;
|
|
129
240
|
}
|
|
130
241
|
case 'stage-and-activate-runtime-release': output(await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-cli:${process.pid}:${Date.now()}`)); return;
|
|
242
|
+
case 'release-session-status': {
|
|
243
|
+
const sessionId = option('--session-id');
|
|
244
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
245
|
+
output(readReleaseSession(config.controllerHome, sessionId) ?? null);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
case 'release-session-advance': output(await advanceConfiguredRuntimeRelease(config, RECOVERY_RUNTIME_RELEASE_PROVIDER, `recovery-cli:${process.pid}:${Date.now()}`)); return;
|
|
249
|
+
case 'release-session-prepare': output(await prepareConfiguredRuntimeReleaseSession(config, {}, `recovery-cli:${process.pid}:${Date.now()}`)); return;
|
|
250
|
+
case 'release-session-static-verify': {
|
|
251
|
+
const sessionId = option('--session-id');
|
|
252
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
253
|
+
output(await verifyConfiguredRuntimeReleaseSessionStaticGates(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
case 'release-session-candidate-verify': {
|
|
257
|
+
const sessionId = option('--session-id');
|
|
258
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
259
|
+
output(await bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
case 'release-session-cutover': {
|
|
263
|
+
const sessionId = option('--session-id');
|
|
264
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
265
|
+
output(await cutoverConfiguredRuntimeReleaseSession(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
case 'release-session-cancel': {
|
|
269
|
+
const sessionId = option('--session-id');
|
|
270
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
271
|
+
output(await cancelConfiguredRuntimeReleaseSession(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
case 'release-session-rollback': {
|
|
275
|
+
const sessionId = option('--session-id');
|
|
276
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
277
|
+
output(await rollbackConfiguredRuntimeReleaseSession(config, sessionId, {}, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
case 'release-session-known-good': {
|
|
281
|
+
const sessionId = option('--session-id');
|
|
282
|
+
if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
283
|
+
output(await promoteConfiguredRuntimeReleaseSessionKnownGood(config, sessionId, {}, `recovery-cli:${process.pid}:${Date.now()}`));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
case 'migrate-controller-home-worker': {
|
|
287
|
+
const canonicalSourceRoot = option('--canonical-source-root');
|
|
288
|
+
const expectedSourceRevision = option('--expected-source-revision');
|
|
289
|
+
const migrationRequestId = option('--request-id');
|
|
290
|
+
if (!canonicalSourceRoot) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REQUIRED');
|
|
291
|
+
if (!expectedSourceRevision) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_REQUIRED');
|
|
292
|
+
if (!migrationRequestId) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
293
|
+
output(await runRecoveryControllerHomeMigrationWorker(config, {
|
|
294
|
+
requestId: migrationRequestId,
|
|
295
|
+
canonicalSourceRoot,
|
|
296
|
+
expectedSourceRevision,
|
|
297
|
+
}));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
131
300
|
case 'restart-public-tunnel': output(await repairPublicTunnel(config)); return;
|
|
132
301
|
case 'diagnose': output(await diagnose(config)); return;
|
|
133
302
|
case 'reconnect-main': output(await reconnectMain(config)); return;
|
|
@@ -155,8 +324,8 @@ export function resetWatchdogStateForRecoveryRelease(
|
|
|
155
324
|
};
|
|
156
325
|
}
|
|
157
326
|
|
|
158
|
-
async function startWatchdog(config: RecoveryConfig): Promise<void> {
|
|
159
|
-
const runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'watchdog');
|
|
327
|
+
async function startWatchdog(config: RecoveryConfig, daemonIdentity?: RecoveryRuntimeIdentity): Promise<void> {
|
|
328
|
+
const runtimeIdentity = daemonIdentity ?? writeRecoveryRuntimeIdentity(config.controllerHome, 'watchdog');
|
|
160
329
|
let heartbeat: RecoveryWatchdogHeartbeat = createRecoveryWatchdogHeartbeat(runtimeIdentity);
|
|
161
330
|
const persistHeartbeat = (patch: Partial<RecoveryWatchdogHeartbeat> = {}) => {
|
|
162
331
|
heartbeat = writeRecoveryWatchdogHeartbeat(config.controllerHome, { ...heartbeat, ...patch });
|
|
@@ -224,7 +393,7 @@ function html(response: ServerResponse, status: number, payload: string): void {
|
|
|
224
393
|
|
|
225
394
|
function setCorsHeaders(response: ServerResponse): void {
|
|
226
395
|
response.setHeader('access-control-allow-origin', '*');
|
|
227
|
-
response.setHeader('access-control-allow-methods', 'GET, POST, OPTIONS');
|
|
396
|
+
response.setHeader('access-control-allow-methods', 'GET, POST, DELETE, OPTIONS');
|
|
228
397
|
response.setHeader('access-control-allow-headers', 'authorization, content-type, mcp-session-id, mcp-protocol-version');
|
|
229
398
|
response.setHeader('access-control-expose-headers', 'www-authenticate, mcp-session-id');
|
|
230
399
|
}
|
|
@@ -245,23 +414,59 @@ function matchesAnyPath(url: string | undefined, paths: string[]): boolean {
|
|
|
245
414
|
return paths.some((path) => matchesPath(url, path));
|
|
246
415
|
}
|
|
247
416
|
|
|
417
|
+
function mutationInputSchema(
|
|
418
|
+
extraProperties: Record<string, unknown> = {},
|
|
419
|
+
extraRequired: string[] = [],
|
|
420
|
+
): Record<string, unknown> {
|
|
421
|
+
return {
|
|
422
|
+
type: 'object',
|
|
423
|
+
properties: {
|
|
424
|
+
request_id: { type: 'string', minLength: 8, maxLength: 120 },
|
|
425
|
+
...RECOVERY_MUTATION_IDENTITY_CONTRACT,
|
|
426
|
+
...extraProperties,
|
|
427
|
+
},
|
|
428
|
+
required: ['request_id', ...RECOVERY_MUTATION_IDENTITY_FIELDS, ...extraRequired],
|
|
429
|
+
additionalProperties: false,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
248
433
|
export const RECOVERY_TOOLS = [
|
|
249
|
-
{ name: 'runtime_status', description: 'Read canonical Runtime ownership, readiness, endpoint, and
|
|
434
|
+
{ name: 'runtime_status', description: 'Read canonical Runtime ownership, readiness, endpoint, release observation, and exact Recovery machine identity.', inputSchema: { type: 'object', additionalProperties: false } },
|
|
435
|
+
{ name: 'list_recovery_history', description: 'Read a bounded sanitized tail of canonical standalone Recovery audit events.', inputSchema: { type: 'object', properties: { limit: { type: 'integer', minimum: 1, maximum: 50 } }, additionalProperties: false } },
|
|
250
436
|
{ name: 'list_releases', description: 'Read active, previous, and known-good whole-Runtime release evidence.', inputSchema: { type: 'object', additionalProperties: false } },
|
|
251
437
|
{ name: 'verify_stable_runtime', description: 'Run independent stable runtime verification.', inputSchema: { type: 'object', additionalProperties: false } },
|
|
252
438
|
{ name: 'verify_external_runtime', description: 'Verify the external primary MCP endpoint.', inputSchema: { type: 'object', additionalProperties: false } },
|
|
253
|
-
{ name: 'attest_known_good', description: 'Record the active release as known-good only after full independent verification succeeds.', inputSchema:
|
|
254
|
-
{ name: 'rollback_previous', description: 'While Canonical Runtime is stopped, atomically restore its attested previous whole-Runtime release and SQLite backup.', inputSchema:
|
|
255
|
-
{ name: 'restart_primary_runtime', description: '
|
|
256
|
-
{ name: 'restart_primary_connector', description: 'Restart the explicitly configured primary OAuth/Connector
|
|
257
|
-
{ name: 'recover_primary_runtime', description: 'Stop the canonical Runtime, restore the attested previous whole release and SQLite backup, restart it, and require verification.', inputSchema:
|
|
258
|
-
{ name: 'activate_runtime_release', description: 'Activate an already staged immutable Runtime release only if
|
|
259
|
-
{ name: '
|
|
260
|
-
{ name: '
|
|
439
|
+
{ name: 'attest_known_good', description: 'Record the active release as known-good only after full independent verification succeeds.', inputSchema: mutationInputSchema() },
|
|
440
|
+
{ name: 'rollback_previous', description: 'While Canonical Runtime is stopped, atomically restore its attested previous whole-Runtime release and SQLite backup.', inputSchema: mutationInputSchema() },
|
|
441
|
+
{ name: 'restart_primary_runtime', description: 'Explicitly restart the installed canonical Forge Runtime service, including when currently healthy, then require whole-Runtime verification after exact Recovery machine identity matches.', inputSchema: mutationInputSchema() },
|
|
442
|
+
{ name: 'restart_primary_connector', description: 'Restart the explicitly configured primary OAuth/Connector service only after exact Recovery machine identity and local Canonical Runtime verification succeed.', inputSchema: mutationInputSchema() },
|
|
443
|
+
{ name: 'recover_primary_runtime', description: 'Stop the canonical Runtime, restore the attested previous whole-Runtime release and SQLite backup, restart it, and require verification.', inputSchema: mutationInputSchema() },
|
|
444
|
+
{ name: 'activate_runtime_release', description: 'Activate an already staged immutable Runtime release only if machine identity and caller-observed active release/authority revision are still current. Reverse activation of current.previous is rejected; use rollback_previous/recover_primary_runtime instead.', inputSchema: mutationInputSchema({ release_path: { type: 'string', minLength: 8, maxLength: 1024, description: 'Absolute path to the staged immutable Runtime release directory.' }, expected_active_release_id: { type: 'string', minLength: 1, maxLength: 256 }, expected_authority_revision: { type: 'integer', minimum: 1 } }, ['release_path', 'expected_active_release_id', 'expected_authority_revision']) },
|
|
445
|
+
{ name: 'pin_runtime_release', description: 'Pin one extant legacy/home-bound immutable Runtime release so retention preserves it for explicit Runtime-only recovery activation. Portable source candidates are rejected and must use ReleaseSession.', inputSchema: mutationInputSchema({ release_path: { type: 'string', minLength: 8, maxLength: 1024, description: 'Absolute path to the immutable Runtime release directory or manifest.' } }, ['release_path']) },
|
|
446
|
+
{ name: 'unpin_runtime_release', description: 'Remove the explicit stable Runtime retention pin without deleting or activating any release.', inputSchema: mutationInputSchema() },
|
|
447
|
+
{ name: 'activate_pinned_runtime_release', description: 'Activate the explicitly pinned legacy/home-bound Runtime release without restoring an older SQLite backup; portable source candidates are rejected and must use ReleaseSession.', inputSchema: mutationInputSchema({ expected_active_release_id: { type: 'string', minLength: 1, maxLength: 256 }, expected_authority_revision: { type: 'integer', minimum: 1 } }, ['expected_active_release_id', 'expected_authority_revision']) },
|
|
448
|
+
{ name: 'stage_and_activate_runtime_release', description: 'Compatibility alias: freeze the fixed configured source, create isolated Candidate B, and build one portable immutable Runtime release into a durable ReleaseSession. It no longer activates Stable A.', inputSchema: mutationInputSchema() },
|
|
449
|
+
{ name: 'release_session_status', description: 'Read one durable ReleaseSession and its exact Stable A/Candidate B phase and evidence.', inputSchema: { type: 'object', properties: { session_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['session_id'], additionalProperties: false } },
|
|
450
|
+
{ name: 'advance_runtime_release_session', description: 'Run the single active normal Runtime ReleaseSession autonomously through all immediately executable phases until known-good or a genuine provider/safety boundary. ReleaseSession remains the sole durable progression authority and no Work is created per phase.', inputSchema: mutationInputSchema() },
|
|
451
|
+
{ name: 'prepare_runtime_release_session', description: 'Freeze configured source and Stable A authority, create isolated Candidate B, and build a portable byte-identifiable Runtime artifact without stopping Stable A.', inputSchema: mutationInputSchema() },
|
|
452
|
+
{ name: 'verify_runtime_release_session_static', description: 'Run canonical static gates on the frozen source revision and advance only that exact ReleaseSession.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
453
|
+
{ name: 'verify_runtime_release_session_candidate', description: 'Boot Candidate B in its isolated ControllerHome/service/port, run whole-Runtime and Recovery restart canaries, and mark the session cutover-eligible while Stable A stays active.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
454
|
+
{ name: 'cutover_runtime_release_session', description: 'Perform the single fenced cutover attempt for a cutover-eligible ReleaseSession using the byte-identical verified Candidate B artifact. Failed cutover restores exact Stable A and terminalizes without retry.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
455
|
+
{ name: 'cancel_runtime_release_session', description: 'Retire a superseded or rejected Candidate B before cutover, terminalizing the ReleaseSession with the existing failed state and leaving Stable A unchanged.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
456
|
+
{ name: 'rollback_runtime_release_session', description: 'Abort the exact in-flight ReleaseSession activation transaction and restore its frozen Stable A whole-Runtime release plus SQLite backup.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
457
|
+
{ name: 'promote_runtime_release_session_known_good', description: 'After committed cutover soak, require full verification and performance observation, create a recoverable release+SQLite+service bundle, and terminalize the ReleaseSession known-good.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
|
|
458
|
+
{ name: 'migrate_controller_home', description: 'Schedule a Linux-only standalone Recovery transaction that relocates this Forge installation to the stable user-level Controller Home, reinstalls immutable Runtime/Connector/Recovery owners, verifies them, and rolls back on failure.', inputSchema: mutationInputSchema({ canonical_source_root: { type: 'string', minLength: 1, maxLength: 1024 }, expected_source_revision: { type: 'string', minLength: 7, maxLength: 80 } }, ['canonical_source_root', 'expected_source_revision']) },
|
|
459
|
+
{ name: 'restart_public_tunnel', description: 'Restart the explicitly configured public tunnel only after exact Recovery machine identity and local runtime verification succeeds and the external endpoint is unavailable.', inputSchema: mutationInputSchema() },
|
|
261
460
|
{ name: 'reconnect_primary_connector', description: 'Check canonical Runtime Gateway and primary MCP reconnection readiness without publishing a release.', inputSchema: { type: 'object', additionalProperties: false } },
|
|
262
461
|
] as const;
|
|
263
462
|
|
|
264
463
|
const RECOVERY_OAUTH_SCOPE = 'forge';
|
|
464
|
+
export const RECOVERY_VERIFIER_OAUTH_CLIENT_ID = 'forge-recovery-verifier-v1';
|
|
465
|
+
export const RECOVERY_VERIFIER_OAUTH_CLIENT_NAME = 'Forge Recovery Verification';
|
|
466
|
+
export const RECOVERY_VERIFIER_OAUTH_REDIRECT_URI = 'http://127.0.0.1/forge-recovery-oauth-callback';
|
|
467
|
+
const RECOVERY_OAUTH_CODE_TTL_MS = 10 * 60_000;
|
|
468
|
+
const RECOVERY_MAX_PENDING_OAUTH_CODES = 512;
|
|
469
|
+
const RECOVERY_MAX_PENDING_OAUTH_CODES_PER_CLIENT = 32;
|
|
265
470
|
const RECOVERY_ACCESS_TOKEN_EXPIRES_IN_SECONDS = 10 * 365 * 24 * 60 * 60;
|
|
266
471
|
const TOOL_SECURITY_SCHEMES = [{ type: 'oauth2', scopes: [RECOVERY_OAUTH_SCOPE] }] as const;
|
|
267
472
|
const SUPPORTED_TOKEN_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none'] as const;
|
|
@@ -281,9 +486,55 @@ type OAuthClient = {
|
|
|
281
486
|
clientId: string;
|
|
282
487
|
clientSecret?: string;
|
|
283
488
|
tokenEndpointAuthMethod: TokenAuthMethod;
|
|
489
|
+
owner: 'recovery_verifier' | 'external';
|
|
284
490
|
createdAt: number;
|
|
285
491
|
};
|
|
286
492
|
|
|
493
|
+
function stringArray(value: unknown): string[] {
|
|
494
|
+
return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function recoveryOAuthClientRegistrationIdentity(
|
|
498
|
+
body: Record<string, unknown>,
|
|
499
|
+
generatedClientId: string = randomUUID(),
|
|
500
|
+
): { clientId: string; owner: 'recovery_verifier' | 'external' } {
|
|
501
|
+
const requestedClientId = typeof body.client_id === 'string' && body.client_id.trim() ? body.client_id.trim() : '';
|
|
502
|
+
if (requestedClientId === RECOVERY_VERIFIER_OAUTH_CLIENT_ID) {
|
|
503
|
+
const validVerifierMetadata = body.client_name === RECOVERY_VERIFIER_OAUTH_CLIENT_NAME
|
|
504
|
+
&& stringArray(body.redirect_uris).includes(RECOVERY_VERIFIER_OAUTH_REDIRECT_URI)
|
|
505
|
+
&& body.token_endpoint_auth_method === 'none'
|
|
506
|
+
&& stringArray(body.grant_types).includes('authorization_code')
|
|
507
|
+
&& stringArray(body.response_types).includes('code');
|
|
508
|
+
if (!validVerifierMetadata) throw new Error('RECOVERY_OAUTH_VERIFIER_CLIENT_METADATA_INVALID');
|
|
509
|
+
return { clientId: RECOVERY_VERIFIER_OAUTH_CLIENT_ID, owner: 'recovery_verifier' };
|
|
510
|
+
}
|
|
511
|
+
return { clientId: requestedClientId || generatedClientId, owner: 'external' };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function reserveRecoveryOAuthCodeCapacity(
|
|
515
|
+
codes: Map<string, PendingOAuthCode>,
|
|
516
|
+
clientId: string,
|
|
517
|
+
now = Date.now(),
|
|
518
|
+
): void {
|
|
519
|
+
for (const [code, pending] of codes) {
|
|
520
|
+
if (now - pending.createdAt > RECOVERY_OAUTH_CODE_TTL_MS) codes.delete(code);
|
|
521
|
+
}
|
|
522
|
+
const evictOldest = (predicate: (pending: PendingOAuthCode) => boolean): boolean => {
|
|
523
|
+
let oldest: { code: string; createdAt: number } | undefined;
|
|
524
|
+
for (const [code, pending] of codes) {
|
|
525
|
+
if (!predicate(pending)) continue;
|
|
526
|
+
if (!oldest || pending.createdAt < oldest.createdAt) oldest = { code, createdAt: pending.createdAt };
|
|
527
|
+
}
|
|
528
|
+
return oldest ? codes.delete(oldest.code) : false;
|
|
529
|
+
};
|
|
530
|
+
while ([...codes.values()].filter((pending) => pending.clientId === clientId).length >= RECOVERY_MAX_PENDING_OAUTH_CODES_PER_CLIENT) {
|
|
531
|
+
if (!evictOldest((pending) => pending.clientId === clientId)) break;
|
|
532
|
+
}
|
|
533
|
+
while (codes.size >= RECOVERY_MAX_PENDING_OAUTH_CODES) {
|
|
534
|
+
if (!evictOldest(() => true)) break;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
287
538
|
function readBody(request: IncomingMessage): Promise<string> {
|
|
288
539
|
return new Promise((resolveBody, reject) => {
|
|
289
540
|
let body = '';
|
|
@@ -365,7 +616,7 @@ export function classifyRecoveryMcpRequest(
|
|
|
365
616
|
if (!matchesAnyPath(request.url, ['/mcp', '/recovery/mcp'])) return 'not_mcp';
|
|
366
617
|
const supplied = request.headers.authorization?.replace(/^Bearer\s+/i, '').trim();
|
|
367
618
|
if (!expectedToken || !supplied || !secureEqual(supplied, expectedToken)) return 'auth_required';
|
|
368
|
-
if (request.method !== 'POST') return 'method_not_supported';
|
|
619
|
+
if (request.method !== 'POST' && request.method !== 'GET' && request.method !== 'DELETE') return 'method_not_supported';
|
|
369
620
|
return 'mcp';
|
|
370
621
|
}
|
|
371
622
|
|
|
@@ -485,71 +736,382 @@ function requestId(value: unknown): string | undefined {
|
|
|
485
736
|
return typeof value === 'string' && /^[A-Za-z0-9._:-]{8,120}$/.test(value) ? value : undefined;
|
|
486
737
|
}
|
|
487
738
|
|
|
739
|
+
function mutationResponse(config: RecoveryConfig, payload: unknown): Record<string, unknown> {
|
|
740
|
+
const result = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : { result: payload };
|
|
741
|
+
return { ...result, identity: recoveryMachineIdentity(config) };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function assertRecoveryGatewayMutationIdentity(config: RecoveryConfig, args: Record<string, unknown>): RecoveryMachineIdentity {
|
|
745
|
+
const suppliedFields = RECOVERY_MUTATION_IDENTITY_FIELDS.filter((field) => typeof args[field] === 'string' && String(args[field]).trim());
|
|
746
|
+
if (suppliedFields.length === 0) return recoveryMachineIdentity(config);
|
|
747
|
+
return assertRecoveryMutationIdentity(config, args);
|
|
748
|
+
}
|
|
749
|
+
|
|
488
750
|
export async function dispatchRecoveryTool(config: RecoveryConfig, name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
489
751
|
switch (name) {
|
|
490
752
|
case 'runtime_status': return runtimeStatus(config);
|
|
753
|
+
case 'list_recovery_history': return listRecoveryHistory(config, typeof args.limit === 'number' ? args.limit : 20);
|
|
491
754
|
case 'list_releases': return listReleases(config);
|
|
492
755
|
case 'verify_stable_runtime': return verifyStableRuntime(config);
|
|
493
756
|
case 'verify_external_runtime': {
|
|
494
757
|
const verified = await verifyStableRuntime(config);
|
|
495
|
-
|
|
758
|
+
const externalConfigured = Boolean(config.publicMcpUrl);
|
|
759
|
+
return {
|
|
760
|
+
ok: externalConfigured ? verified.probes.external_mcp_http?.ok === true : verified.ok,
|
|
761
|
+
externalConfigured,
|
|
762
|
+
external: verified.probes.external_mcp_http,
|
|
763
|
+
mcp: verified.probes.mcp_initialize,
|
|
764
|
+
};
|
|
496
765
|
}
|
|
497
766
|
case 'attest_known_good': {
|
|
498
767
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
499
|
-
|
|
768
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
769
|
+
return mutationResponse(config, await attestKnownGood(config));
|
|
500
770
|
}
|
|
501
771
|
case 'rollback_previous': {
|
|
502
772
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
503
|
-
|
|
773
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
774
|
+
return mutationResponse(config, await rollbackPrevious(config, `recovery-gateway:${args.request_id}`));
|
|
504
775
|
}
|
|
505
776
|
case 'restart_primary_runtime': {
|
|
506
777
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
507
|
-
|
|
778
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
779
|
+
return mutationResponse(config, await restartPrimaryRuntime(config, { forceHealthyRestart: true }));
|
|
508
780
|
}
|
|
509
781
|
case 'restart_primary_connector': {
|
|
510
782
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
511
|
-
|
|
783
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
784
|
+
return mutationResponse(config, await restartPrimaryConnector(config, { requestId: `recovery-gateway:${args.request_id}` }));
|
|
512
785
|
}
|
|
513
786
|
case 'recover_primary_runtime': {
|
|
514
787
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
515
|
-
|
|
788
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
789
|
+
return mutationResponse(config, await recoverPrimaryRuntime(config, `recovery-gateway:${args.request_id}`));
|
|
516
790
|
}
|
|
517
791
|
case 'activate_runtime_release': {
|
|
518
792
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
793
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
519
794
|
if (typeof args.release_path !== 'string' || !args.release_path.trim()) throw new Error('RECOVERY_RELEASE_PATH_REQUIRED');
|
|
520
795
|
if (typeof args.expected_active_release_id !== 'string' || !args.expected_active_release_id.trim()) throw new Error('RECOVERY_EXPECTED_ACTIVE_RELEASE_REQUIRED');
|
|
521
796
|
if (!Number.isInteger(args.expected_authority_revision) || Number(args.expected_authority_revision) < 1) throw new Error('RECOVERY_EXPECTED_AUTHORITY_REVISION_REQUIRED');
|
|
522
797
|
const releasePath = args.release_path.trim();
|
|
523
798
|
const manifestPath = basename(releasePath) === 'manifest.json' ? releasePath : join(releasePath, 'manifest.json');
|
|
524
|
-
return activateRuntimeRelease(config, manifestPath, {}, {
|
|
799
|
+
return mutationResponse(config, await activateRuntimeRelease(config, manifestPath, {}, {
|
|
525
800
|
requestId: `recovery-gateway:${args.request_id}`,
|
|
526
801
|
expectedActiveReleaseId: args.expected_active_release_id.trim(),
|
|
527
802
|
expectedAuthorityRevision: Number(args.expected_authority_revision),
|
|
528
|
-
});
|
|
803
|
+
}));
|
|
804
|
+
}
|
|
805
|
+
case 'pin_runtime_release': {
|
|
806
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
807
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
808
|
+
if (typeof args.release_path !== 'string' || !args.release_path.trim()) throw new Error('RECOVERY_RELEASE_PATH_REQUIRED');
|
|
809
|
+
const releasePath = args.release_path.trim();
|
|
810
|
+
const manifestPath = basename(releasePath) === 'manifest.json' ? releasePath : join(releasePath, 'manifest.json');
|
|
811
|
+
return mutationResponse(config, await pinRuntimeRelease(config, manifestPath, `recovery-gateway:${args.request_id}`));
|
|
812
|
+
}
|
|
813
|
+
case 'unpin_runtime_release': {
|
|
814
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
815
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
816
|
+
return mutationResponse(config, await unpinRuntimeRelease(config, `recovery-gateway:${args.request_id}`));
|
|
817
|
+
}
|
|
818
|
+
case 'activate_pinned_runtime_release': {
|
|
819
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
820
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
821
|
+
if (typeof args.expected_active_release_id !== 'string' || !args.expected_active_release_id.trim()) throw new Error('RECOVERY_EXPECTED_ACTIVE_RELEASE_REQUIRED');
|
|
822
|
+
if (!Number.isInteger(args.expected_authority_revision) || Number(args.expected_authority_revision) < 1) throw new Error('RECOVERY_EXPECTED_AUTHORITY_REVISION_REQUIRED');
|
|
823
|
+
return mutationResponse(config, await activatePinnedRuntimeRelease(config, {}, {
|
|
824
|
+
requestId: `recovery-gateway:${args.request_id}`,
|
|
825
|
+
expectedActiveReleaseId: args.expected_active_release_id.trim(),
|
|
826
|
+
expectedAuthorityRevision: Number(args.expected_authority_revision),
|
|
827
|
+
}));
|
|
529
828
|
}
|
|
530
829
|
case 'stage_and_activate_runtime_release': {
|
|
531
830
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
532
|
-
|
|
831
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
832
|
+
return mutationResponse(config, await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-gateway:${args.request_id}`));
|
|
833
|
+
}
|
|
834
|
+
case 'release_session_status': {
|
|
835
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
836
|
+
return readReleaseSession(config.controllerHome, args.session_id.trim()) ?? null;
|
|
837
|
+
}
|
|
838
|
+
case 'advance_runtime_release_session': {
|
|
839
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
840
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
841
|
+
return mutationResponse(config, await advanceConfiguredRuntimeRelease(config, RECOVERY_RUNTIME_RELEASE_PROVIDER, `recovery-gateway:${args.request_id}`));
|
|
842
|
+
}
|
|
843
|
+
case 'prepare_runtime_release_session': {
|
|
844
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
845
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
846
|
+
return mutationResponse(config, await prepareConfiguredRuntimeReleaseSession(config, {}, `recovery-gateway:${args.request_id}`));
|
|
847
|
+
}
|
|
848
|
+
case 'verify_runtime_release_session_static': {
|
|
849
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
850
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
851
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
852
|
+
return mutationResponse(config, await verifyConfiguredRuntimeReleaseSessionStaticGates(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
|
|
853
|
+
}
|
|
854
|
+
case 'verify_runtime_release_session_candidate': {
|
|
855
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
856
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
857
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
858
|
+
return mutationResponse(config, await bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
|
|
859
|
+
}
|
|
860
|
+
case 'cutover_runtime_release_session': {
|
|
861
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
862
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
863
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
864
|
+
return mutationResponse(config, await cutoverConfiguredRuntimeReleaseSession(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
|
|
865
|
+
}
|
|
866
|
+
case 'cancel_runtime_release_session': {
|
|
867
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
868
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
869
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
870
|
+
return mutationResponse(config, await cancelConfiguredRuntimeReleaseSession(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
|
|
871
|
+
}
|
|
872
|
+
case 'rollback_runtime_release_session': {
|
|
873
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
874
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
875
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
876
|
+
return mutationResponse(config, await rollbackConfiguredRuntimeReleaseSession(config, args.session_id.trim(), {}, `recovery-gateway:${args.request_id}`));
|
|
877
|
+
}
|
|
878
|
+
case 'promote_runtime_release_session_known_good': {
|
|
879
|
+
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
880
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
881
|
+
if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
|
|
882
|
+
return mutationResponse(config, await promoteConfiguredRuntimeReleaseSessionKnownGood(config, args.session_id.trim(), {}, `recovery-gateway:${args.request_id}`));
|
|
883
|
+
}
|
|
884
|
+
case 'migrate_controller_home': {
|
|
885
|
+
const migrationRequestId = requestId(args.request_id);
|
|
886
|
+
if (!migrationRequestId) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
887
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
888
|
+
if (typeof args.canonical_source_root !== 'string' || !args.canonical_source_root.trim()) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REQUIRED');
|
|
889
|
+
if (typeof args.expected_source_revision !== 'string' || !args.expected_source_revision.trim()) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_REQUIRED');
|
|
890
|
+
return scheduleRecoveryControllerHomeMigration(config, {
|
|
891
|
+
requestId: migrationRequestId,
|
|
892
|
+
canonicalSourceRoot: args.canonical_source_root.trim(),
|
|
893
|
+
expectedSourceRevision: args.expected_source_revision.trim(),
|
|
894
|
+
});
|
|
533
895
|
}
|
|
534
896
|
case 'restart_public_tunnel': {
|
|
535
897
|
if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
|
|
536
|
-
|
|
898
|
+
assertRecoveryGatewayMutationIdentity(config, args);
|
|
899
|
+
return mutationResponse(config, await repairPublicTunnel(config));
|
|
537
900
|
}
|
|
538
901
|
case 'reconnect_primary_connector': return reconnectMain(config);
|
|
539
902
|
default: throw new Error('RECOVERY_TOOL_NOT_FOUND');
|
|
540
903
|
}
|
|
541
904
|
}
|
|
542
905
|
|
|
543
|
-
|
|
906
|
+
/**
|
|
907
|
+
* Stable identity of the release step the daemon is currently driving. A step
|
|
908
|
+
* that keeps failing without progressing (same session, phase, and revision)
|
|
909
|
+
* must not fork a full Recovery executable every interval forever.
|
|
910
|
+
*/
|
|
911
|
+
function releaseReconciliationFingerprint(decision: { action?: unknown; session?: { sessionId?: unknown; phase?: unknown; revision?: unknown } }): string {
|
|
912
|
+
return [
|
|
913
|
+
typeof decision.action === 'string' ? decision.action : 'none',
|
|
914
|
+
typeof decision.session?.sessionId === 'string' ? decision.session.sessionId : 'none',
|
|
915
|
+
typeof decision.session?.phase === 'string' ? decision.session.phase : 'none',
|
|
916
|
+
typeof decision.session?.revision === 'number' ? String(decision.session.revision) : 'none',
|
|
917
|
+
].join(':');
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Retry schedule for the automatic release driver. A step that fails without
|
|
922
|
+
* progressing (same fingerprint) backs off exponentially up to a bounded cap so a
|
|
923
|
+
* non-converging release degrades into a slow retry instead of a
|
|
924
|
+
* full-executable fork storm that starves this daemon, the Recovery gateway it
|
|
925
|
+
* serves, and every tunnel behind it. Progress, or a new session/step, resets the
|
|
926
|
+
* schedule; a successful step restores the base interval.
|
|
927
|
+
*/
|
|
928
|
+
export function nextReleaseReconcileBackoff(
|
|
929
|
+
prior: { fingerprint?: string; consecutiveFailures: number },
|
|
930
|
+
failed: boolean,
|
|
931
|
+
fingerprint: string | undefined,
|
|
932
|
+
): { fingerprint?: string; consecutiveFailures: number; delayMs: number } {
|
|
933
|
+
if (!failed) {
|
|
934
|
+
return { fingerprint: undefined, consecutiveFailures: 0, delayMs: RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS };
|
|
935
|
+
}
|
|
936
|
+
const nextFingerprint = fingerprint ?? 'unknown';
|
|
937
|
+
const consecutiveFailures = nextFingerprint === prior.fingerprint ? Math.max(1, prior.consecutiveFailures) + 1 : 1;
|
|
938
|
+
return {
|
|
939
|
+
fingerprint: nextFingerprint,
|
|
940
|
+
consecutiveFailures,
|
|
941
|
+
delayMs: Math.min(RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS * 2 ** (consecutiveFailures - 1), RECOVERY_AUTOMATIC_RELEASE_FAILURE_BACKOFF_MAX_MS),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async function runAutomaticReleaseReconciliationStep(
|
|
946
|
+
config: RecoveryConfig,
|
|
947
|
+
observed: { fingerprint?: string } = {},
|
|
948
|
+
selectedDecision?: ReturnType<typeof decideConfiguredRuntimeReleaseReconciliation>,
|
|
949
|
+
): Promise<void> {
|
|
950
|
+
// Most daemon ticks are no-ops. Decide that in the resident process first so
|
|
951
|
+
// a healthy/current source does not fork a complete Recovery executable every
|
|
952
|
+
// fifteen seconds merely to rediscover the same result. The short-lived child
|
|
953
|
+
// remains the mutation boundary whenever a durable release action is needed.
|
|
954
|
+
const decision = selectedDecision ?? decideConfiguredRuntimeReleaseReconciliation(
|
|
955
|
+
config.controllerHome,
|
|
956
|
+
() => configuredRuntimeReleaseSourceState(config),
|
|
957
|
+
);
|
|
958
|
+
observed.fingerprint = decision.required ? releaseReconciliationFingerprint(decision) : undefined;
|
|
959
|
+
if (!decision.required) return;
|
|
960
|
+
|
|
961
|
+
const release = readCurrentRecoveryRelease(config.controllerHome);
|
|
962
|
+
if (!release) throw new Error('RECOVERY_AUTOMATIC_RELEASE_CURRENT_RECOVERY_UNKNOWN');
|
|
963
|
+
const executable = join(release.releasePath, 'forge-recovery');
|
|
964
|
+
if (!existsSync(executable)) throw new Error('RECOVERY_AUTOMATIC_RELEASE_EXECUTABLE_UNAVAILABLE');
|
|
965
|
+
const result = await runBoundedChild(
|
|
966
|
+
executable,
|
|
967
|
+
[RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND, '--controller-home', config.controllerHome],
|
|
968
|
+
{
|
|
969
|
+
timeoutMs: RECOVERY_AUTOMATIC_RELEASE_STEP_TIMEOUT_MS,
|
|
970
|
+
maxOutputBytes: 64 * 1024,
|
|
971
|
+
forwardSignals: false,
|
|
972
|
+
env: runtimeAuthorityFreeEnvironment(process.env),
|
|
973
|
+
},
|
|
974
|
+
);
|
|
975
|
+
if (result.status !== 0 || result.failureCode || result.timedOut) {
|
|
976
|
+
const detail = result.failureCode ?? result.error ?? (result.stderr.trim() || `exit=${result.status}`);
|
|
977
|
+
throw new Error(`RECOVERY_AUTOMATIC_RELEASE_STEP_FAILED: ${detail.slice(0, 500)}`);
|
|
978
|
+
}
|
|
979
|
+
let envelope: unknown;
|
|
980
|
+
try { envelope = JSON.parse(result.stdout); }
|
|
981
|
+
catch { throw new Error('RECOVERY_AUTOMATIC_RELEASE_PROTOCOL_INVALID'); }
|
|
982
|
+
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) throw new Error('RECOVERY_AUTOMATIC_RELEASE_PROTOCOL_INVALID');
|
|
983
|
+
const parsed = envelope as {
|
|
984
|
+
ok?: unknown;
|
|
985
|
+
attempted?: unknown;
|
|
986
|
+
error?: unknown;
|
|
987
|
+
decision?: { reason?: unknown; action?: unknown };
|
|
988
|
+
result?: { detail?: unknown };
|
|
989
|
+
};
|
|
990
|
+
if (parsed.ok !== true) {
|
|
991
|
+
const detail = typeof parsed.error === 'string'
|
|
992
|
+
? parsed.error
|
|
993
|
+
: typeof parsed.result?.detail === 'string'
|
|
994
|
+
? parsed.result.detail
|
|
995
|
+
: 'automatic release reconciliation failed';
|
|
996
|
+
throw new Error(`RECOVERY_AUTOMATIC_RELEASE_STEP_FAILED: ${detail.slice(0, 500)}`);
|
|
997
|
+
}
|
|
998
|
+
if (parsed.decision?.action) {
|
|
999
|
+
process.stdout.write(JSON.stringify({
|
|
1000
|
+
at: new Date().toISOString(),
|
|
1001
|
+
action: 'automatic_release_reconcile',
|
|
1002
|
+
reason: parsed.decision?.reason,
|
|
1003
|
+
releaseAction: parsed.decision?.action,
|
|
1004
|
+
}) + '\n');
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
async function startAutomaticReleaseReconciliation(config: RecoveryConfig): Promise<never> {
|
|
1009
|
+
let lastFailedFingerprint: string | undefined;
|
|
1010
|
+
let consecutiveFailures = 0;
|
|
1011
|
+
for (;;) {
|
|
1012
|
+
const observed: { fingerprint?: string } = {};
|
|
1013
|
+
let failure: string | undefined;
|
|
1014
|
+
try {
|
|
1015
|
+
const decision = decideConfiguredRuntimeReleaseReconciliation(
|
|
1016
|
+
config.controllerHome,
|
|
1017
|
+
() => configuredRuntimeReleaseSourceState(config),
|
|
1018
|
+
);
|
|
1019
|
+
observed.fingerprint = decision.required ? releaseReconciliationFingerprint(decision) : undefined;
|
|
1020
|
+
if (decision.required && decision.action === 'cutover') {
|
|
1021
|
+
const gate = await probeReleaseCutoverReadiness(config);
|
|
1022
|
+
if (!gate.ready) {
|
|
1023
|
+
process.stdout.write(JSON.stringify({
|
|
1024
|
+
at: new Date().toISOString(),
|
|
1025
|
+
action: 'automatic_release_cutover_deferred',
|
|
1026
|
+
reason: gate.detail,
|
|
1027
|
+
sessionId: decision.session?.sessionId,
|
|
1028
|
+
phase: decision.session?.phase,
|
|
1029
|
+
activeSessions: gate.activeSessions,
|
|
1030
|
+
activePosts: gate.activePosts,
|
|
1031
|
+
activeStreams: gate.activeStreams,
|
|
1032
|
+
initializing: gate.initializing,
|
|
1033
|
+
latestActivityAgeMs: gate.latestActivityAgeMs,
|
|
1034
|
+
}) + '\n');
|
|
1035
|
+
} else {
|
|
1036
|
+
await runAutomaticReleaseReconciliationStep(config, observed, decision);
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
await runAutomaticReleaseReconciliationStep(config, observed, decision);
|
|
1040
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
1043
|
+
process.stderr.write(`automatic release reconciliation failed: ${failure}\n`);
|
|
1044
|
+
}
|
|
1045
|
+
const backoff = nextReleaseReconcileBackoff(
|
|
1046
|
+
{ fingerprint: lastFailedFingerprint, consecutiveFailures },
|
|
1047
|
+
Boolean(failure),
|
|
1048
|
+
observed.fingerprint,
|
|
1049
|
+
);
|
|
1050
|
+
lastFailedFingerprint = backoff.fingerprint;
|
|
1051
|
+
consecutiveFailures = backoff.consecutiveFailures;
|
|
1052
|
+
if (failure && backoff.delayMs > RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS) {
|
|
1053
|
+
process.stderr.write(
|
|
1054
|
+
`automatic release reconciliation backing off ${Math.round(backoff.delayMs / 1000)}s after ${backoff.consecutiveFailures} consecutive failures of ${backoff.fingerprint}\n`,
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
await new Promise((resolveSleep) => setTimeout(resolveSleep, backoff.delayMs));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
async function startRecoveryDaemon(config: RecoveryConfig): Promise<void> {
|
|
1062
|
+
const runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'daemon');
|
|
1063
|
+
|
|
1064
|
+
// The Recovery gateway is the control plane used to repair every other
|
|
1065
|
+
// Recovery subsystem. Bind it before starting background work. A reconcile
|
|
1066
|
+
// or watchdog tick may perform expensive synchronous setup before its first
|
|
1067
|
+
// await, so invoking those loops first can leave a live daemon process with
|
|
1068
|
+
// no listening gateway.
|
|
1069
|
+
const backgroundTimer = setTimeout(() => {
|
|
1070
|
+
void startAutomaticReleaseReconciliation(config).catch((error) => {
|
|
1071
|
+
process.stderr.write(`Recovery release driver failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
});
|
|
1074
|
+
if (config.installProfile === 'self-healing') {
|
|
1075
|
+
void startWatchdog(config, runtimeIdentity).catch((error) => {
|
|
1076
|
+
process.stderr.write(`Recovery monitor failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1077
|
+
process.exit(1);
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}, 0);
|
|
1081
|
+
backgroundTimer.unref?.();
|
|
1082
|
+
|
|
1083
|
+
await startGateway(config, runtimeIdentity);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
async function startGateway(config: RecoveryConfig, daemonIdentity?: RecoveryRuntimeIdentity): Promise<void> {
|
|
544
1087
|
const gateway = config.gateway;
|
|
545
1088
|
if (!gateway || gateway.host !== '127.0.0.1' || !Number.isInteger(gateway.port) || gateway.port < 1024 || gateway.port > 65535) {
|
|
546
1089
|
throw new Error('RECOVERY_GATEWAY_CONFIG_INVALID');
|
|
547
1090
|
}
|
|
548
|
-
let runtimeIdentity: RecoveryRuntimeIdentity | undefined;
|
|
549
|
-
let
|
|
1091
|
+
let runtimeIdentity: RecoveryRuntimeIdentity | undefined = daemonIdentity;
|
|
1092
|
+
let activeMcpRequests = 0;
|
|
1093
|
+
let latestMcpActivityAt: number | undefined;
|
|
550
1094
|
const recentMutations = new Map<string, number[]>();
|
|
551
1095
|
const oauthCodes = new Map<string, PendingOAuthCode>();
|
|
552
1096
|
const oauthClients = new Map<string, OAuthClient>();
|
|
1097
|
+
const recoveryTools = RECOVERY_TOOLS.map((tool) => ({
|
|
1098
|
+
...tool,
|
|
1099
|
+
securitySchemes: TOOL_SECURITY_SCHEMES,
|
|
1100
|
+
_meta: { securitySchemes: TOOL_SECURITY_SCHEMES },
|
|
1101
|
+
})) as unknown as Tool[];
|
|
1102
|
+
const recoveryMcp = new RecoveryMcpServer({
|
|
1103
|
+
tools: recoveryTools,
|
|
1104
|
+
dispatchTool: async (name, args, context) => {
|
|
1105
|
+
if (name === 'attest_known_good' || name === 'rollback_previous' || name === 'restart_primary_runtime' || name === 'restart_primary_connector' || name === 'recover_primary_runtime' || name === 'activate_runtime_release' || name === 'pin_runtime_release' || name === 'unpin_runtime_release' || name === 'activate_pinned_runtime_release' || name === 'stage_and_activate_runtime_release' || name === 'prepare_runtime_release_session' || name === 'verify_runtime_release_session_static' || name === 'verify_runtime_release_session_candidate' || name === 'cutover_runtime_release_session' || name === 'promote_runtime_release_session_known_good' || name === 'migrate_controller_home' || name === 'restart_public_tunnel') {
|
|
1106
|
+
const now = Date.now();
|
|
1107
|
+
const window = (recentMutations.get(context.remoteAddress) ?? []).filter((at) => now - at < 60_000);
|
|
1108
|
+
if (window.length >= 3) throw new Error('Recovery mutation rate limit exceeded.');
|
|
1109
|
+
window.push(now);
|
|
1110
|
+
recentMutations.set(context.remoteAddress, window);
|
|
1111
|
+
}
|
|
1112
|
+
return await dispatchRecoveryTool(config, name, args);
|
|
1113
|
+
},
|
|
1114
|
+
});
|
|
553
1115
|
const server = createServer(async (request, response) => {
|
|
554
1116
|
if (request.method === 'OPTIONS') {
|
|
555
1117
|
response.statusCode = 204;
|
|
@@ -558,17 +1120,20 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
558
1120
|
return;
|
|
559
1121
|
}
|
|
560
1122
|
if (request.method === 'GET' && matchesAnyPath(request.url, ['/health', '/recovery/health'])) {
|
|
561
|
-
const watchdog = observeRecoveryWatchdogHealth(
|
|
1123
|
+
const watchdog = observeRecoveryWatchdogHealth(
|
|
1124
|
+
config.controllerHome,
|
|
1125
|
+
config.installProfile === 'self-healing',
|
|
1126
|
+
);
|
|
562
1127
|
json(response, 200, {
|
|
563
1128
|
status: watchdog.ok ? 'ok' : 'degraded',
|
|
564
1129
|
service: 'forge-standalone-recovery',
|
|
565
1130
|
watchdog: {
|
|
566
1131
|
ok: watchdog.ok,
|
|
567
1132
|
detail: watchdog.detail,
|
|
568
|
-
pulseAgeMs: watchdog.pulseAgeMs,
|
|
569
|
-
tickAgeMs: watchdog.tickAgeMs,
|
|
570
|
-
releaseRevision: watchdog.runtimeIdentity?.releaseRevision,
|
|
571
|
-
pid: watchdog.runtimeIdentity?.pid,
|
|
1133
|
+
pulseAgeMs: 'pulseAgeMs' in watchdog ? watchdog.pulseAgeMs : undefined,
|
|
1134
|
+
tickAgeMs: 'tickAgeMs' in watchdog ? watchdog.tickAgeMs : undefined,
|
|
1135
|
+
releaseRevision: 'runtimeIdentity' in watchdog ? watchdog.runtimeIdentity?.releaseRevision : runtimeIdentity?.releaseRevision,
|
|
1136
|
+
pid: 'runtimeIdentity' in watchdog ? watchdog.runtimeIdentity?.pid : runtimeIdentity?.pid,
|
|
572
1137
|
},
|
|
573
1138
|
version: FORGE_VERSION,
|
|
574
1139
|
...(runtimeIdentity ? {
|
|
@@ -577,6 +1142,11 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
577
1142
|
sourceCommit: runtimeIdentity.sourceCommit,
|
|
578
1143
|
manifestSha256: runtimeIdentity.manifestSha256,
|
|
579
1144
|
} : {}),
|
|
1145
|
+
requests: {
|
|
1146
|
+
draining: recoveryGatewayIsDraining(config.controllerHome),
|
|
1147
|
+
activeMcpRequests,
|
|
1148
|
+
...(latestMcpActivityAt !== undefined ? { latestMcpActivityAgeMs: Math.max(0, Date.now() - latestMcpActivityAt) } : {}),
|
|
1149
|
+
},
|
|
580
1150
|
});
|
|
581
1151
|
return;
|
|
582
1152
|
}
|
|
@@ -597,6 +1167,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
597
1167
|
html(response, 401, renderAuthorizeForm(params, 'Invalid passphrase.'));
|
|
598
1168
|
return;
|
|
599
1169
|
}
|
|
1170
|
+
reserveRecoveryOAuthCodeCapacity(oauthCodes, clientId);
|
|
600
1171
|
const code = randomUUID();
|
|
601
1172
|
oauthCodes.set(code, {
|
|
602
1173
|
clientId,
|
|
@@ -622,16 +1193,26 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
622
1193
|
const raw = await readBody(request);
|
|
623
1194
|
body = raw.trim() ? JSON.parse(raw) as Record<string, unknown> : {};
|
|
624
1195
|
} catch { /* tolerate minimal DCR clients */ }
|
|
625
|
-
|
|
1196
|
+
let registrationIdentity: ReturnType<typeof recoveryOAuthClientRegistrationIdentity>;
|
|
1197
|
+
try {
|
|
1198
|
+
registrationIdentity = recoveryOAuthClientRegistrationIdentity(body);
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
auditGateway({ oauth: 'register', outcome: 'invalid_client_metadata' });
|
|
1201
|
+
json(response, 400, { error: 'invalid_client_metadata', error_description: error instanceof Error ? error.message : String(error) });
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const { clientId, owner } = registrationIdentity;
|
|
1205
|
+
const reused = oauthClients.has(clientId);
|
|
626
1206
|
const clientSecret = randomUUID();
|
|
627
1207
|
const authMethod = tokenAuthMethod(body.token_endpoint_auth_method);
|
|
628
1208
|
oauthClients.set(clientId, {
|
|
629
1209
|
clientId,
|
|
630
1210
|
clientSecret: authMethod === 'none' ? undefined : clientSecret,
|
|
631
1211
|
tokenEndpointAuthMethod: authMethod,
|
|
1212
|
+
owner,
|
|
632
1213
|
createdAt: Date.now(),
|
|
633
1214
|
});
|
|
634
|
-
auditGateway({ oauth: 'register', outcome: 'created', token_endpoint_auth_method: authMethod });
|
|
1215
|
+
auditGateway({ oauth: 'register', outcome: reused ? 'reused' : 'created', client_owner: owner, token_endpoint_auth_method: authMethod });
|
|
635
1216
|
json(response, 201, {
|
|
636
1217
|
...body,
|
|
637
1218
|
client_id: clientId,
|
|
@@ -641,6 +1222,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
641
1222
|
token_endpoint_auth_method: authMethod,
|
|
642
1223
|
grant_types: ['authorization_code'],
|
|
643
1224
|
response_types: ['code'],
|
|
1225
|
+
...(owner === 'recovery_verifier' ? { forge_client_owner: owner, forge_client_reused: reused } : {}),
|
|
644
1226
|
});
|
|
645
1227
|
return;
|
|
646
1228
|
}
|
|
@@ -665,7 +1247,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
665
1247
|
const redirectUri = params.get('redirect_uri') ?? '';
|
|
666
1248
|
const verifier = params.get('code_verifier') ?? '';
|
|
667
1249
|
const pending = code ? oauthCodes.get(code) : undefined;
|
|
668
|
-
if (!code || !pending || pending.clientId !== clientId || pending.redirectUri !== redirectUri || Date.now() - pending.createdAt >
|
|
1250
|
+
if (!code || !pending || pending.clientId !== clientId || pending.redirectUri !== redirectUri || Date.now() - pending.createdAt > RECOVERY_OAUTH_CODE_TTL_MS) {
|
|
669
1251
|
auditGateway({ oauth: 'token', outcome: 'invalid_grant' });
|
|
670
1252
|
json(response, 400, { error: 'invalid_grant' });
|
|
671
1253
|
return;
|
|
@@ -701,56 +1283,35 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
|
|
|
701
1283
|
const mcpRequest = classifyRecoveryMcpRequest(request, gatewayToken(config));
|
|
702
1284
|
if (mcpRequest === 'not_mcp' || mcpRequest === 'method_not_supported') { json(response, 404, { error: 'NOT_FOUND' }); return; }
|
|
703
1285
|
if (mcpRequest === 'auth_required') { response.setHeader('www-authenticate', recoveryWwwAuthenticate(request, config)); json(response, 401, recoveryUnauthorizedBody()); return; }
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const id = message.id ?? null;
|
|
708
|
-
if (message.method === 'initialize') { json(response, 200, { jsonrpc: '2.0', id, result: { protocolVersion: '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: 'forge-standalone-recovery', version: FORGE_VERSION } } }); return; }
|
|
709
|
-
if (message.method === 'notifications/initialized') { response.statusCode = 202; response.end(); return; }
|
|
710
|
-
if (message.method === 'tools/list') {
|
|
711
|
-
const tools = RECOVERY_TOOLS.map((tool) => ({
|
|
712
|
-
...tool,
|
|
713
|
-
securitySchemes: TOOL_SECURITY_SCHEMES,
|
|
714
|
-
_meta: { securitySchemes: TOOL_SECURITY_SCHEMES },
|
|
715
|
-
}));
|
|
716
|
-
json(response, 200, { jsonrpc: '2.0', id, result: { tools } });
|
|
1286
|
+
if (recoveryGatewayIsDraining(config.controllerHome)) {
|
|
1287
|
+
response.setHeader('retry-after', '1');
|
|
1288
|
+
json(response, 503, { error: 'RECOVERY_DRAINING' });
|
|
717
1289
|
return;
|
|
718
1290
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const address = request.socket.remoteAddress ?? 'unknown'; const now = Date.now();
|
|
724
|
-
const window = (recentMutations.get(address) ?? []).filter((at) => now - at < 60_000);
|
|
725
|
-
if (window.length >= 3) { json(response, 429, rpcError(id, -32029, 'Recovery mutation rate limit exceeded.')); return; }
|
|
726
|
-
window.push(now); recentMutations.set(address, window);
|
|
1291
|
+
let body: unknown;
|
|
1292
|
+
if (request.method === 'POST') {
|
|
1293
|
+
if (!/^application\/json(?:\s*;|$)/i.test(String(request.headers['content-type'] ?? ''))) { json(response, 415, { error: 'RECOVERY_CONTENT_TYPE_REQUIRED' }); return; }
|
|
1294
|
+
try { body = JSON.parse(await readBody(request)); } catch { json(response, 400, rpcError(null, -32700, 'Invalid JSON.')); return; }
|
|
727
1295
|
}
|
|
1296
|
+
activeMcpRequests += 1;
|
|
1297
|
+
latestMcpActivityAt = Date.now();
|
|
1298
|
+
let settled = false;
|
|
1299
|
+
const settle = () => {
|
|
1300
|
+
if (settled) return;
|
|
1301
|
+
settled = true;
|
|
1302
|
+
activeMcpRequests = Math.max(0, activeMcpRequests - 1);
|
|
1303
|
+
latestMcpActivityAt = Date.now();
|
|
1304
|
+
};
|
|
1305
|
+
response.once('finish', settle);
|
|
1306
|
+
response.once('close', settle);
|
|
728
1307
|
try {
|
|
729
|
-
|
|
730
|
-
json(response, 200, { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload } });
|
|
731
|
-
} catch (error) { json(response, 200, rpcError(id, -32602, error instanceof Error ? error.message : 'Recovery request rejected')); }
|
|
732
|
-
});
|
|
733
|
-
await new Promise<void>((resolveListen, reject) => { server.once('error', reject); server.listen(gateway.port, gateway.host, () => resolveListen()); });
|
|
734
|
-
runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'gateway');
|
|
735
|
-
const superviseWatchdog = async () => {
|
|
736
|
-
if (!runtimeIdentity || watchdogRestartInFlight) return;
|
|
737
|
-
const watchdog = observeRecoveryWatchdogHealth(config.controllerHome);
|
|
738
|
-
if (watchdog.ok) return;
|
|
739
|
-
watchdogRestartInFlight = true;
|
|
740
|
-
try {
|
|
741
|
-
const recovery = await restartRecoveryWatchdog(config);
|
|
742
|
-
auditGateway({
|
|
743
|
-
watchdog_supervision: recovery.ok ? 'recovered' : 'failed',
|
|
744
|
-
detail: recovery.detail,
|
|
745
|
-
attempted: recovery.attempted,
|
|
746
|
-
serviceTarget: recovery.serviceTarget,
|
|
747
|
-
});
|
|
1308
|
+
await recoveryMcp.handle(request, response, body);
|
|
748
1309
|
} finally {
|
|
749
|
-
|
|
1310
|
+
if (response.writableEnded || response.destroyed) settle();
|
|
750
1311
|
}
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
|
|
1312
|
+
});
|
|
1313
|
+
await new Promise<void>((resolveListen, reject) => { server.once('error', reject); server.listen(gateway.port, gateway.host, () => resolveListen()); });
|
|
1314
|
+
runtimeIdentity ??= writeRecoveryRuntimeIdentity(config.controllerHome, 'gateway');
|
|
754
1315
|
process.stdout.write(JSON.stringify({ status: 'ready', host: gateway.host, port: gateway.port, runtimeIdentity }) + '\n');
|
|
755
1316
|
}
|
|
756
1317
|
|