spine-framework 0.4.3 → 0.5.0-rc.4

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 (320) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/agent-runner.ts +1000 -81
  15. package/.framework/functions/_shared/cil.ts +2 -1
  16. package/.framework/functions/_shared/db.ts +3 -6
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/kb.ts +61 -7
  19. package/.framework/functions/_shared/logging.ts +562 -224
  20. package/.framework/functions/_shared/message-sequence.ts +53 -0
  21. package/.framework/functions/_shared/middleware.ts +47 -44
  22. package/.framework/functions/_shared/pipeline-runner.ts +150 -37
  23. package/.framework/functions/_shared/principal.ts +9 -6
  24. package/.framework/functions/_shared/trigger-engine.ts +149 -40
  25. package/.framework/functions/admin-data.ts +48 -31
  26. package/.framework/functions/ai-agents.ts +103 -33
  27. package/.framework/functions/api-keys.ts +301 -122
  28. package/.framework/functions/apps.ts +43 -26
  29. package/.framework/functions/cil-ingest.ts +48 -15
  30. package/.framework/functions/debug-auth.ts +14 -8
  31. package/.framework/functions/deliveries.ts +39 -12
  32. package/.framework/functions/embeddings.ts +145 -76
  33. package/.framework/functions/integration-outbound.ts +67 -27
  34. package/.framework/functions/integrations.ts +254 -127
  35. package/.framework/functions/logs.ts +273 -133
  36. package/.framework/functions/observability.ts +277 -147
  37. package/.framework/functions/pipeline-executions.ts +1 -1
  38. package/.framework/functions/pipeline-runner-background.ts +120 -0
  39. package/.framework/functions/pipelines.ts +14 -9
  40. package/.framework/functions/prompt-configs.ts +7 -4
  41. package/.framework/functions/roles.ts +8 -5
  42. package/.framework/functions/security.ts +30 -17
  43. package/.framework/functions/system-cron.ts +250 -128
  44. package/.framework/functions/tests.ts +7 -6
  45. package/.framework/functions/timers.ts +310 -107
  46. package/.framework/functions/trigger-scheduler.ts +65 -5
  47. package/.framework/functions/triggers.ts +28 -13
  48. package/.framework/functions/types.ts +13 -10
  49. package/.framework/migrations/064_episode_leases.sql +196 -0
  50. package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
  51. package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
  52. package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
  53. package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
  54. package/.framework/migrations/069_log_stats_window.sql +54 -0
  55. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  56. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  57. package/.framework/migrations/072_slug_autofill.sql +77 -0
  58. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  59. package/.framework/schemas/spine.config.schema.json +62 -0
  60. package/.framework/src/App.tsx +3 -1
  61. package/.framework/src/apps/admin/index.tsx +1141 -775
  62. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  63. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  64. package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
  65. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  66. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  67. package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
  68. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  69. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  70. package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
  71. package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
  72. package/.framework/src/components/layout/Layout.tsx +4 -2
  73. package/.framework/src/components/layout/Sidebar.tsx +304 -179
  74. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  75. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  76. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  77. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  78. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  79. package/.framework/src/components/ui/sheet.tsx +24 -26
  80. package/.framework/src/components/ui/textarea.tsx +74 -48
  81. package/.framework/src/hooks/useBranding.ts +47 -0
  82. package/.framework/src/hooks/useEntityList.ts +12 -9
  83. package/.framework/src/hooks/useListSchema.ts +108 -58
  84. package/.framework/src/instructions/README.md +57 -0
  85. package/.framework/src/instructions/ai-agents.md +69 -0
  86. package/.framework/src/instructions/alerts.md +80 -0
  87. package/.framework/src/instructions/api-keys.md +87 -0
  88. package/.framework/src/instructions/apps.md +77 -0
  89. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  90. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  91. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  92. package/.framework/src/instructions/concepts/automation.md +186 -0
  93. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  94. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  95. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  96. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  97. package/.framework/src/instructions/concepts/observability.md +116 -0
  98. package/.framework/src/instructions/concepts/permissions.md +150 -0
  99. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  100. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  101. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  102. package/.framework/src/instructions/deliveries.md +59 -0
  103. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  104. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  105. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  106. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  107. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  108. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  109. package/.framework/src/instructions/integrations.md +96 -0
  110. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  111. package/.framework/src/instructions/invites.md +60 -0
  112. package/.framework/src/instructions/kb.md +80 -0
  113. package/.framework/src/instructions/logs.md +77 -0
  114. package/.framework/src/instructions/observability.md +61 -0
  115. package/.framework/src/instructions/operations/deployment.md +115 -0
  116. package/.framework/src/instructions/operations/migrations.md +125 -0
  117. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  118. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  119. package/.framework/src/instructions/operations/upgrade.md +116 -0
  120. package/.framework/src/instructions/pipeline-executions.md +62 -0
  121. package/.framework/src/instructions/pipelines.md +78 -0
  122. package/.framework/src/instructions/prompts.md +95 -0
  123. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  124. package/.framework/src/instructions/reference/api/auth.md +113 -0
  125. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  126. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  127. package/.framework/src/instructions/reference/cli.md +124 -0
  128. package/.framework/src/instructions/reference/database.md +132 -0
  129. package/.framework/src/instructions/reference/design-schema.md +280 -0
  130. package/.framework/src/instructions/reference/env-vars.md +133 -0
  131. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  132. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  133. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  134. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  135. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  136. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  137. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  138. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  139. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  140. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  141. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  142. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  143. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  144. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  145. package/.framework/src/instructions/reference/functions/index.md +103 -0
  146. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  147. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  148. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  149. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  150. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  151. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  152. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  153. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  154. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  155. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  156. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  157. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  158. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  159. package/.framework/src/instructions/reference/functions/security.md +140 -0
  160. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  161. package/.framework/src/instructions/reference/functions/system.md +106 -0
  162. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  163. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  164. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  165. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  166. package/.framework/src/instructions/reference/functions/types.md +146 -0
  167. package/.framework/src/instructions/reference/seeds.md +126 -0
  168. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  169. package/.framework/src/instructions/reference/spine-config.md +114 -0
  170. package/.framework/src/instructions/roles.md +80 -0
  171. package/.framework/src/instructions/runtime-detail.md +70 -0
  172. package/.framework/src/instructions/runtime-list.md +77 -0
  173. package/.framework/src/instructions/security.md +67 -0
  174. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  175. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  176. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  177. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  178. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  179. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  180. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  181. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  182. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  183. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  184. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  185. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  186. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  187. package/.framework/src/instructions/testing.md +59 -0
  188. package/.framework/src/instructions/timers.md +82 -0
  189. package/.framework/src/instructions/triggers.md +69 -0
  190. package/.framework/src/instructions/types.md +93 -0
  191. package/.framework/src/lib/branding.ts +128 -0
  192. package/.framework/src/pages/NoAppPage.tsx +3 -1
  193. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  194. package/.framework/src/pages/SetupPage.tsx +5 -2
  195. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  196. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  197. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  198. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  199. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  200. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  201. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  202. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  203. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  204. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  205. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  206. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  207. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  208. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  209. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  210. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  211. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  212. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  213. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  214. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  215. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  216. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  217. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  218. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  219. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  220. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  221. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  222. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  223. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  224. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  225. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  226. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  227. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  228. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  229. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  230. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  231. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  232. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  233. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  234. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  235. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  236. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  237. package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
  238. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
  239. package/.framework/src/pages/auth/LoginPage.tsx +4 -2
  240. package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
  241. package/.framework/templates/agent/AGENTS.md +150 -0
  242. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  243. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  244. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  245. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  246. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  247. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  248. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  249. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  250. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  251. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  252. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  253. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  254. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  255. package/.framework/templates/project.json +5 -1
  256. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  257. package/.framework/tests/unit/agent-files.test.ts +355 -0
  258. package/.framework/tests/unit/agent-runner.test.ts +496 -47
  259. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  260. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  261. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  262. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  263. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  264. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  265. package/.framework/tests/unit/episode-lease.test.ts +695 -0
  266. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  267. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  268. package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
  269. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  270. package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
  271. package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
  272. package/.framework/tests/unit/principal-cache.test.ts +15 -0
  273. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  274. package/.framework/tests/unit/system-cron.test.ts +240 -0
  275. package/.framework/tests/unit/timers.test.ts +159 -0
  276. package/.framework/tests/unit/trigger-engine.test.ts +278 -91
  277. package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
  278. package/README.md +125 -166
  279. package/config/tsconfig.cli.json +2 -2
  280. package/netlify.toml +5 -0
  281. package/package.json +15 -4
  282. package/templates/project/package-lock.json +2 -2
  283. package/.framework/docs/admin-routes.md +0 -76
  284. package/.framework/docs/api-docs-progress.md +0 -38
  285. package/.framework/docs/api-governance.md +0 -146
  286. package/.framework/docs/api-testing-results.md +0 -212
  287. package/.framework/docs/assembly-launch-guide.md +0 -138
  288. package/.framework/docs/audit-results.md +0 -590
  289. package/.framework/docs/db-api-inventory.md +0 -95
  290. package/.framework/docs/gap-remediation-backlog.md +0 -103
  291. package/.framework/docs/ui-adoption-verification.md +0 -111
  292. package/.framework/docs/ui-api-coverage.md +0 -84
  293. package/.framework/docs/v2-compatibility-audit.md +0 -228
  294. package/.framework/mcp/index.ts +0 -133
  295. package/.framework/mcp/load-env.ts +0 -60
  296. package/.framework/mcp/shared/app-manifest.ts +0 -275
  297. package/.framework/mcp/shared/db.ts +0 -477
  298. package/.framework/mcp/shared/kb.ts +0 -118
  299. package/.framework/mcp/tools/architect.ts +0 -431
  300. package/.framework/mcp/tools/cil/context.ts +0 -345
  301. package/.framework/mcp/tools/cil/graph.ts +0 -242
  302. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  303. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  304. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  305. package/.framework/mcp/tools/cil/project.ts +0 -791
  306. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  307. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  308. package/.framework/mcp/tools/cil/search.ts +0 -97
  309. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  310. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  311. package/.framework/mcp/tools/kb/delete.ts +0 -139
  312. package/.framework/mcp/tools/kb/index.ts +0 -52
  313. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  314. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  315. package/.framework/mcp/tools/kb/publish.ts +0 -100
  316. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  317. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  318. package/.framework/mcp/tools/kb/search.ts +0 -101
  319. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  320. package/.framework/mcp/tools/observability.ts +0 -510
