@straiffi/archon 1.8.20 → 1.8.22
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/dist/client/assets/{ActivityView-DDE3U1zB.js → ActivityView-CdEPjmqD.js} +1 -1
- package/dist/client/assets/GitHubActionsDialog-CA189pr2.js +1 -0
- package/dist/client/assets/VerificationDialog-BPVkhKRj.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-oPUmRpZr.js → architectureDiagram-3BPJPVTR-BrZ08mqq.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-0sq1jkkp.js → blockDiagram-GPEHLZMM-b93XwJ6Z.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BeDxMy7J.js → c4Diagram-AAUBKEIU-BKOdbZ_v.js} +1 -1
- package/dist/client/assets/channel-D0zqzBT_.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-D8VGsje2.js → chunk-2J33WTMH-D7Vxn_1m.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DuTUTeBe.js → chunk-3OPIFGDE-B1zo2UZq.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BzX5dPNW.js → chunk-5ZQYHXKU-BajvkDdQ.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DS9putxN.js → chunk-727SXJPM-CSH7cVpV.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CWzFZRBv.js → chunk-AQP2D5EJ-DE-wgUNP.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DXZ-ADYW.js → chunk-CSCIHK7Q-Ba7JgCEf.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-BD7mL3KK.js → chunk-KSCS5N6A-0o3YWkHM.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DmYQi9AP.js → chunk-L5ZTLDWV-DihWczP8.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-CZyFYHvS.js → chunk-LZXEDZCA-Bxnu-hme.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-BxdjvT9I.js → chunk-ND2GUHAM-CoaR5kmP.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BwuZcZ_9.js → chunk-NZK2D7GU-By92Rd3d.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-CC-TZXr3.js → chunk-O5CBEL6O-D83Pkgfu.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CJZJkca2.js → chunk-WU5MYG2G-v8OVf5Lz.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChhyvHH2.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Cl02kurK.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-D2L5RvPF.js → dagre-BM42HDAG-Dq1ALF7Z.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DBJzxSqZ.js → diagram-2AECGRRQ-1t1_TKtu.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-J_1M-Wou.js → diagram-5GNKFQAL-DtvYRL6f.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CmT1Vbmc.js → diagram-KO2AKTUF-PRNrSX5F.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DfPoJyQI.js → diagram-LMA3HP47-DaTX0DhT.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-D4uEb6vo.js → diagram-OG6HWLK6-C2p9p-92.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-l7yIFj0e.js → erDiagram-TEJ5UH35-DKzLtnp-.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-B3caTMes.js → flowDiagram-I6XJVG4X-GdL8lm6e.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-9Am36v9m.js → ganttDiagram-6RSMTGT7-Ok-4XYwT.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-B7OjYsv_.js → gitGraphDiagram-PVQCEYII-DSMrOCK7.js} +1 -1
- package/dist/client/assets/index-Bs8KsdV9.js +276 -0
- package/dist/client/assets/index-DdwM8Wgt.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-ZAcNejkW.js → infoDiagram-5YYISTIA-Dl4hRXis.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BaaJwuN0.js → ishikawaDiagram-YF4QCWOH-C1Dc1fTT.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DjzTU3kM.js → journeyDiagram-JHISSGLW-CUscmJze.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DYRchzub.js → kanban-definition-UN3LZRKU-C0nwYjQV.js} +1 -1
- package/dist/client/assets/{line-X0j9gj61.js → line-ByiuEIg_.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-DCKG87Vl.js → mermaid-parser.core-B-XdAyDR.js} +1 -1
- package/dist/client/assets/{mermaid.core-BxI5T2gs.js → mermaid.core-D_bIfuZD.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BE7MZzQO.js → mindmap-definition-RKZ34NQL-BGNiPJDK.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-D3fTYovg.js → pieDiagram-4H26LBE5-CP6FpAoO.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-B35nXRdQ.js → quadrantDiagram-W4KKPZXB-dsAWBAQn.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CLBcHvP0.js → requirementDiagram-4Y6WPE33-DJH-zHqF.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DdBCxGHF.js → sankeyDiagram-5OEKKPKP-BJMnZrqH.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-KcLt0X1K.js → sequenceDiagram-3UESZ5HK-CzNjUSlM.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Bq23ObLK.js → stateDiagram-AJRCARHV-jppMhOwh.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CLR1WgdV.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-SK3oVUD9.js → timeline-definition-PNZ67QCA-BX2CI5H_.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-COVtAlEC.js → vennDiagram-CIIHVFJN-Bd1KZaXh.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BvfOOwga.js → wardleyDiagram-YWT4CUSO-D-0BNz49.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DFnAD9k4.js → xychartDiagram-2RQKCTM6-DbYa0ZWK.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +780 -16
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +198 -20
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +8 -4
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +8 -1
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/approvalReconciliation.js +101 -0
- package/dist/server/lib/approvalReconciliation.js.map +1 -0
- package/dist/server/lib/approvalRequests.js +587 -0
- package/dist/server/lib/approvalRequests.js.map +1 -0
- package/dist/server/lib/approvalRoutes.js +143 -0
- package/dist/server/lib/approvalRoutes.js.map +1 -0
- package/dist/server/lib/automationProgression.js +44 -14
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/buildCompletionContinuation.js +14 -3
- package/dist/server/lib/buildCompletionContinuation.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +350 -66
- package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/buildOpenQuestionOperations.js +47 -3
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
- package/dist/server/lib/buildSequences.js +267 -18
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/buildSetupAuthority.js +138 -0
- package/dist/server/lib/buildSetupAuthority.js.map +1 -0
- package/dist/server/lib/bundleReviewOperations.js +15 -4
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/checkDefinitions.js +29 -1
- package/dist/server/lib/checkDefinitions.js.map +1 -1
- package/dist/server/lib/checkRunner.js +306 -27
- package/dist/server/lib/checkRunner.js.map +1 -1
- package/dist/server/lib/checkRuns.js +4 -2
- package/dist/server/lib/checkRuns.js.map +1 -1
- package/dist/server/lib/consequentialActionAuthority.js +142 -0
- package/dist/server/lib/consequentialActionAuthority.js.map +1 -0
- package/dist/server/lib/consequentialActions.js +231 -0
- package/dist/server/lib/consequentialActions.js.map +1 -0
- package/dist/server/lib/dockerSandboxCredentials.js +64 -10
- package/dist/server/lib/dockerSandboxCredentials.js.map +1 -1
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js +119 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +174 -11
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionProvider.js +12 -3
- package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
- package/dist/server/lib/dockerSandboxNetworkPolicy.js +222 -0
- package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -0
- package/dist/server/lib/executionCredentialCapabilities.js +106 -0
- package/dist/server/lib/executionCredentialCapabilities.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -1
- package/dist/server/lib/executionEnvironmentContexts.js +59 -3
- package/dist/server/lib/executionEnvironmentContexts.js.map +1 -1
- package/dist/server/lib/executionEnvironmentLifecycle.js +34 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
- package/dist/server/lib/executionEnvironmentRoutes.js +127 -9
- package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
- package/dist/server/lib/executionEnvironmentValidation.js +169 -45
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
- package/dist/server/lib/executionEnvironmentValidationOperations.js +203 -19
- package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
- package/dist/server/lib/executionEnvironments.js +103 -14
- package/dist/server/lib/executionEnvironments.js.map +1 -1
- package/dist/server/lib/executionProfileRoutes.js +67 -0
- package/dist/server/lib/executionProfileRoutes.js.map +1 -0
- package/dist/server/lib/executionProfileStore.js +117 -0
- package/dist/server/lib/executionProfileStore.js.map +1 -0
- package/dist/server/lib/executionProfiles.js +324 -0
- package/dist/server/lib/executionProfiles.js.map +1 -0
- package/dist/server/lib/executionReconciliation.js +345 -197
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +24 -5
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +467 -0
- package/dist/server/lib/executionRunAuthority.js.map +1 -0
- package/dist/server/lib/executionRuns.js +30 -4
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +2 -1
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/git.js +77 -10
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/gitPushApprovals.js +367 -0
- package/dist/server/lib/gitPushApprovals.js.map +1 -0
- package/dist/server/lib/hostExecutionEnvironment.js +7 -1
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +115 -1
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/productionCheckApprovals.js +106 -0
- package/dist/server/lib/productionCheckApprovals.js.map +1 -0
- package/dist/server/lib/projects.js +1 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/run.js +58 -2
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +32 -6
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/transcriptRuns.js +3 -0
- package/dist/server/lib/transcriptRuns.js.map +1 -1
- package/dist/server/lib/types.js +40 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/lib/workspaceProcessEnvironment.js +72 -1
- package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -1
- package/dist/server/workers/build.js +214 -46
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +74 -1
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/review.js +30 -13
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-CM1z-amZ.js +0 -1
- package/dist/client/assets/VerificationDialog-oHjNllh6.js +0 -4
- package/dist/client/assets/channel-CF8hkOdu.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-D6FiG9_B.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bim05ijr.js +0 -1
- package/dist/client/assets/index-CsZfTdMO.css +0 -2
- package/dist/client/assets/index-DDefs3iM.js +0 -275
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BLzZdVo0.js +0 -1
package/dist/server/db.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from 'crypto';
|
|
|
3
3
|
import { parseCheckDefinitionsJsonStrict, parseLegacyTestCommandsJsonStrict, projectCheckDefinitionsToTestCommands, } from './lib/checkDefinitions.js';
|
|
4
4
|
import { ensureParentDirectory, resolveArchonDbPath } from './lib/runtimePaths.js';
|
|
5
5
|
import { TRANSCRIPT_RUN_TEXT_LIMIT_BYTES, truncateUtf8 } from './lib/transcriptRuns.js';
|
|
6
|
+
import { createExecutionProfile, EXECUTION_PROFILE_SCHEMA_VERSION, fingerprintExecutionProfile, normalizeExecutionProfile, serializeExecutionProfile, } from './lib/executionProfiles.js';
|
|
6
7
|
const dbPath = ensureParentDirectory(resolveArchonDbPath());
|
|
7
8
|
const db = new Database(dbPath);
|
|
8
9
|
db.pragma('foreign_keys = ON');
|
|
@@ -55,6 +56,7 @@ db.exec(`
|
|
|
55
56
|
name TEXT NOT NULL,
|
|
56
57
|
repo_path TEXT NOT NULL,
|
|
57
58
|
build_execution_provider TEXT NOT NULL DEFAULT 'host' CHECK (build_execution_provider IN ('host', 'docker_sandbox')),
|
|
59
|
+
active_execution_profile_revision_id TEXT NOT NULL DEFAULT '',
|
|
58
60
|
active_target_kind TEXT NOT NULL DEFAULT 'repo_root',
|
|
59
61
|
active_target_bundle_id TEXT,
|
|
60
62
|
worktree_cmd TEXT,
|
|
@@ -148,6 +150,7 @@ db.exec('UPDATE worktree_bundles SET required_check_policy_override = NULL WHERE
|
|
|
148
150
|
ensureColumn('projects', 'worktree_sync', 'TEXT');
|
|
149
151
|
ensureColumn('projects', 'build_execution_provider', "TEXT NOT NULL DEFAULT 'host'");
|
|
150
152
|
db.exec("UPDATE projects SET build_execution_provider = 'host' WHERE build_execution_provider IS NULL OR build_execution_provider NOT IN ('host', 'docker_sandbox')");
|
|
153
|
+
ensureColumn('projects', 'active_execution_profile_revision_id', "TEXT NOT NULL DEFAULT ''");
|
|
151
154
|
ensureColumn('projects', 'active_target_kind', "TEXT NOT NULL DEFAULT 'repo_root'");
|
|
152
155
|
ensureColumn('projects', 'active_target_bundle_id', 'TEXT');
|
|
153
156
|
ensureColumn('projects', 'check_definitions', 'TEXT');
|
|
@@ -173,6 +176,278 @@ ensureColumn('projects', 'spec_aware_code_review_enabled', 'INTEGER NOT NULL DEF
|
|
|
173
176
|
ensureColumn('projects', 'deletion_started_at', 'DATETIME');
|
|
174
177
|
ensureColumn('projects', 'deletion_claim_id', 'TEXT');
|
|
175
178
|
ensureColumn('projects', 'deletion_cleanup_started_at', 'DATETIME');
|
|
179
|
+
db.exec(`
|
|
180
|
+
CREATE TABLE IF NOT EXISTS execution_profile_revisions (
|
|
181
|
+
id TEXT PRIMARY KEY,
|
|
182
|
+
project_id TEXT NOT NULL,
|
|
183
|
+
revision INTEGER NOT NULL CHECK (revision > 0),
|
|
184
|
+
schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2)),
|
|
185
|
+
fingerprint TEXT NOT NULL CHECK (length(fingerprint) = 64),
|
|
186
|
+
profile_json TEXT NOT NULL CHECK (
|
|
187
|
+
json_valid(profile_json)
|
|
188
|
+
AND json_type(profile_json) = 'object'
|
|
189
|
+
AND length(CAST(profile_json AS BLOB)) BETWEEN 2 AND 16384
|
|
190
|
+
),
|
|
191
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
192
|
+
UNIQUE (project_id, revision),
|
|
193
|
+
UNIQUE (project_id, fingerprint),
|
|
194
|
+
UNIQUE (project_id, id),
|
|
195
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
CREATE TABLE IF NOT EXISTS execution_profile_audit_events (
|
|
199
|
+
id TEXT PRIMARY KEY,
|
|
200
|
+
project_id TEXT NOT NULL,
|
|
201
|
+
event_type TEXT NOT NULL CHECK (event_type IN ('revision_created', 'revision_activated')),
|
|
202
|
+
profile_revision_id TEXT NOT NULL,
|
|
203
|
+
previous_profile_revision_id TEXT,
|
|
204
|
+
metadata_json TEXT CHECK (
|
|
205
|
+
metadata_json IS NULL OR (
|
|
206
|
+
json_valid(metadata_json)
|
|
207
|
+
AND json_type(metadata_json) = 'object'
|
|
208
|
+
AND length(CAST(metadata_json AS BLOB)) BETWEEN 2 AND 16384
|
|
209
|
+
)
|
|
210
|
+
),
|
|
211
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
212
|
+
FOREIGN KEY (project_id, profile_revision_id)
|
|
213
|
+
REFERENCES execution_profile_revisions(project_id, id) ON DELETE CASCADE,
|
|
214
|
+
FOREIGN KEY (project_id, previous_profile_revision_id)
|
|
215
|
+
REFERENCES execution_profile_revisions(project_id, id) ON DELETE CASCADE
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
CREATE INDEX IF NOT EXISTS idx_execution_profile_revisions_project
|
|
219
|
+
ON execution_profile_revisions(project_id, revision DESC);
|
|
220
|
+
CREATE INDEX IF NOT EXISTS idx_execution_profile_audit_project
|
|
221
|
+
ON execution_profile_audit_events(project_id, created_at DESC, id DESC);
|
|
222
|
+
`);
|
|
223
|
+
const executionProfileRevisionsTableSql = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'execution_profile_revisions'").get()?.sql ?? '';
|
|
224
|
+
if (!executionProfileRevisionsTableSql.includes('schema_version IN (1, 2)')) {
|
|
225
|
+
db.pragma('foreign_keys = OFF');
|
|
226
|
+
db.transaction(() => {
|
|
227
|
+
const referencingTriggers = db.prepare(`
|
|
228
|
+
SELECT name FROM sqlite_master
|
|
229
|
+
WHERE type = 'trigger' AND instr(sql, 'execution_profile_revisions') > 0
|
|
230
|
+
`).all();
|
|
231
|
+
for (const trigger of referencingTriggers) {
|
|
232
|
+
db.exec(`DROP TRIGGER IF EXISTS "${trigger.name.replaceAll('"', '""')}"`);
|
|
233
|
+
}
|
|
234
|
+
db.exec(`
|
|
235
|
+
CREATE TABLE execution_profile_revisions_v2 (
|
|
236
|
+
id TEXT PRIMARY KEY,
|
|
237
|
+
project_id TEXT NOT NULL,
|
|
238
|
+
revision INTEGER NOT NULL CHECK (revision > 0),
|
|
239
|
+
schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2)),
|
|
240
|
+
fingerprint TEXT NOT NULL CHECK (length(fingerprint) = 64),
|
|
241
|
+
profile_json TEXT NOT NULL CHECK (
|
|
242
|
+
json_valid(profile_json)
|
|
243
|
+
AND json_type(profile_json) = 'object'
|
|
244
|
+
AND length(CAST(profile_json AS BLOB)) BETWEEN 2 AND 16384
|
|
245
|
+
),
|
|
246
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
247
|
+
UNIQUE (project_id, revision),
|
|
248
|
+
UNIQUE (project_id, fingerprint),
|
|
249
|
+
UNIQUE (project_id, id),
|
|
250
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
251
|
+
);
|
|
252
|
+
INSERT INTO execution_profile_revisions_v2 (
|
|
253
|
+
id, project_id, revision, schema_version, fingerprint, profile_json, created_at
|
|
254
|
+
)
|
|
255
|
+
SELECT id, project_id, revision, schema_version, fingerprint, profile_json, created_at
|
|
256
|
+
FROM execution_profile_revisions;
|
|
257
|
+
DROP TABLE execution_profile_revisions;
|
|
258
|
+
ALTER TABLE execution_profile_revisions_v2 RENAME TO execution_profile_revisions;
|
|
259
|
+
`);
|
|
260
|
+
})();
|
|
261
|
+
db.pragma('foreign_keys = ON');
|
|
262
|
+
}
|
|
263
|
+
const backfillExecutionProfiles = () => {
|
|
264
|
+
const projects = db.prepare(`
|
|
265
|
+
SELECT id, build_execution_provider, active_execution_profile_revision_id
|
|
266
|
+
FROM projects
|
|
267
|
+
`).all();
|
|
268
|
+
const findOwnedRevision = db.prepare(`
|
|
269
|
+
SELECT 1 FROM execution_profile_revisions WHERE project_id = ? AND id = ?
|
|
270
|
+
`);
|
|
271
|
+
const insertRevision = db.prepare(`
|
|
272
|
+
INSERT OR IGNORE INTO execution_profile_revisions (
|
|
273
|
+
id, project_id, revision, schema_version, fingerprint, profile_json
|
|
274
|
+
) VALUES (?, ?, 1, ${EXECUTION_PROFILE_SCHEMA_VERSION}, ?, ?)
|
|
275
|
+
`);
|
|
276
|
+
const activateRevision = db.prepare(`
|
|
277
|
+
UPDATE projects SET active_execution_profile_revision_id = ? WHERE id = ?
|
|
278
|
+
`);
|
|
279
|
+
const insertEvent = db.prepare(`
|
|
280
|
+
INSERT OR IGNORE INTO execution_profile_audit_events (
|
|
281
|
+
id, project_id, event_type, profile_revision_id, previous_profile_revision_id
|
|
282
|
+
) VALUES (?, ?, 'revision_created', ?, NULL)
|
|
283
|
+
`);
|
|
284
|
+
db.transaction(() => {
|
|
285
|
+
for (const project of projects) {
|
|
286
|
+
if (project.active_execution_profile_revision_id
|
|
287
|
+
&& findOwnedRevision.get(project.id, project.active_execution_profile_revision_id)) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const profile = createExecutionProfile(project.build_execution_provider);
|
|
291
|
+
const profileJson = serializeExecutionProfile(profile);
|
|
292
|
+
const fingerprint = fingerprintExecutionProfile(profile);
|
|
293
|
+
const revisionId = `${project.id}:execution-profile:1`;
|
|
294
|
+
insertRevision.run(revisionId, project.id, fingerprint, profileJson);
|
|
295
|
+
activateRevision.run(revisionId, project.id);
|
|
296
|
+
insertEvent.run(`${revisionId}:created`, project.id, revisionId);
|
|
297
|
+
}
|
|
298
|
+
})();
|
|
299
|
+
};
|
|
300
|
+
const backfillExecutionProfilesToSchemaV2 = () => {
|
|
301
|
+
const legacyRows = db.prepare(`
|
|
302
|
+
SELECT projects.id AS project_id,
|
|
303
|
+
revisions.id AS revision_id,
|
|
304
|
+
revisions.profile_json
|
|
305
|
+
FROM projects
|
|
306
|
+
INNER JOIN execution_profile_revisions revisions
|
|
307
|
+
ON revisions.id = projects.active_execution_profile_revision_id
|
|
308
|
+
AND revisions.project_id = projects.id
|
|
309
|
+
WHERE revisions.schema_version = 1 AND projects.deletion_started_at IS NULL
|
|
310
|
+
`).all();
|
|
311
|
+
const nextRevisionNumber = db.prepare(`
|
|
312
|
+
SELECT COALESCE(MAX(revision), 0) + 1 AS revision
|
|
313
|
+
FROM execution_profile_revisions WHERE project_id = ?
|
|
314
|
+
`);
|
|
315
|
+
const findRevisionByFingerprint = db.prepare(`
|
|
316
|
+
SELECT id FROM execution_profile_revisions WHERE project_id = ? AND fingerprint = ?
|
|
317
|
+
`);
|
|
318
|
+
const insertRevision = db.prepare(`
|
|
319
|
+
INSERT OR IGNORE INTO execution_profile_revisions (
|
|
320
|
+
id, project_id, revision, schema_version, fingerprint, profile_json
|
|
321
|
+
) VALUES (?, ?, ?, ${EXECUTION_PROFILE_SCHEMA_VERSION}, ?, ?)
|
|
322
|
+
`);
|
|
323
|
+
const activateRevision = db.prepare(`
|
|
324
|
+
UPDATE projects SET active_execution_profile_revision_id = ?
|
|
325
|
+
WHERE id = ? AND active_execution_profile_revision_id = ?
|
|
326
|
+
`);
|
|
327
|
+
const insertEvent = db.prepare(`
|
|
328
|
+
INSERT OR IGNORE INTO execution_profile_audit_events (
|
|
329
|
+
id, project_id, event_type, profile_revision_id, previous_profile_revision_id
|
|
330
|
+
) VALUES (?, ?, 'revision_activated', ?, ?)
|
|
331
|
+
`);
|
|
332
|
+
db.transaction(() => {
|
|
333
|
+
for (const row of legacyRows) {
|
|
334
|
+
let normalized;
|
|
335
|
+
try {
|
|
336
|
+
normalized = normalizeExecutionProfile(JSON.parse(row.profile_json));
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
normalized = null;
|
|
340
|
+
}
|
|
341
|
+
if (!normalized?.ok) {
|
|
342
|
+
throw new Error(`Stored execution profile revision ${row.revision_id} is invalid`);
|
|
343
|
+
}
|
|
344
|
+
const fingerprint = fingerprintExecutionProfile(normalized.profile);
|
|
345
|
+
const existing = findRevisionByFingerprint.get(row.project_id, fingerprint);
|
|
346
|
+
let revisionId;
|
|
347
|
+
if (existing) {
|
|
348
|
+
revisionId = existing.id;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
revisionId = `${row.project_id}:execution-profile:v2`;
|
|
352
|
+
const revisionNumber = nextRevisionNumber.get(row.project_id).revision;
|
|
353
|
+
insertRevision.run(revisionId, row.project_id, revisionNumber, fingerprint, serializeExecutionProfile(normalized.profile));
|
|
354
|
+
}
|
|
355
|
+
const activated = activateRevision.run(revisionId, row.project_id, row.revision_id);
|
|
356
|
+
if (activated.changes > 0) {
|
|
357
|
+
insertEvent.run(`${revisionId}:v2-backfill`, row.project_id, revisionId, row.revision_id);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
})();
|
|
361
|
+
};
|
|
362
|
+
backfillExecutionProfiles();
|
|
363
|
+
backfillExecutionProfilesToSchemaV2();
|
|
364
|
+
const hostCompatibilityProfile = createExecutionProfile('host');
|
|
365
|
+
const hostCompatibilityProfileJson = serializeExecutionProfile(hostCompatibilityProfile).replaceAll("'", "''");
|
|
366
|
+
const hostCompatibilityFingerprint = fingerprintExecutionProfile(hostCompatibilityProfile);
|
|
367
|
+
const dockerProfile = createExecutionProfile('docker_sandbox');
|
|
368
|
+
const dockerProfileJson = serializeExecutionProfile(dockerProfile).replaceAll("'", "''");
|
|
369
|
+
const dockerProfileFingerprint = fingerprintExecutionProfile(dockerProfile);
|
|
370
|
+
db.exec(`
|
|
371
|
+
CREATE TRIGGER IF NOT EXISTS create_project_execution_profile
|
|
372
|
+
AFTER INSERT ON projects
|
|
373
|
+
WHEN NEW.active_execution_profile_revision_id = ''
|
|
374
|
+
BEGIN
|
|
375
|
+
INSERT INTO execution_profile_revisions (
|
|
376
|
+
id, project_id, revision, schema_version, fingerprint, profile_json
|
|
377
|
+
) VALUES (
|
|
378
|
+
NEW.id || ':execution-profile:1', NEW.id, 1, ${EXECUTION_PROFILE_SCHEMA_VERSION},
|
|
379
|
+
CASE WHEN NEW.build_execution_provider = 'docker_sandbox'
|
|
380
|
+
THEN '${dockerProfileFingerprint}' ELSE '${hostCompatibilityFingerprint}' END,
|
|
381
|
+
CASE WHEN NEW.build_execution_provider = 'docker_sandbox'
|
|
382
|
+
THEN '${dockerProfileJson}' ELSE '${hostCompatibilityProfileJson}' END
|
|
383
|
+
);
|
|
384
|
+
UPDATE projects
|
|
385
|
+
SET active_execution_profile_revision_id = NEW.id || ':execution-profile:1'
|
|
386
|
+
WHERE id = NEW.id;
|
|
387
|
+
INSERT INTO execution_profile_audit_events (
|
|
388
|
+
id, project_id, event_type, profile_revision_id, previous_profile_revision_id
|
|
389
|
+
) VALUES (
|
|
390
|
+
NEW.id || ':execution-profile:1:created', NEW.id, 'revision_created',
|
|
391
|
+
NEW.id || ':execution-profile:1', NULL
|
|
392
|
+
);
|
|
393
|
+
END;
|
|
394
|
+
|
|
395
|
+
CREATE TRIGGER IF NOT EXISTS validate_project_execution_profile_owner_insert
|
|
396
|
+
BEFORE INSERT ON projects
|
|
397
|
+
WHEN NEW.active_execution_profile_revision_id <> '' AND NOT EXISTS (
|
|
398
|
+
SELECT 1 FROM execution_profile_revisions
|
|
399
|
+
WHERE project_id = NEW.id AND id = NEW.active_execution_profile_revision_id
|
|
400
|
+
)
|
|
401
|
+
BEGIN
|
|
402
|
+
SELECT RAISE(ABORT, 'Active execution profile revision is not owned by project');
|
|
403
|
+
END;
|
|
404
|
+
|
|
405
|
+
CREATE TRIGGER IF NOT EXISTS validate_project_execution_profile_owner_update
|
|
406
|
+
BEFORE UPDATE OF active_execution_profile_revision_id ON projects
|
|
407
|
+
WHEN NEW.active_execution_profile_revision_id = '' OR NOT EXISTS (
|
|
408
|
+
SELECT 1 FROM execution_profile_revisions
|
|
409
|
+
WHERE project_id = NEW.id AND id = NEW.active_execution_profile_revision_id
|
|
410
|
+
)
|
|
411
|
+
BEGIN
|
|
412
|
+
SELECT RAISE(ABORT, 'Active execution profile revision is not owned by project');
|
|
413
|
+
END;
|
|
414
|
+
|
|
415
|
+
CREATE TRIGGER IF NOT EXISTS validate_project_execution_profile_projection_insert
|
|
416
|
+
BEFORE INSERT ON projects
|
|
417
|
+
WHEN NEW.active_execution_profile_revision_id <> '' AND NOT EXISTS (
|
|
418
|
+
SELECT 1 FROM execution_profile_revisions
|
|
419
|
+
WHERE project_id = NEW.id
|
|
420
|
+
AND id = NEW.active_execution_profile_revision_id
|
|
421
|
+
AND json_extract(profile_json, '$.provider_preference') = NEW.build_execution_provider
|
|
422
|
+
)
|
|
423
|
+
BEGIN
|
|
424
|
+
SELECT RAISE(ABORT, 'Build execution provider must project the active execution profile');
|
|
425
|
+
END;
|
|
426
|
+
|
|
427
|
+
CREATE TRIGGER IF NOT EXISTS validate_project_execution_profile_projection_update
|
|
428
|
+
BEFORE UPDATE OF active_execution_profile_revision_id, build_execution_provider ON projects
|
|
429
|
+
WHEN NOT EXISTS (
|
|
430
|
+
SELECT 1 FROM execution_profile_revisions
|
|
431
|
+
WHERE project_id = NEW.id
|
|
432
|
+
AND id = NEW.active_execution_profile_revision_id
|
|
433
|
+
AND json_extract(profile_json, '$.provider_preference') = NEW.build_execution_provider
|
|
434
|
+
)
|
|
435
|
+
BEGIN
|
|
436
|
+
SELECT RAISE(ABORT, 'Build execution provider must project the active execution profile');
|
|
437
|
+
END;
|
|
438
|
+
|
|
439
|
+
CREATE TRIGGER IF NOT EXISTS preserve_execution_profile_revision
|
|
440
|
+
BEFORE UPDATE ON execution_profile_revisions
|
|
441
|
+
BEGIN
|
|
442
|
+
SELECT RAISE(ABORT, 'Execution profile revisions are immutable');
|
|
443
|
+
END;
|
|
444
|
+
|
|
445
|
+
CREATE TRIGGER IF NOT EXISTS preserve_execution_profile_audit_event
|
|
446
|
+
BEFORE UPDATE ON execution_profile_audit_events
|
|
447
|
+
BEGIN
|
|
448
|
+
SELECT RAISE(ABORT, 'Execution profile audit events are immutable');
|
|
449
|
+
END;
|
|
450
|
+
`);
|
|
176
451
|
const backfillProjectCheckDefinitions = () => {
|
|
177
452
|
const rows = db.prepare(`
|
|
178
453
|
SELECT id, check_definitions, test_commands
|
|
@@ -946,11 +1221,115 @@ db.exec(`
|
|
|
946
1221
|
entered_queue_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
947
1222
|
started_at DATETIME,
|
|
948
1223
|
finished_at DATETIME,
|
|
1224
|
+
execution_profile_revision_id TEXT,
|
|
1225
|
+
execution_profile_fingerprint TEXT,
|
|
1226
|
+
execution_profile_selection_reason TEXT,
|
|
1227
|
+
activation_execution_run_id TEXT,
|
|
949
1228
|
PRIMARY KEY (build_sequence_id, ticket_id),
|
|
950
1229
|
FOREIGN KEY (build_sequence_id) REFERENCES build_sequences(id) ON DELETE CASCADE,
|
|
951
1230
|
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
|
|
952
1231
|
)
|
|
953
1232
|
`);
|
|
1233
|
+
db.exec(`
|
|
1234
|
+
CREATE TABLE IF NOT EXISTS build_sequence_ticket_replacements (
|
|
1235
|
+
id TEXT PRIMARY KEY,
|
|
1236
|
+
build_sequence_id TEXT NOT NULL,
|
|
1237
|
+
ticket_id TEXT NOT NULL,
|
|
1238
|
+
position INTEGER NOT NULL,
|
|
1239
|
+
execution_profile_revision_id TEXT NOT NULL,
|
|
1240
|
+
execution_profile_fingerprint TEXT NOT NULL,
|
|
1241
|
+
execution_profile_selection_reason TEXT NOT NULL,
|
|
1242
|
+
replaced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
1243
|
+
FOREIGN KEY (build_sequence_id) REFERENCES build_sequences(id) ON DELETE CASCADE,
|
|
1244
|
+
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
|
|
1245
|
+
)
|
|
1246
|
+
`);
|
|
1247
|
+
ensureColumn('build_sequence_tickets', 'execution_profile_revision_id', 'TEXT');
|
|
1248
|
+
ensureColumn('build_sequence_tickets', 'execution_profile_fingerprint', 'TEXT');
|
|
1249
|
+
ensureColumn('build_sequence_tickets', 'execution_profile_selection_reason', 'TEXT');
|
|
1250
|
+
ensureColumn('build_sequence_tickets', 'activation_execution_run_id', 'TEXT');
|
|
1251
|
+
db.transaction(() => {
|
|
1252
|
+
const incompleteSequenceIds = db.prepare(`
|
|
1253
|
+
SELECT DISTINCT build_sequence_id
|
|
1254
|
+
FROM build_sequence_tickets
|
|
1255
|
+
WHERE status IN ('active', 'queued')
|
|
1256
|
+
AND (
|
|
1257
|
+
execution_profile_revision_id IS NULL
|
|
1258
|
+
OR execution_profile_fingerprint IS NULL
|
|
1259
|
+
OR execution_profile_selection_reason IS NULL
|
|
1260
|
+
)
|
|
1261
|
+
`).all();
|
|
1262
|
+
const failActive = db.prepare(`
|
|
1263
|
+
UPDATE build_sequence_tickets
|
|
1264
|
+
SET status = 'failed', finished_at = COALESCE(finished_at, CURRENT_TIMESTAMP)
|
|
1265
|
+
WHERE build_sequence_id = ? AND status = 'active'
|
|
1266
|
+
`);
|
|
1267
|
+
const cancelQueued = db.prepare(`
|
|
1268
|
+
UPDATE build_sequence_tickets
|
|
1269
|
+
SET status = 'cancelled', finished_at = COALESCE(finished_at, CURRENT_TIMESTAMP)
|
|
1270
|
+
WHERE build_sequence_id = ? AND status = 'queued'
|
|
1271
|
+
`);
|
|
1272
|
+
const failSequence = db.prepare(`
|
|
1273
|
+
UPDATE build_sequences SET status = 'failed', updated_at = CURRENT_TIMESTAMP
|
|
1274
|
+
WHERE id = ? AND status = 'active'
|
|
1275
|
+
`);
|
|
1276
|
+
for (const row of incompleteSequenceIds) {
|
|
1277
|
+
failActive.run(row.build_sequence_id);
|
|
1278
|
+
cancelQueued.run(row.build_sequence_id);
|
|
1279
|
+
failSequence.run(row.build_sequence_id);
|
|
1280
|
+
}
|
|
1281
|
+
})();
|
|
1282
|
+
db.exec(`
|
|
1283
|
+
CREATE TRIGGER IF NOT EXISTS validate_build_sequence_ticket_profile_insert
|
|
1284
|
+
BEFORE INSERT ON build_sequence_tickets
|
|
1285
|
+
WHEN NEW.status IN ('active', 'queued')
|
|
1286
|
+
OR NEW.execution_profile_revision_id IS NOT NULL OR NEW.execution_profile_fingerprint IS NOT NULL
|
|
1287
|
+
OR NEW.execution_profile_selection_reason IS NOT NULL
|
|
1288
|
+
BEGIN
|
|
1289
|
+
SELECT CASE WHEN
|
|
1290
|
+
NEW.execution_profile_revision_id IS NULL
|
|
1291
|
+
OR NEW.execution_profile_fingerprint IS NULL
|
|
1292
|
+
OR NEW.execution_profile_selection_reason NOT IN ('project_active_at_acceptance', 'historical_lineage')
|
|
1293
|
+
OR NOT EXISTS (
|
|
1294
|
+
SELECT 1
|
|
1295
|
+
FROM execution_profile_revisions revision
|
|
1296
|
+
JOIN build_sequences sequence ON sequence.id = NEW.build_sequence_id
|
|
1297
|
+
WHERE revision.id = NEW.execution_profile_revision_id
|
|
1298
|
+
AND revision.project_id = sequence.project_id
|
|
1299
|
+
AND revision.fingerprint = NEW.execution_profile_fingerprint
|
|
1300
|
+
)
|
|
1301
|
+
THEN RAISE(ABORT, 'Invalid Build sequence execution profile selection') END;
|
|
1302
|
+
END;
|
|
1303
|
+
|
|
1304
|
+
CREATE TRIGGER IF NOT EXISTS validate_build_sequence_ticket_profile_activation
|
|
1305
|
+
BEFORE UPDATE OF status ON build_sequence_tickets
|
|
1306
|
+
WHEN NEW.status IN ('active', 'queued') AND (
|
|
1307
|
+
NEW.execution_profile_revision_id IS NULL
|
|
1308
|
+
OR NEW.execution_profile_fingerprint IS NULL
|
|
1309
|
+
OR NEW.execution_profile_selection_reason NOT IN ('project_active_at_acceptance', 'historical_lineage')
|
|
1310
|
+
OR NOT EXISTS (
|
|
1311
|
+
SELECT 1
|
|
1312
|
+
FROM execution_profile_revisions revision
|
|
1313
|
+
JOIN build_sequences sequence ON sequence.id = NEW.build_sequence_id
|
|
1314
|
+
WHERE revision.id = NEW.execution_profile_revision_id
|
|
1315
|
+
AND revision.project_id = sequence.project_id
|
|
1316
|
+
AND revision.fingerprint = NEW.execution_profile_fingerprint
|
|
1317
|
+
)
|
|
1318
|
+
)
|
|
1319
|
+
BEGIN
|
|
1320
|
+
SELECT RAISE(ABORT, 'Invalid Build sequence execution profile selection');
|
|
1321
|
+
END;
|
|
1322
|
+
|
|
1323
|
+
CREATE TRIGGER IF NOT EXISTS preserve_build_sequence_ticket_profile
|
|
1324
|
+
BEFORE UPDATE OF execution_profile_revision_id, execution_profile_fingerprint, execution_profile_selection_reason
|
|
1325
|
+
ON build_sequence_tickets
|
|
1326
|
+
WHEN NEW.execution_profile_revision_id IS NOT OLD.execution_profile_revision_id
|
|
1327
|
+
OR NEW.execution_profile_fingerprint IS NOT OLD.execution_profile_fingerprint
|
|
1328
|
+
OR NEW.execution_profile_selection_reason IS NOT OLD.execution_profile_selection_reason
|
|
1329
|
+
BEGIN
|
|
1330
|
+
SELECT RAISE(ABORT, 'Build sequence execution profile selection is immutable');
|
|
1331
|
+
END;
|
|
1332
|
+
`);
|
|
954
1333
|
db.exec(`
|
|
955
1334
|
CREATE TABLE IF NOT EXISTS ticket_dependencies (
|
|
956
1335
|
blocker_ticket_id TEXT NOT NULL,
|
|
@@ -2132,6 +2511,9 @@ db.exec(`
|
|
|
2132
2511
|
environment_revision INTEGER NOT NULL,
|
|
2133
2512
|
workspace_mode TEXT NOT NULL,
|
|
2134
2513
|
environment_validation_fingerprint TEXT NOT NULL,
|
|
2514
|
+
execution_profile_revision_id TEXT,
|
|
2515
|
+
execution_profile_fingerprint TEXT,
|
|
2516
|
+
credential_requirements_fingerprint TEXT,
|
|
2135
2517
|
template_identity TEXT,
|
|
2136
2518
|
template_digest TEXT,
|
|
2137
2519
|
agent_identity TEXT,
|
|
@@ -2151,6 +2533,7 @@ db.exec(`
|
|
|
2151
2533
|
last_used_at DATETIME,
|
|
2152
2534
|
stopped_at DATETIME,
|
|
2153
2535
|
retirement_started_at DATETIME,
|
|
2536
|
+
retirement_requested_at DATETIME,
|
|
2154
2537
|
cleanup_pending_at DATETIME,
|
|
2155
2538
|
retired_at DATETIME,
|
|
2156
2539
|
failed_at DATETIME,
|
|
@@ -2169,6 +2552,10 @@ db.exec(`
|
|
|
2169
2552
|
CHECK (environment_revision >= 1),
|
|
2170
2553
|
CHECK (workspace_mode = 'direct'),
|
|
2171
2554
|
CHECK (length(environment_validation_fingerprint) = 64 AND environment_validation_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2555
|
+
CHECK ((execution_profile_revision_id IS NULL) = (execution_profile_fingerprint IS NULL)),
|
|
2556
|
+
CHECK ((execution_profile_fingerprint IS NULL) = (credential_requirements_fingerprint IS NULL)),
|
|
2557
|
+
CHECK (execution_profile_fingerprint IS NULL OR (length(execution_profile_fingerprint) = 64 AND execution_profile_fingerprint NOT GLOB '*[^0-9a-f]*')),
|
|
2558
|
+
CHECK (credential_requirements_fingerprint IS NULL OR (length(credential_requirements_fingerprint) = 64 AND credential_requirements_fingerprint NOT GLOB '*[^0-9a-f]*')),
|
|
2172
2559
|
CHECK ((template_identity IS NULL) <> (template_digest IS NULL)),
|
|
2173
2560
|
CHECK (template_identity IS NULL OR length(CAST(template_identity AS BLOB)) BETWEEN 1 AND 500),
|
|
2174
2561
|
CHECK (template_digest IS NULL OR length(CAST(template_digest AS BLOB)) BETWEEN 1 AND 500),
|
|
@@ -2214,6 +2601,7 @@ db.exec(`
|
|
|
2214
2601
|
relationship TEXT NOT NULL,
|
|
2215
2602
|
environment_revision INTEGER NOT NULL,
|
|
2216
2603
|
environment_validation_fingerprint TEXT NOT NULL,
|
|
2604
|
+
execution_profile_fingerprint TEXT,
|
|
2217
2605
|
authority_json TEXT,
|
|
2218
2606
|
placement_json TEXT,
|
|
2219
2607
|
bound_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -2224,6 +2612,7 @@ db.exec(`
|
|
|
2224
2612
|
CHECK (relationship IN ('validation', 'execution')),
|
|
2225
2613
|
CHECK (environment_revision >= 1),
|
|
2226
2614
|
CHECK (length(environment_validation_fingerprint) = 64 AND environment_validation_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2615
|
+
CHECK (execution_profile_fingerprint IS NULL OR (length(execution_profile_fingerprint) = 64 AND execution_profile_fingerprint NOT GLOB '*[^0-9a-f]*')),
|
|
2227
2616
|
CHECK (relationship <> 'execution' OR length(CAST(authority_json AS BLOB)) BETWEEN 2 AND 16384),
|
|
2228
2617
|
CHECK (placement_json IS NULL OR (relationship = 'execution' AND length(CAST(placement_json AS BLOB)) BETWEEN 2 AND 16384))
|
|
2229
2618
|
);
|
|
@@ -2298,8 +2687,357 @@ db.exec(`
|
|
|
2298
2687
|
SELECT RAISE(ABORT, 'Execution environment run binding identity is immutable');
|
|
2299
2688
|
END;
|
|
2300
2689
|
`);
|
|
2690
|
+
ensureColumn('execution_environments', 'execution_profile_revision_id', 'TEXT');
|
|
2691
|
+
ensureColumn('execution_environments', 'execution_profile_fingerprint', 'TEXT');
|
|
2692
|
+
ensureColumn('execution_environments', 'credential_requirements_fingerprint', 'TEXT');
|
|
2693
|
+
ensureColumn('execution_environments', 'retirement_requested_at', 'DATETIME');
|
|
2694
|
+
ensureColumn('execution_environment_run_bindings', 'execution_profile_fingerprint', 'TEXT');
|
|
2301
2695
|
ensureColumn('execution_environment_run_bindings', 'authority_json', 'TEXT');
|
|
2302
2696
|
ensureColumn('execution_environment_run_bindings', 'placement_json', 'TEXT');
|
|
2697
|
+
db.exec(`
|
|
2698
|
+
CREATE TABLE IF NOT EXISTS execution_run_authorities (
|
|
2699
|
+
execution_run_id TEXT NOT NULL,
|
|
2700
|
+
project_id TEXT NOT NULL,
|
|
2701
|
+
execution_profile_revision_id TEXT NOT NULL,
|
|
2702
|
+
execution_profile_fingerprint TEXT NOT NULL,
|
|
2703
|
+
authority_json TEXT NOT NULL,
|
|
2704
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2705
|
+
PRIMARY KEY (execution_run_id),
|
|
2706
|
+
FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
|
|
2707
|
+
FOREIGN KEY (project_id, execution_profile_revision_id)
|
|
2708
|
+
REFERENCES execution_profile_revisions(project_id, id) ON DELETE RESTRICT,
|
|
2709
|
+
CHECK (length(execution_profile_fingerprint) = 64 AND execution_profile_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2710
|
+
CHECK (length(CAST(authority_json AS BLOB)) BETWEEN 2 AND 16384)
|
|
2711
|
+
);
|
|
2712
|
+
|
|
2713
|
+
CREATE TRIGGER IF NOT EXISTS preserve_execution_run_authority
|
|
2714
|
+
BEFORE UPDATE ON execution_run_authorities
|
|
2715
|
+
BEGIN
|
|
2716
|
+
SELECT RAISE(ABORT, 'Execution run authority is immutable');
|
|
2717
|
+
END;
|
|
2718
|
+
|
|
2719
|
+
`);
|
|
2720
|
+
db.exec(`
|
|
2721
|
+
CREATE TABLE IF NOT EXISTS approval_requests (
|
|
2722
|
+
id TEXT PRIMARY KEY,
|
|
2723
|
+
project_id TEXT NOT NULL,
|
|
2724
|
+
execution_run_id TEXT NOT NULL,
|
|
2725
|
+
action_kind TEXT NOT NULL,
|
|
2726
|
+
action_schema_version INTEGER NOT NULL,
|
|
2727
|
+
action_json TEXT NOT NULL,
|
|
2728
|
+
action_fingerprint TEXT NOT NULL,
|
|
2729
|
+
authority_schema_version INTEGER NOT NULL,
|
|
2730
|
+
authority_json TEXT NOT NULL,
|
|
2731
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
2732
|
+
expires_at DATETIME NOT NULL,
|
|
2733
|
+
decision_at DATETIME,
|
|
2734
|
+
consumed_at DATETIME,
|
|
2735
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2736
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2737
|
+
UNIQUE (id, project_id),
|
|
2738
|
+
UNIQUE (execution_run_id),
|
|
2739
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
2740
|
+
FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
|
|
2741
|
+
CHECK (length(id) BETWEEN 1 AND 128),
|
|
2742
|
+
CHECK (action_kind IN ('git_push', 'custom_check')),
|
|
2743
|
+
CHECK (action_schema_version = 1),
|
|
2744
|
+
CHECK (length(CAST(action_json AS BLOB)) BETWEEN 2 AND 65536),
|
|
2745
|
+
CHECK (length(action_fingerprint) = 64 AND action_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2746
|
+
CHECK (authority_schema_version = 1),
|
|
2747
|
+
CHECK (length(CAST(authority_json AS BLOB)) BETWEEN 2 AND 16384),
|
|
2748
|
+
CHECK (status IN ('pending', 'approved', 'denied', 'cancelled', 'expired', 'invalidated', 'consumed')),
|
|
2749
|
+
CHECK (status NOT IN ('approved', 'denied', 'consumed') OR decision_at IS NOT NULL),
|
|
2750
|
+
CHECK (status <> 'pending' OR decision_at IS NULL),
|
|
2751
|
+
CHECK ((status = 'consumed') = (consumed_at IS NOT NULL))
|
|
2752
|
+
);
|
|
2753
|
+
|
|
2754
|
+
CREATE TABLE IF NOT EXISTS approval_request_events (
|
|
2755
|
+
id TEXT PRIMARY KEY,
|
|
2756
|
+
project_id TEXT NOT NULL,
|
|
2757
|
+
approval_request_id TEXT NOT NULL,
|
|
2758
|
+
sequence INTEGER NOT NULL,
|
|
2759
|
+
event_type TEXT NOT NULL,
|
|
2760
|
+
from_status TEXT,
|
|
2761
|
+
to_status TEXT NOT NULL,
|
|
2762
|
+
actor_classification TEXT NOT NULL,
|
|
2763
|
+
actor_reference TEXT,
|
|
2764
|
+
reason TEXT,
|
|
2765
|
+
metadata_schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2766
|
+
metadata_json TEXT,
|
|
2767
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2768
|
+
UNIQUE (approval_request_id, sequence),
|
|
2769
|
+
FOREIGN KEY (approval_request_id, project_id) REFERENCES approval_requests(id, project_id) ON DELETE CASCADE,
|
|
2770
|
+
CHECK (sequence >= 1),
|
|
2771
|
+
CHECK (length(event_type) BETWEEN 1 AND 100),
|
|
2772
|
+
CHECK (from_status IS NULL OR from_status IN ('pending', 'approved', 'denied', 'cancelled', 'expired', 'invalidated', 'consumed')),
|
|
2773
|
+
CHECK (to_status IN ('pending', 'approved', 'denied', 'cancelled', 'expired', 'invalidated', 'consumed')),
|
|
2774
|
+
CHECK (actor_classification IN ('human', 'system', 'action_executor')),
|
|
2775
|
+
CHECK (actor_reference IS NULL OR length(CAST(actor_reference AS BLOB)) BETWEEN 1 AND 200),
|
|
2776
|
+
CHECK (reason IS NULL OR length(CAST(reason AS BLOB)) <= 2000),
|
|
2777
|
+
CHECK (metadata_schema_version = 1),
|
|
2778
|
+
CHECK (metadata_json IS NULL OR length(CAST(metadata_json AS BLOB)) <= 4096)
|
|
2779
|
+
);
|
|
2780
|
+
|
|
2781
|
+
CREATE TRIGGER IF NOT EXISTS preserve_approval_request_identity
|
|
2782
|
+
BEFORE UPDATE OF id, project_id, execution_run_id, action_kind, action_schema_version,
|
|
2783
|
+
action_json, action_fingerprint, authority_schema_version, authority_json, expires_at, created_at
|
|
2784
|
+
ON approval_requests
|
|
2785
|
+
BEGIN
|
|
2786
|
+
SELECT RAISE(ABORT, 'Approval request identity and evidence are immutable');
|
|
2787
|
+
END;
|
|
2788
|
+
|
|
2789
|
+
CREATE TRIGGER IF NOT EXISTS preserve_approval_request_decision_evidence
|
|
2790
|
+
BEFORE UPDATE OF decision_at, consumed_at ON approval_requests
|
|
2791
|
+
WHEN (OLD.decision_at IS NOT NULL AND NEW.decision_at IS NOT OLD.decision_at)
|
|
2792
|
+
OR (OLD.consumed_at IS NOT NULL AND NEW.consumed_at IS NOT OLD.consumed_at)
|
|
2793
|
+
BEGIN
|
|
2794
|
+
SELECT RAISE(ABORT, 'Approval request decision evidence is immutable');
|
|
2795
|
+
END;
|
|
2796
|
+
|
|
2797
|
+
CREATE TRIGGER IF NOT EXISTS validate_approval_request_status_transition
|
|
2798
|
+
BEFORE UPDATE OF status ON approval_requests
|
|
2799
|
+
WHEN NOT (
|
|
2800
|
+
(OLD.status = 'pending' AND NEW.status IN ('approved', 'denied', 'cancelled', 'expired', 'invalidated'))
|
|
2801
|
+
OR (OLD.status = 'approved' AND NEW.status IN ('consumed', 'expired', 'invalidated'))
|
|
2802
|
+
)
|
|
2803
|
+
BEGIN
|
|
2804
|
+
SELECT RAISE(ABORT, 'Invalid approval request status transition');
|
|
2805
|
+
END;
|
|
2806
|
+
|
|
2807
|
+
CREATE TRIGGER IF NOT EXISTS preserve_approval_request_events
|
|
2808
|
+
BEFORE UPDATE ON approval_request_events
|
|
2809
|
+
BEGIN
|
|
2810
|
+
SELECT RAISE(ABORT, 'Approval request events are append-only');
|
|
2811
|
+
END;
|
|
2812
|
+
|
|
2813
|
+
CREATE TRIGGER IF NOT EXISTS prevent_approval_request_event_deletion
|
|
2814
|
+
BEFORE DELETE ON approval_request_events
|
|
2815
|
+
WHEN EXISTS (
|
|
2816
|
+
SELECT 1 FROM projects
|
|
2817
|
+
WHERE id = OLD.project_id AND deletion_started_at IS NULL
|
|
2818
|
+
)
|
|
2819
|
+
BEGIN
|
|
2820
|
+
SELECT RAISE(ABORT, 'Approval request events are append-only');
|
|
2821
|
+
END;
|
|
2822
|
+
|
|
2823
|
+
CREATE INDEX IF NOT EXISTS idx_approval_requests_project_created
|
|
2824
|
+
ON approval_requests(project_id, created_at DESC, id DESC);
|
|
2825
|
+
CREATE INDEX IF NOT EXISTS idx_approval_requests_project_status_expiry
|
|
2826
|
+
ON approval_requests(project_id, status, expires_at, id);
|
|
2827
|
+
CREATE INDEX IF NOT EXISTS idx_approval_requests_action_kind
|
|
2828
|
+
ON approval_requests(project_id, action_kind, created_at DESC, id DESC);
|
|
2829
|
+
CREATE INDEX IF NOT EXISTS idx_approval_request_events_sequence
|
|
2830
|
+
ON approval_request_events(project_id, approval_request_id, sequence ASC);
|
|
2831
|
+
`);
|
|
2832
|
+
const migrateApprovalRequestActionLimit = () => {
|
|
2833
|
+
const tableSql = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'approval_requests'").get()?.sql ?? '';
|
|
2834
|
+
if (tableSql.includes('BETWEEN 2 AND 65536')) {
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
db.exec('PRAGMA foreign_keys = OFF');
|
|
2838
|
+
try {
|
|
2839
|
+
db.exec('BEGIN');
|
|
2840
|
+
db.exec(`
|
|
2841
|
+
CREATE TABLE approval_requests_v2 (
|
|
2842
|
+
id TEXT PRIMARY KEY,
|
|
2843
|
+
project_id TEXT NOT NULL,
|
|
2844
|
+
execution_run_id TEXT NOT NULL,
|
|
2845
|
+
action_kind TEXT NOT NULL,
|
|
2846
|
+
action_schema_version INTEGER NOT NULL,
|
|
2847
|
+
action_json TEXT NOT NULL,
|
|
2848
|
+
action_fingerprint TEXT NOT NULL,
|
|
2849
|
+
authority_schema_version INTEGER NOT NULL,
|
|
2850
|
+
authority_json TEXT NOT NULL,
|
|
2851
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
2852
|
+
expires_at DATETIME NOT NULL,
|
|
2853
|
+
decision_at DATETIME,
|
|
2854
|
+
consumed_at DATETIME,
|
|
2855
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2856
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2857
|
+
UNIQUE (id, project_id),
|
|
2858
|
+
UNIQUE (execution_run_id),
|
|
2859
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
2860
|
+
FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
|
|
2861
|
+
CHECK (length(id) BETWEEN 1 AND 128),
|
|
2862
|
+
CHECK (action_kind IN ('git_push', 'custom_check')),
|
|
2863
|
+
CHECK (action_schema_version = 1),
|
|
2864
|
+
CHECK (length(CAST(action_json AS BLOB)) BETWEEN 2 AND 65536),
|
|
2865
|
+
CHECK (length(action_fingerprint) = 64 AND action_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2866
|
+
CHECK (authority_schema_version = 1),
|
|
2867
|
+
CHECK (length(CAST(authority_json AS BLOB)) BETWEEN 2 AND 16384),
|
|
2868
|
+
CHECK (status IN ('pending', 'approved', 'denied', 'cancelled', 'expired', 'invalidated', 'consumed')),
|
|
2869
|
+
CHECK (status NOT IN ('approved', 'denied', 'consumed') OR decision_at IS NOT NULL),
|
|
2870
|
+
CHECK (status <> 'pending' OR decision_at IS NULL),
|
|
2871
|
+
CHECK ((status = 'consumed') = (consumed_at IS NOT NULL))
|
|
2872
|
+
);
|
|
2873
|
+
INSERT INTO approval_requests_v2 (
|
|
2874
|
+
id, project_id, execution_run_id, action_kind, action_schema_version, action_json,
|
|
2875
|
+
action_fingerprint, authority_schema_version, authority_json, status, expires_at,
|
|
2876
|
+
decision_at, consumed_at, created_at, updated_at
|
|
2877
|
+
)
|
|
2878
|
+
SELECT
|
|
2879
|
+
id, project_id, execution_run_id, action_kind, action_schema_version, action_json,
|
|
2880
|
+
action_fingerprint, authority_schema_version, authority_json, status, expires_at,
|
|
2881
|
+
decision_at, consumed_at, created_at, updated_at
|
|
2882
|
+
FROM approval_requests;
|
|
2883
|
+
DROP TABLE approval_requests;
|
|
2884
|
+
ALTER TABLE approval_requests_v2 RENAME TO approval_requests;
|
|
2885
|
+
`);
|
|
2886
|
+
db.exec('COMMIT');
|
|
2887
|
+
}
|
|
2888
|
+
catch (error) {
|
|
2889
|
+
db.exec('ROLLBACK');
|
|
2890
|
+
throw error;
|
|
2891
|
+
}
|
|
2892
|
+
finally {
|
|
2893
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
2894
|
+
}
|
|
2895
|
+
db.exec(`
|
|
2896
|
+
CREATE TRIGGER preserve_approval_request_identity
|
|
2897
|
+
BEFORE UPDATE OF id, project_id, execution_run_id, action_kind, action_schema_version,
|
|
2898
|
+
action_json, action_fingerprint, authority_schema_version, authority_json, expires_at, created_at
|
|
2899
|
+
ON approval_requests
|
|
2900
|
+
BEGIN
|
|
2901
|
+
SELECT RAISE(ABORT, 'Approval request identity and evidence are immutable');
|
|
2902
|
+
END;
|
|
2903
|
+
|
|
2904
|
+
CREATE TRIGGER preserve_approval_request_decision_evidence
|
|
2905
|
+
BEFORE UPDATE OF decision_at, consumed_at ON approval_requests
|
|
2906
|
+
WHEN (OLD.decision_at IS NOT NULL AND NEW.decision_at IS NOT OLD.decision_at)
|
|
2907
|
+
OR (OLD.consumed_at IS NOT NULL AND NEW.consumed_at IS NOT OLD.consumed_at)
|
|
2908
|
+
BEGIN
|
|
2909
|
+
SELECT RAISE(ABORT, 'Approval request decision evidence is immutable');
|
|
2910
|
+
END;
|
|
2911
|
+
|
|
2912
|
+
CREATE TRIGGER validate_approval_request_status_transition
|
|
2913
|
+
BEFORE UPDATE OF status ON approval_requests
|
|
2914
|
+
WHEN NOT (
|
|
2915
|
+
(OLD.status = 'pending' AND NEW.status IN ('approved', 'denied', 'cancelled', 'expired', 'invalidated'))
|
|
2916
|
+
OR (OLD.status = 'approved' AND NEW.status IN ('consumed', 'expired', 'invalidated'))
|
|
2917
|
+
)
|
|
2918
|
+
BEGIN
|
|
2919
|
+
SELECT RAISE(ABORT, 'Invalid approval request status transition');
|
|
2920
|
+
END;
|
|
2921
|
+
|
|
2922
|
+
CREATE INDEX idx_approval_requests_project_created
|
|
2923
|
+
ON approval_requests(project_id, created_at DESC, id DESC);
|
|
2924
|
+
CREATE INDEX idx_approval_requests_project_status_expiry
|
|
2925
|
+
ON approval_requests(project_id, status, expires_at, id);
|
|
2926
|
+
CREATE INDEX idx_approval_requests_action_kind
|
|
2927
|
+
ON approval_requests(project_id, action_kind, created_at DESC, id DESC);
|
|
2928
|
+
`);
|
|
2929
|
+
};
|
|
2930
|
+
migrateApprovalRequestActionLimit();
|
|
2931
|
+
db.exec(`
|
|
2932
|
+
CREATE TABLE IF NOT EXISTS build_setup_evidence (
|
|
2933
|
+
id TEXT PRIMARY KEY,
|
|
2934
|
+
project_id TEXT NOT NULL,
|
|
2935
|
+
execution_run_id TEXT NOT NULL,
|
|
2936
|
+
target_kind TEXT NOT NULL,
|
|
2937
|
+
target_key TEXT NOT NULL,
|
|
2938
|
+
target_workspace_path TEXT NOT NULL,
|
|
2939
|
+
workspace_evidence_state TEXT NOT NULL,
|
|
2940
|
+
workspace_evidence_fingerprint TEXT,
|
|
2941
|
+
setup_configuration_fingerprint TEXT NOT NULL,
|
|
2942
|
+
execution_profile_fingerprint TEXT NOT NULL,
|
|
2943
|
+
provider TEXT NOT NULL,
|
|
2944
|
+
environment_revision INTEGER,
|
|
2945
|
+
command_count INTEGER NOT NULL,
|
|
2946
|
+
outcome TEXT NOT NULL,
|
|
2947
|
+
reused_from_evidence_id TEXT,
|
|
2948
|
+
started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2949
|
+
completed_at DATETIME,
|
|
2950
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
2951
|
+
FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
|
|
2952
|
+
FOREIGN KEY (reused_from_evidence_id) REFERENCES build_setup_evidence(id) ON DELETE CASCADE,
|
|
2953
|
+
CHECK (length(id) BETWEEN 1 AND 128),
|
|
2954
|
+
CHECK (length(target_kind) BETWEEN 1 AND 100),
|
|
2955
|
+
CHECK (length(CAST(target_key AS BLOB)) BETWEEN 1 AND 1000),
|
|
2956
|
+
CHECK (length(CAST(target_workspace_path AS BLOB)) BETWEEN 1 AND 4096),
|
|
2957
|
+
CHECK (workspace_evidence_state IN ('available', 'unavailable')),
|
|
2958
|
+
CHECK ((workspace_evidence_state = 'available') = (workspace_evidence_fingerprint IS NOT NULL)),
|
|
2959
|
+
CHECK (workspace_evidence_fingerprint IS NULL OR (length(workspace_evidence_fingerprint) = 64 AND workspace_evidence_fingerprint NOT GLOB '*[^0-9a-f]*')),
|
|
2960
|
+
CHECK (length(setup_configuration_fingerprint) = 64 AND setup_configuration_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2961
|
+
CHECK (length(execution_profile_fingerprint) = 64 AND execution_profile_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2962
|
+
CHECK (provider IN ('host', 'docker_sandbox')),
|
|
2963
|
+
CHECK ((provider = 'host') = (environment_revision IS NULL)),
|
|
2964
|
+
CHECK (environment_revision IS NULL OR environment_revision >= 1),
|
|
2965
|
+
CHECK (command_count >= 0),
|
|
2966
|
+
CHECK (outcome IN ('running', 'completed', 'noop', 'reused', 'failed')),
|
|
2967
|
+
CHECK ((outcome = 'reused') = (reused_from_evidence_id IS NOT NULL)),
|
|
2968
|
+
CHECK ((outcome = 'running') = (completed_at IS NULL))
|
|
2969
|
+
);
|
|
2970
|
+
|
|
2971
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_build_setup_evidence_active_target
|
|
2972
|
+
ON build_setup_evidence(project_id, target_key)
|
|
2973
|
+
WHERE outcome = 'running';
|
|
2974
|
+
|
|
2975
|
+
CREATE INDEX IF NOT EXISTS idx_build_setup_evidence_reuse
|
|
2976
|
+
ON build_setup_evidence(
|
|
2977
|
+
project_id, target_key, workspace_evidence_fingerprint,
|
|
2978
|
+
setup_configuration_fingerprint, execution_profile_fingerprint,
|
|
2979
|
+
provider, environment_revision, completed_at DESC
|
|
2980
|
+
)
|
|
2981
|
+
WHERE outcome IN ('completed', 'noop', 'reused');
|
|
2982
|
+
|
|
2983
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_build_setup_evidence_execution_run
|
|
2984
|
+
ON build_setup_evidence(execution_run_id);
|
|
2985
|
+
|
|
2986
|
+
CREATE TRIGGER IF NOT EXISTS preserve_build_setup_evidence_identity
|
|
2987
|
+
BEFORE UPDATE OF id, project_id, execution_run_id, target_kind, target_key,
|
|
2988
|
+
target_workspace_path, workspace_evidence_state, workspace_evidence_fingerprint,
|
|
2989
|
+
setup_configuration_fingerprint, execution_profile_fingerprint, provider,
|
|
2990
|
+
environment_revision, command_count, reused_from_evidence_id, started_at
|
|
2991
|
+
ON build_setup_evidence
|
|
2992
|
+
BEGIN
|
|
2993
|
+
SELECT RAISE(ABORT, 'Build setup evidence identity is immutable');
|
|
2994
|
+
END;
|
|
2995
|
+
`);
|
|
2996
|
+
db.exec('DROP TRIGGER IF EXISTS preserve_execution_environment_identity');
|
|
2997
|
+
db.exec('DROP TRIGGER IF EXISTS validate_execution_environment_binding_identity');
|
|
2998
|
+
db.exec('DROP TRIGGER IF EXISTS preserve_execution_environment_binding_identity');
|
|
2999
|
+
db.exec(`
|
|
3000
|
+
CREATE TRIGGER preserve_execution_environment_identity
|
|
3001
|
+
BEFORE UPDATE OF id, project_id, provider_kind, provider_version, target_kind, target_key,
|
|
3002
|
+
target_workspace_path, environment_revision, workspace_mode,
|
|
3003
|
+
environment_validation_fingerprint, execution_profile_revision_id, execution_profile_fingerprint,
|
|
3004
|
+
credential_requirements_fingerprint, template_identity, template_digest,
|
|
3005
|
+
agent_identity, compatible_agents_json, created_at ON execution_environments
|
|
3006
|
+
WHEN NEW.id <> OLD.id OR NEW.project_id <> OLD.project_id OR NEW.provider_kind <> OLD.provider_kind
|
|
3007
|
+
OR NEW.provider_version <> OLD.provider_version OR NEW.target_kind <> OLD.target_kind
|
|
3008
|
+
OR NEW.target_key <> OLD.target_key OR NEW.target_workspace_path <> OLD.target_workspace_path
|
|
3009
|
+
OR NEW.environment_revision <> OLD.environment_revision OR NEW.workspace_mode <> OLD.workspace_mode
|
|
3010
|
+
OR NEW.environment_validation_fingerprint <> OLD.environment_validation_fingerprint
|
|
3011
|
+
OR NEW.execution_profile_revision_id IS NOT OLD.execution_profile_revision_id
|
|
3012
|
+
OR NEW.execution_profile_fingerprint IS NOT OLD.execution_profile_fingerprint
|
|
3013
|
+
OR NEW.credential_requirements_fingerprint IS NOT OLD.credential_requirements_fingerprint
|
|
3014
|
+
OR NEW.template_identity IS NOT OLD.template_identity OR NEW.template_digest IS NOT OLD.template_digest
|
|
3015
|
+
OR NEW.agent_identity IS NOT OLD.agent_identity OR NEW.compatible_agents_json <> OLD.compatible_agents_json
|
|
3016
|
+
OR NEW.created_at <> OLD.created_at
|
|
3017
|
+
BEGIN SELECT RAISE(ABORT, 'Execution environment identity is immutable'); END;
|
|
3018
|
+
|
|
3019
|
+
CREATE TRIGGER validate_execution_environment_binding_identity
|
|
3020
|
+
BEFORE INSERT ON execution_environment_run_bindings
|
|
3021
|
+
WHEN NOT EXISTS (
|
|
3022
|
+
SELECT 1 FROM execution_environments WHERE id = NEW.environment_id AND project_id = NEW.project_id
|
|
3023
|
+
AND environment_revision = NEW.environment_revision
|
|
3024
|
+
AND environment_validation_fingerprint = NEW.environment_validation_fingerprint
|
|
3025
|
+
AND execution_profile_fingerprint IS NEW.execution_profile_fingerprint
|
|
3026
|
+
)
|
|
3027
|
+
BEGIN SELECT RAISE(ABORT, 'Execution environment binding identity does not match'); END;
|
|
3028
|
+
|
|
3029
|
+
CREATE TRIGGER preserve_execution_environment_binding_identity
|
|
3030
|
+
BEFORE UPDATE OF environment_id, execution_run_id, project_id, relationship,
|
|
3031
|
+
environment_revision, environment_validation_fingerprint, execution_profile_fingerprint, bound_at
|
|
3032
|
+
ON execution_environment_run_bindings
|
|
3033
|
+
WHEN NEW.environment_id <> OLD.environment_id OR NEW.execution_run_id <> OLD.execution_run_id
|
|
3034
|
+
OR NEW.project_id <> OLD.project_id OR NEW.relationship <> OLD.relationship
|
|
3035
|
+
OR NEW.environment_revision <> OLD.environment_revision
|
|
3036
|
+
OR NEW.environment_validation_fingerprint <> OLD.environment_validation_fingerprint
|
|
3037
|
+
OR NEW.execution_profile_fingerprint IS NOT OLD.execution_profile_fingerprint
|
|
3038
|
+
OR NEW.bound_at <> OLD.bound_at
|
|
3039
|
+
BEGIN SELECT RAISE(ABORT, 'Execution environment run binding identity is immutable'); END;
|
|
3040
|
+
`);
|
|
2303
3041
|
db.exec('DROP TRIGGER IF EXISTS validate_execution_environment_binding_authority');
|
|
2304
3042
|
db.exec(`
|
|
2305
3043
|
CREATE TRIGGER IF NOT EXISTS validate_project_build_execution_provider_insert
|
|
@@ -2344,7 +3082,8 @@ db.exec(`
|
|
|
2344
3082
|
SELECT RAISE(ABORT, 'Execution environment binding evidence is immutable');
|
|
2345
3083
|
END;
|
|
2346
3084
|
`);
|
|
2347
|
-
db.exec('
|
|
3085
|
+
db.exec('DROP INDEX IF EXISTS idx_execution_environments_reuse');
|
|
3086
|
+
db.exec('CREATE INDEX idx_execution_environments_reuse ON execution_environments(project_id, provider_kind, target_key, target_workspace_path, environment_revision, workspace_mode, environment_validation_fingerprint, execution_profile_fingerprint, credential_requirements_fingerprint, retirement_requested_at, lifecycle_state)');
|
|
2348
3087
|
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_execution_environments_external_id ON execution_environments(provider_kind, external_id) WHERE external_id IS NOT NULL');
|
|
2349
3088
|
db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environments_cleanup ON execution_environments(lifecycle_state, cleanup_claim_id, updated_at, cleanup_attempts)');
|
|
2350
3089
|
db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environment_events_sequence ON execution_environment_events(project_id, environment_id, sequence)');
|
|
@@ -2677,7 +3416,7 @@ db.exec(`
|
|
|
2677
3416
|
CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2678
3417
|
CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
|
|
2679
3418
|
'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
|
|
2680
|
-
'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss')),
|
|
3419
|
+
'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss')),
|
|
2681
3420
|
CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
|
|
2682
3421
|
AND total_persisted_bytes >= retained_end_offset),
|
|
2683
3422
|
CHECK (output_retention_state IN ('retained', 'expired'))
|
|
@@ -2688,14 +3427,23 @@ ensureColumn('check_attempt_details', 'retained_output', "TEXT NOT NULL DEFAULT
|
|
|
2688
3427
|
const checkAttemptSchema = db.prepare(`
|
|
2689
3428
|
SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'check_attempt_details'
|
|
2690
3429
|
`).get();
|
|
2691
|
-
if (checkAttemptSchema && !checkAttemptSchema.sql.includes('evidence_persistence_failure')
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
3430
|
+
if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistence_failure')
|
|
3431
|
+
|| !checkAttemptSchema.sql.includes('ambiguous_process_loss'))) {
|
|
3432
|
+
const foreignKeysEnabled = db.pragma('foreign_keys', { simple: true }) === 1;
|
|
3433
|
+
if (foreignKeysEnabled) {
|
|
3434
|
+
db.pragma('foreign_keys = OFF');
|
|
3435
|
+
}
|
|
3436
|
+
try {
|
|
3437
|
+
const referencingTriggers = db.prepare(`
|
|
3438
|
+
SELECT name FROM sqlite_master
|
|
3439
|
+
WHERE type = 'trigger' AND sql LIKE '%check_attempt_details%'
|
|
3440
|
+
`).all();
|
|
3441
|
+
for (const trigger of referencingTriggers) {
|
|
3442
|
+
db.exec(`DROP TRIGGER IF EXISTS "${trigger.name.replaceAll('"', '""')}"`);
|
|
3443
|
+
}
|
|
3444
|
+
db.transaction(() => {
|
|
3445
|
+
db.exec(`
|
|
3446
|
+
CREATE TABLE check_attempt_details_replacement (
|
|
2699
3447
|
execution_run_id TEXT PRIMARY KEY,
|
|
2700
3448
|
batch_run_id TEXT NOT NULL,
|
|
2701
3449
|
definition_id TEXT NOT NULL,
|
|
@@ -2721,13 +3469,13 @@ if (checkAttemptSchema && !checkAttemptSchema.sql.includes('evidence_persistence
|
|
|
2721
3469
|
CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
|
|
2722
3470
|
CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
|
|
2723
3471
|
'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
|
|
2724
|
-
'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss')),
|
|
3472
|
+
'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss')),
|
|
2725
3473
|
CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
|
|
2726
3474
|
AND total_persisted_bytes >= retained_end_offset),
|
|
2727
3475
|
CHECK (output_retention_state IN ('retained', 'expired'))
|
|
2728
3476
|
);
|
|
2729
3477
|
|
|
2730
|
-
INSERT INTO
|
|
3478
|
+
INSERT INTO check_attempt_details_replacement (
|
|
2731
3479
|
execution_run_id, batch_run_id, definition_id, command_snapshot_json, definition_fingerprint,
|
|
2732
3480
|
initial_workspace_identity_json, final_workspace_identity_json, outcome, rerun_of_attempt_run_id,
|
|
2733
3481
|
exit_code, exit_signal, exit_error, output_revision, retained_start_offset, retained_end_offset,
|
|
@@ -2738,11 +3486,22 @@ if (checkAttemptSchema && !checkAttemptSchema.sql.includes('evidence_persistence
|
|
|
2738
3486
|
initial_workspace_identity_json, final_workspace_identity_json, outcome, rerun_of_attempt_run_id,
|
|
2739
3487
|
exit_code, exit_signal, exit_error, output_revision, retained_start_offset, retained_end_offset,
|
|
2740
3488
|
total_persisted_bytes, retained_output, output_retention_state
|
|
2741
|
-
FROM
|
|
3489
|
+
FROM check_attempt_details;
|
|
2742
3490
|
|
|
2743
|
-
DROP TABLE
|
|
2744
|
-
|
|
2745
|
-
|
|
3491
|
+
DROP TABLE check_attempt_details;
|
|
3492
|
+
ALTER TABLE check_attempt_details_replacement RENAME TO check_attempt_details;
|
|
3493
|
+
`);
|
|
3494
|
+
})();
|
|
3495
|
+
}
|
|
3496
|
+
finally {
|
|
3497
|
+
if (foreignKeysEnabled) {
|
|
3498
|
+
db.pragma('foreign_keys = ON');
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
const foreignKeyViolation = db.pragma('foreign_key_check')[0];
|
|
3502
|
+
if (foreignKeyViolation) {
|
|
3503
|
+
throw new Error(`Check attempt migration left an invalid foreign key: ${JSON.stringify(foreignKeyViolation)}`);
|
|
3504
|
+
}
|
|
2746
3505
|
}
|
|
2747
3506
|
db.exec(`
|
|
2748
3507
|
CREATE TABLE IF NOT EXISTS check_attempt_diagnostic_parsers (
|
|
@@ -4327,6 +5086,11 @@ db.exec('CREATE INDEX IF NOT EXISTS idx_project_links_linked_project_id ON proje
|
|
|
4327
5086
|
db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequences_project_status ON build_sequences(project_id, status)');
|
|
4328
5087
|
db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequences_bundle_status ON build_sequences(worktree_bundle_id, status)');
|
|
4329
5088
|
db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequence_tickets_sequence_position ON build_sequence_tickets(build_sequence_id, position)');
|
|
5089
|
+
db.exec(`
|
|
5090
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_build_sequence_tickets_activation_run
|
|
5091
|
+
ON build_sequence_tickets(activation_execution_run_id)
|
|
5092
|
+
WHERE activation_execution_run_id IS NOT NULL
|
|
5093
|
+
`);
|
|
4330
5094
|
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_integration_connections_provider_project ON integration_connections(provider, project_id)');
|
|
4331
5095
|
db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_external_key ON tickets(external_provider, external_key)');
|
|
4332
5096
|
db.exec('CREATE INDEX IF NOT EXISTS idx_bundle_automation_state_project_bundle ON bundle_automation_state(project_id, worktree_bundle_id)');
|