spine-framework 0.4.4 → 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 (283) 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/hash-api-key.ts +183 -79
  15. package/.framework/functions/_shared/logging.ts +562 -224
  16. package/.framework/functions/_shared/middleware.ts +47 -44
  17. package/.framework/functions/admin-data.ts +39 -28
  18. package/.framework/functions/ai-agents.ts +10 -6
  19. package/.framework/functions/api-keys.ts +301 -122
  20. package/.framework/functions/apps.ts +43 -26
  21. package/.framework/functions/cil-ingest.ts +48 -15
  22. package/.framework/functions/debug-auth.ts +14 -8
  23. package/.framework/functions/deliveries.ts +39 -12
  24. package/.framework/functions/embeddings.ts +145 -76
  25. package/.framework/functions/integration-outbound.ts +67 -27
  26. package/.framework/functions/integrations.ts +254 -127
  27. package/.framework/functions/logs.ts +273 -133
  28. package/.framework/functions/observability.ts +277 -147
  29. package/.framework/functions/pipeline-executions.ts +1 -1
  30. package/.framework/functions/pipelines.ts +14 -9
  31. package/.framework/functions/prompt-configs.ts +7 -4
  32. package/.framework/functions/roles.ts +8 -5
  33. package/.framework/functions/security.ts +30 -17
  34. package/.framework/functions/system-cron.ts +240 -127
  35. package/.framework/functions/tests.ts +7 -6
  36. package/.framework/functions/timers.ts +310 -107
  37. package/.framework/functions/triggers.ts +28 -13
  38. package/.framework/functions/types.ts +13 -10
  39. package/.framework/migrations/069_log_stats_window.sql +54 -0
  40. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  41. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  42. package/.framework/migrations/072_slug_autofill.sql +77 -0
  43. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  44. package/.framework/src/apps/admin/index.tsx +1141 -775
  45. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  46. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  47. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  48. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  49. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  50. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  51. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  52. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  53. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  54. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  55. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  56. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  57. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  58. package/.framework/src/components/ui/sheet.tsx +24 -26
  59. package/.framework/src/components/ui/textarea.tsx +74 -48
  60. package/.framework/src/hooks/useEntityList.ts +12 -9
  61. package/.framework/src/hooks/useListSchema.ts +108 -58
  62. package/.framework/src/instructions/README.md +57 -0
  63. package/.framework/src/instructions/ai-agents.md +69 -0
  64. package/.framework/src/instructions/alerts.md +80 -0
  65. package/.framework/src/instructions/api-keys.md +87 -0
  66. package/.framework/src/instructions/apps.md +77 -0
  67. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  68. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  69. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  70. package/.framework/src/instructions/concepts/automation.md +186 -0
  71. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  72. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  73. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  74. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  75. package/.framework/src/instructions/concepts/observability.md +116 -0
  76. package/.framework/src/instructions/concepts/permissions.md +150 -0
  77. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  78. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  79. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  80. package/.framework/src/instructions/deliveries.md +59 -0
  81. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  82. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  83. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  84. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  85. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  86. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  87. package/.framework/src/instructions/integrations.md +96 -0
  88. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  89. package/.framework/src/instructions/invites.md +60 -0
  90. package/.framework/src/instructions/kb.md +80 -0
  91. package/.framework/src/instructions/logs.md +77 -0
  92. package/.framework/src/instructions/observability.md +61 -0
  93. package/.framework/src/instructions/operations/deployment.md +115 -0
  94. package/.framework/src/instructions/operations/migrations.md +125 -0
  95. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  96. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  97. package/.framework/src/instructions/operations/upgrade.md +116 -0
  98. package/.framework/src/instructions/pipeline-executions.md +62 -0
  99. package/.framework/src/instructions/pipelines.md +78 -0
  100. package/.framework/src/instructions/prompts.md +95 -0
  101. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  102. package/.framework/src/instructions/reference/api/auth.md +113 -0
  103. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  104. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  105. package/.framework/src/instructions/reference/cli.md +124 -0
  106. package/.framework/src/instructions/reference/database.md +132 -0
  107. package/.framework/src/instructions/reference/design-schema.md +280 -0
  108. package/.framework/src/instructions/reference/env-vars.md +133 -0
  109. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  110. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  111. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  112. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  113. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  114. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  115. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  116. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  117. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  118. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  119. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  120. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  121. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  122. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  123. package/.framework/src/instructions/reference/functions/index.md +103 -0
  124. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  125. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  126. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  127. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  128. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  129. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  130. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  131. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  132. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  133. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  134. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  135. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  136. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  137. package/.framework/src/instructions/reference/functions/security.md +140 -0
  138. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  139. package/.framework/src/instructions/reference/functions/system.md +106 -0
  140. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  141. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  142. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  143. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  144. package/.framework/src/instructions/reference/functions/types.md +146 -0
  145. package/.framework/src/instructions/reference/seeds.md +126 -0
  146. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  147. package/.framework/src/instructions/reference/spine-config.md +114 -0
  148. package/.framework/src/instructions/roles.md +80 -0
  149. package/.framework/src/instructions/runtime-detail.md +70 -0
  150. package/.framework/src/instructions/runtime-list.md +77 -0
  151. package/.framework/src/instructions/security.md +67 -0
  152. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  153. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  154. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  155. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  156. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  157. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  158. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  159. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  160. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  161. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  162. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  163. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  164. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  165. package/.framework/src/instructions/testing.md +59 -0
  166. package/.framework/src/instructions/timers.md +82 -0
  167. package/.framework/src/instructions/triggers.md +69 -0
  168. package/.framework/src/instructions/types.md +93 -0
  169. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  170. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  171. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  172. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  173. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  174. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  175. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  176. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  177. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  178. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  179. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  180. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  181. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  182. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  183. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  184. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  185. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  186. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  187. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  188. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  189. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  190. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  191. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  192. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  193. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  194. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  195. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  196. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  197. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  198. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  199. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  200. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  201. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  202. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  203. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  204. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  205. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  206. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  207. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  208. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  209. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  210. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  211. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  212. package/.framework/templates/agent/AGENTS.md +150 -0
  213. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  214. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  215. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  216. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  217. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  218. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  219. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  220. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  221. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  222. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  223. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  224. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  225. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  226. package/.framework/templates/project.json +4 -1
  227. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  228. package/.framework/tests/unit/agent-files.test.ts +355 -0
  229. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  230. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  231. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  232. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  233. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  234. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  235. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  236. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  237. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  238. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  239. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  240. package/.framework/tests/unit/system-cron.test.ts +240 -0
  241. package/.framework/tests/unit/timers.test.ts +159 -0
  242. package/README.md +125 -166
  243. package/config/tsconfig.cli.json +2 -2
  244. package/package.json +15 -4
  245. package/templates/project/package-lock.json +2 -2
  246. package/.framework/docs/admin-routes.md +0 -76
  247. package/.framework/docs/api-docs-progress.md +0 -38
  248. package/.framework/docs/api-governance.md +0 -146
  249. package/.framework/docs/api-testing-results.md +0 -212
  250. package/.framework/docs/assembly-launch-guide.md +0 -138
  251. package/.framework/docs/audit-results.md +0 -590
  252. package/.framework/docs/db-api-inventory.md +0 -95
  253. package/.framework/docs/gap-remediation-backlog.md +0 -103
  254. package/.framework/docs/ui-adoption-verification.md +0 -111
  255. package/.framework/docs/ui-api-coverage.md +0 -84
  256. package/.framework/docs/v2-compatibility-audit.md +0 -228
  257. package/.framework/mcp/index.ts +0 -133
  258. package/.framework/mcp/load-env.ts +0 -60
  259. package/.framework/mcp/shared/app-manifest.ts +0 -275
  260. package/.framework/mcp/shared/db.ts +0 -477
  261. package/.framework/mcp/shared/kb.ts +0 -118
  262. package/.framework/mcp/tools/architect.ts +0 -431
  263. package/.framework/mcp/tools/cil/context.ts +0 -345
  264. package/.framework/mcp/tools/cil/graph.ts +0 -242
  265. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  266. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  267. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  268. package/.framework/mcp/tools/cil/project.ts +0 -791
  269. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  270. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  271. package/.framework/mcp/tools/cil/search.ts +0 -97
  272. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  273. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  274. package/.framework/mcp/tools/kb/delete.ts +0 -139
  275. package/.framework/mcp/tools/kb/index.ts +0 -52
  276. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  277. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  278. package/.framework/mcp/tools/kb/publish.ts +0 -100
  279. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  280. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  281. package/.framework/mcp/tools/kb/search.ts +0 -101
  282. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  283. package/.framework/mcp/tools/observability.ts +0 -510