@@ -1,3 +1,4 @@
1
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/admin-data.ts"]
1
2
  /**
2
3
  * @module admin-data
3
4
  * @audience internal
@@ -13,7 +14,6 @@
13
14
  *
14
15
  * @cil-generated
15
16
  */
16
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/admin-data.ts"]
17
17
  // CIL[DOC-MACRO purpose="This module handles administrative data operations within the system, providing essential functionalities for managing records and permissions."]
18
18
  // CIL[DOC-RESPONSIBILITY responsibility="This module handles administrative data operations within the system, providing essential functionalities for managing records and permissions. It serves as an API handler for various administrative actions, ensuring that only authorized users can perform updates and access sensitive data. INVARIANT: Only users with system admin privileges can execute these operations."]
19
19
  // CIL[DOC-CONTEXT module="admin-data" layer="app-specific" related_files="middleware.ts (provides request handling logic).,permissions.ts (manages access control and validation).,db.ts (interfaces with the database for data operations).,principal.ts (determines user roles and permissions)."]
@@ -62,7 +62,6 @@ import { checkTypeSlugRateLimit } from './_shared/security'
62
62
  * @stability stable
63
63
  * @cil-generated
64
64
  */
65
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:permissions_all:constant" type="constant" identity="PERMISSIONS_ALL" parent="~/.framework/functions/admin-data.ts"]
66
65
  // CIL[DOC-MACRO purpose="PERMISSIONS_ALL constant"]
