@straiffi/archon 1.8.22 → 1.10.0

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 (219) hide show
  1. package/dist/client/assets/{ActivityView-CdEPjmqD.js → ActivityView-Y5IaJiim.js} +1 -1
  2. package/dist/client/assets/GitHubActionsDialog-GWVxWGPK.js +1 -0
  3. package/dist/client/assets/VerificationDialog-C4RMuI4y.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BrZ08mqq.js → architectureDiagram-3BPJPVTR-CeM6gbLN.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-b93XwJ6Z.js → blockDiagram-GPEHLZMM-BP4hta96.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-BKOdbZ_v.js → c4Diagram-AAUBKEIU-5aJScvhn.js} +1 -1
  7. package/dist/client/assets/channel-B3DjZXbm.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-D7Vxn_1m.js → chunk-2J33WTMH-BKc2IelV.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-B1zo2UZq.js → chunk-3OPIFGDE-DCc15la5.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-BajvkDdQ.js → chunk-5ZQYHXKU-DSD9HbTp.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-CSH7cVpV.js → chunk-727SXJPM-DrAjnKiA.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DE-wgUNP.js → chunk-AQP2D5EJ-DxKnpHfQ.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-Ba7JgCEf.js → chunk-CSCIHK7Q-DLejrAB_.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-0o3YWkHM.js → chunk-KSCS5N6A-BWRNmF8S.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-DihWczP8.js → chunk-L5ZTLDWV-D48i966f.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Bxnu-hme.js → chunk-LZXEDZCA-DLhg6Bok.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-CoaR5kmP.js → chunk-ND2GUHAM-B4-7cOEq.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-By92Rd3d.js → chunk-NZK2D7GU-D2ttXA_d.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-D83Pkgfu.js → chunk-O5CBEL6O-BrWrOVCq.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-v8OVf5Lz.js → chunk-WU5MYG2G-CLcPwxPe.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-DproSkn2.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-JVMMzJxJ.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-Dq1ALF7Z.js → dagre-BM42HDAG-DIXN8_VX.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-1t1_TKtu.js → diagram-2AECGRRQ-D2wjdCij.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-DtvYRL6f.js → diagram-5GNKFQAL-E1Og_PHE.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-PRNrSX5F.js → diagram-KO2AKTUF-B6P1Ud8a.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-DaTX0DhT.js → diagram-LMA3HP47-C3Jxq56v.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-C2p9p-92.js → diagram-OG6HWLK6-Do9LVhdL.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DKzLtnp-.js → erDiagram-TEJ5UH35-DLb5E4-f.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-GdL8lm6e.js → flowDiagram-I6XJVG4X-CcTs_wU9.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-Ok-4XYwT.js → ganttDiagram-6RSMTGT7-q9ez5fri.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DSMrOCK7.js → gitGraphDiagram-PVQCEYII-BcaKiz4c.js} +1 -1
  33. package/dist/client/assets/index-1CokwSFm.css +2 -0
  34. package/dist/client/assets/index-CSOKbu3w.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Dl4hRXis.js → infoDiagram-5YYISTIA-Cu0w4frE.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-C1Dc1fTT.js → ishikawaDiagram-YF4QCWOH-CJ47Z-ak.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-CUscmJze.js → journeyDiagram-JHISSGLW-fAL_Zcvs.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-C0nwYjQV.js → kanban-definition-UN3LZRKU-BTNIybMC.js} +1 -1
  39. package/dist/client/assets/{line-ByiuEIg_.js → line-BvsadeZR.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-B-XdAyDR.js → mermaid-parser.core-C0EkH2y_.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-D_bIfuZD.js → mermaid.core-BU5QIIbY.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BGNiPJDK.js → mindmap-definition-RKZ34NQL-CWbSYK8f.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CP6FpAoO.js → pieDiagram-4H26LBE5-t4w2cB-y.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-dsAWBAQn.js → quadrantDiagram-W4KKPZXB-Cqasvrpa.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DJH-zHqF.js → requirementDiagram-4Y6WPE33-_l-mY-tb.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BJMnZrqH.js → sankeyDiagram-5OEKKPKP-Cr9YrZ-Y.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CzNjUSlM.js → sequenceDiagram-3UESZ5HK-DvzZhQpb.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-jppMhOwh.js → stateDiagram-AJRCARHV-cFjPpAO3.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-btHMH5Cg.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BX2CI5H_.js → timeline-definition-PNZ67QCA-BjRNrEJY.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-Bd1KZaXh.js → vennDiagram-CIIHVFJN-xs9afjrw.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D-0BNz49.js → wardleyDiagram-YWT4CUSO-DH68ZYti.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DbYa0ZWK.js → xychartDiagram-2RQKCTM6-DLW5ktTU.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +231 -14
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +93 -9
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +368 -143
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/agentProvider.js.map +1 -1
  62. package/dist/server/lib/approvalReconciliation.js +14 -2
  63. package/dist/server/lib/approvalReconciliation.js.map +1 -1
  64. package/dist/server/lib/buildExecutionEnvironment.js +251 -57
  65. package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
  66. package/dist/server/lib/chatOperations.js +125 -3
  67. package/dist/server/lib/chatOperations.js.map +1 -1
  68. package/dist/server/lib/chatPostBuildRuns.js +12 -6
  69. package/dist/server/lib/chatPostBuildRuns.js.map +1 -1
  70. package/dist/server/lib/chatTargets.js +2 -1
  71. package/dist/server/lib/chatTargets.js.map +1 -1
  72. package/dist/server/lib/checkDefinitionCwd.js +34 -0
  73. package/dist/server/lib/checkDefinitionCwd.js.map +1 -0
  74. package/dist/server/lib/checkExecutionEnvironment.js +95 -0
  75. package/dist/server/lib/checkExecutionEnvironment.js.map +1 -0
  76. package/dist/server/lib/checkPolicyDefinitionIds.js +41 -0
  77. package/dist/server/lib/checkPolicyDefinitionIds.js.map +1 -0
  78. package/dist/server/lib/checkRepairDispatch.js +2 -0
  79. package/dist/server/lib/checkRepairDispatch.js.map +1 -1
  80. package/dist/server/lib/checkRunner.js +371 -175
  81. package/dist/server/lib/checkRunner.js.map +1 -1
  82. package/dist/server/lib/checkRuns.js +99 -8
  83. package/dist/server/lib/checkRuns.js.map +1 -1
  84. package/dist/server/lib/claudeAgentProvider.js +3 -6
  85. package/dist/server/lib/claudeAgentProvider.js.map +1 -1
  86. package/dist/server/lib/claudeStdin.js +27 -0
  87. package/dist/server/lib/claudeStdin.js.map +1 -0
  88. package/dist/server/lib/completedAssistantOutput.js +38 -0
  89. package/dist/server/lib/completedAssistantOutput.js.map +1 -0
  90. package/dist/server/lib/consequentialActionAuthority.js +99 -8
  91. package/dist/server/lib/consequentialActionAuthority.js.map +1 -1
  92. package/dist/server/lib/consequentialActions.js +42 -2
  93. package/dist/server/lib/consequentialActions.js.map +1 -1
  94. package/dist/server/lib/dockerSandboxCredentials.js +10 -3
  95. package/dist/server/lib/dockerSandboxCredentials.js.map +1 -1
  96. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +9 -8
  97. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
  98. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +167 -75
  99. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  100. package/dist/server/lib/dockerSandboxExecutionProvider.js +22 -14
  101. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
  102. package/dist/server/lib/dockerSandboxNetworkPolicy.js +23 -14
  103. package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -1
  104. package/dist/server/lib/dockerSandboxReleaseProfiles.js +91 -0
  105. package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -0
  106. package/dist/server/lib/dockerSandboxRuntimeController.js +16 -1
  107. package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -1
  108. package/dist/server/lib/executionCredentialCapabilities.js +9 -6
  109. package/dist/server/lib/executionCredentialCapabilities.js.map +1 -1
  110. package/dist/server/lib/executionEnvironment.js.map +1 -1
  111. package/dist/server/lib/executionEnvironmentLifecycle.js +7 -0
  112. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
  113. package/dist/server/lib/executionEnvironmentOperations.js +241 -0
  114. package/dist/server/lib/executionEnvironmentOperations.js.map +1 -0
  115. package/dist/server/lib/executionEnvironmentReconciliation.js +98 -3
  116. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
  117. package/dist/server/lib/executionEnvironmentRoutes.js +83 -29
  118. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
  119. package/dist/server/lib/executionEnvironmentValidation.js +19 -5
  120. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
  121. package/dist/server/lib/executionEnvironmentValidationOperations.js +58 -31
  122. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
  123. package/dist/server/lib/executionEnvironments.js +187 -9
  124. package/dist/server/lib/executionEnvironments.js.map +1 -1
  125. package/dist/server/lib/executionProfileCheckPolicy.js +14 -0
  126. package/dist/server/lib/executionProfileCheckPolicy.js.map +1 -0
  127. package/dist/server/lib/executionProfileRoutes.js +9 -1
  128. package/dist/server/lib/executionProfileRoutes.js.map +1 -1
  129. package/dist/server/lib/executionProfileStore.js +5 -3
  130. package/dist/server/lib/executionProfileStore.js.map +1 -1
  131. package/dist/server/lib/executionProfiles.js +97 -15
  132. package/dist/server/lib/executionProfiles.js.map +1 -1
  133. package/dist/server/lib/executionReconciliation.js +43 -6
  134. package/dist/server/lib/executionReconciliation.js.map +1 -1
  135. package/dist/server/lib/executionRunAuthority.js +115 -24
  136. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  137. package/dist/server/lib/executionTargets.js +30 -4
  138. package/dist/server/lib/executionTargets.js.map +1 -1
  139. package/dist/server/lib/executionWorkloads.js +75 -0
  140. package/dist/server/lib/executionWorkloads.js.map +1 -0
  141. package/dist/server/lib/git.js +10 -1
  142. package/dist/server/lib/git.js.map +1 -1
  143. package/dist/server/lib/opencodeAgentProvider.js +3 -1
  144. package/dist/server/lib/opencodeAgentProvider.js.map +1 -1
  145. package/dist/server/lib/opencodeSessionOperations.js +5 -3
  146. package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
  147. package/dist/server/lib/planQuestions.js +2 -1
  148. package/dist/server/lib/planQuestions.js.map +1 -1
  149. package/dist/server/lib/postBuildCheckRuns.js +9 -5
  150. package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
  151. package/dist/server/lib/preview.js +47 -6
  152. package/dist/server/lib/preview.js.map +1 -1
  153. package/dist/server/lib/previewProxy.js +11 -3
  154. package/dist/server/lib/previewProxy.js.map +1 -1
  155. package/dist/server/lib/productionCheckApprovals.js +97 -35
  156. package/dist/server/lib/productionCheckApprovals.js.map +1 -1
  157. package/dist/server/lib/projectMemorySuggestions.js +29 -6
  158. package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
  159. package/dist/server/lib/requiredCheckPolicy.js +3 -40
  160. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  161. package/dist/server/lib/reviewAgentWatchdog.js +75 -0
  162. package/dist/server/lib/reviewAgentWatchdog.js.map +1 -0
  163. package/dist/server/lib/reviewFindingReconciliation.js +176 -0
  164. package/dist/server/lib/reviewFindingReconciliation.js.map +1 -0
  165. package/dist/server/lib/reviewFindings.js +45 -17
  166. package/dist/server/lib/reviewFindings.js.map +1 -1
  167. package/dist/server/lib/run.js +370 -80
  168. package/dist/server/lib/run.js.map +1 -1
  169. package/dist/server/lib/serviceRuns.js +196 -0
  170. package/dist/server/lib/serviceRuns.js.map +1 -0
  171. package/dist/server/lib/sessionTargets.js +34 -0
  172. package/dist/server/lib/sessionTargets.js.map +1 -1
  173. package/dist/server/lib/shell.js +20 -0
  174. package/dist/server/lib/shell.js.map +1 -1
  175. package/dist/server/lib/specArtifact.js +36 -15
  176. package/dist/server/lib/specArtifact.js.map +1 -1
  177. package/dist/server/lib/specBundleGroups.js +4 -4
  178. package/dist/server/lib/specBundleGroups.js.map +1 -1
  179. package/dist/server/lib/specOperations.js +2 -0
  180. package/dist/server/lib/specOperations.js.map +1 -1
  181. package/dist/server/lib/specReviewOperations.js +2 -0
  182. package/dist/server/lib/specReviewOperations.js.map +1 -1
  183. package/dist/server/lib/specTicketMaterialization.js +37 -24
  184. package/dist/server/lib/specTicketMaterialization.js.map +1 -1
  185. package/dist/server/lib/ticketFollowUpOperations.js +9 -4
  186. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  187. package/dist/server/lib/ticketRunOperations.js +1 -0
  188. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  189. package/dist/server/lib/types.js +1 -0
  190. package/dist/server/lib/types.js.map +1 -1
  191. package/dist/server/lib/workloadExecutionEnvironment.js +2 -0
  192. package/dist/server/lib/workloadExecutionEnvironment.js.map +1 -0
  193. package/dist/server/lib/workspaceIdentity.js +30 -6
  194. package/dist/server/lib/workspaceIdentity.js.map +1 -1
  195. package/dist/server/workers/build.js +39 -8
  196. package/dist/server/workers/build.js.map +1 -1
  197. package/dist/server/workers/chat.js +142 -15
  198. package/dist/server/workers/chat.js.map +1 -1
  199. package/dist/server/workers/followUp.js +77 -20
  200. package/dist/server/workers/followUp.js.map +1 -1
  201. package/dist/server/workers/plan.js +295 -48
  202. package/dist/server/workers/plan.js.map +1 -1
  203. package/dist/server/workers/planFollowUp.js +77 -7
  204. package/dist/server/workers/planFollowUp.js.map +1 -1
  205. package/dist/server/workers/review.js +153 -40
  206. package/dist/server/workers/review.js.map +1 -1
  207. package/dist/server/workers/specReview.js +43 -5
  208. package/dist/server/workers/specReview.js.map +1 -1
  209. package/dist/server/workers/specSession.js +156 -31
  210. package/dist/server/workers/specSession.js.map +1 -1
  211. package/package.json +1 -1
  212. package/dist/client/assets/GitHubActionsDialog-CA189pr2.js +0 -1
  213. package/dist/client/assets/VerificationDialog-BPVkhKRj.js +0 -4
  214. package/dist/client/assets/channel-D0zqzBT_.js +0 -1
  215. package/dist/client/assets/classDiagram-4FO5ZUOK-ChhyvHH2.js +0 -1
  216. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Cl02kurK.js +0 -1
  217. package/dist/client/assets/index-Bs8KsdV9.js +0 -276
  218. package/dist/client/assets/index-DdwM8Wgt.css +0 -2
  219. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CLR1WgdV.js +0 -1
package/dist/server/db.js CHANGED
@@ -4,6 +4,7 @@ import { parseCheckDefinitionsJsonStrict, parseLegacyTestCommandsJsonStrict, pro
4
4
  import { ensureParentDirectory, resolveArchonDbPath } from './lib/runtimePaths.js';
5
5
  import { TRANSCRIPT_RUN_TEXT_LIMIT_BYTES, truncateUtf8 } from './lib/transcriptRuns.js';
6
6
  import { createExecutionProfile, EXECUTION_PROFILE_SCHEMA_VERSION, fingerprintExecutionProfile, normalizeExecutionProfile, serializeExecutionProfile, } from './lib/executionProfiles.js';
7
+ import { hasConfiguredCheckPolicy } from './lib/executionProfileCheckPolicy.js';
7
8
  const dbPath = ensureParentDirectory(resolveArchonDbPath());
8
9
  const db = new Database(dbPath);
9
10
  db.pragma('foreign_keys = ON');
@@ -181,7 +182,7 @@ db.exec(`
181
182
  id TEXT PRIMARY KEY,
182
183
  project_id TEXT NOT NULL,
183
184
  revision INTEGER NOT NULL CHECK (revision > 0),
184
- schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2)),
185
+ schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2, 3)),
185
186
  fingerprint TEXT NOT NULL CHECK (length(fingerprint) = 64),
186
187
  profile_json TEXT NOT NULL CHECK (
187
188
  json_valid(profile_json)
@@ -221,7 +222,7 @@ db.exec(`
221
222
  ON execution_profile_audit_events(project_id, created_at DESC, id DESC);
222
223
  `);
223
224
  const executionProfileRevisionsTableSql = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'execution_profile_revisions'").get()?.sql ?? '';
224
- if (!executionProfileRevisionsTableSql.includes('schema_version IN (1, 2)')) {
225
+ if (!executionProfileRevisionsTableSql.includes('schema_version IN (1, 2, 3)')) {
225
226
  db.pragma('foreign_keys = OFF');
226
227
  db.transaction(() => {
227
228
  const referencingTriggers = db.prepare(`
@@ -236,7 +237,7 @@ if (!executionProfileRevisionsTableSql.includes('schema_version IN (1, 2)')) {
236
237
  id TEXT PRIMARY KEY,
237
238
  project_id TEXT NOT NULL,
238
239
  revision INTEGER NOT NULL CHECK (revision > 0),
239
- schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2)),
240
+ schema_version INTEGER NOT NULL CHECK (schema_version IN (1, 2, 3)),
240
241
  fingerprint TEXT NOT NULL CHECK (length(fingerprint) = 64),
241
242
  profile_json TEXT NOT NULL CHECK (
242
243
  json_valid(profile_json)
@@ -297,7 +298,7 @@ const backfillExecutionProfiles = () => {
297
298
  }
298
299
  })();
299
300
  };
