@zq-silk/yui 0.16.1 → 1.0.0-alpha
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/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- package/dist/storage/migrations/workItemHistory.js +0 -46
|
@@ -1,1765 +1,130 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite WAL control-plane schema and migration runner (task-21, work-item-3).
|
|
3
|
-
*
|
|
4
|
-
* This is the normalized, `task_id`-partitioned schema that replaces the single
|
|
5
|
-
* aggregate `state.json` document. It implements the logical schema from
|
|
6
|
-
* `docs/sqlite-control-plane-design.md` §4 (31 tables) plus the cross-task
|
|
7
|
-
* coordination tables the design references in §5:
|
|
8
|
-
*
|
|
9
|
-
* - `global_sequences` (§5.3): global record ID high-water marks.
|
|
10
|
-
* - `outbox` (§5.4): durable outbox with `UNIQUE(request_id)` for exactly-once.
|
|
11
|
-
* - `config`: the `YuiConfig` singleton (`home_meta` keeps identity/revision).
|
|
12
|
-
*
|
|
13
|
-
* Record payloads are stored two ways, per §4: typed columns for fields that are
|
|
14
|
-
* queried/filtered/used-for-CAS, and a `payload` JSON column holding the full
|
|
15
|
-
* current record. Record-local `schemaVersion` tags remain validation guards;
|
|
16
|
-
* they are not independent Home compatibility axes. Any historical payload
|
|
17
|
-
* rewrite belongs to the ordered Home migration that introduced the new shape.
|
|
18
|
-
*
|
|
19
|
-
* The migration runner is append-only and idempotent: it records one ordered
|
|
20
|
-
* Home version in `schema_migrations`. Re-running an already-current database
|
|
21
|
-
* is a no-op; a crash mid-upgrade rolls the whole migration transaction back.
|
|
22
|
-
*/
|
|
23
1
|
import { createHash } from "node:crypto";
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
import { migrateNotificationOnlyWakes } from "./migrations/notificationOnlyWakes.js";
|
|
29
|
-
import { migrateWorkItemHistory } from "./migrations/workItemHistory.js";
|
|
30
|
-
import { migrateVerificationPolicy } from "./migrations/verificationPolicy.js";
|
|
31
|
-
import { migrateCurrentInputContract } from "./migrations/currentInputContract.js";
|
|
32
|
-
import { CURRENT_RUNTIME_CONTRACT_SQL, migrateCurrentRuntimeContract } from "./migrations/currentRuntimeContract.js";
|
|
33
|
-
import { migrateAgentFailureContext } from "./migrations/agentFailureContext.js";
|
|
34
|
-
import { migrateNarrowAgentFailureContext } from "./migrations/narrowAgentFailureContext.js";
|
|
35
|
-
import { UNIFY_HOME_LAYOUT_SQL, migrateUnifyHomeLayout } from "./migrations/unifyHomeLayout.js";
|
|
36
|
-
import { COLLAPSE_WORKTREE_LAYOUT_SQL, migrateCollapseWorktreeLayout } from "./migrations/collapseWorktreeLayout.js";
|
|
37
|
-
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
|
|
38
|
-
/** Telemetry retention bounds (§4.4). Open question 3 in §11; defaults from the design. */
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import { BASELINE_SCHEMA_SQL, SQLITE_SCHEMA_TABLES } from "./baselineSchema.js";
|
|
4
|
+
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION, STORAGE_FORMAT, isMinorStorageUpgrade, isStorageVersion, storageVersionParts } from "./storageVersions.js";
|
|
5
|
+
export { BASELINE_SCHEMA_SQL, SQLITE_SCHEMA_TABLES };
|
|
39
6
|
export const TELEMETRY_KEEP_PER_RUN = 200;
|
|
40
7
|
export const TELEMETRY_RUN_CAP = 50_000;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
* This is the Yui 0.15.0 / storage-version-1 baseline. Future releases append
|
|
49
|
-
* migrations after it so fresh and upgraded databases converge on the same
|
|
50
|
-
* current contract.
|
|
51
|
-
*/
|
|
52
|
-
const BASELINE_CORE_SQL = `
|
|
53
|
-
-- Global catalog and coordination (§4.1) -------------------------------------
|
|
54
|
-
|
|
55
|
-
CREATE TABLE IF NOT EXISTS home_meta (
|
|
56
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
57
|
-
home_identity TEXT NOT NULL,
|
|
58
|
-
revision INTEGER NOT NULL,
|
|
59
|
-
created_at TEXT NOT NULL,
|
|
60
|
-
updated_at TEXT NOT NULL
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
CREATE TABLE IF NOT EXISTS config (
|
|
64
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
65
|
-
payload TEXT NOT NULL,
|
|
66
|
-
updated_at TEXT NOT NULL
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
CREATE TABLE IF NOT EXISTS configured_agents (
|
|
70
|
-
id TEXT PRIMARY KEY,
|
|
71
|
-
payload TEXT NOT NULL,
|
|
72
|
-
updated_at TEXT NOT NULL
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
CREATE TABLE IF NOT EXISTS agent_profiles (
|
|
76
|
-
id TEXT PRIMARY KEY,
|
|
77
|
-
payload TEXT NOT NULL,
|
|
78
|
-
updated_at TEXT NOT NULL
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
82
|
-
id TEXT PRIMARY KEY,
|
|
83
|
-
name TEXT NOT NULL,
|
|
84
|
-
path TEXT NOT NULL,
|
|
85
|
-
payload TEXT NOT NULL,
|
|
86
|
-
created_at TEXT NOT NULL,
|
|
87
|
-
updated_at TEXT NOT NULL
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
CREATE TABLE IF NOT EXISTS global_roles (
|
|
91
|
-
name TEXT PRIMARY KEY,
|
|
92
|
-
payload TEXT NOT NULL,
|
|
93
|
-
updated_at TEXT NOT NULL
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
CREATE TABLE IF NOT EXISTS global_role_session_sets (
|
|
97
|
-
name TEXT PRIMARY KEY,
|
|
98
|
-
payload TEXT NOT NULL,
|
|
99
|
-
updated_at TEXT NOT NULL,
|
|
100
|
-
FOREIGN KEY (name) REFERENCES global_roles(name)
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
-- Global record ID high-water marks (§5.3).
|
|
104
|
-
CREATE TABLE IF NOT EXISTS global_sequences (
|
|
105
|
-
name TEXT PRIMARY KEY,
|
|
106
|
-
high_water INTEGER NOT NULL
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
-- Task catalog: the global active index and lifecycle lookup.
|
|
110
|
-
CREATE TABLE IF NOT EXISTS tasks_catalog (
|
|
111
|
-
task_id TEXT PRIMARY KEY,
|
|
112
|
-
status TEXT NOT NULL,
|
|
113
|
-
lifecycle TEXT NOT NULL,
|
|
114
|
-
is_active INTEGER NOT NULL CHECK (is_active IN (0,1)),
|
|
115
|
-
created_at TEXT NOT NULL,
|
|
116
|
-
updated_at TEXT NOT NULL
|
|
117
|
-
);
|
|
118
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks_catalog(is_active) WHERE is_active = 1;
|
|
119
|
-
|
|
120
|
-
-- Session/Workspace ownership is global (workspaces outlive task activity).
|
|
121
|
-
CREATE TABLE IF NOT EXISTS managed_workspaces (
|
|
122
|
-
owner_kind TEXT NOT NULL CHECK (owner_kind IN
|
|
123
|
-
('task','work-item','review-round','integration-attempt','execution-lane')),
|
|
124
|
-
owner_id TEXT NOT NULL,
|
|
125
|
-
task_id TEXT,
|
|
126
|
-
path TEXT NOT NULL,
|
|
127
|
-
payload TEXT NOT NULL,
|
|
128
|
-
status TEXT NOT NULL,
|
|
129
|
-
created_at TEXT NOT NULL,
|
|
130
|
-
updated_at TEXT NOT NULL,
|
|
131
|
-
PRIMARY KEY (owner_kind, owner_id)
|
|
132
|
-
);
|
|
133
|
-
CREATE INDEX IF NOT EXISTS idx_workspaces_task ON managed_workspaces(task_id);
|
|
134
|
-
|
|
135
|
-
-- Per-task record ID high-water marks (replaces StoredTask.idHighWaterMarks).
|
|
136
|
-
CREATE TABLE IF NOT EXISTS id_sequences (
|
|
137
|
-
task_id TEXT NOT NULL,
|
|
138
|
-
kind TEXT NOT NULL,
|
|
139
|
-
high_water INTEGER NOT NULL,
|
|
140
|
-
PRIMARY KEY (task_id, kind)
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
-- Cross-task coordination: Project locks and the Integration queue.
|
|
144
|
-
CREATE TABLE IF NOT EXISTS coordination_locks (
|
|
145
|
-
lock_key TEXT PRIMARY KEY,
|
|
146
|
-
holder_task TEXT NOT NULL,
|
|
147
|
-
holder_ref TEXT NOT NULL,
|
|
148
|
-
acquired_at TEXT NOT NULL,
|
|
149
|
-
expires_at TEXT
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
CREATE TABLE IF NOT EXISTS integration_queue (
|
|
153
|
-
queue_id TEXT PRIMARY KEY,
|
|
154
|
-
task_id TEXT NOT NULL,
|
|
155
|
-
project_id TEXT NOT NULL,
|
|
156
|
-
change_set TEXT NOT NULL,
|
|
157
|
-
status TEXT NOT NULL,
|
|
158
|
-
payload TEXT NOT NULL,
|
|
159
|
-
created_at TEXT NOT NULL,
|
|
160
|
-
updated_at TEXT NOT NULL
|
|
161
|
-
);
|
|
162
|
-
CREATE INDEX IF NOT EXISTS idx_integration_queue_status ON integration_queue(status, created_at);
|
|
163
|
-
|
|
164
|
-
-- Durable outbox (§5.4): UNIQUE(request_id) makes cross-task effects exactly-once.
|
|
165
|
-
CREATE TABLE IF NOT EXISTS outbox (
|
|
166
|
-
outbox_id INTEGER PRIMARY KEY,
|
|
167
|
-
request_id TEXT NOT NULL UNIQUE,
|
|
168
|
-
command TEXT NOT NULL,
|
|
169
|
-
state TEXT NOT NULL DEFAULT 'pending',
|
|
170
|
-
created_at TEXT NOT NULL,
|
|
171
|
-
applied_at TEXT
|
|
172
|
-
);
|
|
173
|
-
CREATE INDEX IF NOT EXISTS idx_outbox_state ON outbox(state, created_at);
|
|
174
|
-
|
|
175
|
-
-- Mailboxes (§4.2): per-target ordering and exactly-once signals. -------------
|
|
176
|
-
|
|
177
|
-
CREATE TABLE IF NOT EXISTS mailboxes (
|
|
178
|
-
mailbox_id INTEGER PRIMARY KEY,
|
|
179
|
-
target_kind TEXT NOT NULL CHECK (target_kind IN
|
|
180
|
-
('task','role','role-runtime','global-role-runtime','operator')),
|
|
181
|
-
task_id TEXT,
|
|
182
|
-
role_name TEXT,
|
|
183
|
-
-- The stable mailboxTargetKey string carries the real uniqueness: the column
|
|
184
|
-
-- UNIQUE below cannot, because NULL task_id/role_name (the 'operator' and
|
|
185
|
-
-- 'global-role-runtime' targets) are distinct under SQL NULL semantics.
|
|
186
|
-
target_key TEXT NOT NULL,
|
|
187
|
-
next_sequence INTEGER NOT NULL,
|
|
188
|
-
processing TEXT,
|
|
189
|
-
pending TEXT,
|
|
190
|
-
recent_dedupe_keys TEXT NOT NULL DEFAULT '[]',
|
|
191
|
-
UNIQUE (target_kind, task_id, role_name)
|
|
192
|
-
);
|
|
193
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_mailboxes_target_key
|
|
194
|
-
ON mailboxes(target_key);
|
|
195
|
-
CREATE INDEX IF NOT EXISTS idx_mailboxes_ready
|
|
196
|
-
ON mailboxes(target_key)
|
|
197
|
-
WHERE processing IS NOT NULL OR json_type(pending) <> 'null';
|
|
198
|
-
|
|
199
|
-
-- Task-partitioned tables (§4.3). Every task-local read constrains task_id. ----
|
|
200
|
-
|
|
201
|
-
CREATE TABLE IF NOT EXISTS task_records (
|
|
202
|
-
task_id TEXT PRIMARY KEY,
|
|
203
|
-
payload TEXT NOT NULL,
|
|
204
|
-
brief TEXT,
|
|
205
|
-
updated_at TEXT NOT NULL,
|
|
206
|
-
FOREIGN KEY (task_id) REFERENCES tasks_catalog(task_id)
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
CREATE TABLE IF NOT EXISTS task_roles (
|
|
210
|
-
task_id TEXT NOT NULL,
|
|
211
|
-
role_name TEXT NOT NULL,
|
|
212
|
-
payload TEXT NOT NULL,
|
|
213
|
-
updated_at TEXT NOT NULL,
|
|
214
|
-
PRIMARY KEY (task_id, role_name)
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
CREATE TABLE IF NOT EXISTS role_session_sets (
|
|
218
|
-
task_id TEXT NOT NULL,
|
|
219
|
-
role_name TEXT NOT NULL,
|
|
220
|
-
payload TEXT NOT NULL,
|
|
221
|
-
updated_at TEXT NOT NULL,
|
|
222
|
-
PRIMARY KEY (task_id, role_name)
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
CREATE TABLE IF NOT EXISTS work_items (
|
|
226
|
-
task_id TEXT NOT NULL,
|
|
227
|
-
work_item_id TEXT NOT NULL,
|
|
228
|
-
status TEXT NOT NULL,
|
|
229
|
-
payload TEXT NOT NULL,
|
|
230
|
-
updated_at TEXT NOT NULL,
|
|
231
|
-
PRIMARY KEY (task_id, work_item_id)
|
|
232
|
-
);
|
|
233
|
-
CREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(task_id, status);
|
|
234
|
-
|
|
235
|
-
CREATE TABLE IF NOT EXISTS work_item_candidates (
|
|
236
|
-
task_id TEXT NOT NULL,
|
|
237
|
-
candidate_id TEXT NOT NULL,
|
|
238
|
-
work_item_id TEXT NOT NULL,
|
|
239
|
-
payload TEXT NOT NULL,
|
|
240
|
-
created_at TEXT NOT NULL,
|
|
241
|
-
PRIMARY KEY (task_id, candidate_id)
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
CREATE TABLE IF NOT EXISTS turns (
|
|
245
|
-
task_id TEXT NOT NULL,
|
|
246
|
-
turn_id TEXT NOT NULL,
|
|
247
|
-
role_name TEXT NOT NULL,
|
|
248
|
-
status TEXT NOT NULL,
|
|
249
|
-
payload TEXT NOT NULL,
|
|
250
|
-
updated_at TEXT NOT NULL,
|
|
251
|
-
PRIMARY KEY (task_id, turn_id)
|
|
252
|
-
);
|
|
253
|
-
CREATE INDEX IF NOT EXISTS idx_turns_role_status ON turns(task_id, role_name, status);
|
|
254
|
-
|
|
255
|
-
-- Active-Turn pointers (getActiveTurn / execution-lane Turns).
|
|
256
|
-
CREATE TABLE IF NOT EXISTS active_turns (
|
|
257
|
-
task_id TEXT NOT NULL,
|
|
258
|
-
pointer TEXT NOT NULL,
|
|
259
|
-
turn_id TEXT NOT NULL,
|
|
260
|
-
payload TEXT NOT NULL,
|
|
261
|
-
updated_at TEXT NOT NULL,
|
|
262
|
-
PRIMARY KEY (task_id, pointer)
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
CREATE TABLE IF NOT EXISTS review_rounds (
|
|
266
|
-
task_id TEXT NOT NULL,
|
|
267
|
-
review_round_id TEXT NOT NULL,
|
|
268
|
-
status TEXT NOT NULL,
|
|
269
|
-
payload TEXT NOT NULL,
|
|
270
|
-
updated_at TEXT NOT NULL,
|
|
271
|
-
PRIMARY KEY (task_id, review_round_id)
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
CREATE TABLE IF NOT EXISTS change_sets (
|
|
275
|
-
task_id TEXT NOT NULL,
|
|
276
|
-
change_set_id TEXT NOT NULL,
|
|
277
|
-
project_id TEXT NOT NULL,
|
|
278
|
-
head_sha TEXT NOT NULL,
|
|
279
|
-
payload TEXT NOT NULL,
|
|
280
|
-
created_at TEXT NOT NULL,
|
|
281
|
-
PRIMARY KEY (task_id, change_set_id)
|
|
282
|
-
);
|
|
283
|
-
CREATE INDEX IF NOT EXISTS idx_change_sets_project ON change_sets(task_id, project_id);
|
|
284
|
-
|
|
285
|
-
CREATE TABLE IF NOT EXISTS integration_attempts (
|
|
286
|
-
task_id TEXT NOT NULL,
|
|
287
|
-
integration_id TEXT NOT NULL,
|
|
288
|
-
status TEXT NOT NULL,
|
|
289
|
-
payload TEXT NOT NULL,
|
|
290
|
-
updated_at TEXT NOT NULL,
|
|
291
|
-
PRIMARY KEY (task_id, integration_id)
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
295
|
-
task_id TEXT NOT NULL,
|
|
296
|
-
message_id TEXT NOT NULL,
|
|
297
|
-
seq INTEGER NOT NULL,
|
|
298
|
-
payload TEXT NOT NULL,
|
|
299
|
-
created_at TEXT NOT NULL,
|
|
300
|
-
PRIMARY KEY (task_id, message_id)
|
|
301
|
-
);
|
|
302
|
-
CREATE INDEX IF NOT EXISTS idx_messages_seq ON messages(task_id, seq);
|
|
303
|
-
|
|
304
|
-
CREATE TABLE IF NOT EXISTS input_requests (
|
|
305
|
-
task_id TEXT NOT NULL,
|
|
306
|
-
input_id TEXT NOT NULL,
|
|
307
|
-
status TEXT NOT NULL,
|
|
308
|
-
blocks TEXT,
|
|
309
|
-
payload TEXT NOT NULL,
|
|
310
|
-
created_at TEXT NOT NULL,
|
|
311
|
-
updated_at TEXT NOT NULL,
|
|
312
|
-
PRIMARY KEY (task_id, input_id)
|
|
313
|
-
);
|
|
314
|
-
CREATE INDEX IF NOT EXISTS idx_input_open ON input_requests(task_id, status) WHERE status <> 'resolved';
|
|
315
|
-
CREATE INDEX IF NOT EXISTS idx_input_requests_open_hot
|
|
316
|
-
ON input_requests(task_id, input_id)
|
|
317
|
-
WHERE status = 'open';
|
|
318
|
-
|
|
319
|
-
CREATE TABLE IF NOT EXISTS decisions (
|
|
320
|
-
task_id TEXT NOT NULL,
|
|
321
|
-
decision_id TEXT NOT NULL,
|
|
322
|
-
payload TEXT NOT NULL,
|
|
323
|
-
created_at TEXT NOT NULL,
|
|
324
|
-
PRIMARY KEY (task_id, decision_id)
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
CREATE TABLE IF NOT EXISTS milestones (
|
|
328
|
-
task_id TEXT NOT NULL,
|
|
329
|
-
milestone_id TEXT NOT NULL,
|
|
330
|
-
payload TEXT NOT NULL,
|
|
331
|
-
created_at TEXT NOT NULL,
|
|
332
|
-
PRIMARY KEY (task_id, milestone_id)
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
-- Terminal/semantic events: retained individually, never pruned.
|
|
336
|
-
CREATE TABLE IF NOT EXISTS events (
|
|
337
|
-
task_id TEXT NOT NULL,
|
|
338
|
-
event_id TEXT NOT NULL,
|
|
339
|
-
type TEXT NOT NULL,
|
|
340
|
-
occurred_at TEXT NOT NULL,
|
|
341
|
-
payload TEXT NOT NULL,
|
|
342
|
-
PRIMARY KEY (task_id, event_id)
|
|
343
|
-
);
|
|
344
|
-
CREATE INDEX IF NOT EXISTS idx_events_type_time ON events(task_id, type, occurred_at);
|
|
345
|
-
|
|
346
|
-
-- Per-task scheduler projections (leaderFailure, operatorNotification).
|
|
347
|
-
CREATE TABLE IF NOT EXISTS task_projections (
|
|
348
|
-
task_id TEXT NOT NULL,
|
|
349
|
-
kind TEXT NOT NULL CHECK (kind IN ('leader-failure','operator-notification')),
|
|
350
|
-
payload TEXT,
|
|
351
|
-
updated_at TEXT NOT NULL,
|
|
352
|
-
PRIMARY KEY (task_id, kind)
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
-- Telemetry (§4.4): bounded, latest-per-key. WITHOUT ROWID, PK is the key.
|
|
356
|
-
CREATE TABLE IF NOT EXISTS telemetry (
|
|
357
|
-
task_id TEXT NOT NULL,
|
|
358
|
-
role_name TEXT NOT NULL,
|
|
359
|
-
turn_id TEXT NOT NULL,
|
|
360
|
-
generation TEXT NOT NULL,
|
|
361
|
-
progress_id TEXT NOT NULL,
|
|
362
|
-
sequence INTEGER,
|
|
363
|
-
payload TEXT NOT NULL,
|
|
364
|
-
received_at TEXT NOT NULL,
|
|
365
|
-
PRIMARY KEY (task_id, role_name, turn_id, generation, progress_id)
|
|
366
|
-
) WITHOUT ROWID;
|
|
367
|
-
CREATE INDEX IF NOT EXISTS idx_telemetry_turn ON telemetry(task_id, turn_id);
|
|
368
|
-
`;
|
|
369
|
-
/** Task-scoped execution and release record families. */
|
|
370
|
-
const BASELINE_JOB_AND_RELEASE_SQL = `
|
|
371
|
-
CREATE TABLE IF NOT EXISTS durable_jobs (
|
|
372
|
-
job_id TEXT NOT NULL,
|
|
373
|
-
task_id TEXT NOT NULL,
|
|
374
|
-
idempotency_key TEXT,
|
|
375
|
-
status TEXT NOT NULL,
|
|
376
|
-
payload TEXT NOT NULL,
|
|
377
|
-
created_at TEXT NOT NULL,
|
|
378
|
-
updated_at TEXT NOT NULL,
|
|
379
|
-
PRIMARY KEY (task_id, job_id)
|
|
380
|
-
);
|
|
381
|
-
CREATE INDEX IF NOT EXISTS idx_durable_jobs_task ON durable_jobs(task_id);
|
|
382
|
-
CREATE INDEX IF NOT EXISTS idx_durable_jobs_status ON durable_jobs(status);
|
|
383
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_durable_jobs_idempotency
|
|
384
|
-
ON durable_jobs(task_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
385
|
-
CREATE TABLE IF NOT EXISTS capability_grants (
|
|
386
|
-
task_id TEXT NOT NULL,
|
|
387
|
-
grant_id TEXT NOT NULL,
|
|
388
|
-
payload TEXT NOT NULL,
|
|
389
|
-
updated_at TEXT NOT NULL,
|
|
390
|
-
PRIMARY KEY (task_id, grant_id)
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
CREATE TABLE IF NOT EXISTS release_workflows (
|
|
394
|
-
task_id TEXT NOT NULL,
|
|
395
|
-
workflow_id TEXT NOT NULL,
|
|
396
|
-
payload TEXT NOT NULL,
|
|
397
|
-
updated_at TEXT NOT NULL,
|
|
398
|
-
PRIMARY KEY (task_id, workflow_id)
|
|
399
|
-
);
|
|
400
|
-
`;
|
|
401
|
-
/** Durable caller-key hashes used to verify DurableJob ownership. */
|
|
402
|
-
const BASELINE_JOB_CALLER_SQL = `
|
|
403
|
-
CREATE TABLE IF NOT EXISTS job_caller_key_hashes (
|
|
404
|
-
task_id TEXT NOT NULL,
|
|
405
|
-
role_name TEXT NOT NULL,
|
|
406
|
-
agent_id TEXT NOT NULL,
|
|
407
|
-
hash TEXT NOT NULL,
|
|
408
|
-
updated_at TEXT NOT NULL,
|
|
409
|
-
PRIMARY KEY (task_id, role_name, agent_id)
|
|
410
|
-
);
|
|
411
|
-
`;
|
|
412
|
-
/**
|
|
413
|
-
* The `telemetry` table stores the bounded latest-per-key progress window; this companion
|
|
414
|
-
* table holds the authoritative per-Turn/generation summary (count, first/last,
|
|
415
|
-
* max sequence, error count) so aggregates stay accurate after the window is
|
|
416
|
-
* pruned. Triggers maintain it on telemetry INSERT/UPDATE; DELETE intentionally
|
|
417
|
-
* leaves it untouched because pruned rows were still observed.
|
|
418
|
-
*/
|
|
419
|
-
const BASELINE_TELEMETRY_AGGREGATE_SQL = `
|
|
420
|
-
CREATE TABLE IF NOT EXISTS telemetry_aggregate (
|
|
421
|
-
task_id TEXT NOT NULL,
|
|
422
|
-
role_name TEXT NOT NULL,
|
|
423
|
-
turn_id TEXT NOT NULL,
|
|
424
|
-
generation TEXT NOT NULL,
|
|
425
|
-
first_at TEXT NOT NULL,
|
|
426
|
-
last_at TEXT NOT NULL,
|
|
427
|
-
count INTEGER NOT NULL,
|
|
428
|
-
max_sequence INTEGER,
|
|
429
|
-
error_count INTEGER NOT NULL DEFAULT 0,
|
|
430
|
-
updated_at TEXT NOT NULL,
|
|
431
|
-
PRIMARY KEY (task_id, role_name, turn_id, generation)
|
|
432
|
-
) WITHOUT ROWID;
|
|
433
|
-
|
|
434
|
-
CREATE TRIGGER IF NOT EXISTS telemetry_ai AFTER INSERT ON telemetry
|
|
435
|
-
BEGIN
|
|
436
|
-
INSERT INTO telemetry_aggregate
|
|
437
|
-
(task_id, role_name, turn_id, generation, first_at, last_at, count, max_sequence, error_count, updated_at)
|
|
438
|
-
VALUES
|
|
439
|
-
(NEW.task_id, NEW.role_name, NEW.turn_id, NEW.generation, NEW.received_at, NEW.received_at, 1, NEW.sequence,
|
|
440
|
-
CASE WHEN json_valid(NEW.payload)
|
|
441
|
-
AND (COALESCE(json_extract(NEW.payload, '$.error'), '') <> ''
|
|
442
|
-
OR COALESCE(json_extract(NEW.payload, '$.errorKind'), '') <> '')
|
|
443
|
-
THEN 1 ELSE 0 END,
|
|
444
|
-
NEW.received_at)
|
|
445
|
-
ON CONFLICT(task_id, role_name, turn_id, generation) DO UPDATE SET
|
|
446
|
-
first_at = MIN(telemetry_aggregate.first_at, excluded.first_at),
|
|
447
|
-
last_at = MAX(telemetry_aggregate.last_at, excluded.last_at),
|
|
448
|
-
count = telemetry_aggregate.count + 1,
|
|
449
|
-
max_sequence = CASE
|
|
450
|
-
WHEN excluded.max_sequence IS NOT NULL
|
|
451
|
-
AND (telemetry_aggregate.max_sequence IS NULL OR excluded.max_sequence > telemetry_aggregate.max_sequence)
|
|
452
|
-
THEN excluded.max_sequence ELSE telemetry_aggregate.max_sequence END,
|
|
453
|
-
error_count = telemetry_aggregate.error_count + excluded.error_count,
|
|
454
|
-
updated_at = excluded.updated_at;
|
|
455
|
-
END;
|
|
456
|
-
|
|
457
|
-
CREATE TRIGGER IF NOT EXISTS telemetry_au AFTER UPDATE ON telemetry
|
|
458
|
-
BEGIN
|
|
459
|
-
UPDATE telemetry_aggregate SET
|
|
460
|
-
last_at = CASE WHEN NEW.received_at > last_at THEN NEW.received_at ELSE last_at END,
|
|
461
|
-
max_sequence = CASE
|
|
462
|
-
WHEN NEW.sequence IS NOT NULL AND (max_sequence IS NULL OR NEW.sequence > max_sequence)
|
|
463
|
-
THEN NEW.sequence ELSE max_sequence END,
|
|
464
|
-
updated_at = NEW.received_at
|
|
465
|
-
WHERE task_id = NEW.task_id AND role_name = NEW.role_name
|
|
466
|
-
AND turn_id = NEW.turn_id AND generation = NEW.generation;
|
|
467
|
-
END;
|
|
468
|
-
`;
|
|
469
|
-
/**
|
|
470
|
-
* Session owner physical identity records.
|
|
471
|
-
*
|
|
472
|
-
* One row per runtime generation, keyed by runtime generation id. The payload column
|
|
473
|
-
* stores the full current JSON record; typed columns support the
|
|
474
|
-
* reconciliation queries (task/role lookup, PID liveness).
|
|
475
|
-
*/
|
|
476
|
-
const BASELINE_SESSION_OWNER_SQL = `
|
|
477
|
-
CREATE TABLE IF NOT EXISTS session_owners (
|
|
478
|
-
launch_id TEXT PRIMARY KEY,
|
|
479
|
-
scope TEXT NOT NULL CHECK (scope IN ('task','global')),
|
|
480
|
-
task_id TEXT,
|
|
481
|
-
role_name TEXT NOT NULL,
|
|
482
|
-
agent_id TEXT NOT NULL,
|
|
483
|
-
native_session_id TEXT,
|
|
484
|
-
provider_root_pid INTEGER,
|
|
485
|
-
payload TEXT NOT NULL,
|
|
486
|
-
recorded_at TEXT NOT NULL
|
|
487
|
-
);
|
|
488
|
-
CREATE INDEX IF NOT EXISTS idx_session_owners_task
|
|
489
|
-
ON session_owners(task_id, role_name);
|
|
490
|
-
`;
|
|
491
|
-
/**
|
|
492
|
-
* Resource GC registry.
|
|
493
|
-
*
|
|
494
|
-
* GC-owned table for resource lifecycle records. The registry is GC's own
|
|
495
|
-
* state. Records are stored as full current JSON in `payload`, with
|
|
496
|
-
* typed columns for the fields GC queries (disposition, kind, task_id).
|
|
497
|
-
*/
|
|
498
|
-
const BASELINE_RESOURCE_REGISTRY_SQL = `
|
|
499
|
-
CREATE TABLE IF NOT EXISTS resource_registry (
|
|
500
|
-
id TEXT PRIMARY KEY,
|
|
501
|
-
kind TEXT NOT NULL,
|
|
502
|
-
path TEXT NOT NULL,
|
|
503
|
-
disposition TEXT NOT NULL,
|
|
504
|
-
task_id TEXT,
|
|
505
|
-
payload TEXT NOT NULL,
|
|
506
|
-
created_at TEXT NOT NULL,
|
|
507
|
-
updated_at TEXT NOT NULL
|
|
508
|
-
);
|
|
509
|
-
CREATE INDEX IF NOT EXISTS idx_resource_registry_disposition
|
|
510
|
-
ON resource_registry(disposition);
|
|
511
|
-
CREATE INDEX IF NOT EXISTS idx_resource_registry_task
|
|
512
|
-
ON resource_registry(task_id);
|
|
513
|
-
`;
|
|
514
|
-
/**
|
|
515
|
-
* Content-addressed GateArtifact storage. Content-addressed gate
|
|
516
|
-
* evidence records with per-step logs stored as BLOBs. The artifact key is
|
|
517
|
-
* the SHA-256 of the identity tuple (Project + commit + plan digest +
|
|
518
|
-
* toolchain digest + L2 boundary), so the same tuple always maps to one row.
|
|
519
|
-
* Typed columns support the reuse lookup paths (exact-commit L2 search,
|
|
520
|
-
* Project-level prune) without scanning payloads.
|
|
521
|
-
*/
|
|
522
|
-
const BASELINE_GATE_ARTIFACT_SQL = `
|
|
523
|
-
CREATE TABLE IF NOT EXISTS gate_artifacts (
|
|
524
|
-
key TEXT PRIMARY KEY,
|
|
525
|
-
project_id TEXT NOT NULL,
|
|
526
|
-
level TEXT NOT NULL CHECK (level IN ('L1','L2')),
|
|
527
|
-
commit_sha TEXT NOT NULL,
|
|
528
|
-
plan_digest TEXT NOT NULL,
|
|
529
|
-
toolchain_digest TEXT NOT NULL,
|
|
530
|
-
target_ref TEXT,
|
|
531
|
-
status TEXT NOT NULL CHECK (status IN ('incomplete','complete')),
|
|
532
|
-
outcome TEXT NOT NULL CHECK (outcome IN ('unknown','succeeded','failed')),
|
|
533
|
-
payload TEXT NOT NULL,
|
|
534
|
-
created_at TEXT NOT NULL,
|
|
535
|
-
completed_at TEXT,
|
|
536
|
-
last_used_at TEXT NOT NULL
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
CREATE INDEX IF NOT EXISTS idx_gate_artifacts_project_commit
|
|
540
|
-
ON gate_artifacts(project_id, commit_sha);
|
|
541
|
-
|
|
542
|
-
CREATE INDEX IF NOT EXISTS idx_gate_artifacts_project_last_used
|
|
543
|
-
ON gate_artifacts(project_id, last_used_at);
|
|
544
|
-
|
|
545
|
-
CREATE TABLE IF NOT EXISTS gate_artifact_logs (
|
|
546
|
-
artifact_key TEXT NOT NULL,
|
|
547
|
-
step_name TEXT NOT NULL,
|
|
548
|
-
log_content BLOB NOT NULL,
|
|
549
|
-
log_digest TEXT NOT NULL,
|
|
550
|
-
log_bytes INTEGER NOT NULL,
|
|
551
|
-
PRIMARY KEY (artifact_key, step_name),
|
|
552
|
-
FOREIGN KEY (artifact_key) REFERENCES gate_artifacts(key) ON DELETE CASCADE
|
|
553
|
-
);
|
|
554
|
-
`;
|
|
555
|
-
/** Bounded current-Session projection for runtime cleanup. */
|
|
556
|
-
const BASELINE_RUNTIME_SESSION_SQL = `
|
|
557
|
-
CREATE TABLE IF NOT EXISTS runtime_session_candidates (
|
|
558
|
-
scope TEXT NOT NULL CHECK (scope IN ('task','global')),
|
|
559
|
-
task_id TEXT NOT NULL,
|
|
560
|
-
role_name TEXT NOT NULL,
|
|
561
|
-
agent_id TEXT NOT NULL,
|
|
562
|
-
adapter_id TEXT NOT NULL,
|
|
563
|
-
native_session_id TEXT NOT NULL,
|
|
564
|
-
launch_id TEXT,
|
|
565
|
-
session_updated_at TEXT NOT NULL,
|
|
566
|
-
cleanup_required INTEGER NOT NULL CHECK (cleanup_required IN (0,1)),
|
|
567
|
-
PRIMARY KEY (scope, task_id, role_name),
|
|
568
|
-
CHECK (
|
|
569
|
-
(scope = 'task' AND length(task_id) > 0)
|
|
570
|
-
OR (scope = 'global' AND task_id = '')
|
|
571
|
-
),
|
|
572
|
-
CHECK (
|
|
573
|
-
cleanup_required = CASE WHEN launch_id IS NOT NULL THEN 1 ELSE 0 END
|
|
574
|
-
)
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
CREATE INDEX IF NOT EXISTS idx_runtime_session_cleanup_required
|
|
578
|
-
ON runtime_session_candidates(scope, task_id, role_name)
|
|
579
|
-
WHERE cleanup_required = 1;
|
|
580
|
-
`;
|
|
581
|
-
/**
|
|
582
|
-
* External publication evidence. Records are immutable;
|
|
583
|
-
* a corrected MR/PR state appends a superseding record with the same
|
|
584
|
-
* external_key, so only the unsuperseded root is globally unique.
|
|
585
|
-
*/
|
|
586
|
-
const BASELINE_PUBLICATION_REFERENCE_SQL = `
|
|
587
|
-
CREATE TABLE IF NOT EXISTS publication_references (
|
|
588
|
-
task_id TEXT NOT NULL,
|
|
589
|
-
publication_id TEXT NOT NULL,
|
|
590
|
-
project_id TEXT NOT NULL,
|
|
591
|
-
provider TEXT NOT NULL,
|
|
592
|
-
repository TEXT NOT NULL,
|
|
593
|
-
external_kind TEXT NOT NULL,
|
|
594
|
-
external_id TEXT NOT NULL,
|
|
595
|
-
external_key TEXT NOT NULL,
|
|
596
|
-
state TEXT NOT NULL,
|
|
597
|
-
verification TEXT NOT NULL,
|
|
598
|
-
external_url TEXT,
|
|
599
|
-
title TEXT,
|
|
600
|
-
source_branch TEXT,
|
|
601
|
-
target_branch TEXT,
|
|
602
|
-
local_commit TEXT,
|
|
603
|
-
remote_commit TEXT,
|
|
604
|
-
supersedes TEXT,
|
|
605
|
-
payload TEXT NOT NULL,
|
|
606
|
-
merged_at TEXT,
|
|
607
|
-
created_at TEXT NOT NULL,
|
|
608
|
-
PRIMARY KEY (task_id, publication_id)
|
|
609
|
-
);
|
|
610
|
-
CREATE INDEX IF NOT EXISTS idx_publication_references_task
|
|
611
|
-
ON publication_references(task_id);
|
|
612
|
-
CREATE INDEX IF NOT EXISTS idx_publication_references_external
|
|
613
|
-
ON publication_references(external_key);
|
|
614
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_publication_references_external_root
|
|
615
|
-
ON publication_references(external_key) WHERE supersedes IS NULL;
|
|
616
|
-
`;
|
|
617
|
-
const BASELINE_TASK_WAKE_SQL = `
|
|
618
|
-
-- Durable Leader wake ledger (Issue 04 long-term design). A wake is a
|
|
619
|
-
-- notification envelope, not a context dump: the record holds the aggregated
|
|
620
|
-
-- reason tags and the delta window; the Agent reads delta content on demand.
|
|
621
|
-
CREATE TABLE IF NOT EXISTS task_wakes (
|
|
622
|
-
task_id TEXT NOT NULL,
|
|
623
|
-
wake_id TEXT NOT NULL,
|
|
624
|
-
seq INTEGER NOT NULL,
|
|
625
|
-
status TEXT NOT NULL CHECK (status IN ('dispatched','consumed')),
|
|
626
|
-
turn_id TEXT,
|
|
627
|
-
from_cursor TEXT NOT NULL,
|
|
628
|
-
to_cursor TEXT NOT NULL,
|
|
629
|
-
reasons TEXT NOT NULL,
|
|
630
|
-
payload TEXT NOT NULL,
|
|
631
|
-
created_at TEXT NOT NULL,
|
|
632
|
-
consumed_at TEXT,
|
|
633
|
-
PRIMARY KEY (task_id, wake_id)
|
|
634
|
-
);
|
|
635
|
-
CREATE INDEX IF NOT EXISTS idx_task_wakes_seq ON task_wakes(task_id, seq);
|
|
636
|
-
`;
|
|
637
|
-
/** Immutable, Task-scoped ContextSnapshot records. */
|
|
638
|
-
const BASELINE_CONTEXT_SNAPSHOT_SQL = `
|
|
639
|
-
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
640
|
-
task_id TEXT NOT NULL,
|
|
641
|
-
snapshot_id TEXT NOT NULL,
|
|
642
|
-
scope TEXT NOT NULL CHECK (scope IN ('task','workitem','stage')),
|
|
643
|
-
scope_ref TEXT,
|
|
644
|
-
sequence INTEGER NOT NULL CHECK (sequence > 0),
|
|
645
|
-
digest TEXT NOT NULL CHECK (length(digest) = 64),
|
|
646
|
-
payload TEXT NOT NULL,
|
|
647
|
-
frozen_at TEXT NOT NULL,
|
|
648
|
-
PRIMARY KEY (task_id, snapshot_id),
|
|
649
|
-
FOREIGN KEY (task_id) REFERENCES tasks_catalog(task_id) ON DELETE CASCADE,
|
|
650
|
-
CHECK ((scope = 'task' AND scope_ref IS NULL) OR (scope <> 'task' AND scope_ref IS NOT NULL))
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_context_snapshots_scope_sequence
|
|
654
|
-
ON context_snapshots(task_id, scope, COALESCE(scope_ref, ''), sequence);
|
|
655
|
-
`;
|
|
656
|
-
/**
|
|
657
|
-
* The Global-owned Message store (decision-3 §9/§11). It extends the Message
|
|
658
|
-
* store to a Global owner exactly as `global_role_session_sets` parallels
|
|
659
|
-
* `role_session_sets`: keyed by the Global Role `name` with an FK to
|
|
660
|
-
* `global_roles`, never by a fabricated `task_id`, and never a new private
|
|
661
|
-
* queue. Ids are minted from the existing `global_sequences` counter, so a
|
|
662
|
-
* Global input has an explicit owner and an authorizable reference without
|
|
663
|
-
* reusing Task record shapes or Task permissions.
|
|
664
|
-
*/
|
|
665
|
-
const GLOBAL_ROLE_MESSAGE_SQL = `
|
|
666
|
-
CREATE TABLE IF NOT EXISTS global_role_messages (
|
|
667
|
-
name TEXT NOT NULL,
|
|
668
|
-
message_id TEXT NOT NULL,
|
|
669
|
-
seq INTEGER NOT NULL,
|
|
670
|
-
payload TEXT NOT NULL,
|
|
671
|
-
created_at TEXT NOT NULL,
|
|
672
|
-
PRIMARY KEY (name, message_id),
|
|
673
|
-
FOREIGN KEY (name) REFERENCES global_roles(name)
|
|
674
|
-
);
|
|
675
|
-
CREATE INDEX IF NOT EXISTS idx_global_role_messages_seq ON global_role_messages(name, seq);
|
|
676
|
-
`;
|
|
677
|
-
const MIGRATION_1_SQL = [
|
|
678
|
-
BASELINE_CORE_SQL,
|
|
679
|
-
BASELINE_JOB_AND_RELEASE_SQL,
|
|
680
|
-
BASELINE_JOB_CALLER_SQL,
|
|
681
|
-
BASELINE_TELEMETRY_AGGREGATE_SQL,
|
|
682
|
-
BASELINE_SESSION_OWNER_SQL,
|
|
683
|
-
BASELINE_RESOURCE_REGISTRY_SQL,
|
|
684
|
-
BASELINE_GATE_ARTIFACT_SQL,
|
|
685
|
-
BASELINE_RUNTIME_SESSION_SQL,
|
|
686
|
-
BASELINE_PUBLICATION_REFERENCE_SQL,
|
|
687
|
-
BASELINE_TASK_WAKE_SQL,
|
|
688
|
-
BASELINE_CONTEXT_SNAPSHOT_SQL
|
|
689
|
-
].join("\n");
|
|
690
|
-
/** Released migrations are append-only and must never be rewritten. */
|
|
691
|
-
const MIGRATIONS = Object.freeze([
|
|
692
|
-
{
|
|
693
|
-
version: 1,
|
|
694
|
-
name: "v0.15.0-baseline",
|
|
695
|
-
introducedIn: "0.15.0",
|
|
696
|
-
sql: MIGRATION_1_SQL
|
|
697
|
-
},
|
|
698
|
-
{
|
|
699
|
-
version: 2,
|
|
700
|
-
name: "job-operation-facts",
|
|
701
|
-
introducedIn: "0.15.2",
|
|
702
|
-
// Historical records never carried caller identity or external effect
|
|
703
|
-
// evidence. Preserve that uncertainty rather than inventing attribution.
|
|
704
|
-
// No old executable implementation or runtime dual-read is needed.
|
|
705
|
-
sql: `
|
|
706
|
-
UPDATE durable_jobs SET payload = json_set(payload,
|
|
707
|
-
'$.schemaVersion', 2,
|
|
708
|
-
'$.operation', json_object(
|
|
709
|
-
'requestId', json_extract(payload, '$.idempotencyKey'),
|
|
710
|
-
'inputDigest', json_extract(payload, '$.idempotencyKey'),
|
|
711
|
-
'actorId', 'historical:unrecorded',
|
|
712
|
-
'authorityRef', 'historical:unrecorded',
|
|
713
|
-
'targetId', json_extract(payload, '$.workspace'),
|
|
714
|
-
'capability', 'job.start',
|
|
715
|
-
'implementation', json_object('id', 'yui:job-runner', 'generation', '1'),
|
|
716
|
-
'effect', 'possible',
|
|
717
|
-
'receiptRefs', json('[]'),
|
|
718
|
-
'partialResultRefs', json('[]')
|
|
719
|
-
)
|
|
720
|
-
);
|
|
721
|
-
CREATE UNIQUE INDEX idx_durable_jobs_request
|
|
722
|
-
ON durable_jobs(task_id, json_extract(payload, '$.operation.actorId'),
|
|
723
|
-
json_extract(payload, '$.operation.requestId'));
|
|
724
|
-
`
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
version: 3,
|
|
728
|
-
name: "exact-attempt-result-identity",
|
|
729
|
-
introducedIn: "0.15.5",
|
|
730
|
-
// Append after the released T01 migration without changing its checksum.
|
|
731
|
-
// Accepted inputs/results can use an exact attempt without a native Turn id.
|
|
732
|
-
// Preserve all valid historical records; never repair failed Turns or logs.
|
|
733
|
-
sql: "SELECT 1; -- exact attempt identity without a fabricated native Turn id"
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
version: 4,
|
|
737
|
-
name: "session-and-process-identity",
|
|
738
|
-
introducedIn: "0.15.8",
|
|
739
|
-
sql: REMOVE_RUNTIME_GENERATION_SQL,
|
|
740
|
-
migrateData: removeRuntimeGenerationRecords
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
version: 5,
|
|
744
|
-
name: "project-resource-artifacts",
|
|
745
|
-
introducedIn: "0.15.8",
|
|
746
|
-
sql: `
|
|
747
|
-
CREATE TABLE artifacts (
|
|
748
|
-
task_id TEXT NOT NULL REFERENCES tasks_catalog(task_id),
|
|
749
|
-
id TEXT NOT NULL,
|
|
750
|
-
payload TEXT NOT NULL,
|
|
751
|
-
PRIMARY KEY (task_id, id)
|
|
752
|
-
);
|
|
753
|
-
CREATE TABLE local_resources (
|
|
754
|
-
id TEXT PRIMARY KEY,
|
|
755
|
-
canonical_identity TEXT NOT NULL UNIQUE,
|
|
756
|
-
payload TEXT NOT NULL
|
|
757
|
-
);
|
|
758
|
-
CREATE TABLE environment_preparations (
|
|
759
|
-
task_id TEXT NOT NULL REFERENCES tasks_catalog(task_id),
|
|
760
|
-
id TEXT NOT NULL,
|
|
761
|
-
payload TEXT NOT NULL,
|
|
762
|
-
PRIMARY KEY (task_id, id)
|
|
763
|
-
);
|
|
764
|
-
UPDATE projects SET payload = json_set(payload,
|
|
765
|
-
'$.schemaVersion', 6, '$.resourceRefs', json('[]'),
|
|
766
|
-
'$.defaultCapabilityProviders', json('{}'));
|
|
767
|
-
`
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
version: 6,
|
|
771
|
-
name: "session-endpoint-implementation",
|
|
772
|
-
introducedIn: "0.15.8",
|
|
773
|
-
// Valid earlier Sessions used these two built-in protocols. Generation 1
|
|
774
|
-
// retains those codecs and identities behind the new execution boundary.
|
|
775
|
-
// Preserve native IDs, effective snapshots and all historical results.
|
|
776
|
-
sql: `
|
|
777
|
-
UPDATE role_session_sets SET payload = json_set(payload, '$.sessions', json((
|
|
778
|
-
SELECT json_group_object(key, json_set(value, '$.schemaVersion', 6,
|
|
779
|
-
'$.endpointImplementation', json_object(
|
|
780
|
-
'id', 'yui.agent-endpoint.' || json_extract(value, '$.adapterId'), 'generation', '1')))
|
|
781
|
-
FROM json_each(payload, '$.sessions')
|
|
782
|
-
)));
|
|
783
|
-
UPDATE role_session_sets SET payload = json_set(payload, '$.history', json((
|
|
784
|
-
SELECT json_group_array(json_set(value, '$.schemaVersion', 6,
|
|
785
|
-
'$.endpointImplementation', json_object(
|
|
786
|
-
'id', 'yui.agent-endpoint.' || json_extract(value, '$.adapterId'), 'generation', '1')))
|
|
787
|
-
FROM json_each(payload, '$.history')
|
|
788
|
-
))) WHERE json_type(payload, '$.history') = 'array';
|
|
789
|
-
UPDATE global_role_session_sets SET payload = json_set(payload, '$.sessions', json((
|
|
790
|
-
SELECT json_group_object(key, json_set(value, '$.schemaVersion', 6,
|
|
791
|
-
'$.endpointImplementation', json_object(
|
|
792
|
-
'id', 'yui.agent-endpoint.' || json_extract(value, '$.adapterId'), 'generation', '1')))
|
|
793
|
-
FROM json_each(payload, '$.sessions')
|
|
794
|
-
)));
|
|
795
|
-
UPDATE global_role_session_sets SET payload = json_set(payload, '$.history', json((
|
|
796
|
-
SELECT json_group_object(key, json_set(value, '$.schemaVersion', 6,
|
|
797
|
-
'$.endpointImplementation', json_object(
|
|
798
|
-
'id', 'yui.agent-endpoint.' || json_extract(value, '$.adapterId'), 'generation', '1')))
|
|
799
|
-
FROM json_each(payload, '$.history')
|
|
800
|
-
))) WHERE json_type(payload, '$.history') = 'object';
|
|
801
|
-
`
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
version: 7,
|
|
805
|
-
name: "task-facts-and-explicit-acceptance",
|
|
806
|
-
introducedIn: "0.15.8",
|
|
807
|
-
// Retain the complete retirement metadata/events and diagnostic facts.
|
|
808
|
-
// Only the public lifecycle changes; ordinary stores read one shape.
|
|
809
|
-
sql: `
|
|
810
|
-
UPDATE task_records SET payload = json_set(payload, '$.status', 'cancelled')
|
|
811
|
-
WHERE json_extract(payload, '$.status') = 'retired';
|
|
812
|
-
UPDATE task_records SET payload = json_set(payload, '$.retirementIsolation', json('true'))
|
|
813
|
-
WHERE json_extract(payload, '$.retiredAt') IS NOT NULL;
|
|
814
|
-
UPDATE tasks_catalog SET status = 'cancelled' WHERE status = 'retired';
|
|
815
|
-
UPDATE tasks_catalog SET lifecycle = 'cancelled' WHERE lifecycle = 'retired';
|
|
816
|
-
UPDATE task_records SET brief = json_set(brief, '$.revision', 1) WHERE brief IS NOT NULL;
|
|
817
|
-
UPDATE work_items SET payload = json_set(payload,
|
|
818
|
-
'$.historicalState', json_patch(json('{}'), json_object(
|
|
819
|
-
'status', json_extract(payload, '$.status'),
|
|
820
|
-
'outcome', json_extract(payload, '$.outcome'),
|
|
821
|
-
'endedAt', json_extract(payload, '$.endedAt'))),
|
|
822
|
-
'$.status', CASE status WHEN 'completed' THEN 'accepted' WHEN 'retired' THEN 'retired' ELSE 'open' END);
|
|
823
|
-
UPDATE work_items SET payload = json_remove(payload, '$.outcome', '$.endedAt')
|
|
824
|
-
WHERE json_extract(payload, '$.status') = 'open';
|
|
825
|
-
UPDATE work_items SET status = json_extract(payload, '$.status');
|
|
826
|
-
UPDATE work_items SET payload = json_set(payload, '$.currentCandidateId',
|
|
827
|
-
json_extract(payload, '$.candidates[#-1].id'))
|
|
828
|
-
WHERE json_extract(payload, '$.historicalState.status') = 'awaiting_acceptance';
|
|
829
|
-
UPDATE work_items SET payload = json_set(payload, '$.acceptedCandidateId',
|
|
830
|
-
json_extract(payload, '$.candidates[#-1].id'))
|
|
831
|
-
WHERE status = 'accepted' AND json_array_length(payload, '$.candidates') > 0;
|
|
832
|
-
`
|
|
833
|
-
},
|
|
834
|
-
{
|
|
835
|
-
version: 8,
|
|
836
|
-
name: "event-owned-edit-history",
|
|
837
|
-
introducedIn: "0.15.8",
|
|
838
|
-
// Move embedded histories to one immutable import event per Task.
|
|
839
|
-
// Existing events stay byte-identical; imports supply otherwise missing
|
|
840
|
-
// historical fields and are evidence only, never executable lifecycle input.
|
|
841
|
-
// Candidates may now carry optional fixed Artifact refs; absent refs remain
|
|
842
|
-
// valid for historical Candidates, so this payload addition needs no backfill.
|
|
843
|
-
sql: `
|
|
844
|
-
CREATE TEMP TABLE migrated_edit_history AS
|
|
845
|
-
SELECT records.task_id,
|
|
846
|
-
COALESCE(sequences.high_water, 0) + 1 AS sequence,
|
|
847
|
-
strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS imported_at,
|
|
848
|
-
json_object(
|
|
849
|
-
'sourceStorageVersion', '7',
|
|
850
|
-
'taskOutcomes', '' || COALESCE(json_extract(records.payload, '$.outcomeHistory'), '[]'),
|
|
851
|
-
'workAcceptances', '' || COALESCE((
|
|
852
|
-
SELECT json_group_array(json_object(
|
|
853
|
-
'workItemId', work_item_id,
|
|
854
|
-
'history', json_extract(payload, '$.acceptanceHistory')))
|
|
855
|
-
FROM (SELECT * FROM work_items
|
|
856
|
-
WHERE task_id = records.task_id
|
|
857
|
-
AND json_array_length(payload, '$.acceptanceHistory') > 0
|
|
858
|
-
ORDER BY work_item_id)
|
|
859
|
-
), '[]')
|
|
860
|
-
) AS history
|
|
861
|
-
FROM task_records AS records
|
|
862
|
-
LEFT JOIN id_sequences AS sequences
|
|
863
|
-
ON sequences.task_id = records.task_id AND sequences.kind = 'event'
|
|
864
|
-
WHERE json_array_length(records.payload, '$.outcomeHistory') > 0
|
|
865
|
-
OR EXISTS (SELECT 1 FROM work_items
|
|
866
|
-
WHERE task_id = records.task_id AND json_array_length(payload, '$.acceptanceHistory') > 0);
|
|
867
|
-
|
|
868
|
-
INSERT INTO events (task_id, event_id, type, occurred_at, payload)
|
|
869
|
-
SELECT task_id, 'event-' || sequence, 'history.imported', imported_at,
|
|
870
|
-
json_object('schemaVersion', 2, 'id', 'event-' || sequence,
|
|
871
|
-
'taskId', task_id, 'type', 'history.imported',
|
|
872
|
-
'createdAt', imported_at, 'payload', json(history))
|
|
873
|
-
FROM migrated_edit_history;
|
|
874
|
-
INSERT INTO id_sequences (task_id, kind, high_water)
|
|
875
|
-
SELECT task_id, 'event', sequence FROM migrated_edit_history WHERE true
|
|
876
|
-
ON CONFLICT(task_id, kind) DO UPDATE SET high_water = excluded.high_water;
|
|
877
|
-
DROP TABLE migrated_edit_history;
|
|
878
|
-
|
|
879
|
-
UPDATE task_records SET payload = json_remove(payload, '$.outcomeHistory'),
|
|
880
|
-
brief = CASE WHEN brief IS NULL THEN NULL ELSE json_remove(brief, '$.revision') END;
|
|
881
|
-
UPDATE work_items SET payload = json_remove(payload, '$.acceptanceHistory');
|
|
882
|
-
`
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
version: 9,
|
|
886
|
-
name: "adopted-agent-execution-environment",
|
|
887
|
-
introducedIn: "0.15.8",
|
|
888
|
-
// Optional Role selection and immutable Session/Turn effective snapshot.
|
|
889
|
-
// Existing records deliberately keep their managed-workspace execution;
|
|
890
|
-
// adopted preparations must never be inferred as an automatic binding.
|
|
891
|
-
sql: "SELECT 1; -- explicit adopted execution environment; absent means managed workspace"
|
|
892
|
-
},
|
|
893
|
-
{
|
|
894
|
-
version: 10,
|
|
895
|
-
name: "plugin-validation-evidence",
|
|
896
|
-
introducedIn: "0.15.8",
|
|
897
|
-
sql: `
|
|
898
|
-
CREATE TABLE plugin_validations (
|
|
899
|
-
task_id TEXT NOT NULL REFERENCES tasks_catalog(task_id),
|
|
900
|
-
id TEXT NOT NULL,
|
|
901
|
-
payload TEXT NOT NULL,
|
|
902
|
-
PRIMARY KEY (task_id, id)
|
|
903
|
-
);
|
|
904
|
-
`
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
version: 11,
|
|
908
|
-
name: "plugin-enable-intent",
|
|
909
|
-
introducedIn: "0.15.8",
|
|
910
|
-
// A v10 validation proves no enable/disable choice. Preserve it unchanged;
|
|
911
|
-
// never infer desired configuration from historical reports or processes.
|
|
912
|
-
sql: `
|
|
913
|
-
CREATE TABLE plugin_intents (
|
|
914
|
-
task_id TEXT NOT NULL REFERENCES tasks_catalog(task_id),
|
|
915
|
-
plugin_id TEXT NOT NULL,
|
|
916
|
-
payload TEXT NOT NULL,
|
|
917
|
-
PRIMARY KEY (task_id, plugin_id)
|
|
918
|
-
);
|
|
919
|
-
`
|
|
920
|
-
},
|
|
921
|
-
{
|
|
922
|
-
// Renumbered from 10 to 12 when this branch adopted upstream master: PR320
|
|
923
|
-
// took 10 and 11 while this work was in review. Both are already merged, so
|
|
924
|
-
// they own those numbers; this migration appends after them rather than
|
|
925
|
-
// competing for a number, and no released entry is touched.
|
|
926
|
-
version: 12,
|
|
927
|
-
name: "draft-planning-and-deferred-activation",
|
|
928
|
-
// Same unreleased release as migrations 4-11: this does not bump the version.
|
|
929
|
-
introducedIn: "0.15.8",
|
|
930
|
-
// One current-version migration for everything this change adds. No existing
|
|
931
|
-
// record is rewritten and no payload is backfilled:
|
|
932
|
-
//
|
|
933
|
-
// - the optional Task activation request: absent means activation was
|
|
934
|
-
// never explicitly requested, so a Draft is never inferred to have
|
|
935
|
-
// requested it;
|
|
936
|
-
// - its bounded settled-request history: absent means no request ever
|
|
937
|
-
// reached a terminal disposition on this Task. This is a display
|
|
938
|
-
// projection, not the authority: whether an explicitly cancelled or
|
|
939
|
-
// adopted requestId may be replayed is decided from the durable
|
|
940
|
-
// activation event ledger (never compacted), so eviction from this
|
|
941
|
-
// bounded list never resurrects a decided outcome — per-request state
|
|
942
|
-
// inside the Task that owns it, not a second scheduling or operation
|
|
943
|
-
// ledger;
|
|
944
|
-
// - the planning Turn purpose: historical Turns keep their execution or
|
|
945
|
-
// review purpose untouched.
|
|
946
|
-
//
|
|
947
|
-
// The partial indexes bound Draft planning selection and pending-activation
|
|
948
|
-
// recovery to the few rows that qualify, so no execution phase scans Task
|
|
949
|
-
// history to find them. Creating an index adds no row and rewrites no
|
|
950
|
-
// payload.
|
|
951
|
-
sql: `
|
|
952
|
-
-- explicit activation request, its bounded settled history, and planning Turn
|
|
953
|
-
-- purpose; absent means activation was never requested
|
|
954
|
-
CREATE INDEX IF NOT EXISTS idx_turns_planning_active ON turns(task_id, turn_id)
|
|
955
|
-
WHERE status = 'active' AND json_extract(payload, '$.purpose') = 'planning';
|
|
956
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_activation_pending ON task_records(task_id)
|
|
957
|
-
WHERE json_extract(payload, '$.activationRequest.disposition') = 'pending';
|
|
958
|
-
`
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
version: 13,
|
|
962
|
-
name: "session-authority-and-execution-admission",
|
|
963
|
-
introducedIn: "0.15.9",
|
|
964
|
-
sql: "SELECT 1; -- AgentRun contract, notification admission and Session authority",
|
|
965
|
-
migrateData: migrateAgentRunContract
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
version: 14,
|
|
969
|
-
name: "acp-session-workspace-configuration",
|
|
970
|
-
introducedIn: "0.15.10",
|
|
971
|
-
// ACP is a new legal adapter/configuration value, including its optional
|
|
972
|
-
// additional workspace roots. The v13 baseline has no ACP bindings; existing
|
|
973
|
-
// Codex/Claude configuration and Session history remain valid unchanged.
|
|
974
|
-
// This widens the persistent contract without rewriting payloads or earlier
|
|
975
|
-
// migration checksums. Workspace delivery is negotiated at initialize, not
|
|
976
|
-
// stored as another configuration authority.
|
|
977
|
-
sql: "SELECT 1; -- ACP bindings may carry additionalDirectories; history stays valid"
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
version: 15,
|
|
981
|
-
name: "agent-execution-component",
|
|
982
|
-
introducedIn: "0.15.10",
|
|
983
|
-
// Which product executes is now its own recorded fact, separate from the
|
|
984
|
-
// connection plan that reaches it. The plan keeps its `adapterId` name and
|
|
985
|
-
// its meaning; only the product identity is new.
|
|
986
|
-
//
|
|
987
|
-
// The backfill is a total function of the stored plan, never of a command
|
|
988
|
-
// string. `codex` and `claude` each have exactly one component, so those
|
|
989
|
-
// rows gain their true value. Every `acp` row becomes `unknown-acp-agent`:
|
|
990
|
-
// a v14 Home cannot say which product answered, and an executable named
|
|
991
|
-
// `claude-agent-acp` is not evidence that it was the Claude Agent SDK. The
|
|
992
|
-
// unidentified value is the honest one and stays correctable by hand.
|
|
993
|
-
//
|
|
994
|
-
// Sessions keep their own recorded component, so a Session started before
|
|
995
|
-
// this migration continues against exactly the implementation it began on.
|
|
996
|
-
//
|
|
997
|
-
// Every persisted effective snapshot must be reached, not only the ones on
|
|
998
|
-
// Session sets: `validateEffectiveLaunchSnapshot` demands schemaVersion 4,
|
|
999
|
-
// and the upgrade verifier replays it over Runs, WorkItem ExecutionLanes
|
|
1000
|
-
// and ReviewRound ExecutionLanes too. A Home holding any of those would
|
|
1001
|
-
// otherwise fail the upgrade and roll back. The list below is the
|
|
1002
|
-
// verifier's own reachable set, not a scan for fields that look similar.
|
|
1003
|
-
sql: `
|
|
1004
|
-
UPDATE configured_agents SET payload = json_set(payload,
|
|
1005
|
-
'$.schemaVersion', 3,
|
|
1006
|
-
'$.component', CASE json_extract(payload, '$.adapterId')
|
|
1007
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1008
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1009
|
-
ELSE 'unknown-acp-agent' END)
|
|
1010
|
-
WHERE json_extract(payload, '$.component') IS NULL;
|
|
1011
|
-
|
|
1012
|
-
UPDATE global_roles SET payload = json_set(payload, '$.agentBindings', json((
|
|
1013
|
-
SELECT json_group_object(key, json_set(value, '$.component',
|
|
1014
|
-
CASE json_extract(value, '$.adapterId')
|
|
1015
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1016
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1017
|
-
ELSE 'unknown-acp-agent' END))
|
|
1018
|
-
FROM json_each(payload, '$.agentBindings')
|
|
1019
|
-
))) WHERE json_type(payload, '$.agentBindings') = 'object';
|
|
1020
|
-
|
|
1021
|
-
UPDATE task_roles SET payload = json_set(payload, '$.agentBindings', json((
|
|
1022
|
-
SELECT json_group_object(key, json_set(value, '$.component',
|
|
1023
|
-
CASE json_extract(value, '$.adapterId')
|
|
1024
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1025
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1026
|
-
ELSE 'unknown-acp-agent' END))
|
|
1027
|
-
FROM json_each(payload, '$.agentBindings')
|
|
1028
|
-
))) WHERE json_type(payload, '$.agentBindings') = 'object';
|
|
1029
|
-
|
|
1030
|
-
UPDATE global_role_session_sets SET payload = json_set(payload, '$.sessions', json((
|
|
1031
|
-
SELECT json_group_object(key, json_set(value, '$.effective.schemaVersion', 4,
|
|
1032
|
-
'$.effective.component', CASE json_extract(value, '$.effective.adapterId')
|
|
1033
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1034
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1035
|
-
ELSE 'unknown-acp-agent' END))
|
|
1036
|
-
FROM json_each(payload, '$.sessions')
|
|
1037
|
-
))) WHERE json_type(payload, '$.sessions') = 'object';
|
|
1038
|
-
UPDATE global_role_session_sets SET payload = json_set(payload, '$.history', json((
|
|
1039
|
-
SELECT json_group_object(key, json_set(value, '$.effective.schemaVersion', 4,
|
|
1040
|
-
'$.effective.component', CASE json_extract(value, '$.effective.adapterId')
|
|
1041
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1042
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1043
|
-
ELSE 'unknown-acp-agent' END))
|
|
1044
|
-
FROM json_each(payload, '$.history')
|
|
1045
|
-
))) WHERE json_type(payload, '$.history') = 'object';
|
|
1046
|
-
|
|
1047
|
-
UPDATE role_session_sets SET payload = json_set(payload, '$.sessions', json((
|
|
1048
|
-
SELECT json_group_object(key, json_set(value, '$.effective.schemaVersion', 4,
|
|
1049
|
-
'$.effective.component', CASE json_extract(value, '$.effective.adapterId')
|
|
1050
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1051
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1052
|
-
ELSE 'unknown-acp-agent' END))
|
|
1053
|
-
FROM json_each(payload, '$.sessions')
|
|
1054
|
-
))) WHERE json_type(payload, '$.sessions') = 'object';
|
|
1055
|
-
UPDATE role_session_sets SET payload = json_set(payload, '$.history', json((
|
|
1056
|
-
SELECT json_group_array(json_set(value, '$.effective.schemaVersion', 4,
|
|
1057
|
-
'$.effective.component', CASE json_extract(value, '$.effective.adapterId')
|
|
1058
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1059
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1060
|
-
ELSE 'unknown-acp-agent' END))
|
|
1061
|
-
FROM json_each(payload, '$.history')
|
|
1062
|
-
))) WHERE json_type(payload, '$.history') = 'array';
|
|
1063
|
-
|
|
1064
|
-
-- A Turn records the launch it actually ran on, one snapshot per row.
|
|
1065
|
-
UPDATE turns SET payload = json_set(payload,
|
|
1066
|
-
'$.effective.schemaVersion', 4,
|
|
1067
|
-
'$.effective.component', CASE json_extract(payload, '$.effective.adapterId')
|
|
1068
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1069
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1070
|
-
ELSE 'unknown-acp-agent' END)
|
|
1071
|
-
WHERE json_type(payload, '$.effective') = 'object';
|
|
1072
|
-
|
|
1073
|
-
-- ExecutionLane launch facts are frozen per Lane, inside an array of Groups
|
|
1074
|
-
-- that each hold an array of Lanes. A Lane that was never dispatched has no
|
|
1075
|
-
-- effective at all, and must keep that absence: json_set would otherwise
|
|
1076
|
-
-- create a partial snapshot the Lane validator rejects as incomplete.
|
|
1077
|
-
UPDATE work_items SET payload = json_set(payload, '$.executionGroups', json((
|
|
1078
|
-
SELECT json_group_array(json_set(grp.value, '$.lanes', json((
|
|
1079
|
-
SELECT json_group_array(CASE
|
|
1080
|
-
WHEN json_type(lane.value, '$.effective') = 'object'
|
|
1081
|
-
THEN json_set(lane.value, '$.effective.schemaVersion', 4,
|
|
1082
|
-
'$.effective.component', CASE json_extract(lane.value, '$.effective.adapterId')
|
|
1083
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1084
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1085
|
-
ELSE 'unknown-acp-agent' END)
|
|
1086
|
-
ELSE lane.value END)
|
|
1087
|
-
FROM json_each(grp.value, '$.lanes') AS lane
|
|
1088
|
-
))))
|
|
1089
|
-
FROM json_each(payload, '$.executionGroups') AS grp
|
|
1090
|
-
))) WHERE json_type(payload, '$.executionGroups') = 'array';
|
|
1091
|
-
|
|
1092
|
-
-- A ReviewRound holds at most one Group, so only the Lane array nests here.
|
|
1093
|
-
UPDATE review_rounds SET payload = json_set(payload, '$.executionGroup.lanes', json((
|
|
1094
|
-
SELECT json_group_array(CASE
|
|
1095
|
-
WHEN json_type(lane.value, '$.effective') = 'object'
|
|
1096
|
-
THEN json_set(lane.value, '$.effective.schemaVersion', 4,
|
|
1097
|
-
'$.effective.component', CASE json_extract(lane.value, '$.effective.adapterId')
|
|
1098
|
-
WHEN 'codex' THEN 'codex-cli'
|
|
1099
|
-
WHEN 'claude' THEN 'claude-code-cli'
|
|
1100
|
-
ELSE 'unknown-acp-agent' END)
|
|
1101
|
-
ELSE lane.value END)
|
|
1102
|
-
FROM json_each(payload, '$.executionGroup.lanes') AS lane
|
|
1103
|
-
))) WHERE json_type(payload, '$.executionGroup.lanes') = 'array';
|
|
1104
|
-
`
|
|
1105
|
-
},
|
|
1106
|
-
{
|
|
1107
|
-
version: 16,
|
|
1108
|
-
name: "acp-session-run-configuration",
|
|
1109
|
-
introducedIn: "0.15.10",
|
|
1110
|
-
// An ACP Role binding may now carry a model, a reasoning effort and a
|
|
1111
|
-
// permission strategy beyond `default`, because Yui's ACP client implements
|
|
1112
|
-
// `session/set_config_option` and pushes those values to the Session before
|
|
1113
|
-
// it prompts. Previously the adapter rejected all three, so no stored
|
|
1114
|
-
// payload can contain them.
|
|
1115
|
-
//
|
|
1116
|
-
// This widens the contract without rewriting anything, and the absence of a
|
|
1117
|
-
// payload update is the substantive decision rather than an omission. A v10
|
|
1118
|
-
// ACP binding holds `permission.strategy = "default"`, which keeps exactly
|
|
1119
|
-
// the meaning it always had: Yui sends no mode, so the Agent's own default
|
|
1120
|
-
// stands. Rewriting those rows to `bypass` — or to any newly expressible
|
|
1121
|
-
// value — would grant authority the user never chose, on Homes whose owner
|
|
1122
|
-
// did nothing but upgrade. `bypass` is reachable only by asking for it.
|
|
1123
|
-
//
|
|
1124
|
-
// Effective launch snapshots need no change either. They already carry
|
|
1125
|
-
// optional `model` and `effort` on the shared base, and their ACP permission
|
|
1126
|
-
// is the same object the adapter canonicalizes, so a historical snapshot
|
|
1127
|
-
// still validates unchanged at schemaVersion 4 across Sessions, Session
|
|
1128
|
-
// history, Turns, WorkItem ExecutionLanes and ReviewRound ExecutionLanes.
|
|
1129
|
-
// A frozen snapshot therefore keeps describing the launch it actually ran,
|
|
1130
|
-
// which is what makes replaying old history honest.
|
|
1131
|
-
sql: "SELECT 1; -- ACP bindings may carry model/effort and a chosen "
|
|
1132
|
-
+ "permission mode; existing `default` bindings keep their meaning"
|
|
1133
|
-
},
|
|
1134
|
-
{
|
|
1135
|
-
version: 17,
|
|
1136
|
-
name: "session-origin-input-requests",
|
|
1137
|
-
introducedIn: "0.15.8",
|
|
1138
|
-
// Widen requester provenance: notifications originate in a current Leader
|
|
1139
|
-
// Session without an AgentRun. Existing schemaVersion-3 requests keep their
|
|
1140
|
-
// exact Run/Session origin, including frozen historical Context values.
|
|
1141
|
-
// No guessed native identity or history rewrite is needed.
|
|
1142
|
-
sql: "SELECT 1; -- InputRequest requester.runId is optional when an exact nativeSessionId is present"
|
|
1143
|
-
},
|
|
1144
|
-
{
|
|
1145
|
-
version: 18,
|
|
1146
|
-
name: "replaceable-session-process-custody",
|
|
1147
|
-
introducedIn: "0.15.8",
|
|
1148
|
-
// Existing Session/Run history remains unchanged. Runtime mailboxes may
|
|
1149
|
-
// now retain explicit replacement intent; OS owner records may identify
|
|
1150
|
-
// the dedicated execution child, independently of a disposable Host.
|
|
1151
|
-
sql: "SELECT 1; -- Replacement intent, independent retained native control evidence/process custody, and optional fixed TaskWake refs"
|
|
1152
|
-
},
|
|
1153
|
-
{
|
|
1154
|
-
version: 19,
|
|
1155
|
-
name: "task-artifacts-local-git",
|
|
1156
|
-
introducedIn: "0.16.0",
|
|
1157
|
-
// Retire the DB-owned immutable Artifact store: file/directory artifacts now
|
|
1158
|
-
// live in a per-Task local Git repository, referenced by a self-certifying
|
|
1159
|
-
// `commit + relativePath`. The whole rewrite is payload work that must READ
|
|
1160
|
-
// the `artifacts` table before it is dropped, so it runs entirely in
|
|
1161
|
-
// `migrateData` (which executes after this `sql`) — the table is dropped
|
|
1162
|
-
// there, last, once its rows have been moved. Requirement A's independent,
|
|
1163
|
-
// deterministic submit-intent backfill (events + id_sequences only) is the
|
|
1164
|
-
// final step inside that same transaction. This `sql` is intentionally a
|
|
1165
|
-
// no-op: dropping the table here would destroy the rows before they move.
|
|
1166
|
-
//
|
|
1167
|
-
// The 18->19 contract also DECLARES three optional `messages.payload` fields
|
|
1168
|
-
// added by Requirement A — `intent` (record|discuss|develop), an optional
|
|
1169
|
-
// client idempotency `submissionKey`, and a frozen `submissionReceipt`
|
|
1170
|
-
// disposition. They add no column and no table and are only ever written
|
|
1171
|
-
// going forward (absent `intent` reads as discuss; absent key is keyless;
|
|
1172
|
-
// absent receipt is a pre-19 message), so NO historical row is rewritten and
|
|
1173
|
-
// no key or receipt is ever fabricated for old data — declaring them here
|
|
1174
|
-
// satisfies "optional still requires a migration declaration".
|
|
1175
|
-
sql: "SELECT 1; -- artifacts move to per-Task local Git; see migrateArtifactsToGit",
|
|
1176
|
-
migrateData: migrateArtifactsToGit
|
|
1177
|
-
},
|
|
1178
|
-
{
|
|
1179
|
-
version: 20,
|
|
1180
|
-
name: "integration-conflict-continuation",
|
|
1181
|
-
introducedIn: "0.16.0",
|
|
1182
|
-
// Widen Integration status with conflicted; declare optional sourceProgress
|
|
1183
|
-
// (Git cursor/reflog action) and checkInputDigest. Exact old bound FF Job
|
|
1184
|
-
// receipts supply these facts; unprovable history remains unchanged.
|
|
1185
|
-
// Frozen Context and events are never rewritten.
|
|
1186
|
-
sql: "SELECT 1; -- Integration Git progress and exact check admission",
|
|
1187
|
-
migrateData: migrateIntegrationContinuation
|
|
1188
|
-
},
|
|
1189
|
-
{
|
|
1190
|
-
version: 21,
|
|
1191
|
-
name: "force-archive-independent-cleanup",
|
|
1192
|
-
introducedIn: "0.16.0",
|
|
1193
|
-
// Declare optional task.archived force/cleanup/warnings/retainedResources
|
|
1194
|
-
// audit fields and task.archive-cleanup resource/status/detail/paths events;
|
|
1195
|
-
// runtime.event-obsolete may retain the full originalEvent as source evidence.
|
|
1196
|
-
// Archived Tasks may retain exact runtime/workspace/mailbox ownership.
|
|
1197
|
-
// Existing archive, completion and Publication history is unchanged.
|
|
1198
|
-
sql: "SELECT 1; -- archive intent and cleanup evidence remain separate facts"
|
|
1199
|
-
},
|
|
1200
|
-
{
|
|
1201
|
-
version: 22,
|
|
1202
|
-
name: "controller-owned-agent-host-ingress",
|
|
1203
|
-
introducedIn: "0.16.0",
|
|
1204
|
-
// Add optional `host` source metadata to the stable Inbox v1 observation
|
|
1205
|
-
// envelope. Such facts contain no resolved Run: current Controller alone
|
|
1206
|
-
// validates ownership and commits observations, process custody and native
|
|
1207
|
-
// account locations. Existing Inbox facts and domain history stay intact.
|
|
1208
|
-
// Host status capabilities/transport diagnostics are live, not stored.
|
|
1209
|
-
sql: "SELECT 1; -- Controller-owned Host ingress; no historical data rewrite"
|
|
1210
|
-
},
|
|
1211
|
-
{
|
|
1212
|
-
version: 23,
|
|
1213
|
-
name: "unified-message-input-control",
|
|
1214
|
-
introducedIn: "0.16.1",
|
|
1215
|
-
// Widen the Message payload with an optional durable input action
|
|
1216
|
-
// (queue/steer) plus its stable requestId, and an optional interrupt-then
|
|
1217
|
-
// handoff claim (requestId + exact target AgentRun). Existing messages carry
|
|
1218
|
-
// neither field and keep their current queue semantics and delivery history;
|
|
1219
|
-
// no native Turn identity is synthesized and no history is rewritten. The
|
|
1220
|
-
// same contiguous migration adds the Global-owned Message store
|
|
1221
|
-
// (global_role_messages), extending the Message store to a Global owner
|
|
1222
|
-
// exactly as global_role_session_sets parallels role_session_sets: an
|
|
1223
|
-
// explicit Global owner and authorizable reference, no fabricated Task and
|
|
1224
|
-
// no new private global queue (decision-3 §9/§11).
|
|
1225
|
-
sql: `SELECT 1; -- Message input actions, exact Session delivery pins, provider receipts and interrupt-then claims; Global Session provider bindings and native interrupt evidence\n${GLOBAL_ROLE_MESSAGE_SQL}`
|
|
1226
|
-
},
|
|
1227
|
-
{
|
|
1228
|
-
version: 24,
|
|
1229
|
-
name: "unify-home-layout",
|
|
1230
|
-
introducedIn: "0.15.9",
|
|
1231
|
-
// Unify every Yui self-managed path under a single canonical YUI_HOME. The
|
|
1232
|
-
// managed Git worktrees move from the out-of-Home `defaultWorkspace` into
|
|
1233
|
-
// `<home>/workspaces/{worktree,tasks}` and the provider runtimes from the
|
|
1234
|
-
// `<home>.task-runtimes` sibling into `<home>/runtime/task-runtimes`. Only
|
|
1235
|
-
// the durable Git worktree tree is physically relocated, by COPY (never
|
|
1236
|
-
// rename-away): its replica is digest-verified against the source and
|
|
1237
|
-
// atomically published while the original is PRESERVED as the rollback
|
|
1238
|
-
// anchor; the regenerable task views and disposable runtimes are pointer
|
|
1239
|
-
// rewrites only. The copied worktrees are then repaired, and only the
|
|
1240
|
-
// persisted pointers the runtime trusts as live are rewritten. Frozen
|
|
1241
|
-
// evidence (Context snapshots, terminal Run Git evidence, candidate
|
|
1242
|
-
// snapshots, terminal Jobs, reports) and self-healing/self-rediscovered
|
|
1243
|
-
// records (Task cwd, active Role workspaces, views, resource registry) are
|
|
1244
|
-
// left intact; a queued/running Job bound under a relocating root is refused
|
|
1245
|
-
// rather than silently moved. The data/filesystem transform is the
|
|
1246
|
-
// substantive step; the SQL is inert.
|
|
1247
|
-
sql: UNIFY_HOME_LAYOUT_SQL,
|
|
1248
|
-
migrateData: migrateUnifyHomeLayout
|
|
1249
|
-
},
|
|
1250
|
-
{
|
|
1251
|
-
version: 25,
|
|
1252
|
-
name: "collapse-worktree-layout",
|
|
1253
|
-
introducedIn: "0.15.9",
|
|
1254
|
-
// Collapse the two-layer managed workspace layout into a single layer of real
|
|
1255
|
-
// Git worktrees addressed by Task/owner and Project. Each managed worktree
|
|
1256
|
-
// moves from its per-Project physical root
|
|
1257
|
-
// (`<home>/workspaces/worktree/<projectName>/<taskKey>/<roleKey>`) to the
|
|
1258
|
-
// location the v19 symlink view used to point at
|
|
1259
|
-
// (`<home>/workspaces/tasks/<taskId>/<owner>/<projectDirectory>`), so the
|
|
1260
|
-
// logical entry path and the physical worktree become the same directory and
|
|
1261
|
-
// the symlink-view indirection is gone; an integration attempt moves under a
|
|
1262
|
-
// new `tasks/<taskId>/integrations/<id>/` owner root, addressed by the bound
|
|
1263
|
-
// Project directory rather than the Project name. Only the durable Git
|
|
1264
|
-
// worktrees are physically relocated, by COPY (never rename-away): each
|
|
1265
|
-
// replica is digest-verified against the source and atomically published over
|
|
1266
|
-
// the regenerable view symlink while the original is PRESERVED as the
|
|
1267
|
-
// rollback anchor. The copied worktrees are then repaired, and only the
|
|
1268
|
-
// persisted pointers the runtime trusts as live are rewritten. Frozen
|
|
1269
|
-
// evidence and self-healing records are left intact; a queued/running Job
|
|
1270
|
-
// bound under a relocating worktree is refused rather than silently moved.
|
|
1271
|
-
// The data/filesystem transform is the substantive step; the SQL is inert.
|
|
1272
|
-
sql: COLLAPSE_WORKTREE_LAYOUT_SQL,
|
|
1273
|
-
migrateData: migrateCollapseWorktreeLayout
|
|
1274
|
-
},
|
|
1275
|
-
{
|
|
1276
|
-
version: 26,
|
|
1277
|
-
name: "publication-candidate-adoption",
|
|
1278
|
-
introducedIn: "0.15.12",
|
|
1279
|
-
// Add optional Publication.headCommit (the observed PR/MR source head) and
|
|
1280
|
-
// publication.candidate-adopted events binding publicationId, completionEventId,
|
|
1281
|
-
// projectId, acceptedCommit/localCommit, acceptedTree/candidateTree, diffDigest,
|
|
1282
|
-
// acceptance and by, optionally integrationId/integrationDigest. Derived
|
|
1283
|
-
// task.archived mergeCoverage may also be uncovered or unverified.
|
|
1284
|
-
// No stored delivery index and no inferred adoption or verification:
|
|
1285
|
-
// valid old Publication, completion and archive payloads remain byte-identical.
|
|
1286
|
-
sql: "SELECT 1; -- fixed acceptance-to-publication evidence; preserve all historical facts"
|
|
1287
|
-
},
|
|
1288
|
-
{
|
|
1289
|
-
version: 27,
|
|
1290
|
-
name: "bounded-provider-input-retry",
|
|
1291
|
-
introducedIn: "0.16.0",
|
|
1292
|
-
// Optional owned input/recovery records on the existing Provider binding;
|
|
1293
|
-
// positive Driver transient evidence on StandardAgentError. No historical
|
|
1294
|
-
// failures are scanned, scheduled, reclassified or replayed by upgrading.
|
|
1295
|
-
sql: `
|
|
1296
|
-
-- ProviderTurn.input/retrySupported/failure; ProviderRuntimeBinding.retry/retryHistory/retryDisabled;
|
|
1297
|
-
-- optional error retryable and retryAfterMs facts. Index existing records only.
|
|
1298
|
-
CREATE INDEX idx_task_provider_retry ON role_session_sets(
|
|
1299
|
-
json_extract(payload, '$.providerBinding.retry.status')
|
|
1300
|
-
) WHERE json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight');
|
|
1301
|
-
CREATE INDEX idx_global_provider_retry ON global_role_session_sets(
|
|
1302
|
-
json_extract(payload, '$.providerBinding.retry.status')
|
|
1303
|
-
) WHERE json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight');
|
|
1304
|
-
`
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
version: 28,
|
|
1308
|
-
name: "current-contract-only-dispatch",
|
|
1309
|
-
introducedIn: "0.16.0",
|
|
1310
|
-
// Freeze the one valid earlier singleton dispatch shape here, not in the
|
|
1311
|
-
// normal runtime. Preserve the full input batch and sequence; only replace
|
|
1312
|
-
// its transport dedupe identity. Merged/ambiguous batches, processing
|
|
1313
|
-
// claims, original Messages, results and external-effect evidence remain
|
|
1314
|
-
// untouched. This step does not accept, replay or complete any execution.
|
|
1315
|
-
sql: `
|
|
1316
|
-
UPDATE mailboxes
|
|
1317
|
-
SET pending = json_set(pending,
|
|
1318
|
-
'$.sources', json_array('turn-dispatch'),
|
|
1319
|
-
'$.dedupeKeys', json_array('role-turn:' || task_id || ':' || role_name || ':' ||
|
|
1320
|
-
(SELECT json_extract(value, '$.id') FROM json_each(mailboxes.pending, '$.refs')
|
|
1321
|
-
WHERE json_extract(value, '$.type') = 'run'
|
|
1322
|
-
AND json_extract(value, '$.taskId') = mailboxes.task_id)))
|
|
1323
|
-
WHERE target_kind = 'role' AND role_name <> 'leader' AND pending IS NOT NULL
|
|
1324
|
-
AND json_extract(pending, '$.requestCount') = 1
|
|
1325
|
-
AND json_array_length(pending, '$.sources') = 1
|
|
1326
|
-
AND json_extract(pending, '$.sources[0]') = 'yui'
|
|
1327
|
-
AND EXISTS (SELECT 1 FROM json_each(mailboxes.pending, '$.reasons')
|
|
1328
|
-
WHERE value IN ('turn-dispatched', 'turn-retried', 'review-requested',
|
|
1329
|
-
'workitem-synthesis-ready', 'review-synthesis-ready'))
|
|
1330
|
-
AND (SELECT count(*) FROM json_each(mailboxes.pending, '$.refs')
|
|
1331
|
-
WHERE json_extract(value, '$.type') = 'run') = 1
|
|
1332
|
-
AND EXISTS (SELECT 1 FROM json_each(mailboxes.pending, '$.refs')
|
|
1333
|
-
WHERE json_extract(value, '$.type') = 'run'
|
|
1334
|
-
AND json_extract(value, '$.taskId') = mailboxes.task_id);
|
|
1335
|
-
`
|
|
1336
|
-
},
|
|
1337
|
-
{
|
|
1338
|
-
version: 29,
|
|
1339
|
-
name: "agent-ordered-atomic-integration",
|
|
1340
|
-
introducedIn: "0.16.0",
|
|
1341
|
-
// Retire the scheduler, not its user's intent. Freeze each complete queue
|
|
1342
|
-
// payload in ordinary Task history before dropping the active table.
|
|
1343
|
-
// Existing Integrations, Jobs and WorkItem delivery evidence remain exact;
|
|
1344
|
-
// no entry is interpreted as committed or automatically replayed.
|
|
1345
|
-
sql: `
|
|
1346
|
-
CREATE TEMP TABLE retired_integration_queue AS
|
|
1347
|
-
SELECT q.*,
|
|
1348
|
-
strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS retired_at,
|
|
1349
|
-
max(
|
|
1350
|
-
coalesce((SELECT high_water FROM id_sequences
|
|
1351
|
-
WHERE task_id = q.task_id AND kind = 'event'), 0),
|
|
1352
|
-
coalesce((SELECT max(CAST(substr(event_id, 7) AS INTEGER)) FROM events
|
|
1353
|
-
WHERE task_id = q.task_id), 0)
|
|
1354
|
-
) + row_number() OVER (
|
|
1355
|
-
PARTITION BY q.task_id ORDER BY q.created_at,
|
|
1356
|
-
CAST(substr(q.queue_id, length('integration-queue-') + 1) AS INTEGER)
|
|
1357
|
-
) AS event_sequence
|
|
1358
|
-
FROM integration_queue AS q;
|
|
1359
|
-
|
|
1360
|
-
INSERT INTO events(task_id, event_id, type, occurred_at, payload)
|
|
1361
|
-
SELECT task_id, 'event-' || event_sequence, 'integration.queue-retired', retired_at,
|
|
1362
|
-
json_object(
|
|
1363
|
-
'schemaVersion', 2, 'id', 'event-' || event_sequence, 'taskId', task_id,
|
|
1364
|
-
'type', 'integration.queue-retired', 'createdAt', retired_at,
|
|
1365
|
-
'payload', json_object('queueId', queue_id, 'record', payload,
|
|
1366
|
-
'disposition', 'retired-without-replay')
|
|
1367
|
-
)
|
|
1368
|
-
FROM retired_integration_queue;
|
|
1369
|
-
|
|
1370
|
-
INSERT INTO id_sequences(task_id, kind, high_water)
|
|
1371
|
-
SELECT task_id, 'event', max(event_sequence)
|
|
1372
|
-
FROM retired_integration_queue GROUP BY task_id
|
|
1373
|
-
ON CONFLICT(task_id, kind) DO UPDATE SET high_water = excluded.high_water;
|
|
1374
|
-
|
|
1375
|
-
DROP TABLE retired_integration_queue;
|
|
1376
|
-
DROP TABLE integration_queue;
|
|
1377
|
-
DELETE FROM id_sequences WHERE kind = 'integrationQueue';
|
|
1378
|
-
`
|
|
1379
|
-
},
|
|
1380
|
-
{
|
|
1381
|
-
version: 30,
|
|
1382
|
-
name: "notification-only-wakes",
|
|
1383
|
-
introducedIn: "0.16.0",
|
|
1384
|
-
sql: "SELECT 1; -- Retire Run-linked wakes as audit events; notification schema 2; explicit Global providerBinding",
|
|
1385
|
-
migrateData: migrateNotificationOnlyWakes
|
|
1386
|
-
},
|
|
1387
|
-
{
|
|
1388
|
-
version: 31,
|
|
1389
|
-
name: "explicit-review-scope",
|
|
1390
|
-
introducedIn: "0.16.0",
|
|
1391
|
-
sql: `
|
|
1392
|
-
UPDATE review_rounds
|
|
1393
|
-
SET payload = json_set(payload, '$.scope', 'work-item')
|
|
1394
|
-
WHERE json_type(payload, '$.scope') IS NULL
|
|
1395
|
-
OR json_type(payload, '$.scope') = 'null';
|
|
1396
|
-
`
|
|
1397
|
-
},
|
|
1398
|
-
{
|
|
1399
|
-
version: 32,
|
|
1400
|
-
name: "work-item-history-as-events",
|
|
1401
|
-
introducedIn: "0.16.0",
|
|
1402
|
-
sql: "SELECT 1; -- Archive original WorkItem payloads before removing historicalState",
|
|
1403
|
-
migrateData: migrateWorkItemHistory
|
|
1404
|
-
},
|
|
1405
|
-
{
|
|
1406
|
-
version: 33,
|
|
1407
|
-
name: "advisory-and-verification-policy",
|
|
1408
|
-
introducedIn: "0.16.0",
|
|
1409
|
-
sql: "SELECT 1; -- Retire rollout modes/shadow metrics; explicit rerun intent; verification proof v3",
|
|
1410
|
-
migrateData: migrateVerificationPolicy
|
|
1411
|
-
},
|
|
1412
|
-
{
|
|
1413
|
-
version: 34,
|
|
1414
|
-
name: "current-input-contract",
|
|
1415
|
-
introducedIn: "0.16.0",
|
|
1416
|
-
sql: "SELECT 1; -- Archive retired Message wake policies and activation origin gates",
|
|
1417
|
-
migrateData: migrateCurrentInputContract
|
|
1418
|
-
},
|
|
1419
|
-
{
|
|
1420
|
-
version: 35,
|
|
1421
|
-
name: "current-verification-and-owner-contract",
|
|
1422
|
-
introducedIn: "0.16.0",
|
|
1423
|
-
sql: CURRENT_RUNTIME_CONTRACT_SQL,
|
|
1424
|
-
migrateData: migrateCurrentRuntimeContract
|
|
1425
|
-
},
|
|
1426
|
-
{
|
|
1427
|
-
version: 36,
|
|
1428
|
-
name: "agent-failure-configuration-context",
|
|
1429
|
-
introducedIn: "0.16.0",
|
|
1430
|
-
sql: "SELECT 1; -- Preserve historical errors; absent original launch configuration is explicitly unavailable",
|
|
1431
|
-
migrateData: migrateAgentFailureContext
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
version: 37,
|
|
1435
|
-
name: "narrow-agent-failure-context",
|
|
1436
|
-
introducedIn: "0.16.0",
|
|
1437
|
-
sql: "SELECT 1; -- Archive full v36 failure snapshots; retain only native metadata selectors",
|
|
1438
|
-
migrateData: migrateNarrowAgentFailureContext
|
|
8
|
+
export const CURRENT_SCHEMA_CHECKSUM = createHash("sha256").update(BASELINE_SCHEMA_SQL).digest("hex");
|
|
9
|
+
export class SqliteSchemaError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(detail, code = "STORAGE_SCHEMA_INVALID") {
|
|
12
|
+
super(`SQLite storage schema cannot be opened: ${detail}`);
|
|
13
|
+
this.code = code;
|
|
1439
14
|
}
|
|
1440
|
-
]);
|
|
1441
|
-
for (let index = 0; index < MIGRATIONS.length; index += 1) {
|
|
1442
|
-
const expectedVersion = MIN_SUPPORTED_STORAGE_VERSION + index;
|
|
1443
|
-
if (MIGRATIONS[index]?.version !== expectedVersion) {
|
|
1444
|
-
throw new Error(`Storage migration registry must be contiguous from `
|
|
1445
|
-
+ `${MIN_SUPPORTED_STORAGE_VERSION}; missing version ${expectedVersion}.`);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
if (MIGRATIONS.at(-1)?.version !== CURRENT_STORAGE_VERSION) {
|
|
1449
|
-
throw new Error(`Storage migration registry head ${String(MIGRATIONS.at(-1)?.version)} does not match `
|
|
1450
|
-
+ `CURRENT_STORAGE_VERSION ${CURRENT_STORAGE_VERSION}.`);
|
|
1451
|
-
}
|
|
1452
|
-
/** Current hot-path indexes whose absence would invalidate a current Home. */
|
|
1453
|
-
const REQUIRED_SCHEMA_INDEXES = [
|
|
1454
|
-
"idx_mailboxes_ready",
|
|
1455
|
-
"idx_input_requests_open_hot",
|
|
1456
|
-
"idx_turns_planning_active",
|
|
1457
|
-
"idx_tasks_activation_pending"
|
|
1458
|
-
];
|
|
1459
|
-
function checksum(sql) {
|
|
1460
|
-
return createHash("sha256").update(sql).digest("hex");
|
|
1461
15
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
16
|
+
// Only explicit, contiguous minor changes in this baseline may be added here.
|
|
17
|
+
// The initial 1.0 release has no upgrade steps and carries no older format code.
|
|
18
|
+
const MINOR_UPGRADES = Object.freeze([]);
|
|
19
|
+
export function storageMinorUpgradePlan(from) {
|
|
20
|
+
if (from === CURRENT_STORAGE_VERSION)
|
|
21
|
+
return [];
|
|
22
|
+
if (!isMinorStorageUpgrade(from, CURRENT_STORAGE_VERSION))
|
|
23
|
+
return null;
|
|
24
|
+
const result = [];
|
|
25
|
+
let current = from;
|
|
26
|
+
while (current !== CURRENT_STORAGE_VERSION) {
|
|
27
|
+
const step = MINOR_UPGRADES.find(candidate => candidate.fromVersion === current);
|
|
28
|
+
if (step === undefined || !isMinorStorageUpgrade(step.fromVersion, step.toVersion)
|
|
29
|
+
|| storageVersionParts(step.toVersion).minor !== storageVersionParts(current).minor + 1)
|
|
30
|
+
return null;
|
|
31
|
+
result.push(step);
|
|
32
|
+
current = step.toVersion;
|
|
1472
33
|
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function objects(db) {
|
|
37
|
+
return db.prepare(`SELECT type,name,sql FROM sqlite_master
|
|
38
|
+
WHERE name NOT GLOB 'sqlite_*' ORDER BY type,name`).all();
|
|
1473
39
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
`;
|
|
1482
|
-
/**
|
|
1483
|
-
* Admit the migration ledger only for a genuinely empty SQLite database.
|
|
1484
|
-
* Recreating an absent ledger on top of existing Yui tables would make the
|
|
1485
|
-
* migration runner mistake a live Home for a fresh one and replay destructive
|
|
1486
|
-
* layout migrations. A database with any sqlite_master object is therefore
|
|
1487
|
-
* diagnosed as corrupt/partially initialized and left untouched.
|
|
1488
|
-
*/
|
|
1489
|
-
function ensureMigrationLedger(db, mode) {
|
|
1490
|
-
const objects = db.prepare("SELECT type, name FROM sqlite_master WHERE name IS NOT NULL").all();
|
|
1491
|
-
const ledger = objects.find(({ name }) => name === "schema_migrations");
|
|
1492
|
-
if (ledger === undefined) {
|
|
1493
|
-
if (mode === "validate") {
|
|
1494
|
-
throw new SqliteSchemaMigrationError("schema_migrations ledger is missing from an existing database");
|
|
40
|
+
let expectedObjects;
|
|
41
|
+
function validateSchemaObjects(db) {
|
|
42
|
+
if (expectedObjects === undefined) {
|
|
43
|
+
const reference = new Database(":memory:");
|
|
44
|
+
try {
|
|
45
|
+
reference.exec(BASELINE_SCHEMA_SQL);
|
|
46
|
+
expectedObjects = objects(reference);
|
|
1495
47
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
48
|
+
finally {
|
|
49
|
+
reference.close();
|
|
1498
50
|
}
|
|
1499
|
-
db.exec(SCHEMA_MIGRATIONS_SQL);
|
|
1500
|
-
return true;
|
|
1501
|
-
}
|
|
1502
|
-
if (ledger.type !== "table") {
|
|
1503
|
-
throw new SqliteSchemaMigrationError(`schema_migrations has type ${String(ledger.type)} instead of table`);
|
|
1504
51
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
if (columns.size === 4
|
|
1511
|
-
&& ["version", "name", "applied_at", "checksum"].every((name) => columns.has(name))) {
|
|
1512
|
-
return;
|
|
52
|
+
const actual = objects(db);
|
|
53
|
+
for (const expected of expectedObjects) {
|
|
54
|
+
const found = actual.find(object => object.type === expected.type && object.name === expected.name);
|
|
55
|
+
if (found?.sql !== expected.sql)
|
|
56
|
+
throw new SqliteSchemaError(`required ${expected.type} '${expected.name}' differs from the current definition.`);
|
|
1513
57
|
}
|
|
1514
|
-
|
|
58
|
+
if (actual.length !== expectedObjects.length)
|
|
59
|
+
throw new SqliteSchemaError("unexpected schema objects; preserve the database for diagnosis.");
|
|
1515
60
|
}
|
|
1516
|
-
/**
|
|
1517
|
-
function
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
if (rows.length === 0 && !ledgerWasCreated) {
|
|
1522
|
-
throw new SqliteSchemaMigrationError("schema_migrations ledger is empty in an existing database");
|
|
61
|
+
/** Read the one format identity. Missing metadata never authorizes initialization. */
|
|
62
|
+
export function inspectSqliteSchema(db) {
|
|
63
|
+
const table = db.prepare("SELECT type FROM sqlite_master WHERE name='storage_schema'").get();
|
|
64
|
+
if (table?.type !== "table") {
|
|
65
|
+
throw new SqliteSchemaError("storage_schema is missing: this is not the current baseline. Use the independent conversion tool for a different format.", "STORAGE_FORMAT_UNSUPPORTED");
|
|
1523
66
|
}
|
|
1524
|
-
const
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
throw new SqliteSchemaMigrationError(`duplicate migration version ${version}`);
|
|
1533
|
-
}
|
|
1534
|
-
applied.add(version);
|
|
1535
|
-
if (migration === undefined) {
|
|
1536
|
-
if (version <= CURRENT_STORAGE_VERSION) {
|
|
1537
|
-
throw new SqliteSchemaMigrationError(`unknown migration version ${version}`);
|
|
1538
|
-
}
|
|
1539
|
-
if (typeof row.name !== "string" || row.name.length === 0
|
|
1540
|
-
|| typeof row.checksum !== "string" || row.checksum.length === 0) {
|
|
1541
|
-
throw new SqliteSchemaMigrationError(`future migration ${version} metadata is invalid`);
|
|
1542
|
-
}
|
|
1543
|
-
continue;
|
|
1544
|
-
}
|
|
1545
|
-
if (row.name !== migration.name) {
|
|
1546
|
-
throw new SqliteSchemaMigrationError(`migration ${version} name ${String(row.name)} does not match ${migration.name}`);
|
|
1547
|
-
}
|
|
1548
|
-
const expectedChecksum = checksum(migration.sql);
|
|
1549
|
-
if (row.checksum !== expectedChecksum) {
|
|
1550
|
-
throw new SqliteSchemaMigrationError(`migration ${version} checksum ${String(row.checksum)} does not match current definition`);
|
|
1551
|
-
}
|
|
67
|
+
const rows = db.prepare("SELECT * FROM storage_schema").all();
|
|
68
|
+
const row = rows[0];
|
|
69
|
+
const version = row === undefined ? "" : `${row.major}.${row.minor}`;
|
|
70
|
+
if (rows.length !== 1 || row?.id !== 1 || row.format !== STORAGE_FORMAT
|
|
71
|
+
|| !Number.isSafeInteger(row.major) || !Number.isSafeInteger(row.minor)
|
|
72
|
+
|| !isStorageVersion(version) || !/^[a-f0-9]{64}$/u.test(row.checksum)
|
|
73
|
+
|| !Number.isFinite(Date.parse(row.created_at))) {
|
|
74
|
+
throw new SqliteSchemaError("invalid baseline identity.");
|
|
1552
75
|
}
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
}
|
|
76
|
+
const plan = storageMinorUpgradePlan(version);
|
|
77
|
+
const expectedChecksum = version === CURRENT_STORAGE_VERSION
|
|
78
|
+
? CURRENT_SCHEMA_CHECKSUM : plan?.[0]?.sourceChecksum;
|
|
79
|
+
if (expectedChecksum !== undefined && row.checksum !== expectedChecksum) {
|
|
80
|
+
throw new SqliteSchemaError("baseline checksum does not match its version.");
|
|
1559
81
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
82
|
+
if (version === CURRENT_STORAGE_VERSION)
|
|
83
|
+
validateSchemaObjects(db);
|
|
1562
84
|
return {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
85
|
+
currentVersion: version, currentChecksum: row.checksum,
|
|
86
|
+
targetVersion: CURRENT_STORAGE_VERSION, targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
87
|
+
minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
88
|
+
pendingVersions: plan?.map(step => step.toVersion) ?? []
|
|
1566
89
|
};
|
|
1567
90
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
* manually removed or replaced.
|
|
1573
|
-
*/
|
|
1574
|
-
function validateSchemaObjects(db) {
|
|
1575
|
-
const objects = new Map(db.prepare("SELECT type, name FROM sqlite_master WHERE name IS NOT NULL").all().flatMap(({ type, name }) => (typeof type === "string" && typeof name === "string" ? [[name, type]] : [])));
|
|
1576
|
-
for (const table of SQLITE_SCHEMA_TABLES) {
|
|
1577
|
-
if (objects.get(table) !== "table") {
|
|
1578
|
-
throw new SqliteSchemaMigrationError(`required table '${table}' is missing or has the wrong type`, "schema object");
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
for (const index of REQUIRED_SCHEMA_INDEXES) {
|
|
1582
|
-
if (objects.get(index) !== "index") {
|
|
1583
|
-
throw new SqliteSchemaMigrationError(`required index '${index}' is missing or has the wrong type`, "schema object");
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
const homeMetaColumns = db.prepare("PRAGMA table_info(home_meta)").all().map(({ name }) => name);
|
|
1587
|
-
const expectedHomeMetaColumns = [
|
|
1588
|
-
"id",
|
|
1589
|
-
"home_identity",
|
|
1590
|
-
"revision",
|
|
1591
|
-
"created_at",
|
|
1592
|
-
"updated_at"
|
|
1593
|
-
];
|
|
1594
|
-
if (homeMetaColumns.length !== expectedHomeMetaColumns.length
|
|
1595
|
-
|| homeMetaColumns.some((name, index) => name !== expectedHomeMetaColumns[index])) {
|
|
1596
|
-
throw new SqliteSchemaMigrationError("home_meta columns do not match the current storage contract", "schema object");
|
|
91
|
+
export function validateSqliteSchema(db) {
|
|
92
|
+
const state = inspectSqliteSchema(db);
|
|
93
|
+
if (state.currentVersion !== CURRENT_STORAGE_VERSION) {
|
|
94
|
+
throw new SqliteSchemaError(`storage ${state.currentVersion} is not current ${CURRENT_STORAGE_VERSION}; ordinary opens never convert data.`);
|
|
1597
95
|
}
|
|
96
|
+
return state;
|
|
1598
97
|
}
|
|
1599
|
-
/**
|
|
1600
|
-
export function
|
|
1601
|
-
if (
|
|
1602
|
-
|
|
1603
|
-
|| currentVersion > CURRENT_STORAGE_VERSION) {
|
|
1604
|
-
return null;
|
|
1605
|
-
}
|
|
1606
|
-
return MIGRATIONS
|
|
1607
|
-
.filter(({ version }) => version > currentVersion)
|
|
1608
|
-
.map(({ version, name, introducedIn }) => ({
|
|
1609
|
-
fromVersion: version - 1,
|
|
1610
|
-
toVersion: version,
|
|
1611
|
-
name,
|
|
1612
|
-
introducedIn
|
|
1613
|
-
}));
|
|
1614
|
-
}
|
|
1615
|
-
/** Inspect a recognized migration prefix without changing it. */
|
|
1616
|
-
export function inspectSqliteSchemaMigrations(db) {
|
|
1617
|
-
const ledgerWasCreated = ensureMigrationLedger(db, "validate");
|
|
1618
|
-
const applied = validateAppliedMigrations(db, ledgerWasCreated);
|
|
1619
|
-
if (applied.currentVersion > CURRENT_STORAGE_VERSION) {
|
|
1620
|
-
return {
|
|
1621
|
-
currentVersion: applied.currentVersion,
|
|
1622
|
-
currentChecksum: applied.currentChecksum,
|
|
1623
|
-
targetVersion: CURRENT_STORAGE_VERSION,
|
|
1624
|
-
minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
1625
|
-
targetChecksum: checksum(MIGRATIONS.at(-1).sql),
|
|
1626
|
-
pendingVersions: []
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
const pendingVersions = MIGRATIONS
|
|
1630
|
-
.filter((migration) => !applied.versions.has(migration.version))
|
|
1631
|
-
.map((migration) => migration.version);
|
|
1632
|
-
if (pendingVersions.length === 0)
|
|
1633
|
-
validateSchemaObjects(db);
|
|
1634
|
-
const target = MIGRATIONS.at(-1);
|
|
1635
|
-
if (applied.currentVersion === 0) {
|
|
1636
|
-
throw new SqliteSchemaMigrationError("schema_migrations ledger has no current head");
|
|
98
|
+
/** Fresh initialization, never a replay of previous schema definitions. */
|
|
99
|
+
export function initializeSqliteSchema(db) {
|
|
100
|
+
if (db.prepare("SELECT 1 FROM sqlite_master LIMIT 1").get() !== undefined) {
|
|
101
|
+
throw new SqliteSchemaError("initialization requires an empty database.");
|
|
1637
102
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
};
|
|
103
|
+
const { major, minor } = storageVersionParts(CURRENT_STORAGE_VERSION);
|
|
104
|
+
return db.transaction(() => {
|
|
105
|
+
db.exec(BASELINE_SCHEMA_SQL);
|
|
106
|
+
db.prepare("INSERT INTO storage_schema VALUES (1,?,?,?,?,?)")
|
|
107
|
+
.run(STORAGE_FORMAT, major, minor, CURRENT_SCHEMA_CHECKSUM, new Date().toISOString());
|
|
108
|
+
return validateSqliteSchema(db);
|
|
109
|
+
})();
|
|
1646
110
|
}
|
|
1647
|
-
/**
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
if (applied.currentVersion > CURRENT_STORAGE_VERSION) {
|
|
1664
|
-
throw new SqliteSchemaMigrationError(`storage version ${applied.currentVersion} is newer than supported `
|
|
1665
|
-
+ `${CURRENT_STORAGE_VERSION}`, "admission");
|
|
1666
|
-
}
|
|
1667
|
-
if (applied.currentVersion !== 0
|
|
1668
|
-
&& applied.currentVersion < MIN_SUPPORTED_STORAGE_VERSION) {
|
|
1669
|
-
throw new SqliteSchemaMigrationError(`storage version ${applied.currentVersion} is older than the minimum supported `
|
|
1670
|
-
+ `${MIN_SUPPORTED_STORAGE_VERSION}`, "admission");
|
|
1671
|
-
}
|
|
1672
|
-
// Production callers omit `throughVersion`, so the effective target is head
|
|
1673
|
-
// and behavior is unchanged; a partial target is honored only in apply mode.
|
|
1674
|
-
const effectiveTarget = options.mode === "apply" && options.throughVersion !== undefined
|
|
1675
|
-
? options.throughVersion
|
|
1676
|
-
: CURRENT_STORAGE_VERSION;
|
|
1677
|
-
const pending = MIGRATIONS.filter((migration) => !applied.versions.has(migration.version) && migration.version <= effectiveTarget);
|
|
1678
|
-
if (!ledgerWasCreated && pending.length > 0 && options.mode === "validate") {
|
|
1679
|
-
throw new SqliteSchemaMigrationError(`Storage version ${applied.currentVersion} requires an explicit upgrade to `
|
|
1680
|
-
+ `${CURRENT_STORAGE_VERSION}`, "admission");
|
|
1681
|
-
}
|
|
1682
|
-
// Bound the applied steps to `throughVersion` (default and clamp: head), so a
|
|
1683
|
-
// migration test can stop at one step's intermediate state while production
|
|
1684
|
-
// always advances to head. The validate-mode guard above still fires on the
|
|
1685
|
-
// FULL pending set — the bound never lets an ordinary open partially migrate.
|
|
1686
|
-
const targetVersion = Math.min(options.throughVersion ?? CURRENT_STORAGE_VERSION, CURRENT_STORAGE_VERSION);
|
|
1687
|
-
const toApply = pending.filter((migration) => migration.version <= targetVersion);
|
|
1688
|
-
const newlyApplied = [];
|
|
1689
|
-
for (const migration of toApply) {
|
|
1690
|
-
db.exec(migration.sql);
|
|
1691
|
-
migration.migrateData?.(db);
|
|
1692
|
-
const appliedAt = new Date().toISOString();
|
|
1693
|
-
db.prepare(`INSERT INTO schema_migrations (version, name, applied_at, checksum)
|
|
1694
|
-
VALUES (?, ?, ?, ?)`).run(migration.version, migration.name, appliedAt, checksum(migration.sql));
|
|
1695
|
-
newlyApplied.push(migration.version);
|
|
111
|
+
/** Called only after an explicit minor upgrade has obtained its maintenance fence. */
|
|
112
|
+
export function applySqliteMinorUpgrades(db) {
|
|
113
|
+
db.transaction(() => {
|
|
114
|
+
const before = inspectSqliteSchema(db);
|
|
115
|
+
const plan = storageMinorUpgradePlan(before.currentVersion);
|
|
116
|
+
if (plan === null)
|
|
117
|
+
throw new SqliteSchemaError("cross-major or unknown upgrades require an independent explicit converter.");
|
|
118
|
+
for (const step of plan) {
|
|
119
|
+
const state = inspectSqliteSchema(db);
|
|
120
|
+
if (state.currentVersion !== step.fromVersion || state.currentChecksum !== step.sourceChecksum) {
|
|
121
|
+
throw new SqliteSchemaError("minor upgrade source changed.");
|
|
122
|
+
}
|
|
123
|
+
db.exec(step.sql);
|
|
124
|
+
const { major, minor } = storageVersionParts(step.toVersion);
|
|
125
|
+
db.prepare("UPDATE storage_schema SET major=?,minor=?,checksum=? WHERE id=1")
|
|
126
|
+
.run(major, minor, step.targetChecksum);
|
|
1696
127
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
// earlier and is validated when the real upgrade later completes it).
|
|
1700
|
-
if (effectiveTarget >= CURRENT_STORAGE_VERSION)
|
|
1701
|
-
validateSchemaObjects(db);
|
|
1702
|
-
// The true head reached: the highest already-applied version or, when this
|
|
1703
|
-
// run advanced the ledger, the last step it committed. Bounded runs report
|
|
1704
|
-
// the intermediate head; an unbounded run reports CURRENT_STORAGE_VERSION.
|
|
1705
|
-
const head = newlyApplied.at(-1) ?? applied.currentVersion;
|
|
1706
|
-
return { newlyApplied, head };
|
|
1707
|
-
};
|
|
1708
|
-
const outcome = options.mode === "apply" && !db.inTransaction
|
|
1709
|
-
? db.transaction(migrate)()
|
|
1710
|
-
: migrate();
|
|
1711
|
-
return { applied: outcome.newlyApplied, version: outcome.head };
|
|
128
|
+
validateSqliteSchema(db);
|
|
129
|
+
})();
|
|
1712
130
|
}
|
|
1713
|
-
/** The names of every table the schema creates (for tests/introspection). */
|
|
1714
|
-
export const SQLITE_SCHEMA_TABLES = [
|
|
1715
|
-
"storage_migration_archive",
|
|
1716
|
-
"plugin_intents",
|
|
1717
|
-
"plugin_validations",
|
|
1718
|
-
"local_resources",
|
|
1719
|
-
"environment_preparations",
|
|
1720
|
-
"schema_migrations",
|
|
1721
|
-
"home_meta",
|
|
1722
|
-
"config",
|
|
1723
|
-
"configured_agents",
|
|
1724
|
-
"agent_profiles",
|
|
1725
|
-
"projects",
|
|
1726
|
-
"global_roles",
|
|
1727
|
-
"global_role_session_sets",
|
|
1728
|
-
"global_role_messages",
|
|
1729
|
-
"global_sequences",
|
|
1730
|
-
"tasks_catalog",
|
|
1731
|
-
"managed_workspaces",
|
|
1732
|
-
"id_sequences",
|
|
1733
|
-
"coordination_locks",
|
|
1734
|
-
"durable_jobs",
|
|
1735
|
-
"outbox",
|
|
1736
|
-
"mailboxes",
|
|
1737
|
-
"task_records",
|
|
1738
|
-
"task_roles",
|
|
1739
|
-
"role_session_sets",
|
|
1740
|
-
"work_items",
|
|
1741
|
-
"work_item_candidates",
|
|
1742
|
-
"context_snapshots",
|
|
1743
|
-
"turns",
|
|
1744
|
-
"active_turns",
|
|
1745
|
-
"review_rounds",
|
|
1746
|
-
"change_sets",
|
|
1747
|
-
"integration_attempts",
|
|
1748
|
-
"messages",
|
|
1749
|
-
"input_requests",
|
|
1750
|
-
"decisions",
|
|
1751
|
-
"milestones",
|
|
1752
|
-
"events",
|
|
1753
|
-
"task_projections",
|
|
1754
|
-
"telemetry",
|
|
1755
|
-
"telemetry_aggregate",
|
|
1756
|
-
"capability_grants",
|
|
1757
|
-
"release_workflows",
|
|
1758
|
-
"publication_references",
|
|
1759
|
-
"task_wakes",
|
|
1760
|
-
"session_owners",
|
|
1761
|
-
"runtime_session_candidates",
|
|
1762
|
-
"resource_registry",
|
|
1763
|
-
"gate_artifacts",
|
|
1764
|
-
"gate_artifact_logs"
|
|
1765
|
-
];
|