@straiffi/archon 1.8.23 → 1.10.1

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 (221) hide show
  1. package/dist/client/assets/{ActivityView-BaIZ8eHC.js → ActivityView-C5OrI0uK.js} +1 -1
  2. package/dist/client/assets/GitHubActionsDialog-BABPYwJP.js +1 -0
  3. package/dist/client/assets/VerificationDialog-LN87YY0r.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Dl5UF67P.js → architectureDiagram-3BPJPVTR-DBXhcjPI.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-DCcERfrZ.js → blockDiagram-GPEHLZMM-c3auwNH7.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-BGhbASFm.js → c4Diagram-AAUBKEIU-DH2YznEQ.js} +1 -1
  7. package/dist/client/assets/channel-CYsnLOcX.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-C1ARozTV.js → chunk-2J33WTMH-D0_MoyXZ.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-BEovLizF.js → chunk-3OPIFGDE-DLvSp1DN.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-C5MmlLa_.js → chunk-5ZQYHXKU-B4sX8Htk.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-8y8jBk4n.js → chunk-727SXJPM-CkdnShG1.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-q-mByAd3.js → chunk-AQP2D5EJ-BdPfgkcs.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-BOx2mLSz.js → chunk-CSCIHK7Q-CK-DpVF6.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-zEwQFq7q.js → chunk-KSCS5N6A-DSbTWVl2.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-C80z6rY_.js → chunk-L5ZTLDWV-DuQ4EPLl.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-BLaEsGPd.js → chunk-LZXEDZCA-CtpoXWMY.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-x26BZ95L.js → chunk-ND2GUHAM-CDfNOP6o.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-D2WAxr1p.js → chunk-NZK2D7GU-s6ReY4ev.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-DDPCrzKh.js → chunk-O5CBEL6O-DxqYGKhS.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CE-JcvIc.js → chunk-WU5MYG2G-BvrhxyV8.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-CElk31Q4.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-lnAahwmt.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-Bf7wpUk_.js → dagre-BM42HDAG-DEK_rftr.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-BQoKmc3B.js → diagram-2AECGRRQ-CpobcZnv.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-D2jarPLn.js → diagram-5GNKFQAL-zL1D1L_8.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-Bcbbjvsl.js → diagram-KO2AKTUF-gyW-Vc7c.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-t862RRBQ.js → diagram-LMA3HP47-DDEYc8sh.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CacbMcO0.js → diagram-OG6HWLK6-BtUrfJnE.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DRQ79baI.js → erDiagram-TEJ5UH35-qGrmpmmk.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-BfW4KEdc.js → flowDiagram-I6XJVG4X-DO1Nm1FA.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BPIl0TAj.js → ganttDiagram-6RSMTGT7-DGuMVCAc.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CRIusBMV.js → gitGraphDiagram-PVQCEYII-CehPAc8-.js} +1 -1
  33. package/dist/client/assets/index-1CokwSFm.css +2 -0
  34. package/dist/client/assets/index-DtefHOHC.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-DTKzK8EJ.js → infoDiagram-5YYISTIA-BkRaMDId.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DVIHZLvL.js → ishikawaDiagram-YF4QCWOH-B45ubODJ.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-CQBZ4tCx.js → journeyDiagram-JHISSGLW-BLxrlitK.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-D7naegR0.js → kanban-definition-UN3LZRKU-DmyeJDyy.js} +1 -1
  39. package/dist/client/assets/{line-CizZlaeV.js → line-BhwjsX6t.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-NrDqgpZa.js → mermaid-parser.core-CPvivy_u.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CwVO3oX0.js → mermaid.core-Dr0kld-g.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BWxy3Aa_.js → mindmap-definition-RKZ34NQL-dfudPzBR.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-B3gXpmBn.js → pieDiagram-4H26LBE5-BhsIQVNX.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dpp4g8Al.js → quadrantDiagram-W4KKPZXB-CAKxScLD.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-lFKXnxml.js → requirementDiagram-4Y6WPE33-Br6jIbke.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BBdtwqrS.js → sankeyDiagram-5OEKKPKP-BqujNgnY.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ANpVZzqm.js → sequenceDiagram-3UESZ5HK-Dr_VM_ei.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-D58ZxmKY.js → stateDiagram-AJRCARHV-CDbSbbTW.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-p-kfFJi7.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-B6FK8IQC.js → timeline-definition-PNZ67QCA-CnuLuWDs.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-lFWtZEEY.js → vennDiagram-CIIHVFJN-CVAhxdBZ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DteAVp4l.js → wardleyDiagram-YWT4CUSO-Dmczey27.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CNcDlRLN.js → xychartDiagram-2RQKCTM6-Bl6JGLCU.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +225 -12
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +88 -27
  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 +255 -58
  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/claudeValidationOutput.js +4 -4
  89. package/dist/server/lib/claudeValidationOutput.js.map +1 -1
  90. package/dist/server/lib/completedAssistantOutput.js +38 -0
  91. package/dist/server/lib/completedAssistantOutput.js.map +1 -0
  92. package/dist/server/lib/consequentialActionAuthority.js +99 -8
  93. package/dist/server/lib/consequentialActionAuthority.js.map +1 -1
  94. package/dist/server/lib/consequentialActions.js +42 -2
  95. package/dist/server/lib/consequentialActions.js.map +1 -1
  96. package/dist/server/lib/dockerSandboxCredentials.js +10 -3
  97. package/dist/server/lib/dockerSandboxCredentials.js.map +1 -1
  98. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +9 -8
  99. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
  100. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +197 -81
  101. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  102. package/dist/server/lib/dockerSandboxExecutionProvider.js +22 -14
  103. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
  104. package/dist/server/lib/dockerSandboxNetworkPolicy.js +23 -14
  105. package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -1
  106. package/dist/server/lib/dockerSandboxReleaseProfiles.js +91 -0
  107. package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -0
  108. package/dist/server/lib/dockerSandboxRuntimeController.js +16 -1
  109. package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -1
  110. package/dist/server/lib/executionCredentialCapabilities.js +9 -6
  111. package/dist/server/lib/executionCredentialCapabilities.js.map +1 -1
  112. package/dist/server/lib/executionEnvironment.js.map +1 -1
  113. package/dist/server/lib/executionEnvironmentLifecycle.js +7 -0
  114. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
  115. package/dist/server/lib/executionEnvironmentOperations.js +241 -0
  116. package/dist/server/lib/executionEnvironmentOperations.js.map +1 -0
  117. package/dist/server/lib/executionEnvironmentReconciliation.js +98 -3
  118. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
  119. package/dist/server/lib/executionEnvironmentRoutes.js +66 -27
  120. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
  121. package/dist/server/lib/executionEnvironmentValidation.js +21 -12
  122. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
  123. package/dist/server/lib/executionEnvironmentValidationOperations.js +58 -31
  124. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
  125. package/dist/server/lib/executionEnvironments.js +187 -9
  126. package/dist/server/lib/executionEnvironments.js.map +1 -1
  127. package/dist/server/lib/executionProfileCheckPolicy.js +14 -0
  128. package/dist/server/lib/executionProfileCheckPolicy.js.map +1 -0
  129. package/dist/server/lib/executionProfileRoutes.js +9 -1
  130. package/dist/server/lib/executionProfileRoutes.js.map +1 -1
  131. package/dist/server/lib/executionProfileStore.js +5 -3
  132. package/dist/server/lib/executionProfileStore.js.map +1 -1
  133. package/dist/server/lib/executionProfiles.js +97 -15
  134. package/dist/server/lib/executionProfiles.js.map +1 -1
  135. package/dist/server/lib/executionReconciliation.js +43 -6
  136. package/dist/server/lib/executionReconciliation.js.map +1 -1
  137. package/dist/server/lib/executionRunAuthority.js +145 -24
  138. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  139. package/dist/server/lib/executionTargets.js +30 -4
  140. package/dist/server/lib/executionTargets.js.map +1 -1
  141. package/dist/server/lib/executionWorkloads.js +75 -0
  142. package/dist/server/lib/executionWorkloads.js.map +1 -0
  143. package/dist/server/lib/git.js +10 -1
  144. package/dist/server/lib/git.js.map +1 -1
  145. package/dist/server/lib/opencodeAgentProvider.js +3 -1
  146. package/dist/server/lib/opencodeAgentProvider.js.map +1 -1
  147. package/dist/server/lib/opencodeSessionOperations.js +5 -3
  148. package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
  149. package/dist/server/lib/planQuestions.js +2 -1
  150. package/dist/server/lib/planQuestions.js.map +1 -1
  151. package/dist/server/lib/postBuildCheckRuns.js +88 -7
  152. package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
  153. package/dist/server/lib/preview.js +47 -6
  154. package/dist/server/lib/preview.js.map +1 -1
  155. package/dist/server/lib/previewProxy.js +11 -3
  156. package/dist/server/lib/previewProxy.js.map +1 -1
  157. package/dist/server/lib/productionCheckApprovals.js +97 -35
  158. package/dist/server/lib/productionCheckApprovals.js.map +1 -1
  159. package/dist/server/lib/projectMemorySuggestions.js +29 -6
  160. package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
  161. package/dist/server/lib/requiredCheckPolicy.js +3 -40
  162. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  163. package/dist/server/lib/reviewAgentWatchdog.js +75 -0
  164. package/dist/server/lib/reviewAgentWatchdog.js.map +1 -0
  165. package/dist/server/lib/reviewFindingReconciliation.js +176 -0
  166. package/dist/server/lib/reviewFindingReconciliation.js.map +1 -0
  167. package/dist/server/lib/reviewFindings.js +33 -11
  168. package/dist/server/lib/reviewFindings.js.map +1 -1
  169. package/dist/server/lib/run.js +370 -80
  170. package/dist/server/lib/run.js.map +1 -1
  171. package/dist/server/lib/serviceRuns.js +196 -0
  172. package/dist/server/lib/serviceRuns.js.map +1 -0
  173. package/dist/server/lib/sessionTargets.js +34 -0
  174. package/dist/server/lib/sessionTargets.js.map +1 -1
  175. package/dist/server/lib/shell.js +20 -0
  176. package/dist/server/lib/shell.js.map +1 -1
  177. package/dist/server/lib/specArtifact.js +36 -15
  178. package/dist/server/lib/specArtifact.js.map +1 -1
  179. package/dist/server/lib/specBundleGroups.js +4 -4
  180. package/dist/server/lib/specBundleGroups.js.map +1 -1
  181. package/dist/server/lib/specOperations.js +2 -0
  182. package/dist/server/lib/specOperations.js.map +1 -1
  183. package/dist/server/lib/specReviewOperations.js +2 -0
  184. package/dist/server/lib/specReviewOperations.js.map +1 -1
  185. package/dist/server/lib/specTicketMaterialization.js +37 -24
  186. package/dist/server/lib/specTicketMaterialization.js.map +1 -1
  187. package/dist/server/lib/ticketFollowUpOperations.js +28 -7
  188. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  189. package/dist/server/lib/ticketRunOperations.js +1 -0
  190. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  191. package/dist/server/lib/types.js +1 -0
  192. package/dist/server/lib/types.js.map +1 -1
  193. package/dist/server/lib/workloadExecutionEnvironment.js +2 -0
  194. package/dist/server/lib/workloadExecutionEnvironment.js.map +1 -0
  195. package/dist/server/lib/workspaceIdentity.js +30 -6
  196. package/dist/server/lib/workspaceIdentity.js.map +1 -1
  197. package/dist/server/workers/build.js +150 -9
  198. package/dist/server/workers/build.js.map +1 -1
  199. package/dist/server/workers/chat.js +142 -15
  200. package/dist/server/workers/chat.js.map +1 -1
  201. package/dist/server/workers/followUp.js +97 -28
  202. package/dist/server/workers/followUp.js.map +1 -1
  203. package/dist/server/workers/plan.js +295 -48
  204. package/dist/server/workers/plan.js.map +1 -1
  205. package/dist/server/workers/planFollowUp.js +77 -7
  206. package/dist/server/workers/planFollowUp.js.map +1 -1
  207. package/dist/server/workers/review.js +149 -38
  208. package/dist/server/workers/review.js.map +1 -1
  209. package/dist/server/workers/specReview.js +43 -5
  210. package/dist/server/workers/specReview.js.map +1 -1
  211. package/dist/server/workers/specSession.js +156 -31
  212. package/dist/server/workers/specSession.js.map +1 -1
  213. package/package.json +1 -1
  214. package/dist/client/assets/GitHubActionsDialog-BFCqikDY.js +0 -1
  215. package/dist/client/assets/VerificationDialog-B1_6HMdt.js +0 -4
  216. package/dist/client/assets/channel-BfskvH2B.js +0 -1
  217. package/dist/client/assets/classDiagram-4FO5ZUOK-D4HPfxL3.js +0 -1
  218. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DQQ_WqAU.js +0 -1
  219. package/dist/client/assets/index-CZ1z72fo.js +0 -276
  220. package/dist/client/assets/index-SMj5ZmMs.css +0 -2
  221. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cv8cmDfz.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);