@@ -131,7 +131,8 @@ export const create = createHandler(async (ctx, body) => {
131
131
  throw new Error('Account context required')
132
132
  }
133
133
 
134
- const { data, error: err } = await ctx.db
134
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
135
+ const { data, error: err } = await db
135
136
  .from('prompt_configs')
136
137
  .insert({
137
138
  app_id: app_id || null,
@@ -201,7 +202,8 @@ export const update = createHandler(async (ctx, body) => {
201
202
  if (updates[key] !== undefined) updateData[key] = updates[key]
202
203
  }
203
204
 
204
- const { data, error: err } = await ctx.db
205
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
206
+ const { data, error: err } = await db
205
207
  .from('prompt_configs')
206
208
  .update(updateData)
207
209
  .eq('id', id)
@@ -239,7 +241,8 @@ export const remove = createHandler(async (ctx, _body) => {
239
241
  throw new Error('Config ID is required')
240
242
  }
241
243
 
242
- const { data: current } = await ctx.db
244
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
245
+ const { data: current } = await db
243
246
  .from('prompt_configs')
244
247
  .select('id, name, slug')
245
248
  .eq('id', id)
@@ -247,7 +250,7 @@ export const remove = createHandler(async (ctx, _body) => {
247
250
 
248
251
  if (!current) throw new Error('Prompt config not found')
249
252
 
250
- const { error: err } = await ctx.db
253
+ const { error: err } = await db
251
254
  .from('prompt_configs')
252
255
  .delete()
253
256
  .eq('id', id)
@@ -188,7 +188,8 @@ export const create = createHandler(async (ctx, body) => {
188
188
  throw new Error('Role slug already exists for this app')
189
189
  }
190
190
 
191
- const { data, error: err } = await ctx.db
191
+ const db = permissions.isSystemAdmin(ctx) ? adminDb : ctx.db
192
+ const { data, error: err } = await db
192
193
  .from('roles')
193
194
  .insert({
194
195
  app_id,
@@ -234,7 +235,8 @@ export const update = createHandler(async (ctx, body) => {
234
235
  throw new Error('User context (person and account) required')
235
236
  }
236
237
 
237
- const { data: current, error: fetchErr } = await ctx.db
238
+ const db = permissions.isSystemAdmin(ctx) ? adminDb : ctx.db
239
+ const { data: current, error: fetchErr } = await db
238
240
  .from('roles')
239
241
  .select('*')
240
242
  .eq('id', id)
@@ -255,7 +257,7 @@ export const update = createHandler(async (ctx, body) => {
255
257
  throw new Error(fieldValidation.error)
256
258
  }
257
259
 
258
- const { data, error: err } = await ctx.db
260
+ const { data, error: err } = await db
259
261
  .from('roles')
260
262
  .update({
261
263
  ...updates,
@@ -297,7 +299,8 @@ export const remove = createHandler(async (ctx, body) => {
297
299
  throw new Error('User context (person and account) required')
298
300
  }
299
301
 
300
- const { data: current, error: fetchErr } = await ctx.db
302
+ const db = permissions.isSystemAdmin(ctx) ? adminDb : ctx.db
303
+ const { data: current, error: fetchErr } = await db
301
304
  .from('roles')
302
305
  .select('*')
303
306
  .eq('id', id)
@@ -307,7 +310,7 @@ export const remove = createHandler(async (ctx, body) => {
307
310
  throw new Error('Role not found or access denied')
308
311
  }
309
312
 
310
- const { data, error: err } = await ctx.db
313
+ const { data, error: err } = await db
311
314
  .from('roles')
312
315
  .update({
313
316
  is_active: false,
@@ -1,3 +1,4 @@
1
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/security.ts"]
1
2
  /**
2
3
  * @module security
3
4
  * @audience internal
@@ -15,7 +16,6 @@
15
16
  *
16
17
  * @cil-generated
17
18
  */
18
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/security.ts"]
19
19
  // CIL[DOC-MACRO purpose="System-admin security operations: flagged-actor report, ban/unban, blocklist CRUD, Netlify edge sync"]
20
20
  // CIL[DOC-RESPONSIBILITY responsibility="Aggregate security.* audit events into a flagged-accounts report; apply three-layer bans (security_blocklist + auth.admin ban_duration + Netlify firewall rule); expose blocklist list/add/revoke"]
21
21
  // CIL[DOC-CONTEXT module="functions" layer="security" related_files=".framework/functions/_shared/security.ts,.framework/migrations/049_security_blocklist.sql,.framework/src/pages/admin/SecurityPage.tsx"]
@@ -29,15 +29,19 @@ import { adminDb } from './_shared/db'
29
29
  import { emitAudit } from './_shared/audit'
30
30
  // CIL[END stable_id="~/.framework/functions/security.ts:module_header:module_header"]
31
31
 
32
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:netlifyedgesync:comment" type="comment" identity="netlifyEdgeSync" parent="~/.framework/functions/security.ts"]
33
32
  // CIL[DOC-MACRO purpose="Netlify Firewall Traffic Rules sync for banned IPs"]
34
33
  // CIL[DOC-RESPONSIBILITY responsibility="Keep a single managed ip-deny rule (description spine-security:blocklist) whose data.addresses mirrors active ip blocklist entries"]
35
34
  // CIL[DOC-CONTRACT inputs="ips to add/remove" outputs="{ synced: boolean, skipped?: boolean, error?: string }" errors="never throws — returns error field" side_effects="Netlify API GET/PUT of full ruleset"]
36
35
  // CIL[DOC-MICRO logic_summary="GET firewall_rule_set → mutate managed rule's addresses (or create rule) → PUT whole published ruleset back; user-managed rules are preserved verbatim" notable_decisions="PUT replaces the entire ruleset, so the full published rule list is round-tripped; concurrent edits could still race — acceptable at admin-action frequency"]
37
36
  // CIL[DOC-STABILITY stability="experimental"]
37
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:MANAGED_RULE_DESC:constant" type="constant" identity="MANAGED_RULE_DESC" parent="~/.framework/functions/security.ts"]
38
38
  const MANAGED_RULE_DESC = 'spine-security:blocklist'
39
+ // CIL[END stable_id="~/.framework/functions/security.ts:MANAGED_RULE_DESC:constant"]
40
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:NETLIFY_API:constant" type="constant" identity="NETLIFY_API" parent="~/.framework/functions/security.ts"]
39
41
  const NETLIFY_API = 'https://api.netlify.com'
42
+ // CIL[END stable_id="~/.framework/functions/security.ts:NETLIFY_API:constant"]
40
43
 
44
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:netlifyEdgeSync:function" type="function" identity="netlifyEdgeSync" parent="~/.framework/functions/security.ts"]
41
45
  async function netlifyEdgeSync(ipsToAdd: string[], ipsToRemove: string[] = []): Promise<{ synced: boolean; skipped?: boolean; error?: string }> {
42
46
  const token = process.env.NETLIFY_ACCESS_TOKEN
43
47
  const siteId = process.env.NETLIFY_SITE_ID
@@ -90,25 +94,29 @@ async function netlifyEdgeSync(ipsToAdd: string[], ipsToRemove: string[] = []):
90
94
  return { synced: false, error: err?.message || 'Netlify sync failed' }
91
95
  }
92
96
  }
93
- // CIL[END stable_id="~/.framework/functions/security.ts:netlifyedgesync:comment"]
97
+ // CIL[END stable_id="~/.framework/functions/security.ts:netlifyEdgeSync:function"]
94
98
 
95
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:flaggedaccounts:function" type="function" identity="flaggedAccounts" parent="~/.framework/functions/security.ts"]
96
99
  // CIL[DOC-MACRO purpose="Aggregate security.* audit events into a flagged-actors report"]
97
100
  // CIL[DOC-RESPONSIBILITY responsibility="Group recent security.rate_limit_exceeded / injection_detected / registration_blocked log rows by acting person or account, enrich with people + account + blocklist state"]
98
101
  // CIL[DOC-CONTRACT inputs="ctx" outputs="{ rows: FlaggedRow[] }" errors="returns empty rows on missing tables" side_effects="logs + people + accounts + security_blocklist reads"]
99
- // CIL[DOC-MICRO logic_summary="Fetch last 500 security.* events → bucket by person id (fallback account id) → count signals + collect kinds/patterns/ips → join people(email, geo, status), accounts(display_name), active blocklist entries → sort by signal count desc" notable_decisions="IP shown comes from the person's registration_ip/last_ip (trusted edge capture), not spoofable request data"]
102
+ // CIL[DOC-MICRO logic_summary="Fetch last 500 security.* events within 30 days → bucket by person id (fallback account id) → count signals + collect kinds/patterns/ips → join people(email, geo, status), accounts(display_name), active blocklist entries → sort by signal count desc" notable_decisions="IP shown comes from the person's registration_ip/last_ip (trusted edge capture), not spoofable request data; started_at lower bound keeps the span_name scan index-bounded (no span_name index, and zero-signal scans would otherwise walk the whole logs table)"]
100
103
  // CIL[DOC-STABILITY stability="experimental"]
104
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:SECURITY_SIGNALS:constant" type="constant" identity="SECURITY_SIGNALS" parent="~/.framework/functions/security.ts"]
101
105
  const SECURITY_SIGNALS = [
102
106
  'security.rate_limit_exceeded',
103
107
  'security.injection_detected',
104
108
  'security.registration_blocked',
105
109
  ]
110
+ // CIL[END stable_id="~/.framework/functions/security.ts:SECURITY_SIGNALS:constant"]
106
111
 
112
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:flaggedAccounts:function" type="function" identity="flaggedAccounts" parent="~/.framework/functions/security.ts"]
107
113
  async function flaggedAccounts(ctx: RequestContext) {
114
+ const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
108
115
  const { data: events, error } = await adminDb
109
116
  .from('logs')
110
117
  .select('account_id, started_at, metadata')
111
118
  .in('span_name', SECURITY_SIGNALS)
119
+ .gte('started_at', since)
112
120
  .order('started_at', { ascending: false })
113
121
  .limit(500)
114
122
  if (error) {
@@ -194,14 +202,14 @@ async function flaggedAccounts(ctx: RequestContext) {
194
202
  rows.sort((a, b) => b.signals - a.signals)
195
203
  return { rows }
196
204
  }
197
- // CIL[END stable_id="~/.framework/functions/security.ts:flaggedaccounts:function"]
205
+ // CIL[END stable_id="~/.framework/functions/security.ts:flaggedAccounts:function"]
198
206
 
199
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:addblockrows:function" type="function" identity="addBlockRows" parent="~/.framework/functions/security.ts"]
200
207
  // CIL[DOC-MACRO purpose="Upsert active hard-block rows into security_blocklist"]
201
208
  // CIL[DOC-RESPONSIBILITY responsibility="Insert rows with revoked_at cleared on (type,value) conflict so a re-ban re-activates a revoked row"]
202
209
  // CIL[DOC-CONTRACT inputs="rows[], bannedBy, reason" outputs="void" errors="throws on DB error" side_effects="security_blocklist upsert"]
203
210
  // CIL[DOC-MICRO logic_summary="map rows → upsert onConflict type,value with revoked_at:null" notable_decisions="[]"]
204
211
  // CIL[DOC-STABILITY stability="stable"]
212
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:addBlockRows:function" type="function" identity="addBlockRows" parent="~/.framework/functions/security.ts"]
205
213
  async function addBlockRows(
206
214
  rows: Array<{ type: string; value: string }>,
207
215
  bannedBy: string | undefined,
@@ -225,14 +233,14 @@ async function addBlockRows(
225
233
  )
226
234
  if (error) throw new Error('security_blocklist upsert failed: ' + error.message)
227
235
  }
228
- // CIL[END stable_id="~/.framework/functions/security.ts:addblockrows:function"]
236
+ // CIL[END stable_id="~/.framework/functions/security.ts:addBlockRows:function"]
229
237
 
230
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:banuser:function" type="function" identity="banUser" parent="~/.framework/functions/security.ts"]
231
238
  // CIL[DOC-MACRO purpose="Three-layer ban: internal blocklist + Supabase Auth ban + Netlify edge deny"]
232
239
  // CIL[DOC-RESPONSIBILITY responsibility="Given a person_id (or bare email/ip), persist blocklist rows for person+email+account+IP, ban the auth user, and push the IP into the managed Netlify deny rule"]
233
240
  // CIL[DOC-CONTRACT inputs="ctx, body {person_id?|email?|ip?, reason?, block_ip?, block_email?}" outputs="{ ok, person_id, layers }" errors="400 missing target; 404 person not found; 500 blocklist write failure" side_effects="security_blocklist upserts, auth.admin.updateUserById, Netlify ruleset PUT, audit events"]
234
241
  // CIL[DOC-MICRO logic_summary="Resolve person → write blocklist rows (fail = abort) → auth ban via ban_duration 876600h → edge sync for last/registration IP → audit security.user_banned with per-layer results" notable_decisions="Internal blocklist is authoritative and must succeed; auth/edge failures are recorded in the audit event and returned, not thrown — a ban must never half-apply silently"]
235
242
  // CIL[DOC-STABILITY stability="experimental"]
243
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:banUser:function" type="function" identity="banUser" parent="~/.framework/functions/security.ts"]
236
244
  async function banUser(ctx: RequestContext, body: any) {
237
245
  const { person_id, email: rawEmail, ip: rawIp, reason, block_ip = true, block_email = true } = body || {}
238
246
  if (!person_id && !rawEmail && !rawIp) {
@@ -295,14 +303,14 @@ async function banUser(ctx: RequestContext, body: any) {
295
303
 
296
304
  return { ok: true, person_id: person?.id || null, email, ip, layers }
297
305
  }
298
- // CIL[END stable_id="~/.framework/functions/security.ts:banuser:function"]
306
+ // CIL[END stable_id="~/.framework/functions/security.ts:banUser:function"]
299
307
 
300
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:unbanuser:function" type="function" identity="unbanUser" parent="~/.framework/functions/security.ts"]
301
308
  // CIL[DOC-MACRO purpose="Reverse a ban: revoke blocklist rows, lift auth ban, remove edge IP"]
302
309
  // CIL[DOC-RESPONSIBILITY responsibility="Revoke active security_blocklist rows for the person's identifiers, lift the Supabase ban, and pull the IP out of the managed Netlify rule"]
303
310
  // CIL[DOC-CONTRACT inputs="ctx, body {person_id}" outputs="{ ok, revoked, layers }" errors="404 person not found" side_effects="blocklist updates, auth admin call, Netlify PUT, audit"]
304
311
  // CIL[DOC-MICRO logic_summary="Load person → revoke matching (type,value) rows → ban_duration 'none' → edge remove → audit security.user_unbanned" notable_decisions="account_id rows are intentionally left alone on unban — an org-level block is a separate decision"]
305
312
  // CIL[DOC-STABILITY stability="experimental"]
313
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:unbanUser:function" type="function" identity="unbanUser" parent="~/.framework/functions/security.ts"]
306
314
  async function unbanUser(ctx: RequestContext, body: any) {
307
315
  const { person_id } = body || {}
308
316
  if (!person_id) throw new Error('person_id is required')
@@ -353,13 +361,13 @@ async function unbanUser(ctx: RequestContext, body: any) {
353
361
 
354
362
  return { ok: true, person_id: person.id, layers }
355
363
  }
356
- // CIL[END stable_id="~/.framework/functions/security.ts:unbanuser:function"]
364
+ // CIL[END stable_id="~/.framework/functions/security.ts:unbanUser:function"]
357
365
 
358
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:blocklistcrud:comment" type="comment" identity="blocklistCrud" parent="~/.framework/functions/security.ts"]
359
366
  // CIL[DOC-MACRO purpose="Blocklist list/add/revoke for the admin UI"]
360
367
  // CIL[DOC-RESPONSIBILITY responsibility="Expose the security_blocklist table to system admins; ip entries sync to the Netlify edge on add/revoke"]
361
368
  // CIL[DOC-MICRO logic_summary="list returns active+recent rows; add upserts (type,value,severity,reason,expires_at); revoke stamps revoked_at. Both edge-sync ip values" notable_decisions="[]"]
362
369
  // CIL[DOC-STABILITY stability="experimental"]
370
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:listBlocklist:function" type="function" identity="listBlocklist" parent="~/.framework/functions/security.ts"]
363
371
  async function listBlocklist() {
364
372
  const { data, error } = await adminDb
365
373
  .from('security_blocklist')
@@ -369,7 +377,9 @@ async function listBlocklist() {
369
377
  if (error) throw new Error('blocklist read failed: ' + error.message)
370
378
  return { rows: data || [] }
371
379
  }
380
+ // CIL[END stable_id="~/.framework/functions/security.ts:listBlocklist:function"]
372
381
 
382
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:blocklistAdd:function" type="function" identity="blocklistAdd" parent="~/.framework/functions/security.ts"]
373
383
  async function blocklistAdd(ctx: RequestContext, body: any) {
374
384
  const { type, value, severity = 'hard', reason, expires_at } = body || {}
375
385
  if (!type || !value) throw new Error('type and value are required')
@@ -395,7 +405,9 @@ async function blocklistAdd(ctx: RequestContext, body: any) {
395
405
  })
396
406
  return { ok: true }
397
407
  }
408
+ // CIL[END stable_id="~/.framework/functions/security.ts:blocklistAdd:function"]
398
409
 
410
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:blocklistRevoke:function" type="function" identity="blocklistRevoke" parent="~/.framework/functions/security.ts"]
399
411
  async function blocklistRevoke(ctx: RequestContext, body: any) {
400
412
  const { id, type, value } = body || {}
401
413
  if (!id && !(type && value)) throw new Error('id or (type, value) is required')
@@ -417,17 +429,18 @@ async function blocklistRevoke(ctx: RequestContext, body: any) {
417
429
  })
418
430
  return { ok: true, revoked: revoked?.length || 0 }
419
431
  }
420
- // CIL[END stable_id="~/.framework/functions/security.ts:blocklistcrud:comment"]
432
+ // CIL[END stable_id="~/.framework/functions/security.ts:blocklistRevoke:function"]
421
433
 
422
- // CIL[BEGIN stable_id="~/.framework/functions/security.ts:handler:constant" type="constant" identity="handler" parent="~/.framework/functions/security.ts"]
423
434
  // CIL[DOC-MACRO purpose="Action dispatcher — system-admin only"]
424
435
  // CIL[DOC-RESPONSIBILITY responsibility="Route ?action= to the security operations; every action is gated by requireSystemContextWithAudit"]
425
436
  // CIL[DOC-CONTRACT inputs="ctx, body" outputs="action result JSON" errors="401/403 unauthenticated/non-admin; 400 unknown action" side_effects="delegated"]
426
437
  // CIL[DOC-MICRO logic_summary="Gate once, then switch on ctx.query.action || body.action" notable_decisions="[]"]
427
438
  // CIL[DOC-STABILITY stability="experimental"]
439
+ // CIL[BEGIN stable_id="~/.framework/functions/security.ts:handler:constant" type="constant" identity="handler" parent="~/.framework/functions/security.ts"]
428
440
  export const handler = createHandler(async (ctx, body) => {
429
- const deny = requireSystemContextWithAudit(ctx, 'security_admin')
430
- if (deny) return deny
441
+ // Throws a statusCode-carrying error on denial; createHandler maps it to a
442
+ // real HTTP 401/403 (F-051).
443
+ requireSystemContextWithAudit(ctx, 'security_admin')
431
444
 
432
445
  const action = (ctx.query?.action as string) || (body?.action as string) || ''
433
446
  switch (action) {