300
- const backfillExecutionProfilesToSchemaV2 = () => {
301
+ const backfillExecutionProfilesToCurrentSchema = () => {
301
302
  const legacyRows = db.prepare(`
302
303
  SELECT projects.id AS project_id,
303
304
  revisions.id AS revision_id,
@@ -306,7 +307,7 @@ const backfillExecutionProfilesToSchemaV2 = () => {
306
307
  INNER JOIN execution_profile_revisions revisions
307
308
  ON revisions.id = projects.active_execution_profile_revision_id
308
309
  AND revisions.project_id = projects.id
309
- WHERE revisions.schema_version = 1 AND projects.deletion_started_at IS NULL
310
+ WHERE revisions.schema_version IN (1, 2) AND projects.deletion_started_at IS NULL
310
311
  `).all();
311
312
  const nextRevisionNumber = db.prepare(`
312
313
  SELECT COALESCE(MAX(revision), 0) + 1 AS revision
@@ -348,19 +349,118 @@ const backfillExecutionProfilesToSchemaV2 = () => {
348
349
  revisionId = existing.id;
349
350
  }
350
351
  else {
351
- revisionId = `${row.project_id}:execution-profile:v2`;
352
+ revisionId = `${row.project_id}:execution-profile:v${EXECUTION_PROFILE_SCHEMA_VERSION}`;
352
353
  const revisionNumber = nextRevisionNumber.get(row.project_id).revision;
353
354
  insertRevision.run(revisionId, row.project_id, revisionNumber, fingerprint, serializeExecutionProfile(normalized.profile));
354
355
  }
355
356
  const activated = activateRevision.run(revisionId, row.project_id, row.revision_id);
356
357
  if (activated.changes > 0) {
357
- insertEvent.run(`${revisionId}:v2-backfill`, row.project_id, revisionId, row.revision_id);
358
+ insertEvent.run(`${revisionId}:schema-backfill`, row.project_id, revisionId, row.revision_id);
358
359
  }
359
360
  }
360
361
  })();
361
362
  };
363
+ const backfillExecutionProfileWorkloadEligibility = () => {
364
+ const listActiveRows = db.prepare(`
365
+ SELECT projects.id AS project_id,
366
+ revisions.id AS revision_id,
367
+ revisions.profile_json,
368
+ projects.required_check_definition_ids,
369
+ projects.pre_review_check_definition_ids,
370
+ projects.post_build_check_definition_ids,
371
+ projects.auto_fix_check_definition_ids
372
+ FROM projects
373
+ INNER JOIN execution_profile_revisions revisions
374
+ ON revisions.id = projects.active_execution_profile_revision_id
375
+ AND revisions.project_id = projects.id
376
+ WHERE revisions.schema_version = ${EXECUTION_PROFILE_SCHEMA_VERSION}
377
+ AND projects.deletion_started_at IS NULL
378
+ `);
379
+ const nextRevisionNumber = db.prepare(`
380
+ SELECT COALESCE(MAX(revision), 0) + 1 AS revision
381
+ FROM execution_profile_revisions WHERE project_id = ?
382
+ `);
383
+ const findRevisionByFingerprint = db.prepare(`
384
+ SELECT id FROM execution_profile_revisions WHERE project_id = ? AND fingerprint = ?
385
+ `);
386
+ const insertRevision = db.prepare(`
387
+ INSERT OR IGNORE INTO execution_profile_revisions (
388
+ id, project_id, revision, schema_version, fingerprint, profile_json
389
+ ) VALUES (?, ?, ?, ${EXECUTION_PROFILE_SCHEMA_VERSION}, ?, ?)
390
+ `);
391
+ const activateRevision = db.prepare(`
392
+ UPDATE projects SET active_execution_profile_revision_id = ?
393
+ WHERE id = ? AND active_execution_profile_revision_id = ?
394
+ `);
395
+ const insertEvent = db.prepare(`
396
+ INSERT OR IGNORE INTO execution_profile_audit_events (
397
+ id, project_id, event_type, profile_revision_id, previous_profile_revision_id
398
+ ) VALUES (?, ?, ?, ?, ?)
399
+ `);
400
+ db.transaction(() => {
401
+ const activeRows = listActiveRows.all();
402
+ for (const row of activeRows) {
403
+ let normalized;
404
+ try {
405
+ normalized = normalizeExecutionProfile(JSON.parse(row.profile_json));
406
+ }
407
+ catch {
408
+ normalized = null;
409
+ }
410
+ if (!normalized?.ok) {
411
+ throw new Error(`Stored execution profile revision ${row.revision_id} is invalid`);
412
+ }
413
+ const buildEligible = normalized.profile.eligible_workloads.includes('build');
414
+ const needsChatBuild = buildEligible && !normalized.profile.eligible_workloads.includes('chat_build');
415
+ const needsCheckTemplate = buildEligible
416
+ && hasConfiguredCheckPolicy(row)
417
+ && normalized.profile.check_template_policy.mode !== 'agent_template';
418
+ if (!needsChatBuild && !needsCheckTemplate) {
419
+ continue;
420
+ }
421
+ const repaired = normalizeExecutionProfile({
422
+ ...normalized.profile,
423
+ eligible_workloads: [
424
+ ...normalized.profile.eligible_workloads,
425
+ ...(needsChatBuild ? ['chat_build'] : []),
426
+ ...(needsCheckTemplate ? ['check'] : []),
427
+ ],
428
+ check_template_policy: needsCheckTemplate
429
+ ? { mode: 'agent_template', agent: 'opencode' }
430
+ : normalized.profile.check_template_policy,
431
+ });
432
+ if (!repaired.ok) {
433
+ throw new Error(`Stored execution profile revision ${row.revision_id} is invalid`);
434
+ }
435
+ const fingerprint = fingerprintExecutionProfile(repaired.profile);
436
+ let target = findRevisionByFingerprint.get(row.project_id, fingerprint);
437
+ const repairKey = needsChatBuild && needsCheckTemplate
438
+ ? 'chat-build-check'
439
+ : needsChatBuild
440
+ ? 'chat-build'
441
+ : 'configured-check';
442
+ if (!target) {
443
+ const revisionId = `${row.project_id}:execution-profile:${repairKey}-v${EXECUTION_PROFILE_SCHEMA_VERSION}`;
444
+ const revisionNumber = nextRevisionNumber.get(row.project_id).revision;
445
+ const inserted = insertRevision.run(revisionId, row.project_id, revisionNumber, fingerprint, serializeExecutionProfile(repaired.profile));
446
+ target = findRevisionByFingerprint.get(row.project_id, fingerprint);
447
+ if (!target) {
448
+ throw new Error(`Failed to migrate stored execution profile revision ${row.revision_id}`);
449
+ }
450
+ if (inserted.changes > 0) {
451
+ insertEvent.run(`${target.id}:${repairKey}-backfill-created`, row.project_id, 'revision_created', target.id, row.revision_id);
452
+ }
453
+ }
454
+ const activated = activateRevision.run(target.id, row.project_id, row.revision_id);
455
+ if (activated.changes > 0) {
456
+ insertEvent.run(`${target.id}:${repairKey}-backfill-activated`, row.project_id, 'revision_activated', target.id, row.revision_id);
457
+ }
458
+ }
459
+ }).immediate();
460
+ };
362
461
  backfillExecutionProfiles();
363
- backfillExecutionProfilesToSchemaV2();
462
+ backfillExecutionProfilesToCurrentSchema();
463
+ backfillExecutionProfileWorkloadEligibility();
364
464
  const hostCompatibilityProfile = createExecutionProfile('host');
365
465
  const hostCompatibilityProfileJson = serializeExecutionProfile(hostCompatibilityProfile).replaceAll("'", "''");
366
466
  const hostCompatibilityFingerprint = fingerprintExecutionProfile(hostCompatibilityProfile);
@@ -2195,6 +2295,7 @@ db.exec(`
2195
2295
  stage TEXT NOT NULL,
2196
2296
  diff_signature TEXT,
2197
2297
  workspace_signature TEXT,
2298
+ change_scope TEXT,
2198
2299
  message_id TEXT,
2199
2300
  model TEXT,
2200
2301
  variant TEXT,
@@ -2212,6 +2313,7 @@ const migrateReviewRunsLifecycleSchema = () => {
2212
2313
  const workspaceSignatureColumn = columns.find(column => column.name === 'workspace_signature');
2213
2314
  const needsMigration = diffSignatureColumn?.notnull === 1
2214
2315
  || workspaceSignatureColumn?.notnull === 1
2316
+ || !columns.some(column => column.name === 'change_scope')
2215
2317
  || !columns.some(column => column.name === 'model')
2216
2318
  || !columns.some(column => column.name === 'variant')
2217
2319
  || !columns.some(column => column.name === 'failure_message')
@@ -2232,6 +2334,7 @@ const migrateReviewRunsLifecycleSchema = () => {
2232
2334
  stage TEXT NOT NULL,
2233
2335
  diff_signature TEXT,
2234
2336
  workspace_signature TEXT,
2337
+ change_scope TEXT,
2235
2338
  message_id TEXT,
2236
2339
  model TEXT,
2237
2340
  variant TEXT,
@@ -2246,12 +2349,13 @@ const migrateReviewRunsLifecycleSchema = () => {
2246
2349
  db.exec(`
2247
2350
  INSERT INTO review_runs_v2 (
2248
2351
  id, ticket_id, project_id, worktree_bundle_id, stage, diff_signature,
2249
- workspace_signature, message_id, model, variant, failure_message,
2352
+ workspace_signature, change_scope, message_id, model, variant, failure_message,
2250
2353
  status, created_at, updated_at
2251
2354
  )
2252
2355
  SELECT
2253
2356
  id, ticket_id, project_id, worktree_bundle_id, stage, diff_signature,
2254
- ${selectColumn('workspace_signature', 'NULL')}, message_id,
2357
+ ${selectColumn('workspace_signature', 'NULL')},
2358
+ ${selectColumn('change_scope', 'NULL')}, message_id,
2255
2359
  ${selectColumn('model', 'NULL')}, ${selectColumn('variant', 'NULL')},
2256
2360
  ${selectColumn('failure_message', 'NULL')}, status, created_at,
2257
2361
  ${selectColumn('updated_at', 'created_at')}
@@ -2497,6 +2601,49 @@ const migrateExecutionLifecycleSchema = () => {
2497
2601
  }
2498
2602
  };
2499
2603
  migrateExecutionLifecycleSchema();
2604
+ db.exec(`
2605
+ CREATE TABLE IF NOT EXISTS service_run_details (
2606
+ execution_run_id TEXT PRIMARY KEY,
2607
+ project_id TEXT NOT NULL,
2608
+ context_key TEXT NOT NULL,
2609
+ target_kind TEXT NOT NULL,
2610
+ target_key TEXT NOT NULL,
2611
+ target_workspace_path TEXT NOT NULL,
2612
+ execution_profile_revision_id TEXT NOT NULL,
2613
+ execution_profile_fingerprint TEXT NOT NULL,
2614
+ provider_kind TEXT,
2615
+ environment_id TEXT,
2616
+ environment_revision INTEGER,
2617
+ service_snapshot_json TEXT NOT NULL,
2618
+ placement_json TEXT,
2619
+ interruption_scope TEXT,
2620
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2621
+ started_at DATETIME,
2622
+ completed_at DATETIME,
2623
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2624
+ FOREIGN KEY (execution_run_id, project_id) REFERENCES execution_runs(id, project_id) ON DELETE CASCADE,
2625
+ FOREIGN KEY (project_id, execution_profile_revision_id)
2626
+ REFERENCES execution_profile_revisions(project_id, id) ON DELETE RESTRICT,
2627
+ CHECK (length(CAST(context_key AS BLOB)) BETWEEN 1 AND 1000),
2628
+ CHECK (length(CAST(target_kind AS BLOB)) BETWEEN 1 AND 100),
2629
+ CHECK (length(CAST(target_key AS BLOB)) BETWEEN 1 AND 1000),
2630
+ CHECK (length(CAST(target_workspace_path AS BLOB)) BETWEEN 1 AND 4096),
2631
+ CHECK (length(execution_profile_fingerprint) = 64 AND execution_profile_fingerprint NOT GLOB '*[^0-9a-f]*'),
2632
+ CHECK (provider_kind IS NULL OR provider_kind IN ('host', 'docker_sandbox')),
2633
+ CHECK ((environment_id IS NULL) = (environment_revision IS NULL)),
2634
+ CHECK (environment_revision IS NULL OR environment_revision >= 1),
2635
+ CHECK (length(CAST(service_snapshot_json AS BLOB)) BETWEEN 2 AND 16384),
2636
+ CHECK (placement_json IS NULL OR length(CAST(placement_json AS BLOB)) BETWEEN 2 AND 16384),
2637
+ CHECK (interruption_scope IS NULL OR interruption_scope IN ('local_processes', 'whole_environment'))
2638
+ );
2639
+
2640
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_active_service_run_context
2641
+ ON service_run_details(project_id, context_key)
2642
+ WHERE completed_at IS NULL;
2643
+
2644
+ CREATE INDEX IF NOT EXISTS idx_service_run_profile
2645
+ ON service_run_details(project_id, execution_profile_revision_id, created_at);
2646
+ `);
2500
2647
  const executionEnvironmentStates = "'queued', 'preparing', 'ready', 'running', 'stopped', 'retiring', 'cleanup_pending', 'retired', 'failed', 'unavailable'";
2501
2648
  db.exec(`
2502
2649
  CREATE TABLE IF NOT EXISTS execution_environments (
@@ -2526,6 +2673,11 @@ db.exec(`
2526
2673
  cleanup_claim_id TEXT,
2527
2674
  cleanup_claimed_at DATETIME,
2528
2675
  cleanup_attempts INTEGER NOT NULL DEFAULT 0,
2676
+ cleanup_retry_at DATETIME,
2677
+ idle_stop_at DATETIME,
2678
+ operation_claim_id TEXT,
2679
+ operation_kind TEXT,
2680
+ operation_claimed_at DATETIME,
2529
2681
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2530
2682
  preparation_started_at DATETIME,
2531
2683
  ready_at DATETIME,
@@ -2567,7 +2719,10 @@ db.exec(`
2567
2719
  CHECK (failure_message IS NULL OR length(failure_message) <= 4000),
2568
2720
  CHECK (failure_details_json IS NULL OR length(CAST(failure_details_json AS BLOB)) <= 16384),
2569
2721
  CHECK ((cleanup_claim_id IS NULL) = (cleanup_claimed_at IS NULL)),
2570
- CHECK (cleanup_attempts >= 0)
2722
+ CHECK (cleanup_attempts >= 0),
2723
+ CHECK ((operation_claim_id IS NULL) = (operation_kind IS NULL)),
2724
+ CHECK ((operation_claim_id IS NULL) = (operation_claimed_at IS NULL)),
2725
+ CHECK (operation_kind IS NULL OR operation_kind IN ('stop', 'retire', 'reset', 'cleanup_retry'))
2571
2726
  );
2572
2727
 
2573
2728
  CREATE TABLE IF NOT EXISTS execution_environment_events (
@@ -2691,6 +2846,11 @@ ensureColumn('execution_environments', 'execution_profile_revision_id', 'TEXT');
2691
2846
  ensureColumn('execution_environments', 'execution_profile_fingerprint', 'TEXT');
2692
2847
  ensureColumn('execution_environments', 'credential_requirements_fingerprint', 'TEXT');
2693
2848
  ensureColumn('execution_environments', 'retirement_requested_at', 'DATETIME');
2849
+ ensureColumn('execution_environments', 'cleanup_retry_at', 'DATETIME');
2850
+ ensureColumn('execution_environments', 'idle_stop_at', 'DATETIME');
2851
+ ensureColumn('execution_environments', 'operation_claim_id', 'TEXT');
2852
+ ensureColumn('execution_environments', 'operation_kind', 'TEXT');
2853
+ ensureColumn('execution_environments', 'operation_claimed_at', 'DATETIME');
2694
2854
  ensureColumn('execution_environment_run_bindings', 'execution_profile_fingerprint', 'TEXT');
2695
2855
  ensureColumn('execution_environment_run_bindings', 'authority_json', 'TEXT');
2696
2856
  ensureColumn('execution_environment_run_bindings', 'placement_json', 'TEXT');
@@ -3086,9 +3246,41 @@ db.exec('DROP INDEX IF EXISTS idx_execution_environments_reuse');
3086
3246
  db.exec('CREATE INDEX idx_execution_environments_reuse ON execution_environments(project_id, provider_kind, target_key, target_workspace_path, environment_revision, workspace_mode, environment_validation_fingerprint, execution_profile_fingerprint, credential_requirements_fingerprint, retirement_requested_at, lifecycle_state)');
3087
3247
  db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_execution_environments_external_id ON execution_environments(provider_kind, external_id) WHERE external_id IS NOT NULL');
3088
3248
  db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environments_cleanup ON execution_environments(lifecycle_state, cleanup_claim_id, updated_at, cleanup_attempts)');
3249
+ db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environments_cleanup_retry ON execution_environments(lifecycle_state, cleanup_retry_at, cleanup_claim_id)');
3250
+ db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environments_idle_stop ON execution_environments(idle_stop_at, lifecycle_state) WHERE idle_stop_at IS NOT NULL');
3251
+ db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environments_operation_claim ON execution_environments(operation_claim_id, operation_claimed_at) WHERE operation_claim_id IS NOT NULL');
3089
3252
  db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environment_events_sequence ON execution_environment_events(project_id, environment_id, sequence)');
3090
3253
  db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environment_bindings_run ON execution_environment_run_bindings(project_id, execution_run_id, bound_at)');
3091
3254
  db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_execution_environment_active_execution_lease ON execution_environment_run_bindings(environment_id) WHERE relationship = 'execution' AND released_at IS NULL");
3255
+ db.exec(`
3256
+ CREATE TRIGGER IF NOT EXISTS validate_execution_environment_operation_claim
3257
+ BEFORE UPDATE OF operation_claim_id, operation_kind, operation_claimed_at ON execution_environments
3258
+ WHEN NOT (
3259
+ (NEW.operation_claim_id IS NULL AND NEW.operation_kind IS NULL AND NEW.operation_claimed_at IS NULL)
3260
+ OR (
3261
+ length(NEW.operation_claim_id) BETWEEN 1 AND 128
3262
+ AND NEW.operation_kind IN ('stop', 'retire', 'reset', 'cleanup_retry')
3263
+ AND NEW.operation_claimed_at IS NOT NULL
3264
+ )
3265
+ )
3266
+ BEGIN
3267
+ SELECT RAISE(ABORT, 'Invalid execution environment operation claim');
3268
+ END;
3269
+
3270
+ CREATE TRIGGER IF NOT EXISTS validate_execution_environment_lease_admission
3271
+ BEFORE INSERT ON execution_environment_run_bindings
3272
+ WHEN NEW.relationship = 'execution' AND NOT EXISTS (
3273
+ SELECT 1 FROM execution_environments
3274
+ WHERE id = NEW.environment_id
3275
+ AND project_id = NEW.project_id
3276
+ AND lifecycle_state IN ('ready', 'running', 'stopped')
3277
+ AND retirement_requested_at IS NULL
3278
+ AND operation_claim_id IS NULL
3279
+ )
3280
+ BEGIN
3281
+ SELECT RAISE(ABORT, 'Execution environment is not admitting leases');
3282
+ END;
3283
+ `);
3092
3284
  db.exec(`
3093
3285
  CREATE TRIGGER IF NOT EXISTS validate_repository_graph_execution_run_binding
3094
3286
  BEFORE UPDATE OF execution_run_id ON repository_graph_scans
@@ -3360,6 +3552,8 @@ db.exec(`
3360
3552
  automation_boundary_purpose TEXT,
3361
3553
  purpose TEXT NOT NULL DEFAULT 'manual',
3362
3554
  causal_source_run_id TEXT,
3555
+ runtime_compatibility_json TEXT,
3556
+ batch_environment_json TEXT,
3363
3557
  FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
3364
3558
  FOREIGN KEY (rerun_of_batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
3365
3559
  FOREIGN KEY (causal_source_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
@@ -3409,6 +3603,7 @@ db.exec(`
3409
3603
  total_persisted_bytes INTEGER NOT NULL DEFAULT 0,
3410
3604
  retained_output TEXT NOT NULL DEFAULT '',
3411
3605
  output_retention_state TEXT NOT NULL DEFAULT 'retained',
3606
+ placement_json TEXT,
3412
3607
  FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
3413
3608
  FOREIGN KEY (batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
3414
3609
  FOREIGN KEY (rerun_of_attempt_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
@@ -3416,7 +3611,7 @@ db.exec(`
3416
3611
  CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
3417
3612
  CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
3418
3613
  'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
3419
- 'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss')),
3614
+ 'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss', 'environment_loss')),
3420
3615
  CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
3421
3616
  AND total_persisted_bytes >= retained_end_offset),
3422
3617
  CHECK (output_retention_state IN ('retained', 'expired'))
@@ -3424,11 +3619,15 @@ db.exec(`
3424
3619
  `);
3425
3620
  ensureColumn('check_attempt_details', 'retained_end_offset', 'INTEGER NOT NULL DEFAULT 0');
3426
3621
  ensureColumn('check_attempt_details', 'retained_output', "TEXT NOT NULL DEFAULT ''");
3622
+ ensureColumn('check_attempt_details', 'placement_json', 'TEXT');
3623
+ ensureColumn('check_batch_details', 'runtime_compatibility_json', 'TEXT');
3624
+ ensureColumn('check_batch_details', 'batch_environment_json', 'TEXT');
3427
3625
  const checkAttemptSchema = db.prepare(`
3428
3626
  SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'check_attempt_details'
3429
3627
  `).get();
3430
3628
  if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistence_failure')
3431
- || !checkAttemptSchema.sql.includes('ambiguous_process_loss'))) {
3629
+ || !checkAttemptSchema.sql.includes('ambiguous_process_loss')
3630
+ || !checkAttemptSchema.sql.includes('environment_loss'))) {
3432
3631
  const foreignKeysEnabled = db.pragma('foreign_keys', { simple: true }) === 1;
3433
3632
  if (foreignKeysEnabled) {
3434
3633
  db.pragma('foreign_keys = OFF');
@@ -3469,7 +3668,7 @@ if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistenc
3469
3668
  CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
3470
3669
  CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
3471
3670
  'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
3472
- 'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss')),
3671
+ 'workspace_changed_before_start', 'user_stop', 'restart_loss', 'shutdown_loss', 'ambiguous_process_loss', 'environment_loss')),
3473
3672
  CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
3474
3673
  AND total_persisted_bytes >= retained_end_offset),
3475
3674
  CHECK (output_retention_state IN ('retained', 'expired'))
@@ -3503,6 +3702,7 @@ if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistenc
3503
3702
  throw new Error(`Check attempt migration left an invalid foreign key: ${JSON.stringify(foreignKeyViolation)}`);
3504
3703
  }
3505
3704
  }