@@ -2501,6 +2601,49 @@ const migrateExecutionLifecycleSchema = () => {
2501
2601
  }
2502
2602
  };
2503
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
+ `);
2504
2647
  const executionEnvironmentStates = "'queued', 'preparing', 'ready', 'running', 'stopped', 'retiring', 'cleanup_pending', 'retired', 'failed', 'unavailable'";
2505
2648
  db.exec(`
2506
2649
  CREATE TABLE IF NOT EXISTS execution_environments (
@@ -2530,6 +2673,11 @@ db.exec(`
2530
2673
  cleanup_claim_id TEXT,
2531
2674
  cleanup_claimed_at DATETIME,
2532
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,
2533
2681
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
2534
2682
  preparation_started_at DATETIME,
2535
2683
  ready_at DATETIME,
@@ -2571,7 +2719,10 @@ db.exec(`
2571
2719
  CHECK (failure_message IS NULL OR length(failure_message) <= 4000),
2572
2720
  CHECK (failure_details_json IS NULL OR length(CAST(failure_details_json AS BLOB)) <= 16384),
2573
2721
  CHECK ((cleanup_claim_id IS NULL) = (cleanup_claimed_at IS NULL)),
2574
- 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'))
2575
2726
  );
2576
2727
 
2577
2728
  CREATE TABLE IF NOT EXISTS execution_environment_events (
@@ -2695,6 +2846,11 @@ ensureColumn('execution_environments', 'execution_profile_revision_id', 'TEXT');
2695
2846
  ensureColumn('execution_environments', 'execution_profile_fingerprint', 'TEXT');
2696
2847
  ensureColumn('execution_environments', 'credential_requirements_fingerprint', 'TEXT');
2697
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');
2698
2854
  ensureColumn('execution_environment_run_bindings', 'execution_profile_fingerprint', 'TEXT');
2699
2855
  ensureColumn('execution_environment_run_bindings', 'authority_json', 'TEXT');
2700
2856
  ensureColumn('execution_environment_run_bindings', 'placement_json', 'TEXT');
@@ -3090,9 +3246,41 @@ db.exec('DROP INDEX IF EXISTS idx_execution_environments_reuse');
3090
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)');
3091
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');
3092
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');
3093
3252
  db.exec('CREATE INDEX IF NOT EXISTS idx_execution_environment_events_sequence ON execution_environment_events(project_id, environment_id, sequence)');
3094
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)');
3095
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
+ `);
3096
3284
  db.exec(`
