@straiffi/archon 1.7.7 → 1.7.9

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