3705
+ ensureColumn('check_attempt_details', 'placement_json', 'TEXT');
3506
3706
  db.exec(`
3507
3707
  CREATE TABLE IF NOT EXISTS check_attempt_diagnostic_parsers (
3508
3708
  attempt_run_id TEXT PRIMARY KEY,
@@ -4416,6 +4616,8 @@ db.prepare(`
4416
4616
  db.exec('DROP TRIGGER IF EXISTS validate_check_batch_details_insert');
4417
4617
  db.exec('DROP TRIGGER IF EXISTS preserve_check_batch_snapshots');
4418
4618
  db.exec('DROP TRIGGER IF EXISTS preserve_completed_check_batch_details');
4619
+ db.exec('DROP TRIGGER IF EXISTS preserve_check_batch_environment_binding');
4620
+ db.exec('DROP TRIGGER IF EXISTS preserve_check_attempt_placement');
4419
4621
  db.exec(`
4420
4622
  CREATE TRIGGER IF NOT EXISTS preserve_check_batch_snapshots
4421
4623
  BEFORE UPDATE OF execution_run_id, target_kind, target_bundle_id, target_label,
@@ -4432,6 +4634,14 @@ db.exec(`
4432
4634
  SELECT RAISE(ABORT, 'Completed check batch details are immutable');
4433
4635
  END;
4434
4636
 
4637
+ CREATE TRIGGER IF NOT EXISTS preserve_check_batch_environment_binding
4638
+ BEFORE UPDATE OF runtime_compatibility_json, batch_environment_json ON check_batch_details
4639
+ WHEN OLD.runtime_compatibility_json IS NOT NULL OR OLD.batch_environment_json IS NOT NULL
4640
+ OR NEW.runtime_compatibility_json IS NULL OR NEW.batch_environment_json IS NULL
4641
+ BEGIN
4642
+ SELECT RAISE(ABORT, 'Check batch environment binding is immutable');
4643
+ END;
4644
+
4435
4645
  CREATE TRIGGER IF NOT EXISTS preserve_check_attempt_snapshots
4436
4646
  BEFORE UPDATE OF execution_run_id, batch_run_id, definition_id, command_snapshot_json,
4437
4647
  definition_fingerprint, initial_workspace_identity_json, rerun_of_attempt_run_id
@@ -4448,6 +4658,13 @@ db.exec(`
4448
4658
  SELECT RAISE(ABORT, 'Completed check attempt details are immutable');
4449
4659
  END;
4450
4660
 
4661
+ CREATE TRIGGER IF NOT EXISTS preserve_check_attempt_placement
4662
+ BEFORE UPDATE OF placement_json ON check_attempt_details
4663
+ WHEN OLD.placement_json IS NOT NULL OR NEW.placement_json IS NULL
4664
+ BEGIN
4665
+ SELECT RAISE(ABORT, 'Check attempt placement is immutable');
4666
+ END;
4667
+
4451
4668
  CREATE TRIGGER IF NOT EXISTS validate_check_batch_details_insert
4452
4669
  BEFORE INSERT ON check_batch_details
4453
4670
  WHEN NOT EXISTS (