3097
3285
  CREATE TRIGGER IF NOT EXISTS validate_repository_graph_execution_run_binding
3098
3286
  BEFORE UPDATE OF execution_run_id ON repository_graph_scans
@@ -3364,6 +3552,8 @@ db.exec(`
3364
3552
  automation_boundary_purpose TEXT,
3365
3553
  purpose TEXT NOT NULL DEFAULT 'manual',
3366
3554
  causal_source_run_id TEXT,
3555
+ runtime_compatibility_json TEXT,
3556
+ batch_environment_json TEXT,
3367
3557
  FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
3368
3558
  FOREIGN KEY (rerun_of_batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
3369
3559
  FOREIGN KEY (causal_source_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
@@ -3413,6 +3603,7 @@ db.exec(`
3413
3603
  total_persisted_bytes INTEGER NOT NULL DEFAULT 0,
3414
3604
  retained_output TEXT NOT NULL DEFAULT '',
3415
3605
  output_retention_state TEXT NOT NULL DEFAULT 'retained',
3606
+ placement_json TEXT,
3416
3607
  FOREIGN KEY (execution_run_id) REFERENCES execution_runs(id) ON DELETE CASCADE,
3417
3608
  FOREIGN KEY (batch_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
3418
3609
  FOREIGN KEY (rerun_of_attempt_run_id) REFERENCES execution_runs(id) ON DELETE RESTRICT,
@@ -3420,7 +3611,7 @@ db.exec(`
3420
3611
  CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
3421
3612
  CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
3422
3613
  'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
3423
- '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')),
3424
3615
  CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
3425
3616
  AND total_persisted_bytes >= retained_end_offset),
3426
3617
  CHECK (output_retention_state IN ('retained', 'expired'))
@@ -3428,11 +3619,15 @@ db.exec(`
3428
3619
  `);
3429
3620
  ensureColumn('check_attempt_details', 'retained_end_offset', 'INTEGER NOT NULL DEFAULT 0');
3430
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');
3431
3625
  const checkAttemptSchema = db.prepare(`
3432
3626
  SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'check_attempt_details'
3433
3627
  `).get();
3434
3628
  if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistence_failure')
3435
- || !checkAttemptSchema.sql.includes('ambiguous_process_loss'))) {
3629
+ || !checkAttemptSchema.sql.includes('ambiguous_process_loss')
3630
+ || !checkAttemptSchema.sql.includes('environment_loss'))) {
3436
3631
  const foreignKeysEnabled = db.pragma('foreign_keys', { simple: true }) === 1;
3437
3632
  if (foreignKeysEnabled) {
3438
3633
  db.pragma('foreign_keys = OFF');
@@ -3473,7 +3668,7 @@ if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistenc
3473
3668
  CHECK (length(definition_fingerprint) = 64 AND definition_fingerprint NOT GLOB '*[^0-9a-f]*'),
3474
3669
  CHECK (outcome IS NULL OR outcome IN ('passed', 'command_failure', 'spawn_failure',
3475
3670
  'infrastructure_failure', 'evidence_persistence_failure', 'timeout',
3476
- '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')),
3477
3672
  CHECK (output_revision >= 0 AND retained_start_offset >= 0 AND retained_end_offset >= retained_start_offset
3478
3673
  AND total_persisted_bytes >= retained_end_offset),
3479
3674
  CHECK (output_retention_state IN ('retained', 'expired'))
@@ -3507,6 +3702,7 @@ if (checkAttemptSchema && (!checkAttemptSchema.sql.includes('evidence_persistenc
3507
3702
  throw new Error(`Check attempt migration left an invalid foreign key: ${JSON.stringify(foreignKeyViolation)}`);
3508
3703
  }
3509
3704
  }
3705
+ ensureColumn('check_attempt_details', 'placement_json', 'TEXT');
3510
3706
  db.exec(`
3511
3707
  CREATE TABLE IF NOT EXISTS check_attempt_diagnostic_parsers (
3512
3708
  attempt_run_id TEXT PRIMARY KEY,
@@ -4420,6 +4616,8 @@ db.prepare(`
4420
4616
  db.exec('DROP TRIGGER IF EXISTS validate_check_batch_details_insert');
4421
4617
  db.exec('DROP TRIGGER IF EXISTS preserve_check_batch_snapshots');
4422
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');
4423
4621
  db.exec(`
4424
4622
  CREATE TRIGGER IF NOT EXISTS preserve_check_batch_snapshots
4425
4623
  BEFORE UPDATE OF execution_run_id, target_kind, target_bundle_id, target_label,
@@ -4436,6 +4634,14 @@ db.exec(`
4436
4634
  SELECT RAISE(ABORT, 'Completed check batch details are immutable');
4437
4635
  END;
4438
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
+
4439
4645
  CREATE TRIGGER IF NOT EXISTS preserve_check_attempt_snapshots
4440
4646
  BEFORE UPDATE OF execution_run_id, batch_run_id, definition_id, command_snapshot_json,
4441
4647
  definition_fingerprint, initial_workspace_identity_json, rerun_of_attempt_run_id
@@ -4452,6 +4658,13 @@ db.exec(`
4452
4658
  SELECT RAISE(ABORT, 'Completed check attempt details are immutable');
4453
4659
  END;
4454
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
+
4455
4668
  CREATE TRIGGER IF NOT EXISTS validate_check_batch_details_insert
4456
4669
  BEFORE INSERT ON check_batch_details
4457
4670
  WHEN NOT EXISTS (