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
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: Roles
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/roles.ts
9
+ - .framework/functions/_shared/permissions.ts
10
+ - .framework/functions/auth.ts
11
+ - .framework/functions/_shared/principal.ts
12
+ - .framework/src/pages/admin/RolesPage.tsx
13
+ - .framework/src/pages/admin/RoleDetailPage.tsx
14
+ - .framework/migrations/003_core_system_data.sql
15
+ ---
16
+
17
+ ## What this is for
18
+
19
+ A **role** is a named principal category assigned to people (`people.role_id`). Its **slug** is what permission evaluation keys on: each type's `design_schema.record_permissions` maps role slugs to the record actions that role may perform. The role's own `permissions` column feeds `principal.permissions` for coarse gates.
20
+
21
+ ## Fields and settings
22
+
23
+ - **Name** — string, required. Display label (e.g. "Support Manager"); `slug` and `name` are the API's required fields.
24
+ - **Slug** — string, required, unique per app. **Disabled after creation** because `record_permissions` keys and `people.role_id` references depend on it.
25
+ - **Description** — string, optional.
26
+ - **System Role** — checkbox, create-only. Reserved roles can't be deleted and are protected for core functions.
27
+ - **Active** — checkbox. Inactive roles can't be assigned to people.
28
+ - **Permissions (JSON)** — the form suggests an object mapping type slugs to action arrays (`{ "ticket": ["create","read"] }`), but the permission engine does **not** evaluate this column for record access — see Downstream effects. A **flat array** form (`["read","write","admin","system"]`) is what `auth.ts` copies into `principal.permissions`.
29
+
30
+ **Downstream effects**
31
+
32
+ - Record CRUD is governed by each type's `design_schema.record_permissions`, keyed by **role slug** — e.g. `"record_permissions": { "support-agent": ["read"] }`. Assigning a person a role takes effect immediately (the engine resolves `people.role_id → roles.slug` per request, short cache).
33
+ - `record_permissions` keys are matched **verbatim** against `roles.slug` — `system-admin` is a dead key that silently denies the real `system_admin` role. The `all` key is a wildcard granting every authenticated principal.
34
+ - `auth.ts` copies `role.permissions` into `principal.permissions` **only when it is a flat array** (`system_admin` is hardcoded to `["read","write","admin","system"]`). `principal.permissions` backs coarse checks like the invites `system` gate — not per-record access.
35
+ - Malformed permissions don't lock anyone out of records (the column isn't read for that) — a wrong `record_permissions` slug on the type does.
36
+ - Creating a role with slug `system_admin` is restricted to `system_admin` principals.
37
+
38
+ ## Minimal configuration
39
+
40
+ ```json
41
+ {
42
+ "name": "Support Agent",
43
+ "slug": "support-agent",
44
+ "is_active": true,
45
+ "permissions": { "ticket": ["create", "read", "update"] }
46
+ }
47
+ ```
48
+
49
+ ## Full configuration
50
+
51
+ ```json
52
+ {
53
+ "name": "Support Manager",
54
+ "slug": "support-manager",
55
+ "is_active": true,
56
+ "is_system": false,
57
+ "permissions": { "ticket": ["create", "read", "update", "delete"] },
58
+ "metadata": { "department": "support" }
59
+ }
60
+ ```
61
+
62
+ Both examples use the object form the UI suggests — it is stored as documentation/delegated-policy data. Record access still comes from each type's `record_permissions` under the same slug.
63
+
64
+ ## Example
65
+
66
+ A support app ships three roles: `support-agent`, `support-manager`, and `support-viewer`. On the `ticket` type, `record_permissions` grants `support-agent` `["create","read","update"]`, `support-manager` all four actions, and `support-viewer` `["read"]`; assignment happens on `people` records via `role_id`.
67
+
68
+ ## Core files
69
+
70
+ - `.framework/functions/roles.ts` — API (list/get/create/update/delete; `system_admin` slug creation gated)
71
+ - `.framework/functions/_shared/permissions.ts` — `record_permissions`/`fields.*.permissions` evaluation (role-slug keyed; `all` wildcard)
72
+ - `.framework/functions/auth.ts` / `.framework/functions/_shared/principal.ts` — `role.permissions` → `principal.permissions` (flat array only)
73
+ - `.framework/src/pages/admin/RolesPage.tsx` / `RoleDetailPage.tsx` — admin UI
74
+ - Table: `public.roles`; assignment on `public.people.role_id`
75
+
76
+ ## Agent notes
77
+
78
+ - The Permissions textarea's own placeholder shows the object form — know that it is not what gates records. The gate is `record_permissions[role_slug]` on the type.
79
+ - Slug is immutable after create; rename = new role + reassignment. Never introduce a hyphenated `system-admin` slug — seeded slugs use underscores.
80
+ - The seeded `system_admin` role must stay assigned for admin functions; `auth.ts` hardcodes its principal permissions, it is not data-driven.
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: Runtime Record (detail)
3
+ audience: both
4
+ kind: ui
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/components/runtime/DataDetailPage.tsx
9
+ - .framework/src/hooks/useEntityRecord.ts
10
+ - .framework/src/components/runtime/SchemaDetailForm.tsx
11
+ - .framework/functions/admin-data.ts
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ **Runtime detail** (`/spine-framework/admin/runtime/:entity/new` and `/runtime/:entity/:id`) is the generic create/view/edit form for one runtime record. It renders sections and fields from the record's stamped `design_schema.views.default_detail` — so any type with a detail view gets a working record page without a custom UI.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Header** — record title (`design_schema.display_field`, falling back through `full_name` → `display_name` → `name` → `title` → `slug` → `email` → "Untitled"), plus Edit / Save / Cancel / Delete buttons. `?edit=true` toggles edit mode; `new` starts in edit mode.
21
+ - **Sections** — `views.default_detail.sections[*]`: each section's `fields` render as form controls per `display_type` (`input`, `textarea`, `select`, `checkbox`, `date_picker`, `json`-as-textarea, ...).
22
+ - **Field values** — `system: true` fields read `record.<field>`; everything else reads `record.data.<field>` and is written back under `data`.
23
+ - **Field permissions** — `fieldPermissions` from the record response gate which inputs are editable (field-level `read`/`write` from `fields.*.permissions` over the record grant).
24
+ - **Delete** — confirm → `admin-data` remove → back to the entity list.
25
+
26
+ **Downstream effects**
27
+
28
+ - All writes go through `/api/admin-data` (`create`/`update`/`remove`): type stamping, `validation_schema` checks, `record_permissions`, and create/update triggers all apply — the same path custom apps must use.
29
+ - Creating a record stamps the type's current `design_schema` onto it — the detail page then renders from that stamped copy (`record.design_schema || record.type.design_schema`).
30
+ - Default `type_slug` per entity on create: `accounts→account`, `people→person`, `items→item`, `threads→thread`, `messages→message`, `links→link`, `attachments→attachment`, `watchers→watcher`, `item_progress→item_progress` — a `typeSlug` route param overrides it.
31
+
32
+ ## Minimal configuration
33
+
34
+ Define `views.default_detail` on the type:
35
+
36
+ ```json
37
+ "default_detail": {
38
+ "type": "detail", "label": "Ticket",
39
+ "sections": [
40
+ { "title": "Core", "fields": {
41
+ "title": { "display_type": "input" },
42
+ "status": { "display_type": "select" }
43
+ } }
44
+ ]
45
+ }
46
+ ```
47
+
48
+ ## Full configuration
49
+
50
+ Split fields across multiple named sections, choose richer `display_type` controls (`textarea`, `select`, `multiselect`, `date_picker`, `checkbox`, `color_picker`, `file_upload`, `range_slider`), and set `display_field` on the schema so the header shows a meaningful title. Field-level `permissions` on `design_schema.fields` restrict which roles can write a field even when the record grant allows updates.
51
+
52
+ ## Example
53
+
54
+ `/spine-framework/admin/runtime/items/<id>?edit=true` renders the ticket: a "Core" section with Title (input), Status (select), Priority (select), and a "Notes" section with a `json` field shown as a monospaced textarea. Save → `admin-data` validates against `validation_schema` and fires `item_updated` triggers.
55
+
56
+ ## Core files
57
+
58
+ - `.framework/src/components/runtime/DataDetailPage.tsx` — this page
59
+ - `.framework/src/components/runtime/SchemaDetailForm.tsx` — renders sections/fields from the schema
60
+ - `.framework/src/components/runtime/DataDetailHeader.tsx` — title + action buttons
61
+ - `.framework/src/hooks/useEntityRecord.ts` — record fetch (`design_schema` fallback), save/delete via `admin-data`
62
+ - `.framework/functions/admin-data.ts` — get/create/update/remove backend
63
+ - `.framework/src/apps/admin/index.tsx` — `instructionKeyFor`: `runtime/:entity/...` (≥4 segments) → `runtime-detail`
64
+
65
+ ## Agent notes
66
+
67
+ - Error card "Failed to load record or schema" = the record has no stamped `design_schema` or the type's `default_detail` view is missing — fix the type's schema, not this page.
68
+ - `system: true` fields (e.g. `title`) are top-level record columns; a field defined in `design_schema.fields` but absent from a section won't render — the form only shows what `default_detail` declares.
69
+ - A record stamped under an old schema renders the old view — re-save it to re-stamp the current schema.
70
+ - `new` with no `typeSlug` uses the default type slug map above; creating a different type's record needs the `typeSlug` route param or a different entry point.
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: Runtime Records (list)
3
+ audience: both
4
+ kind: ui
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/components/runtime/DataListPage.tsx
9
+ - .framework/src/hooks/useListSchema.ts
10
+ - .framework/src/hooks/useEntityList.ts
11
+ - .framework/functions/admin-data.ts
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ **Runtime list** (`/spine-framework/admin/runtime/:entity`) is the generic, schema-driven list page for any runtime entity table — `items`, `threads`, `messages`, `links`, `attachments`, `watchers`, `people`, `accounts`, `item_progress`. It renders whatever the entity's type `design_schema.views.default_list` declares, so every type gets a working admin list for free.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Header** — entity name, description, and a New button → `runtime/:entity/new`.
21
+ - **Stats cards** — driven by `views.default_list.stats` (empty when undeclared).
22
+ - **Filters** — driven by `views.default_list.filters`; values go into the `admin-data` list query.
23
+ - **Columns** — `views.default_list.fields`: label from `design_schema.fields[key].label`, sortable unless `sortable: false`, rendered per `display_type` (`text`, `badge`, `timestamp`, `currency`, ...).
24
+ - **Sort** — defaults to `views.default_list.default_sort` (fallback `created_at` desc); sent as `sort_field`/`sort_direction`.
25
+ - **Rows** — `record.data.<field>` values for non-system fields, `record.<field>` for `system: true` fields.
26
+
27
+ **How the schema is resolved** (`useListSchema`, in order)
28
+
29
+ 1. Fetch one sample record (`admin-data?action=list&entity=<entity>&limit=1`) and use its **stamped** `design_schema.views.default_list`.
30
+ 2. No stamped schema → look up `/api/types?action=list&kind=<mapped>`; used only when **exactly one** type matches the entity's kind.
31
+ 3. Otherwise a fallback view (just `id` + `created_at` columns).
32
+
33
+ **Downstream effects**
34
+
35
+ - All data flows through `/api/admin-data` — the same permission, validation, and trigger path as any other client.
36
+ - Multi-type entities (e.g. `items` with `ticket`, `threshold_alert`, `kb-article`) resolve schema from the *sample record* — the columns shown are whichever type the most recent matching record carries, and `type_slug` filtering is available via the route's `typeSlug` param.
37
+ - A type with no `default_list` view (or a fresh table with no records and >1 candidate type) renders the fallback two-column view — fix the type's `design_schema`, not this page.
38
+
39
+ ## Minimal configuration
40
+
41
+ Define `views.default_list` on the type:
42
+
43
+ ```json
44
+ "default_list": {
45
+ "type": "list", "display": "table", "label": "Tickets",
46
+ "fields": {
47
+ "title": { "sortable": true, "display_type": "text" },
48
+ "status": { "sortable": true, "display_type": "badge" },
49
+ "created_at": { "sortable": true, "display_type": "timestamp" }
50
+ },
51
+ "default_sort": { "field": "created_at", "direction": "desc" }
52
+ }
53
+ ```
54
+
55
+ ## Full configuration
56
+
57
+ Add `stats` and `filters` arrays to the view for stat cards and filter controls, choose `display_type` per column to control rendering, and set a different `default_sort` (e.g. `priority asc`). Valid `entity` path segments are the `admin-data` whitelist: `accounts`, `people`, `items`, `threads`, `messages`, `links`, `attachments`, `watchers`, `item_progress`, plus config entities (`types`, `link_types`, `ai_agents`, `prompt_configs`, ...).
58
+
59
+ ## Example
60
+
61
+ `/spine-framework/admin/runtime/items` shows the ticket list: columns Title/Status/Priority/Created from `default_list.fields`, status rendered as a badge, sorted by `created_at` desc; the New button lands on `runtime/items/new`.
62
+
63
+ ## Core files
64
+
65
+ - `.framework/src/components/runtime/DataListPage.tsx` — this page
66
+ - `.framework/src/components/runtime/DataTable.tsx` / `DataFilters.tsx` / `DataStats.tsx` / `DataHeader.tsx` — subcomponents
67
+ - `.framework/src/hooks/useListSchema.ts` — stamped-schema → type → fallback resolution
68
+ - `.framework/src/hooks/useEntityList.ts` — the `admin-data` list query (filters, sort)
69
+ - `.framework/functions/admin-data.ts` — list/get backend (`VALID_ENTITIES` whitelist)
70
+ - `.framework/src/apps/admin/index.tsx` — `instructionKeyFor`: `runtime/:entity` → `runtime-list`
71
+
72
+ ## Agent notes
73
+
74
+ - The schema comes from a **record's stamped** `design_schema` first — old records stamped before a schema change keep old views; the list can flip shape depending on which record it samples.
75
+ - If the page shows only `id`/`created_at`, the type's `default_list` view is missing or ambiguous — check the type, then check that the entity has records.
76
+ - Non-system fields live under `record.data` — a column for a `system: true` field reads `record.<field>`; mixing these up shows blank cells.
77
+ - Filter/sort state is client-side over the fetched page — this is a browsing surface, not a reporting tool.
@@ -0,0 +1,67 @@
1
+ ---
2
+ title: Security
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/pages/admin/SecurityPage.tsx
9
+ - .framework/functions/security.ts
10
+ - .framework/functions/_shared/security.ts
11
+ - .framework/migrations/049_security_blocklist.sql
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ The **Security** page (`/spine-framework/admin/security`) is the system-admin console for abusive actors: a report of flagged users/accounts/IPs built from security signal logs, plus the platform blocklist. Its actions apply a three-layer ban — internal `security_blocklist` rows, a Supabase Auth ban, and a Netlify edge IP-deny rule.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Flagged actors table** — Actor (email or "(no person)"), Account, IP / Country, Signals (count + kind badges e.g. `rate_limit`, `prompt_injection`, `blocked_registration`), Last seen, Status (`active`/person status or `banned`).
21
+ - **Row checkboxes + Add to Blacklist** — issues one `ban_user` call per selected row and prints per-layer results (`blocklist`, `auth`, `edge`) inline.
22
+ - **Unban** — per-row on banned actors with a `person_id`; revokes their blocklist rows and lifts the auth ban and edge rule.
23
+ - **Active blocklist table** — Type (`ip`/`email`/`person`/`account`), Value, Severity, Reason, Created, with per-row Revoke (`blocklist_revoke` sets `revoked_at`).
24
+ - All calls go to `/api/security?action=…` (POST): `flagged_accounts`, `blocklist`, `ban_user`, `unban_user`, `blocklist_add`, `blocklist_revoke`.
25
+
26
+ **Downstream effects**
27
+
28
+ - `ban_user` accepts `person_id`, `email`, or `ip` — anonymous-signal rows (no person) still ban by account/IP. Layer 1 (internal blocklist) is authoritative; the Supabase Auth and Netlify edge layers report `applied`/`skipped`/`error` per call — the edge layer degrades gracefully when Netlify API credentials aren't configured.
29
+ - Blocked principals are rejected at request time by `isBlockedRequest`/`isBlockedIp` in `_shared/security.ts` (checked in middleware and at registration).
30
+ - Every action emits `security.*` audit events into `logs` — the same table the flagged report is aggregated from.
31
+ - Every action requires a `system_admin` principal.
32
+
33
+ ## Minimal configuration
34
+
35
+ Nothing to configure — the report fills itself from security signals (rate-limit violations, prompt-injection detections, blocked registrations). To act: select flagged rows → Add to Blacklist.
36
+
37
+ ## Full configuration
38
+
39
+ Direct API calls for finer control:
40
+
41
+ ```
42
+ POST /api/security?action=blocklist_add { type: "ip", value: "203.0.113.7", severity: "high", reason: "credential stuffing", expires_at: null }
43
+ POST /api/security?action=blocklist_revoke { id: "<row uuid>" }
44
+ POST /api/security?action=ban_user { email: "abuser@example.com", ip: "203.0.113.7", reason: "..." }
45
+ POST /api/security?action=unban_user { person_id: "<uuid>" }
46
+ ```
47
+
48
+ `expires_at` may be set for temporary bans; entries with `revoked_at` are hidden from the Active blocklist.
49
+
50
+ ## Example
51
+
52
+ A burst of `rate_limit` signals from one IP appears in Flagged actors. Select the row, Add to Blacklist — the result line shows `banned (auth:applied edge:applied)`; subsequent requests from that IP 403 at the edge and at `isBlockedIp`. If it was a false positive, Unban restores the person and revokes the rows.
53
+
54
+ ## Core files
55
+
56
+ - `.framework/src/pages/admin/SecurityPage.tsx` — this page
57
+ - `.framework/functions/security.ts` — flagged report, three-layer ban/unban, blocklist CRUD, Netlify edge sync
58
+ - `.framework/functions/_shared/security.ts` — `isBlockedRequest`/`isBlockedIp` enforcement, `emitSecurityEvent`, injection patterns
59
+ - `.framework/migrations/049_security_blocklist.sql` — `security_blocklist` schema
60
+ - Tables: `public.security_blocklist`, `public.logs` (signal source)
61
+
62
+ ## Agent notes
63
+
64
+ - The Netlify edge layer needs Netlify API credentials in env — without them `edge: skipped` is normal, not a failure; the internal blocklist still enforces.
65
+ - Banning by `email`/`ip` does not stop a determined actor from rotating — treat the edge deny as a speed bump and watch for re-registration signals.
66
+ - Unban requires a `person_id` — rows banned only by IP/email must be lifted via `blocklist_revoke` on the blocklist entries.
67
+ - This page is marked experimental and is system-admin only; all writes are audited.
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Add a pipeline
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/pipelines.ts
9
+ - .framework/functions/_shared/pipeline-runner.ts
10
+ - .framework/migrations/053_builtin_action_seeds.sql
11
+ - .framework/functions/system-cron.ts
12
+ ---
13
+
14
+ # Add a pipeline
15
+
16
+ ## Goal
17
+
18
+ Create a `public.pipelines` row: an ordered `stages` array the runner
19
+ executes when something (a trigger, a timer, or a manual run) invokes it.
20
+
21
+ ## Steps
22
+
23
+ 1. **Choose the `trigger_type`.** `manual` (run by button/API), `event`
24
+ (fired by a trigger), `scheduled` (fired by a timer), or `webhook`. This
25
+ is metadata for callers — what actually runs a pipeline is whatever
26
+ invokes `runPipeline`, so the type should match how you plan to wire it
27
+ ([Automation](../concepts/automation.md)).
28
+
29
+ 2. **Draft the `stages` array.** Each stage needs a `stage_type` matching an
30
+ active slug in `public.actions` plus a `config`. Built-in stage types
31
+ (seeded by `.framework/migrations/053_builtin_action_seeds.sql`):
32
+
33
+ `update_item`, `create_record`, `http_request`, `send_notification`,
34
+ `send_email`, `run_pipeline`, `search_knowledge`, `query_items`,
35
+ `agent_inference`, `generate_embeddings`, `revoke_embeddings`,
36
+ `kb_ingest`, `kb_revoke`, `create_interaction`
37
+ (`.framework/functions/_shared/pipeline-runner.ts`).
38
+
39
+ ```json
40
+ [
41
+ {
42
+ "stage_type": "query_items",
43
+ "config": { "entity": "items", "type_slug": "ticket",
44
+ "filters": { "status": "open" } }
45
+ },
46
+ {
47
+ "stage_type": "http_request",
48
+ "config": { "url": "https://example.com/hook", "method": "POST",
49
+ "body": { "title": "{{record.data.title}}" } }
50
+ }
51
+ ]
52
+ ```
53
+
54
+ - Stage config merges `action.config` (defaults on the actions row) under
55
+ `stage.config`, plus injected `_pipelineId` / `_executionId` /
56
+ `_stageIndex` / `_triggerData`
57
+ (`.framework/functions/_shared/pipeline-runner.ts`).
58
+ - `{{trigger.*}}` resolves into the trigger-data envelope; `{{record.*}}`
59
+ resolves into `triggerData.entity.data` — the record that fired the
60
+ run (`.framework/functions/_shared/pipeline-runner.ts`).
61
+ - `continue_on_error: true` on a stage lets the run continue after a
62
+ failure; otherwise a failed stage aborts the whole run
63
+ (`.framework/functions/_shared/pipeline-runner.ts`).
64
+
65
+ 3. **Create the pipeline.** Admin UI → **Configs → Pipelines → New**
66
+ (`configs/pipelines/new`), or `POST /api/pipelines` with
67
+ `{ name, trigger_type, stages }` — all three required
68
+ (`.framework/functions/pipelines.ts`). Set `is_active: true`;
69
+ `runPipeline` refuses inactive pipelines.
70
+
71
+ 4. **Wire the entry point.** A pipeline with `trigger_type: "event"` needs a
72
+ trigger ([Add a trigger](./add-a-trigger.md)); `scheduled` needs a timer
73
+ ([Add a timer](./add-a-timer.md)); `manual` runs on demand. Changing
74
+ `trigger_type` later does not rewire existing triggers or timers — relink
75
+ them separately.
76
+
77
+ ## Verify
78
+
79
+ - Run it directly: `npx spine-framework pipelines run <id> --data '{"x":1}'`
80
+ — see [CLI reference](../reference/cli.md).
81
+ - Check **Observability → Executions** (`observability/executions`) or
82
+ `pipelines executions <id>` for a `completed` row with per-stage results.
83
+ - A missing `stage_type` fails fast with
84
+ `Action not found for stage type: <type>` — the runner looks the slug up
85
+ in `public.actions` with `is_active = true` before executing
86
+ (`.framework/functions/_shared/pipeline-runner.ts`).
87
+
88
+ ## Common mistakes
89
+
90
+ - **Nonexistent stage_type.** Seed validation and the runner both reject
91
+ unknown slugs; stick to the built-ins or seed your own `actions` row
92
+ (see [Package and install an app](./package-and-install-an-app.md)).
93
+ - **Expecting stage outputs to chain into the next stage.** Each stage gets
94
+ the merged `action.config` + `stage.config` + `_triggerData`; previous
95
+ stage outputs are collected into the execution result, not injected into
96
+ later configs. Templates only see `{{trigger.*}}` and `{{record.*}}`.
97
+ - **`scheduled` with no timer, or `event` with no trigger.** The pipeline
98
+ is then unreachable — `get_due_timers` only returns timers with
99
+ `pipeline_id` set, and triggers dispatch by `pipeline_id`.
100
+ - **Editing `stages` while runs are queued.** The runner reads the pipeline
101
+ row at execution time; a queued execution picks up whatever `stages`
102
+ exist when it starts, not when it was queued.
103
+
104
+ ## Reference
105
+
106
+ - [pipelines entity page](../pipelines.md) ·
107
+ [Automation](../concepts/automation.md)
108
+ - [pipeline-executions reference](../reference/functions/pipeline-executions.md) ·
109
+ [pipeline-runner-background reference](../reference/functions/pipeline-runner-background.md)
110
+ - [Add a trigger](./add-a-trigger.md) · [Add a timer](./add-a-timer.md)
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: Add a role
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/roles.ts
9
+ - .framework/functions/_shared/permissions.ts
10
+ - .framework/migrations/001_core_schema.sql
11
+ - .framework/migrations/003_core_system_data.sql
12
+ - .framework/migrations/009_roles_slug_unique.sql
13
+ ---
14
+
15
+ # Add a role
16
+
17
+ ## Goal
18
+
19
+ Create a `public.roles` row whose slug you can reference from
20
+ `record_permissions` in type design schemas, `min_role`/`required_roles` on
21
+ apps, and `role:<slug>` notification recipients.
22
+
23
+ ## Prerequisites
24
+
25
+ - A `system_admin` login — role create/update/remove requires it
26
+ (`.framework/functions/roles.ts`).
27
+
28
+ ## Steps
29
+
30
+ 1. **Pick the slug.** Lowercase snake_case (`support_agent`). It is matched
31
+ verbatim as a key inside every type's `record_permissions` and is
32
+ effectively immutable — the admin UI disables slug editing after creation.
33
+ Uniqueness is enforced at both the API layer (duplicate-slug check before
34
+ insert) and the database (`UNIQUE(slug)`,
35
+ `.framework/migrations/009_roles_slug_unique.sql`).
36
+
37
+ 2. **Decide the permissions payload.** `roles.permissions` is a JSON
38
+ document on the role row (`permissions jsonb` on the table,
39
+ `.framework/migrations/001_core_schema.sql`). Seeded roles carry
40
+ `'["*"]'` — a wildcard grant. Per-type grants are expressed on the
41
+ **type** side (`design_schema.record_permissions` keys are role slugs),
42
+ which is what `admin-data` evaluates per request — see
43
+ [Permission model](../concepts/permissions.md).
44
+
45
+ 3. **Create the role.** Admin UI → **Configs → Roles → New**
46
+ (`configs/roles/new`), or `POST /api/roles` with
47
+ `{ slug, name, permissions, is_system }` — `slug` and `name` are required
48
+ (`.framework/functions/roles.ts`). The `system_admin` slug itself
49
+ is protected: only a system admin may create it
50
+ (`.framework/functions/roles.ts`).
51
+
52
+ 4. **Reference the role.** Add its slug to each type's
53
+ `design_schema.record_permissions`, to `apps.min_role` /
54
+ `apps.required_roles`, and to `send_notification` stage recipients as
55
+ `role:<slug>` (resolved via `people.role_id → roles.slug` — see
56
+ [Automation](../concepts/automation.md)).
57
+
58
+ 5. **Assign it to people.** A person's `role_id` joins to `roles`; the
59
+ permission engine resolves `people.role:role_id(slug)` and evaluates
60
+ `record_permissions` against that slug
61
+ (`.framework/functions/_shared/permissions.ts`).
62
+
63
+ ## Verify
64
+
65
+ - `GET /api/roles?action=list` returns the row; `slug` is unique and active.
66
+ - Create or update a type whose `record_permissions` includes the new slug,
67
+ then perform an `admin-data` call as a person holding the role — the
68
+ action is permitted.
69
+ - `system_admin` users see all roles regardless of `is_active` filters.
70
+
71
+ ## Common mistakes
72
+
73
+ - **`system-admin` vs `system_admin`.** All seeded slugs are snake_case:
74
+ `system_admin`, `member`, `member_admin`, `engineering`,
75
+ `engineering_admin`, `product`, `docs`, `qa`, `devops`, `management`,
76
+ `sales`, `csm`, `tam`, `support`, `support_admin`
77
+ (`.framework/migrations/003_core_system_data.sql`,
78
+ `.framework/migrations/073_seed_member_roles.sql`).
79
+ Using hyphens in `record_permissions` grants nothing.
80
+ - **Expecting `roles.permissions` to gate `admin-data`.** Record-level
81
+ grants live on the type's `record_permissions`; the role row's
82
+ `permissions` JSON is not consulted per-request for record CRUD.
83
+ - **Renaming a slug later.** Every type's `record_permissions` and every
84
+ app's `min_role`/`required_roles` reference the slug verbatim — a rename
85
+ orphans the grants. Create a new role and migrate people instead.
86
+ - **Creating `system_admin` via API as a non-admin** — explicitly blocked.
87
+ - **Deactivating a role with live assignees** — the slug stops resolving to
88
+ a grant for those people; check `people.role_id` usage first.
89
+
90
+ ## Reference
91
+
92
+ - [roles entity page](../roles.md) ·
93
+ [roles function reference](../reference/functions/roles.md)
94
+ - [Permission model](../concepts/permissions.md) ·
95
+ [Add a type](./add-a-type.md)
96
+ - [Database reference](../reference/database.md) for the `roles` columns
@@ -0,0 +1,114 @@
1
+ ---
2
+ title: Add a timer
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/timers.ts
9
+ - .framework/functions/system-cron.ts
10
+ - .framework/functions/trigger-scheduler.ts
11
+ - .framework/migrations/020_schema_reconciliation.sql
12
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
13
+ - .framework/src/pages/admin/TimerDetailPage.tsx
14
+ ---
15
+
16
+ # Add a timer
17
+
18
+ ## Goal
19
+
20
+ Create a `public.timers` row that runs a pipeline every
21
+ `config.interval_minutes` via the system cron sweep.
22
+
23
+ ## Prerequisites
24
+
25
+ - A pipeline to point at ([Add a pipeline](./add-a-pipeline.md)) —
26
+ `get_due_timers` ignores timers with `pipeline_id` NULL.
27
+
28
+ ## Steps
29
+
30
+ 1. **Create the timer.** Admin UI → **Configs → Timers → New**
31
+ (`configs/timers/new`), or `POST /api/timers` — `name` and `timer_type`
32
+ required (`.framework/functions/timers.ts`). Set
33
+ `pipeline_id` and `config.interval_minutes`:
34
+
35
+ ```json
36
+ {
37
+ "name": "Hourly sync",
38
+ "timer_type": "interval",
39
+ "pipeline_id": "<pipeline-uuid>",
40
+ "config": { "interval_minutes": 60 }
41
+ }
42
+ ```
43
+
44
+ `timer_type` is a label (`interval` | `cron` | `once` in the admin
45
+ detail page, `.framework/src/pages/admin/TimerDetailPage.tsx`) — the
46
+ only field that actually reschedules is `config.interval_minutes`
47
+ (see below).
48
+
49
+ 2. **`next_execution` is armed for you.** On create or toggle-on, the API
50
+ computes `next_execution` from `config.interval_minutes` (or
51
+ `config.interval_seconds`, seconds ÷ 60); `once` timers arm from an
52
+ ISO `config.run_at`/`config.schedule`. You may also pass
53
+ `next_execution` explicitly. If `next_execution` ever ends up NULL on
54
+ an active timer it cannot fire — `get_due_timers` requires
55
+ `next_execution IS NOT NULL` — so check it first when a timer never
56
+ runs.
57
+
58
+ 3. **Understand the clock.** `trigger-scheduler` is the Netlify scheduled
59
+ function (`* * * * *` in `netlify.toml`); each minute it invokes the
60
+ `system-cron` handler under the system principal, which calls
61
+ `get_due_timers(now())`, then `runPipeline(timer.pipeline_id, …)` with a
62
+ `timer:<id>` machine principal, then `update_timer_after_run`
63
+ (`.framework/functions/system-cron.ts`).
64
+
65
+ **`netlify dev` does not run scheduled functions.** Clockwork only exists
66
+ on deployed Netlify — locally, `trigger-scheduler` is loaded but never
67
+ invoked on schedule. To emulate a tick, invoke it manually:
68
+
69
+ ```bash
70
+ curl -X POST http://localhost:8888/.netlify/functions/trigger-scheduler \
71
+ -H 'x-nf-event: schedule' \
72
+ -d "{\"next_run\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}"
73
+ # or: npx netlify functions:invoke trigger-scheduler
74
+ ```
75
+
76
+ 4. **Rescheduling is automatic — or never.** `update_timer_after_run`
77
+ stamps `last_execution`, increments `execution_count` /
78
+ `success_count` / `failure_count`, and sets
79
+ `next_execution = now() + interval_minutes` when
80
+ `config.interval_minutes` is a positive number — otherwise `NULL`, so
81
+ the timer fires once and stops
82
+ (`.framework/migrations/068_cron_schedule_timer_rpcs.sql`).
83
+
84
+ ## Verify
85
+
86
+ - `SELECT id, next_execution, last_execution, execution_count FROM timers`
87
+ shows `next_execution` in the future, then advancing each tick.
88
+ - **Observability → Executions** shows pipeline runs with a
89
+ `timer:<id>` principal.
90
+ - `timer.execution_count` increments; `success_count` / `failure_count`
91
+ reflect outcomes.
92
+
93
+ ## Common mistakes
94
+
95
+ - **`interval_seconds` vs `interval_minutes`.** The admin detail page seeds
96
+ `config` with `{ "interval_seconds": 3600 }`, but `update_timer_after_run`
97
+ only reads `config.interval_minutes` — a timer saved with the default
98
+ config fires once (if `next_execution` was set) and never reschedules.
99
+ - **No `next_execution`.** The API can't set it; timers created through
100
+ `/api/timers` or the admin UI sit dormant until the column is seeded.
101
+ - **`toggle` is blind.** `?action=toggle` flips `is_active` to its negation
102
+ with no target-state argument — read the row first.
103
+ - **Cron expressions do nothing.** There is no cron-expression evaluator;
104
+ `timer_type: "cron"` is only a label — scheduling is interval arithmetic
105
+ on `next_execution`.
106
+ - **Timer with no `pipeline_id`** — filtered out of `get_due_timers`.
107
+
108
+ ## Reference
109
+
110
+ - [timers entity page](../timers.md) ·
111
+ [timers function reference](../reference/functions/timers.md) ·
112
+ [system-cron reference](../reference/functions/system-cron.md) ·
113
+ [trigger-scheduler reference](../reference/functions/trigger-scheduler.md)
114
+ - [Automation](../concepts/automation.md) — *Timers and the scheduler*