specrails-desktop 2.55.0 → 2.56.0
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/cli/dist/args.js +80 -0
- package/cli/dist/desktop-client.js +125 -0
- package/cli/dist/desktop-control.js +339 -0
- package/cli/dist/format.js +45 -0
- package/cli/dist/help.js +68 -0
- package/cli/dist/output.js +47 -0
- package/cli/dist/run-command.js +340 -0
- package/cli/dist/specrails-desktop.js +41 -1107
- package/cli/dist/status.js +111 -0
- package/client/dist/assets/{ActivityFeedPage-ILXMZXcb.js → ActivityFeedPage-DdMNmVBk.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-UcsupCy7.js → AgentBrowserCapture-CeerUf--.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-CQuxYh8B.js → AgentModeAnalyticsPane-BjGb-8rh.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-zPWbW7ei.js → AgentModeCodePane-8XRkQEo_.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CInByIdl.js → AgentModeJobsPane-BN3dgJOA.js} +1 -1
- package/client/dist/assets/{AgentsPage-BWvgxaBv.js → AgentsPage-DfHWgP_x.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DP3jakv-.js → AnalyticsPage-CyfhYiBn.js} +1 -1
- package/client/dist/assets/{CodePage-BKXiwE5a.js → CodePage-C2Nf-Q5W.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DryJOaMz.js → DesktopAnalyticsPage-tc0p4jJW.js} +1 -1
- package/client/dist/assets/{DocsDialog-BsCG9ksG.js → DocsDialog-Br3bczCp.js} +1 -1
- package/client/dist/assets/{DocsPage-Cc-GeKYl.js → DocsPage-BcDr2cBm.js} +1 -1
- package/client/dist/assets/{ExportDropdown-ClahJdDn.js → ExportDropdown-BVGKEgkd.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-DfLF6N0f.js → InteractiveJobComposer-DFPpdg2D.js} +1 -1
- package/client/dist/assets/{JobDetailModal-DHeur9YK.js → JobDetailModal-BdOv-l-M.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CraDKzeG.js → JobDetailPage-vBbNeRuz.js} +1 -1
- package/client/dist/assets/{JobsPage-CcbuSwj7.js → JobsPage-CJCtPhIz.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-BKtNp494.js → LoopBuilderPage-BemvUOxy.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-DbZd9isy.js → LoopPreviewModal-C6hNAeYN.js} +1 -1
- package/client/dist/assets/{LoopsPage-DEg6sIkG.js → LoopsPage-BZr0quvh.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-BGtEjWsc.js → MinimizedChatsContext-D3QzgV0B.js} +1 -1
- package/client/dist/assets/{PluginsPage-BVqSsPzL.js → PluginsPage-DOpTV75p.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-CAjLgJAZ.js → ProjectSettingsDialog-Dkwrn0z1.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-DrcrxLe0.js → RepositoryDeliveries-Du2UCf1a.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-CAGY3xW6.js → RepositoryScopeSelector-hPGZNRBm.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-DcOq-UTA.js +4 -0
- package/client/dist/assets/{TemplatePreviewModal-Bqs-9fhn.js → TemplatePreviewModal-Cejcquah.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-C0jQ5yOk.js → TicketDetailModalContext-DAK-NoK6.js} +1 -1
- package/client/dist/assets/{Trans-DtWlCJN9.js → Trans-DEbgkuAY.js} +1 -1
- package/client/dist/assets/{dashboard-CTfEtkqX.js → dashboard-B9uWNGO2.js} +1 -1
- package/client/dist/assets/{dashboard-DMklepmz.js → dashboard-BIwmO9wc.js} +1 -1
- package/client/dist/assets/{dashboard-DBAP2_SQ.js → dashboard-BV4vOZkR.js} +1 -1
- package/client/dist/assets/{dashboard-D40HP2ks.js → dashboard-Bf6MPVsM.js} +1 -1
- package/client/dist/assets/{dashboard-Csf1yGSN.js → dashboard-C8klWVvZ.js} +1 -1
- package/client/dist/assets/{dashboard-C29Qwa-L.js → dashboard-D1ORhPF0.js} +1 -1
- package/client/dist/assets/{dashboard-Cr4D8oMn.js → dashboard-DSubvs9x.js} +1 -1
- package/client/dist/assets/{dashboard-CSJzf5OT.js → dashboard-px_vlRx_.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-DYmvOA3R.js → formatDistanceToNow-Cc2u5FwQ.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BAhW5E6V.js → getTimezoneOffsetInMilliseconds-BHm2U-w1.js} +1 -1
- package/client/dist/assets/index-C51x61Bz.css +2 -0
- package/client/dist/assets/{index-BfAZg5DG.js → index-Cxb288Rj.js} +29 -29
- package/client/dist/assets/{jira-api-B27Za5FP.js → jira-api-DrGxiD9p.js} +1 -1
- package/client/dist/assets/packet-8nn9RsU7.js +1 -0
- package/client/dist/assets/packet-B0H15elv.js +1 -0
- package/client/dist/assets/packet-BQvBgrwh.js +1 -0
- package/client/dist/assets/packet-BR7mO0-a.js +1 -0
- package/client/dist/assets/packet-BgcQ6q_K.js +1 -0
- package/client/dist/assets/packet-D2s92Jnn.js +1 -0
- package/client/dist/assets/packet-YbtcD1HS.js +1 -0
- package/client/dist/assets/packet-c0Qj_9eX.js +1 -0
- package/client/dist/assets/{project-repositories-DixIZHTA.js → project-repositories-9Wzs0coQ.js} +1 -1
- package/client/dist/assets/{spending-BGmZBX5B.js → spending-T5GM7wpn.js} +1 -1
- package/client/dist/assets/{useDesktop-CPB4cnwI.js → useDesktop-QogiN0zR.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-DEQ9HdAM.js → useRuntimeRuns-Dqmz9fS9.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-DcPRKR8R.js → useSharedWebSocket-Dz-HI6De.js} +2 -2
- package/client/dist/index.html +17 -17
- package/docs/codex.md +5 -5
- package/docs/customizing.md +1 -1
- package/docs/gemini-cli-provider-study.md +14 -14
- package/docs/gemini-core-support-evaluation.md +1 -1
- package/docs/gemini.md +5 -5
- package/docs/internals/README.md +6 -2
- package/docs/internals/adding-a-provider.md +22 -15
- package/docs/internals/agent-runtime-framework-evaluation.md +8 -8
- package/docs/internals/api-reference.md +2 -2
- package/docs/internals/architecture.md +9 -9
- package/docs/internals/browser-capture-performance.md +3 -3
- package/docs/internals/code-explorer-story.md +2 -2
- package/docs/internals/companion-rails-as-loops-contract.md +2 -2
- package/docs/internals/core-runtime-updates.md +5 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +5 -5
- package/docs/internals/gemini-mcp-registration.md +2 -2
- package/docs/internals/global-artifacts-alignment-contract.md +1 -1
- package/docs/internals/global-artifacts-relocation-evaluation.md +1 -1
- package/docs/internals/interactive-jobs.md +8 -8
- package/docs/internals/legacy-implementation-notes.md +722 -0
- package/docs/internals/loop-step-log-explorer.md +8 -8
- package/docs/internals/mission-rail-cards.md +14 -14
- package/docs/internals/modular-architecture.md +107 -0
- package/docs/internals/profiles.md +1 -1
- package/docs/internals/programmatic-agent-runtime.md +66 -0
- package/docs/internals/project-builder.md +18 -18
- package/docs/internals/review-packet.md +8 -8
- package/docs/internals/safe-pr-review-flow.md +11 -11
- package/docs/internals/source-architecture.md +131 -0
- package/docs/internals/source-map.md +1356 -0
- package/docs/internals/spec-addenda.md +65 -9
- package/docs/internals/switch-to-agent-mode-design.md +11 -11
- package/docs/tracking-cost.md +1 -1
- package/package.json +5 -2
- package/server/dist/agent-mcp-config.js +1 -1
- package/server/dist/attachment-manager.js +1 -1
- package/server/dist/build-dirs.js +2 -2
- package/server/dist/config.js +0 -3
- package/server/dist/core-completion.js +1 -1
- package/server/dist/core-execution.js +1 -1
- package/server/dist/db/activity.js +39 -0
- package/server/dist/db/connection.js +41 -0
- package/server/dist/db/conversations.js +48 -0
- package/server/dist/db/jobs.js +251 -0
- package/server/dist/db/migrations.js +1521 -0
- package/server/dist/db/proposals.js +50 -0
- package/server/dist/db/settings.js +15 -0
- package/server/dist/db/stats.js +61 -0
- package/server/dist/db/telemetry.js +56 -0
- package/server/dist/db/templates.js +37 -0
- package/server/dist/db/types.js +2 -0
- package/server/dist/db.js +66 -2224
- package/server/dist/desktop-db.js +3 -3
- package/server/dist/desktop-router.js +8 -8
- package/server/dist/git-diagnostics.js +1 -1
- package/server/dist/index.js +11 -11
- package/server/dist/integration-branch.js +3 -14
- package/server/dist/jira/jira-issue-fields.js +0 -1
- package/server/dist/jira/jira-materializer.js +1 -1
- package/server/dist/jira/jira-sync-manager.js +1 -1
- package/server/dist/mcp/guide.js +16 -2
- package/server/dist/mcp/mcp-admin-router.js +1 -1
- package/server/dist/mcp/mcp-server.js +1 -1
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/jobs.js +23 -5
- package/server/dist/mcp/tools/rails.js +2 -2
- package/server/dist/mcp/tools/recovery.js +35 -0
- package/server/dist/mcp/tools/specs.js +2 -2
- package/server/dist/mcp/tools/support.js +4 -1
- package/server/dist/mcp/tools/types.js +3 -3
- package/server/dist/mobile/mobile-gateway.js +1 -1
- package/server/dist/mobile/mobile-missions.js +1 -1
- package/server/dist/{desktop-analytics.js → modules/accounting/runtime/desktop-analytics.js} +2 -2
- package/server/dist/{pricing.js → modules/accounting/runtime/pricing.js} +10 -3
- package/server/dist/{result-event.js → modules/accounting/runtime/result-event.js} +3 -3
- package/server/dist/{spending.js → modules/accounting/runtime/spending.js} +0 -3
- package/server/dist/{telemetry-compactor.js → modules/accounting/runtime/telemetry-compactor.js} +1 -1
- package/server/dist/{telemetry-receiver.js → modules/accounting/runtime/telemetry-receiver.js} +2 -2
- package/server/dist/{agent-runtime-bridge.js → modules/agent-runtime/runtime/agent-runtime-bridge.js} +2 -2
- package/server/dist/{agent-runtime-controls-router.js → modules/agent-runtime/runtime/agent-runtime-controls-router.js} +16 -0
- package/server/dist/{agent-runtime-controls.js → modules/agent-runtime/runtime/agent-runtime-controls.js} +100 -8
- package/server/dist/{agent-runtime-effective-config.js → modules/agent-runtime/runtime/agent-runtime-effective-config.js} +1 -1
- package/server/dist/{agent-runtime-loader.js → modules/agent-runtime/runtime/agent-runtime-loader.js} +3 -3
- package/server/dist/{agent-runtime-paths.js → modules/agent-runtime/runtime/agent-runtime-paths.js} +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-recovery.js +53 -0
- package/server/dist/{agent-runtime-settings-router.js → modules/agent-runtime/runtime/agent-runtime-settings-router.js} +2 -2
- package/server/dist/{agent-runtime-settings.js → modules/agent-runtime/runtime/agent-runtime-settings.js} +3 -3
- package/server/dist/{agent-runtime-settlement.js → modules/agent-runtime/runtime/agent-runtime-settlement.js} +6 -6
- package/server/dist/{agent-defaults.js → modules/agents/runtime/agent-defaults.js} +3 -3
- package/server/dist/{agent-generator.js → modules/agents/runtime/agent-generator.js} +7 -7
- package/server/dist/{agent-refine-manager.js → modules/agents/runtime/agent-refine-manager.js} +5 -5
- package/server/dist/{agent-store.js → modules/agents/runtime/agent-store.js} +1 -1
- package/server/dist/{profile-manager.js → modules/agents/runtime/profile-manager.js} +2 -2
- package/server/dist/{profiles-router.js → modules/agents/runtime/profiles-router.js} +5 -5
- package/server/dist/{browser-capture-manager.js → modules/browser/runtime/browser-capture-manager.js} +2 -4
- package/server/dist/{browser-playwright.js → modules/browser/runtime/browser-playwright.js} +1 -1
- package/server/dist/{blueprint-chat-manager.js → modules/builder/runtime/blueprint-chat-manager.js} +7 -7
- package/server/dist/{blueprint-commit.js → modules/builder/runtime/blueprint-commit.js} +8 -8
- package/server/dist/{blueprint-draft-parser.js → modules/builder/runtime/blueprint-draft-parser.js} +1 -1
- package/server/dist/{blueprint-generation.js → modules/builder/runtime/blueprint-generation.js} +1 -1
- package/server/dist/{blueprint-operator-prompt.js → modules/builder/runtime/blueprint-operator-prompt.js} +1 -1
- package/server/dist/{blueprint-router.js → modules/builder/runtime/blueprint-router.js} +3 -3
- package/server/dist/{blueprint-spec-quality.js → modules/builder/runtime/blueprint-spec-quality.js} +1 -1
- package/server/dist/{milestone-chain.js → modules/builder/runtime/milestone-chain.js} +1 -1
- package/server/dist/{milestone-progress.js → modules/builder/runtime/milestone-progress.js} +6 -6
- package/server/dist/{code-activity.js → modules/code/runtime/code-activity.js} +1 -1
- package/server/dist/{code-explorer-router.js → modules/code/runtime/code-explorer-router.js} +5 -5
- package/server/dist/{file-provenance.js → modules/code/runtime/file-provenance.js} +1 -1
- package/server/dist/{file-story-manager.js → modules/code/runtime/file-story-manager.js} +2 -2
- package/server/dist/{file-story.js → modules/code/runtime/file-story.js} +2 -2
- package/server/dist/{file-summary-generator.js → modules/code/runtime/file-summary-generator.js} +3 -3
- package/server/dist/{file-summary-manager.js → modules/code/runtime/file-summary-manager.js} +3 -3
- package/server/dist/{project-code-discovery.js → modules/code/runtime/project-code-discovery.js} +2 -2
- package/server/dist/modules/conversations/domain/draft-stream.js +91 -0
- package/server/dist/modules/conversations/domain/recovery-context.js +101 -0
- package/server/dist/modules/conversations/index.js +10 -0
- package/server/dist/{chat-manager.js → modules/conversations/runtime/chat-manager.js} +32 -216
- package/server/dist/{context-budget.js → modules/conversations/runtime/context-budget.js} +1 -1
- package/server/dist/{explore-cwd-manager.js → modules/conversations/runtime/explore-cwd-manager.js} +1 -1
- package/server/dist/{explore-smash.js → modules/conversations/runtime/explore-smash.js} +1 -1
- package/server/dist/{explore-stdin-session.js → modules/conversations/runtime/explore-stdin-session.js} +1 -1
- package/server/dist/modules/delivery/adapters/decisions/chains.js +138 -0
- package/server/dist/modules/delivery/adapters/decisions/contracts.js +2 -0
- package/server/dist/modules/delivery/adapters/decisions/create.js +234 -0
- package/server/dist/modules/delivery/adapters/decisions/discard.js +251 -0
- package/server/dist/modules/delivery/adapters/decisions/dispatch.js +107 -0
- package/server/dist/modules/delivery/adapters/decisions/evidence.js +205 -0
- package/server/dist/modules/delivery/adapters/decisions/merge-local.js +338 -0
- package/server/dist/modules/delivery/adapters/decisions/metadata.js +109 -0
- package/server/dist/modules/delivery/adapters/decisions/observations.js +260 -0
- package/server/dist/modules/delivery/adapters/decisions/ownership.js +83 -0
- package/server/dist/modules/delivery/adapters/decisions/publish.js +242 -0
- package/server/dist/modules/delivery/adapters/decisions/recovery.js +425 -0
- package/server/dist/modules/delivery/adapters/decisions/retry.js +112 -0
- package/server/dist/modules/delivery/adapters/decisions/transitions.js +94 -0
- package/server/dist/modules/delivery/domain/decision-policy.js +50 -0
- package/server/dist/modules/delivery/domain/state.js +2 -0
- package/server/dist/modules/delivery/index.js +7 -0
- package/server/dist/{active-pr-continuation.js → modules/delivery/runtime/active-pr-continuation.js} +2 -2
- package/server/dist/{delivery-evidence.js → modules/delivery/runtime/delivery-evidence.js} +3 -3
- package/server/dist/{multi-repo-bases.js → modules/delivery/runtime/multi-repo-bases.js} +1 -1
- package/server/dist/{multi-repo-checkout.js → modules/delivery/runtime/multi-repo-checkout.js} +4 -4
- package/server/dist/{multi-repo-delivery.js → modules/delivery/runtime/multi-repo-delivery.js} +4 -4
- package/server/dist/{multi-repo-execution.js → modules/delivery/runtime/multi-repo-execution.js} +8 -8
- package/server/dist/{pr-body.js → modules/delivery/runtime/pr-body.js} +1 -1
- package/server/dist/{pr-follow-up-scope.js → modules/delivery/runtime/pr-follow-up-scope.js} +2 -2
- package/server/dist/{pr-follow-up.js → modules/delivery/runtime/pr-follow-up.js} +1 -1
- package/server/dist/{pr-naming.js → modules/delivery/runtime/pr-naming.js} +1 -1
- package/server/dist/{pr-publisher.js → modules/delivery/runtime/pr-publisher.js} +2 -2
- package/server/dist/{rail-isolated-launch.js → modules/delivery/runtime/rail-isolated-launch.js} +29 -27
- package/server/dist/{rail-launch-parser.js → modules/delivery/runtime/rail-launch-parser.js} +2 -2
- package/server/dist/{rail-merge-orchestrator.js → modules/delivery/runtime/rail-merge-orchestrator.js} +5 -5
- package/server/dist/modules/delivery/runtime/rail-pr-decision.js +12 -0
- package/server/dist/{rail-pr-delivery.js → modules/delivery/runtime/rail-pr-delivery.js} +1 -1
- package/server/dist/{rail-pr-store.js → modules/delivery/runtime/rail-pr-store.js} +2 -2
- package/server/dist/{rail-pr-ticket-effects.js → modules/delivery/runtime/rail-pr-ticket-effects.js} +3 -3
- package/server/dist/{rail-worktree-release.js → modules/delivery/runtime/rail-worktree-release.js} +4 -4
- package/server/dist/{rails-router.js → modules/delivery/runtime/rails-router.js} +66 -67
- package/server/dist/{review-packet.js → modules/delivery/runtime/review-packet.js} +3 -3
- package/server/dist/modules/execution/adapters/budget-storage.js +21 -0
- package/server/dist/modules/execution/adapters/usage-reader.js +38 -0
- package/server/dist/modules/execution/application/enforce-budget.js +14 -0
- package/server/dist/modules/execution/application/record-job-invocations.js +64 -0
- package/server/dist/modules/execution/application/recover-job-usage.js +200 -0
- package/server/dist/modules/execution/domain/job-accounting.js +2 -0
- package/server/dist/modules/execution/domain/scheduling.js +24 -0
- package/server/dist/modules/execution/domain/usage.js +21 -0
- package/server/dist/modules/execution/index.js +14 -0
- package/server/dist/modules/execution/ports.js +2 -0
- package/server/dist/{accept-ladder.js → modules/execution/runtime/accept-ladder.js} +1 -1
- package/server/dist/{interactive-job-session.js → modules/execution/runtime/interactive-job-session.js} +11 -11
- package/server/dist/{job-phase-breakdown.js → modules/execution/runtime/job-phase-breakdown.js} +1 -1
- package/server/dist/{queue-manager.js → modules/execution/runtime/queue-manager.js} +74 -491
- package/server/dist/{revision-seed.js → modules/execution/runtime/revision-seed.js} +1 -1
- package/server/dist/{spawn-lifecycle.js → modules/execution/runtime/spawn-lifecycle.js} +3 -3
- package/server/dist/{stuck-run-detector.js → modules/execution/runtime/stuck-run-detector.js} +1 -1
- package/server/dist/{loop-command-catalog.js → modules/loops/runtime/loop-command-catalog.js} +1 -1
- package/server/dist/{loop-executors.js → modules/loops/runtime/loop-executors.js} +17 -17
- package/server/dist/{loop-factory.js → modules/loops/runtime/loop-factory.js} +2 -21
- package/server/dist/{loop-graph.js → modules/loops/runtime/loop-graph.js} +25 -0
- package/server/dist/{loop-role-engines.js → modules/loops/runtime/loop-role-engines.js} +4 -4
- package/server/dist/{loop-run-manager.js → modules/loops/runtime/loop-run-manager.js} +101 -26
- package/server/dist/{loop-runs-store.js → modules/loops/runtime/loop-runs-store.js} +1 -1
- package/server/dist/{loop-shell-invocation.js → modules/loops/runtime/loop-shell-invocation.js} +2 -2
- package/server/dist/{loop-step-idle.js → modules/loops/runtime/loop-step-idle.js} +1 -1
- package/server/dist/{loop-templates.js → modules/loops/runtime/loop-templates.js} +24 -29
- package/server/dist/{loops-router.js → modules/loops/runtime/loops-router.js} +2 -2
- package/server/dist/{agent-chat-manager.js → modules/missions/runtime/agent-chat-manager.js} +19 -19
- package/server/dist/{agent-chat-router.js → modules/missions/runtime/agent-chat-router.js} +6 -6
- package/server/dist/{agent-context-resolver.js → modules/missions/runtime/agent-context-resolver.js} +8 -8
- package/server/dist/{agent-failure-briefing.js → modules/missions/runtime/agent-failure-briefing.js} +4 -2
- package/server/dist/{agent-fence-promotion.js → modules/missions/runtime/agent-fence-promotion.js} +1 -1
- package/server/dist/{agent-input-store.js → modules/missions/runtime/agent-input-store.js} +1 -1
- package/server/dist/{agent-operator-prompt.js → modules/missions/runtime/agent-operator-prompt.js} +75 -12
- package/server/dist/{agent-spec-framing.js → modules/missions/runtime/agent-spec-framing.js} +4 -4
- package/server/dist/{agent-steering.js → modules/missions/runtime/agent-steering.js} +1 -1
- package/server/dist/{mission-run-notify.js → modules/missions/runtime/mission-run-notify.js} +5 -2
- package/server/dist/modules/project-settings/adapters/http.js +24 -0
- package/server/dist/modules/project-settings/adapters/sqlite.js +120 -0
- package/server/dist/modules/project-settings/application.js +11 -0
- package/server/dist/modules/project-settings/domain.js +95 -0
- package/server/dist/modules/project-settings/index.js +11 -0
- package/server/dist/modules/project-settings/ports.js +2 -0
- package/server/dist/{contract-refine-runner.js → modules/specs/runtime/contract-refine-runner.js} +12 -12
- package/server/dist/{proposal-manager.js → modules/specs/runtime/proposal-manager.js} +8 -8
- package/server/dist/{smash-runner.js → modules/specs/runtime/smash-runner.js} +26 -58
- package/server/dist/{spec-addenda-core.js → modules/specs/runtime/spec-addenda-core.js} +2 -2
- package/server/dist/{spec-addenda.js → modules/specs/runtime/spec-addenda.js} +3 -3
- package/server/dist/{spec-launcher-manager.js → modules/specs/runtime/spec-launcher-manager.js} +8 -8
- package/server/dist/{spec-models.js → modules/specs/runtime/spec-models.js} +1 -1
- package/server/dist/{ticket-watcher.js → modules/specs/runtime/ticket-watcher.js} +1 -1
- package/server/dist/{background-process-control.js → modules/terminals/runtime/background-process-control.js} +1 -1
- package/server/dist/{background-process-service.js → modules/terminals/runtime/background-process-service.js} +3 -3
- package/server/dist/{background-process-store.js → modules/terminals/runtime/background-process-store.js} +1 -1
- package/server/dist/{background-windows-bootstrap.js → modules/terminals/runtime/background-windows-bootstrap.js} +3 -3
- package/server/dist/{terminal-manager.js → modules/terminals/runtime/terminal-manager.js} +3 -3
- package/server/dist/project-git.js +1 -1
- package/server/dist/project-profile-support.js +32 -0
- package/server/dist/project-registry.js +34 -40
- package/server/dist/project-router-background-processes.js +1 -1
- package/server/dist/project-router-chat.js +6 -6
- package/server/dist/project-router-git.js +1 -1
- package/server/dist/project-router-helpers.js +2 -2
- package/server/dist/project-router-jobs.js +14 -12
- package/server/dist/project-router-loop-runs.js +12 -12
- package/server/dist/project-router-settings.js +15 -75
- package/server/dist/project-router-setup.js +2 -2
- package/server/dist/project-router-spending.js +4 -4
- package/server/dist/project-router-terminals.js +4 -4
- package/server/dist/project-router-tickets.js +14 -14
- package/server/dist/project-router.js +13 -13
- package/server/dist/providers/claude-adapter.js +3 -2
- package/server/dist/providers/codex-adapter.js +3 -3
- package/server/dist/providers/gemini-adapter.js +1 -1
- package/server/dist/providers/telemetry-env.js +55 -0
- package/server/dist/runtime-role-prompts-router.js +2 -2
- package/server/dist/setup-manager.js +2 -48
- package/server/dist/shared/git-branch-name.js +16 -0
- package/server/dist/transient-children.js +3 -3
- package/server/dist/util/distribute-int.js +33 -0
- package/client/dist/assets/ReviewPacketPage-D-UjTef3.js +0 -4
- package/client/dist/assets/index-BAtqmAZR.css +0 -2
- package/client/dist/assets/packet-CjS_478C.js +0 -1
- package/client/dist/assets/packet-D69dKsHN.js +0 -1
- package/client/dist/assets/packet-D9rU6Oxo.js +0 -1
- package/client/dist/assets/packet-DMDFoamg.js +0 -1
- package/client/dist/assets/packet-Dd2RMs1h.js +0 -1
- package/client/dist/assets/packet-DjC9OibC.js +0 -1
- package/client/dist/assets/packet-DwPtJoDz.js +0 -1
- package/client/dist/assets/packet-ceahDg8_.js +0 -1
- package/server/dist/rail-pr-decision.js +0 -2377
- package/server/dist/ticket-broadcast.js +0 -47
- /package/server/dist/{ai-invocations.js → modules/accounting/runtime/ai-invocations.js} +0 -0
- /package/server/dist/{codex-otel-bridge.js → modules/accounting/runtime/codex-otel-bridge.js} +0 -0
- /package/server/dist/{metrics.js → modules/accounting/runtime/metrics.js} +0 -0
- /package/server/dist/{telemetry-export.js → modules/accounting/runtime/telemetry-export.js} +0 -0
- /package/server/dist/{agent-runtime-accounting.js → modules/agent-runtime/runtime/agent-runtime-accounting.js} +0 -0
- /package/server/dist/{agent-runtime-events.js → modules/agent-runtime/runtime/agent-runtime-events.js} +0 -0
- /package/server/dist/{agent-runtime-history.js → modules/agent-runtime/runtime/agent-runtime-history.js} +0 -0
- /package/server/dist/{agent-runtime-metrics.js → modules/agent-runtime/runtime/agent-runtime-metrics.js} +0 -0
- /package/server/dist/{agent-runtime-package.js → modules/agent-runtime/runtime/agent-runtime-package.js} +0 -0
- /package/server/dist/{agent-runtime-repositories.js → modules/agent-runtime/runtime/agent-runtime-repositories.js} +0 -0
- /package/server/dist/{agent-runtime-verification-suggestions.js → modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js} +0 -0
- /package/server/dist/{agent-refine-db.js → modules/agents/runtime/agent-refine-db.js} +0 -0
- /package/server/dist/{browser-capture-types.js → modules/browser/runtime/browser-capture-types.js} +0 -0
- /package/server/dist/{browser-context-pool.js → modules/browser/runtime/browser-context-pool.js} +0 -0
- /package/server/dist/{browser-network.js → modules/browser/runtime/browser-network.js} +0 -0
- /package/server/dist/{browser-viewport.js → modules/browser/runtime/browser-viewport.js} +0 -0
- /package/server/dist/{blueprint-render.js → modules/builder/runtime/blueprint-render.js} +0 -0
- /package/server/dist/{blueprint-spec-fixtures.js → modules/builder/runtime/blueprint-spec-fixtures.js} +0 -0
- /package/server/dist/{blueprint-store.js → modules/builder/runtime/blueprint-store.js} +0 -0
- /package/server/dist/{blueprint-types.js → modules/builder/runtime/blueprint-types.js} +0 -0
- /package/server/dist/{builder-cwd-manager.js → modules/builder/runtime/builder-cwd-manager.js} +0 -0
- /package/server/dist/{milestone-chain-store.js → modules/builder/runtime/milestone-chain-store.js} +0 -0
- /package/server/dist/{changes-reader.js → modules/code/runtime/changes-reader.js} +0 -0
- /package/server/dist/{context-scope.js → modules/conversations/runtime/context-scope.js} +0 -0
- /package/server/dist/{explore-contract-refine.js → modules/conversations/runtime/explore-contract-refine.js} +0 -0
- /package/server/dist/{explore-draft-title.js → modules/conversations/runtime/explore-draft-title.js} +0 -0
- /package/server/dist/{merge-manager.js → modules/delivery/runtime/merge-manager.js} +0 -0
- /package/server/dist/{multi-repo-execution-store.js → modules/delivery/runtime/multi-repo-execution-store.js} +0 -0
- /package/server/dist/{pr-lifecycle.js → modules/delivery/runtime/pr-lifecycle.js} +0 -0
- /package/server/dist/{rail-isolation.js → modules/delivery/runtime/rail-isolation.js} +0 -0
- /package/server/dist/{rail-pr-recovery-git.js → modules/delivery/runtime/rail-pr-recovery-git.js} +0 -0
- /package/server/dist/{rail-worktrees-store.js → modules/delivery/runtime/rail-worktrees-store.js} +0 -0
- /package/server/dist/{rails-store.js → modules/delivery/runtime/rails-store.js} +0 -0
- /package/server/dist/{job-listing.js → modules/execution/runtime/job-listing.js} +0 -0
- /package/server/dist/{job-spawn-idempotency.js → modules/execution/runtime/job-spawn-idempotency.js} +0 -0
- /package/server/dist/{run-duration-stats.js → modules/execution/runtime/run-duration-stats.js} +0 -0
- /package/server/dist/{verification-sentinel.js → modules/execution/runtime/verification-sentinel.js} +0 -0
- /package/server/dist/{loop-constants.js → modules/loops/runtime/loop-constants.js} +0 -0
- /package/server/dist/{loop-decider.js → modules/loops/runtime/loop-decider.js} +0 -0
- /package/server/dist/{loop-effect.js → modules/loops/runtime/loop-effect.js} +0 -0
- /package/server/dist/{loop-preview.js → modules/loops/runtime/loop-preview.js} +0 -0
- /package/server/dist/{loop-templates-ported.js → modules/loops/runtime/loop-templates-ported.js} +0 -0
- /package/server/dist/{loops-store.js → modules/loops/runtime/loops-store.js} +0 -0
- /package/server/dist/{agent-chat-registry.js → modules/missions/runtime/agent-chat-registry.js} +0 -0
- /package/server/dist/{agent-cwd-manager.js → modules/missions/runtime/agent-cwd-manager.js} +0 -0
- /package/server/dist/{agent-tier.js → modules/missions/runtime/agent-tier.js} +0 -0
- /package/server/dist/{spec-contract-prompt.js → modules/specs/runtime/spec-contract-prompt.js} +0 -0
- /package/server/dist/{spec-draft-parser.js → modules/specs/runtime/spec-draft-parser.js} +0 -0
- /package/server/dist/{ticket-store.js → modules/specs/runtime/ticket-store.js} +0 -0
- /package/server/dist/{terminal-marks-store.js → modules/terminals/runtime/terminal-marks-store.js} +0 -0
- /package/server/dist/{terminal-osc-parser.js → modules/terminals/runtime/terminal-osc-parser.js} +0 -0
- /package/server/dist/{terminal-settings.js → modules/terminals/runtime/terminal-settings.js} +0 -0
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyMigrations = applyMigrations;
|
|
4
|
+
const sqlite_migrations_1 = require("../util/sqlite-migrations");
|
|
5
|
+
const multi_repo_execution_store_1 = require("../modules/delivery/runtime/multi-repo-execution-store");
|
|
6
|
+
const project_repository_provenance_1 = require("../project-repository-provenance");
|
|
7
|
+
const file_story_1 = require("../modules/code/runtime/file-story");
|
|
8
|
+
/** Backfill only terminal Safe-PR rows whose ticket ownership is unambiguous.
|
|
9
|
+
* Eligibility is intentionally deferred to the outbox drainer, which freezes
|
|
10
|
+
* only candidates still parked at on_review in the external ticket JSON. */
|
|
11
|
+
function backfillTerminalRailPrTicketEffects(db) {
|
|
12
|
+
const terminalRows = db.prepare(`
|
|
13
|
+
SELECT id, ticket_ids, run_ids, decision, is_continuation,
|
|
14
|
+
supersedes_delivery_id, pr_url
|
|
15
|
+
FROM rail_pr_deliveries
|
|
16
|
+
WHERE decision = 'merged'
|
|
17
|
+
OR (decision = 'discarded' AND is_continuation = 0
|
|
18
|
+
AND supersedes_delivery_id IS NULL AND pr_url IS NULL)
|
|
19
|
+
`).all();
|
|
20
|
+
const insertEffect = db.prepare(`
|
|
21
|
+
INSERT OR IGNORE INTO rail_pr_ticket_effects (
|
|
22
|
+
delivery_id, ticket_ids, causal_owners, target_status, jira_action, pr_url
|
|
23
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
24
|
+
`);
|
|
25
|
+
const currentOwner = db.prepare(`
|
|
26
|
+
SELECT owner_id FROM ticket_outcome_ownership WHERE ticket_id = ?
|
|
27
|
+
`);
|
|
28
|
+
for (const row of terminalRows) {
|
|
29
|
+
let ticketIds = [];
|
|
30
|
+
let runIds = [];
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(row.ticket_ids);
|
|
33
|
+
if (Array.isArray(parsed)) {
|
|
34
|
+
ticketIds = [...new Set(parsed.filter((id) => Number.isSafeInteger(id) && id > 0))];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { /* malformed historical evidence is not safe to act on */ }
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(row.run_ids);
|
|
40
|
+
if (Array.isArray(parsed)) {
|
|
41
|
+
runIds = [...new Set(parsed.filter((id) => typeof id === 'string' && id.length > 0))];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch { /* absent causal evidence means the historical effect is unsafe */ }
|
|
45
|
+
if (ticketIds.length === 0 || runIds.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const allowedOwners = new Set(runIds);
|
|
48
|
+
const causalOwners = {};
|
|
49
|
+
ticketIds = ticketIds.filter((ticketId) => {
|
|
50
|
+
const owner = currentOwner.get(ticketId);
|
|
51
|
+
if (!owner || !allowedOwners.has(owner.owner_id))
|
|
52
|
+
return false;
|
|
53
|
+
causalOwners[String(ticketId)] = owner.owner_id;
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
if (ticketIds.length === 0)
|
|
57
|
+
continue;
|
|
58
|
+
const merged = row.decision === 'merged';
|
|
59
|
+
insertEffect.run(row.id, JSON.stringify(ticketIds), JSON.stringify(causalOwners), merged ? 'done' : 'todo',
|
|
60
|
+
// A v49 fresh discard has lost whether it meant Refine or a real
|
|
61
|
+
// discard. Recover the backlog status without inventing either cause.
|
|
62
|
+
merged ? 'merged' : 'backlog', merged ? row.pr_url : null);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const MIGRATIONS = [
|
|
66
|
+
// Migration 1: initial schema
|
|
67
|
+
(db) => {
|
|
68
|
+
db.exec(`
|
|
69
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
70
|
+
version INTEGER PRIMARY KEY,
|
|
71
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
command TEXT NOT NULL,
|
|
77
|
+
started_at TEXT NOT NULL,
|
|
78
|
+
finished_at TEXT,
|
|
79
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
80
|
+
exit_code INTEGER,
|
|
81
|
+
tokens_in INTEGER,
|
|
82
|
+
tokens_out INTEGER,
|
|
83
|
+
tokens_cache_read INTEGER,
|
|
84
|
+
tokens_cache_create INTEGER,
|
|
85
|
+
total_cost_usd REAL,
|
|
86
|
+
num_turns INTEGER,
|
|
87
|
+
model TEXT,
|
|
88
|
+
duration_ms INTEGER,
|
|
89
|
+
duration_api_ms INTEGER,
|
|
90
|
+
session_id TEXT
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_started_at ON jobs(started_at);
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
95
|
+
|
|
96
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
97
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
99
|
+
seq INTEGER NOT NULL,
|
|
100
|
+
event_type TEXT NOT NULL,
|
|
101
|
+
source TEXT,
|
|
102
|
+
payload TEXT NOT NULL,
|
|
103
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_events_job_id ON events(job_id);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS job_phases (
|
|
109
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
110
|
+
phase TEXT NOT NULL,
|
|
111
|
+
state TEXT NOT NULL,
|
|
112
|
+
updated_at TEXT NOT NULL,
|
|
113
|
+
PRIMARY KEY (job_id, phase)
|
|
114
|
+
);
|
|
115
|
+
`);
|
|
116
|
+
},
|
|
117
|
+
// Migration 2: add queue_position column to jobs
|
|
118
|
+
(db) => {
|
|
119
|
+
db.exec(`
|
|
120
|
+
ALTER TABLE jobs ADD COLUMN queue_position INTEGER;
|
|
121
|
+
`);
|
|
122
|
+
},
|
|
123
|
+
// Migration 3: add queue_state table for persisting queue config (e.g., paused)
|
|
124
|
+
(db) => {
|
|
125
|
+
db.exec(`
|
|
126
|
+
CREATE TABLE IF NOT EXISTS queue_state (
|
|
127
|
+
key TEXT PRIMARY KEY,
|
|
128
|
+
value TEXT NOT NULL
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
INSERT OR IGNORE INTO queue_state (key, value) VALUES ('paused', 'false');
|
|
132
|
+
`);
|
|
133
|
+
},
|
|
134
|
+
// Migration 4: chat conversations and messages
|
|
135
|
+
(db) => {
|
|
136
|
+
db.exec(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS chat_conversations (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
title TEXT,
|
|
140
|
+
model TEXT NOT NULL DEFAULT 'sonnet',
|
|
141
|
+
session_id TEXT,
|
|
142
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
143
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
147
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
148
|
+
conversation_id TEXT NOT NULL REFERENCES chat_conversations(id) ON DELETE CASCADE,
|
|
149
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant')),
|
|
150
|
+
content TEXT NOT NULL,
|
|
151
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_conv ON chat_messages(conversation_id);
|
|
155
|
+
`);
|
|
156
|
+
},
|
|
157
|
+
// Migration 5: proposals table
|
|
158
|
+
(db) => {
|
|
159
|
+
db.exec(`
|
|
160
|
+
CREATE TABLE IF NOT EXISTS proposals (
|
|
161
|
+
id TEXT PRIMARY KEY,
|
|
162
|
+
idea TEXT NOT NULL,
|
|
163
|
+
session_id TEXT,
|
|
164
|
+
status TEXT NOT NULL DEFAULT 'input',
|
|
165
|
+
result_markdown TEXT,
|
|
166
|
+
issue_url TEXT,
|
|
167
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
168
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
169
|
+
);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_created_at ON proposals(created_at);
|
|
172
|
+
`);
|
|
173
|
+
},
|
|
174
|
+
// Migration 6: job templates
|
|
175
|
+
(db) => {
|
|
176
|
+
db.exec(`
|
|
177
|
+
CREATE TABLE IF NOT EXISTS job_templates (
|
|
178
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
179
|
+
name TEXT NOT NULL UNIQUE,
|
|
180
|
+
description TEXT,
|
|
181
|
+
commands TEXT NOT NULL,
|
|
182
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
183
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
184
|
+
);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_job_templates_created_at ON job_templates(created_at);
|
|
186
|
+
`);
|
|
187
|
+
},
|
|
188
|
+
// Migration 7: add priority column to jobs
|
|
189
|
+
(db) => {
|
|
190
|
+
db.exec(`
|
|
191
|
+
ALTER TABLE jobs ADD COLUMN priority TEXT NOT NULL DEFAULT 'normal';
|
|
192
|
+
`);
|
|
193
|
+
},
|
|
194
|
+
// Migration 8: job dependencies and pipelines
|
|
195
|
+
(db) => {
|
|
196
|
+
db.exec(`
|
|
197
|
+
ALTER TABLE jobs ADD COLUMN depends_on_job_id TEXT REFERENCES jobs(id);
|
|
198
|
+
ALTER TABLE jobs ADD COLUMN pipeline_id TEXT;
|
|
199
|
+
ALTER TABLE jobs ADD COLUMN skip_reason TEXT;
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_depends_on ON jobs(depends_on_job_id);
|
|
201
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_pipeline_id ON jobs(pipeline_id);
|
|
202
|
+
`);
|
|
203
|
+
},
|
|
204
|
+
// Migration 9: rails table for Rails board job integration
|
|
205
|
+
(db) => {
|
|
206
|
+
db.exec(`
|
|
207
|
+
CREATE TABLE IF NOT EXISTS rails (
|
|
208
|
+
rail_index INTEGER NOT NULL,
|
|
209
|
+
ticket_id INTEGER NOT NULL,
|
|
210
|
+
position INTEGER NOT NULL,
|
|
211
|
+
mode TEXT NOT NULL DEFAULT 'implement',
|
|
212
|
+
PRIMARY KEY (rail_index, ticket_id)
|
|
213
|
+
);
|
|
214
|
+
CREATE INDEX IF NOT EXISTS idx_rails_rail_index ON rails(rail_index);
|
|
215
|
+
`);
|
|
216
|
+
},
|
|
217
|
+
// Migration 10: pipeline telemetry blob and summary tables.
|
|
218
|
+
// The pipelineTelemetryEnabled flag reuses the existing queue_state key-value
|
|
219
|
+
// store (key = 'config.pipeline_telemetry_enabled') so no schema change needed
|
|
220
|
+
// for settings; only the raw-data tables are new.
|
|
221
|
+
(db) => {
|
|
222
|
+
db.exec(`
|
|
223
|
+
CREATE TABLE IF NOT EXISTS telemetry_blobs (
|
|
224
|
+
jobId TEXT PRIMARY KEY,
|
|
225
|
+
path TEXT,
|
|
226
|
+
byteSize INTEGER NOT NULL DEFAULT 0,
|
|
227
|
+
startedAt INTEGER,
|
|
228
|
+
endedAt INTEGER,
|
|
229
|
+
state TEXT NOT NULL DEFAULT 'active'
|
|
230
|
+
CHECK(state IN ('active','compacted','expired'))
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
CREATE TABLE IF NOT EXISTS telemetry_summaries (
|
|
234
|
+
jobId TEXT NOT NULL,
|
|
235
|
+
phase TEXT NOT NULL,
|
|
236
|
+
durationMs INTEGER,
|
|
237
|
+
tokensInput INTEGER,
|
|
238
|
+
tokensOutput INTEGER,
|
|
239
|
+
tokensCache INTEGER,
|
|
240
|
+
toolCalls TEXT,
|
|
241
|
+
apiErrors INTEGER,
|
|
242
|
+
costUsd REAL,
|
|
243
|
+
PRIMARY KEY (jobId, phase)
|
|
244
|
+
);
|
|
245
|
+
`);
|
|
246
|
+
},
|
|
247
|
+
// Migration 11: agent profiles — per-rail profile snapshots, custom agent
|
|
248
|
+
// version history, and sandboxed "test agent" run records. These back the
|
|
249
|
+
// Agents section (profiles + studio) added by add-agents-profiles.
|
|
250
|
+
(db) => {
|
|
251
|
+
db.exec(`
|
|
252
|
+
CREATE TABLE IF NOT EXISTS job_profiles (
|
|
253
|
+
job_id TEXT PRIMARY KEY,
|
|
254
|
+
profile_name TEXT NOT NULL,
|
|
255
|
+
profile_json TEXT NOT NULL,
|
|
256
|
+
created_at INTEGER NOT NULL
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
CREATE INDEX IF NOT EXISTS idx_job_profiles_name ON job_profiles(profile_name);
|
|
260
|
+
|
|
261
|
+
CREATE TABLE IF NOT EXISTS agent_versions (
|
|
262
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
263
|
+
agent_name TEXT NOT NULL,
|
|
264
|
+
version INTEGER NOT NULL,
|
|
265
|
+
body TEXT NOT NULL,
|
|
266
|
+
created_at INTEGER NOT NULL,
|
|
267
|
+
UNIQUE (agent_name, version)
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_agent_versions_name ON agent_versions(agent_name);
|
|
271
|
+
|
|
272
|
+
CREATE TABLE IF NOT EXISTS agent_tests (
|
|
273
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
274
|
+
agent_name TEXT NOT NULL,
|
|
275
|
+
draft_hash TEXT NOT NULL,
|
|
276
|
+
sample_task_id TEXT,
|
|
277
|
+
tokens INTEGER,
|
|
278
|
+
duration_ms INTEGER,
|
|
279
|
+
output TEXT,
|
|
280
|
+
created_at INTEGER NOT NULL
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tests_name ON agent_tests(agent_name);
|
|
284
|
+
`);
|
|
285
|
+
},
|
|
286
|
+
// Migration 12: remember per-rail agent profile selection across launches.
|
|
287
|
+
(db) => {
|
|
288
|
+
try {
|
|
289
|
+
db.exec(`ALTER TABLE rails ADD COLUMN profile_name TEXT`);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// Column may already exist (partially-migrated DB); no-op.
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
// Migration 13: agent_refine_sessions — in-flight AI Edit sessions for
|
|
296
|
+
// custom agents. Distinct from agent_versions (which is committed history);
|
|
297
|
+
// rows here are drafts in progress that may or may not be applied.
|
|
298
|
+
(db) => {
|
|
299
|
+
db.exec(`
|
|
300
|
+
CREATE TABLE IF NOT EXISTS agent_refine_sessions (
|
|
301
|
+
id TEXT PRIMARY KEY,
|
|
302
|
+
agent_id TEXT NOT NULL,
|
|
303
|
+
session_id TEXT,
|
|
304
|
+
base_version INTEGER NOT NULL,
|
|
305
|
+
base_body_hash TEXT NOT NULL,
|
|
306
|
+
draft_body TEXT,
|
|
307
|
+
history_json TEXT NOT NULL DEFAULT '[]',
|
|
308
|
+
phase TEXT NOT NULL DEFAULT 'idle',
|
|
309
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
310
|
+
auto_test INTEGER NOT NULL DEFAULT 1,
|
|
311
|
+
last_test_at INTEGER,
|
|
312
|
+
last_test_hash TEXT,
|
|
313
|
+
created_at INTEGER NOT NULL,
|
|
314
|
+
updated_at INTEGER NOT NULL
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
CREATE INDEX IF NOT EXISTS idx_agent_refine_sessions_agent
|
|
318
|
+
ON agent_refine_sessions(agent_id, status);
|
|
319
|
+
CREATE INDEX IF NOT EXISTS idx_agent_refine_sessions_updated
|
|
320
|
+
ON agent_refine_sessions(updated_at);
|
|
321
|
+
`);
|
|
322
|
+
},
|
|
323
|
+
// Migration 14: terminal_settings_override — per-project key/value override
|
|
324
|
+
// for app-wide terminal settings. Absence of a row means "inherit app default".
|
|
325
|
+
(db) => {
|
|
326
|
+
db.exec(`
|
|
327
|
+
CREATE TABLE IF NOT EXISTS terminal_settings_override (
|
|
328
|
+
key TEXT PRIMARY KEY,
|
|
329
|
+
value TEXT NOT NULL
|
|
330
|
+
);
|
|
331
|
+
`);
|
|
332
|
+
},
|
|
333
|
+
// Migration 15: terminal_command_marks — per-session record of completed
|
|
334
|
+
// commands derived from OSC 133 prompt marks. FIFO-capped at 1000 rows per
|
|
335
|
+
// session by the marks store.
|
|
336
|
+
(db) => {
|
|
337
|
+
db.exec(`
|
|
338
|
+
CREATE TABLE IF NOT EXISTS terminal_command_marks (
|
|
339
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
340
|
+
session_id TEXT NOT NULL,
|
|
341
|
+
started_at INTEGER NOT NULL,
|
|
342
|
+
finished_at INTEGER,
|
|
343
|
+
exit_code INTEGER,
|
|
344
|
+
command TEXT,
|
|
345
|
+
cwd TEXT
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
CREATE INDEX IF NOT EXISTS idx_terminal_marks_session_started
|
|
349
|
+
ON terminal_command_marks(session_id, started_at);
|
|
350
|
+
`);
|
|
351
|
+
},
|
|
352
|
+
// Migration 16: ai_invocations — unified per-project AI CLI invocation
|
|
353
|
+
// tracking across surfaces (job, quick-spec, explore-spec, ai-edit).
|
|
354
|
+
(db) => {
|
|
355
|
+
db.exec(`
|
|
356
|
+
CREATE TABLE IF NOT EXISTS ai_invocations (
|
|
357
|
+
id TEXT PRIMARY KEY,
|
|
358
|
+
project_id TEXT NOT NULL,
|
|
359
|
+
surface TEXT NOT NULL,
|
|
360
|
+
surface_ref_id TEXT,
|
|
361
|
+
ticket_id INTEGER,
|
|
362
|
+
conversation_id TEXT,
|
|
363
|
+
model TEXT,
|
|
364
|
+
status TEXT NOT NULL,
|
|
365
|
+
started_at TEXT NOT NULL,
|
|
366
|
+
finished_at TEXT,
|
|
367
|
+
duration_ms INTEGER,
|
|
368
|
+
duration_api_ms INTEGER,
|
|
369
|
+
tokens_in INTEGER,
|
|
370
|
+
tokens_out INTEGER,
|
|
371
|
+
tokens_cache_read INTEGER,
|
|
372
|
+
tokens_cache_create INTEGER,
|
|
373
|
+
total_cost_usd REAL,
|
|
374
|
+
num_turns INTEGER,
|
|
375
|
+
session_id TEXT,
|
|
376
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_started
|
|
380
|
+
ON ai_invocations(project_id, started_at DESC);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_surface
|
|
382
|
+
ON ai_invocations(project_id, surface);
|
|
383
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_ticket
|
|
384
|
+
ON ai_invocations(project_id, ticket_id) WHERE ticket_id IS NOT NULL;
|
|
385
|
+
`);
|
|
386
|
+
},
|
|
387
|
+
// Migration 17: chat_conversations.kind — distinguishes Explore conversations
|
|
388
|
+
// (kind='explore') from sidebar chat (kind='sidebar'). Capture for ai_invocations
|
|
389
|
+
// is gated on kind='explore'.
|
|
390
|
+
(db) => {
|
|
391
|
+
db.exec(`
|
|
392
|
+
ALTER TABLE chat_conversations ADD COLUMN kind TEXT NOT NULL DEFAULT 'sidebar';
|
|
393
|
+
`);
|
|
394
|
+
},
|
|
395
|
+
// Migration 18: chat_conversations.context_scope — per-conversation JSON
|
|
396
|
+
// freezing the Add Spec context scope at creation time. NULL means "legacy
|
|
397
|
+
// behavior" so existing rows behave unchanged.
|
|
398
|
+
//
|
|
399
|
+
// Idempotent: a parallel WIP branch shipped this column under migration #20,
|
|
400
|
+
// so on machines where it already exists we swallow the duplicate-column
|
|
401
|
+
// error rather than crash on a re-run.
|
|
402
|
+
(db) => {
|
|
403
|
+
try {
|
|
404
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN context_scope TEXT;`);
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
const msg = err.message ?? '';
|
|
408
|
+
if (!/duplicate column name/i.test(msg))
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
// Migration 19: ai_invocations.provider — provider id stamped at insert.
|
|
413
|
+
// Existing rows backfill to 'claude' since pre-migration that was the only
|
|
414
|
+
// path. New rows MUST be populated from the resolved adapter's id (see
|
|
415
|
+
// openspec/changes/add-multi-provider-support/specs/project-spending/spec.md).
|
|
416
|
+
//
|
|
417
|
+
// Idempotent: same dual-WIP concern — multi-provider branch originally
|
|
418
|
+
// numbered this #18, so on machines that ran the pre-merge multi-provider
|
|
419
|
+
// build the column already exists.
|
|
420
|
+
(db) => {
|
|
421
|
+
try {
|
|
422
|
+
db.exec(`ALTER TABLE ai_invocations ADD COLUMN provider TEXT;`);
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
const msg = err.message ?? '';
|
|
426
|
+
if (!/duplicate column name/i.test(msg))
|
|
427
|
+
throw err;
|
|
428
|
+
}
|
|
429
|
+
db.exec(`UPDATE ai_invocations SET provider = 'claude' WHERE provider IS NULL;`);
|
|
430
|
+
db.exec(`
|
|
431
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_provider
|
|
432
|
+
ON ai_invocations(project_id, provider);
|
|
433
|
+
`);
|
|
434
|
+
},
|
|
435
|
+
// Migration 20: ai_invocations.total_cost_usd_estimated — 1 when the cost
|
|
436
|
+
// came from server/modules/accounting/runtime/pricing.ts (estimated fallback for non-native-cost
|
|
437
|
+
// providers); 0 when authoritative from the provider's terminal event.
|
|
438
|
+
//
|
|
439
|
+
// Idempotent for the same reason as #18/#19.
|
|
440
|
+
(db) => {
|
|
441
|
+
try {
|
|
442
|
+
db.exec(`
|
|
443
|
+
ALTER TABLE ai_invocations
|
|
444
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
445
|
+
`);
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
const msg = err.message ?? '';
|
|
449
|
+
if (!/duplicate column name/i.test(msg))
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
// Migration 21: self-heal `ai_invocations.provider` and
|
|
454
|
+
// `ai_invocations.total_cost_usd_estimated` for projects whose
|
|
455
|
+
// `schema_migrations` table marked versions 19 / 20 as applied without the
|
|
456
|
+
// corresponding ALTER actually running (parallel WIP branches reshuffled
|
|
457
|
+
// migration indices during development, leaving some on-disk DBs with the
|
|
458
|
+
// applied row but no column). Uses `PRAGMA table_info` so we only ALTER
|
|
459
|
+
// when the column is genuinely missing — safe to re-run.
|
|
460
|
+
(db) => {
|
|
461
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(ai_invocations)`).all()
|
|
462
|
+
.map((r) => r.name));
|
|
463
|
+
if (!cols.has('provider')) {
|
|
464
|
+
db.exec(`ALTER TABLE ai_invocations ADD COLUMN provider TEXT;`);
|
|
465
|
+
db.exec(`UPDATE ai_invocations SET provider = 'claude' WHERE provider IS NULL;`);
|
|
466
|
+
db.exec(`
|
|
467
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_provider
|
|
468
|
+
ON ai_invocations(project_id, provider);
|
|
469
|
+
`);
|
|
470
|
+
}
|
|
471
|
+
if (!cols.has('total_cost_usd_estimated')) {
|
|
472
|
+
db.exec(`
|
|
473
|
+
ALTER TABLE ai_invocations
|
|
474
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
475
|
+
`);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
// Migration 22: file_provenance — per-project file ⇄ ticket tracking,
|
|
479
|
+
// populated by the QueueManager post-job hook and consumed by the Code
|
|
480
|
+
// Explorer router + TicketDetailModal.
|
|
481
|
+
(db) => {
|
|
482
|
+
db.exec(`
|
|
483
|
+
CREATE TABLE IF NOT EXISTS file_provenance (
|
|
484
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
485
|
+
file_path TEXT NOT NULL,
|
|
486
|
+
ticket_id INTEGER,
|
|
487
|
+
job_id TEXT,
|
|
488
|
+
kind TEXT NOT NULL CHECK(kind IN ('created','modified','deleted')),
|
|
489
|
+
at INTEGER NOT NULL
|
|
490
|
+
);
|
|
491
|
+
CREATE INDEX IF NOT EXISTS idx_fp_path ON file_provenance(file_path);
|
|
492
|
+
CREATE INDEX IF NOT EXISTS idx_fp_ticket ON file_provenance(ticket_id);
|
|
493
|
+
CREATE INDEX IF NOT EXISTS idx_fp_at ON file_provenance(at DESC);
|
|
494
|
+
`);
|
|
495
|
+
},
|
|
496
|
+
// Migration 23: optional per-job file patch storage for Code Explorer
|
|
497
|
+
// provenance. Older provenance rows remain valid without a patch.
|
|
498
|
+
(db) => {
|
|
499
|
+
db.exec(`
|
|
500
|
+
CREATE TABLE IF NOT EXISTS file_provenance_diffs (
|
|
501
|
+
provenance_id INTEGER PRIMARY KEY REFERENCES file_provenance(id) ON DELETE CASCADE,
|
|
502
|
+
patch TEXT NOT NULL,
|
|
503
|
+
truncated INTEGER NOT NULL DEFAULT 0
|
|
504
|
+
);
|
|
505
|
+
`);
|
|
506
|
+
},
|
|
507
|
+
// Migration 24: chat_conversations.provider — per-conversation AI engine for
|
|
508
|
+
// multi-provider projects. NULL means "fall back to the project's primary
|
|
509
|
+
// provider" (single-provider projects never set it, so behaviour is
|
|
510
|
+
// unchanged). Set at conversation creation from the Add Spec AI Engine
|
|
511
|
+
// selector; resume turns reuse it so the right CLI binary is spawned.
|
|
512
|
+
(db) => {
|
|
513
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN provider TEXT;`);
|
|
514
|
+
},
|
|
515
|
+
// Migration 25: rails.ai_engine — per-rail AI engine override for
|
|
516
|
+
// multi-provider projects. NULL means "use the project's primary provider".
|
|
517
|
+
// Stored on every rail row alongside profile_name; getRail reads the first
|
|
518
|
+
// row's value.
|
|
519
|
+
(db) => {
|
|
520
|
+
db.exec(`ALTER TABLE rails ADD COLUMN ai_engine TEXT;`);
|
|
521
|
+
},
|
|
522
|
+
// Migration 26: self-heal the multi-provider columns. An earlier WIP of the
|
|
523
|
+
// multi-provider feature consumed migration versions 24 and 25 on some
|
|
524
|
+
// databases with DIFFERENT meaning — those DBs already record v24/v25 so
|
|
525
|
+
// Migrations 24/25 above are skipped, leaving `rails.ai_engine` (and possibly
|
|
526
|
+
// `chat_conversations.provider`) missing. This higher-numbered migration is
|
|
527
|
+
// guarded by column checks: a no-op on DBs where the columns already exist,
|
|
528
|
+
// and an additive repair everywhere else. (Mirrors the #18/#19 self-heal
|
|
529
|
+
// precedent in this file.)
|
|
530
|
+
(db) => {
|
|
531
|
+
const convCols = db.prepare("PRAGMA table_info(chat_conversations)").all().map((c) => c.name);
|
|
532
|
+
if (!convCols.includes('provider')) {
|
|
533
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN provider TEXT;`);
|
|
534
|
+
}
|
|
535
|
+
const railCols = db.prepare("PRAGMA table_info(rails)").all().map((c) => c.name);
|
|
536
|
+
if (!railCols.includes('ai_engine')) {
|
|
537
|
+
db.exec(`ALTER TABLE rails ADD COLUMN ai_engine TEXT;`);
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
// Migration 27: jobs.total_cost_usd_estimated — 1 when jobs.total_cost_usd
|
|
541
|
+
// came from server/modules/accounting/runtime/pricing.ts (estimated fallback for non-native-cost
|
|
542
|
+
// providers like codex); 0 when authoritative from the provider's terminal
|
|
543
|
+
// event. Mirrors the ai_invocations column (migration 20) so the app
|
|
544
|
+
// dashboard, budget enforcement, and webhook can distinguish a rate-card
|
|
545
|
+
// estimate from a provider-billed figure. Additive + idempotent.
|
|
546
|
+
(db) => {
|
|
547
|
+
const cols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
|
|
548
|
+
if (!cols.includes('total_cost_usd_estimated')) {
|
|
549
|
+
db.exec(`
|
|
550
|
+
ALTER TABLE jobs
|
|
551
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
552
|
+
`);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
// Migration 28: rail_meta — per-rail display name, keyed by rail_index.
|
|
556
|
+
// The `rails` table stores name-less ticket rows (and has NO rows for an
|
|
557
|
+
// empty rail), so a rail's user-given name can't live there — a renamed but
|
|
558
|
+
// empty rail would lose its name. rail_meta is a separate, ticket-independent
|
|
559
|
+
// store so every rail (0/1/2) keeps its name regardless of assignments.
|
|
560
|
+
// NULL name = client falls back to the default "Rail N" label. This backs the
|
|
561
|
+
// desktop ⇄ mobile rail-name sync (broadcast via rail.updated).
|
|
562
|
+
(db) => {
|
|
563
|
+
db.exec(`
|
|
564
|
+
CREATE TABLE IF NOT EXISTS rail_meta (
|
|
565
|
+
rail_index INTEGER PRIMARY KEY,
|
|
566
|
+
name TEXT
|
|
567
|
+
);
|
|
568
|
+
`);
|
|
569
|
+
},
|
|
570
|
+
// Migration 29: Jira integration (per-project). Each project syncs with its
|
|
571
|
+
// own Jira board, so every Jira table lives here in the per-project jobs.sqlite
|
|
572
|
+
// and is keyed by nothing but its own rows. See docs/jira-integration-plan.md.
|
|
573
|
+
// - jira_connection: one row, the connection config (token stored encrypted).
|
|
574
|
+
// - jira_links: spec↔issue map keyed on the IMMUTABLE Jira numeric id.
|
|
575
|
+
// - jira_outbox: durable transactional write-back queue (status + comments).
|
|
576
|
+
(db) => {
|
|
577
|
+
db.exec(`
|
|
578
|
+
CREATE TABLE IF NOT EXISTS jira_connection (
|
|
579
|
+
project_id TEXT PRIMARY KEY,
|
|
580
|
+
base_url TEXT NOT NULL,
|
|
581
|
+
deployment TEXT NOT NULL,
|
|
582
|
+
api_version TEXT NOT NULL,
|
|
583
|
+
auth_scheme TEXT NOT NULL,
|
|
584
|
+
account_email TEXT,
|
|
585
|
+
jira_project_key TEXT NOT NULL,
|
|
586
|
+
jira_project_id TEXT NOT NULL,
|
|
587
|
+
encrypted_token TEXT,
|
|
588
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
589
|
+
status_map TEXT,
|
|
590
|
+
high_water_ms INTEGER,
|
|
591
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
592
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
CREATE TABLE IF NOT EXISTS jira_links (
|
|
596
|
+
local_id INTEGER PRIMARY KEY,
|
|
597
|
+
jira_issue_id TEXT NOT NULL UNIQUE,
|
|
598
|
+
jira_key TEXT,
|
|
599
|
+
jira_project_id TEXT NOT NULL,
|
|
600
|
+
deployment TEXT NOT NULL,
|
|
601
|
+
status_category TEXT,
|
|
602
|
+
state TEXT NOT NULL DEFAULT 'linked',
|
|
603
|
+
tombstoned INTEGER NOT NULL DEFAULT 0,
|
|
604
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
605
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
606
|
+
);
|
|
607
|
+
CREATE INDEX IF NOT EXISTS idx_jira_links_issue ON jira_links(jira_issue_id);
|
|
608
|
+
|
|
609
|
+
CREATE TABLE IF NOT EXISTS jira_outbox (
|
|
610
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
611
|
+
jira_issue_id TEXT NOT NULL,
|
|
612
|
+
op_type TEXT NOT NULL,
|
|
613
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
614
|
+
payload TEXT NOT NULL,
|
|
615
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
616
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
617
|
+
next_attempt_at TEXT,
|
|
618
|
+
last_error TEXT,
|
|
619
|
+
dead_reason TEXT,
|
|
620
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
621
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
622
|
+
);
|
|
623
|
+
CREATE INDEX IF NOT EXISTS idx_jira_outbox_state ON jira_outbox(state);
|
|
624
|
+
CREATE INDEX IF NOT EXISTS idx_jira_outbox_issue ON jira_outbox(jira_issue_id);
|
|
625
|
+
`);
|
|
626
|
+
},
|
|
627
|
+
// Migration 30: Jira sprint custom-field id. The field that holds an issue's
|
|
628
|
+
// sprint(s) is a custom field whose id varies per instance; we discover it
|
|
629
|
+
// (schema com.pyxis.greenhopper.jira:gh-sprint) and cache it here. NULL =
|
|
630
|
+
// not yet checked, 'none' = checked and no sprint field exists, '<id>' = found.
|
|
631
|
+
(db) => {
|
|
632
|
+
try {
|
|
633
|
+
db.exec(`ALTER TABLE jira_connection ADD COLUMN sprint_field_id TEXT`);
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// Column may already exist on a partially-migrated DB — no-op.
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
// Migration 31: Jira discard target status. The user-configured status name to
|
|
640
|
+
// which a discarded spec's issue is transitioned (instead of being deleted) in
|
|
641
|
+
// a Jira-synced project. NULL/empty = not configured (delete behaves normally).
|
|
642
|
+
(db) => {
|
|
643
|
+
try {
|
|
644
|
+
db.exec(`ALTER TABLE jira_connection ADD COLUMN discard_status TEXT`);
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// Column may already exist on a partially-migrated DB — no-op.
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
// Migration 32: jobs.interactive — 1 when the job is an interactive persistent
|
|
651
|
+
// freestyle session (the user sends multiple prompts across turns; the job
|
|
652
|
+
// stays 'running' until an explicit finalize, at which point every turn's real
|
|
653
|
+
// tokens/cost/num_turns are already summed into the row and status flips to
|
|
654
|
+
// 'completed'); 0 (default) for standard autonomous jobs. Additive + idempotent
|
|
655
|
+
// (guarded by PRAGMA table_info, mirroring migrations 18–26).
|
|
656
|
+
(db) => {
|
|
657
|
+
const cols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
|
|
658
|
+
if (!cols.includes('interactive')) {
|
|
659
|
+
db.exec(`ALTER TABLE jobs ADD COLUMN interactive INTEGER NOT NULL DEFAULT 0`);
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
// Migration 33: loop_runs — per-project record of an executed Loop (the Loops
|
|
663
|
+
// feature). A loop DEFINITION is global (desktop.sqlite `loops`); a RUN happens
|
|
664
|
+
// in a project, bound to a rail + spec. Powers loop analytics (iterations,
|
|
665
|
+
// final outcome, success rate, totals) alongside the per-step ai_invocations
|
|
666
|
+
// rows (surface='loop', linked via loop_run_id). "Running" loop state is
|
|
667
|
+
// derived from rows here with status='running'.
|
|
668
|
+
(db) => {
|
|
669
|
+
db.exec(`
|
|
670
|
+
CREATE TABLE IF NOT EXISTS loop_runs (
|
|
671
|
+
id TEXT PRIMARY KEY,
|
|
672
|
+
project_id TEXT NOT NULL,
|
|
673
|
+
loop_id TEXT NOT NULL,
|
|
674
|
+
loop_name TEXT,
|
|
675
|
+
rail_index INTEGER,
|
|
676
|
+
ticket_id INTEGER,
|
|
677
|
+
provider TEXT,
|
|
678
|
+
model TEXT,
|
|
679
|
+
reasoning_effort TEXT,
|
|
680
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
681
|
+
final_outcome TEXT,
|
|
682
|
+
iteration_limit INTEGER NOT NULL DEFAULT 0,
|
|
683
|
+
iteration_count INTEGER NOT NULL DEFAULT 0,
|
|
684
|
+
total_cost_usd REAL NOT NULL DEFAULT 0,
|
|
685
|
+
total_tokens INTEGER NOT NULL DEFAULT 0,
|
|
686
|
+
total_duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
687
|
+
started_at TEXT NOT NULL,
|
|
688
|
+
finished_at TEXT,
|
|
689
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
690
|
+
);
|
|
691
|
+
CREATE INDEX IF NOT EXISTS idx_loop_runs_project_started ON loop_runs(project_id, started_at DESC);
|
|
692
|
+
CREATE INDEX IF NOT EXISTS idx_loop_runs_outcome ON loop_runs(project_id, final_outcome);
|
|
693
|
+
CREATE INDEX IF NOT EXISTS idx_loop_runs_loop ON loop_runs(loop_id);
|
|
694
|
+
`);
|
|
695
|
+
},
|
|
696
|
+
// Migration 34: ai_invocations.loop_run_id — links a loop's per-iteration AI
|
|
697
|
+
// invocations (AI Step + Loop Decider, surface='loop') to their loop_runs row
|
|
698
|
+
// for run-level rollups. NULL for every non-loop invocation. Additive +
|
|
699
|
+
// idempotent (guarded by PRAGMA table_info, mirroring migrations 18–32).
|
|
700
|
+
(db) => {
|
|
701
|
+
const cols = db.prepare(`PRAGMA table_info(ai_invocations)`).all().map((r) => r.name);
|
|
702
|
+
if (!cols.includes('loop_run_id')) {
|
|
703
|
+
db.exec(`ALTER TABLE ai_invocations ADD COLUMN loop_run_id TEXT`);
|
|
704
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_ai_invocations_loop_run ON ai_invocations(loop_run_id) WHERE loop_run_id IS NOT NULL`);
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
// Migration 35: rail_worktrees — per-(rail launch, ticket) ledger for parallel
|
|
708
|
+
// worktree isolation + merge-back. Lets a restarted server reconcile orphaned
|
|
709
|
+
// worktrees and lets the UI render integration state. Additive + idempotent.
|
|
710
|
+
(db) => {
|
|
711
|
+
db.exec(`
|
|
712
|
+
CREATE TABLE IF NOT EXISTS rail_worktrees (
|
|
713
|
+
id TEXT PRIMARY KEY,
|
|
714
|
+
rail_index INTEGER NOT NULL,
|
|
715
|
+
ticket_id INTEGER NOT NULL,
|
|
716
|
+
run_id TEXT,
|
|
717
|
+
branch TEXT NOT NULL,
|
|
718
|
+
worktree_path TEXT NOT NULL,
|
|
719
|
+
overlay_path TEXT,
|
|
720
|
+
merge_state TEXT NOT NULL DEFAULT 'building',
|
|
721
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
722
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
723
|
+
);
|
|
724
|
+
CREATE INDEX IF NOT EXISTS idx_rail_worktrees_state ON rail_worktrees(merge_state);
|
|
725
|
+
CREATE INDEX IF NOT EXISTS idx_rail_worktrees_rail ON rail_worktrees(rail_index);
|
|
726
|
+
`);
|
|
727
|
+
},
|
|
728
|
+
// Migration 36: rail_pr_deliveries — one row per isolated rail LAUNCH tracking
|
|
729
|
+
// the ask-first PR decision lifecycle (building → on_review → pr_draft →
|
|
730
|
+
// pr_ready → merged | discarded | implementation_failed | pr_failed). The durable single source of
|
|
731
|
+
// truth both decision surfaces (rail row + agent chat) read and write, so a
|
|
732
|
+
// refresh/restart never loses a pending PR decision. `branches` captures the
|
|
733
|
+
// per-unit DeliverBranch records at build-settle (deferred deliverRailAsPr
|
|
734
|
+
// cannot be reconstructed without them); `worktree_ids` links the
|
|
735
|
+
// rail_worktrees ledger rows for discard cleanup. Additive + idempotent.
|
|
736
|
+
(db) => {
|
|
737
|
+
db.exec(`
|
|
738
|
+
CREATE TABLE IF NOT EXISTS rail_pr_deliveries (
|
|
739
|
+
id TEXT PRIMARY KEY,
|
|
740
|
+
rail_index INTEGER NOT NULL,
|
|
741
|
+
loop_id TEXT,
|
|
742
|
+
rail_key TEXT NOT NULL,
|
|
743
|
+
ticket_ids TEXT NOT NULL,
|
|
744
|
+
base_branch TEXT NOT NULL,
|
|
745
|
+
branch TEXT,
|
|
746
|
+
pr_url TEXT,
|
|
747
|
+
pr_number INTEGER,
|
|
748
|
+
pr_state TEXT NOT NULL DEFAULT 'none',
|
|
749
|
+
decision TEXT NOT NULL DEFAULT 'building',
|
|
750
|
+
branches TEXT NOT NULL DEFAULT '[]',
|
|
751
|
+
loop_name TEXT NOT NULL DEFAULT '',
|
|
752
|
+
worktree_ids TEXT NOT NULL DEFAULT '[]',
|
|
753
|
+
origin_surface TEXT NOT NULL DEFAULT 'dashboard',
|
|
754
|
+
origin_conversation_id TEXT,
|
|
755
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
756
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
757
|
+
);
|
|
758
|
+
CREATE INDEX IF NOT EXISTS idx_rail_pr_deliveries_rail ON rail_pr_deliveries(rail_index);
|
|
759
|
+
CREATE INDEX IF NOT EXISTS idx_rail_pr_deliveries_active ON rail_pr_deliveries(decision);
|
|
760
|
+
`);
|
|
761
|
+
},
|
|
762
|
+
// Migration 37: file_story_contributions — per-intervention "construction
|
|
763
|
+
// story" data for the Code/Files explorer. One row per file_provenance row
|
|
764
|
+
// that had a collectable patch: line stats (added/removed), a ~4KB patch
|
|
765
|
+
// excerpt (the full patch stays in file_provenance_diffs), and a nullable
|
|
766
|
+
// plain-language AI `summary` of what that spec contributed to the file
|
|
767
|
+
// (generated on demand, budget-gated — see file-story-manager.ts). Written
|
|
768
|
+
// inside recordProvenanceForJob's transaction so EVERY provenance producer
|
|
769
|
+
// (queue-manager jobs AND the loop-run seam) gets stats for free. Additive.
|
|
770
|
+
(db) => {
|
|
771
|
+
db.exec(`
|
|
772
|
+
CREATE TABLE IF NOT EXISTS file_story_contributions (
|
|
773
|
+
provenance_id INTEGER PRIMARY KEY,
|
|
774
|
+
job_id TEXT,
|
|
775
|
+
file_path TEXT NOT NULL,
|
|
776
|
+
added_lines INTEGER NOT NULL DEFAULT 0,
|
|
777
|
+
removed_lines INTEGER NOT NULL DEFAULT 0,
|
|
778
|
+
patch_excerpt TEXT,
|
|
779
|
+
summary TEXT,
|
|
780
|
+
summary_model TEXT,
|
|
781
|
+
summary_generated_at TEXT
|
|
782
|
+
);
|
|
783
|
+
CREATE INDEX IF NOT EXISTS idx_file_story_contributions_path ON file_story_contributions(file_path);
|
|
784
|
+
`);
|
|
785
|
+
},
|
|
786
|
+
// Migration 38: rail_pr_deliveries.run_ids — JSON string[] of the loop-run
|
|
787
|
+
// ids an isolated rail launch fanned out (order matches ticket order; a
|
|
788
|
+
// scope='all' launch has exactly one). Persisted right after allocation so
|
|
789
|
+
// the PR decision surfaces (agent-chat card + dashboard strip) can render a
|
|
790
|
+
// per-run "View log" chip with live vitals that survives refresh/restart.
|
|
791
|
+
// Additive + idempotent (guarded by PRAGMA table_info, mirroring 18–36).
|
|
792
|
+
(db) => {
|
|
793
|
+
const cols = db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all().map((r) => r.name);
|
|
794
|
+
if (!cols.includes('run_ids')) {
|
|
795
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN run_ids TEXT NOT NULL DEFAULT '[]'`);
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
// Migration 39: seed the three BASE rails (indices 0-2) into rail_meta.
|
|
799
|
+
// Dynamic rails make rail_meta the EXISTENCE authority (a rail exists iff it
|
|
800
|
+
// has a rail_meta identity row or leftover ticket rows) — seeding once here,
|
|
801
|
+
// instead of re-seeding on every read, lets a user genuinely delete a base
|
|
802
|
+
// rail (the router still guards: never the last one, never a non-empty or
|
|
803
|
+
// active one). INSERT OR IGNORE keeps any existing renamed-rail rows intact.
|
|
804
|
+
(db) => {
|
|
805
|
+
db.exec(`
|
|
806
|
+
INSERT OR IGNORE INTO rail_meta (rail_index, name) VALUES (0, NULL), (1, NULL), (2, NULL);
|
|
807
|
+
`);
|
|
808
|
+
},
|
|
809
|
+
// Migration 40: durable idempotency ledger for billable job spawns. Clients
|
|
810
|
+
// can safely retry a timed-out POST (or double-submit) without enqueueing a
|
|
811
|
+
// second job. Entries are deliberately short-lived and pruned by the route;
|
|
812
|
+
// the ledger is not job history and therefore has no foreign key to jobs.
|
|
813
|
+
(db) => {
|
|
814
|
+
db.exec(`
|
|
815
|
+
CREATE TABLE IF NOT EXISTS job_spawn_requests (
|
|
816
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
817
|
+
fingerprint TEXT NOT NULL,
|
|
818
|
+
job_id TEXT NOT NULL,
|
|
819
|
+
created_at_ms INTEGER NOT NULL,
|
|
820
|
+
expires_at_ms INTEGER NOT NULL
|
|
821
|
+
);
|
|
822
|
+
CREATE INDEX IF NOT EXISTS idx_job_spawn_requests_expires
|
|
823
|
+
ON job_spawn_requests(expires_at_ms);
|
|
824
|
+
`);
|
|
825
|
+
},
|
|
826
|
+
// Migration 41: durable outbox for jobs left RUNNING by an ungraceful
|
|
827
|
+
// process exit. QueueManager atomically snapshots each orphan here before it
|
|
828
|
+
// flips the job to failed, then checkpoints accounting and the terminal
|
|
829
|
+
// domain callback independently. A second crash can therefore resume either
|
|
830
|
+
// step without duplicating the ai_invocations ledger or losing the callback.
|
|
831
|
+
(db) => {
|
|
832
|
+
db.exec(`
|
|
833
|
+
CREATE TABLE IF NOT EXISTS orphan_job_recovery (
|
|
834
|
+
job_id TEXT PRIMARY KEY,
|
|
835
|
+
payload TEXT NOT NULL,
|
|
836
|
+
accounting_completed INTEGER NOT NULL DEFAULT 0 CHECK (accounting_completed IN (0, 1)),
|
|
837
|
+
callback_completed INTEGER NOT NULL DEFAULT 0 CHECK (callback_completed IN (0, 1)),
|
|
838
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
839
|
+
);
|
|
840
|
+
CREATE INDEX IF NOT EXISTS idx_orphan_job_recovery_pending
|
|
841
|
+
ON orphan_job_recovery(accounting_completed, callback_completed);
|
|
842
|
+
`);
|
|
843
|
+
},
|
|
844
|
+
// Migration 42: persist the provider actually selected for each job and add
|
|
845
|
+
// a third durable recovery checkpoint for queue/pipeline terminal invariants.
|
|
846
|
+
// Both ALTERs are guarded so developer databases that already applied the
|
|
847
|
+
// unreleased orphan-outbox migration can advance safely.
|
|
848
|
+
(db) => {
|
|
849
|
+
const jobCols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
|
|
850
|
+
if (!jobCols.includes('provider')) {
|
|
851
|
+
db.exec(`ALTER TABLE jobs ADD COLUMN provider TEXT`);
|
|
852
|
+
}
|
|
853
|
+
const recoveryCols = db.prepare(`PRAGMA table_info(orphan_job_recovery)`).all().map((r) => r.name);
|
|
854
|
+
if (!recoveryCols.includes('terminal_completed')) {
|
|
855
|
+
db.exec(`
|
|
856
|
+
ALTER TABLE orphan_job_recovery
|
|
857
|
+
ADD COLUMN terminal_completed INTEGER NOT NULL DEFAULT 0
|
|
858
|
+
CHECK (terminal_completed IN (0, 1))
|
|
859
|
+
`);
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
// Migration 43: durable pre-start queue. This is intentionally separate from
|
|
863
|
+
// jobs: a queue admission is not an execution and therefore has no truthful
|
|
864
|
+
// jobs.started_at value. createJob atomically promotes a row out of this table
|
|
865
|
+
// when the provider process actually starts.
|
|
866
|
+
(db) => {
|
|
867
|
+
db.exec(`
|
|
868
|
+
CREATE TABLE IF NOT EXISTS queued_jobs (
|
|
869
|
+
id TEXT PRIMARY KEY,
|
|
870
|
+
command TEXT NOT NULL,
|
|
871
|
+
queue_position INTEGER,
|
|
872
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
873
|
+
depends_on_job_id TEXT,
|
|
874
|
+
pipeline_id TEXT,
|
|
875
|
+
provider TEXT,
|
|
876
|
+
model TEXT,
|
|
877
|
+
profile_name TEXT,
|
|
878
|
+
profile_selection_set INTEGER NOT NULL DEFAULT 0
|
|
879
|
+
CHECK (profile_selection_set IN (0, 1)),
|
|
880
|
+
interactive INTEGER CHECK (interactive IN (0, 1)),
|
|
881
|
+
causal_ownership INTEGER NOT NULL DEFAULT 0
|
|
882
|
+
CHECK (causal_ownership IN (0, 1)),
|
|
883
|
+
enqueued_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
884
|
+
);
|
|
885
|
+
CREATE INDEX IF NOT EXISTS idx_queued_jobs_position
|
|
886
|
+
ON queued_jobs(queue_position);
|
|
887
|
+
`);
|
|
888
|
+
},
|
|
889
|
+
// Migration 44: persist which manager owns terminal recovery for each jobs
|
|
890
|
+
// row. Before this boundary QueueManager swept every RUNNING row, including
|
|
891
|
+
// LoopRunManager's backing rows, and recorded their already-accounted step
|
|
892
|
+
// spend again as surface='job'. Existing loop rows are classified by their
|
|
893
|
+
// durable loop_runs identity; every other historical job remains queue-owned.
|
|
894
|
+
(db) => {
|
|
895
|
+
const jobCols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
|
|
896
|
+
if (!jobCols.includes('owner')) {
|
|
897
|
+
db.exec(`
|
|
898
|
+
ALTER TABLE jobs
|
|
899
|
+
ADD COLUMN owner TEXT NOT NULL DEFAULT 'queue'
|
|
900
|
+
CHECK (owner IN ('queue', 'loop'))
|
|
901
|
+
`);
|
|
902
|
+
}
|
|
903
|
+
db.exec(`
|
|
904
|
+
UPDATE jobs
|
|
905
|
+
SET owner = 'loop',
|
|
906
|
+
provider = COALESCE(
|
|
907
|
+
jobs.provider,
|
|
908
|
+
(SELECT loop_runs.provider FROM loop_runs WHERE loop_runs.id = jobs.id)
|
|
909
|
+
)
|
|
910
|
+
WHERE EXISTS (SELECT 1 FROM loop_runs WHERE loop_runs.id = jobs.id);
|
|
911
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_owner_status ON jobs(owner, status);
|
|
912
|
+
`);
|
|
913
|
+
},
|
|
914
|
+
// Migration 45: crash-consistent loop terminal/accounting outboxes and
|
|
915
|
+
// causal ownership for rail/ticket effects. `loop_runs.ticket_ids_json`
|
|
916
|
+
// replaces the old startup heuristic ("one active run means scope=all") with
|
|
917
|
+
// the exact launch set. The recovery tables intentionally have no FK to
|
|
918
|
+
// jobs: terminal history is independently deletable while recovery effects
|
|
919
|
+
// still have to finish.
|
|
920
|
+
(db) => {
|
|
921
|
+
const loopCols = db.prepare(`PRAGMA table_info(loop_runs)`).all().map((r) => r.name);
|
|
922
|
+
if (!loopCols.includes('ticket_ids_json')) {
|
|
923
|
+
db.exec(`ALTER TABLE loop_runs ADD COLUMN ticket_ids_json TEXT NOT NULL DEFAULT '[]'`);
|
|
924
|
+
}
|
|
925
|
+
if (!loopCols.includes('ticket_completion_status')) {
|
|
926
|
+
db.exec(`
|
|
927
|
+
ALTER TABLE loop_runs
|
|
928
|
+
ADD COLUMN ticket_completion_status TEXT NOT NULL DEFAULT 'done'
|
|
929
|
+
CHECK (ticket_completion_status IN ('done', 'on_review'))
|
|
930
|
+
`);
|
|
931
|
+
}
|
|
932
|
+
if (!loopCols.includes('causal_ownership')) {
|
|
933
|
+
db.exec(`
|
|
934
|
+
ALTER TABLE loop_runs
|
|
935
|
+
ADD COLUMN causal_ownership INTEGER NOT NULL DEFAULT 0
|
|
936
|
+
CHECK (causal_ownership IN (0, 1))
|
|
937
|
+
`);
|
|
938
|
+
}
|
|
939
|
+
db.exec(`
|
|
940
|
+
CREATE TABLE IF NOT EXISTS loop_terminal_recovery (
|
|
941
|
+
run_id TEXT PRIMARY KEY,
|
|
942
|
+
payload TEXT NOT NULL,
|
|
943
|
+
callback_completed INTEGER NOT NULL DEFAULT 0
|
|
944
|
+
CHECK (callback_completed IN (0, 1)),
|
|
945
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
CREATE TABLE IF NOT EXISTS loop_step_recovery (
|
|
949
|
+
run_id TEXT NOT NULL,
|
|
950
|
+
step_key TEXT NOT NULL,
|
|
951
|
+
invocation_id TEXT NOT NULL UNIQUE,
|
|
952
|
+
payload TEXT NOT NULL,
|
|
953
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
954
|
+
PRIMARY KEY (run_id, step_key)
|
|
955
|
+
);
|
|
956
|
+
CREATE INDEX IF NOT EXISTS idx_loop_step_recovery_run
|
|
957
|
+
ON loop_step_recovery(run_id);
|
|
958
|
+
|
|
959
|
+
CREATE TABLE IF NOT EXISTS rail_ticket_ownership (
|
|
960
|
+
rail_index INTEGER NOT NULL,
|
|
961
|
+
ticket_id INTEGER NOT NULL,
|
|
962
|
+
owner_id TEXT NOT NULL,
|
|
963
|
+
generation INTEGER NOT NULL DEFAULT 1,
|
|
964
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
965
|
+
PRIMARY KEY (rail_index, ticket_id)
|
|
966
|
+
);
|
|
967
|
+
CREATE INDEX IF NOT EXISTS idx_rail_ticket_owner
|
|
968
|
+
ON rail_ticket_ownership(owner_id);
|
|
969
|
+
|
|
970
|
+
CREATE TABLE IF NOT EXISTS ticket_outcome_ownership (
|
|
971
|
+
ticket_id INTEGER PRIMARY KEY,
|
|
972
|
+
owner_id TEXT NOT NULL,
|
|
973
|
+
generation INTEGER NOT NULL DEFAULT 1,
|
|
974
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
975
|
+
);
|
|
976
|
+
CREATE INDEX IF NOT EXISTS idx_ticket_outcome_owner
|
|
977
|
+
ON ticket_outcome_ownership(owner_id);
|
|
978
|
+
|
|
979
|
+
-- Builds from the pre-owner WIP may already have captured loop backing
|
|
980
|
+
-- rows in QueueManager's outbox, or even checkpointed the resulting
|
|
981
|
+
-- surface='job' invocation. Those rows are unambiguously wrong: a jobs
|
|
982
|
+
-- id that is also a loop_runs id is exclusively loop-owned.
|
|
983
|
+
DELETE FROM orphan_job_recovery
|
|
984
|
+
WHERE job_id IN (SELECT id FROM loop_runs);
|
|
985
|
+
DELETE FROM ai_invocations
|
|
986
|
+
WHERE surface = 'job'
|
|
987
|
+
AND EXISTS (
|
|
988
|
+
SELECT 1 FROM loop_runs
|
|
989
|
+
WHERE ai_invocations.surface_ref_id = loop_runs.id
|
|
990
|
+
OR ai_invocations.surface_ref_id LIKE loop_runs.id || '#t%'
|
|
991
|
+
);
|
|
992
|
+
`);
|
|
993
|
+
},
|
|
994
|
+
// Migration 46: preserve every pre-spawn semantic override across restart.
|
|
995
|
+
// Nullable provider/model/interactive encode "use the spawn-time default";
|
|
996
|
+
// profile needs a separate presence bit because NULL itself means the
|
|
997
|
+
// deliberate force-legacy selection when the bit is set.
|
|
998
|
+
(db) => {
|
|
999
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(queued_jobs)`).all()
|
|
1000
|
+
.map((row) => row.name));
|
|
1001
|
+
if (!cols.has('provider')) {
|
|
1002
|
+
db.exec(`ALTER TABLE queued_jobs ADD COLUMN provider TEXT`);
|
|
1003
|
+
}
|
|
1004
|
+
if (!cols.has('model')) {
|
|
1005
|
+
db.exec(`ALTER TABLE queued_jobs ADD COLUMN model TEXT`);
|
|
1006
|
+
}
|
|
1007
|
+
if (!cols.has('profile_name')) {
|
|
1008
|
+
db.exec(`ALTER TABLE queued_jobs ADD COLUMN profile_name TEXT`);
|
|
1009
|
+
}
|
|
1010
|
+
if (!cols.has('profile_selection_set')) {
|
|
1011
|
+
db.exec(`
|
|
1012
|
+
ALTER TABLE queued_jobs
|
|
1013
|
+
ADD COLUMN profile_selection_set INTEGER NOT NULL DEFAULT 0
|
|
1014
|
+
CHECK (profile_selection_set IN (0, 1))
|
|
1015
|
+
`);
|
|
1016
|
+
}
|
|
1017
|
+
if (!cols.has('interactive')) {
|
|
1018
|
+
db.exec(`
|
|
1019
|
+
ALTER TABLE queued_jobs
|
|
1020
|
+
ADD COLUMN interactive INTEGER CHECK (interactive IN (0, 1))
|
|
1021
|
+
`);
|
|
1022
|
+
}
|
|
1023
|
+
if (!cols.has('causal_ownership')) {
|
|
1024
|
+
db.exec(`
|
|
1025
|
+
ALTER TABLE queued_jobs
|
|
1026
|
+
ADD COLUMN causal_ownership INTEGER NOT NULL DEFAULT 0
|
|
1027
|
+
CHECK (causal_ownership IN (0, 1))
|
|
1028
|
+
`);
|
|
1029
|
+
}
|
|
1030
|
+
const jobCols = new Set(db.prepare(`PRAGMA table_info(jobs)`).all()
|
|
1031
|
+
.map((row) => row.name));
|
|
1032
|
+
if (!jobCols.has('causal_ownership')) {
|
|
1033
|
+
db.exec(`
|
|
1034
|
+
ALTER TABLE jobs
|
|
1035
|
+
ADD COLUMN causal_ownership INTEGER NOT NULL DEFAULT 0
|
|
1036
|
+
CHECK (causal_ownership IN (0, 1))
|
|
1037
|
+
`);
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
// Migration 47: crash recovery reads the bounded newest provider frontier;
|
|
1041
|
+
// this composite index avoids sorting/scanning an unbounded transcript while
|
|
1042
|
+
// the queue is fail-stopped during startup.
|
|
1043
|
+
(db) => {
|
|
1044
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_job_seq ON events(job_id, seq)`);
|
|
1045
|
+
},
|
|
1046
|
+
// Migration 48: truthful Safe-PR settlement. Execution truth, delivery
|
|
1047
|
+
// readiness, continuation ownership and decision-operation ownership are
|
|
1048
|
+
// independent durable facts; the old decision/pr_state pair cannot explain
|
|
1049
|
+
// a successful run whose commit/ref/push later failed. Before enforcing the
|
|
1050
|
+
// intended one-active-generation-per-rail invariant, terminalize historical
|
|
1051
|
+
// duplicate rows deterministically (newest rowid wins) so an older iteration
|
|
1052
|
+
// can never resurface after the current one closes.
|
|
1053
|
+
(db) => {
|
|
1054
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all()
|
|
1055
|
+
.map((row) => row.name));
|
|
1056
|
+
const add = (name, ddl) => {
|
|
1057
|
+
if (!cols.has(name))
|
|
1058
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN ${ddl}`);
|
|
1059
|
+
};
|
|
1060
|
+
add('implementation_outcome', `implementation_outcome TEXT NOT NULL DEFAULT 'unknown'`);
|
|
1061
|
+
add('delivery_outcome', `delivery_outcome TEXT NOT NULL DEFAULT 'unknown'`);
|
|
1062
|
+
add('status_code', `status_code TEXT`);
|
|
1063
|
+
add('status_detail', `status_detail TEXT`);
|
|
1064
|
+
add('delivery_sha', `delivery_sha TEXT`);
|
|
1065
|
+
add('is_continuation', `is_continuation INTEGER NOT NULL DEFAULT 0 CHECK (is_continuation IN (0, 1))`);
|
|
1066
|
+
add('supersedes_delivery_id', `supersedes_delivery_id TEXT`);
|
|
1067
|
+
add('operation', `operation TEXT`);
|
|
1068
|
+
add('operation_token', `operation_token TEXT`);
|
|
1069
|
+
add('operation_started_at_ms', `operation_started_at_ms INTEGER`);
|
|
1070
|
+
add('cleanup_warnings', `cleanup_warnings TEXT NOT NULL DEFAULT '[]'`);
|
|
1071
|
+
db.exec(`
|
|
1072
|
+
UPDATE rail_pr_deliveries
|
|
1073
|
+
SET implementation_outcome = CASE decision
|
|
1074
|
+
WHEN 'building' THEN 'running'
|
|
1075
|
+
WHEN 'implementation_failed' THEN 'failed'
|
|
1076
|
+
WHEN 'on_review' THEN 'succeeded'
|
|
1077
|
+
WHEN 'pr_draft' THEN 'succeeded'
|
|
1078
|
+
WHEN 'pr_ready' THEN 'succeeded'
|
|
1079
|
+
WHEN 'pr_failed' THEN 'succeeded'
|
|
1080
|
+
WHEN 'merged' THEN 'succeeded'
|
|
1081
|
+
ELSE implementation_outcome
|
|
1082
|
+
END
|
|
1083
|
+
WHERE implementation_outcome = 'unknown';
|
|
1084
|
+
|
|
1085
|
+
UPDATE rail_pr_deliveries
|
|
1086
|
+
SET delivery_outcome = CASE decision
|
|
1087
|
+
WHEN 'building' THEN 'pending'
|
|
1088
|
+
WHEN 'implementation_failed' THEN 'not_started'
|
|
1089
|
+
WHEN 'on_review' THEN 'ready'
|
|
1090
|
+
WHEN 'pr_draft' THEN 'delivered'
|
|
1091
|
+
WHEN 'pr_ready' THEN 'delivered'
|
|
1092
|
+
WHEN 'pr_failed' THEN 'retryable_failure'
|
|
1093
|
+
WHEN 'merged' THEN 'delivered'
|
|
1094
|
+
ELSE delivery_outcome
|
|
1095
|
+
END
|
|
1096
|
+
WHERE delivery_outcome = 'unknown';
|
|
1097
|
+
|
|
1098
|
+
UPDATE rail_pr_deliveries
|
|
1099
|
+
SET status_code = CASE decision
|
|
1100
|
+
WHEN 'building' THEN 'implementation_running'
|
|
1101
|
+
WHEN 'implementation_failed' THEN 'implementation_failed'
|
|
1102
|
+
WHEN 'on_review' THEN 'ready_for_review'
|
|
1103
|
+
WHEN 'pr_draft' THEN 'pr_draft_ready'
|
|
1104
|
+
WHEN 'pr_ready' THEN 'pr_ready'
|
|
1105
|
+
WHEN 'pr_failed' THEN 'delivery_failed'
|
|
1106
|
+
WHEN 'merged' THEN 'merged'
|
|
1107
|
+
ELSE status_code
|
|
1108
|
+
END
|
|
1109
|
+
WHERE status_code IS NULL;
|
|
1110
|
+
|
|
1111
|
+
UPDATE rail_pr_deliveries
|
|
1112
|
+
SET decision = 'superseded',
|
|
1113
|
+
status_code = 'superseded',
|
|
1114
|
+
updated_at = datetime('now')
|
|
1115
|
+
WHERE decision NOT IN ('merged', 'discarded', 'superseded')
|
|
1116
|
+
AND rowid NOT IN (
|
|
1117
|
+
SELECT MAX(rowid)
|
|
1118
|
+
FROM rail_pr_deliveries
|
|
1119
|
+
WHERE decision NOT IN ('merged', 'discarded', 'superseded')
|
|
1120
|
+
GROUP BY rail_index
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_rail_pr_deliveries_one_active_per_rail
|
|
1124
|
+
ON rail_pr_deliveries(rail_index)
|
|
1125
|
+
WHERE decision NOT IN ('merged', 'discarded', 'superseded');
|
|
1126
|
+
`);
|
|
1127
|
+
},
|
|
1128
|
+
// Migration 49: repair the exact legacy false-failure shape that motivated
|
|
1129
|
+
// the orthogonal lifecycle. Older settlement code could write
|
|
1130
|
+
// decision='implementation_failed' after every loop had durably succeeded,
|
|
1131
|
+
// solely because commit/ref delivery failed. Reclassify only rows whose full
|
|
1132
|
+
// run set is present and terminal; ambiguous rows remain conservative. This
|
|
1133
|
+
// migration also makes truthful no-change acknowledgement (`completed`) a
|
|
1134
|
+
// terminal generation for the one-active-per-rail invariant.
|
|
1135
|
+
(db) => {
|
|
1136
|
+
const legacyRows = db.prepare(`
|
|
1137
|
+
SELECT id, run_ids, branches
|
|
1138
|
+
FROM rail_pr_deliveries
|
|
1139
|
+
WHERE decision = 'implementation_failed'
|
|
1140
|
+
`).all();
|
|
1141
|
+
const update = db.prepare(`
|
|
1142
|
+
UPDATE rail_pr_deliveries
|
|
1143
|
+
SET decision = 'pr_failed',
|
|
1144
|
+
implementation_outcome = ?,
|
|
1145
|
+
delivery_outcome = 'blocked',
|
|
1146
|
+
status_code = 'settlement_interrupted',
|
|
1147
|
+
status_detail = ?,
|
|
1148
|
+
branches = ?,
|
|
1149
|
+
updated_at = datetime('now')
|
|
1150
|
+
WHERE id = ? AND decision = 'implementation_failed'
|
|
1151
|
+
`);
|
|
1152
|
+
for (const row of legacyRows) {
|
|
1153
|
+
let runIds = [];
|
|
1154
|
+
try {
|
|
1155
|
+
const parsed = JSON.parse(row.run_ids);
|
|
1156
|
+
if (Array.isArray(parsed)) {
|
|
1157
|
+
runIds = [...new Set(parsed.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
catch { /* malformed legacy evidence stays conservative */ }
|
|
1161
|
+
if (runIds.length === 0)
|
|
1162
|
+
continue;
|
|
1163
|
+
const placeholders = runIds.map(() => '?').join(',');
|
|
1164
|
+
const runs = db.prepare(`
|
|
1165
|
+
SELECT id, status, final_outcome
|
|
1166
|
+
FROM loop_runs
|
|
1167
|
+
WHERE id IN (${placeholders})
|
|
1168
|
+
`).all(...runIds);
|
|
1169
|
+
if (runs.length !== runIds.length || runs.some((run) => run.status !== 'completed'))
|
|
1170
|
+
continue;
|
|
1171
|
+
const outcomes = new Map(runs.map((run) => [run.id, run.final_outcome]));
|
|
1172
|
+
const succeeded = runIds.filter((runId) => outcomes.get(runId) === 'success').length;
|
|
1173
|
+
if (succeeded === 0)
|
|
1174
|
+
continue;
|
|
1175
|
+
let branches = [];
|
|
1176
|
+
try {
|
|
1177
|
+
const parsed = JSON.parse(row.branches);
|
|
1178
|
+
if (Array.isArray(parsed))
|
|
1179
|
+
branches = parsed;
|
|
1180
|
+
}
|
|
1181
|
+
catch { /* retain an empty evidence set rather than malformed JSON */ }
|
|
1182
|
+
const repairedBranches = branches.map((value, index) => {
|
|
1183
|
+
if (!value || typeof value !== 'object')
|
|
1184
|
+
return value;
|
|
1185
|
+
const unit = value;
|
|
1186
|
+
const runId = runIds[index];
|
|
1187
|
+
const implementationSucceeded = runId ? outcomes.get(runId) === 'success' : succeeded === runIds.length;
|
|
1188
|
+
return {
|
|
1189
|
+
...unit,
|
|
1190
|
+
...(runId ? { runId } : {}),
|
|
1191
|
+
implementationOutcome: implementationSucceeded ? 'succeeded' : 'failed',
|
|
1192
|
+
deliveryOutcome: implementationSucceeded ? 'blocked' : 'not_started',
|
|
1193
|
+
...(implementationSucceeded ? { failureCode: 'settlement_interrupted' } : {}),
|
|
1194
|
+
};
|
|
1195
|
+
});
|
|
1196
|
+
update.run(succeeded === runIds.length ? 'succeeded' : 'partially_succeeded', 'Recovered the successful implementation result from durable run logs; legacy delivery settlement was interrupted.', JSON.stringify(repairedBranches), row.id);
|
|
1197
|
+
}
|
|
1198
|
+
db.exec(`
|
|
1199
|
+
DROP INDEX IF EXISTS idx_rail_pr_deliveries_one_active_per_rail;
|
|
1200
|
+
CREATE UNIQUE INDEX idx_rail_pr_deliveries_one_active_per_rail
|
|
1201
|
+
ON rail_pr_deliveries(rail_index)
|
|
1202
|
+
WHERE decision NOT IN ('completed', 'merged', 'discarded', 'superseded');
|
|
1203
|
+
`);
|
|
1204
|
+
},
|
|
1205
|
+
// Migration 50: terminal Safe-PR decisions and their ticket-file mutation
|
|
1206
|
+
// cross SQLite/JSON boundaries. Persist an idempotent outbox row in the same
|
|
1207
|
+
// transaction as the terminal decision so a crash can never leave tickets
|
|
1208
|
+
// parked on_review with no legal action left to replay.
|
|
1209
|
+
(db) => {
|
|
1210
|
+
db.exec(`
|
|
1211
|
+
CREATE TABLE IF NOT EXISTS rail_pr_ticket_effects (
|
|
1212
|
+
delivery_id TEXT PRIMARY KEY,
|
|
1213
|
+
ticket_ids TEXT NOT NULL,
|
|
1214
|
+
causal_owners TEXT NOT NULL DEFAULT '{}',
|
|
1215
|
+
applied_ticket_ids TEXT,
|
|
1216
|
+
target_status TEXT NOT NULL CHECK (target_status IN ('todo', 'done')),
|
|
1217
|
+
jira_action TEXT NOT NULL CHECK (jira_action IN ('discard', 'merged', 'completed', 'refine', 'backlog')),
|
|
1218
|
+
pr_url TEXT,
|
|
1219
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1220
|
+
last_error TEXT,
|
|
1221
|
+
tickets_applied_at TEXT,
|
|
1222
|
+
jira_enqueued_at TEXT,
|
|
1223
|
+
completed_at TEXT,
|
|
1224
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1225
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1226
|
+
);
|
|
1227
|
+
CREATE INDEX IF NOT EXISTS idx_rail_pr_ticket_effects_pending
|
|
1228
|
+
ON rail_pr_ticket_effects(completed_at)
|
|
1229
|
+
WHERE completed_at IS NULL;
|
|
1230
|
+
`);
|
|
1231
|
+
// Keep the migration restart-safe for development DBs that may have run an
|
|
1232
|
+
// earlier migration-50 draft before these phase columns were added. Shipped
|
|
1233
|
+
// migrations remain atomic, but additive guards make local upgrade testing
|
|
1234
|
+
// deterministic as well.
|
|
1235
|
+
const effectCols = new Set(db.prepare(`PRAGMA table_info(rail_pr_ticket_effects)`).all()
|
|
1236
|
+
.map((row) => row.name));
|
|
1237
|
+
if (!effectCols.has('applied_ticket_ids')) {
|
|
1238
|
+
db.exec(`ALTER TABLE rail_pr_ticket_effects ADD COLUMN applied_ticket_ids TEXT`);
|
|
1239
|
+
}
|
|
1240
|
+
if (!effectCols.has('tickets_applied_at')) {
|
|
1241
|
+
db.exec(`ALTER TABLE rail_pr_ticket_effects ADD COLUMN tickets_applied_at TEXT`);
|
|
1242
|
+
}
|
|
1243
|
+
if (!effectCols.has('jira_enqueued_at')) {
|
|
1244
|
+
db.exec(`ALTER TABLE rail_pr_ticket_effects ADD COLUMN jira_enqueued_at TEXT`);
|
|
1245
|
+
}
|
|
1246
|
+
if (!effectCols.has('causal_owners')) {
|
|
1247
|
+
db.exec(`ALTER TABLE rail_pr_ticket_effects ADD COLUMN causal_owners TEXT NOT NULL DEFAULT '{}'`);
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
// Migration 51: a development build briefly shipped migration 50 without
|
|
1251
|
+
// phase columns and with a CHECK that allowed only discard/merged. A DB that
|
|
1252
|
+
// already records version 50 will never rerun its edited body, so rebuild the
|
|
1253
|
+
// table once, preserve every row, and install the complete action contract.
|
|
1254
|
+
// Migration 52 owns the causal historical backfill after both schemas exist.
|
|
1255
|
+
(db) => {
|
|
1256
|
+
const table = db.prepare(`
|
|
1257
|
+
SELECT sql FROM sqlite_master
|
|
1258
|
+
WHERE type = 'table' AND name = 'rail_pr_ticket_effects'
|
|
1259
|
+
`).get();
|
|
1260
|
+
const createFullTable = (name) => {
|
|
1261
|
+
db.exec(`
|
|
1262
|
+
CREATE TABLE ${name} (
|
|
1263
|
+
delivery_id TEXT PRIMARY KEY,
|
|
1264
|
+
ticket_ids TEXT NOT NULL,
|
|
1265
|
+
causal_owners TEXT NOT NULL DEFAULT '{}',
|
|
1266
|
+
applied_ticket_ids TEXT,
|
|
1267
|
+
target_status TEXT NOT NULL CHECK (target_status IN ('todo', 'done')),
|
|
1268
|
+
jira_action TEXT NOT NULL CHECK (jira_action IN ('discard', 'merged', 'completed', 'refine', 'backlog')),
|
|
1269
|
+
pr_url TEXT,
|
|
1270
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1271
|
+
last_error TEXT,
|
|
1272
|
+
tickets_applied_at TEXT,
|
|
1273
|
+
jira_enqueued_at TEXT,
|
|
1274
|
+
completed_at TEXT,
|
|
1275
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1276
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1277
|
+
)
|
|
1278
|
+
`);
|
|
1279
|
+
};
|
|
1280
|
+
if (!table) {
|
|
1281
|
+
createFullTable('rail_pr_ticket_effects');
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(rail_pr_ticket_effects)`).all()
|
|
1285
|
+
.map((row) => row.name));
|
|
1286
|
+
const sql = table.sql ?? '';
|
|
1287
|
+
const complete = ['causal_owners', 'applied_ticket_ids', 'tickets_applied_at', 'jira_enqueued_at']
|
|
1288
|
+
.every((column) => cols.has(column)) &&
|
|
1289
|
+
['completed', 'refine', 'backlog'].every((action) => sql.includes(`'${action}'`));
|
|
1290
|
+
if (!complete) {
|
|
1291
|
+
db.exec(`DROP TABLE IF EXISTS rail_pr_ticket_effects_v51`);
|
|
1292
|
+
createFullTable('rail_pr_ticket_effects_v51');
|
|
1293
|
+
const value = (column, fallback) => cols.has(column) ? column : fallback;
|
|
1294
|
+
db.exec(`
|
|
1295
|
+
INSERT INTO rail_pr_ticket_effects_v51 (
|
|
1296
|
+
delivery_id, ticket_ids, causal_owners, applied_ticket_ids, target_status,
|
|
1297
|
+
jira_action, pr_url, attempts, last_error, tickets_applied_at,
|
|
1298
|
+
jira_enqueued_at, completed_at, created_at, updated_at
|
|
1299
|
+
)
|
|
1300
|
+
SELECT
|
|
1301
|
+
${value('delivery_id', "''")},
|
|
1302
|
+
${value('ticket_ids', "'[]'")},
|
|
1303
|
+
${value('causal_owners', "'{}'")},
|
|
1304
|
+
${value('applied_ticket_ids', 'NULL')},
|
|
1305
|
+
${value('target_status', "'todo'")},
|
|
1306
|
+
${value('jira_action', "'backlog'")},
|
|
1307
|
+
${value('pr_url', 'NULL')},
|
|
1308
|
+
${value('attempts', '0')},
|
|
1309
|
+
${value('last_error', 'NULL')},
|
|
1310
|
+
${value('tickets_applied_at', 'NULL')},
|
|
1311
|
+
${value('jira_enqueued_at', 'NULL')},
|
|
1312
|
+
${value('completed_at', 'NULL')},
|
|
1313
|
+
${value('created_at', "datetime('now')")},
|
|
1314
|
+
${value('updated_at', "datetime('now')")}
|
|
1315
|
+
FROM rail_pr_ticket_effects;
|
|
1316
|
+
|
|
1317
|
+
DROP TABLE rail_pr_ticket_effects;
|
|
1318
|
+
ALTER TABLE rail_pr_ticket_effects_v51 RENAME TO rail_pr_ticket_effects;
|
|
1319
|
+
`);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
db.exec(`
|
|
1323
|
+
CREATE INDEX IF NOT EXISTS idx_rail_pr_ticket_effects_pending
|
|
1324
|
+
ON rail_pr_ticket_effects(completed_at)
|
|
1325
|
+
WHERE completed_at IS NULL
|
|
1326
|
+
`);
|
|
1327
|
+
},
|
|
1328
|
+
// Migration 52: bind every ticket effect to the SQLite ticket-generation
|
|
1329
|
+
// owner that created it. Historical terminal rows are backfilled only when
|
|
1330
|
+
// their run_ids still own the ticket; an old merge/discard can therefore
|
|
1331
|
+
// never mutate a newer iteration merely because it is also on_review.
|
|
1332
|
+
(db) => {
|
|
1333
|
+
// Some development builds recorded migration 45 before its ownership
|
|
1334
|
+
// table was added. Repair that incomplete historical shape before the
|
|
1335
|
+
// causal backfill so one damaged project DB cannot prevent app startup.
|
|
1336
|
+
db.exec(`
|
|
1337
|
+
CREATE TABLE IF NOT EXISTS ticket_outcome_ownership (
|
|
1338
|
+
ticket_id INTEGER PRIMARY KEY,
|
|
1339
|
+
owner_id TEXT NOT NULL,
|
|
1340
|
+
generation INTEGER NOT NULL DEFAULT 1,
|
|
1341
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1342
|
+
);
|
|
1343
|
+
CREATE INDEX IF NOT EXISTS idx_ticket_outcome_owner
|
|
1344
|
+
ON ticket_outcome_ownership(owner_id);
|
|
1345
|
+
`);
|
|
1346
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(rail_pr_ticket_effects)`).all()
|
|
1347
|
+
.map((row) => row.name));
|
|
1348
|
+
if (!cols.has('causal_owners')) {
|
|
1349
|
+
db.exec(`ALTER TABLE rail_pr_ticket_effects ADD COLUMN causal_owners TEXT NOT NULL DEFAULT '{}'`);
|
|
1350
|
+
}
|
|
1351
|
+
backfillTerminalRailPrTicketEffects(db);
|
|
1352
|
+
},
|
|
1353
|
+
// Migration 53: an allocation rollback is the one legal way a previously
|
|
1354
|
+
// superseded delivery can become active again. Persist the failed replacement
|
|
1355
|
+
// id on the restored predecessor so clients can distinguish that explicit
|
|
1356
|
+
// rollback from an ordinary stale non-terminal replay.
|
|
1357
|
+
(db) => {
|
|
1358
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all()
|
|
1359
|
+
.map((row) => row.name));
|
|
1360
|
+
if (!cols.has('restored_from_delivery_id')) {
|
|
1361
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN restored_from_delivery_id TEXT`);
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
// Migration 54: automatic worktree release quarantines authenticated overlay
|
|
1365
|
+
// roots instead of deleting them. Persist the bounded archive locations so
|
|
1366
|
+
// recovery remains discoverable after restart and card refresh.
|
|
1367
|
+
(db) => {
|
|
1368
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all()
|
|
1369
|
+
.map((row) => row.name));
|
|
1370
|
+
if (!cols.has('safety_archives')) {
|
|
1371
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN safety_archives TEXT NOT NULL DEFAULT '[]'`);
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
// Migration 55: Agent Studio history belongs to a provider-native role
|
|
1375
|
+
// catalog. The same logical custom role id can validly exist for Claude and
|
|
1376
|
+
// Kimi in one project; keying history only by agent_name mixed bodies and
|
|
1377
|
+
// version counters across those catalogs. Legacy rows predate provider-aware
|
|
1378
|
+
// Studio and therefore belong to Claude.
|
|
1379
|
+
(db) => {
|
|
1380
|
+
const versionCols = new Set(db.prepare(`PRAGMA table_info(agent_versions)`).all()
|
|
1381
|
+
.map((row) => row.name));
|
|
1382
|
+
if (!versionCols.has('provider')) {
|
|
1383
|
+
db.exec(`
|
|
1384
|
+
ALTER TABLE agent_versions RENAME TO agent_versions_v54;
|
|
1385
|
+
CREATE TABLE agent_versions (
|
|
1386
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1387
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
1388
|
+
agent_name TEXT NOT NULL,
|
|
1389
|
+
version INTEGER NOT NULL,
|
|
1390
|
+
body TEXT NOT NULL,
|
|
1391
|
+
created_at INTEGER NOT NULL,
|
|
1392
|
+
UNIQUE (provider, agent_name, version)
|
|
1393
|
+
);
|
|
1394
|
+
INSERT INTO agent_versions (id, provider, agent_name, version, body, created_at)
|
|
1395
|
+
SELECT id, 'claude', agent_name, version, body, created_at
|
|
1396
|
+
FROM agent_versions_v54;
|
|
1397
|
+
DROP TABLE agent_versions_v54;
|
|
1398
|
+
CREATE INDEX idx_agent_versions_provider_name
|
|
1399
|
+
ON agent_versions(provider, agent_name);
|
|
1400
|
+
`);
|
|
1401
|
+
}
|
|
1402
|
+
const testCols = new Set(db.prepare(`PRAGMA table_info(agent_tests)`).all()
|
|
1403
|
+
.map((row) => row.name));
|
|
1404
|
+
if (!testCols.has('provider')) {
|
|
1405
|
+
db.exec(`ALTER TABLE agent_tests ADD COLUMN provider TEXT NOT NULL DEFAULT 'claude'`);
|
|
1406
|
+
}
|
|
1407
|
+
db.exec(`
|
|
1408
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tests_provider_name
|
|
1409
|
+
ON agent_tests(provider, agent_name)
|
|
1410
|
+
`);
|
|
1411
|
+
},
|
|
1412
|
+
// Migration 56: nontech-review-experience Wave 1 — two additive nullable
|
|
1413
|
+
// JSON columns on rail_pr_deliveries. `spec_snapshot` freezes each covered
|
|
1414
|
+
// ticket's title/description/labels at LAUNCH so the review packet renders
|
|
1415
|
+
// what the user actually asked (the live store can be edited mid-run).
|
|
1416
|
+
// `settle_evidence` persists the deterministic settle-time harvest (the
|
|
1417
|
+
// VERIFICATION sentinel, the verify step's output tail, the reviewer's
|
|
1418
|
+
// confidence-score.json) captured BEFORE worktree release — the packet's
|
|
1419
|
+
// proof sections survive worktree teardown. NULL on legacy rows = honest
|
|
1420
|
+
// "not captured". Additive + idempotent (guarded by PRAGMA table_info).
|
|
1421
|
+
(db) => {
|
|
1422
|
+
const cols = db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all()
|
|
1423
|
+
.map((r) => r.name);
|
|
1424
|
+
if (!cols.includes('spec_snapshot')) {
|
|
1425
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN spec_snapshot TEXT`);
|
|
1426
|
+
}
|
|
1427
|
+
if (!cols.includes('settle_evidence')) {
|
|
1428
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN settle_evidence TEXT`);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
// Migration 57: nontech-review-experience Wave 3 — revision metadata on the
|
|
1432
|
+
// NEW delivery generation a revision creates. `revision_note` is the user's
|
|
1433
|
+
// one-sentence instruction (so packet v2 can show what was asked to change)
|
|
1434
|
+
// and `revision_of` points at the generation being revised. Deliberately NOT
|
|
1435
|
+
// a new `decision` value: an unknown decision makes older clients drop the
|
|
1436
|
+
// whole PR card, and the shipped generation/supersession model already
|
|
1437
|
+
// expresses "a newer attempt replaced the previous one". Additive +
|
|
1438
|
+
// idempotent; NULL on every pre-existing row and on ordinary launches.
|
|
1439
|
+
(db) => {
|
|
1440
|
+
const cols = db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all()
|
|
1441
|
+
.map((r) => r.name);
|
|
1442
|
+
if (!cols.includes('revision_note')) {
|
|
1443
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN revision_note TEXT`);
|
|
1444
|
+
}
|
|
1445
|
+
if (!cols.includes('revision_of')) {
|
|
1446
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN revision_of TEXT`);
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
// Migration 58: premium-milestone-progress — server-durable milestone launch
|
|
1450
|
+
// chains. One row per sequential "Launch Milestone": the chunk plan, the
|
|
1451
|
+
// chunk in flight (rail / runs / delivery), the branch the next chunk stacks
|
|
1452
|
+
// on, and a CAS status. The partial unique index enforces ONE non-terminal
|
|
1453
|
+
// chain per milestone. Additive; nothing else references it.
|
|
1454
|
+
(db) => {
|
|
1455
|
+
db.exec(`
|
|
1456
|
+
CREATE TABLE IF NOT EXISTS milestone_launch_chains (
|
|
1457
|
+
id TEXT PRIMARY KEY,
|
|
1458
|
+
milestone_n INTEGER NOT NULL,
|
|
1459
|
+
milestone_id TEXT NOT NULL,
|
|
1460
|
+
mode TEXT NOT NULL,
|
|
1461
|
+
chunks TEXT NOT NULL DEFAULT '[]',
|
|
1462
|
+
next_chunk INTEGER NOT NULL DEFAULT 0,
|
|
1463
|
+
current_rail_index INTEGER,
|
|
1464
|
+
current_run_ids TEXT NOT NULL DEFAULT '[]',
|
|
1465
|
+
current_delivery_id TEXT,
|
|
1466
|
+
integration_branch TEXT,
|
|
1467
|
+
head_branch TEXT,
|
|
1468
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
1469
|
+
pause_reason TEXT,
|
|
1470
|
+
launched TEXT NOT NULL DEFAULT '[]',
|
|
1471
|
+
last_run_outcome TEXT,
|
|
1472
|
+
auto_advance INTEGER NOT NULL DEFAULT 1,
|
|
1473
|
+
retry_chunk INTEGER,
|
|
1474
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1475
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1476
|
+
);
|
|
1477
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_milestone_chains_active
|
|
1478
|
+
ON milestone_launch_chains(milestone_n) WHERE status IN ('running','waiting','paused','awaiting_approval');
|
|
1479
|
+
CREATE INDEX IF NOT EXISTS idx_milestone_chains_status ON milestone_launch_chains(status);
|
|
1480
|
+
`);
|
|
1481
|
+
},
|
|
1482
|
+
// Migration 59: coordinated repository execution and grouped delivery.
|
|
1483
|
+
multi_repo_execution_store_1.migrateMultiRepoExecution,
|
|
1484
|
+
// Migration 60: repository-scoped provenance and code identity.
|
|
1485
|
+
project_repository_provenance_1.migrateRepositoryProvenance,
|
|
1486
|
+
// Migration 61: version and evidence identity of recorded-change explanations.
|
|
1487
|
+
file_story_1.migrateFileStoryMetadata,
|
|
1488
|
+
// Migration 62: pr-follow-up-fixes — the frozen review follow-up scope a launch
|
|
1489
|
+
// was approved with (`follow_up`, JSON PrFollowUp: selected comments, required
|
|
1490
|
+
// outcomes, exclusions, verification, OpenSpec change name, id/version/hash).
|
|
1491
|
+
// Lives on the DELIVERY, never on the spec: a follow-up must not rewrite the
|
|
1492
|
+
// ticket description (Jira-synced on linked projects). Additive + idempotent;
|
|
1493
|
+
// NULL on every pre-existing row and on ordinary launches.
|
|
1494
|
+
(db) => {
|
|
1495
|
+
const cols = db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all().map((r) => r.name);
|
|
1496
|
+
if (!cols.includes('follow_up'))
|
|
1497
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN follow_up TEXT`);
|
|
1498
|
+
},
|
|
1499
|
+
// Migration 63: spec-addenda — the frozen identity (ticketId/id/kind/title/
|
|
1500
|
+
// hash) of the spec addenda a launch carried (`spec_addenda`, JSON
|
|
1501
|
+
// SpecAddendaSnapshotEntry[]). The addenda themselves live on the TICKET
|
|
1502
|
+
// (local-tickets.json, schema 1.4); the delivery only records which ones
|
|
1503
|
+
// this generation was briefed with, so the packet can report them and a
|
|
1504
|
+
// discard can reopen exactly those. Additive + idempotent; NULL when none.
|
|
1505
|
+
(db) => {
|
|
1506
|
+
const cols = db.prepare(`PRAGMA table_info(rail_pr_deliveries)`).all().map((r) => r.name);
|
|
1507
|
+
if (!cols.includes('spec_addenda'))
|
|
1508
|
+
db.exec(`ALTER TABLE rail_pr_deliveries ADD COLUMN spec_addenda TEXT`);
|
|
1509
|
+
},
|
|
1510
|
+
];
|
|
1511
|
+
function applyMigrations(db) {
|
|
1512
|
+
// Ensure the migrations table exists (migration 1 creates it, but we need
|
|
1513
|
+
// it before we can read from it)
|
|
1514
|
+
db.exec(`
|
|
1515
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
1516
|
+
version INTEGER PRIMARY KEY,
|
|
1517
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1518
|
+
)
|
|
1519
|
+
`);
|
|
1520
|
+
(0, sqlite_migrations_1.applyNumberedMigrations)(db, MIGRATIONS);
|
|
1521
|
+
}
|