67
66
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the PERMISSIONS_ALL constant."]
68
67
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -70,11 +69,12 @@ import { checkTypeSlugRateLimit } from './_shared/security'
70
69
  // CIL[DOC-MICRO logic_summary="Implements the PERMISSIONS_ALL constant." notable_decisions="[]"]
71
70
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
72
71
  // CIL[DOC-STABILITY stability="stable"]
72
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:PERMISSIONS_ALL:constant" type="constant" identity="PERMISSIONS_ALL" parent="~/.framework/functions/admin-data.ts"]
73
73
  const PERMISSIONS_ALL = {
74
74
  record_permissions: { all: ['create', 'read', 'update', 'delete'] },
75
75
  fields: {}
76
76
  }
77
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:permissions_all:constant"]
77
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:PERMISSIONS_ALL:constant"]
78
78
  // CIL[DOC-VECTOR type="context" content="purpose='PERMISSIONS_ALL defines a comprehensive set of permissions for record management, including create, read, update, and delete actions.'; No verified external callers" status="ready"]
79
79
  // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only" status="ready"]
80
80
  // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified" status="ready"]
@@ -96,7 +96,6 @@ const PERMISSIONS_ALL = {
96
96
  * @stability stable
97
97
  * @cil-generated
98
98
  */
99
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:valid_entities:constant" type="constant" identity="VALID_ENTITIES" parent="~/.framework/functions/admin-data.ts"]
100
99
  // CIL[DOC-MACRO purpose="VALID_ENTITIES constant"]
101
100
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the VALID_ENTITIES constant."]
102
101
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -104,8 +103,9 @@ const PERMISSIONS_ALL = {
104
103
  // CIL[DOC-MICRO logic_summary="Implements the VALID_ENTITIES constant." notable_decisions="[]"]
105
104
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
106
105
  // CIL[DOC-STABILITY stability="stable"]
106
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:VALID_ENTITIES:constant" type="constant" identity="VALID_ENTITIES" parent="~/.framework/functions/admin-data.ts"]
107
107
  const VALID_ENTITIES = ['accounts', 'people', 'items', 'threads', 'messages', 'links', 'attachments', 'watchers', 'item_progress', 'logs', 'test_runs', 'observability', 'test_cases', 'types', 'link_types', 'ai_agents', 'prompt_configs']
108
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:valid_entities:constant"]
108
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:VALID_ENTITIES:constant"]
109
109
  // CIL[DOC-MACRO purpose="VALID_ENTITIES constant"]
110
110
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the VALID_ENTITIES constant."]
111
111
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -134,7 +134,6 @@ const VALID_ENTITIES = ['accounts', 'people', 'items', 'threads', 'messages', 'l
134
134
  * @stability stable
135
135
  * @cil-generated
136
136
  */
137
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:raw_entities:constant" type="constant" identity="RAW_ENTITIES" parent="~/.framework/functions/admin-data.ts"]
138
137
  // CIL[DOC-MACRO purpose="RAW_ENTITIES constant"]
139
138
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the RAW_ENTITIES constant."]
140
139
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -142,11 +141,14 @@ const VALID_ENTITIES = ['accounts', 'people', 'items', 'threads', 'messages', 'l
142
141
  // CIL[DOC-MICRO logic_summary="Implements the RAW_ENTITIES constant." notable_decisions="[]"]
143
142
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
144
143
  // CIL[DOC-STABILITY stability="stable"]
144
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:RAW_ENTITIES:constant" type="constant" identity="RAW_ENTITIES" parent="~/.framework/functions/admin-data.ts"]
145
145
  const RAW_ENTITIES = ['logs', 'test_runs', 'observability', 'test_cases']
146
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:RAW_ENTITIES:constant"]
146
147
  // Entities with no created_by/updated_by columns — audit stamps must be
147
148
  // omitted or writes fail with schema-cache errors.
149
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:UNAUDITED_ENTITIES:constant" type="constant" identity="UNAUDITED_ENTITIES" parent="~/.framework/functions/admin-data.ts"]
148
150
  const UNAUDITED_ENTITIES = ['types', 'link_types']
149
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:raw_entities:constant"]
151
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:UNAUDITED_ENTITIES:constant"]
150
152
  // CIL[DOC-MACRO purpose="RAW_ENTITIES constant"]
151
153
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the RAW_ENTITIES constant."]
152
154
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -175,7 +177,6 @@ const UNAUDITED_ENTITIES = ['types', 'link_types']
175
177
  * @stability stable
176
178
  * @cil-generated
177
179
  */
178
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:entity_to_kind:constant" type="constant" identity="ENTITY_TO_KIND" parent="~/.framework/functions/admin-data.ts"]
179
180
  // CIL[DOC-MACRO purpose="ENTITY_TO_KIND constant"]
180
181
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the ENTITY_TO_KIND constant."]
181
182
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -183,6 +184,7 @@ const UNAUDITED_ENTITIES = ['types', 'link_types']
183
184
  // CIL[DOC-MICRO logic_summary="Implements the ENTITY_TO_KIND constant." notable_decisions="[]"]
184
185
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
185
186
  // CIL[DOC-STABILITY stability="stable"]
187
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:ENTITY_TO_KIND:constant" type="constant" identity="ENTITY_TO_KIND" parent="~/.framework/functions/admin-data.ts"]
186
188
  const ENTITY_TO_KIND: Record<string, string> = {
187
189
  accounts: 'account',
188
190
  people: 'person',
@@ -194,7 +196,7 @@ const ENTITY_TO_KIND: Record<string, string> = {
194
196
  watchers: 'watcher',
195
197
  item_progress: 'item_progress'
196
198
  }
197
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:entity_to_kind:constant"]
199
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:ENTITY_TO_KIND:constant"]
198
200
  // CIL[DOC-VECTOR type="context" content="purpose='ENTITY_TO_KIND is a mapping of entity names to their corresponding kind identifiers.'; No verified external callers" status="ready"]
199
201
  // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only" status="ready"]
200
202
  // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified" status="ready"]
@@ -217,7 +219,6 @@ const ENTITY_TO_KIND: Record<string, string> = {
217
219
  * @calls withAdminLogging, resolveTypeIds, getSearchField
218
220
  * @cil-generated
219
221
  */
220
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:list:constant" type="constant" identity="list" parent="~/.framework/functions/admin-data.ts"]
221
222
  // CIL[DOC-MACRO purpose="list constant"]
222
223
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the list constant."]
223
224
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -225,8 +226,9 @@ const ENTITY_TO_KIND: Record<string, string> = {
225
226
  // CIL[DOC-MICRO logic_summary="Implements the list constant." notable_decisions="[]"]
226
227
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
227
228
  // CIL[DOC-STABILITY stability="stable"]
229
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:list:constant" type="constant" identity="list" parent="~/.framework/functions/admin-data.ts"]
228
230
  export const list = createHandler(async (ctx, _body) => {
229
- const { entity, action, method, search, sort_field = 'created_at', sort_direction = 'desc', limit = 50, offset = 0, type_slug, type_slugs, view: viewSlug, flag_only, exclude_flag, ...filters } = ctx.query || {}
231
+ const { entity, action, method, search, sort_field = 'created_at', sort_direction = 'desc', limit = 50, offset = 0, type_slug, type_slugs, view: viewSlug, flag_only, exclude_flag, include_inactive, ...filters } = ctx.query || {}
230
232
 
231
233
  return await withAdminLogging(
232
234
  ctx,
@@ -292,6 +294,12 @@ export const list = createHandler(async (ctx, _body) => {
292
294
  }
293
295
  })
294
296
 
297
+ // Soft-deleted rows are hidden by default — pass is_active=false or
298
+ // include_inactive=true to see them.
299
+ if (filters.is_active === undefined && include_inactive !== 'true') {
300
+ query = query.eq('is_active', true)
301
+ }
302
+
295
303
  // Generic record-flag filters on the row's data jsonb. flag_only=<field>
296
304
  // returns only rows where data->>field is 'true'; exclude_flag=<field>
297
305
  // hides them (null counts as unset). Flag fields are stamped server-side
@@ -405,7 +413,6 @@ export const list = createHandler(async (ctx, _body) => {
405
413
  * @errorHandling returns null on error during data fetching.
406
414
  * @cil-generated
407
415
  */
408
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:attachlineage:function" type="function" identity="attachLineage" parent="~/.framework/functions/admin-data.ts"]
409
416
  // CIL[DOC-MACRO purpose="attachLineage function"]
410
417
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the attachLineage function from type-declared lineage config."]
411
418
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -413,6 +420,7 @@ export const list = createHandler(async (ctx, _body) => {
413
420
  // CIL[DOC-MICRO logic_summary="Reads functionality.lineage off the record's type design_schema; parents walk declared link_type then parent_fields (5-hop, visited-set cycle guard); children resolve via reverse link_type edges plus per-child-type data field lookups." notable_decisions="['link_type queries are guarded by the declared link_type, parent scan by declared parent_fields, child field lookups by declared children — a type with no lineage block attaches empty arrays with no link/data queries']"]
414
421
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
415
422
  // CIL[DOC-STABILITY stability="stable"]
423
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:attachLineage:function" type="function" identity="attachLineage" parent="~/.framework/functions/admin-data.ts"]
416
424
  async function attachLineage(record: any, ctx: any): Promise<void> {
417
425
  // Lineage is declared per item type in design_schema.functionality.lineage:
418
426
  // { parent_fields: string[], link_type: string, children: { [childTypeSlug]: field } }
@@ -516,7 +524,7 @@ async function attachLineage(record: any, ctx: any): Promise<void> {
516
524
  record.parents = parents
517
525
  record.children = children
518
526
  }
519
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:attachlineage:function"]
527
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:attachLineage:function"]
520
528
  // CIL[DOC-VECTOR type="context" content="purpose='The attachLineage function retrieves and attaches parent and child records to a given record based on its lineage in a hierarchical structure.'; No verified external callers" status="ready"]
521
529
  // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only" status="ready"]
522
530
  // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified" status="ready"]
@@ -540,7 +548,6 @@ async function attachLineage(record: any, ctx: any): Promise<void> {
540
548
  * @calls withAdminLogging, ctx.db.from, sanitizeRecordData, adminDb.from, attachLineage, projectMessageForCustomer
541
549
  * @cil-generated
542
550
  */
543
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:get:constant" type="constant" identity="get" parent="~/.framework/functions/admin-data.ts"]
544
551
  // CIL[DOC-MACRO purpose="get constant"]
545
552
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the get constant."]
546
553
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -548,6 +555,7 @@ async function attachLineage(record: any, ctx: any): Promise<void> {
548
555
  // CIL[DOC-MICRO logic_summary="Implements the get constant." notable_decisions="[]"]
549
556
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
550
557
  // CIL[DOC-STABILITY stability="stable"]
558
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:get:constant" type="constant" identity="get" parent="~/.framework/functions/admin-data.ts"]
551
559
  export const get = createHandler(async (ctx, _body) => {
552
560
  const { entity, id, view: viewSlug } = ctx.query || {}
553
561
  const method = ctx.query?.method || 'GET'
@@ -639,7 +647,6 @@ export const get = createHandler(async (ctx, _body) => {
639
647
  * @calls withAdminLogging, ctx.db.from, fireCreateTriggers, sanitizeRecordData
640
648
  * @cil-generated
641
649
  */
642
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:create:constant" type="constant" identity="create" parent="~/.framework/functions/admin-data.ts"]
643
650
  // CIL[DOC-MACRO purpose="create constant"]
644
651
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the create constant."]
645
652
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -647,6 +654,7 @@ export const get = createHandler(async (ctx, _body) => {
647
654
  // CIL[DOC-MICRO logic_summary="Implements the create constant." notable_decisions="[]"]
648
655
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
649
656
  // CIL[DOC-STABILITY stability="stable"]
657
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:create:constant" type="constant" identity="create" parent="~/.framework/functions/admin-data.ts"]
650
658
  export const create = createHandler(async (ctx, body) => {
651
659
  const entity = body?.entity || ctx.query?.entity
652
660
  const type_slug = body?.type_slug
@@ -686,9 +694,14 @@ export const create = createHandler(async (ctx, body) => {
686
694
  if (entity === 'observability' && (!raw.metric_name || raw.metric_value === undefined)) { const e: any = new Error('metric_name and metric_value are required for observability'); e.statusCode = 400; throw e }
687
695
  if (entity === 'test_cases' && (!raw.slug || !raw.name)) { const e: any = new Error('slug and name are required for test_cases'); e.statusCode = 400; throw e }
688
696
 
689
- const { data, error: err } = await ctx.db.from(entity).insert(raw).select().single()
697
+ const rawInsertDb = (ctx.principal?.isInternal || isSystemAdmin(ctx.principal)) ? adminDb : ctx.db
698
+ const { data, error: err } = await rawInsertDb.from(entity).insert(raw).select().single()
690
699
  if (err) throw err
691
- fireCreateTriggers(entity, data.id, raw, ctx).catch(console.error)
700
+ // Await the trigger check: the heavy work already offloads to
701
+ // pipeline-runner-background, so this only adds eval + queue insert
702
+ // + dispatch. Detached post-response work is CPU-frozen on Netlify
703
+ // and silently dropped trigger fires in production.
704
+ await fireCreateTriggers(entity, data.id, raw, ctx).catch(console.error)
692
705
  return await sanitizeRecordData(ctx, data, entity)
693
706
  }
694
707
 
@@ -798,7 +811,9 @@ export const create = createHandler(async (ctx, body) => {
798
811
  throw e
799
812
  }
800
813
 
801
- const insertDb = entity === 'accounts' ? adminDb : ctx.db
814
+ // Function-side permission check already ran — write via service role.
815
+ // ctx.db is PostgREST `authenticated` for humans, which holds SELECT only.
816
+ const insertDb = adminDb
802
817
 
803
818
  const { data, error: err } = await insertDb.from(entity)
804
819
  .insert(dataToInsert)
@@ -808,7 +823,7 @@ export const create = createHandler(async (ctx, body) => {
808
823
  if (err) throw err
809
824
 
810
825
  const entityData = { ...dataToInsert, id: data.id }
811
- fireCreateTriggers(entity, data.id, entityData, ctx).catch(console.error)
826
+ await fireCreateTriggers(entity, data.id, entityData, ctx).catch(console.error)
812
827
 
813
828
  return await sanitizeRecordData(ctx, data, entity)
814
829
  }
@@ -837,7 +852,6 @@ export const create = createHandler(async (ctx, body) => {
837
852
  * @calls withAdminLogging, validateUpdatePermissions, ctx.db.from, fireUpdateTriggers, sanitizeRecordData
838
853
  * @cil-generated
839
854
  */
840
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:update:constant" type="constant" identity="update" parent="~/.framework/functions/admin-data.ts"]
841
855
  // CIL[DOC-MACRO purpose="update constant"]
842
856
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the update constant."]
843
857
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -845,6 +859,7 @@ export const create = createHandler(async (ctx, body) => {
845
859
  // CIL[DOC-MICRO logic_summary="Implements the update constant." notable_decisions="[]"]
846
860
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
847
861
  // CIL[DOC-STABILITY stability="stable"]
862
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:update:constant" type="constant" identity="update" parent="~/.framework/functions/admin-data.ts"]
848
863
  export const update = createHandler(async (ctx, body) => {
849
864
  const { entity, id } = ctx.query || {}
850
865
  const method = ctx.query?.method || 'PATCH'
@@ -906,7 +921,7 @@ export const update = createHandler(async (ctx, body) => {
906
921
  updated_at: new Date().toISOString()
907
922
  }
908
923
 
909
- const { data, error: err } = await ctx.db.from(entity)
924
+ const { data, error: err } = await adminDb.from(entity)
910
925
  .update(dataToUpdate)
911
926
  .eq('id', id)
912
927
  .select()
@@ -915,7 +930,9 @@ export const update = createHandler(async (ctx, body) => {
915
930
  if (err) throw err
916
931
 
917
932
  const entityData = { ...data, ...dataToUpdate }
918
- fireUpdateTriggers(entity, id, entityData, ctx).catch(console.error)
933
+ // Await the trigger check — see note in the create path. Detached
934
+ // post-response work is CPU-frozen on Netlify and drops trigger fires.
935
+ await fireUpdateTriggers(entity, id, entityData, ctx, existing).catch(console.error)
919
936
 
920
937
  return await sanitizeRecordData(ctx, data, entity)
921
938
  }
@@ -944,7 +961,6 @@ export const update = createHandler(async (ctx, body) => {
944
961
  * @calls withAdminLogging, ctx.db.from, resolveFirstSurfacePermissions, entitySupportsSoftDelete, sanitizeRecordData
945
962
  * @cil-generated
946
963
  */
947
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:remove:constant" type="constant" identity="remove" parent="~/.framework/functions/admin-data.ts"]
948
964
  // CIL[DOC-MACRO purpose="remove constant"]
949
965
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the remove constant."]
950
966
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -952,6 +968,7 @@ export const update = createHandler(async (ctx, body) => {
952
968
  // CIL[DOC-MICRO logic_summary="Implements the remove constant." notable_decisions="[]"]
953
969
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
954
970
  // CIL[DOC-STABILITY stability="stable"]
971
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:remove:constant" type="constant" identity="remove" parent="~/.framework/functions/admin-data.ts"]
955
972
  export const remove = createHandler(async (ctx, _body) => {
956
973
  const { entity, id, soft = 'true' } = ctx.query || {}
957
974
  const method = ctx.query?.method || 'DELETE'
@@ -1000,7 +1017,7 @@ export const remove = createHandler(async (ctx, _body) => {
1000
1017
  const isSoftDelete = soft === 'true'
1001
1018
 
1002
1019
  if (isSoftDelete && entitySupportsSoftDelete(entity)) {
1003
- const { data, error: err } = await ctx.db.from(entity)
1020
+ const { data, error: err } = await adminDb.from(entity)
1004
1021
  .update({
1005
1022
  is_active: false,
1006
1023
  updated_by: ctx.principal?.type === 'human' ? ctx.principal.id : null,
@@ -1013,7 +1030,7 @@ export const remove = createHandler(async (ctx, _body) => {
1013
1030
  if (err) throw err
1014
1031
  return { deleted: true, soft: true, data: await sanitizeRecordData(ctx, data, entity) }
1015
1032
  } else {
1016
- const { error: err } = await ctx.db.from(entity)
1033
+ const { error: err } = await adminDb.from(entity)
1017
1034
  .delete()
1018
1035
  .eq('id', id)
1019
1036
 
@@ -1047,7 +1064,6 @@ export const remove = createHandler(async (ctx, _body) => {
1047
1064
  * @stability stable
1048
1065
  * @cil-generated
1049
1066
  */
1050
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:getsearchfield:function" type="function" identity="getSearchField" parent="~/.framework/functions/admin-data.ts"]
1051
1067
  // CIL[DOC-MACRO purpose="getSearchField function"]
1052
1068
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the getSearchField function."]
1053
1069
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -1055,6 +1071,7 @@ export const remove = createHandler(async (ctx, _body) => {
1055
1071
  // CIL[DOC-MICRO logic_summary="Implements the getSearchField function." notable_decisions="[]"]
1056
1072
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
1057
1073
  // CIL[DOC-STABILITY stability="stable"]
1074
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:getSearchField:function" type="function" identity="getSearchField" parent="~/.framework/functions/admin-data.ts"]
1058
1075
  function getSearchField(entity: string): string {
1059
1076
  const searchFields: Record<string, string> = {
1060
1077
  accounts: 'display_name',
@@ -1073,7 +1090,7 @@ function getSearchField(entity: string): string {
1073
1090
  }
1074
1091
  return searchFields[entity] || 'id'
1075
1092
  }
1076
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:getsearchfield:function"]
1093
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:getSearchField:function"]
1077
1094
  // CIL[DOC-VECTOR type="context" content="purpose='Retrieves the appropriate search field name for a given entity type.'; No verified external callers" status="ready"]
1078
1095
  // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only" status="ready"]
1079
1096
  // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified" status="ready"]
@@ -1097,7 +1114,6 @@ function getSearchField(entity: string): string {
1097
1114
  * @stability stable
1098
1115
  * @cil-generated
1099
1116
  */
1100
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:entitysupportssoftdelete:function" type="function" identity="entitySupportsSoftDelete" parent="~/.framework/functions/admin-data.ts"]
1101
1117
  // CIL[DOC-MACRO purpose="entitySupportsSoftDelete function"]
1102
1118
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the entitySupportsSoftDelete function."]
1103
1119
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -1105,11 +1121,12 @@ function getSearchField(entity: string): string {
1105
1121
  // CIL[DOC-MICRO logic_summary="Implements the entitySupportsSoftDelete function." notable_decisions="[]"]
1106
1122
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
1107
1123
  // CIL[DOC-STABILITY stability="stable"]
1124
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:entitySupportsSoftDelete:function" type="function" identity="entitySupportsSoftDelete" parent="~/.framework/functions/admin-data.ts"]
1108
1125
  function entitySupportsSoftDelete(entity: string): boolean {
1109
1126
  const softDeleteEntities = ['accounts', 'people', 'items', 'threads', 'messages', 'watchers', 'item_progress', 'test_cases']
1110
1127
  return softDeleteEntities.includes(entity)
1111
1128
  }
1112
- // CIL[END stable_id="~/.framework/functions/admin-data.ts:entitysupportssoftdelete:function"]
1129
+ // CIL[END stable_id="~/.framework/functions/admin-data.ts:entitySupportsSoftDelete:function"]
1113
1130
  // CIL[DOC-VECTOR type="context" content="purpose='Determines if a given entity supports soft deletion by checking against a predefined list of entities.'; No verified external callers" status="ready"]
1114
1131
  // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only" status="ready"]
1115
1132
  // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified" status="ready"]
@@ -1132,7 +1149,6 @@ function entitySupportsSoftDelete(entity: string): boolean {
1132
1149
  * @calls withAdminLogging, ctx.db.from
1133
1150
  * @cil-generated
1134
1151
  */
1135
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:stats:constant" type="constant" identity="stats" parent="~/.framework/functions/admin-data.ts"]
1136
1152
  // CIL[DOC-MACRO purpose="stats constant"]
1137
1153
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the stats constant."]
1138
1154
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -1140,6 +1156,7 @@ function entitySupportsSoftDelete(entity: string): boolean {
1140
1156
  // CIL[DOC-MICRO logic_summary="Implements the stats constant." notable_decisions="[]"]
1141
1157
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
1142
1158
  // CIL[DOC-STABILITY stability="stable"]
1159
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:stats:constant" type="constant" identity="stats" parent="~/.framework/functions/admin-data.ts"]
1143
1160
  export const stats = createHandler(async (ctx, _body) => {
1144
1161
  const { entity } = ctx.query || {}
1145
1162
  const method = ctx.query?.method || 'GET'
@@ -1188,7 +1205,6 @@ export const stats = createHandler(async (ctx, _body) => {
1188
1205
  * @calls list, get, stats, create, update, remove
1189
1206
  * @cil-generated
1190
1207
  */
1191
- // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:handler:constant" type="constant" identity="handler" parent="~/.framework/functions/admin-data.ts"]
1192
1208
  // CIL[DOC-MACRO purpose="handler constant"]
1193
1209
  // CIL[DOC-RESPONSIBILITY responsibility="Implements the handler constant."]
1194
1210
  // CIL[DOC-CONTEXT module=".framework/functions/admin-data.ts" layer="app-specific" related_files=""]
@@ -1196,6 +1212,7 @@ export const stats = createHandler(async (ctx, _body) => {
1196
1212
  // CIL[DOC-MICRO logic_summary="Implements the handler constant." notable_decisions="[]"]
1197
1213
  // CIL[DOC-CONFIDENCE pass="first" score="0.8" flags="none"]
1198
1214
  // CIL[DOC-STABILITY stability="stable"]
1215
+ // CIL[BEGIN stable_id="~/.framework/functions/admin-data.ts:handler:constant" type="constant" identity="handler" parent="~/.framework/functions/admin-data.ts"]
1199
1216
  export const handler = createHandler(async (ctx, body) => {
1200
1217
  const { action } = ctx.query || {}
1201
1218
  const method = ctx.query?.method || 'GET'
@@ -30,7 +30,7 @@ import { adminDb } from './_shared/db'
30
30
  import { emitLog } from './_shared/audit'
31
31
  import { isSystemAdmin } from './_shared/principal'
32
32
  import { PermissionEngine, sanitizeRecordData } from './_shared/permissions'
33
- import { runAgent, kickAgentEpisode } from './_shared/agent-runner'
33
+ import { runAgent, claimAgentEpisode, acquireEpisodeLease } from './_shared/agent-runner'
34
34
  import { checkRateLimit } from './_shared/rate-limit'
35
35
  import { detectInjection, emitSecurityEvent, getAppSecurityConfig } from './_shared/security'
36
36
  // CIL[END stable_id="~/.framework/functions/ai-agents.ts:module_header:module_header"]
@@ -244,7 +244,8 @@ export const create = createHandler(async (ctx, body) => {
244
244
  throw new Error('User context (person and account) required')
245
245
  }
246
246
 
247
- const { data, error: err } = await ctx.db
247
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
248
+ const { data, error: err } = await db
248
249
  .from('ai_agents')
249
250
  .insert({
250
251
  app_id: app_id || null,
@@ -258,7 +259,7 @@ export const create = createHandler(async (ctx, body) => {
258
259
  capabilities: capabilities || [],
259
260
  constraints: constraints || {},
260
261
  metadata: metadata || {},
261
- created_by: ctx.principal.id
262
+ created_by: ctx.principal.type === 'human' ? ctx.principal.id : null
262
263
  })
263
264
  .select()
264
265
  .single()
@@ -325,7 +326,8 @@ export const update = createHandler(async (ctx, body) => {
325
326
  if (updates[key] !== undefined) updateData[key] = updates[key]
326
327
  }
327
328
 
328
- const { data, error: err } = await ctx.db
329
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
330
+ const { data, error: err } = await db
329
331
  .from('ai_agents')
330
332
  .update(updateData)
331
333
  .eq('id', id)
@@ -383,7 +385,8 @@ export const remove = createHandler(async (ctx, _body) => {
383
385
  throw new Error('Agent ID is required')
384
386
  }
385
387
 
386
- const { data: current } = await ctx.db
388
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
389
+ const { data: current } = await db
387
390
  .from('ai_agents')
388
391
  .select('id, name')
389
392
  .eq('id', id)
@@ -391,7 +394,7 @@ export const remove = createHandler(async (ctx, _body) => {
391
394
 
392
395
  if (!current) throw new Error('Agent not found')
393
396
 
394
- const { error: err } = await ctx.db
397
+ const { error: err } = await db
395
398
  .from('ai_agents')
396
399
  .delete()
397
400
  .eq('id', id)
@@ -449,7 +452,8 @@ export const run = createHandler(async (ctx, body) => {
449
452
  throw new Error('User context (person and account) required')
450
453
  }
451
454
 
452
- const { data, error: err } = await ctx.db
455
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
456
+ const { data, error: err } = await db
453
457
  .from('threads')
454
458
  .select('*, type:types(*)')
455
459
  .eq('id', thread_id)
@@ -479,20 +483,27 @@ export const run = createHandler(async (ctx, body) => {
479
483
  }
480
484
  }
481
485
 
486
+ // Episode mode runs inline up to this budget — the first turns execute
487
+ // at full request CPU, then the client's step loop continues the
488
+ // episode under the same lease. Sized to leave headroom under the
489
+ // platform's synchronous request limit (~26s) even with a slow turn.
490
+ const EPISODE_INLINE_BUDGET_MS = 8000
491
+
482
492
  const runMode = (mode as 'step' | 'complete' | 'episode') || 'complete'
483
493
 
484
- // Episode single-flight: the in-flight driver stamps driver_id +
485
- // driver_heartbeat_at into research_state. A different driver asking to
486
- // step/kick while the lease is fresh gets 'running' instead of a second
487
- // interleaved write pass. The owner itself is never refused (its own
488
- // staleness check is finer-grained), and a lease expires after
489
- // DRIVER_LEASE_MS so a crashed driver can't wedge the thread.
490
- const DRIVER_LEASE_MS = 60000
494
+ // Episode single-flight: arbitration is the atomic episode_leases RPC
495
+ // (migration 063) — a claim INSERTs/locks the thread's lease row in one
496
+ // statement, so two entry paths can never both pass. The same driver
497
+ // re-acquiring renews; a different live driver is refused 'running';
498
+ // an expired lease is taken over so a crashed driver can't wedge the
499
+ // thread. When the RPC is absent (un-migrated deployment) the legacy
500
+ // non-atomic research_state check below runs instead.
491
501
  const existingState = (data as any)?.data?.research_state
492
502
  const heartbeatAt = Date.parse(existingState?.driver_heartbeat_at || '')
493
503
  const leaseHolder = existingState?.driver_id
494
504
  const callerToken = (body?.driver_token as string) || null
495
505
  const callerId = callerToken || ctx.requestId
506
+ const DRIVER_LEASE_MS = 60000
496
507
  const leaseLive =
497
508
  !!leaseHolder &&
498
509
  leaseHolder !== callerId &&
@@ -501,32 +512,91 @@ export const run = createHandler(async (ctx, body) => {
501
512
  Number.isFinite(heartbeatAt) &&
502
513
  Date.now() - heartbeatAt < DRIVER_LEASE_MS
503
514
 
504
- if ((runMode === 'step' || runMode === 'complete') && leaseLive) {
505
- return { ok: true, thread_id, status: 'running', driver_id: leaseHolder }
506
- }
507
-
508
515
  if (runMode === 'episode') {
509
- if (leaseLive) {
510
- return { ok: true, thread_id, status: 'running', detached: true, already_running: true }
516
+ // Episode mode claims the lease and runs the first turns INLINE
517
+ // (deadline-bounded complete mode) — detached 'void runAgent' work is
518
+ // CPU-throttled after the response flush on serverless platforms
519
+ // (~37s dead window observed before its first persisted message),
520
+ // which is exactly the stall the caller's quiet-window then "rescues"
521
+ // by double-driving. stealDetached lets this request take over from
522
+ // a 'detached:' background kick instantly; the fenced driver aborts
523
+ // at its next heartbeat without side effects.
524
+ const kick = await claimAgentEpisode(thread_id, user_message || '', ctx, callerId, (data as any)?.data, { stealDetached: true })
525
+ if (!kick.claimed && kick.already_done) {
526
+ // Replay of an already-answered question — return the episode's
527
+ // existing final rather than 'running' so callers don't poll for
528
+ // work that isn't coming.
529
+ const { data: lastOutbound } = await adminDb
530
+ .from('messages')
531
+ .select('id, content, data')
532
+ .eq('thread_id', thread_id)
533
+ .eq('direction', 'outbound')
534
+ .order('created_at', { ascending: false })
535
+ .limit(1)
536
+ const final = Array.isArray(lastOutbound) ? lastOutbound[0] : lastOutbound
537
+ return {
538
+ ok: true,
539
+ thread_id,
540
+ status: 'complete',
541
+ already_done: true,
542
+ driver_id: kick.driver_id,
543
+ message_id: final?.id || null,
544
+ content: final?.content || '',
545
+ message_type: final?.data?.message_type || null,
546
+ data: final?.data || null,
547
+ agent_id: final?.data?.agent_id || (data as any)?.data?.agent_id || null,
548
+ prompt_config_id: final?.data?.prompt_config_id || (data as any)?.data?.prompt_config_id || null,
549
+ }
511
550
  }
512
- // Debit agent_step only when a new driver will actually start work —
513
- // lease-refused requests and ticket-creation kicks already debited or
514
- // started nothing, and counting them would double-charge each episode.
515
- await checkRateLimit(ctx, 'agent_step')
516
- // Claim the lease before detaching so a step request landing during
517
- // resolveAgentConfig still sees a live driver. Shared implementation lives
518
- // in kickAgentEpisode (also used by ticket-creation handlers).
519
- const leaseId = callerToken || ctx.requestId
520
- const kick = await kickAgentEpisode(thread_id, user_message || '', ctx, leaseId, (data as any)?.data)
521
551
  if (!kick.claimed) {
522
- return { ok: true, thread_id, status: 'running', detached: true, already_running: true }
552
+ return { ok: true, thread_id, status: 'running', already_running: true, driver_id: kick.driver_id }
553
+ }
554
+ // Debit agent_step only after a claim — refused requests start
555
+ // nothing and must not double-charge the episode.
556
+ await checkRateLimit(ctx, 'agent_step')
557
+ const message = await runAgent(thread_id, user_message || '', ctx, 'complete', kick.driver_id || callerToken || undefined, kick.lease_token, { deadlineMs: EPISODE_INLINE_BUDGET_MS })
558
+
559
+ const { data: updatedThread } = await adminDb
560
+ .from('threads')
561
+ .select('data')
562
+ .eq('id', thread_id)
563
+ .single()
564
+ const state = updatedThread?.data?.research_state || {}
565
+ return {
566
+ ok: true,
567
+ thread_id,
568
+ detached: false,
569
+ message_id: message?.id || null,
570
+ status: state.status || (message ? 'complete' : 'failed'),
571
+ turn_count: state.turn_count ?? 0,
572
+ content: message?.content || '',
573
+ message_type: message?.data?.message_type || null,
574
+ data: message?.data || null,
575
+ agent_id: message?.data?.agent_id || updatedThread?.data?.agent_id || null,
576
+ prompt_config_id: message?.data?.prompt_config_id || updatedThread?.data?.prompt_config_id || null,
523
577
  }
524
- return { ok: true, thread_id, status: 'running', detached: true }
525
578
  }
526
579
 
527
- // step/complete modes debit here — same rule: only when work will start.
580
+ // step/complete: claim the episode lease for this driver. 'held' means
581
+ // another live driver owns the episode — the caller watches persisted
582
+ // state instead of interleaving writes. A claimed lease survives a
583
+ // nonterminal step return so the same driver's next call renews it.
584
+ // stealDetached lets a request driver take over from a 'detached:'
585
+ // background kick without waiting out the TTL.
586
+ const claim = await acquireEpisodeLease(thread_id, ctx.accountId, callerId, undefined, true)
587
+ let leaseToken: string | undefined
588
+ if (claim?.outcome === 'held') {
589
+ return { ok: true, thread_id, status: 'running', driver_id: claim.holder_driver_id }
590
+ }
591
+ if (claim === null && leaseLive) {
592
+ return { ok: true, thread_id, status: 'running', driver_id: leaseHolder }
593
+ }
594
+ if (claim?.outcome === 'claimed') leaseToken = claim.lease_token
595
+
596
+ // step/complete modes debit here — only when work will start, so
597
+ // lease-refused requests never double-charge an episode.
528
598
  await checkRateLimit(ctx, 'agent_step')
529
- const message = await runAgent(thread_id, user_message || '', ctx, runMode, callerToken || undefined)
599
+ const message = await runAgent(thread_id, user_message || '', ctx, runMode, callerId, leaseToken)
530
600
 
531
601
  const { data: updatedThread } = await adminDb
532
602
  .from('threads')