@straiffi/archon 1.10.9 → 1.10.12

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 (165) hide show
  1. package/dist/client/assets/ActivityView-BFn_Nhj4.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-iZD86Yju.js +1 -0
  3. package/dist/client/assets/VerificationDialog-CqKE8t1d.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BAIGV9Mj.js → architectureDiagram-3BPJPVTR-C8KefY67.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-B-5v5jSZ.js → blockDiagram-GPEHLZMM-BO2Q_Osr.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-C6wU415o.js → c4Diagram-AAUBKEIU-DGPkhbrK.js} +1 -1
  7. package/dist/client/assets/channel-DXKn3TOv.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-COmvX2tP.js → chunk-2J33WTMH-Dtb7Xu8C.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-BlANptdY.js → chunk-3OPIFGDE-Bnf4GbOF.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-ETyA2_GU.js → chunk-5ZQYHXKU-BWGid3g7.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-D5-DudwF.js → chunk-727SXJPM-ScA8IRGP.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-k1AJ4sA3.js → chunk-AQP2D5EJ-Bygg04Ip.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-jfksXwY9.js → chunk-CSCIHK7Q-CxdDDtfN.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-CHj0FDj9.js → chunk-KSCS5N6A-BgpZFsB5.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-1ODGxXhG.js → chunk-L5ZTLDWV-CKSew0OL.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Bgh9X2W0.js → chunk-LZXEDZCA-DVG9Rx0K.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-DDkb3sp4.js → chunk-ND2GUHAM-CHBqNmuF.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-B6B8PZ_-.js → chunk-NZK2D7GU-DvZYWn7V.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-DhiUIFgj.js → chunk-O5CBEL6O-BL7lRTSe.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Dzd0fbP3.js → chunk-WU5MYG2G-C-il2wHZ.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-U0fU20hT.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C6bQNSxL.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-DoNDMCXI.js → dagre-BM42HDAG-B00l5tLi.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-ETDXSa5q.js → diagram-2AECGRRQ-Tum6g1_5.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CgTUep7e.js → diagram-5GNKFQAL-BK1GX5jc.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BKTx2iRe.js → diagram-KO2AKTUF-BrzmD5NB.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-9iEiScHT.js → diagram-LMA3HP47-BhrlgXiJ.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-UIZRh7Qa.js → diagram-OG6HWLK6-K-fWsJ3a.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-BpNJmJva.js → erDiagram-TEJ5UH35-Der6PjHE.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-C-rK0XZ1.js → flowDiagram-I6XJVG4X-BVWgGhXe.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DWyfPvv_.js → ganttDiagram-6RSMTGT7-DcoZ5ggj.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-x71bzdc_.js → gitGraphDiagram-PVQCEYII-BhhbMTBu.js} +1 -1
  33. package/dist/client/assets/index-B6BltHSr.css +2 -0
  34. package/dist/client/assets/index-CFOYm3j9.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-CbRus0Xu.js → infoDiagram-5YYISTIA-Lwc-agyc.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DvaMHrgN.js → ishikawaDiagram-YF4QCWOH-B95U1Xcl.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BQBu4YGn.js → journeyDiagram-JHISSGLW-xFpigy5D.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-8rU-U7L1.js → kanban-definition-UN3LZRKU-D3mudQnR.js} +1 -1
  39. package/dist/client/assets/{line-CSZnwjLA.js → line-DtTL3ZI2.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-CTOl2slE.js → mermaid-parser.core-Dau7K6zV.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-B9gr0Vqf.js → mermaid.core-B8qtvATu.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-But1_PWQ.js → mindmap-definition-RKZ34NQL-Cus04Yq3.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CnlVmfSG.js → pieDiagram-4H26LBE5-BCMGpBnn.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dww1ZGuR.js → quadrantDiagram-W4KKPZXB-CAmOr-PJ.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CNf_loe8.js → requirementDiagram-4Y6WPE33-kisD3jc4.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBhi37Jj.js → sankeyDiagram-5OEKKPKP-CsCedmM0.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B64hb4E4.js → sequenceDiagram-3UESZ5HK-B-dAi954.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-DcS6oB-S.js → stateDiagram-AJRCARHV-X0BnedIW.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-6B8YH8Y2.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-Dq-4tXfh.js → timeline-definition-PNZ67QCA-DfFoSjhr.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-CeDklSE_.js → vennDiagram-CIIHVFJN-CKSCBwhG.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D2cJISMm.js → wardleyDiagram-YWT4CUSO-DNIBQmHO.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CBF-tTpF.js → xychartDiagram-2RQKCTM6-FvB55TQN.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +368 -5
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +194 -250
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agentRuns.js +14 -1
  60. package/dist/server/lib/agentRuns.js.map +1 -1
  61. package/dist/server/lib/automationLoopProjection.js +55 -12
  62. package/dist/server/lib/automationLoopProjection.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +2 -5
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationResumeDecision.js +19 -1
  66. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  67. package/dist/server/lib/buildControlEligibility.js +4 -0
  68. package/dist/server/lib/buildControlEligibility.js.map +1 -0
  69. package/dist/server/lib/buildExecutionAdmission.js +76 -0
  70. package/dist/server/lib/buildExecutionAdmission.js.map +1 -0
  71. package/dist/server/lib/buildSequenceResume.js +79 -0
  72. package/dist/server/lib/buildSequenceResume.js.map +1 -0
  73. package/dist/server/lib/buildWorkload.js +11 -0
  74. package/dist/server/lib/buildWorkload.js.map +1 -0
  75. package/dist/server/lib/checkAutoFixDetails.js +36 -1
  76. package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
  77. package/dist/server/lib/executionEnvironmentReconciliation.js +15 -2
  78. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
  79. package/dist/server/lib/executionEnvironments.js +15 -0
  80. package/dist/server/lib/executionEnvironments.js.map +1 -1
  81. package/dist/server/lib/executionRetry.js +13 -1
  82. package/dist/server/lib/executionRetry.js.map +1 -1
  83. package/dist/server/lib/executionRunAuthority.js +2 -2
  84. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  85. package/dist/server/lib/executionTargets.js +7 -4
  86. package/dist/server/lib/executionTargets.js.map +1 -1
  87. package/dist/server/lib/executionTimeline.js +8 -13
  88. package/dist/server/lib/executionTimeline.js.map +1 -1
  89. package/dist/server/lib/executionTimelineHierarchy.js +83 -0
  90. package/dist/server/lib/executionTimelineHierarchy.js.map +1 -0
  91. package/dist/server/lib/failedBuildSequenceRecovery.js +133 -0
  92. package/dist/server/lib/failedBuildSequenceRecovery.js.map +1 -0
  93. package/dist/server/lib/hostRunSession.js +62 -0
  94. package/dist/server/lib/hostRunSession.js.map +1 -0
  95. package/dist/server/lib/hostServiceRecovery.js +159 -0
  96. package/dist/server/lib/hostServiceRecovery.js.map +1 -0
  97. package/dist/server/lib/hostServiceRunProvider.js +32 -0
  98. package/dist/server/lib/hostServiceRunProvider.js.map +1 -0
  99. package/dist/server/lib/mobileAccess.js +6 -2
  100. package/dist/server/lib/mobileAccess.js.map +1 -1
  101. package/dist/server/lib/projectAutoConfig.js +5 -2
  102. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  103. package/dist/server/lib/projectTargets.js +16 -2
  104. package/dist/server/lib/projectTargets.js.map +1 -1
  105. package/dist/server/lib/projects.js +25 -1
  106. package/dist/server/lib/projects.js.map +1 -1
  107. package/dist/server/lib/reviewFindingReconciliation.js +9 -2
  108. package/dist/server/lib/reviewFindingReconciliation.js.map +1 -1
  109. package/dist/server/lib/reviewFindings.js +82 -21
  110. package/dist/server/lib/reviewFindings.js.map +1 -1
  111. package/dist/server/lib/reviewRepairAssessmentRecords.js +41 -0
  112. package/dist/server/lib/reviewRepairAssessmentRecords.js.map +1 -0
  113. package/dist/server/lib/reviewRepairCompletion.js +78 -18
  114. package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
  115. package/dist/server/lib/reviewRepairIterationRecords.js +4 -2
  116. package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -1
  117. package/dist/server/lib/reviewRepairIterations.js +8 -2
  118. package/dist/server/lib/reviewRepairIterations.js.map +1 -1
  119. package/dist/server/lib/reviewRepairProgress.js +177 -0
  120. package/dist/server/lib/reviewRepairProgress.js.map +1 -0
  121. package/dist/server/lib/reviewRepairReconciliation.js +119 -140
  122. package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
  123. package/dist/server/lib/reviewRepairResumeOverride.js +55 -0
  124. package/dist/server/lib/reviewRepairResumeOverride.js.map +1 -0
  125. package/dist/server/lib/run.js +541 -504
  126. package/dist/server/lib/run.js.map +1 -1
  127. package/dist/server/lib/runProcessIdentity.js +71 -0
  128. package/dist/server/lib/runProcessIdentity.js.map +1 -0
  129. package/dist/server/lib/runProcesses.js +273 -0
  130. package/dist/server/lib/runProcesses.js.map +1 -0
  131. package/dist/server/lib/runServices.js +223 -0
  132. package/dist/server/lib/runServices.js.map +1 -0
  133. package/dist/server/lib/runSessionLifecycle.js +77 -0
  134. package/dist/server/lib/runSessionLifecycle.js.map +1 -0
  135. package/dist/server/lib/runStatus.js +8 -0
  136. package/dist/server/lib/runStatus.js.map +1 -0
  137. package/dist/server/lib/serviceRunCleanup.js +70 -0
  138. package/dist/server/lib/serviceRunCleanup.js.map +1 -0
  139. package/dist/server/lib/serviceRuns.js +458 -19
  140. package/dist/server/lib/serviceRuns.js.map +1 -1
  141. package/dist/server/lib/ticketAutomation.js +4 -27
  142. package/dist/server/lib/ticketAutomation.js.map +1 -1
  143. package/dist/server/lib/ticketRunOperations.js +103 -34
  144. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  145. package/dist/server/lib/tickets.js +0 -13
  146. package/dist/server/lib/tickets.js.map +1 -1
  147. package/dist/server/lib/types.js.map +1 -1
  148. package/dist/server/workers/build.js +44 -69
  149. package/dist/server/workers/build.js.map +1 -1
  150. package/dist/server/workers/plan.js +0 -7
  151. package/dist/server/workers/plan.js.map +1 -1
  152. package/dist/server/workers/review.js +95 -146
  153. package/dist/server/workers/review.js.map +1 -1
  154. package/package.json +1 -1
  155. package/dist/client/assets/ActivityView-CKvbDXeA.js +0 -1
  156. package/dist/client/assets/GitHubActionsDialog-Dzo0vtak.js +0 -1
  157. package/dist/client/assets/VerificationDialog-BJnOitrJ.js +0 -4
  158. package/dist/client/assets/channel-5mIy7nm1.js +0 -1
  159. package/dist/client/assets/classDiagram-4FO5ZUOK-BiQ-X2Sg.js +0 -1
  160. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CF-wCYW-.js +0 -1
  161. package/dist/client/assets/index-CIAO5aE_.css +0 -2
  162. package/dist/client/assets/index-nCGyalrn.js +0 -276
  163. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-PWeeL0tR.js +0 -1
  164. package/dist/server/lib/automationDeliveryDecision.js +0 -34
  165. package/dist/server/lib/automationDeliveryDecision.js.map +0 -1
package/dist/server/db.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Database from 'better-sqlite3';
2
- import { createHash } from 'crypto';
2
+ import { createHash, randomUUID } 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';
@@ -183,6 +183,46 @@ ensureColumn('projects', 'spec_aware_code_review_enabled', 'INTEGER NOT NULL DEF
183
183
  ensureColumn('projects', 'deletion_started_at', 'DATETIME');
184
184
  ensureColumn('projects', 'deletion_claim_id', 'TEXT');
185
185
  ensureColumn('projects', 'deletion_cleanup_started_at', 'DATETIME');
186
+ ensureColumn('projects', 'run_services', 'TEXT');
187
+ db.transaction(() => {
188
+ const projects = db.prepare('SELECT id, run_services FROM projects WHERE run_services IS NOT NULL').all();
189
+ const update = db.prepare('UPDATE projects SET run_services = ? WHERE id = ?');
190
+ for (const project of projects) {
191
+ let services;
192
+ try {
193
+ services = JSON.parse(project.run_services);
194
+ }
195
+ catch {
196
+ continue;
197
+ }
198
+ // Leave malformed legacy configuration intact rather than partially rewriting it.
199
+ if (!Array.isArray(services) || services.some(service => !service || typeof service !== 'object' || Array.isArray(service))) {
200
+ continue;
201
+ }
202
+ const entries = services;
203
+ const validId = (id) => typeof id === 'string' && id.trim().length > 0 && id === id.trim();
204
+ const reserved = new Set(entries.map(service => service.id).filter(validId));
205
+ const seen = new Set();
206
+ let changed = false;
207
+ for (const service of entries) {
208
+ if (validId(service.id) && !seen.has(service.id)) {
209
+ seen.add(service.id);
210
+ continue;
211
+ }
212
+ let id;
213
+ do {
214
+ id = randomUUID();
215
+ } while (reserved.has(id));
216
+ reserved.add(id);
217
+ seen.add(id);
218
+ service.id = id;
219
+ changed = true;
220
+ }
221
+ if (changed) {
222
+ update.run(JSON.stringify(entries), project.id);
223
+ }
224
+ }
225
+ }).immediate();
186
226
  const backfillAgentModelMaps = () => {
187
227
  db.transaction(() => {
188
228
  db.exec(`
@@ -2655,6 +2695,15 @@ db.exec(`
2655
2695
  service_snapshot_json TEXT NOT NULL,
2656
2696
  placement_json TEXT,
2657
2697
  interruption_scope TEXT,
2698
+ intended_status TEXT,
2699
+ intended_event_type TEXT,
2700
+ intended_message TEXT,
2701
+ cleanup_claim_id TEXT,
2702
+ cleanup_claimed_at DATETIME,
2703
+ cleanup_attempts INTEGER NOT NULL DEFAULT 0,
2704
+ cleanup_retry_at DATETIME,
2705
+ cleanup_error_message TEXT,
2706
+ force_released_at DATETIME,
2658
2707
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2659
2708
  started_at DATETIME,
2660
2709
  completed_at DATETIME,
@@ -2672,7 +2721,11 @@ db.exec(`
2672
2721
  CHECK (environment_revision IS NULL OR environment_revision >= 1),
2673
2722
  CHECK (length(CAST(service_snapshot_json AS BLOB)) BETWEEN 2 AND 16384),
2674
2723
  CHECK (placement_json IS NULL OR length(CAST(placement_json AS BLOB)) BETWEEN 2 AND 16384),
2675
- CHECK (interruption_scope IS NULL OR interruption_scope IN ('local_processes', 'whole_environment'))
2724
+ CHECK (interruption_scope IS NULL OR interruption_scope IN ('local_processes', 'whole_environment')),
2725
+ CHECK (intended_status IS NULL OR intended_status IN ('completed', 'failed', 'cancelled', 'interrupted')),
2726
+ CHECK ((cleanup_claim_id IS NULL) = (cleanup_claimed_at IS NULL)),
2727
+ CHECK (cleanup_attempts >= 0),
2728
+ CHECK (cleanup_error_message IS NULL OR length(cleanup_error_message) <= 4000)
2676
2729
  );
2677
2730
 
2678
2731
  CREATE UNIQUE INDEX IF NOT EXISTS idx_active_service_run_context
@@ -2682,6 +2735,15 @@ db.exec(`
2682
2735
  CREATE INDEX IF NOT EXISTS idx_service_run_profile
2683
2736
  ON service_run_details(project_id, execution_profile_revision_id, created_at);
2684
2737
  `);
2738
+ ensureColumn('service_run_details', 'intended_status', 'TEXT');
2739
+ ensureColumn('service_run_details', 'intended_event_type', 'TEXT');
2740
+ ensureColumn('service_run_details', 'intended_message', 'TEXT');
2741
+ ensureColumn('service_run_details', 'cleanup_claim_id', 'TEXT');
2742
+ ensureColumn('service_run_details', 'cleanup_claimed_at', 'DATETIME');
2743
+ ensureColumn('service_run_details', 'cleanup_attempts', 'INTEGER NOT NULL DEFAULT 0');
2744
+ ensureColumn('service_run_details', 'cleanup_retry_at', 'DATETIME');
2745
+ ensureColumn('service_run_details', 'cleanup_error_message', 'TEXT');
2746
+ ensureColumn('service_run_details', 'force_released_at', 'DATETIME');
2685
2747
  const executionEnvironmentStates = "'queued', 'preparing', 'ready', 'running', 'stopped', 'retiring', 'cleanup_pending', 'retired', 'failed', 'unavailable'";
2686
2748
  db.exec(`
2687
2749
  CREATE TABLE IF NOT EXISTS execution_environments (
@@ -2892,6 +2954,195 @@ ensureColumn('execution_environments', 'operation_claimed_at', 'DATETIME');
2892
2954
  ensureColumn('execution_environment_run_bindings', 'execution_profile_fingerprint', 'TEXT');
2893
2955
  ensureColumn('execution_environment_run_bindings', 'authority_json', 'TEXT');
2894
2956
  ensureColumn('execution_environment_run_bindings', 'placement_json', 'TEXT');
2957
+ db.exec('DROP TRIGGER IF EXISTS protect_service_project_target');
2958
+ db.exec(`
2959
+ CREATE VIEW IF NOT EXISTS active_project_service_authorities AS
2960
+ SELECT run.id AS execution_run_id, run.project_id
2961
+ FROM execution_runs run
2962
+ WHERE run.kind = 'service' AND (
2963
+ run.status NOT IN ('completed', 'failed', 'cancelled', 'interrupted')
2964
+ OR EXISTS (SELECT 1 FROM service_run_details details
2965
+ WHERE details.execution_run_id = run.id AND details.completed_at IS NULL)
2966
+ OR EXISTS (SELECT 1 FROM execution_environment_run_bindings binding
2967
+ WHERE binding.execution_run_id = run.id AND binding.relationship = 'execution'
2968
+ AND binding.released_at IS NULL)
2969
+ );
2970
+
2971
+ CREATE TRIGGER IF NOT EXISTS admit_project_service_run
2972
+ BEFORE INSERT ON execution_runs WHEN NEW.kind = 'service'
2973
+ BEGIN
2974
+ SELECT RAISE(ABORT, 'Project is being deleted') WHERE EXISTS (
2975
+ SELECT 1 FROM projects WHERE id = NEW.project_id AND deletion_started_at IS NOT NULL);
2976
+ SELECT RAISE(ABORT, 'Project already has active service authority') WHERE EXISTS (
2977
+ SELECT 1 FROM active_project_service_authorities WHERE project_id = NEW.project_id);
2978
+ END;
2979
+
2980
+ CREATE TRIGGER IF NOT EXISTS admit_project_service_details
2981
+ BEFORE INSERT ON service_run_details
2982
+ BEGIN
2983
+ SELECT RAISE(ABORT, 'New service authority must be unfinished') WHERE NEW.completed_at IS NOT NULL;
2984
+ SELECT RAISE(ABORT, 'Service profile fingerprint does not match') WHERE NOT EXISTS (
2985
+ SELECT 1 FROM execution_profile_revisions WHERE id = NEW.execution_profile_revision_id
2986
+ AND project_id = NEW.project_id AND fingerprint = NEW.execution_profile_fingerprint);
2987
+ SELECT RAISE(ABORT, 'Service details require an active service run') WHERE NOT EXISTS (
2988
+ SELECT 1 FROM execution_runs WHERE id = NEW.execution_run_id AND project_id = NEW.project_id
2989
+ AND kind = 'service' AND status NOT IN ('completed', 'failed', 'cancelled', 'interrupted'));
2990
+ SELECT RAISE(ABORT, 'Project is being deleted') WHERE EXISTS (
2991
+ SELECT 1 FROM projects WHERE id = NEW.project_id AND deletion_started_at IS NOT NULL);
2992
+ SELECT RAISE(ABORT, 'Project already has active service authority') WHERE EXISTS (
2993
+ SELECT 1 FROM active_project_service_authorities WHERE project_id = NEW.project_id
2994
+ AND execution_run_id <> NEW.execution_run_id);
2995
+ END;
2996
+
2997
+ CREATE TRIGGER IF NOT EXISTS preserve_service_detail_identity
2998
+ BEFORE UPDATE ON service_run_details
2999
+ WHEN NEW.execution_run_id IS NOT OLD.execution_run_id OR NEW.project_id IS NOT OLD.project_id
3000
+ OR NEW.context_key IS NOT OLD.context_key OR NEW.target_kind IS NOT OLD.target_kind
3001
+ OR NEW.target_key IS NOT OLD.target_key OR NEW.target_workspace_path IS NOT OLD.target_workspace_path
3002
+ OR NEW.execution_profile_revision_id IS NOT OLD.execution_profile_revision_id
3003
+ OR NEW.execution_profile_fingerprint IS NOT OLD.execution_profile_fingerprint
3004
+ OR NEW.service_snapshot_json IS NOT OLD.service_snapshot_json
3005
+ OR (OLD.completed_at IS NOT NULL AND NEW.completed_at IS NOT OLD.completed_at)
3006
+ BEGIN
3007
+ SELECT RAISE(ABORT, 'Service run authority is immutable');
3008
+ END;
3009
+
3010
+ CREATE TRIGGER IF NOT EXISTS protect_service_run_identity
3011
+ BEFORE UPDATE OF id, kind, project_id ON execution_runs
3012
+ WHEN (OLD.kind = 'service' OR NEW.kind = 'service') AND (
3013
+ NEW.id IS NOT OLD.id OR NEW.kind IS NOT OLD.kind OR NEW.project_id IS NOT OLD.project_id)
3014
+ BEGIN
3015
+ SELECT RAISE(ABORT, 'Service run authority is immutable');
3016
+ END;
3017
+
3018
+ CREATE TRIGGER IF NOT EXISTS protect_service_run_reactivation
3019
+ BEFORE UPDATE OF status ON execution_runs
3020
+ WHEN OLD.kind = 'service' AND OLD.status IN ('completed', 'failed', 'cancelled', 'interrupted')
3021
+ AND NEW.status NOT IN ('completed', 'failed', 'cancelled', 'interrupted')
3022
+ BEGIN
3023
+ SELECT RAISE(ABORT, 'Terminal service run cannot be reactivated');
3024
+ END;
3025
+
3026
+ CREATE TRIGGER IF NOT EXISTS admit_service_execution_binding
3027
+ BEFORE INSERT ON execution_environment_run_bindings
3028
+ WHEN NEW.relationship = 'execution' AND NEW.released_at IS NULL AND EXISTS (
3029
+ SELECT 1 FROM execution_runs WHERE id = NEW.execution_run_id AND kind = 'service')
3030
+ BEGIN
3031
+ SELECT RAISE(ABORT, 'Service binding requires unfinished authority') WHERE NOT EXISTS (
3032
+ SELECT 1 FROM service_run_details details JOIN execution_runs run ON run.id = details.execution_run_id
3033
+ JOIN projects project ON project.id = details.project_id
3034
+ WHERE details.execution_run_id = NEW.execution_run_id AND details.project_id = NEW.project_id
3035
+ AND details.completed_at IS NULL AND project.deletion_started_at IS NULL
3036
+ AND run.status NOT IN ('completed', 'failed', 'cancelled', 'interrupted'));
3037
+ SELECT RAISE(ABORT, 'Project already has active service authority') WHERE EXISTS (
3038
+ SELECT 1 FROM active_project_service_authorities WHERE project_id = NEW.project_id
3039
+ AND execution_run_id <> NEW.execution_run_id);
3040
+ END;
3041
+
3042
+ CREATE TRIGGER IF NOT EXISTS protect_service_binding_reactivation
3043
+ BEFORE UPDATE OF released_at ON execution_environment_run_bindings
3044
+ WHEN OLD.relationship = 'execution' AND OLD.released_at IS NOT NULL AND NEW.released_at IS NULL
3045
+ AND EXISTS (SELECT 1 FROM execution_runs WHERE id = OLD.execution_run_id AND kind = 'service')
3046
+ BEGIN
3047
+ SELECT RAISE(ABORT, 'Released service binding cannot be reactivated');
3048
+ END;
3049
+
3050
+ CREATE TRIGGER IF NOT EXISTS protect_service_binding_deletion
3051
+ BEFORE DELETE ON execution_environment_run_bindings
3052
+ WHEN OLD.relationship = 'execution' AND OLD.released_at IS NULL
3053
+ AND EXISTS (SELECT 1 FROM execution_runs WHERE id = OLD.execution_run_id AND kind = 'service')
3054
+ BEGIN
3055
+ SELECT RAISE(ABORT, 'Service execution binding is unreleased');
3056
+ END;
3057
+
3058
+ CREATE TRIGGER IF NOT EXISTS protect_service_completion
3059
+ BEFORE UPDATE OF completed_at ON service_run_details
3060
+ WHEN NEW.completed_at IS NOT NULL AND EXISTS (
3061
+ SELECT 1 FROM execution_environment_run_bindings WHERE execution_run_id = OLD.execution_run_id
3062
+ AND relationship = 'execution' AND released_at IS NULL)
3063
+ BEGIN
3064
+ SELECT RAISE(ABORT, 'Service execution binding is unreleased');
3065
+ END;
3066
+
3067
+ CREATE TRIGGER IF NOT EXISTS require_terminal_service_completion
3068
+ BEFORE UPDATE OF completed_at ON service_run_details
3069
+ WHEN NEW.completed_at IS NOT NULL AND EXISTS (
3070
+ SELECT 1 FROM execution_runs WHERE id = OLD.execution_run_id
3071
+ AND status NOT IN ('completed', 'failed', 'cancelled', 'interrupted'))
3072
+ BEGIN
3073
+ SELECT RAISE(ABORT, 'Service run must be terminal before completing details');
3074
+ END;
3075
+
3076
+ CREATE TRIGGER IF NOT EXISTS protect_service_detail_deletion
3077
+ BEFORE DELETE ON service_run_details
3078
+ WHEN EXISTS (SELECT 1 FROM active_project_service_authorities WHERE execution_run_id = OLD.execution_run_id)
3079
+ BEGIN
3080
+ SELECT RAISE(ABORT, 'Service run authority is active');
3081
+ END;
3082
+
3083
+ CREATE TRIGGER IF NOT EXISTS protect_service_run_deletion
3084
+ BEFORE DELETE ON execution_runs
3085
+ WHEN EXISTS (SELECT 1 FROM active_project_service_authorities WHERE execution_run_id = OLD.id)
3086
+ BEGIN
3087
+ SELECT RAISE(ABORT, 'Service run authority is active');
3088
+ END;
3089
+
3090
+ CREATE TRIGGER IF NOT EXISTS protect_service_ticket_deletion
3091
+ BEFORE DELETE ON tickets
3092
+ WHEN EXISTS (
3093
+ SELECT 1 FROM active_project_service_authorities active
3094
+ JOIN execution_runs run ON run.id = active.execution_run_id
3095
+ WHERE run.source_kind = 'ticket' AND run.source_id = OLD.id)
3096
+ BEGIN
3097
+ SELECT RAISE(ABORT, 'Ticket has active service authority');
3098
+ END;
3099
+
3100
+ CREATE TRIGGER IF NOT EXISTS protect_service_ticket_target
3101
+ BEFORE UPDATE OF project_id, worktree_bundle_id ON tickets
3102
+ WHEN (NEW.project_id IS NOT OLD.project_id OR NEW.worktree_bundle_id IS NOT OLD.worktree_bundle_id)
3103
+ AND EXISTS (
3104
+ SELECT 1 FROM active_project_service_authorities active
3105
+ JOIN execution_runs run ON run.id = active.execution_run_id
3106
+ WHERE run.source_kind = 'ticket' AND run.source_id = OLD.id)
3107
+ BEGIN
3108
+ SELECT RAISE(ABORT, 'Ticket has active service authority');
3109
+ END;
3110
+
3111
+ CREATE TRIGGER IF NOT EXISTS protect_service_project_deletion_claim
3112
+ BEFORE UPDATE OF deletion_started_at ON projects
3113
+ WHEN NEW.deletion_started_at IS NOT NULL AND EXISTS (
3114
+ SELECT 1 FROM active_project_service_authorities WHERE project_id = OLD.id)
3115
+ BEGIN
3116
+ SELECT RAISE(ABORT, 'Project has active service authority');
3117
+ END;
3118
+
3119
+ CREATE TRIGGER IF NOT EXISTS protect_service_project_deletion
3120
+ BEFORE DELETE ON projects
3121
+ WHEN EXISTS (SELECT 1 FROM active_project_service_authorities WHERE project_id = OLD.id)
3122
+ BEGIN
3123
+ SELECT RAISE(ABORT, 'Project has active service authority');
3124
+ END;
3125
+
3126
+ CREATE TRIGGER IF NOT EXISTS protect_service_project_target
3127
+ BEFORE UPDATE OF active_target_kind, active_target_bundle_id ON projects
3128
+ WHEN EXISTS (
3129
+ SELECT 1 FROM active_project_service_authorities active
3130
+ JOIN execution_runs run ON run.id = active.execution_run_id
3131
+ WHERE active.project_id = OLD.id AND (run.source_kind IS NULL OR run.source_kind <> 'ticket'))
3132
+ OR OLD.deletion_started_at IS NOT NULL
3133
+ BEGIN
3134
+ SELECT RAISE(ABORT, 'Cannot change project target while service authority or deletion is active');
3135
+ END;
3136
+
3137
+ CREATE TRIGGER IF NOT EXISTS protect_service_project_repo_path
3138
+ BEFORE UPDATE OF repo_path ON projects
3139
+ WHEN NEW.repo_path IS NOT OLD.repo_path AND (
3140
+ EXISTS (SELECT 1 FROM active_project_service_authorities WHERE project_id = OLD.id)
3141
+ OR OLD.deletion_started_at IS NOT NULL)
3142
+ BEGIN
3143
+ SELECT RAISE(ABORT, 'Cannot change project repository while service authority or deletion is active');
3144
+ END;
3145
+ `);
2895
3146
  db.exec(`
2896
3147
  CREATE TABLE IF NOT EXISTS execution_run_authorities (
2897
3148
  execution_run_id TEXT NOT NULL,
@@ -5176,6 +5427,67 @@ db.exec(`
5176
5427
  FOREIGN KEY (review_run_id) REFERENCES review_runs(id) ON DELETE CASCADE
5177
5428
  )
5178
5429
  `);
5430
+ ensureColumn('review_runs', 'output_provenance', `TEXT CHECK (output_provenance IS NULL OR output_provenance IN ('structured_primary', 'structured_transcript', 'subagent_aggregation', 'markdown_fallback', 'parse_failed'))`);
5431
+ ensureColumn('review_runs', 'evaluator_fingerprint', `TEXT CHECK (evaluator_fingerprint IS NULL OR (length(evaluator_fingerprint) = 64 AND evaluator_fingerprint NOT GLOB '*[^0-9a-f]*'))`);
5432
+ // Historical identity survives deletion of the source ticket; writes are validated below.
5433
+ ensureColumn('review_findings', 'predecessor_finding_id', 'TEXT');
5434
+ ensureColumn('review_findings', 'evaluator_fingerprint', `TEXT CHECK (evaluator_fingerprint IS NULL OR (length(evaluator_fingerprint) = 64 AND evaluator_fingerprint NOT GLOB '*[^0-9a-f]*'))`);
5435
+ ensureColumn('review_findings', 'output_provenance', `TEXT CHECK (output_provenance IS NULL OR output_provenance IN ('structured_primary', 'structured_transcript', 'subagent_aggregation', 'markdown_fallback', 'parse_failed'))`);
5436
+ db.exec(`
5437
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_review_finding_predecessor
5438
+ ON review_findings(review_run_id, predecessor_finding_id) WHERE predecessor_finding_id IS NOT NULL;
5439
+ CREATE TRIGGER IF NOT EXISTS preserve_review_finding_predecessor
5440
+ BEFORE UPDATE OF predecessor_finding_id ON review_findings
5441
+ WHEN NEW.predecessor_finding_id IS NOT OLD.predecessor_finding_id
5442
+ BEGIN
5443
+ SELECT RAISE(ABORT, 'Review finding predecessor is immutable');
5444
+ END;
5445
+ CREATE TRIGGER IF NOT EXISTS preserve_review_finding_lineage_owner
5446
+ BEFORE UPDATE OF review_run_id ON review_findings
5447
+ WHEN NEW.review_run_id IS NOT OLD.review_run_id AND EXISTS (
5448
+ SELECT 1 FROM review_findings WHERE predecessor_finding_id = OLD.id
5449
+ )
5450
+ BEGIN
5451
+ SELECT RAISE(ABORT, 'Review finding lineage ownership is immutable');
5452
+ END;
5453
+ CREATE TRIGGER IF NOT EXISTS preserve_review_run_lineage_owner
5454
+ BEFORE UPDATE OF ticket_id, project_id, worktree_bundle_id, status ON review_runs
5455
+ WHEN (NEW.ticket_id IS NOT OLD.ticket_id OR NEW.project_id IS NOT OLD.project_id
5456
+ OR NEW.worktree_bundle_id IS NOT OLD.worktree_bundle_id OR NEW.status IS NOT OLD.status)
5457
+ AND EXISTS (
5458
+ SELECT 1 FROM review_findings predecessor
5459
+ JOIN review_findings successor ON successor.predecessor_finding_id = predecessor.id
5460
+ WHERE predecessor.review_run_id = OLD.id
5461
+ OR (successor.review_run_id = OLD.id AND (
5462
+ NEW.ticket_id IS NOT OLD.ticket_id OR NEW.project_id IS NOT OLD.project_id
5463
+ OR NEW.worktree_bundle_id IS NOT OLD.worktree_bundle_id
5464
+ ))
5465
+ )
5466
+ BEGIN
5467
+ SELECT RAISE(ABORT, 'Review run lineage ownership is immutable');
5468
+ END;
5469
+ `);
5470
+ for (const operation of ['INSERT', 'UPDATE']) {
5471
+ db.exec(`
5472
+ DROP TRIGGER IF EXISTS validate_review_finding_predecessor_${operation.toLowerCase()};
5473
+ CREATE TRIGGER validate_review_finding_predecessor_${operation.toLowerCase()}
5474
+ BEFORE ${operation} ON review_findings
5475
+ WHEN NEW.predecessor_finding_id IS NOT NULL
5476
+ BEGIN
5477
+ SELECT RAISE(ABORT, 'Invalid review finding predecessor') WHERE NOT EXISTS (
5478
+ SELECT 1 FROM review_findings predecessor
5479
+ JOIN review_runs prior ON prior.id = predecessor.review_run_id
5480
+ JOIN review_runs current ON current.id = NEW.review_run_id
5481
+ WHERE predecessor.id = NEW.predecessor_finding_id
5482
+ AND prior.id <> current.id
5483
+ AND prior.status = 'completed'
5484
+ AND prior.project_id IS current.project_id
5485
+ AND prior.worktree_bundle_id IS current.worktree_bundle_id
5486
+ AND (current.worktree_bundle_id IS NOT NULL OR prior.ticket_id = current.ticket_id)
5487
+ );
5488
+ END;
5489
+ `);
5490
+ }
5179
5491
  db.exec(`
5180
5492
  CREATE TABLE IF NOT EXISTS bundle_pull_requests (
5181
5493
  id TEXT PRIMARY KEY,
@@ -5215,7 +5527,6 @@ db.exec(`
5215
5527
  automation_paused_code TEXT,
5216
5528
  automation_paused_reason TEXT,
5217
5529
  automation_paused_at DATETIME,
5218
- automation_delivery_ready_stage_run_id TEXT,
5219
5530
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
5220
5531
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
5221
5532
  PRIMARY KEY (project_id, worktree_bundle_id),
@@ -5231,7 +5542,6 @@ ensureColumn('bundle_automation_state', 'automation_progression_run_id', 'TEXT')
5231
5542
  ensureColumn('bundle_automation_state', 'automation_paused_stage_run_id', 'TEXT');
5232
5543
  ensureColumn('bundle_automation_state', 'automation_paused_boundary_purpose', 'TEXT');
5233
5544
  ensureColumn('bundle_automation_state', 'automation_pause_reason_code', 'TEXT');
5234
- ensureColumn('bundle_automation_state', 'automation_delivery_ready_stage_run_id', 'TEXT');
5235
5545
  db.exec('DROP TRIGGER IF EXISTS validate_review_repair_result_update');
5236
5546
  db.exec('DROP TRIGGER IF EXISTS validate_review_repair_gate_update');
5237
5547
  db.exec('DROP TRIGGER IF EXISTS validate_review_repair_gate_evaluation_update');
@@ -5693,9 +6003,19 @@ db.exec(`
5693
6003
  SELECT RAISE(ABORT, 'Invalid review repair verification lineage');
5694
6004
  END;
5695
6005
 
5696
- CREATE TRIGGER IF NOT EXISTS validate_review_repair_result_update
6006
+ DROP TRIGGER IF EXISTS validate_review_repair_result_update;
6007
+ CREATE TRIGGER validate_review_repair_result_update
5697
6008
  BEFORE UPDATE OF resulting_review_run_id ON review_repair_iterations
5698
6009
  WHEN (OLD.resulting_review_run_id IS NOT NULL AND NEW.resulting_review_run_id IS NULL
6010
+ AND NOT (
6011
+ OLD.phase = 'paused' AND OLD.outcome IS 'paused' AND OLD.stop_reason IS 'stale_evidence'
6012
+ AND NEW.phase = 'gate_pending' AND NEW.outcome IS NULL AND NEW.stop_reason IS NULL
6013
+ AND NEW.resulting_review_execution_run_id IS NULL AND NEW.resulting_blocking_count IS NULL
6014
+ AND NEW.gate_batch_run_id IS NULL AND NEW.gate_source_batch_run_id IS NULL
6015
+ AND NEW.gate_evaluation_id IS NULL AND NEW.workspace_evidence_json IS NULL
6016
+ AND NEW.gate_active_at IS NULL AND NEW.verification_pending_at IS NULL
6017
+ AND NEW.verification_active_at IS NULL
6018
+ )
5699
6019
  AND NOT (OLD.resulting_review_execution_run_id <> NEW.resulting_review_execution_run_id
5700
6020
  AND EXISTS (
5701
6021
  SELECT 1 FROM review_runs prior_result
@@ -5941,6 +6261,49 @@ db.exec('CREATE INDEX IF NOT EXISTS idx_chat_sessions_project_updated_at ON chat
5941
6261
  db.exec('CREATE INDEX IF NOT EXISTS idx_chat_messages_session_created_at ON chat_messages(chat_session_id, created_at)');
5942
6262
  db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_ticket_stage_changes_ticket_stage ON ticket_stage_changes(ticket_id, stage)');
5943
6263
  db.exec('CREATE INDEX IF NOT EXISTS idx_ticket_stage_changes_bundle_stage_success ON ticket_stage_changes(project_id, worktree_bundle_id, stage, success_revision)');
6264
+ ensureColumn('review_repair_iterations', 'progress_assessment_json', `TEXT CHECK (progress_assessment_json IS NULL OR CASE WHEN json_valid(progress_assessment_json) THEN json_type(progress_assessment_json) = 'object' AND length(CAST(progress_assessment_json AS BLOB)) BETWEEN 2 AND 16384 ELSE 0 END)`);
6265
+ db.exec(`
6266
+ CREATE TRIGGER IF NOT EXISTS validate_review_repair_assessment_insert
6267
+ BEFORE INSERT ON review_repair_iterations
6268
+ WHEN NEW.progress_assessment_json IS NOT NULL
6269
+ BEGIN
6270
+ SELECT RAISE(ABORT, 'Review repair assessment requires existing verification evidence');
6271
+ END;
6272
+
6273
+ CREATE TRIGGER IF NOT EXISTS validate_review_repair_assessment_update
6274
+ BEFORE UPDATE ON review_repair_iterations
6275
+ WHEN (OLD.progress_assessment_json IS NULL AND NEW.progress_assessment_json IS NOT NULL AND (
6276
+ OLD.phase <> 'verification_active' OR NEW.phase <> 'verification_active'
6277
+ OR OLD.resulting_review_execution_run_id IS NULL OR OLD.resulting_review_run_id IS NULL
6278
+ OR NEW.resulting_review_execution_run_id IS NOT OLD.resulting_review_execution_run_id
6279
+ OR NEW.resulting_review_run_id IS NOT OLD.resulting_review_run_id
6280
+ )) OR (OLD.progress_assessment_json IS NOT NULL AND (
6281
+ NEW.progress_assessment_json IS NOT OLD.progress_assessment_json
6282
+ OR NEW.resulting_review_execution_run_id IS NOT OLD.resulting_review_execution_run_id
6283
+ OR NEW.resulting_review_run_id IS NOT OLD.resulting_review_run_id
6284
+ ) AND NOT (
6285
+ OLD.phase = 'paused' AND OLD.outcome IS 'paused' AND OLD.stop_reason IS 'stale_evidence'
6286
+ AND NEW.phase = 'gate_pending' AND NEW.outcome IS NULL AND NEW.stop_reason IS NULL
6287
+ AND NEW.progress_assessment_json IS NULL
6288
+ AND NEW.resulting_review_execution_run_id IS NULL AND NEW.resulting_review_run_id IS NULL
6289
+ AND NEW.resulting_blocking_count IS NULL AND NEW.gate_batch_run_id IS NULL
6290
+ AND NEW.gate_source_batch_run_id IS NULL AND NEW.gate_evaluation_id IS NULL
6291
+ AND NEW.workspace_evidence_json IS NULL AND NEW.gate_active_at IS NULL
6292
+ AND NEW.verification_pending_at IS NULL AND NEW.verification_active_at IS NULL
6293
+ ))
6294
+ BEGIN
6295
+ SELECT RAISE(ABORT, 'Invalid or immutable Review repair progress assessment');
6296
+ END;
6297
+
6298
+ CREATE TRIGGER IF NOT EXISTS prevent_review_repair_no_progress_resume
6299
+ BEFORE UPDATE ON review_repair_iterations
6300
+ WHEN OLD.phase = 'paused' AND OLD.stop_reason = 'no_progress'
6301
+ AND NEW.phase <> 'terminal'
6302
+ AND (NEW.phase <> 'paused' OR NEW.stop_reason IS NOT 'no_progress')
6303
+ BEGIN
6304
+ SELECT RAISE(ABORT, 'Review repair no_progress requires an explicit successor iteration');
6305
+ END;
6306
+ `);
5944
6307
  db.exec('CREATE INDEX IF NOT EXISTS idx_review_runs_ticket_created_at ON review_runs(ticket_id, created_at)');
5945
6308
  db.exec('CREATE INDEX IF NOT EXISTS idx_review_runs_bundle_created_at ON review_runs(project_id, worktree_bundle_id, created_at)');
5946
6309
  db.exec('CREATE INDEX IF NOT EXISTS idx_review_findings_review_run_ordinal ON review_findings(review_run_id, ordinal)');