@moretea-labs/forge 1.6.0 → 1.7.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 +4 -0
- package/CHANGELOG.md +39 -0
- package/CLAUDE.md +4 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +178 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-plugin-management.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +143 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +33 -11
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1558
- package/scripts/check-architecture-sync.sh +2 -371
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +349 -79
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -722
- package/scripts/ensure-task-workflow.sh +1 -1225
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +22 -46
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -1189
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -796
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +5 -1
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -558
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -703
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +1 -677
- package/scripts/workflow-contract.ts +4 -205
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +113 -5
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +828 -487
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +135 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +37 -264
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +2 -515
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -222
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +282 -146
- package/src/cli/mcp/server.ts +586 -116
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +44 -7
- package/src/cli/mcp/transports/http.ts +124 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +103 -21
- package/src/cli/repositories/command-executor.ts +78 -526
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +124 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +118 -6
- package/src/cli/repository/inspector.ts +563 -65
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +223 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
- package/src/runtime/control-plane/facade/index.ts +3 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +90 -10
- package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +24 -21
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +13 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +88 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +53 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +4 -12
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -61
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +186 -84
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
- package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
- package/src/runtime/plugins/browser-adapter.ts +2662 -829
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +110 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
- package/src/runtime/plugins/browser-runtime.ts +193 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
- package/src/runtime/plugins/external-adapter.ts +358 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +47 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +330 -33
- package/src/runtime/plugins/types.ts +38 -3
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/maintenance-executor.ts +426 -25
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +230 -10
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +461 -22
- package/src/runtime/root/release-manifest.ts +144 -18
- package/src/runtime/root/release-materialize.ts +125 -32
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +12 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +1079 -240
- package/src/runtime/standalone-recovery/entry.ts +100 -24
- package/src/runtime/standalone-recovery/installer.ts +20 -4
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
- package/src/runtime/workflow/schedules/engine.ts +364 -202
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +64 -17
- package/src/runtime/workflow/schedules/store.ts +209 -19
- package/src/runtime/workflow/schedules/types.ts +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
- package/docs/architecture/current/entity-model.md +0 -612
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -169
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -321
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { chmodSync, createReadStream, existsSync, mkdirSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import {
|
|
5
|
+
backupControlPlaneDatabase,
|
|
6
|
+
CONTROL_PLANE_SCHEMA_VERSION,
|
|
7
|
+
inspectControlPlaneDatabaseFile,
|
|
8
|
+
listControlPlaneRecords,
|
|
9
|
+
readControlPlaneRecord,
|
|
10
|
+
readControlPlaneRecordWithinTransaction,
|
|
11
|
+
withControlPlaneTransaction,
|
|
12
|
+
writeControlPlaneRecord,
|
|
13
|
+
writeControlPlaneRecordWithinTransaction,
|
|
14
|
+
type ControlPlaneDatabaseInspection,
|
|
15
|
+
type ControlPlaneRecord,
|
|
16
|
+
} from './sqlite-store';
|
|
17
|
+
import { listRepositories } from '../../../cli/repositories/registry';
|
|
18
|
+
|
|
19
|
+
const MIGRATION_NAMESPACE = 'controller_home_migration';
|
|
20
|
+
const MIGRATION_SCOPE = 'controller';
|
|
21
|
+
const IMPORTABLE_REFERENCED_NAMESPACES = [
|
|
22
|
+
'execution_work_validation_index',
|
|
23
|
+
'execution_edit_validation_run',
|
|
24
|
+
'execution_edit_validation_index',
|
|
25
|
+
'chatgpt_work_conversation_binding',
|
|
26
|
+
'controller_session_claim_store',
|
|
27
|
+
'controller_round_relay',
|
|
28
|
+
'external_controller_launch_reservation',
|
|
29
|
+
] as const;
|
|
30
|
+
const TERMINAL_WORK_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
31
|
+
|
|
32
|
+
export interface ControllerHomeMigrationRecordRef {
|
|
33
|
+
namespace: string;
|
|
34
|
+
scope: string;
|
|
35
|
+
key: string;
|
|
36
|
+
schemaVersion: number;
|
|
37
|
+
sourceRevision: number;
|
|
38
|
+
sourceUpdatedAt: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ControllerHomeMigrationConflict extends ControllerHomeMigrationRecordRef {
|
|
42
|
+
kind: 'payload_conflict';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ControllerHomeMigrationPreview {
|
|
46
|
+
sourceHome: string;
|
|
47
|
+
destinationHome: string;
|
|
48
|
+
sourceDatabase: {
|
|
49
|
+
path: string;
|
|
50
|
+
sha256: string;
|
|
51
|
+
walPath?: string;
|
|
52
|
+
walSha256?: string;
|
|
53
|
+
inspection: ControlPlaneDatabaseInspection;
|
|
54
|
+
};
|
|
55
|
+
destinationDatabase: {
|
|
56
|
+
path: string;
|
|
57
|
+
exists: boolean;
|
|
58
|
+
inspection?: ControlPlaneDatabaseInspection;
|
|
59
|
+
};
|
|
60
|
+
selected: ControllerHomeMigrationRecordRef[];
|
|
61
|
+
selectedByNamespace: Record<string, number>;
|
|
62
|
+
identical: ControllerHomeMigrationRecordRef[];
|
|
63
|
+
conflicts: ControllerHomeMigrationConflict[];
|
|
64
|
+
archivedOnly: {
|
|
65
|
+
terminalWorkContracts: number;
|
|
66
|
+
excludedNamespaces: readonly string[];
|
|
67
|
+
auditChain: 'retained_in_source_sqlite';
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ImportedRecord extends ControllerHomeMigrationRecordRef {
|
|
72
|
+
destinationRevision: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface ControllerHomeMigrationRecord {
|
|
76
|
+
schemaVersion: 1;
|
|
77
|
+
migrationId: string;
|
|
78
|
+
status: 'applied' | 'rolled_back';
|
|
79
|
+
sourceHome: string;
|
|
80
|
+
destinationHome: string;
|
|
81
|
+
sourceDatabase: ControllerHomeMigrationPreview['sourceDatabase'];
|
|
82
|
+
destinationBackupPath: string;
|
|
83
|
+
selected: ControllerHomeMigrationRecordRef[];
|
|
84
|
+
imported: ImportedRecord[];
|
|
85
|
+
identical: ControllerHomeMigrationRecordRef[];
|
|
86
|
+
archivedOnly: ControllerHomeMigrationPreview['archivedOnly'];
|
|
87
|
+
sourceArchive: {
|
|
88
|
+
databaseModeBefore: number;
|
|
89
|
+
databaseModeAfter: number;
|
|
90
|
+
walModeBefore?: number;
|
|
91
|
+
walModeAfter?: number;
|
|
92
|
+
state: 'read_only' | 'restored';
|
|
93
|
+
};
|
|
94
|
+
appliedAt: string;
|
|
95
|
+
rolledBackAt?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface AppliedControllerHomeMigration {
|
|
99
|
+
preview: ControllerHomeMigrationPreview;
|
|
100
|
+
migration: ControllerHomeMigrationRecord;
|
|
101
|
+
reportPath: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Archives imported Work that cannot be routed because its repository was not
|
|
106
|
+
* migrated. This is a migration operation, not a Work execution transition:
|
|
107
|
+
* original payloads remain in the frozen source SQLite and each target record
|
|
108
|
+
* receives a new auditable terminal revision.
|
|
109
|
+
*/
|
|
110
|
+
export function archiveUnroutableMigratedWork(input: {
|
|
111
|
+
destinationHome: string;
|
|
112
|
+
migrationId: string;
|
|
113
|
+
}): { migrationId: string; archivedWorkIds: string[]; archivedHandleIds: string[]; skipped: string[] } {
|
|
114
|
+
const destinationHome = resolve(input.destinationHome);
|
|
115
|
+
const migration = readControlPlaneRecord<ControllerHomeMigrationRecord>(destinationHome, MIGRATION_NAMESPACE, MIGRATION_SCOPE, input.migrationId);
|
|
116
|
+
if (!migration?.value || migration.value.status !== 'applied') throw new Error(`CONTROLLER_HOME_MIGRATION_NOT_APPLIED: ${input.migrationId}`);
|
|
117
|
+
const knownRepositories = new Set(listRepositories(destinationHome, { includeRemoved: false }).map((repository) => repository.repoId));
|
|
118
|
+
const imported = new Set(migration.value.imported.map((record) => recordIdentity({ namespace: record.namespace, scope: record.scope, key: record.key })));
|
|
119
|
+
const archivedWorkIds: string[] = [];
|
|
120
|
+
const archivedHandleIds: string[] = [];
|
|
121
|
+
const skipped: string[] = [];
|
|
122
|
+
withControlPlaneTransaction(destinationHome, (database) => {
|
|
123
|
+
for (const record of listControlPlaneRecords<unknown>(destinationHome, { namespace: 'work_contract', limit: 5_000 })) {
|
|
124
|
+
if (!imported.has(recordIdentity(record)) || knownRepositories.has(record.scope)) continue;
|
|
125
|
+
const value = record.value as Record<string, unknown>;
|
|
126
|
+
if (TERMINAL_WORK_STATUSES.has(String(value.status ?? ''))) { skipped.push(record.key); continue; }
|
|
127
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
128
|
+
namespace: record.namespace, scope: record.scope, key: record.key, schemaVersion: record.schemaVersion,
|
|
129
|
+
value: { ...value, status: 'cancelled', updatedAt: new Date().toISOString(), terminalReason: 'CONTROLLER_HOME_MIGRATION_UNROUTABLE_REPOSITORY_ARCHIVED' },
|
|
130
|
+
action: 'controller_home_migration_archive_unroutable_work', expectedRevision: record.revision,
|
|
131
|
+
});
|
|
132
|
+
archivedWorkIds.push(record.key);
|
|
133
|
+
}
|
|
134
|
+
for (const record of listControlPlaneRecords<unknown>(destinationHome, { namespace: 'execution_work_handle', limit: 5_000 })) {
|
|
135
|
+
if (!imported.has(recordIdentity(record)) || knownRepositories.has(record.scope)) continue;
|
|
136
|
+
const value = record.value as Record<string, unknown>;
|
|
137
|
+
if (String(value.state ?? '') === 'cleaned') { skipped.push(record.key); continue; }
|
|
138
|
+
const finalization = typeof value.finalization === 'object' && value.finalization ? value.finalization : {};
|
|
139
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
140
|
+
namespace: record.namespace, scope: record.scope, key: record.key, schemaVersion: record.schemaVersion,
|
|
141
|
+
value: { ...value, state: 'cleaned', updatedAt: new Date().toISOString(), failureReason: 'CONTROLLER_HOME_MIGRATION_UNROUTABLE_REPOSITORY_ARCHIVED', finalization: { ...finalization, validation: 'skipped', commit: 'skipped', merge: 'skipped', branchCleanup: 'skipped', worktreeCleanup: 'skipped' } },
|
|
142
|
+
action: 'controller_home_migration_archive_unroutable_handle', expectedRevision: record.revision,
|
|
143
|
+
});
|
|
144
|
+
archivedHandleIds.push(record.key);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return { migrationId: input.migrationId, archivedWorkIds, archivedHandleIds, skipped };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function databasePath(home: string): string {
|
|
151
|
+
return join(resolve(home), 'control-plane.sqlite');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function recordRef(record: ControlPlaneRecord<unknown>): ControllerHomeMigrationRecordRef {
|
|
155
|
+
return {
|
|
156
|
+
namespace: record.namespace,
|
|
157
|
+
scope: record.scope,
|
|
158
|
+
key: record.key,
|
|
159
|
+
schemaVersion: record.schemaVersion,
|
|
160
|
+
sourceRevision: record.revision,
|
|
161
|
+
sourceUpdatedAt: record.updatedAt,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function recordIdentity(record: Pick<ControlPlaneRecord<unknown>, 'namespace' | 'scope' | 'key'>): string {
|
|
166
|
+
return `${record.namespace}\u0000${record.scope}\u0000${record.key}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function sameRecord(left: ControlPlaneRecord<unknown>, right: ControlPlaneRecord<unknown>): boolean {
|
|
170
|
+
return left.schemaVersion === right.schemaVersion && JSON.stringify(left.value) === JSON.stringify(right.value);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function referencesOneOf(value: unknown, ids: ReadonlySet<string>): boolean {
|
|
174
|
+
if (typeof value === 'string') return ids.has(value);
|
|
175
|
+
if (!value || typeof value !== 'object') return false;
|
|
176
|
+
if (Array.isArray(value)) return value.some((entry) => referencesOneOf(entry, ids));
|
|
177
|
+
return Object.values(value as Record<string, unknown>).some((entry) => referencesOneOf(entry, ids));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function addRecord(
|
|
181
|
+
selected: Map<string, ControlPlaneRecord<unknown>>,
|
|
182
|
+
record: ControlPlaneRecord<unknown>,
|
|
183
|
+
): void {
|
|
184
|
+
selected.set(recordIdentity(record), record);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function allRecords(home: string, namespace: string): ControlPlaneRecord<unknown>[] {
|
|
188
|
+
return listControlPlaneRecords<unknown>(home, { namespace, limit: 5_000 });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function sourceSelection(sourceHome: string): {
|
|
192
|
+
records: ControlPlaneRecord<unknown>[];
|
|
193
|
+
terminalWorkContracts: number;
|
|
194
|
+
} {
|
|
195
|
+
const selected = new Map<string, ControlPlaneRecord<unknown>>();
|
|
196
|
+
const workContracts = allRecords(sourceHome, 'work_contract');
|
|
197
|
+
const activeWork = workContracts.filter((record) => {
|
|
198
|
+
const status = (record.value as { status?: unknown }).status;
|
|
199
|
+
return typeof status === 'string' && !TERMINAL_WORK_STATUSES.has(status);
|
|
200
|
+
});
|
|
201
|
+
for (const record of activeWork) addRecord(selected, record);
|
|
202
|
+
|
|
203
|
+
const planIds = new Set(
|
|
204
|
+
activeWork
|
|
205
|
+
.map((record) => (record.value as { planId?: unknown }).planId)
|
|
206
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0),
|
|
207
|
+
);
|
|
208
|
+
for (const record of allRecords(sourceHome, 'plan_contract')) {
|
|
209
|
+
if (planIds.has(record.key)) addRecord(selected, record);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const workIds = new Set(activeWork.map((record) => record.key));
|
|
213
|
+
const workHandles = allRecords(sourceHome, 'execution_work_handle').filter((record) => {
|
|
214
|
+
const value = record.value as { workId?: unknown; workContractId?: unknown };
|
|
215
|
+
return (typeof value.workId === 'string' && workIds.has(value.workId))
|
|
216
|
+
|| (typeof value.workContractId === 'string' && workIds.has(value.workContractId));
|
|
217
|
+
});
|
|
218
|
+
for (const record of workHandles) addRecord(selected, record);
|
|
219
|
+
|
|
220
|
+
const sessionIds = new Set(
|
|
221
|
+
workHandles
|
|
222
|
+
.map((record) => (record.value as { sessionId?: unknown }).sessionId)
|
|
223
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0),
|
|
224
|
+
);
|
|
225
|
+
for (const record of allRecords(sourceHome, 'execution_session')) {
|
|
226
|
+
const sessionId = (record.value as { sessionId?: unknown }).sessionId;
|
|
227
|
+
if (sessionIds.has(record.key) || (typeof sessionId === 'string' && sessionIds.has(sessionId))) addRecord(selected, record);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const referenceIds = new Set<string>([...workIds, ...planIds, ...sessionIds]);
|
|
231
|
+
for (const namespace of IMPORTABLE_REFERENCED_NAMESPACES) {
|
|
232
|
+
for (const record of allRecords(sourceHome, namespace)) {
|
|
233
|
+
if (referencesOneOf(record.value, referenceIds)) addRecord(selected, record);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
records: [...selected.values()].sort((left, right) => recordIdentity(left).localeCompare(recordIdentity(right))),
|
|
239
|
+
terminalWorkContracts: workContracts.length - activeWork.length,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function sha256(path: string): Promise<string> {
|
|
244
|
+
return await new Promise<string>((resolveHash, reject) => {
|
|
245
|
+
const hash = createHash('sha256');
|
|
246
|
+
const input = createReadStream(path);
|
|
247
|
+
input.on('error', reject);
|
|
248
|
+
input.on('data', (chunk: Buffer) => hash.update(chunk));
|
|
249
|
+
input.on('end', () => resolveHash(hash.digest('hex')));
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function selectedByNamespace(records: readonly ControllerHomeMigrationRecordRef[]): Record<string, number> {
|
|
254
|
+
return records.reduce<Record<string, number>>((counts, record) => {
|
|
255
|
+
counts[record.namespace] = (counts[record.namespace] ?? 0) + 1;
|
|
256
|
+
return counts;
|
|
257
|
+
}, {});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function destinationRecordIfPresent(
|
|
261
|
+
destinationHome: string,
|
|
262
|
+
destinationExists: boolean,
|
|
263
|
+
record: ControlPlaneRecord<unknown>,
|
|
264
|
+
): ControlPlaneRecord<unknown> | undefined {
|
|
265
|
+
if (!destinationExists) return undefined;
|
|
266
|
+
return readControlPlaneRecord<unknown>(destinationHome, record.namespace, record.scope, record.key);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function previewControllerHomeMigration(input: {
|
|
270
|
+
sourceHome: string;
|
|
271
|
+
destinationHome: string;
|
|
272
|
+
}): Promise<ControllerHomeMigrationPreview> {
|
|
273
|
+
const sourceHome = resolve(input.sourceHome);
|
|
274
|
+
const destinationHome = resolve(input.destinationHome);
|
|
275
|
+
if (sourceHome === destinationHome) throw new Error('CONTROLLER_HOME_MIGRATION_SAME_HOME');
|
|
276
|
+
const sourceDatabase = databasePath(sourceHome);
|
|
277
|
+
if (!existsSync(sourceDatabase)) throw new Error(`CONTROLLER_HOME_MIGRATION_SOURCE_DATABASE_MISSING: ${sourceDatabase}`);
|
|
278
|
+
const sourceWal = `${sourceDatabase}-wal`;
|
|
279
|
+
const sourceWalExists = existsSync(sourceWal);
|
|
280
|
+
|
|
281
|
+
const selection = sourceSelection(sourceHome);
|
|
282
|
+
const selected = selection.records.map(recordRef);
|
|
283
|
+
const destinationDatabase = databasePath(destinationHome);
|
|
284
|
+
const destinationExists = existsSync(destinationDatabase);
|
|
285
|
+
const identical: ControllerHomeMigrationRecordRef[] = [];
|
|
286
|
+
const conflicts: ControllerHomeMigrationConflict[] = [];
|
|
287
|
+
for (const record of selection.records) {
|
|
288
|
+
const destination = destinationRecordIfPresent(destinationHome, destinationExists, record);
|
|
289
|
+
if (!destination) continue;
|
|
290
|
+
if (sameRecord(record, destination)) identical.push(recordRef(record));
|
|
291
|
+
else conflicts.push({ ...recordRef(record), kind: 'payload_conflict' });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
sourceHome,
|
|
296
|
+
destinationHome,
|
|
297
|
+
sourceDatabase: {
|
|
298
|
+
path: sourceDatabase,
|
|
299
|
+
sha256: await sha256(sourceDatabase),
|
|
300
|
+
...(sourceWalExists ? { walPath: sourceWal, walSha256: await sha256(sourceWal) } : {}),
|
|
301
|
+
inspection: inspectControlPlaneDatabaseFile(sourceDatabase),
|
|
302
|
+
},
|
|
303
|
+
destinationDatabase: {
|
|
304
|
+
path: destinationDatabase,
|
|
305
|
+
exists: destinationExists,
|
|
306
|
+
...(destinationExists ? { inspection: inspectControlPlaneDatabaseFile(destinationDatabase) } : {}),
|
|
307
|
+
},
|
|
308
|
+
selected,
|
|
309
|
+
selectedByNamespace: selectedByNamespace(selected),
|
|
310
|
+
identical,
|
|
311
|
+
conflicts,
|
|
312
|
+
archivedOnly: {
|
|
313
|
+
terminalWorkContracts: selection.terminalWorkContracts,
|
|
314
|
+
excludedNamespaces: ['mcp credentials', 'runtime release authority', 'runtime owner', 'Recovery state', 'plugin configuration', 'process bindings and logs'],
|
|
315
|
+
auditChain: 'retained_in_source_sqlite',
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function reportPath(destinationHome: string, migrationId: string): string {
|
|
321
|
+
return join(destinationHome, 'archives', 'controller-home-migrations', `${migrationId}.json`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function writeReport(path: string, value: unknown): void {
|
|
325
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
326
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
327
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
328
|
+
renameSync(temporary, path);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function applyControllerHomeMigration(input: {
|
|
332
|
+
sourceHome: string;
|
|
333
|
+
destinationHome: string;
|
|
334
|
+
}): Promise<AppliedControllerHomeMigration> {
|
|
335
|
+
const preview = await previewControllerHomeMigration(input);
|
|
336
|
+
if (preview.conflicts.length > 0) {
|
|
337
|
+
throw new Error(`CONTROLLER_HOME_MIGRATION_CONFLICTS: ${preview.conflicts.length}`);
|
|
338
|
+
}
|
|
339
|
+
const migrationId = `controller-home-${randomUUID()}`;
|
|
340
|
+
const destinationBackupPath = join(
|
|
341
|
+
preview.destinationHome,
|
|
342
|
+
'backups',
|
|
343
|
+
'controller-home-migrations',
|
|
344
|
+
`${migrationId}.before.sqlite`,
|
|
345
|
+
);
|
|
346
|
+
backupControlPlaneDatabase(preview.destinationHome, destinationBackupPath);
|
|
347
|
+
const sourceMode = statSync(preview.sourceDatabase.path).mode & 0o777;
|
|
348
|
+
const sourceWalMode = preview.sourceDatabase.walPath && existsSync(preview.sourceDatabase.walPath)
|
|
349
|
+
? statSync(preview.sourceDatabase.walPath).mode & 0o777
|
|
350
|
+
: undefined;
|
|
351
|
+
const imported: ImportedRecord[] = [];
|
|
352
|
+
const selectedRecords = sourceSelection(preview.sourceHome).records;
|
|
353
|
+
const migration = withControlPlaneTransaction(preview.destinationHome, (database) => {
|
|
354
|
+
for (const record of selectedRecords) {
|
|
355
|
+
const current = readControlPlaneRecordWithinTransaction<unknown>(database, record.namespace, record.scope, record.key);
|
|
356
|
+
if (current) {
|
|
357
|
+
if (sameRecord(record, current)) continue;
|
|
358
|
+
throw new Error(`CONTROLLER_HOME_MIGRATION_CONFLICT: ${record.namespace}/${record.scope}/${record.key}`);
|
|
359
|
+
}
|
|
360
|
+
const written = writeControlPlaneRecordWithinTransaction(database, {
|
|
361
|
+
namespace: record.namespace,
|
|
362
|
+
scope: record.scope,
|
|
363
|
+
key: record.key,
|
|
364
|
+
schemaVersion: record.schemaVersion,
|
|
365
|
+
value: record.value,
|
|
366
|
+
action: 'controller_home_migration_import',
|
|
367
|
+
expectedRevision: null,
|
|
368
|
+
});
|
|
369
|
+
imported.push({ ...recordRef(record), destinationRevision: written.revision });
|
|
370
|
+
}
|
|
371
|
+
const value: ControllerHomeMigrationRecord = {
|
|
372
|
+
schemaVersion: 1,
|
|
373
|
+
migrationId,
|
|
374
|
+
status: 'applied',
|
|
375
|
+
sourceHome: preview.sourceHome,
|
|
376
|
+
destinationHome: preview.destinationHome,
|
|
377
|
+
sourceDatabase: preview.sourceDatabase,
|
|
378
|
+
destinationBackupPath,
|
|
379
|
+
selected: preview.selected,
|
|
380
|
+
imported,
|
|
381
|
+
identical: preview.identical,
|
|
382
|
+
archivedOnly: preview.archivedOnly,
|
|
383
|
+
sourceArchive: {
|
|
384
|
+
databaseModeBefore: sourceMode,
|
|
385
|
+
databaseModeAfter: sourceMode,
|
|
386
|
+
...(sourceWalMode === undefined ? {} : { walModeBefore: sourceWalMode, walModeAfter: sourceWalMode }),
|
|
387
|
+
state: 'restored',
|
|
388
|
+
},
|
|
389
|
+
appliedAt: new Date().toISOString(),
|
|
390
|
+
};
|
|
391
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
392
|
+
namespace: MIGRATION_NAMESPACE,
|
|
393
|
+
scope: MIGRATION_SCOPE,
|
|
394
|
+
key: migrationId,
|
|
395
|
+
schemaVersion: 1,
|
|
396
|
+
value,
|
|
397
|
+
action: 'controller_home_migration_applied',
|
|
398
|
+
expectedRevision: null,
|
|
399
|
+
});
|
|
400
|
+
return value;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Only the legacy SQLite authority is frozen. Its full audit history stays
|
|
404
|
+
// queryable in place; no OAuth, Runtime, Recovery, or plugin files move.
|
|
405
|
+
const readOnlyMode = sourceMode & ~0o222;
|
|
406
|
+
chmodSync(preview.sourceDatabase.path, readOnlyMode);
|
|
407
|
+
const readOnlyWalMode = sourceWalMode === undefined ? undefined : sourceWalMode & ~0o222;
|
|
408
|
+
if (preview.sourceDatabase.walPath && readOnlyWalMode !== undefined) chmodSync(preview.sourceDatabase.walPath, readOnlyWalMode);
|
|
409
|
+
const archived = {
|
|
410
|
+
...migration,
|
|
411
|
+
sourceArchive: {
|
|
412
|
+
databaseModeBefore: sourceMode,
|
|
413
|
+
databaseModeAfter: readOnlyMode,
|
|
414
|
+
...(sourceWalMode === undefined ? {} : { walModeBefore: sourceWalMode, walModeAfter: readOnlyWalMode }),
|
|
415
|
+
state: 'read_only' as const,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
writeControlPlaneRecord(preview.destinationHome, {
|
|
419
|
+
namespace: MIGRATION_NAMESPACE,
|
|
420
|
+
scope: MIGRATION_SCOPE,
|
|
421
|
+
key: migrationId,
|
|
422
|
+
schemaVersion: 1,
|
|
423
|
+
value: archived,
|
|
424
|
+
action: 'controller_home_migration_archive_source',
|
|
425
|
+
expectedRevision: 1,
|
|
426
|
+
});
|
|
427
|
+
const path = reportPath(preview.destinationHome, migrationId);
|
|
428
|
+
writeReport(path, { preview, migration: archived });
|
|
429
|
+
return { preview, migration: archived, reportPath: path };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function rollbackControllerHomeMigration(input: {
|
|
433
|
+
destinationHome: string;
|
|
434
|
+
migrationId: string;
|
|
435
|
+
}): ControllerHomeMigrationRecord {
|
|
436
|
+
const destinationHome = resolve(input.destinationHome);
|
|
437
|
+
const current = readControlPlaneRecord<ControllerHomeMigrationRecord>(
|
|
438
|
+
destinationHome,
|
|
439
|
+
MIGRATION_NAMESPACE,
|
|
440
|
+
MIGRATION_SCOPE,
|
|
441
|
+
input.migrationId,
|
|
442
|
+
);
|
|
443
|
+
if (!current) throw new Error(`CONTROLLER_HOME_MIGRATION_NOT_FOUND: ${input.migrationId}`);
|
|
444
|
+
if (current.value.status === 'rolled_back') return current.value;
|
|
445
|
+
|
|
446
|
+
const rolledBack = withControlPlaneTransaction(destinationHome, (database) => {
|
|
447
|
+
for (const imported of current.value.imported) {
|
|
448
|
+
const record = readControlPlaneRecordWithinTransaction<unknown>(database, imported.namespace, imported.scope, imported.key);
|
|
449
|
+
if (!record || record.revision !== imported.destinationRevision) {
|
|
450
|
+
throw new Error(`CONTROLLER_HOME_MIGRATION_ROLLBACK_CONFLICT: ${imported.namespace}/${imported.scope}/${imported.key}`);
|
|
451
|
+
}
|
|
452
|
+
database.prepare(`
|
|
453
|
+
DELETE FROM control_plane_audit
|
|
454
|
+
WHERE namespace = ? AND scope = ? AND record_key = ?
|
|
455
|
+
`).run(imported.namespace, imported.scope, imported.key);
|
|
456
|
+
database.prepare(`
|
|
457
|
+
DELETE FROM control_plane_records
|
|
458
|
+
WHERE namespace = ? AND scope = ? AND record_key = ?
|
|
459
|
+
`).run(imported.namespace, imported.scope, imported.key);
|
|
460
|
+
}
|
|
461
|
+
const value: ControllerHomeMigrationRecord = {
|
|
462
|
+
...current.value,
|
|
463
|
+
status: 'rolled_back',
|
|
464
|
+
sourceArchive: {
|
|
465
|
+
...current.value.sourceArchive,
|
|
466
|
+
state: 'restored',
|
|
467
|
+
},
|
|
468
|
+
rolledBackAt: new Date().toISOString(),
|
|
469
|
+
};
|
|
470
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
471
|
+
namespace: MIGRATION_NAMESPACE,
|
|
472
|
+
scope: MIGRATION_SCOPE,
|
|
473
|
+
key: current.value.migrationId,
|
|
474
|
+
schemaVersion: CONTROL_PLANE_SCHEMA_VERSION,
|
|
475
|
+
value,
|
|
476
|
+
action: 'controller_home_migration_rolled_back',
|
|
477
|
+
expectedRevision: current.revision,
|
|
478
|
+
});
|
|
479
|
+
return value;
|
|
480
|
+
});
|
|
481
|
+
chmodSync(rolledBack.sourceDatabase.path, rolledBack.sourceArchive.databaseModeBefore);
|
|
482
|
+
if (rolledBack.sourceDatabase.walPath && rolledBack.sourceArchive.walModeBefore !== undefined) {
|
|
483
|
+
chmodSync(rolledBack.sourceDatabase.walPath, rolledBack.sourceArchive.walModeBefore);
|
|
484
|
+
}
|
|
485
|
+
writeReport(reportPath(destinationHome, rolledBack.migrationId), { migration: rolledBack });
|
|
486
|
+
return rolledBack;
|
|
487
|
+
}
|
|
@@ -165,18 +165,6 @@ export function updateRequirement(
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
/** @deprecated Migration/legacy compatibility only. New Plan creation must bind Plan.requirementId and derive active Plan slices by query. */
|
|
169
|
-
export function setRequirementPlan(
|
|
170
|
-
options: RequirementStoreOptions,
|
|
171
|
-
input: { requirementId: string; planId: string; action?: string },
|
|
172
|
-
): Requirement {
|
|
173
|
-
return updateRequirement(options, {
|
|
174
|
-
requirementId: input.requirementId,
|
|
175
|
-
action: input.action ?? 'requirement_active_plan_bound',
|
|
176
|
-
mutate: (current) => ({ ...current, activePlanId: id(input.planId) }),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
168
|
export interface RequirementCompletionInput {
|
|
181
169
|
requirementId: string;
|
|
182
170
|
work: Pick<WorkContract,
|
|
@@ -207,6 +207,37 @@ function openDatabase(controllerHome: string): SqliteDatabase {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Open an already initialized authority for SELECT-only callers without
|
|
212
|
+
* re-running WAL/schema setup. In WAL mode a reader can coexist with a writer
|
|
213
|
+
* holding BEGIN IMMEDIATE; forcing PRAGMA journal_mode/DDL on every read turns
|
|
214
|
+
* that harmless writer reservation into a synchronous busy_timeout on the
|
|
215
|
+
* Canonical Runtime event loop.
|
|
216
|
+
*/
|
|
217
|
+
function openDatabaseForRead(controllerHome: string): SqliteDatabase {
|
|
218
|
+
const path = controlPlaneDatabasePath(controllerHome);
|
|
219
|
+
if (!existsSync(path)) return openDatabase(controllerHome);
|
|
220
|
+
|
|
221
|
+
const database = openRawDatabase(path);
|
|
222
|
+
try {
|
|
223
|
+
database.exec('PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;');
|
|
224
|
+
const schemaVersion = assertSupportedSchema(database, path);
|
|
225
|
+
if (schemaVersion !== undefined) return database;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
database.close();
|
|
228
|
+
const busy = sqliteBusyError(path, error);
|
|
229
|
+
if (busy) throw busy;
|
|
230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
231
|
+
if (message.startsWith('CONTROL_PLANE_')) throw error;
|
|
232
|
+
throw new Error(`CONTROL_PLANE_SQLITE_CORRUPT: ${path}: ${message}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Preserve first-use/partially initialized compatibility: only a database
|
|
236
|
+
// with no schema authority falls back to the initializer/writer path.
|
|
237
|
+
database.close();
|
|
238
|
+
return openDatabase(controllerHome);
|
|
239
|
+
}
|
|
240
|
+
|
|
210
241
|
function rowToRecord<T>(row: StoredRecordRow): ControlPlaneRecord<T> {
|
|
211
242
|
let value: T;
|
|
212
243
|
try {
|
|
@@ -321,7 +352,7 @@ export function withControlPlaneTransaction<T>(controllerHome: string, operation
|
|
|
321
352
|
}
|
|
322
353
|
|
|
323
354
|
export function readControlPlaneRecord<T>(controllerHome: string, namespace: string, scope: string, key: string): ControlPlaneRecord<T> | undefined {
|
|
324
|
-
const database =
|
|
355
|
+
const database = openDatabaseForRead(controllerHome);
|
|
325
356
|
try {
|
|
326
357
|
return selectRecord<T>(database, namespace, scope, key);
|
|
327
358
|
} finally {
|
|
@@ -361,7 +392,7 @@ export function listControlPlaneRecords<T>(
|
|
|
361
392
|
controllerHome: string,
|
|
362
393
|
input: { namespace: string; scope?: string; limit?: number },
|
|
363
394
|
): ControlPlaneRecord<T>[] {
|
|
364
|
-
const database =
|
|
395
|
+
const database = openDatabaseForRead(controllerHome);
|
|
365
396
|
try {
|
|
366
397
|
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 1000), 5000));
|
|
367
398
|
const rows = input.scope === undefined
|
|
@@ -106,6 +106,60 @@ function releasePathFromAuthorityRecord(
|
|
|
106
106
|
return canonical(releasePath);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function loadPackageConnectorReleaseProtection(controllerHome: string, releasesRoot: string): string | undefined {
|
|
110
|
+
const connectorRoot = join(controllerHome, 'runtime', 'connector-service');
|
|
111
|
+
if (!existsSync(connectorRoot)) return undefined;
|
|
112
|
+
const authorityPath = join(connectorRoot, 'authority.json');
|
|
113
|
+
if (!existsSync(authorityPath)) throw new Error('package connector release authority is missing');
|
|
114
|
+
|
|
115
|
+
const parsed = JSON.parse(readFileSync(authorityPath, 'utf8')) as Record<string, unknown>;
|
|
116
|
+
const releaseId = typeof parsed.releaseId === 'string' ? parsed.releaseId.trim() : '';
|
|
117
|
+
const releaseRootValue = typeof parsed.releaseRoot === 'string' ? parsed.releaseRoot.trim() : '';
|
|
118
|
+
const packageRoot = typeof parsed.packageRoot === 'string' ? parsed.packageRoot.trim() : '';
|
|
119
|
+
const endpoint = typeof parsed.endpoint === 'string' ? parsed.endpoint.trim() : '';
|
|
120
|
+
if (parsed.schemaVersion !== 1 || !releaseId || !releaseRootValue || !packageRoot || !endpoint) {
|
|
121
|
+
throw new Error('package connector release authority is invalid');
|
|
122
|
+
}
|
|
123
|
+
const releaseRoot = resolve(releaseRootValue);
|
|
124
|
+
if (basename(releaseRoot) !== releaseId || !directChild(releasesRoot, releaseRoot) || !existsSync(releaseRoot)) {
|
|
125
|
+
throw new Error('package connector release is outside runtime release authority or missing');
|
|
126
|
+
}
|
|
127
|
+
return canonical(releaseRoot);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function loadRecoveryKnownGoodReleaseProtection(controllerHome: string, releasesRoot: string): Set<string> {
|
|
131
|
+
const knownGoodPath = join(controllerHome, 'recovery', 'state', 'known-good.json');
|
|
132
|
+
const protectedPaths = new Set<string>();
|
|
133
|
+
if (!existsSync(knownGoodPath)) return protectedPaths;
|
|
134
|
+
const parsed = JSON.parse(readFileSync(knownGoodPath, 'utf8')) as Record<string, unknown>;
|
|
135
|
+
if (parsed.schemaVersion !== 1 || !Array.isArray(parsed.releases)) {
|
|
136
|
+
throw new Error('standalone recovery known-good authority is invalid');
|
|
137
|
+
}
|
|
138
|
+
for (const raw of parsed.releases) {
|
|
139
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('standalone recovery known-good release record is invalid');
|
|
140
|
+
const record = raw as Record<string, unknown>;
|
|
141
|
+
const releaseId = typeof record.revision === 'string' ? record.revision.trim() : '';
|
|
142
|
+
const manifestPathValue = typeof record.path === 'string' ? record.path.trim() : '';
|
|
143
|
+
if (!releaseId || !manifestPathValue) throw new Error('standalone recovery known-good release identity is incomplete');
|
|
144
|
+
const manifestPath = resolve(manifestPathValue);
|
|
145
|
+
const releaseRoot = dirname(manifestPath);
|
|
146
|
+
if (
|
|
147
|
+
basename(manifestPath) !== 'manifest.json'
|
|
148
|
+
|| basename(releaseRoot) !== releaseId
|
|
149
|
+
|| !directChild(releasesRoot, releaseRoot)
|
|
150
|
+
) {
|
|
151
|
+
throw new Error('standalone recovery known-good release is outside runtime releases');
|
|
152
|
+
}
|
|
153
|
+
// Known-good history is append-only recovery evidence. A historical record
|
|
154
|
+
// may legitimately outlive the retained release directory after a bounded
|
|
155
|
+
// cleanup pass. Missing historical artifacts therefore protect nothing and
|
|
156
|
+
// must not disable retention for the remaining authoritative releases.
|
|
157
|
+
if (!existsSync(manifestPath) || !existsSync(releaseRoot)) continue;
|
|
158
|
+
protectedPaths.add(canonical(releaseRoot));
|
|
159
|
+
}
|
|
160
|
+
return protectedPaths;
|
|
161
|
+
}
|
|
162
|
+
|
|
109
163
|
function loadRuntimeProtection(controllerHome: string): RuntimeProtection | undefined {
|
|
110
164
|
const releasesRoot = join(controllerHome, 'runtime', 'releases');
|
|
111
165
|
if (!existsSync(releasesRoot)) return undefined;
|
|
@@ -124,6 +178,11 @@ function loadRuntimeProtection(controllerHome: string): RuntimeProtection | unde
|
|
|
124
178
|
if (previous !== undefined) {
|
|
125
179
|
releasePaths.add(releasePathFromAuthorityRecord(releasesRoot, previous, 'previous'));
|
|
126
180
|
}
|
|
181
|
+
const connectorRelease = loadPackageConnectorReleaseProtection(controllerHome, releasesRoot);
|
|
182
|
+
if (connectorRelease) releasePaths.add(connectorRelease);
|
|
183
|
+
for (const knownGoodRelease of loadRecoveryKnownGoodReleaseProtection(controllerHome, releasesRoot)) {
|
|
184
|
+
releasePaths.add(knownGoodRelease);
|
|
185
|
+
}
|
|
127
186
|
|
|
128
187
|
const backupPaths = new Set<string>();
|
|
129
188
|
let backupAuthoritySafe = true;
|
|
@@ -409,8 +468,9 @@ function scanLinkedReleaseFamily(
|
|
|
409
468
|
/**
|
|
410
469
|
* Reclaim immutable Controller release history while preserving the exact
|
|
411
470
|
* rollback authorities. Runtime keeps active + previous + the previous DB
|
|
412
|
-
* backup
|
|
413
|
-
*
|
|
471
|
+
* backup plus the immutable release backing the persistent public MCP Gateway;
|
|
472
|
+
* Recovery/Supervisor keep current + previous. Missing or malformed authority
|
|
473
|
+
* always fails closed.
|
|
414
474
|
*/
|
|
415
475
|
export function cleanupControllerReleaseHistory(
|
|
416
476
|
controllerHome: string,
|
|
@@ -2,16 +2,18 @@ import { RepoActor } from './actor';
|
|
|
2
2
|
|
|
3
3
|
export class RepoActorRegistry {
|
|
4
4
|
private readonly controllerHome: string;
|
|
5
|
+
private readonly defaultMaxConcurrentWorkers: number;
|
|
5
6
|
private readonly actors = new Map<string, RepoActor>();
|
|
6
|
-
constructor(controllerHome: string) {
|
|
7
|
+
constructor(controllerHome: string, options: { maxConcurrentWorkers?: number } = {}) {
|
|
7
8
|
this.controllerHome = controllerHome;
|
|
9
|
+
this.defaultMaxConcurrentWorkers = Math.max(1, options.maxConcurrentWorkers ?? 4);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
get(repoId: string): RepoActor {
|
|
11
13
|
let actor = this.actors.get(repoId);
|
|
12
14
|
if (!actor) {
|
|
13
15
|
actor = new RepoActor(this.controllerHome, repoId, {
|
|
14
|
-
maxConcurrentWorkers: Number(process.env.FORGE_PER_REPO_WORKERS ??
|
|
16
|
+
maxConcurrentWorkers: Number(process.env.FORGE_PER_REPO_WORKERS ?? this.defaultMaxConcurrentWorkers),
|
|
15
17
|
});
|
|
16
18
|
this.actors.set(repoId, actor);
|
|
17
19
|
}
|