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
@@ -248,7 +248,8 @@ export const getExecutions = createHandler(async (ctx, body) => {
248
248
  throw new Error('Account context required')
249
249
  }
250
250
 
251
- const { data, error: err } = await ctx.db
251
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
252
+ const { data, error: err } = await db
252
253
  .from('trigger_executions')
253
254
  .select('*')
254
255
  .eq('trigger_id', trigger_id)
@@ -275,7 +276,7 @@ export const getExecutions = createHandler(async (ctx, body) => {
275
276
  // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
276
277
  // CIL[DOC-STABILITY stability="stable"]
277
278
  export const create = createHandler(async (ctx, body) => {
278
- const { app_id, name, description, event_type, pipeline_id, config, metadata } = body
279
+ const { app_id, name, description, event_type, trigger_type, pipeline_id, config, metadata } = body
279
280
 
280
281
  if (!name || !event_type || !pipeline_id) {
281
282
  throw new Error('name, event_type, and pipeline_id are required')
@@ -305,7 +306,8 @@ export const create = createHandler(async (ctx, body) => {
305
306
  }
306
307
  }
307
308
 
308
- const { data, error: err } = await ctx.db
309
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
310
+ const { data, error: err } = await db
309
311
  .from('triggers')
310
312
  .insert({
311
313
  app_id: app_id || null,
@@ -316,7 +318,8 @@ export const create = createHandler(async (ctx, body) => {
316
318
  pipeline_id,
317
319
  config: config || {},
318
320
  metadata: metadata || {},
319
- created_by: ctx.principal.id
321
+ trigger_type: trigger_type || 'event',
322
+ created_by: ctx.principal.type === 'human' ? ctx.principal.id : null
320
323
  })
321
324
  .select()
322
325
  .single()
@@ -353,13 +356,14 @@ export const update = createHandler(async (ctx, body) => {
353
356
  throw new Error('Trigger ID is required')
354
357
  }
355
358
 
356
- const allowed = ['name', 'description', 'config', 'pipeline_id', 'metadata', 'is_active', 'event_type', 'app_id']
359
+ const allowed = ['name', 'description', 'config', 'pipeline_id', 'metadata', 'is_active', 'event_type', 'trigger_type', 'app_id']
357
360
  const updateData: Record<string, any> = { updated_at: new Date().toISOString() }
358
361
  for (const key of allowed) {
359
362
  if (updates[key] !== undefined) updateData[key] = updates[key]
360
363
  }
361
364
 
362
- const { data, error: err } = await ctx.db
365
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
366
+ const { data, error: err } = await db
363
367
  .from('triggers')
364
368
  .update(updateData)
365
369
  .eq('id', id)
@@ -397,7 +401,8 @@ export const remove = createHandler(async (ctx, body) => {
397
401
  throw new Error('Trigger ID is required')
398
402
  }
399
403
 
400
- const { data: existing } = await ctx.db
404
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
405
+ const { data: existing } = await db
401
406
  .from('triggers')
402
407
  .select('name, event_type')
403
408
  .eq('id', id)
@@ -407,9 +412,9 @@ export const remove = createHandler(async (ctx, body) => {
407
412
  throw new Error('Trigger not found')
408
413
  }
409
414
 
410
- const { error: err } = await ctx.db
415
+ const { error: err } = await db
411
416
  .from('triggers')
412
- .update({
417
+ .update({
413
418
  is_active: false,
414
419
  updated_at: new Date().toISOString()
415
420
  })
@@ -446,7 +451,8 @@ export const toggle = createHandler(async (ctx, body) => {
446
451
  throw new Error('Trigger ID is required')
447
452
  }
448
453
 
449
- const { data: existing } = await ctx.db
454
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
455
+ const { data: existing } = await db
450
456
  .from('triggers')
451
457
  .select('is_active, name')
452
458
  .eq('id', id)
@@ -458,9 +464,9 @@ export const toggle = createHandler(async (ctx, body) => {
458
464
 
459
465
  const newStatus = !existing.is_active
460
466
 
461
- const { data, error: err } = await ctx.db
467
+ const { data, error: err } = await db
462
468
  .from('triggers')
463
- .update({
469
+ .update({
464
470
  is_active: newStatus,
465
471
  updated_at: new Date().toISOString()
466
472
  })
@@ -497,7 +503,16 @@ export const toggle = createHandler(async (ctx, body) => {
497
503
  // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
498
504
  // CIL[DOC-STABILITY stability="stable"]
499
505
  export const handler = createHandler(async (ctx, body) => {
500
- const { action } = ctx.query || {}
506
+ const { action, id } = ctx.query || {}
507
+ const method = ctx.query?.method || 'GET'
508
+
509
+ if (!action) {
510
+ if (method === 'GET') return id ? await get(ctx, body) : await list(ctx, body)
511
+ if (method === 'POST') return await create(ctx, body)
512
+ if (method === 'PATCH') return await update(ctx, body)
513
+ if (method === 'DELETE') return await remove(ctx, body)
514
+ throw new Error(`Unsupported method: ${method}`)
515
+ }
501
516
 
502
517
  switch (action) {
503
518
  case 'list':
@@ -42,22 +42,27 @@ const permissions = PermissionEngine as any
42
42
  // CIL[DOC-MACRO purpose="List active type definitions with optional filters and pagination"]
43
43
  // CIL[DOC-RESPONSIBILITY responsibility="Query the public.types table and sanitize records based on the caller principal"]
44
44
  // CIL[DOC-CONTEXT module="types function" layer="framework/functions" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts,.framework/functions/_shared/principal.ts,.framework/functions/_shared/schema-utils.ts,.framework/functions/types.ts"]
45
- // CIL[DOC-CONTRACT inputs="kind, app_id, ownership, limit, offset, include_schema query parameters" outputs="array of sanitized type rows" errors="Database query errors" side_effects="none"]
46
- // CIL[DOC-MICRO logic_summary="Builds a filtered, paginated Supabase query against the types table and returns sanitized results" notable_decisions="Returns minimal fields for unauthenticated callers; includes schema only when requested"]
45
+ // CIL[DOC-CONTRACT inputs="kind, app_id, ownership, active, limit, offset, include_schema query parameters" outputs="array of sanitized type rows" errors="Database query errors" side_effects="none"]
46
+ // CIL[DOC-MICRO logic_summary="Builds a filtered, paginated Supabase query against the types table and returns sanitized results" notable_decisions="Returns minimal fields for unauthenticated callers; includes schema only when requested; active defaults to true, 'all' lists active and inactive, 'false' lists inactive only"]
47
47
  // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
48
48
  // CIL[DOC-STABILITY stability="stable"]
49
49
  export const list = createHandler(async (ctx, body) => {
50
- const { kind, app_id, ownership, limit = '50', offset = '0', include_schema } = ctx.query || {}
50
+ const { kind, app_id, ownership, active, limit = '50', offset = '0', include_schema } = ctx.query || {}
51
51
 
52
52
  const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
53
53
 
54
54
  let query = db
55
55
  .from('types')
56
56
  .select('*')
57
- .eq('is_active', true)
58
57
  .order('kind')
59
58
  .order('name')
60
59
 
60
+ if (active === 'false') {
61
+ query = query.eq('is_active', false)
62
+ } else if (active !== 'all') {
63
+ query = query.eq('is_active', true)
64
+ }
65
+
61
66
  if (kind) {
62
67
  query = query.eq('kind', kind)
63
68
  }
@@ -132,7 +137,6 @@ export const get = createHandler(async (ctx, body) => {
132
137
  .from('types')
133
138
  .select('*')
134
139
  .eq('id', id)
135
- .eq('is_active', true)
136
140
  .single()
137
141
 
138
142
  if (err) throw err
@@ -237,7 +241,6 @@ export const create = createHandler(async (ctx, body) => {
237
241
  app_id = null,
238
242
  design_schema = {},
239
243
  validation_schema = null,
240
- view_config = {},
241
244
  description = null,
242
245
  icon = null,
243
246
  color = null
@@ -272,7 +275,6 @@ export const create = createHandler(async (ctx, body) => {
272
275
  app_id,
273
276
  design_schema,
274
277
  validation_schema: validation,
275
- view_config,
276
278
  description,
277
279
  icon,
278
280
  color,
@@ -325,7 +327,6 @@ export const update = createHandler(async (ctx, body) => {
325
327
  .from('types')
326
328
  .select('*')
327
329
  .eq('id', id)
328
- .eq('is_active', true)
329
330
  .single()
330
331
 
331
332
  if (!existing) {
@@ -340,7 +341,6 @@ export const update = createHandler(async (ctx, body) => {
340
341
  name,
341
342
  slug,
342
343
  design_schema,
343
- view_config,
344
344
  description,
345
345
  icon,
346
346
  color,
@@ -354,7 +354,6 @@ export const update = createHandler(async (ctx, body) => {
354
354
 
355
355
  if (name !== undefined) updates.name = name
356
356
  if (slug !== undefined) updates.slug = slug
357
- if (view_config !== undefined) updates.view_config = view_config
358
357
  if (description !== undefined) updates.description = description
359
358
  if (icon !== undefined) updates.icon = icon
360
359
  if (color !== undefined) updates.color = color
@@ -368,6 +367,10 @@ export const update = createHandler(async (ctx, body) => {
368
367
  updates.validation_schema = generateValidationSchema(design_schema)
369
368
  }
370
369
 
370
+ if (Object.keys(updates).length === 0) {
371
+ return existing
372
+ }
373
+
371
374
  const { data, error } = await adminDb
372
375
  .from('types')
373
376
  .update(updates)
@@ -0,0 +1,54 @@
1
+ CREATE OR REPLACE FUNCTION public.get_log_stats(
2
+ p_account_id uuid,
3
+ p_level text DEFAULT NULL::text,
4
+ p_source text DEFAULT NULL::text,
5
+ p_source_type text DEFAULT NULL::text,
6
+ p_source_id text DEFAULT NULL::text,
7
+ p_person_id text DEFAULT NULL::text,
8
+ p_query text DEFAULT NULL::text,
9
+ p_date_from timestamp with time zone DEFAULT NULL::timestamp with time zone,
10
+ p_date_to timestamp with time zone DEFAULT NULL::timestamp with time zone
11
+ )
12
+ RETURNS TABLE(total bigint, error_count bigint, user_action_count bigint, event_type_count bigint, levels text[], sources text[], source_types text[])
13
+ LANGUAGE plpgsql
14
+ AS $function$
15
+ DECLARE
16
+ v_from timestamptz := COALESCE(p_date_from, now() - interval '7 days');
17
+ v_total bigint;
18
+ v_error_count bigint;
19
+ v_user_action_count bigint;
20
+ v_event_type_count bigint;
21
+ v_levels text[];
22
+ v_sources text[];
23
+ v_source_types text[];
24
+ BEGIN
25
+ SELECT
26
+ count(*),
27
+ count(*) FILTER (WHERE level ILIKE '%error%'),
28
+ count(*) FILTER (WHERE person_id IS NOT NULL),
29
+ count(DISTINCT level)
30
+ INTO v_total, v_error_count, v_user_action_count, v_event_type_count
31
+ FROM public.logs
32
+ WHERE account_id = p_account_id
33
+ AND created_at >= v_from
34
+ AND (p_level IS NULL OR level = p_level)
35
+ AND (p_source IS NULL OR source = p_source)
36
+ AND (p_source_type IS NULL OR source_type = p_source_type)
37
+ AND (p_source_id IS NULL OR source_id = p_source_id::uuid)
38
+ AND (p_person_id IS NULL OR person_id = p_person_id::uuid)
39
+ AND (p_query IS NULL OR message ILIKE '%' || p_query || '%')
40
+ AND (p_date_to IS NULL OR created_at <= p_date_to);
41
+
42
+ SELECT
43
+ coalesce(array_agg(DISTINCT level ORDER BY level) FILTER (WHERE level IS NOT NULL), ARRAY[]::text[]),
44
+ coalesce(array_agg(DISTINCT source ORDER BY source) FILTER (WHERE source IS NOT NULL), ARRAY[]::text[]),
45
+ coalesce(array_agg(DISTINCT source_type ORDER BY source_type) FILTER (WHERE source_type IS NOT NULL), ARRAY[]::text[])
46
+ INTO v_levels, v_sources, v_source_types
47
+ FROM public.logs
48
+ WHERE account_id = p_account_id
49
+ AND created_at >= v_from
50
+ AND (p_date_to IS NULL OR created_at <= p_date_to);
51
+
52
+ RETURN QUERY SELECT v_total, v_error_count, v_user_action_count, v_event_type_count, v_levels, v_sources, v_source_types;
53
+ END;
54
+ $function$;
@@ -0,0 +1,166 @@
1
+ -- Migration 070: bootstrap the RPCs the types/apps handlers expect
2
+ --
3
+ -- types.ts getSchema (?action=schema) and apps.ts getSchema /
4
+ -- checkAvailability / updateVersion (?action=schema|available|version)
5
+ -- call four Postgres functions that existed in the pre-consolidation v2
6
+ -- schema (v2-core/migrations/010_apps.sql, 004_types.sql +
7
+ -- 049_schema_driven_permissions.sql) but were never ported into
8
+ -- .framework/migrations — each action fails at runtime on a stock
9
+ -- install with "Could not find the function public.<name>(...) in the
10
+ -- schema cache". This migration recreates them against the public
11
+ -- schema.
12
+ --
13
+ -- Semantics are ported from the recovered v2 definitions:
14
+ -- * get_type_schema returns the matching active type's design_schema
15
+ -- ('{}' when none). An app_id argument prefers that app's row and
16
+ -- falls back to the global (app_id IS NULL) type — the documented
17
+ -- "app-specific fallback to system" behavior.
18
+ -- * get_app_schema returns a jsonb projection of the active app row
19
+ -- ('{}' when none), extended with the required_roles / route_prefix /
20
+ -- renderer columns added since v2.
21
+ -- * is_app_available returns true when an active app with the slug is
22
+ -- available to the account under the same ownership rule
23
+ -- get_account_apps uses (is_system OR owner_account_id IS NULL OR
24
+ -- owner_account_id = account_id).
25
+ -- * update_app_version bumps apps.version + updated_at and returns
26
+ -- void — the handler only checks for an error.
27
+ --
28
+ -- All four run as SECURITY INVOKER (the v2 default): callers go through
29
+ -- ctx.db, so human callers keep RLS scoping (types are world-readable via
30
+ -- types_read; apps via apps_access) and machine principals bypass RLS as
31
+ -- service_role. GRANTs match the sibling RPC convention in
32
+ -- 004_core_rpc_and_broker.sql.
33
+
34
+ -- CIL[BEGIN stable_id="~/.framework/migrations/070_app_type_schema_rpcs.sql:module:migration" type="migration" identity="app-type-schema-rpcs" parent="~/.framework/migrations/070_app_type_schema_rpcs.sql"]
35
+ -- CIL[DOC-MACRO purpose="Create the four Postgres RPCs the types and apps Netlify handlers invoke: get_type_schema, get_app_schema, is_app_available, update_app_version"]
36
+ -- CIL[DOC-RESPONSIBILITY responsibility="Provide schema lookup, app-schema projection, account-scoped app availability, and app version bump functions missing since the v2-to-public consolidation"]
37
+ -- CIL[DOC-CONTEXT module="functions" layer="schema" related_files=".framework/functions/types.ts,.framework/functions/apps.ts,.framework/migrations/013_get_account_apps_role_filter.sql"]
38
+ -- CIL[DOC-CONTRACT inputs="text/uuid params matching the .rpc named args in types.ts and apps.ts" outputs="jsonb scalars, boolean, void matching the shapes the handlers read" errors="none — SECURITY INVOKER relies on caller RLS" side_effects="update_app_version mutates public.apps version and updated_at"]
39
+ -- CIL[DOC-MICRO logic_summary="Ports the recovered v2-core function bodies to the public schema; get_type_schema reads design_schema with app-specific fallback to the global type; is_app_available reuses the get_account_apps ownership rule including NULL-owner apps" notable_decisions="SECURITY INVOKER rather than DEFINER so human callers keep RLS scoping and no caller-supplied privilege claims are trusted"]
40
+ -- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
41
+ -- CIL[DOC-STABILITY stability="stable"]
42
+ -- CIL[DOC-AUDIENCE audience="framework_developers"]
43
+ -- CIL[END stable_id="~/.framework/migrations/070_app_type_schema_rpcs.sql:module:migration"]
44
+
45
+ -- ── types ─────────────────────────────────────────────────────────────
46
+
47
+ CREATE OR REPLACE FUNCTION public.get_type_schema(
48
+ kind text,
49
+ slug text,
50
+ app_id uuid DEFAULT NULL
51
+ )
52
+ RETURNS jsonb
53
+ LANGUAGE plpgsql
54
+ STABLE
55
+ AS $$
56
+ DECLARE
57
+ type_schema jsonb;
58
+ BEGIN
59
+ SELECT t.design_schema INTO type_schema
60
+ FROM public.types t
61
+ WHERE t.kind = get_type_schema.kind
62
+ AND t.slug = get_type_schema.slug
63
+ AND t.is_active = true
64
+ AND (t.app_id = get_type_schema.app_id OR t.app_id IS NULL)
65
+ ORDER BY t.app_id NULLS LAST -- prefer app-specific types over global
66
+ LIMIT 1;
67
+
68
+ RETURN COALESCE(type_schema, '{}'::jsonb);
69
+ END;
70
+ $$;
71
+
72
+ GRANT EXECUTE ON FUNCTION public.get_type_schema(text, text, uuid) TO authenticated;
73
+ GRANT EXECUTE ON FUNCTION public.get_type_schema(text, text, uuid) TO service_role;
74
+
75
+ COMMENT ON FUNCTION public.get_type_schema(text, text, uuid) IS 'Get type design_schema by kind+slug, app-specific with fallback to global';
76
+
77
+ -- ── apps ──────────────────────────────────────────────────────────────
78
+
79
+ CREATE OR REPLACE FUNCTION public.get_app_schema(app_slug text)
80
+ RETURNS jsonb
81
+ LANGUAGE plpgsql
82
+ STABLE
83
+ AS $$
84
+ DECLARE
85
+ app_schema jsonb;
86
+ BEGIN
87
+ SELECT jsonb_build_object(
88
+ 'id', a.id,
89
+ 'slug', a.slug,
90
+ 'name', a.name,
91
+ 'description', a.description,
92
+ 'icon', a.icon,
93
+ 'color', a.color,
94
+ 'version', a.version,
95
+ 'app_type', a.app_type,
96
+ 'source', a.source,
97
+ 'config', a.config,
98
+ 'nav_items', a.nav_items,
99
+ 'min_role', a.min_role,
100
+ 'required_roles', a.required_roles,
101
+ 'integration_deps', a.integration_deps,
102
+ 'metadata', a.metadata,
103
+ 'route_prefix', a.route_prefix,
104
+ 'renderer', a.renderer
105
+ ) INTO app_schema
106
+ FROM public.apps a
107
+ WHERE a.slug = get_app_schema.app_slug
108
+ AND a.is_active = true;
109
+
110
+ RETURN COALESCE(app_schema, '{}'::jsonb);
111
+ END;
112
+ $$;
113
+
114
+ GRANT EXECUTE ON FUNCTION public.get_app_schema(text) TO authenticated;
115
+ GRANT EXECUTE ON FUNCTION public.get_app_schema(text) TO service_role;
116
+
117
+ COMMENT ON FUNCTION public.get_app_schema(text) IS 'Get full app schema by slug';
118
+
119
+ CREATE OR REPLACE FUNCTION public.is_app_available(
120
+ app_slug text,
121
+ account_id uuid
122
+ )
123
+ RETURNS boolean
124
+ LANGUAGE plpgsql
125
+ STABLE
126
+ AS $$
127
+ BEGIN
128
+ RETURN EXISTS (
129
+ SELECT 1
130
+ FROM public.apps a
131
+ WHERE a.slug = is_app_available.app_slug
132
+ AND a.is_active = true
133
+ AND (
134
+ a.is_system = true
135
+ OR a.owner_account_id IS NULL
136
+ OR a.owner_account_id = is_app_available.account_id
137
+ )
138
+ );
139
+ END;
140
+ $$;
141
+
142
+ GRANT EXECUTE ON FUNCTION public.is_app_available(text, uuid) TO authenticated;
143
+ GRANT EXECUTE ON FUNCTION public.is_app_available(text, uuid) TO service_role;
144
+
145
+ COMMENT ON FUNCTION public.is_app_available(text, uuid) IS 'Check if app is available to account';
146
+
147
+ CREATE OR REPLACE FUNCTION public.update_app_version(
148
+ app_id uuid,
149
+ new_version text
150
+ )
151
+ RETURNS void
152
+ LANGUAGE plpgsql
153
+ AS $$
154
+ BEGIN
155
+ UPDATE public.apps
156
+ SET
157
+ version = update_app_version.new_version,
158
+ updated_at = now()
159
+ WHERE id = update_app_version.app_id;
160
+ END;
161
+ $$;
162
+
163
+ GRANT EXECUTE ON FUNCTION public.update_app_version(uuid, text) TO authenticated;
164
+ GRANT EXECUTE ON FUNCTION public.update_app_version(uuid, text) TO service_role;
165
+
166
+ COMMENT ON FUNCTION public.update_app_version(uuid, text) IS 'Update app version';
@@ -0,0 +1,96 @@
1
+ -- Migration 071: F-056 — make timers created via the API/admin UI fire.
2
+ --
3
+ -- Two halves of the fix:
4
+ -- 1. timers.ts now writes next_execution server-side on
5
+ -- create/update/activate (068's get_due_timers gates on
6
+ -- next_execution IS NOT NULL and nothing else ever set it).
7
+ -- 2. This file: update_timer_after_run previously rescheduled only
8
+ -- from config->>'interval_minutes', but TimerDetailPage seeded
9
+ -- config.interval_seconds — UI-created timers would fire once (if
10
+ -- armed) and never reschedule. The RPC now accepts both keys with
11
+ -- the same precedence the TS handler uses: interval_minutes
12
+ -- (minutes) first, then interval_seconds (seconds / 60).
13
+ -- Existing rows keep working under either key.
14
+ --
15
+ -- A backfill at the bottom arms existing active timers that have a
16
+ -- computable interval but next_execution IS NULL — rows created before
17
+ -- the API could write the column could never fire.
18
+
19
+ -- CIL[BEGIN stable_id="~/.framework/migrations/071_timer_interval_keys.sql:module:migration" type="migration" identity="timer-interval-keys" parent="~/.framework/migrations/071_timer_interval_keys.sql"]
20
+ -- CIL[DOC-MACRO purpose="Teach update_timer_after_run the interval_seconds config key and backfill next_execution for dormant armed-by-interval timers"]
21
+ -- CIL[DOC-RESPONSIBILITY responsibility="Replace update_timer_after_run with a dual-key interval resolver; arm existing active timers whose next_execution is NULL"]
22
+ -- CIL[DOC-CONTEXT module="system-cron" layer="schema" related_files=".framework/functions/system-cron.ts, .framework/functions/timers.ts, .framework/migrations/068_cron_schedule_timer_rpcs.sql"]
23
+ -- CIL[DOC-CONTRACT inputs="p_timer_id uuid, p_success boolean, p_error_message text" outputs="void" errors="none — SECURITY DEFINER" side_effects="mutates timers bookkeeping columns; backfill sets next_execution on dormant interval timers"]
24
+ -- CIL[DOC-MICRO logic_summary="Interval precedence: config->>'interval_minutes' (minutes) then config->>'interval_seconds' (seconds/60); non-positive or absent => next_execution NULL one-shot stop. Backfill UPDATE arms is_active + next_execution IS NULL + positive-interval rows to now()+interval" notable_decisions="Both keys kept forever so rows seeded with the old UI default (interval_seconds) and the canonical key (interval_minutes) both reschedule; 'once'/cron timers still dead-end to NULL since no cron evaluator exists in the DB"]
25
+ -- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
26
+ -- CIL[DOC-STABILITY stability="stable"]
27
+ -- CIL[DOC-AUDIENCE audience="system-admin"]
28
+ -- CIL[END stable_id="~/.framework/migrations/071_timer_interval_keys.sql:module:migration"]
29
+
30
+ CREATE OR REPLACE FUNCTION public.update_timer_after_run(
31
+ p_timer_id uuid,
32
+ p_success boolean,
33
+ p_error_message text
34
+ )
35
+ RETURNS void
36
+ LANGUAGE plpgsql
37
+ SECURITY DEFINER
38
+ SET search_path = public
39
+ AS $$
40
+ DECLARE
41
+ v_config jsonb;
42
+ v_interval numeric; -- resolved interval in MINUTES
43
+ BEGIN
44
+ SELECT config INTO v_config FROM public.timers WHERE id = p_timer_id;
45
+
46
+ -- interval_minutes (canonical) wins; interval_seconds is the legacy UI
47
+ -- default and is converted seconds -> minutes.
48
+ v_interval := CASE
49
+ WHEN v_config->>'interval_minutes' ~ '^\d+(\.\d+)?$'
50
+ THEN (v_config->>'interval_minutes')::numeric
51
+ WHEN v_config->>'interval_seconds' ~ '^\d+(\.\d+)?$'
52
+ THEN (v_config->>'interval_seconds')::numeric / 60
53
+ ELSE NULL
54
+ END;
55
+
56
+ UPDATE public.timers
57
+ SET
58
+ last_execution = now(),
59
+ execution_count = COALESCE(execution_count, 0) + 1,
60
+ success_count = COALESCE(success_count, 0) + CASE WHEN p_success THEN 1 ELSE 0 END,
61
+ failure_count = COALESCE(failure_count, 0) + CASE WHEN p_success THEN 0 ELSE 1 END,
62
+ next_execution = CASE
63
+ WHEN v_interval IS NOT NULL AND v_interval > 0
64
+ THEN now() + (v_interval * interval '1 minute')
65
+ ELSE NULL
66
+ END,
67
+ updated_at = now()
68
+ WHERE id = p_timer_id;
69
+ END;
70
+ $$;
71
+
72
+ -- Arm timers that are active and have a resolvable interval but were
73
+ -- created while next_execution was unwritable (pre-F-056 API). One-shot
74
+ -- and unparseable configs stay NULL — a NULL next_execution is a safe
75
+ -- never-fire state, matching update_timer_after_run's stop semantics.
76
+ UPDATE public.timers t
77
+ SET
78
+ next_execution = now() + (v.mins * interval '1 minute'),
79
+ updated_at = now()
80
+ FROM (
81
+ SELECT
82
+ id,
83
+ CASE
84
+ WHEN config->>'interval_minutes' ~ '^\d+(\.\d+)?$'
85
+ THEN (config->>'interval_minutes')::numeric
86
+ WHEN config->>'interval_seconds' ~ '^\d+(\.\d+)?$'
87
+ THEN (config->>'interval_seconds')::numeric / 60
88
+ ELSE NULL
89
+ END AS mins
90
+ FROM public.timers
91
+ ) v
92
+ WHERE t.id = v.id
93
+ AND t.is_active
94
+ AND t.next_execution IS NULL
95
+ AND v.mins IS NOT NULL
96
+ AND v.mins > 0;
@@ -0,0 +1,77 @@
1
+ -- 072_slug_autofill.sql
2
+ -- Fill slug from name on insert when the caller omits it.
3
+ -- Fresh-install schema (001) declares slug NOT NULL on these tables, but the
4
+ -- create handlers for triggers/timers/pipelines/ai_agents/test_runs/
5
+ -- item_progress/invites never write slug — inserts fail on fresh chains.
6
+ -- The trigger is a no-op whenever the caller supplies a slug.
7
+
8
+ CREATE OR REPLACE FUNCTION public.spine_fill_slug()
9
+ RETURNS trigger
10
+ LANGUAGE plpgsql
11
+ AS $$
12
+ DECLARE
13
+ base text;
14
+ candidate text;
15
+ clash boolean;
16
+ i int := 2;
17
+ BEGIN
18
+ IF NEW.slug IS NOT NULL AND NEW.slug <> '' THEN
19
+ RETURN NEW;
20
+ END IF;
21
+
22
+ base := trim(both '_' from regexp_replace(lower(coalesce(NEW.name, '')), '[^a-z0-9]+', '_', 'g'));
23
+ IF base = '' THEN
24
+ base := 'row';
25
+ END IF;
26
+ candidate := base;
27
+
28
+ LOOP
29
+ EXECUTE format(
30
+ 'SELECT EXISTS (SELECT 1 FROM %I.%I WHERE app_id IS NOT DISTINCT FROM $1 AND slug = $2)',
31
+ TG_TABLE_SCHEMA, TG_TABLE_NAME
32
+ ) INTO clash USING NEW.app_id, candidate;
33
+ EXIT WHEN NOT clash;
34
+ candidate := base || '_' || i;
35
+ i := i + 1;
36
+ IF i > 100 THEN
37
+ candidate := base || '_' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 6);
38
+ EXECUTE format(
39
+ 'SELECT EXISTS (SELECT 1 FROM %I.%I WHERE app_id IS NOT DISTINCT FROM $1 AND slug = $2)',
40
+ TG_TABLE_SCHEMA, TG_TABLE_NAME
41
+ ) INTO clash USING NEW.app_id, candidate;
42
+ EXIT WHEN NOT clash;
43
+ END IF;
44
+ END LOOP;
45
+
46
+ NEW.slug := candidate;
47
+ RETURN NEW;
48
+ END;
49
+ $$;
50
+
51
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.triggers;
52
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.triggers
53
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
54
+
55
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.timers;
56
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.timers
57
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
58
+
59
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.pipelines;
60
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.pipelines
61
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
62
+
63
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.ai_agents;
64
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.ai_agents
65
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
66
+
67
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.test_runs;
68
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.test_runs
69
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
70
+
71
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.item_progress;
72
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.item_progress
73
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
74
+
75
+ DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.invites;
76
+ CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.invites
77
+ FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
@@ -0,0 +1,26 @@
1
+ -- 073_seed_member_roles.sql
2
+ -- Seed the member and member_admin end-user roles.
3
+ --
4
+ -- F-066: 003_core_system_data.sql seeds system_admin plus the department
5
+ -- roles but never member/member_admin, even though the platform assumes
6
+ -- they exist:
7
+ -- * 003's own thread/message types grant record_permissions to both
8
+ -- * 060 grants them CRU on account/person; 051/007 grant member reads
9
+ -- * the registration RPCs (052/059) default join_request to 'member'
10
+ -- * RegisterPage falls back to 'member'; the admin UI defaults app
11
+ -- min_role to 'member'; create-app defaults --role member
12
+ -- With no roles row the slug never resolves, so every member grant was a
13
+ -- dead key (guaranteed-deny) and apps scaffolded with the default --role
14
+ -- wrote a min_role no person could satisfy.
15
+ --
16
+ -- is_system stays false: invites.ts resolveRoleId and the invite-accept
17
+ -- path reject is_system role slugs, and these are the self-service
18
+ -- end-user roles (same shape the app seed/roles.json files carry).
19
+ -- App installs upsert the same slugs (conflict 'slug'), so ON CONFLICT
20
+ -- DO NOTHING keeps this a no-op wherever an app already claimed them.
21
+
22
+ INSERT INTO public.roles (id, slug, name, description, permissions, is_system, is_active, is_protected, app_id, account_id)
23
+ VALUES
24
+ (gen_random_uuid(), 'member', 'Member', 'Standard account member — end-user access to account-scoped apps', '["*"]'::jsonb, false, true, false, NULL, NULL),
25
+ (gen_random_uuid(), 'member_admin', 'Member Admin', 'Member administrator — same access as member plus invite and member management', '["*"]'::jsonb, false, true, false, NULL, NULL)
26
+ ON CONFLICT (slug) DO NOTHING;