@straiffi/archon 1.7.7 → 1.7.8

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.
Files changed (122) hide show
  1. package/dist/client/assets/ActivityView-DwfDSF4q.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-3dMCYCSZ.js +1 -0
  3. package/dist/client/assets/VerificationDialog-CoTfXMyb.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DVbmGv0b.js → architectureDiagram-3BPJPVTR-Bdnc97XK.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-BsxPuJKK.js → blockDiagram-GPEHLZMM-boxM3She.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-9Jjpu2R1.js → c4Diagram-AAUBKEIU-CJDdF7xd.js} +1 -1
  7. package/dist/client/assets/channel-BGqdAcIT.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-Dzljq_5a.js → chunk-2J33WTMH-ClpcO-Om.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CiBHaLdV.js → chunk-3OPIFGDE-CcuL_65J.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-DZFrI-_H.js → chunk-5ZQYHXKU-CUGYZXkH.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-u519MeJ7.js → chunk-727SXJPM-CApeLDyK.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DC91Nw9K.js → chunk-AQP2D5EJ-8FdSvF2D.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CR7cwH-D.js → chunk-CSCIHK7Q-CZoIOF3a.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-DJwQEfa_.js → chunk-KSCS5N6A-BigFQI5y.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-w6TWSOTT.js → chunk-L5ZTLDWV-DIi3SHaT.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Dfw_jEjR.js → chunk-LZXEDZCA-B-Z0BcQy.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-DHdJgCA7.js → chunk-ND2GUHAM-EBeba0mg.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-GvOuUiUy.js → chunk-NZK2D7GU-DJBI2y53.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O--r1VQgDc.js → chunk-O5CBEL6O-BqxdXhMd.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Xgc6shf5.js → chunk-WU5MYG2G-Coe1_Cbb.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-D5VuKRcj.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9GNqpRr.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-IcfTsQ6f.js → dagre-BM42HDAG-tuTmeA0Y.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-D8Z4_NSQ.js → diagram-2AECGRRQ-ILGCpG2F.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CIjC8QzH.js → diagram-5GNKFQAL-C8qBZsgK.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BBbYFygn.js → diagram-KO2AKTUF-BXlcm_Zo.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-lZstJdXv.js → diagram-LMA3HP47-B8V5EVbD.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-Cnqij60i.js → diagram-OG6HWLK6-CWsdOqcP.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-1DAJwSbR.js → erDiagram-TEJ5UH35-DW8pgly2.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DIQkR95e.js → flowDiagram-I6XJVG4X-Djn7fOVR.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-mDGbRM53.js → ganttDiagram-6RSMTGT7-DZ421MEy.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BQirGZuE.js → gitGraphDiagram-PVQCEYII-CCNzLPuP.js} +1 -1
  33. package/dist/client/assets/index-BRc02lbW.js +273 -0
  34. package/dist/client/assets/index-CZO9HL1Z.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-CljoXByt.js → infoDiagram-5YYISTIA-Bd1gYgMp.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-ouY7AiSo.js → ishikawaDiagram-YF4QCWOH-txyMoatJ.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BiI9xI0r.js → journeyDiagram-JHISSGLW-DhT3bmX6.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-CvuW7aYT.js → kanban-definition-UN3LZRKU-BBHt2hfC.js} +1 -1
  39. package/dist/client/assets/{line-CoE97hT8.js → line-KGRcT7Qz.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BHswQsmF.js → mermaid-parser.core-CDzt4_zR.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-a9PzZlL1.js → mermaid.core-DD_-k1aB.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-CIkEQqLV.js → mindmap-definition-RKZ34NQL-DoctVTDl.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-BIvcv2M5.js → pieDiagram-4H26LBE5-DFlLEi70.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BWCVll7J.js → quadrantDiagram-W4KKPZXB-3YHOQgl7.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-w5l0CZn2.js → requirementDiagram-4Y6WPE33-X4fZVL9M.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BuKReL9w.js → sankeyDiagram-5OEKKPKP-BUPnMXeG.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Br5KpukA.js → sequenceDiagram-3UESZ5HK-D6QU3M64.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-BfFCsPCI.js → stateDiagram-AJRCARHV-c35bu4qZ.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ymy1_EHr.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BrDP2iO0.js → timeline-definition-PNZ67QCA-CX2y6e_b.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-C7lGYnr9.js → vennDiagram-CIIHVFJN-BDaWltjF.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BRKJRuEF.js → wardleyDiagram-YWT4CUSO-BpBRL2sb.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BjaB0hQ8.js → xychartDiagram-2RQKCTM6-Bqw7X0UT.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +458 -2
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +324 -25
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/activeAutomationChild.js +31 -0
  60. package/dist/server/lib/activeAutomationChild.js.map +1 -0
  61. package/dist/server/lib/automationCheckGate.js +266 -0
  62. package/dist/server/lib/automationCheckGate.js.map +1 -0
  63. package/dist/server/lib/automationProgression.js +20 -21
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +16 -2
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +4 -1
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationStageDetails.js +173 -41
  70. package/dist/server/lib/automationStageDetails.js.map +1 -1
  71. package/dist/server/lib/checkDefinitions.js +278 -0
  72. package/dist/server/lib/checkDefinitions.js.map +1 -0
  73. package/dist/server/lib/checkGateEvaluations.js +294 -0
  74. package/dist/server/lib/checkGateEvaluations.js.map +1 -0
  75. package/dist/server/lib/checkRunner.js +674 -0
  76. package/dist/server/lib/checkRunner.js.map +1 -0
  77. package/dist/server/lib/checkRuns.js +736 -0
  78. package/dist/server/lib/checkRuns.js.map +1 -0
  79. package/dist/server/lib/checkWorkspaceIdentity.js +207 -0
  80. package/dist/server/lib/checkWorkspaceIdentity.js.map +1 -0
  81. package/dist/server/lib/executionReconciliation.js +79 -2
  82. package/dist/server/lib/executionReconciliation.js.map +1 -1
  83. package/dist/server/lib/executionRetry.js +14 -6
  84. package/dist/server/lib/executionRetry.js.map +1 -1
  85. package/dist/server/lib/executionRetryContext.js +3 -3
  86. package/dist/server/lib/executionRetryContext.js.map +1 -1
  87. package/dist/server/lib/executionRunRetention.js +41 -3
  88. package/dist/server/lib/executionRunRetention.js.map +1 -1
  89. package/dist/server/lib/executionRuns.js +135 -2
  90. package/dist/server/lib/executionRuns.js.map +1 -1
  91. package/dist/server/lib/executionTimeline.js +52 -1
  92. package/dist/server/lib/executionTimeline.js.map +1 -1
  93. package/dist/server/lib/projectAutoConfig.js +47 -65
  94. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  95. package/dist/server/lib/projects.js +87 -7
  96. package/dist/server/lib/projects.js.map +1 -1
  97. package/dist/server/lib/requiredCheckPolicy.js +68 -0
  98. package/dist/server/lib/requiredCheckPolicy.js.map +1 -0
  99. package/dist/server/lib/shutdown.js +4 -4
  100. package/dist/server/lib/shutdown.js.map +1 -1
  101. package/dist/server/lib/testCommandRunner.js +3 -2
  102. package/dist/server/lib/testCommandRunner.js.map +1 -1
  103. package/dist/server/lib/testSessions.js +2 -6
  104. package/dist/server/lib/testSessions.js.map +1 -1
  105. package/dist/server/lib/ticketAutomationCommit.js +185 -22
  106. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  107. package/dist/server/lib/tickets.js +21 -1
  108. package/dist/server/lib/tickets.js.map +1 -1
  109. package/dist/server/lib/types.js +2 -0
  110. package/dist/server/lib/types.js.map +1 -1
  111. package/dist/server/workers/review.js +2 -10
  112. package/dist/server/workers/review.js.map +1 -1
  113. package/package.json +1 -1
  114. package/dist/client/assets/ActivityView-Btpsv-5U.js +0 -1
  115. package/dist/client/assets/GitHubActionsDialog-DRlIMgSB.js +0 -1
  116. package/dist/client/assets/TestsDialog-DSWDkKPV.js +0 -4
  117. package/dist/client/assets/channel-Lic13vb5.js +0 -1
  118. package/dist/client/assets/classDiagram-4FO5ZUOK-DACLwQIJ.js +0 -1
  119. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKi6c1d9.js +0 -1
  120. package/dist/client/assets/index-DQt4dcwc.css +0 -2
  121. package/dist/client/assets/index-W9OCqkMU.js +0 -273
  122. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-lhIQ8FTe.js +0 -1
package/dist/server/db.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { createHash } from 'crypto';
3
+ import { parseCheckDefinitionsJsonStrict, parseLegacyTestCommandsJsonStrict, projectCheckDefinitionsToTestCommands, } from './lib/checkDefinitions.js';
3
4
  import { ensureParentDirectory, resolveArchonDbPath } from './lib/runtimePaths.js';
4
5
  import { TRANSCRIPT_RUN_TEXT_LIMIT_BYTES, truncateUtf8 } from './lib/transcriptRuns.js';
5
6
  const dbPath = ensureParentDirectory(resolveArchonDbPath());
@@ -55,6 +56,8 @@ db.exec(`
55
56
  worktree_cmd TEXT,
56
57
  run_setup TEXT,
57
58
  run_services TEXT,
59
+ check_definitions TEXT,
60
+ required_check_definition_ids TEXT NOT NULL DEFAULT '[]',
58
61
  test_commands TEXT,
59
62
  run_ide TEXT,
60
63
  preview_service_name TEXT,
@@ -129,6 +132,8 @@ ensureColumn('worktree_bundles', 'automation_pause_on_open_questions', 'INTEGER'
129
132
  ensureColumn('projects', 'worktree_sync', 'TEXT');
130
133
  ensureColumn('projects', 'active_target_kind', "TEXT NOT NULL DEFAULT 'repo_root'");
131
134
  ensureColumn('projects', 'active_target_bundle_id', 'TEXT');
135
+ ensureColumn('projects', 'check_definitions', 'TEXT');
136
+ ensureColumn('projects', 'required_check_definition_ids', "TEXT NOT NULL DEFAULT '[]'");
132
137
  ensureColumn('projects', 'test_commands', 'TEXT');
133
138
  ensureColumn('projects', 'helper_model', 'TEXT');
134
139
  ensureColumn('projects', 'helper_variant', 'TEXT');
@@ -144,6 +149,38 @@ ensureColumn('projects', 'spec_aware_code_review_enabled', 'INTEGER NOT NULL DEF
144
149
  ensureColumn('projects', 'deletion_started_at', 'DATETIME');
145
150
  ensureColumn('projects', 'deletion_claim_id', 'TEXT');
146
151
  ensureColumn('projects', 'deletion_cleanup_started_at', 'DATETIME');
152
+ const backfillProjectCheckDefinitions = () => {
153
+ const rows = db.prepare(`
154
+ SELECT id, check_definitions, test_commands
155
+ FROM projects
156
+ `).all();
157
+ const update = db.prepare(`
158
+ UPDATE projects
159
+ SET check_definitions = ?, test_commands = ?
160
+ WHERE id = ?
161
+ `);
162
+ const migrate = db.transaction(() => {
163
+ for (const row of rows) {
164
+ if (row.check_definitions == null && row.test_commands == null) {
165
+ continue;
166
+ }
167
+ const result = row.check_definitions == null
168
+ ? parseLegacyTestCommandsJsonStrict(row.test_commands)
169
+ : parseCheckDefinitionsJsonStrict(row.check_definitions);
170
+ if ('error' in result) {
171
+ continue;
172
+ }
173
+ const definitions = result.value;
174
+ const canonicalJson = JSON.stringify(definitions);
175
+ const projectionJson = JSON.stringify(projectCheckDefinitionsToTestCommands(definitions));
176
+ if (row.check_definitions !== canonicalJson || row.test_commands !== projectionJson) {
177
+ update.run(canonicalJson, projectionJson, row.id);
178
+ }
179
+ }
180
+ });
181
+ migrate();
182
+ };
183
+ backfillProjectCheckDefinitions();
147
184
  db.exec(`
148
185
  CREATE TRIGGER IF NOT EXISTS reject_ticket_insert_for_deleting_project
149
186
  BEFORE INSERT ON tickets
@@ -1516,7 +1553,14 @@ const migrateExecutionLifecycleSchema = () => {
1516
1553
  db.exec('ALTER TABLE lifecycle_events_v2 RENAME TO lifecycle_events');
1517
1554
  db.exec('DROP TABLE execution_run_correlations');
1518
1555
  const violations = db.prepare('PRAGMA foreign_key_check').all();
1519
- if (violations.some(violation => ['execution_runs', 'lifecycle_events', 'test_execution_details', 'github_actions_run_details'].includes(violation.table))) {
1556
+ if (violations.some(violation => [
1557
+ 'execution_runs',
1558
+ 'lifecycle_events',
1559
+ 'test_execution_details',
1560
+ 'github_actions_run_details',
1561
+ 'check_batch_details',
1562
+ 'check_attempt_details',
1563
+ ].includes(violation.table))) {
1520
1564
  throw new Error('Execution lifecycle migration produced foreign key violations');
1521
1565
  }
1522
1566
  db.exec('COMMIT');
@@ -1755,6 +1799,377 @@ db.exec(`
1755
1799
  FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE
1756
1800
  )
1757
1801
  `);
1802
+ db.exec(`
1803
+ CREATE TABLE IF NOT EXISTS check_batch_details (
1804
+ execution_run_id TEXT PRIMARY KEY,
1805
+ target_kind TEXT NOT NULL,
1806
+ target_bundle_id TEXT,
1807
+ target_label TEXT,
1808
+ selected_definition_snapshots_json TEXT NOT NULL,
1809
+ initial_workspace_identity_json TEXT NOT NULL,
1810
+ configuration_fingerprint TEXT NOT NULL,
1811
+ stop_requested INTEGER NOT NULL DEFAULT 0,
1812
+ queued_count INTEGER NOT NULL DEFAULT 0,
1813
+ running_count INTEGER NOT NULL DEFAULT 0,
1814
+ passed_count INTEGER NOT NULL DEFAULT 0,
1815
+ failed_count INTEGER NOT NULL DEFAULT 0,
1816
+ cancelled_count INTEGER NOT NULL DEFAULT 0,
1817
+ interrupted_count INTEGER NOT NULL DEFAULT 0,
1818
+ rerun_of_batch_run_id TEXT,
1819
+ FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
1820
+ FOREIGN KEY (rerun_of_batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
1821
+ CHECK (target_kind IN ('repo_root', 'bundle')),
1822
+ CHECK ((target_kind = 'repo_root' AND target_bundle_id IS NULL)
1823
+ OR (target_kind = 'bundle' AND target_bundle_id IS NOT NULL AND length(trim(target_bundle_id)) > 0)),
1824
+ CHECK (length(configuration_fingerprint) = 64 AND configuration_fingerprint NOT GLOB '*[^0-9a-f]*'),
1825
+ CHECK (stop_requested IN (0, 1)),
1826
+ CHECK (queued_count >= 0 AND running_count >= 0 AND passed_count >= 0 AND failed_count >= 0
1827
+ AND cancelled_count >= 0 AND interrupted_count >= 0)
1828
+ )
1829
+ `);
1830
+ db.exec(`
1831
+ CREATE TABLE IF NOT EXISTS check_attempt_details (
1832
+ execution_run_id TEXT PRIMARY KEY,
1833
+ batch_run_id TEXT NOT NULL,
1834
+ definition_id TEXT NOT NULL,
1835
+ command_snapshot_json TEXT NOT NULL,
1836
+ definition_fingerprint TEXT NOT NULL,
1837
+ initial_workspace_identity_json TEXT NOT NULL,
1838
+ final_workspace_identity_json TEXT,
1839
+ outcome TEXT,
1840
+ rerun_of_attempt_run_id TEXT,
1841
+ exit_code INTEGER,
1842
+ exit_signal TEXT,
1843
+ exit_error TEXT,
1844
+ output_revision INTEGER NOT NULL DEFAULT 0,
1845
+ retained_start_offset INTEGER NOT NULL DEFAULT 0,
1846
+ retained_end_offset INTEGER NOT NULL DEFAULT 0,
1847
+ total_persisted_bytes INTEGER NOT NULL DEFAULT 0,
1848
+ retained_output TEXT NOT NULL DEFAULT '',
1849
+ output_retention_state TEXT NOT NULL DEFAULT 'retained',
1850
+ FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
1851
+ FOREIGN KEY (batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
1852
+ FOREIGN KEY (rerun_of_attempt_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
1853
+ CHECK (length(definition_id) BETWEEN 1 AND 500),
1854
+ CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
1855
+ CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure', 'timeout',
1856
+ 'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss')),
1857
+ CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
1858
+ AND total_persisted_bytes >= retained_end_offset),
1859
+ CHECK (output_retention_state IN ('retained', 'expired'))
1860
+ )
1861
+ `);
1862
+ db.exec(`
1863
+ CREATE TABLE IF NOT EXISTS check_gate_evaluations (
1864
+ id TEXT PRIMARY KEY,
1865
+ project_id TEXT NOT NULL,
1866
+ target_kind TEXT NOT NULL,
1867
+ target_bundle_id TEXT,
1868
+ automation_progression_run_id TEXT,
1869
+ boundary_kind TEXT NOT NULL,
1870
+ effective_policy_fingerprint TEXT NOT NULL,
1871
+ source_batch_run_id TEXT,
1872
+ decision TEXT NOT NULL,
1873
+ reason_codes_json TEXT NOT NULL,
1874
+ snapshot_json TEXT NOT NULL,
1875
+ expected_attempt_count INTEGER NOT NULL DEFAULT 0,
1876
+ attempts_sealed INTEGER NOT NULL DEFAULT 1,
1877
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
1878
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
1879
+ FOREIGN KEY (source_batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
1880
+ CHECK (target_kind IN ('repo_root', 'bundle')),
1881
+ CHECK ((target_kind = 'repo_root' AND target_bundle_id IS NULL)
1882
+ OR (target_kind = 'bundle' AND target_bundle_id IS NOT NULL AND length(trim(target_bundle_id)) > 0)),
1883
+ CHECK (length(boundary_kind) BETWEEN 1 AND 100),
1884
+ CHECK (length(effective_policy_fingerprint) = 64 AND effective_policy_fingerprint NOT GLOB '*[^0-9a-f]*'),
1885
+ CHECK (decision IN ('passed', 'blocked')),
1886
+ CHECK (expected_attempt_count >= 0),
1887
+ CHECK (attempts_sealed IN (0, 1)),
1888
+ CHECK (length(CAST(reason_codes_json AS BLOB)) <= 16384),
1889
+ CHECK (length(CAST(snapshot_json AS BLOB)) <= 262144)
1890
+ );
1891
+
1892
+ CREATE TABLE IF NOT EXISTS check_gate_evaluation_attempts (
1893
+ evaluation_id TEXT NOT NULL,
1894
+ ordinal INTEGER NOT NULL,
1895
+ attempt_run_id TEXT NOT NULL,
1896
+ attempt_snapshot_json TEXT NOT NULL,
1897
+ PRIMARY KEY (evaluation_id, ordinal),
1898
+ UNIQUE (evaluation_id, attempt_run_id),
1899
+ FOREIGN KEY (evaluation_id) REFERENCES check_gate_evaluations(id) ON DELETE CASCADE,
1900
+ FOREIGN KEY (attempt_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
1901
+ CHECK (ordinal >= 0),
1902
+ CHECK (length(CAST(attempt_snapshot_json AS BLOB)) <= 65536)
1903
+ );
1904
+
1905
+ CREATE TRIGGER IF NOT EXISTS preserve_check_gate_evaluations
1906
+ BEFORE UPDATE OF id, project_id, target_kind, target_bundle_id, automation_progression_run_id,
1907
+ boundary_kind, effective_policy_fingerprint, source_batch_run_id, decision, reason_codes_json,
1908
+ snapshot_json, expected_attempt_count, created_at ON check_gate_evaluations
1909
+ BEGIN
1910
+ SELECT RAISE(ABORT, 'Check gate evaluations are immutable');
1911
+ END;
1912
+
1913
+ CREATE TRIGGER IF NOT EXISTS preserve_check_gate_evaluation_attempts
1914
+ BEFORE UPDATE ON check_gate_evaluation_attempts
1915
+ BEGIN
1916
+ SELECT RAISE(ABORT, 'Check gate evaluation attempts are immutable');
1917
+ END;
1918
+
1919
+ CREATE TRIGGER IF NOT EXISTS prevent_check_gate_evaluation_delete
1920
+ BEFORE DELETE ON check_gate_evaluations
1921
+ WHEN EXISTS (
1922
+ SELECT 1 FROM projects WHERE id = OLD.project_id AND deletion_started_at IS NULL
1923
+ )
1924
+ BEGIN
1925
+ SELECT RAISE(ABORT, 'Check gate evaluations are immutable');
1926
+ END;
1927
+
1928
+
1929
+ CREATE TRIGGER IF NOT EXISTS prevent_check_gate_evaluation_attempt_delete
1930
+ BEFORE DELETE ON check_gate_evaluation_attempts
1931
+ WHEN EXISTS (
1932
+ SELECT 1
1933
+ FROM check_gate_evaluations evaluation
1934
+ JOIN projects project ON project.id = evaluation.project_id
1935
+ WHERE evaluation.id = OLD.evaluation_id AND project.deletion_started_at IS NULL
1936
+ )
1937
+ BEGIN
1938
+ SELECT RAISE(ABORT, 'Check gate evaluation attempts are immutable');
1939
+ END;
1940
+
1941
+ CREATE TRIGGER IF NOT EXISTS validate_check_gate_evaluation_insert
1942
+ BEFORE INSERT ON check_gate_evaluations
1943
+ WHEN (
1944
+ NEW.source_batch_run_id IS NOT NULL
1945
+ AND NOT EXISTS (
1946
+ SELECT 1 FROM execution_runs
1947
+ WHERE id = NEW.source_batch_run_id AND project_id = NEW.project_id AND kind = 'check_batch'
1948
+ )
1949
+ ) OR (
1950
+ NEW.automation_progression_run_id IS NOT NULL
1951
+ AND NOT EXISTS (
1952
+ SELECT 1 FROM execution_runs
1953
+ WHERE id = NEW.automation_progression_run_id
1954
+ AND project_id = NEW.project_id AND kind = 'automation_progression'
1955
+ )
1956
+ ) OR (
1957
+ NEW.source_batch_run_id IS NOT NULL
1958
+ AND NOT EXISTS (
1959
+ SELECT 1 FROM execution_runs batch
1960
+ WHERE batch.id = NEW.source_batch_run_id
1961
+ AND (
1962
+ (batch.parent_run_id IS NULL AND NEW.automation_progression_run_id IS NULL)
1963
+ OR batch.parent_run_id = NEW.automation_progression_run_id
1964
+ )
1965
+ )
1966
+ )
1967
+ BEGIN
1968
+ SELECT RAISE(ABORT, 'Invalid check gate evaluation lineage');
1969
+ END;
1970
+
1971
+ CREATE TRIGGER IF NOT EXISTS validate_check_gate_evaluation_attempt_insert
1972
+ BEFORE INSERT ON check_gate_evaluation_attempts
1973
+ WHEN NOT EXISTS (
1974
+ SELECT 1
1975
+ FROM check_gate_evaluations evaluation
1976
+ JOIN execution_runs attempt ON attempt.id = NEW.attempt_run_id
1977
+ WHERE evaluation.id = NEW.evaluation_id
1978
+ AND attempt.project_id = evaluation.project_id
1979
+ AND attempt.kind = 'check_attempt'
1980
+ )
1981
+ BEGIN
1982
+ SELECT RAISE(ABORT, 'Invalid check gate evaluation attempt lineage');
1983
+ END;
1984
+ `);
1985
+ ensureColumn('check_gate_evaluations', 'expected_attempt_count', 'INTEGER NOT NULL DEFAULT 0');
1986
+ ensureColumn('check_gate_evaluations', 'attempts_sealed', 'INTEGER NOT NULL DEFAULT 1');
1987
+ db.exec(`
1988
+ DROP TRIGGER IF EXISTS preserve_check_gate_evaluations;
1989
+ DROP TRIGGER IF EXISTS seal_check_gate_evaluation_attempts;
1990
+ DROP TRIGGER IF EXISTS validate_check_gate_evaluation_insert;
1991
+ DROP TRIGGER IF EXISTS validate_check_gate_evaluation_attempt_insert;
1992
+
1993
+ CREATE TRIGGER preserve_check_gate_evaluations
1994
+ BEFORE UPDATE OF id, project_id, target_kind, target_bundle_id, automation_progression_run_id,
1995
+ boundary_kind, effective_policy_fingerprint, source_batch_run_id, decision, reason_codes_json,
1996
+ snapshot_json, expected_attempt_count, created_at ON check_gate_evaluations
1997
+ BEGIN
1998
+ SELECT RAISE(ABORT, 'Check gate evaluations are immutable');
1999
+ END;
2000
+
2001
+ CREATE TRIGGER seal_check_gate_evaluation_attempts
2002
+ BEFORE UPDATE OF attempts_sealed ON check_gate_evaluations
2003
+ WHEN OLD.attempts_sealed <> 0 OR NEW.attempts_sealed <> 1 OR (
2004
+ SELECT COUNT(*) FROM check_gate_evaluation_attempts WHERE evaluation_id = OLD.id
2005
+ ) <> OLD.expected_attempt_count
2006
+ BEGIN
2007
+ SELECT RAISE(ABORT, 'Invalid check gate evaluation seal');
2008
+ END;
2009
+
2010
+ CREATE TRIGGER validate_check_gate_evaluation_insert
2011
+ BEFORE INSERT ON check_gate_evaluations
2012
+ WHEN (
2013
+ NEW.source_batch_run_id IS NOT NULL
2014
+ AND NOT EXISTS (
2015
+ SELECT 1 FROM execution_runs
2016
+ WHERE id = NEW.source_batch_run_id AND project_id = NEW.project_id AND kind = 'check_batch'
2017
+ )
2018
+ ) OR (
2019
+ NEW.automation_progression_run_id IS NOT NULL
2020
+ AND NOT EXISTS (
2021
+ SELECT 1 FROM execution_runs
2022
+ WHERE id = NEW.automation_progression_run_id
2023
+ AND project_id = NEW.project_id AND kind = 'automation_progression'
2024
+ )
2025
+ ) OR (
2026
+ NEW.source_batch_run_id IS NOT NULL
2027
+ AND NOT EXISTS (
2028
+ SELECT 1 FROM execution_runs batch
2029
+ WHERE batch.id = NEW.source_batch_run_id
2030
+ AND (
2031
+ (batch.initiated_by <> 'automation' AND batch.parent_run_id IS NULL)
2032
+ OR (batch.initiated_by = 'automation' AND batch.parent_run_id = NEW.automation_progression_run_id)
2033
+ )
2034
+ )
2035
+ )
2036
+ BEGIN
2037
+ SELECT RAISE(ABORT, 'Invalid check gate evaluation lineage');
2038
+ END;
2039
+
2040
+ CREATE TRIGGER validate_check_gate_evaluation_attempt_insert
2041
+ BEFORE INSERT ON check_gate_evaluation_attempts
2042
+ WHEN NOT EXISTS (
2043
+ SELECT 1
2044
+ FROM check_gate_evaluations evaluation
2045
+ JOIN execution_runs attempt ON attempt.id = NEW.attempt_run_id
2046
+ JOIN check_attempt_details details ON details.execution_run_id = attempt.id
2047
+ WHERE evaluation.id = NEW.evaluation_id
2048
+ AND evaluation.attempts_sealed = 0
2049
+ AND NEW.ordinal < evaluation.expected_attempt_count
2050
+ AND evaluation.source_batch_run_id IS NOT NULL
2051
+ AND details.batch_run_id = evaluation.source_batch_run_id
2052
+ AND attempt.project_id = evaluation.project_id
2053
+ AND attempt.kind = 'check_attempt'
2054
+ )
2055
+ BEGIN
2056
+ SELECT RAISE(ABORT, 'Invalid check gate evaluation attempt lineage');
2057
+ END;
2058
+ `);
2059
+ ensureColumn('check_attempt_details', 'retained_end_offset', 'INTEGER NOT NULL DEFAULT 0');
2060
+ ensureColumn('check_attempt_details', 'retained_output', "TEXT NOT NULL DEFAULT ''");
2061
+ db.prepare(`
2062
+ UPDATE check_attempt_details
2063
+ SET retained_end_offset = total_persisted_bytes
2064
+ WHERE retained_end_offset = 0 AND total_persisted_bytes > 0
2065
+ `).run();
2066
+ db.exec('DROP TRIGGER IF EXISTS validate_check_batch_details_insert');
2067
+ db.exec(`
2068
+ CREATE TRIGGER IF NOT EXISTS preserve_check_batch_snapshots
2069
+ BEFORE UPDATE OF execution_run_id, target_kind, target_bundle_id, target_label,
2070
+ selected_definition_snapshots_json, initial_workspace_identity_json, configuration_fingerprint,
2071
+ rerun_of_batch_run_id ON check_batch_details
2072
+ BEGIN
2073
+ SELECT RAISE(ABORT, 'Check batch snapshots are immutable');
2074
+ END;
2075
+
2076
+ CREATE TRIGGER IF NOT EXISTS preserve_check_attempt_snapshots
2077
+ BEFORE UPDATE OF execution_run_id, batch_run_id, definition_id, command_snapshot_json,
2078
+ definition_fingerprint, initial_workspace_identity_json, rerun_of_attempt_run_id
2079
+ ON check_attempt_details
2080
+ BEGIN
2081
+ SELECT RAISE(ABORT, 'Check attempt snapshots are immutable');
2082
+ END;
2083
+
2084
+ CREATE TRIGGER IF NOT EXISTS preserve_completed_check_attempt_details
2085
+ BEFORE UPDATE OF final_workspace_identity_json, outcome, exit_code, exit_signal, exit_error
2086
+ ON check_attempt_details
2087
+ WHEN OLD.outcome IS NOT NULL
2088
+ BEGIN
2089
+ SELECT RAISE(ABORT, 'Completed check attempt details are immutable');
2090
+ END;
2091
+
2092
+ CREATE TRIGGER IF NOT EXISTS validate_check_batch_details_insert
2093
+ BEFORE INSERT ON check_batch_details
2094
+ WHEN NOT EXISTS (
2095
+ SELECT 1
2096
+ FROM execution_runs run
2097
+ WHERE run.id = NEW.execution_run_id
2098
+ AND run.kind = 'check_batch'
2099
+ AND (
2100
+ (run.parent_run_id IS NULL AND run.initiated_by <> 'automation')
2101
+ OR (
2102
+ run.initiated_by = 'automation'
2103
+ AND EXISTS (
2104
+ SELECT 1
2105
+ FROM execution_runs progression
2106
+ JOIN execution_run_links owner
2107
+ ON owner.execution_run_id = progression.id
2108
+ AND owner.project_id = progression.project_id
2109
+ AND owner.entity_type = 'bundle'
2110
+ AND owner.relationship = 'primary'
2111
+ JOIN worktree_bundles bundle
2112
+ ON bundle.id = owner.entity_id
2113
+ AND bundle.project_id = owner.project_id
2114
+ JOIN bundle_automation_state state
2115
+ ON state.project_id = progression.project_id
2116
+ AND state.worktree_bundle_id = owner.entity_id
2117
+ AND state.automation_progression_run_id = progression.id
2118
+ WHERE progression.id = run.parent_run_id
2119
+ AND progression.project_id = run.project_id
2120
+ AND progression.kind = 'automation_progression'
2121
+ AND progression.status IN ('queued', 'running', 'waiting')
2122
+ AND (
2123
+ (NEW.target_kind = 'bundle' AND owner.entity_id = NEW.target_bundle_id
2124
+ AND COALESCE(bundle.kind, 'worktree') <> 'project_root')
2125
+ OR (NEW.target_kind = 'repo_root' AND NEW.target_bundle_id IS NULL
2126
+ AND bundle.kind = 'project_root')
2127
+ )
2128
+ AND NOT EXISTS (
2129
+ SELECT 1 FROM execution_runs sibling
2130
+ WHERE sibling.project_id = run.project_id
2131
+ AND sibling.parent_run_id = progression.id
2132
+ AND sibling.id <> run.id
2133
+ AND sibling.kind IN ('check_batch', 'review', 'automation_review_fix', 'automation_commit', 'automation_push', 'automation_pull_request')
2134
+ AND sibling.status IN ('queued', 'running', 'waiting')
2135
+ )
2136
+ )
2137
+ )
2138
+ )
2139
+ ) OR (
2140
+ NEW.rerun_of_batch_run_id IS NOT NULL AND NOT EXISTS (
2141
+ SELECT 1 FROM execution_runs source, execution_runs current
2142
+ WHERE source.id = NEW.rerun_of_batch_run_id AND source.kind = 'check_batch'
2143
+ AND current.id = NEW.execution_run_id AND source.project_id = current.project_id
2144
+ )
2145
+ )
2146
+ BEGIN
2147
+ SELECT RAISE(ABORT, 'Invalid check batch execution details');
2148
+ END;
2149
+
2150
+ CREATE TRIGGER IF NOT EXISTS validate_check_attempt_details_insert
2151
+ BEFORE INSERT ON check_attempt_details
2152
+ WHEN NOT EXISTS (
2153
+ SELECT 1
2154
+ FROM execution_runs attempt
2155
+ JOIN execution_runs batch ON batch.id = NEW.batch_run_id
2156
+ WHERE attempt.id = NEW.execution_run_id AND attempt.kind = 'check_attempt'
2157
+ AND attempt.parent_run_id = batch.id AND batch.kind = 'check_batch'
2158
+ AND attempt.project_id = batch.project_id AND attempt.correlation_id = batch.correlation_id
2159
+ ) OR (
2160
+ NEW.rerun_of_attempt_run_id IS NOT NULL AND NOT EXISTS (
2161
+ SELECT 1
2162
+ FROM check_attempt_details source
2163
+ JOIN check_batch_details current_batch ON current_batch.execution_run_id = NEW.batch_run_id
2164
+ WHERE source.execution_run_id = NEW.rerun_of_attempt_run_id
2165
+ AND source.batch_run_id = current_batch.rerun_of_batch_run_id
2166
+ AND source.definition_id = NEW.definition_id
2167
+ )
2168
+ )
2169
+ BEGIN
2170
+ SELECT RAISE(ABORT, 'Invalid check attempt execution details');
2171
+ END;
2172
+ `);
1758
2173
  ensureColumn('github_actions_run_details', 'ref', 'TEXT');
1759
2174
  ensureColumn('github_actions_run_details', 'input_names_json', 'TEXT');
1760
2175
  ensureColumn('github_actions_run_details', 'input_fingerprint', 'TEXT');
@@ -1983,6 +2398,12 @@ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_execution_run_links_primary ON ex
1983
2398
  db.exec('CREATE INDEX IF NOT EXISTS idx_lifecycle_events_run_sequence ON lifecycle_events(project_id, execution_run_id, sequence ASC)');
1984
2399
  db.exec('CREATE INDEX IF NOT EXISTS idx_lifecycle_events_project_created ON lifecycle_events(project_id, created_at DESC, id DESC)');
1985
2400
  db.exec('CREATE INDEX IF NOT EXISTS idx_github_actions_details_dispatch ON github_actions_run_details(workflow_id, ref, dispatched_at DESC)');
2401
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_batches_rerun ON check_batch_details(rerun_of_batch_run_id)');
2402
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_attempts_batch ON check_attempt_details(batch_run_id, definition_id)');
2403
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_attempts_rerun ON check_attempt_details(rerun_of_attempt_run_id)');
2404
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_gate_evaluations_target ON check_gate_evaluations(project_id, target_kind, target_bundle_id, created_at DESC, id DESC)');
2405
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_gate_evaluations_batch ON check_gate_evaluations(source_batch_run_id)');
2406
+ db.exec('CREATE INDEX IF NOT EXISTS idx_check_gate_evaluation_attempts_run ON check_gate_evaluation_attempts(attempt_run_id)');
1986
2407
  db.exec(`
1987
2408
  CREATE TABLE IF NOT EXISTS project_memory_selections (
1988
2409
  id TEXT PRIMARY KEY,
@@ -2133,11 +2554,46 @@ db.exec(`
2133
2554
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
2134
2555
  FOREIGN KEY (worktree_bundle_id) REFERENCES worktree_bundles(id) ON DELETE CASCADE,
2135
2556
  CHECK (stage_kind IN ('automation_commit', 'automation_push', 'automation_pull_request')),
2136
- CHECK (schema_version = 1),
2557
+ CHECK (schema_version IN (1, 2)),
2137
2558
  CHECK (length(CAST(intent_json AS BLOB)) BETWEEN 2 AND 16384),
2138
2559
  CHECK (result_json IS NULL OR length(CAST(result_json AS BLOB)) BETWEEN 2 AND 16384)
2139
2560
  )
2140
2561
  `);
2562
+ const automationStageDetailsTableSql = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'automation_stage_details'").get()?.sql ?? '';
2563
+ if (!automationStageDetailsTableSql.includes('schema_version IN (1, 2)')) {
2564
+ db.transaction(() => {
2565
+ db.exec(`
2566
+ CREATE TABLE automation_stage_details_v2 (
2567
+ execution_run_id TEXT PRIMARY KEY,
2568
+ project_id TEXT NOT NULL,
2569
+ worktree_bundle_id TEXT NOT NULL,
2570
+ stage_kind TEXT NOT NULL,
2571
+ schema_version INTEGER NOT NULL,
2572
+ intent_json TEXT NOT NULL,
2573
+ result_json TEXT,
2574
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2575
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2576
+ FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
2577
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
2578
+ FOREIGN KEY (worktree_bundle_id) REFERENCES worktree_bundles(id) ON DELETE CASCADE,
2579
+ CHECK (stage_kind IN ('automation_commit', 'automation_push', 'automation_pull_request')),
2580
+ CHECK (schema_version IN (1, 2)),
2581
+ CHECK (length(CAST(intent_json AS BLOB)) BETWEEN 2 AND 16384),
2582
+ CHECK (result_json IS NULL OR length(CAST(result_json AS BLOB)) BETWEEN 2 AND 16384)
2583
+ );
2584
+ INSERT INTO automation_stage_details_v2 (
2585
+ execution_run_id, project_id, worktree_bundle_id, stage_kind, schema_version,
2586
+ intent_json, result_json, created_at, updated_at
2587
+ )
2588
+ SELECT
2589
+ execution_run_id, project_id, worktree_bundle_id, stage_kind, schema_version,
2590
+ intent_json, result_json, created_at, updated_at
2591
+ FROM automation_stage_details;
2592
+ DROP TABLE automation_stage_details;
2593
+ ALTER TABLE automation_stage_details_v2 RENAME TO automation_stage_details;
2594
+ `);
2595
+ })();
2596
+ }
2141
2597
  db.exec(`
2142
2598
  CREATE TABLE IF NOT EXISTS build_open_question_batches (
2143
2599
  id TEXT PRIMARY KEY,