@dsh-enhanced/plugin-control-plane 0.1.31 → 0.1.34
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/README.md +352 -4
- package/bin/dsh-local-release-adapter.js +80 -16
- package/bin/dsh-npm-registry-adapter.js +613 -0
- package/bin/dsh-source-adoption-authority.js +12 -0
- package/bin/dsh-source-approval-authority.js +16 -0
- package/bin/dsh-source-release-authority.js +12 -0
- package/bin/dsh-systemd-host-attestor.js +707 -0
- package/bin/dsh-task-observation-authority.js +15 -0
- package/lib/adapter-process.d.ts +23 -0
- package/lib/adapter-process.d.ts.map +1 -0
- package/lib/adapter-process.js +305 -0
- package/lib/adapter-process.js.map +1 -0
- package/lib/adoption-coordinator.d.ts +63 -0
- package/lib/adoption-coordinator.d.ts.map +1 -0
- package/lib/adoption-coordinator.js +241 -0
- package/lib/adoption-coordinator.js.map +1 -0
- package/lib/adoption-handoff.d.ts +16 -0
- package/lib/adoption-handoff.d.ts.map +1 -0
- package/lib/adoption-handoff.js +11 -0
- package/lib/adoption-handoff.js.map +1 -0
- package/lib/attestation.d.ts.map +1 -1
- package/lib/attestation.js +49 -1
- package/lib/attestation.js.map +1 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +29 -2
- package/lib/catalog.js.map +1 -1
- package/lib/cli.d.ts +33 -9
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +624 -178
- package/lib/cli.js.map +1 -1
- package/lib/deployment-readiness.d.ts +38 -0
- package/lib/deployment-readiness.d.ts.map +1 -0
- package/lib/deployment-readiness.js +154 -0
- package/lib/deployment-readiness.js.map +1 -0
- package/lib/effect-blocked-replay.d.ts +81 -0
- package/lib/effect-blocked-replay.d.ts.map +1 -0
- package/lib/effect-blocked-replay.js +259 -0
- package/lib/effect-blocked-replay.js.map +1 -0
- package/lib/errors.d.ts +6 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +9 -0
- package/lib/errors.js.map +1 -0
- package/lib/foreground-deployment-runtime.d.ts +20 -0
- package/lib/foreground-deployment-runtime.d.ts.map +1 -0
- package/lib/foreground-deployment-runtime.js +79 -0
- package/lib/foreground-deployment-runtime.js.map +1 -0
- package/lib/foreground-deployment.d.ts +17 -0
- package/lib/foreground-deployment.d.ts.map +1 -0
- package/lib/foreground-deployment.js +43 -0
- package/lib/foreground-deployment.js.map +1 -0
- package/lib/host-attestor.d.ts +1 -1
- package/lib/host-attestor.d.ts.map +1 -1
- package/lib/host-attestor.js +51 -57
- package/lib/host-attestor.js.map +1 -1
- package/lib/index.d.ts +25 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -2
- package/lib/index.js.map +1 -1
- package/lib/npm-publish.d.ts +25 -0
- package/lib/npm-publish.d.ts.map +1 -0
- package/lib/npm-publish.js +280 -0
- package/lib/npm-publish.js.map +1 -0
- package/lib/owner-task-gap-types.d.ts +21 -0
- package/lib/owner-task-gap-types.d.ts.map +1 -0
- package/lib/owner-task-gap-types.js +2 -0
- package/lib/owner-task-gap-types.js.map +1 -0
- package/lib/owner-task-gaps.d.ts +24 -0
- package/lib/owner-task-gaps.d.ts.map +1 -0
- package/lib/owner-task-gaps.js +74 -0
- package/lib/owner-task-gaps.js.map +1 -0
- package/lib/post-activation.d.ts +33 -0
- package/lib/post-activation.d.ts.map +1 -0
- package/lib/post-activation.js +219 -0
- package/lib/post-activation.js.map +1 -0
- package/lib/registry-fetch.d.ts +50 -0
- package/lib/registry-fetch.d.ts.map +1 -0
- package/lib/registry-fetch.js +267 -0
- package/lib/registry-fetch.js.map +1 -0
- package/lib/release.d.ts +4 -4
- package/lib/release.d.ts.map +1 -1
- package/lib/release.js +142 -56
- package/lib/release.js.map +1 -1
- package/lib/replay-endpoint-protocol.d.ts +65 -0
- package/lib/replay-endpoint-protocol.d.ts.map +1 -0
- package/lib/replay-endpoint-protocol.js +183 -0
- package/lib/replay-endpoint-protocol.js.map +1 -0
- package/lib/replay-endpoint.d.ts +7 -0
- package/lib/replay-endpoint.d.ts.map +1 -0
- package/lib/replay-endpoint.js +193 -0
- package/lib/replay-endpoint.js.map +1 -0
- package/lib/replay-grant.d.ts +62 -0
- package/lib/replay-grant.d.ts.map +1 -0
- package/lib/replay-grant.js +208 -0
- package/lib/replay-grant.js.map +1 -0
- package/lib/replay-journal.d.ts +25 -0
- package/lib/replay-journal.d.ts.map +1 -0
- package/lib/replay-journal.js +275 -0
- package/lib/replay-journal.js.map +1 -0
- package/lib/runtime-observer-protocol.d.ts +63 -0
- package/lib/runtime-observer-protocol.d.ts.map +1 -0
- package/lib/runtime-observer-protocol.js +280 -0
- package/lib/runtime-observer-protocol.js.map +1 -0
- package/lib/runtime-observer.d.ts +8 -0
- package/lib/runtime-observer.d.ts.map +1 -0
- package/lib/runtime-observer.js +182 -0
- package/lib/runtime-observer.js.map +1 -0
- package/lib/service.d.ts +145 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +734 -6
- package/lib/service.js.map +1 -1
- package/lib/source-adoption-authority.d.ts +65 -0
- package/lib/source-adoption-authority.d.ts.map +1 -0
- package/lib/source-adoption-authority.js +271 -0
- package/lib/source-adoption-authority.js.map +1 -0
- package/lib/source-adoption-runner.d.ts +28 -0
- package/lib/source-adoption-runner.d.ts.map +1 -0
- package/lib/source-adoption-runner.js +197 -0
- package/lib/source-adoption-runner.js.map +1 -0
- package/lib/source-approval-authority.d.ts +50 -0
- package/lib/source-approval-authority.d.ts.map +1 -0
- package/lib/source-approval-authority.js +412 -0
- package/lib/source-approval-authority.js.map +1 -0
- package/lib/source-approval-client.d.ts +32 -0
- package/lib/source-approval-client.d.ts.map +1 -0
- package/lib/source-approval-client.js +175 -0
- package/lib/source-approval-client.js.map +1 -0
- package/lib/source-build.d.ts +60 -0
- package/lib/source-build.d.ts.map +1 -0
- package/lib/source-build.js +311 -0
- package/lib/source-build.js.map +1 -0
- package/lib/source-context.d.ts +24 -0
- package/lib/source-context.d.ts.map +1 -0
- package/lib/source-context.js +99 -0
- package/lib/source-context.js.map +1 -0
- package/lib/source-job-types.d.ts +87 -0
- package/lib/source-job-types.d.ts.map +1 -0
- package/lib/source-job-types.js +2 -0
- package/lib/source-job-types.js.map +1 -0
- package/lib/source-jobs.d.ts +113 -0
- package/lib/source-jobs.d.ts.map +1 -0
- package/lib/source-jobs.js +535 -0
- package/lib/source-jobs.js.map +1 -0
- package/lib/source-release-authority.d.ts +35 -0
- package/lib/source-release-authority.d.ts.map +1 -0
- package/lib/source-release-authority.js +271 -0
- package/lib/source-release-authority.js.map +1 -0
- package/lib/source-release-client.d.ts +10 -0
- package/lib/source-release-client.d.ts.map +1 -0
- package/lib/source-release-client.js +24 -0
- package/lib/source-release-client.js.map +1 -0
- package/lib/source-release-runner.d.ts +31 -0
- package/lib/source-release-runner.d.ts.map +1 -0
- package/lib/source-release-runner.js +125 -0
- package/lib/source-release-runner.js.map +1 -0
- package/lib/source-versioning.d.ts +24 -0
- package/lib/source-versioning.d.ts.map +1 -0
- package/lib/source-versioning.js +213 -0
- package/lib/source-versioning.js.map +1 -0
- package/lib/source-workspace.d.ts +117 -0
- package/lib/source-workspace.d.ts.map +1 -0
- package/lib/source-workspace.js +458 -0
- package/lib/source-workspace.js.map +1 -0
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +509 -7
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +229 -6
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +2431 -383
- package/lib/store.js.map +1 -1
- package/lib/task-observation-authority.d.ts +29 -0
- package/lib/task-observation-authority.d.ts.map +1 -0
- package/lib/task-observation-authority.js +370 -0
- package/lib/task-observation-authority.js.map +1 -0
- package/lib/task-observation-client.d.ts +4 -0
- package/lib/task-observation-client.d.ts.map +1 -0
- package/lib/task-observation-client.js +20 -0
- package/lib/task-observation-client.js.map +1 -0
- package/lib/task-observation-runtime.d.ts +59 -0
- package/lib/task-observation-runtime.d.ts.map +1 -0
- package/lib/task-observation-runtime.js +331 -0
- package/lib/task-observation-runtime.js.map +1 -0
- package/lib/task-observation-store.d.ts +17 -0
- package/lib/task-observation-store.d.ts.map +1 -0
- package/lib/task-observation-store.js +115 -0
- package/lib/task-observation-store.js.map +1 -0
- package/lib/task-observation-types.d.ts +67 -0
- package/lib/task-observation-types.d.ts.map +1 -0
- package/lib/task-observation-types.js +2 -0
- package/lib/task-observation-types.js.map +1 -0
- package/lib/trust.d.ts +3 -0
- package/lib/trust.d.ts.map +1 -1
- package/lib/trust.js +0 -0
- package/lib/trust.js.map +1 -1
- package/lib/types.d.ts +275 -8
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +40 -5
package/lib/sqlite.js
CHANGED
|
@@ -2,7 +2,41 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync } from 'node:fs';
|
|
3
3
|
import { dirname, isAbsolute } from 'node:path';
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
-
export const controlPlaneSchemaVersion =
|
|
5
|
+
export const controlPlaneSchemaVersion = 23;
|
|
6
|
+
const adoptionHandoffsSchema = `CREATE TABLE IF NOT EXISTS adoption_handoffs (
|
|
7
|
+
plan_id TEXT PRIMARY KEY REFERENCES activation_plans(id) ON DELETE RESTRICT, plan_digest TEXT NOT NULL CHECK(length(plan_digest)=64), coordinator_id TEXT NOT NULL,
|
|
8
|
+
created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, revoked_at INTEGER, CHECK(expires_at > created_at)
|
|
9
|
+
) STRICT, WITHOUT ROWID;`;
|
|
10
|
+
const hostAttestationDispatchesSchema = `CREATE TABLE IF NOT EXISTS host_attestation_dispatches (
|
|
11
|
+
operation_id TEXT PRIMARY KEY REFERENCES host_attestation_operations(operation_id) ON DELETE RESTRICT,
|
|
12
|
+
status TEXT NOT NULL CHECK(status IN ('claimed','completed')),
|
|
13
|
+
claimed_at INTEGER NOT NULL, completed_at INTEGER,
|
|
14
|
+
CHECK((status = 'claimed' AND completed_at IS NULL) OR (status = 'completed' AND completed_at IS NOT NULL AND completed_at >= claimed_at))
|
|
15
|
+
) STRICT, WITHOUT ROWID;`;
|
|
16
|
+
const taskObservationsSchema = `CREATE TABLE IF NOT EXISTS task_observation_batches (
|
|
17
|
+
id TEXT PRIMARY KEY, lane TEXT NOT NULL, plan_id TEXT NOT NULL REFERENCES activation_plans(id) ON DELETE RESTRICT,
|
|
18
|
+
batch_json TEXT NOT NULL CHECK(json_valid(batch_json) AND json_type(batch_json) = 'object'),
|
|
19
|
+
batch_digest TEXT NOT NULL CHECK(length(batch_digest) = 64),
|
|
20
|
+
state TEXT NOT NULL CHECK(state IN ('pending','signed','applied','stale')),
|
|
21
|
+
receipt_json TEXT CHECK(receipt_json IS NULL OR (json_valid(receipt_json) AND json_type(receipt_json) = 'object')),
|
|
22
|
+
receipt_digest TEXT CHECK(receipt_digest IS NULL OR length(receipt_digest) = 64),
|
|
23
|
+
created_at INTEGER NOT NULL
|
|
24
|
+
) STRICT, WITHOUT ROWID;
|
|
25
|
+
CREATE INDEX IF NOT EXISTS task_observation_batches_lane ON task_observation_batches(lane, created_at DESC);
|
|
26
|
+
CREATE UNIQUE INDEX IF NOT EXISTS task_observation_batches_open_lane ON task_observation_batches(lane) WHERE state IN ('pending','signed');`;
|
|
27
|
+
const foregroundDeploymentsSchema = `CREATE TABLE IF NOT EXISTS foreground_deployments (
|
|
28
|
+
inbox_id TEXT PRIMARY KEY,
|
|
29
|
+
plan_id TEXT NOT NULL REFERENCES activation_plans(id) ON DELETE RESTRICT,
|
|
30
|
+
record_json TEXT NOT NULL CHECK(json_valid(record_json) AND json_type(record_json) = 'object'),
|
|
31
|
+
record_digest TEXT NOT NULL CHECK(length(record_digest) = 64)
|
|
32
|
+
) STRICT, WITHOUT ROWID;`;
|
|
33
|
+
const sourceAdoptionsSchema = `CREATE TABLE IF NOT EXISTS source_adoptions (
|
|
34
|
+
source_plan_id TEXT PRIMARY KEY REFERENCES source_plans(id) ON DELETE RESTRICT,
|
|
35
|
+
activation_plan_id TEXT NOT NULL UNIQUE REFERENCES activation_plans(id) ON DELETE RESTRICT,
|
|
36
|
+
binding_json TEXT NOT NULL CHECK(json_valid(binding_json) AND json_type(binding_json) = 'object'),
|
|
37
|
+
binding_digest TEXT NOT NULL CHECK(length(binding_digest) = 64),
|
|
38
|
+
created_at INTEGER NOT NULL
|
|
39
|
+
) STRICT, WITHOUT ROWID;`;
|
|
6
40
|
export function controlPlaneOperationReceiptDigest(idempotencyKey, operation, inputDigest, resultJson, createdAt) {
|
|
7
41
|
return createHash('sha256').update(JSON.stringify([idempotencyKey, operation, inputDigest, resultJson, createdAt])).digest('hex');
|
|
8
42
|
}
|
|
@@ -64,6 +98,13 @@ function createCurrent(database) {
|
|
|
64
98
|
updated_at INTEGER NOT NULL
|
|
65
99
|
) STRICT;
|
|
66
100
|
|
|
101
|
+
CREATE TABLE owner_task_failure_gaps (
|
|
102
|
+
gap_id TEXT PRIMARY KEY REFERENCES capability_gaps(id) ON DELETE RESTRICT,
|
|
103
|
+
reference_json TEXT NOT NULL CHECK(json_valid(reference_json) AND json_type(reference_json) = 'object'),
|
|
104
|
+
reference_digest TEXT NOT NULL CHECK(length(reference_digest) = 64)
|
|
105
|
+
) STRICT, WITHOUT ROWID;
|
|
106
|
+
|
|
107
|
+
|
|
67
108
|
CREATE TABLE activation_plans (
|
|
68
109
|
id TEXT PRIMARY KEY,
|
|
69
110
|
plan_digest TEXT NOT NULL UNIQUE CHECK(length(plan_digest) = 64),
|
|
@@ -96,6 +137,9 @@ function createCurrent(database) {
|
|
|
96
137
|
activation_fence INTEGER NOT NULL DEFAULT 0 CHECK(activation_fence >= 0),
|
|
97
138
|
activation_lease_until INTEGER,
|
|
98
139
|
activation_target_existed INTEGER CHECK(activation_target_existed IN (0, 1)),
|
|
140
|
+
activation_target_baseline_json TEXT CHECK(activation_target_baseline_json IS NULL OR (json_valid(activation_target_baseline_json) AND json_type(activation_target_baseline_json) = 'array')),
|
|
141
|
+
host_recovery_required INTEGER NOT NULL DEFAULT 0 CHECK(host_recovery_required IN (0, 1)),
|
|
142
|
+
rollback_profile_restored INTEGER NOT NULL DEFAULT 0 CHECK(rollback_profile_restored IN (0, 1)),
|
|
99
143
|
failure_code TEXT,
|
|
100
144
|
updated_at INTEGER NOT NULL,
|
|
101
145
|
FOREIGN KEY(gap_id) REFERENCES capability_gaps(id) ON DELETE RESTRICT
|
|
@@ -119,8 +163,9 @@ function createCurrent(database) {
|
|
|
119
163
|
plugin_name TEXT NOT NULL,
|
|
120
164
|
generator_digest TEXT NOT NULL CHECK(length(generator_digest) = 64),
|
|
121
165
|
scope_json TEXT NOT NULL CHECK(json_valid(scope_json) AND json_type(scope_json) = 'array'),
|
|
166
|
+
mode TEXT NOT NULL DEFAULT 'create' CHECK(mode IN ('create', 'modify')),
|
|
122
167
|
status TEXT NOT NULL CHECK(status IN (
|
|
123
|
-
'pending-approval', 'approved', 'running-local-checks', 'ready-for-human-review', 'local-checks-failed',
|
|
168
|
+
'expired', 'pending-approval', 'approved', 'running-local-checks', 'ready-for-human-review', 'local-checks-failed',
|
|
124
169
|
'awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
|
|
125
170
|
'awaiting-registry-verify', 'awaiting-catalog-admission', 'release-complete', 'release-failed', 'publish-ambiguous'
|
|
126
171
|
)),
|
|
@@ -131,6 +176,8 @@ function createCurrent(database) {
|
|
|
131
176
|
checked_tree_digest TEXT CHECK(checked_tree_digest IS NULL OR (length(checked_tree_digest) = 64 AND checked_tree_digest NOT GLOB '*[^a-f0-9]*')),
|
|
132
177
|
checked_patch_digest TEXT CHECK(checked_patch_digest IS NULL OR (length(checked_patch_digest) = 64 AND checked_patch_digest NOT GLOB '*[^a-f0-9]*')),
|
|
133
178
|
checked_at INTEGER,
|
|
179
|
+
prepared_evidence_json TEXT CHECK(prepared_evidence_json IS NULL OR
|
|
180
|
+
(json_valid(prepared_evidence_json) AND json_type(prepared_evidence_json) = 'object')),
|
|
134
181
|
release_authorization_json TEXT CHECK(release_authorization_json IS NULL OR (json_valid(release_authorization_json) AND json_type(release_authorization_json) = 'object')),
|
|
135
182
|
release_authorization_digest TEXT CHECK(release_authorization_digest IS NULL OR length(release_authorization_digest) = 64),
|
|
136
183
|
release_id TEXT,
|
|
@@ -142,11 +189,51 @@ function createCurrent(database) {
|
|
|
142
189
|
updated_at INTEGER NOT NULL,
|
|
143
190
|
CHECK((checked_tree_digest IS NULL AND checked_patch_digest IS NULL AND checked_at IS NULL) OR
|
|
144
191
|
(checked_tree_digest IS NOT NULL AND checked_patch_digest IS NOT NULL AND checked_at IS NOT NULL)),
|
|
192
|
+
CHECK(mode = 'create' OR
|
|
193
|
+
(checked_tree_digest IS NOT NULL AND checked_patch_digest IS NOT NULL AND checked_at IS NOT NULL)),
|
|
194
|
+
CHECK((mode = 'create' AND prepared_evidence_json IS NULL) OR
|
|
195
|
+
(mode = 'modify' AND prepared_evidence_json IS NOT NULL)),
|
|
145
196
|
CHECK((release_authorization_json IS NULL AND release_authorization_digest IS NULL) OR
|
|
146
197
|
(release_authorization_json IS NOT NULL AND release_authorization_digest IS NOT NULL)),
|
|
147
198
|
FOREIGN KEY(gap_id) REFERENCES capability_gaps(id) ON DELETE RESTRICT
|
|
148
199
|
) STRICT;
|
|
149
200
|
|
|
201
|
+
CREATE TABLE IF NOT EXISTS source_job_authorities (
|
|
202
|
+
authority_id TEXT PRIMARY KEY,
|
|
203
|
+
authority_digest TEXT NOT NULL CHECK(length(authority_digest) = 64),
|
|
204
|
+
expires_at INTEGER NOT NULL,
|
|
205
|
+
max_submissions INTEGER NOT NULL CHECK(max_submissions >= 1),
|
|
206
|
+
submissions INTEGER NOT NULL CHECK(submissions >= 0 AND submissions <= max_submissions)
|
|
207
|
+
) STRICT, WITHOUT ROWID;
|
|
208
|
+
|
|
209
|
+
CREATE TABLE IF NOT EXISTS source_jobs (
|
|
210
|
+
id TEXT PRIMARY KEY,
|
|
211
|
+
automation_id TEXT NOT NULL UNIQUE,
|
|
212
|
+
authority_id TEXT NOT NULL,
|
|
213
|
+
idempotency_key TEXT NOT NULL,
|
|
214
|
+
intent_json TEXT NOT NULL CHECK(json_valid(intent_json) AND json_type(intent_json) = 'object'),
|
|
215
|
+
intent_digest TEXT NOT NULL CHECK(length(intent_digest) = 64),
|
|
216
|
+
status TEXT NOT NULL CHECK(status IN ('queued', 'running', 'prepared', 'failed', 'unknown')),
|
|
217
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
218
|
+
created_at INTEGER NOT NULL,
|
|
219
|
+
expires_at INTEGER NOT NULL,
|
|
220
|
+
definition_hash TEXT,
|
|
221
|
+
occurrence_id TEXT,
|
|
222
|
+
plan_id TEXT UNIQUE,
|
|
223
|
+
failure_code TEXT,
|
|
224
|
+
updated_at INTEGER NOT NULL,
|
|
225
|
+
UNIQUE(authority_id, idempotency_key),
|
|
226
|
+
FOREIGN KEY(authority_id) REFERENCES source_job_authorities(authority_id) ON DELETE RESTRICT,
|
|
227
|
+
FOREIGN KEY(plan_id) REFERENCES source_plans(id) ON DELETE RESTRICT,
|
|
228
|
+
CHECK((status = 'queued' AND occurrence_id IS NULL AND plan_id IS NULL AND failure_code IS NULL)
|
|
229
|
+
OR (status = 'running' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NULL AND failure_code IS NULL)
|
|
230
|
+
OR (status = 'prepared' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NOT NULL AND failure_code IS NULL)
|
|
231
|
+
OR (status = 'failed' AND plan_id IS NULL AND failure_code IS NOT NULL)
|
|
232
|
+
OR (status = 'unknown' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NULL AND failure_code IS NOT NULL))
|
|
233
|
+
) STRICT;
|
|
234
|
+
CREATE UNIQUE INDEX IF NOT EXISTS source_jobs_single_active ON source_jobs((1)) WHERE status IN ('queued', 'running', 'unknown');
|
|
235
|
+
CREATE INDEX IF NOT EXISTS source_jobs_created ON source_jobs(created_at, id);
|
|
236
|
+
|
|
150
237
|
CREATE TABLE gap_plan_claims (
|
|
151
238
|
gap_id TEXT PRIMARY KEY,
|
|
152
239
|
plan_id TEXT NOT NULL UNIQUE,
|
|
@@ -155,9 +242,9 @@ function createCurrent(database) {
|
|
|
155
242
|
FOREIGN KEY(gap_id) REFERENCES capability_gaps(id) ON DELETE RESTRICT
|
|
156
243
|
) STRICT, WITHOUT ROWID;
|
|
157
244
|
|
|
158
|
-
CREATE TABLE host_attestations (
|
|
245
|
+
CREATE TABLE IF NOT EXISTS host_attestations (
|
|
159
246
|
plan_id TEXT NOT NULL,
|
|
160
|
-
phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health')),
|
|
247
|
+
phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health', 'rollback')),
|
|
161
248
|
receipt_id TEXT NOT NULL UNIQUE,
|
|
162
249
|
receipt_digest TEXT NOT NULL CHECK(length(receipt_digest) = 64),
|
|
163
250
|
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json) AND json_type(receipt_json) = 'object'),
|
|
@@ -167,9 +254,9 @@ function createCurrent(database) {
|
|
|
167
254
|
FOREIGN KEY(plan_id) REFERENCES activation_plans(id) ON DELETE RESTRICT
|
|
168
255
|
) STRICT, WITHOUT ROWID;
|
|
169
256
|
|
|
170
|
-
CREATE TABLE host_attestation_operations (
|
|
257
|
+
CREATE TABLE IF NOT EXISTS host_attestation_operations (
|
|
171
258
|
plan_id TEXT NOT NULL,
|
|
172
|
-
phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health')),
|
|
259
|
+
phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health', 'rollback')),
|
|
173
260
|
operation_id TEXT NOT NULL UNIQUE,
|
|
174
261
|
binding_digest TEXT NOT NULL CHECK(length(binding_digest) = 64),
|
|
175
262
|
request_digest TEXT NOT NULL CHECK(length(request_digest) = 64),
|
|
@@ -210,6 +297,26 @@ function createCurrent(database) {
|
|
|
210
297
|
FOREIGN KEY(plan_id) REFERENCES source_plans(id) ON DELETE RESTRICT
|
|
211
298
|
) STRICT, WITHOUT ROWID;
|
|
212
299
|
|
|
300
|
+
-- A release adapter can mutate an external registry. Its durable dispatch
|
|
301
|
+
-- claim is deliberately separate from the signed receipt: after a crash
|
|
302
|
+
-- between dispatch and receipt persistence, the outcome is unknown and
|
|
303
|
+
-- must be reconciled rather than executed again.
|
|
304
|
+
CREATE TABLE source_release_dispatches (
|
|
305
|
+
operation_id TEXT PRIMARY KEY REFERENCES source_release_operations(operation_id) ON DELETE RESTRICT,
|
|
306
|
+
status TEXT NOT NULL CHECK(status IN ('claimed', 'completed')),
|
|
307
|
+
claimed_at INTEGER NOT NULL,
|
|
308
|
+
completed_at INTEGER,
|
|
309
|
+
CHECK((status = 'claimed' AND completed_at IS NULL) OR
|
|
310
|
+
(status = 'completed' AND completed_at IS NOT NULL AND completed_at >= claimed_at))
|
|
311
|
+
) STRICT, WITHOUT ROWID;
|
|
312
|
+
|
|
313
|
+
CREATE TABLE host_attestation_dispatches (
|
|
314
|
+
operation_id TEXT PRIMARY KEY REFERENCES host_attestation_operations(operation_id) ON DELETE RESTRICT,
|
|
315
|
+
status TEXT NOT NULL CHECK(status IN ('claimed', 'completed')),
|
|
316
|
+
claimed_at INTEGER NOT NULL, completed_at INTEGER,
|
|
317
|
+
CHECK((status = 'claimed' AND completed_at IS NULL) OR (status = 'completed' AND completed_at IS NOT NULL AND completed_at >= claimed_at))
|
|
318
|
+
) STRICT, WITHOUT ROWID;
|
|
319
|
+
|
|
213
320
|
CREATE TABLE operation_receipts (
|
|
214
321
|
idempotency_key TEXT PRIMARY KEY,
|
|
215
322
|
operation TEXT NOT NULL,
|
|
@@ -252,7 +359,75 @@ function createCurrent(database) {
|
|
|
252
359
|
) STRICT, WITHOUT ROWID;
|
|
253
360
|
CREATE INDEX source_publish_reconciliations_release ON source_publish_reconciliations(plan_id, release_fence, created_at);
|
|
254
361
|
|
|
255
|
-
|
|
362
|
+
-- Post-activation quality watch for a promoted plugin cohort. A watch is an
|
|
363
|
+
-- independent lifecycle from the activation state machine: the activated
|
|
364
|
+
-- state has no exit and its backup/stage are physically removed, so
|
|
365
|
+
-- post-promotion quality is tracked here against the exact immutable
|
|
366
|
+
-- package@version+integrity rather than by trying to re-open an activation.
|
|
367
|
+
CREATE TABLE activation_watch (
|
|
368
|
+
plan_id TEXT PRIMARY KEY,
|
|
369
|
+
package_name TEXT NOT NULL,
|
|
370
|
+
package_version TEXT NOT NULL,
|
|
371
|
+
package_integrity TEXT NOT NULL,
|
|
372
|
+
activation_id TEXT NOT NULL,
|
|
373
|
+
fence INTEGER NOT NULL CHECK(fence >= 1),
|
|
374
|
+
state TEXT NOT NULL CHECK(state IN ('watching', 'closed-regressed', 'closed-retracted')),
|
|
375
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
376
|
+
last_host_generation INTEGER NOT NULL DEFAULT 0 CHECK(last_host_generation >= 0),
|
|
377
|
+
healthy_observations INTEGER NOT NULL DEFAULT 0 CHECK(healthy_observations >= 0),
|
|
378
|
+
close_disposition TEXT CHECK(close_disposition IS NULL OR close_disposition IN ('regressed', 'retracted')),
|
|
379
|
+
close_at INTEGER,
|
|
380
|
+
close_evidence_id TEXT,
|
|
381
|
+
close_signature_digest TEXT CHECK(close_signature_digest IS NULL OR length(close_signature_digest) = 64),
|
|
382
|
+
started_at INTEGER NOT NULL,
|
|
383
|
+
updated_at INTEGER NOT NULL,
|
|
384
|
+
CHECK(
|
|
385
|
+
(state = 'watching' AND close_disposition IS NULL AND close_at IS NULL AND close_evidence_id IS NULL AND close_signature_digest IS NULL) OR
|
|
386
|
+
(state IN ('closed-regressed', 'closed-retracted') AND close_disposition IS NOT NULL AND close_at IS NOT NULL
|
|
387
|
+
AND close_evidence_id IS NOT NULL AND close_signature_digest IS NOT NULL)
|
|
388
|
+
),
|
|
389
|
+
FOREIGN KEY(plan_id) REFERENCES activation_plans(id) ON DELETE RESTRICT
|
|
390
|
+
) STRICT, WITHOUT ROWID;
|
|
391
|
+
|
|
392
|
+
-- Append-only signed observations. Positive healthy rows are kept as
|
|
393
|
+
-- evidence but never close a watch; only a regressed Host observation or an
|
|
394
|
+
-- owner retraction transitions the watch to a terminal state.
|
|
395
|
+
CREATE TABLE activation_watch_evidence (
|
|
396
|
+
observation_id TEXT PRIMARY KEY,
|
|
397
|
+
plan_id TEXT NOT NULL,
|
|
398
|
+
disposition TEXT NOT NULL CHECK(disposition IN ('regressed', 'healthy', 'retracted')),
|
|
399
|
+
receipt_digest TEXT NOT NULL CHECK(length(receipt_digest) = 64),
|
|
400
|
+
signature_digest TEXT NOT NULL CHECK(length(signature_digest) = 64),
|
|
401
|
+
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json) AND json_type(receipt_json) = 'object'),
|
|
402
|
+
host_generation INTEGER NOT NULL DEFAULT 0 CHECK(host_generation >= 0),
|
|
403
|
+
failures INTEGER NOT NULL DEFAULT 0 CHECK(failures >= 0),
|
|
404
|
+
checks INTEGER NOT NULL DEFAULT 0 CHECK(checks >= 0),
|
|
405
|
+
created_at INTEGER NOT NULL,
|
|
406
|
+
FOREIGN KEY(plan_id) REFERENCES activation_watch(plan_id) ON DELETE RESTRICT
|
|
407
|
+
) STRICT, WITHOUT ROWID;
|
|
408
|
+
CREATE INDEX activation_watch_evidence_plan ON activation_watch_evidence(plan_id, created_at);
|
|
409
|
+
|
|
410
|
+
-- Core-file pins captured immediately before terminal promotion. They are
|
|
411
|
+
-- deliberately outside the immutable activation dossier: they describe the
|
|
412
|
+
-- Host-visible deployment and give a post-promotion rollback an ABA-safe
|
|
413
|
+
-- ordering without rewriting the approved plan.
|
|
414
|
+
CREATE TABLE activation_deployment_checkpoints (
|
|
415
|
+
plan_id TEXT PRIMARY KEY REFERENCES activation_plans(id) ON DELETE RESTRICT,
|
|
416
|
+
baseline_json TEXT NOT NULL CHECK(json_valid(baseline_json) AND json_type(baseline_json) = 'array'),
|
|
417
|
+
exposure_order INTEGER NOT NULL UNIQUE CHECK(exposure_order >= 1),
|
|
418
|
+
successful_order INTEGER UNIQUE CHECK(successful_order IS NULL OR successful_order = exposure_order),
|
|
419
|
+
recorded_at INTEGER NOT NULL,
|
|
420
|
+
succeeded_at INTEGER
|
|
421
|
+
) STRICT, WITHOUT ROWID;
|
|
422
|
+
CREATE INDEX activation_deployment_checkpoints_success ON activation_deployment_checkpoints(successful_order)
|
|
423
|
+
WHERE successful_order IS NOT NULL;
|
|
424
|
+
CREATE TABLE activation_deployment_sequence (
|
|
425
|
+
singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
|
|
426
|
+
next_exposure_order INTEGER NOT NULL CHECK(next_exposure_order >= 1)
|
|
427
|
+
) STRICT, WITHOUT ROWID;
|
|
428
|
+
INSERT INTO activation_deployment_sequence (singleton, next_exposure_order) VALUES (1, 1);
|
|
429
|
+
|
|
430
|
+
PRAGMA user_version = 18;
|
|
256
431
|
`);
|
|
257
432
|
}
|
|
258
433
|
function migrateV1(database) {
|
|
@@ -666,6 +841,290 @@ function migrateV10ToV11(database) {
|
|
|
666
841
|
throw error;
|
|
667
842
|
}
|
|
668
843
|
}
|
|
844
|
+
function migrateV11ToV12(database) {
|
|
845
|
+
// IF NOT EXISTS makes the migration idempotent when a database was created with
|
|
846
|
+
// the current schema and its user_version was rolled back to exercise the full
|
|
847
|
+
// migration chain (the v6/v8 fixtures do this). A real v11 database can never
|
|
848
|
+
// contain these table names, so the guard cannot mask a structurally different
|
|
849
|
+
// legacy table in production.
|
|
850
|
+
database.exec('BEGIN IMMEDIATE');
|
|
851
|
+
try {
|
|
852
|
+
database.exec(`
|
|
853
|
+
CREATE TABLE IF NOT EXISTS activation_watch (
|
|
854
|
+
plan_id TEXT PRIMARY KEY,
|
|
855
|
+
package_name TEXT NOT NULL,
|
|
856
|
+
package_version TEXT NOT NULL,
|
|
857
|
+
package_integrity TEXT NOT NULL,
|
|
858
|
+
activation_id TEXT NOT NULL,
|
|
859
|
+
fence INTEGER NOT NULL CHECK(fence >= 1),
|
|
860
|
+
state TEXT NOT NULL CHECK(state IN ('watching', 'closed-regressed', 'closed-retracted')),
|
|
861
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
862
|
+
last_host_generation INTEGER NOT NULL DEFAULT 0 CHECK(last_host_generation >= 0),
|
|
863
|
+
healthy_observations INTEGER NOT NULL DEFAULT 0 CHECK(healthy_observations >= 0),
|
|
864
|
+
close_disposition TEXT CHECK(close_disposition IS NULL OR close_disposition IN ('regressed', 'retracted')),
|
|
865
|
+
close_at INTEGER,
|
|
866
|
+
close_evidence_id TEXT,
|
|
867
|
+
close_signature_digest TEXT CHECK(close_signature_digest IS NULL OR length(close_signature_digest) = 64),
|
|
868
|
+
started_at INTEGER NOT NULL,
|
|
869
|
+
updated_at INTEGER NOT NULL,
|
|
870
|
+
CHECK(
|
|
871
|
+
(state = 'watching' AND close_disposition IS NULL AND close_at IS NULL AND close_evidence_id IS NULL AND close_signature_digest IS NULL) OR
|
|
872
|
+
(state IN ('closed-regressed', 'closed-retracted') AND close_disposition IS NOT NULL AND close_at IS NOT NULL
|
|
873
|
+
AND close_evidence_id IS NOT NULL AND close_signature_digest IS NOT NULL)
|
|
874
|
+
),
|
|
875
|
+
FOREIGN KEY(plan_id) REFERENCES activation_plans(id) ON DELETE RESTRICT
|
|
876
|
+
) STRICT, WITHOUT ROWID;
|
|
877
|
+
|
|
878
|
+
CREATE TABLE IF NOT EXISTS activation_watch_evidence (
|
|
879
|
+
observation_id TEXT PRIMARY KEY,
|
|
880
|
+
plan_id TEXT NOT NULL,
|
|
881
|
+
disposition TEXT NOT NULL CHECK(disposition IN ('regressed', 'healthy', 'retracted')),
|
|
882
|
+
receipt_digest TEXT NOT NULL CHECK(length(receipt_digest) = 64),
|
|
883
|
+
signature_digest TEXT NOT NULL CHECK(length(signature_digest) = 64),
|
|
884
|
+
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json) AND json_type(receipt_json) = 'object'),
|
|
885
|
+
host_generation INTEGER NOT NULL DEFAULT 0 CHECK(host_generation >= 0),
|
|
886
|
+
failures INTEGER NOT NULL DEFAULT 0 CHECK(failures >= 0),
|
|
887
|
+
checks INTEGER NOT NULL DEFAULT 0 CHECK(checks >= 0),
|
|
888
|
+
created_at INTEGER NOT NULL,
|
|
889
|
+
FOREIGN KEY(plan_id) REFERENCES activation_watch(plan_id) ON DELETE RESTRICT
|
|
890
|
+
) STRICT, WITHOUT ROWID;
|
|
891
|
+
CREATE INDEX IF NOT EXISTS activation_watch_evidence_plan ON activation_watch_evidence(plan_id, created_at);
|
|
892
|
+
`);
|
|
893
|
+
// Backfill watches for plans already promoted before the watch lifecycle existed.
|
|
894
|
+
// The exact target is reconstructed from the immutable candidate snapshot pinned
|
|
895
|
+
// in the plan row (candidate_json), never from a mutable current catalog.
|
|
896
|
+
const activated = database.prepare(`SELECT id, candidate_json, activation_id, activation_fence, updated_at
|
|
897
|
+
FROM activation_plans WHERE status = 'activated'`).all();
|
|
898
|
+
const insert = database.prepare(`INSERT INTO activation_watch (plan_id, package_name, package_version, package_integrity,
|
|
899
|
+
activation_id, fence, state, revision, last_host_generation, healthy_observations, started_at, updated_at)
|
|
900
|
+
VALUES (?, ?, ?, ?, ?, ?, 'watching', 1, 0, 0, ?, ?)`);
|
|
901
|
+
for (const row of activated) {
|
|
902
|
+
const candidate = JSON.parse(row.candidate_json);
|
|
903
|
+
if (typeof candidate.package !== 'string' || typeof candidate.version !== 'string' || typeof candidate.integrity !== 'string'
|
|
904
|
+
|| typeof row.activation_id !== 'string' || row.activation_fence < 1) {
|
|
905
|
+
throw new ControlPlaneDatabaseError('unsafe-file', 'legacy activated plan has an invalid immutable candidate binding');
|
|
906
|
+
}
|
|
907
|
+
insert.run(row.id, candidate.package, candidate.version, candidate.integrity, row.activation_id, row.activation_fence, row.updated_at, row.updated_at);
|
|
908
|
+
}
|
|
909
|
+
database.exec('PRAGMA user_version = 12; COMMIT');
|
|
910
|
+
}
|
|
911
|
+
catch (error) {
|
|
912
|
+
database.exec('ROLLBACK');
|
|
913
|
+
throw error;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function migrateV12ToV13(database) {
|
|
917
|
+
// Add the 'modify' source-plan mode: a growth executor may prepare a patch for
|
|
918
|
+
// an *existing* plugin in an isolated worktree and attach its frozen-build
|
|
919
|
+
// evidence while the plan is still pending owner approval. SQLite cannot widen
|
|
920
|
+
// a CHECK in place, so rebuild source_plans and copy every column verbatim;
|
|
921
|
+
// every pre-existing row is backfilled to mode='create' with no prepared
|
|
922
|
+
// evidence. Existing rows are all create-mode and retain their immutable
|
|
923
|
+
// digest. Build the replacement under a temporary name before dropping the
|
|
924
|
+
// parent, then rename it into place: renaming the old parent would rewrite
|
|
925
|
+
// dependent foreign-key declarations to the temporary name.
|
|
926
|
+
database.exec(`
|
|
927
|
+
PRAGMA foreign_keys = OFF;
|
|
928
|
+
BEGIN IMMEDIATE;
|
|
929
|
+
CREATE TABLE source_plans_v13 (
|
|
930
|
+
id TEXT PRIMARY KEY,
|
|
931
|
+
plan_digest TEXT NOT NULL UNIQUE CHECK(length(plan_digest) = 64),
|
|
932
|
+
gap_id TEXT NOT NULL,
|
|
933
|
+
gap_snapshot_json TEXT NOT NULL CHECK(json_valid(gap_snapshot_json) AND json_type(gap_snapshot_json) = 'object'),
|
|
934
|
+
repository TEXT NOT NULL,
|
|
935
|
+
worktree TEXT NOT NULL,
|
|
936
|
+
base_commit TEXT NOT NULL CHECK(length(base_commit) = 40),
|
|
937
|
+
plugin_name TEXT NOT NULL,
|
|
938
|
+
generator_digest TEXT NOT NULL CHECK(length(generator_digest) = 64),
|
|
939
|
+
scope_json TEXT NOT NULL CHECK(json_valid(scope_json) AND json_type(scope_json) = 'array'),
|
|
940
|
+
mode TEXT NOT NULL DEFAULT 'create' CHECK(mode IN ('create', 'modify')),
|
|
941
|
+
status TEXT NOT NULL CHECK(status IN (
|
|
942
|
+
'expired', 'pending-approval', 'approved', 'running-local-checks', 'ready-for-human-review', 'local-checks-failed',
|
|
943
|
+
'awaiting-pr', 'awaiting-review', 'awaiting-merge', 'awaiting-build', 'awaiting-sign', 'awaiting-publish',
|
|
944
|
+
'awaiting-registry-verify', 'awaiting-catalog-admission', 'release-complete', 'release-failed', 'publish-ambiguous'
|
|
945
|
+
)),
|
|
946
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
947
|
+
created_at INTEGER NOT NULL,
|
|
948
|
+
expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
|
|
949
|
+
approval_json TEXT CHECK(approval_json IS NULL OR json_valid(approval_json)),
|
|
950
|
+
checked_tree_digest TEXT CHECK(checked_tree_digest IS NULL OR (length(checked_tree_digest) = 64 AND checked_tree_digest NOT GLOB '*[^a-f0-9]*')),
|
|
951
|
+
checked_patch_digest TEXT CHECK(checked_patch_digest IS NULL OR (length(checked_patch_digest) = 64 AND checked_patch_digest NOT GLOB '*[^a-f0-9]*')),
|
|
952
|
+
checked_at INTEGER,
|
|
953
|
+
prepared_evidence_json TEXT CHECK(prepared_evidence_json IS NULL OR
|
|
954
|
+
(json_valid(prepared_evidence_json) AND json_type(prepared_evidence_json) = 'object')),
|
|
955
|
+
release_authorization_json TEXT CHECK(release_authorization_json IS NULL OR (json_valid(release_authorization_json) AND json_type(release_authorization_json) = 'object')),
|
|
956
|
+
release_authorization_digest TEXT CHECK(release_authorization_digest IS NULL OR length(release_authorization_digest) = 64),
|
|
957
|
+
release_id TEXT,
|
|
958
|
+
release_fence INTEGER NOT NULL DEFAULT 0 CHECK(release_fence >= 0),
|
|
959
|
+
release_failure_phase TEXT CHECK(release_failure_phase IS NULL OR release_failure_phase IN (
|
|
960
|
+
'pr', 'review', 'merge', 'build', 'sign', 'publish', 'registry-verify', 'catalog-admission'
|
|
961
|
+
)),
|
|
962
|
+
release_failure_code TEXT,
|
|
963
|
+
updated_at INTEGER NOT NULL,
|
|
964
|
+
CHECK((checked_tree_digest IS NULL AND checked_patch_digest IS NULL AND checked_at IS NULL) OR
|
|
965
|
+
(checked_tree_digest IS NOT NULL AND checked_patch_digest IS NOT NULL AND checked_at IS NOT NULL)),
|
|
966
|
+
CHECK(mode = 'create' OR
|
|
967
|
+
(checked_tree_digest IS NOT NULL AND checked_patch_digest IS NOT NULL AND checked_at IS NOT NULL)),
|
|
968
|
+
CHECK((mode = 'create' AND prepared_evidence_json IS NULL) OR
|
|
969
|
+
(mode = 'modify' AND prepared_evidence_json IS NOT NULL)),
|
|
970
|
+
CHECK((release_authorization_json IS NULL AND release_authorization_digest IS NULL) OR
|
|
971
|
+
(release_authorization_json IS NOT NULL AND release_authorization_digest IS NOT NULL)),
|
|
972
|
+
FOREIGN KEY(gap_id) REFERENCES capability_gaps(id) ON DELETE RESTRICT
|
|
973
|
+
) STRICT;
|
|
974
|
+
INSERT INTO source_plans_v13 (id, plan_digest, gap_id, gap_snapshot_json, repository, worktree, base_commit,
|
|
975
|
+
plugin_name, generator_digest, scope_json, mode, status, revision, created_at, expires_at, approval_json,
|
|
976
|
+
checked_tree_digest, checked_patch_digest, checked_at, prepared_evidence_json,
|
|
977
|
+
release_authorization_json, release_authorization_digest, release_id, release_fence,
|
|
978
|
+
release_failure_phase, release_failure_code, updated_at)
|
|
979
|
+
SELECT id, plan_digest, gap_id, gap_snapshot_json, repository, worktree, base_commit, plugin_name,
|
|
980
|
+
generator_digest, scope_json, 'create', status, revision, created_at, expires_at, approval_json,
|
|
981
|
+
checked_tree_digest, checked_patch_digest, checked_at, NULL,
|
|
982
|
+
release_authorization_json, release_authorization_digest, release_id, release_fence,
|
|
983
|
+
release_failure_phase, release_failure_code, updated_at FROM source_plans;
|
|
984
|
+
DROP TABLE source_plans;
|
|
985
|
+
ALTER TABLE source_plans_v13 RENAME TO source_plans;
|
|
986
|
+
PRAGMA user_version = 13;
|
|
987
|
+
COMMIT;
|
|
988
|
+
PRAGMA foreign_keys = ON;
|
|
989
|
+
`);
|
|
990
|
+
}
|
|
991
|
+
function migrateV13ToV14(database) {
|
|
992
|
+
database.exec(`
|
|
993
|
+
BEGIN IMMEDIATE;
|
|
994
|
+
CREATE TABLE IF NOT EXISTS source_job_authorities (
|
|
995
|
+
authority_id TEXT PRIMARY KEY,
|
|
996
|
+
authority_digest TEXT NOT NULL CHECK(length(authority_digest) = 64),
|
|
997
|
+
expires_at INTEGER NOT NULL,
|
|
998
|
+
max_submissions INTEGER NOT NULL CHECK(max_submissions >= 1),
|
|
999
|
+
submissions INTEGER NOT NULL CHECK(submissions >= 0 AND submissions <= max_submissions)
|
|
1000
|
+
) STRICT, WITHOUT ROWID;
|
|
1001
|
+
CREATE TABLE IF NOT EXISTS source_jobs (
|
|
1002
|
+
id TEXT PRIMARY KEY,
|
|
1003
|
+
automation_id TEXT NOT NULL UNIQUE,
|
|
1004
|
+
authority_id TEXT NOT NULL,
|
|
1005
|
+
idempotency_key TEXT NOT NULL,
|
|
1006
|
+
intent_json TEXT NOT NULL CHECK(json_valid(intent_json) AND json_type(intent_json) = 'object'),
|
|
1007
|
+
intent_digest TEXT NOT NULL CHECK(length(intent_digest) = 64),
|
|
1008
|
+
status TEXT NOT NULL CHECK(status IN ('queued', 'running', 'prepared', 'failed', 'unknown')),
|
|
1009
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
1010
|
+
created_at INTEGER NOT NULL,
|
|
1011
|
+
expires_at INTEGER NOT NULL,
|
|
1012
|
+
definition_hash TEXT,
|
|
1013
|
+
occurrence_id TEXT,
|
|
1014
|
+
plan_id TEXT UNIQUE,
|
|
1015
|
+
failure_code TEXT,
|
|
1016
|
+
updated_at INTEGER NOT NULL,
|
|
1017
|
+
UNIQUE(authority_id, idempotency_key),
|
|
1018
|
+
FOREIGN KEY(authority_id) REFERENCES source_job_authorities(authority_id) ON DELETE RESTRICT,
|
|
1019
|
+
FOREIGN KEY(plan_id) REFERENCES source_plans(id) ON DELETE RESTRICT,
|
|
1020
|
+
CHECK((status = 'queued' AND occurrence_id IS NULL AND plan_id IS NULL AND failure_code IS NULL)
|
|
1021
|
+
OR (status = 'running' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NULL AND failure_code IS NULL)
|
|
1022
|
+
OR (status = 'prepared' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NOT NULL AND failure_code IS NULL)
|
|
1023
|
+
OR (status = 'failed' AND plan_id IS NULL AND failure_code IS NOT NULL)
|
|
1024
|
+
OR (status = 'unknown' AND definition_hash IS NOT NULL AND occurrence_id IS NOT NULL AND plan_id IS NULL AND failure_code IS NOT NULL))
|
|
1025
|
+
) STRICT;
|
|
1026
|
+
CREATE UNIQUE INDEX IF NOT EXISTS source_jobs_single_active ON source_jobs((1)) WHERE status IN ('queued', 'running', 'unknown');
|
|
1027
|
+
CREATE INDEX IF NOT EXISTS source_jobs_created ON source_jobs(created_at, id);
|
|
1028
|
+
PRAGMA user_version = 14;
|
|
1029
|
+
COMMIT;
|
|
1030
|
+
`);
|
|
1031
|
+
}
|
|
1032
|
+
function migrateV14ToV15(database) {
|
|
1033
|
+
const columns = new Set(database.prepare('PRAGMA table_info(activation_plans)').all().map(row => row.name));
|
|
1034
|
+
const hasRollbackPhase = database.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'host_attestations'").get()?.sql?.includes("'rollback'") === true;
|
|
1035
|
+
database.exec(`
|
|
1036
|
+
BEGIN IMMEDIATE;
|
|
1037
|
+
${columns.has('activation_target_baseline_json') ? '' : "ALTER TABLE activation_plans ADD COLUMN activation_target_baseline_json TEXT CHECK(activation_target_baseline_json IS NULL OR (json_valid(activation_target_baseline_json) AND json_type(activation_target_baseline_json) = 'array'));"}
|
|
1038
|
+
${columns.has('host_recovery_required') ? '' : 'ALTER TABLE activation_plans ADD COLUMN host_recovery_required INTEGER NOT NULL DEFAULT 0 CHECK(host_recovery_required IN (0, 1));'}
|
|
1039
|
+
${columns.has('rollback_profile_restored') ? '' : 'ALTER TABLE activation_plans ADD COLUMN rollback_profile_restored INTEGER NOT NULL DEFAULT 0 CHECK(rollback_profile_restored IN (0, 1));'}
|
|
1040
|
+
-- Older active Host states have no trustworthy core-file baseline. Keep them
|
|
1041
|
+
-- pending recovery, never manufacture a baseline from mutable files.
|
|
1042
|
+
UPDATE activation_plans SET host_recovery_required = 1
|
|
1043
|
+
WHERE status IN ('awaiting-reload', 'awaiting-readiness', 'awaiting-effect-blocked-replay', 'awaiting-shadow', 'awaiting-canary', 'awaiting-soak', 'awaiting-health', 'commit-pending', 'rollback-pending');
|
|
1044
|
+
${hasRollbackPhase ? '' : 'ALTER TABLE host_attestations RENAME TO host_attestations_v14;'}
|
|
1045
|
+
CREATE TABLE IF NOT EXISTS host_attestations (
|
|
1046
|
+
plan_id TEXT NOT NULL, phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health', 'rollback')),
|
|
1047
|
+
receipt_id TEXT NOT NULL UNIQUE, receipt_digest TEXT NOT NULL CHECK(length(receipt_digest) = 64),
|
|
1048
|
+
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json) AND json_type(receipt_json) = 'object'), host_generation INTEGER NOT NULL CHECK(host_generation >= 1),
|
|
1049
|
+
created_at INTEGER NOT NULL, PRIMARY KEY(plan_id, phase), FOREIGN KEY(plan_id) REFERENCES activation_plans(id) ON DELETE RESTRICT
|
|
1050
|
+
) STRICT, WITHOUT ROWID;
|
|
1051
|
+
${hasRollbackPhase ? '' : 'INSERT INTO host_attestations SELECT * FROM host_attestations_v14; DROP TABLE host_attestations_v14;'}
|
|
1052
|
+
${hasRollbackPhase ? '' : 'ALTER TABLE host_attestation_operations RENAME TO host_attestation_operations_v14;'}
|
|
1053
|
+
CREATE TABLE IF NOT EXISTS host_attestation_operations (
|
|
1054
|
+
plan_id TEXT NOT NULL, phase TEXT NOT NULL CHECK(phase IN ('reload', 'readiness', 'effect-blocked-replay', 'shadow', 'canary', 'soak', 'health', 'rollback')),
|
|
1055
|
+
operation_id TEXT NOT NULL UNIQUE, binding_digest TEXT NOT NULL CHECK(length(binding_digest) = 64), request_digest TEXT NOT NULL CHECK(length(request_digest) = 64),
|
|
1056
|
+
request_json TEXT NOT NULL CHECK(json_valid(request_json) AND json_type(request_json) = 'object'), status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'applied')),
|
|
1057
|
+
receipt_digest TEXT CHECK(receipt_digest IS NULL OR length(receipt_digest) = 64), receipt_json TEXT CHECK(receipt_json IS NULL OR (json_valid(receipt_json) AND json_type(receipt_json) = 'object')),
|
|
1058
|
+
created_at INTEGER NOT NULL, completed_at INTEGER, applied_at INTEGER, PRIMARY KEY(plan_id, phase), FOREIGN KEY(plan_id) REFERENCES activation_plans(id) ON DELETE RESTRICT
|
|
1059
|
+
) STRICT, WITHOUT ROWID;
|
|
1060
|
+
${hasRollbackPhase ? '' : 'INSERT INTO host_attestation_operations SELECT * FROM host_attestation_operations_v14; DROP TABLE host_attestation_operations_v14;'}
|
|
1061
|
+
PRAGMA user_version = 15;
|
|
1062
|
+
COMMIT;
|
|
1063
|
+
`);
|
|
1064
|
+
}
|
|
1065
|
+
function migrateV15ToV16(database) {
|
|
1066
|
+
database.exec(`
|
|
1067
|
+
BEGIN IMMEDIATE;
|
|
1068
|
+
CREATE TABLE IF NOT EXISTS owner_task_failure_gaps (
|
|
1069
|
+
gap_id TEXT PRIMARY KEY REFERENCES capability_gaps(id) ON DELETE RESTRICT,
|
|
1070
|
+
reference_json TEXT NOT NULL CHECK(json_valid(reference_json) AND json_type(reference_json) = 'object'),
|
|
1071
|
+
reference_digest TEXT NOT NULL CHECK(length(reference_digest) = 64)
|
|
1072
|
+
) STRICT, WITHOUT ROWID;
|
|
1073
|
+
PRAGMA user_version = 16;
|
|
1074
|
+
COMMIT;
|
|
1075
|
+
`);
|
|
1076
|
+
}
|
|
1077
|
+
function migrateV16ToV17(database) {
|
|
1078
|
+
database.exec(`
|
|
1079
|
+
BEGIN IMMEDIATE;
|
|
1080
|
+
CREATE TABLE IF NOT EXISTS activation_deployment_checkpoints (
|
|
1081
|
+
plan_id TEXT PRIMARY KEY REFERENCES activation_plans(id) ON DELETE RESTRICT,
|
|
1082
|
+
baseline_json TEXT NOT NULL CHECK(json_valid(baseline_json) AND json_type(baseline_json) = 'array'),
|
|
1083
|
+
exposure_order INTEGER NOT NULL UNIQUE CHECK(exposure_order >= 1),
|
|
1084
|
+
successful_order INTEGER UNIQUE CHECK(successful_order IS NULL OR successful_order = exposure_order),
|
|
1085
|
+
recorded_at INTEGER NOT NULL,
|
|
1086
|
+
succeeded_at INTEGER
|
|
1087
|
+
) STRICT, WITHOUT ROWID;
|
|
1088
|
+
CREATE INDEX IF NOT EXISTS activation_deployment_checkpoints_success ON activation_deployment_checkpoints(successful_order)
|
|
1089
|
+
WHERE successful_order IS NOT NULL;
|
|
1090
|
+
CREATE TABLE IF NOT EXISTS activation_deployment_sequence (
|
|
1091
|
+
singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
|
|
1092
|
+
next_exposure_order INTEGER NOT NULL CHECK(next_exposure_order >= 1)
|
|
1093
|
+
) STRICT, WITHOUT ROWID;
|
|
1094
|
+
INSERT OR IGNORE INTO activation_deployment_sequence (singleton, next_exposure_order) VALUES (1, 1);
|
|
1095
|
+
-- Historical promotions did not retain a verified installed core-file pin.
|
|
1096
|
+
-- Do not invent one from mutable profile contents: they remain observable,
|
|
1097
|
+
-- but cannot be physically rolled back by this new path.
|
|
1098
|
+
PRAGMA user_version = 17;
|
|
1099
|
+
COMMIT;
|
|
1100
|
+
`);
|
|
1101
|
+
}
|
|
1102
|
+
function migrateV17ToV18(database) {
|
|
1103
|
+
database.exec(`
|
|
1104
|
+
PRAGMA foreign_keys = OFF;
|
|
1105
|
+
BEGIN IMMEDIATE;
|
|
1106
|
+
CREATE TABLE IF NOT EXISTS source_release_dispatches (
|
|
1107
|
+
operation_id TEXT PRIMARY KEY REFERENCES source_release_operations(operation_id) ON DELETE RESTRICT,
|
|
1108
|
+
status TEXT NOT NULL CHECK(status IN ('claimed', 'completed')),
|
|
1109
|
+
claimed_at INTEGER NOT NULL,
|
|
1110
|
+
completed_at INTEGER,
|
|
1111
|
+
CHECK((status = 'claimed' AND completed_at IS NULL) OR
|
|
1112
|
+
(status = 'completed' AND completed_at IS NOT NULL AND completed_at >= claimed_at))
|
|
1113
|
+
) STRICT, WITHOUT ROWID;
|
|
1114
|
+
-- A v17 pending operation may already have reached an external adapter
|
|
1115
|
+
-- before this binary could persist its signed receipt. Preserve that
|
|
1116
|
+
-- uncertainty; do not infer that dispatch is safe to repeat.
|
|
1117
|
+
INSERT OR IGNORE INTO source_release_dispatches (operation_id, status, claimed_at, completed_at)
|
|
1118
|
+
SELECT operation_id,
|
|
1119
|
+
CASE WHEN status = 'pending' THEN 'claimed' ELSE 'completed' END,
|
|
1120
|
+
created_at,
|
|
1121
|
+
CASE WHEN status = 'pending' THEN NULL ELSE completed_at END
|
|
1122
|
+
FROM source_release_operations;
|
|
1123
|
+
PRAGMA user_version = 18;
|
|
1124
|
+
COMMIT;
|
|
1125
|
+
PRAGMA foreign_keys = ON;
|
|
1126
|
+
`);
|
|
1127
|
+
}
|
|
669
1128
|
export function openControlPlaneDatabase(path) {
|
|
670
1129
|
prepare(path);
|
|
671
1130
|
const database = new DatabaseSync(path);
|
|
@@ -697,7 +1156,50 @@ export function openControlPlaneDatabase(path) {
|
|
|
697
1156
|
migrateV9ToV10(database);
|
|
698
1157
|
if (version <= 10)
|
|
699
1158
|
migrateV10ToV11(database);
|
|
1159
|
+
if (version <= 11)
|
|
1160
|
+
migrateV11ToV12(database);
|
|
1161
|
+
if (version <= 12)
|
|
1162
|
+
migrateV12ToV13(database);
|
|
1163
|
+
if (version <= 13)
|
|
1164
|
+
migrateV13ToV14(database);
|
|
1165
|
+
if (version <= 14)
|
|
1166
|
+
migrateV14ToV15(database);
|
|
1167
|
+
if (version <= 15)
|
|
1168
|
+
migrateV15ToV16(database);
|
|
1169
|
+
if (version <= 16)
|
|
1170
|
+
migrateV16ToV17(database);
|
|
1171
|
+
if (version <= 17)
|
|
1172
|
+
migrateV17ToV18(database);
|
|
1173
|
+
}
|
|
1174
|
+
if (Number(database.prepare('PRAGMA user_version').get()?.user_version) < 19) {
|
|
1175
|
+
database.exec(`BEGIN IMMEDIATE; ${sourceAdoptionsSchema} PRAGMA user_version = 19; COMMIT;`);
|
|
1176
|
+
}
|
|
1177
|
+
else
|
|
1178
|
+
database.exec(sourceAdoptionsSchema);
|
|
1179
|
+
if (Number(database.prepare('PRAGMA user_version').get()?.user_version) < 20) {
|
|
1180
|
+
database.exec(`BEGIN IMMEDIATE; ${foregroundDeploymentsSchema} PRAGMA user_version = 20; COMMIT;`);
|
|
1181
|
+
}
|
|
1182
|
+
else
|
|
1183
|
+
database.exec(foregroundDeploymentsSchema);
|
|
1184
|
+
if (Number(database.prepare('PRAGMA user_version').get()?.user_version) < 21) {
|
|
1185
|
+
database.exec(`BEGIN IMMEDIATE; ${taskObservationsSchema} PRAGMA user_version = 21; COMMIT;`);
|
|
1186
|
+
}
|
|
1187
|
+
else
|
|
1188
|
+
database.exec(taskObservationsSchema);
|
|
1189
|
+
if (Number(database.prepare('PRAGMA user_version').get()?.user_version) < 22) {
|
|
1190
|
+
database.exec(`BEGIN IMMEDIATE; ${hostAttestationDispatchesSchema}
|
|
1191
|
+
INSERT OR IGNORE INTO host_attestation_dispatches (operation_id,status,claimed_at,completed_at)
|
|
1192
|
+
SELECT operation_id, CASE WHEN status = 'pending' THEN 'claimed' ELSE 'completed' END, created_at,
|
|
1193
|
+
CASE WHEN status = 'pending' THEN NULL ELSE completed_at END FROM host_attestation_operations;
|
|
1194
|
+
PRAGMA user_version = 22; COMMIT;`);
|
|
1195
|
+
}
|
|
1196
|
+
else
|
|
1197
|
+
database.exec(hostAttestationDispatchesSchema);
|
|
1198
|
+
if (Number(database.prepare('PRAGMA user_version').get()?.user_version) < 23) {
|
|
1199
|
+
database.exec(`BEGIN IMMEDIATE; ${adoptionHandoffsSchema} PRAGMA user_version = 23; COMMIT;`);
|
|
700
1200
|
}
|
|
1201
|
+
else
|
|
1202
|
+
database.exec(adoptionHandoffsSchema);
|
|
701
1203
|
database.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;');
|
|
702
1204
|
return database;
|
|
703
1205
|
}
|