spine-framework 0.4.4 → 0.5.0-rc.5

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 (305) 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/install-app.ts +183 -13
  8. package/.framework/cli/commands/migrate.ts +36 -30
  9. package/.framework/cli/commands/migrations.ts +16 -56
  10. package/.framework/cli/commands/upgrade.ts +35 -0
  11. package/.framework/cli/commands/validate-app.ts +127 -1
  12. package/.framework/cli/commands/validate-schema.ts +147 -0
  13. package/.framework/cli/context.ts +223 -110
  14. package/.framework/cli/index.ts +6 -0
  15. package/.framework/cli/lib/project-files.ts +241 -1
  16. package/.framework/functions/_shared/app-manifest.ts +2 -0
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/logging.ts +562 -224
  19. package/.framework/functions/_shared/middleware.ts +47 -44
  20. package/.framework/functions/_shared/permissions.ts +4 -0
  21. package/.framework/functions/admin-data.ts +51 -28
  22. package/.framework/functions/ai-agents.ts +10 -6
  23. package/.framework/functions/api-keys.ts +301 -122
  24. package/.framework/functions/apps.ts +63 -29
  25. package/.framework/functions/cil-ingest.ts +48 -15
  26. package/.framework/functions/debug-auth.ts +14 -8
  27. package/.framework/functions/deliveries.ts +39 -12
  28. package/.framework/functions/embeddings.ts +149 -78
  29. package/.framework/functions/integration-outbound.ts +67 -27
  30. package/.framework/functions/integrations.ts +254 -127
  31. package/.framework/functions/logs.ts +273 -133
  32. package/.framework/functions/observability.ts +278 -148
  33. package/.framework/functions/pipeline-executions.ts +1 -1
  34. package/.framework/functions/pipelines.ts +14 -9
  35. package/.framework/functions/prompt-configs.ts +7 -4
  36. package/.framework/functions/roles.ts +8 -5
  37. package/.framework/functions/security.ts +30 -17
  38. package/.framework/functions/system-cron.ts +240 -127
  39. package/.framework/functions/tests.ts +7 -6
  40. package/.framework/functions/timers.ts +310 -107
  41. package/.framework/functions/triggers.ts +28 -13
  42. package/.framework/functions/types.ts +13 -10
  43. package/.framework/migrations/069_log_stats_window.sql +54 -0
  44. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  45. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  46. package/.framework/migrations/072_slug_autofill.sql +77 -0
  47. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  48. package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
  49. package/.framework/migrations/075_security_definer_grants.sql +157 -0
  50. package/.framework/src/apps/admin/index.tsx +1141 -775
  51. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  52. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  53. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  54. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  55. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  56. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  57. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  58. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  59. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  60. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  61. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  62. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  63. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  64. package/.framework/src/components/ui/sheet.tsx +24 -26
  65. package/.framework/src/components/ui/textarea.tsx +74 -48
  66. package/.framework/src/contexts/AppContext.tsx +5 -12
  67. package/.framework/src/contexts/AuthContext.tsx +17 -9
  68. package/.framework/src/hooks/useApps.ts +18 -22
  69. package/.framework/src/hooks/useEntityList.ts +12 -9
  70. package/.framework/src/hooks/useListSchema.ts +108 -58
  71. package/.framework/src/instructions/README.md +74 -0
  72. package/.framework/src/instructions/accounts.md +97 -0
  73. package/.framework/src/instructions/ai-agents.md +69 -0
  74. package/.framework/src/instructions/alerts.md +80 -0
  75. package/.framework/src/instructions/api-keys.md +87 -0
  76. package/.framework/src/instructions/apps.md +77 -0
  77. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  78. package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
  79. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  80. package/.framework/src/instructions/concepts/automation.md +186 -0
  81. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  82. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  83. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  84. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  85. package/.framework/src/instructions/concepts/observability.md +116 -0
  86. package/.framework/src/instructions/concepts/permissions.md +150 -0
  87. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  88. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  89. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  90. package/.framework/src/instructions/deliveries.md +59 -0
  91. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  92. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  93. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  94. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  95. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  96. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  97. package/.framework/src/instructions/integrations.md +96 -0
  98. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  99. package/.framework/src/instructions/internal/docs-contract.md +158 -0
  100. package/.framework/src/instructions/invites.md +60 -0
  101. package/.framework/src/instructions/kb.md +80 -0
  102. package/.framework/src/instructions/logs.md +77 -0
  103. package/.framework/src/instructions/notifications.md +77 -0
  104. package/.framework/src/instructions/observability.md +61 -0
  105. package/.framework/src/instructions/operations/deployment.md +115 -0
  106. package/.framework/src/instructions/operations/migrations.md +125 -0
  107. package/.framework/src/instructions/operations/security-checklist.md +170 -0
  108. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  109. package/.framework/src/instructions/operations/upgrade.md +116 -0
  110. package/.framework/src/instructions/people.md +91 -0
  111. package/.framework/src/instructions/pipeline-executions.md +62 -0
  112. package/.framework/src/instructions/pipelines.md +78 -0
  113. package/.framework/src/instructions/prompts.md +95 -0
  114. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  115. package/.framework/src/instructions/reference/api/auth.md +113 -0
  116. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  117. package/.framework/src/instructions/reference/app-manifest.md +184 -0
  118. package/.framework/src/instructions/reference/cli.md +124 -0
  119. package/.framework/src/instructions/reference/database.md +132 -0
  120. package/.framework/src/instructions/reference/design-schema.md +280 -0
  121. package/.framework/src/instructions/reference/env-vars.md +133 -0
  122. package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
  123. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  124. package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
  125. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  126. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  127. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  128. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  129. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  130. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  131. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  132. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  133. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  134. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  135. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  136. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  137. package/.framework/src/instructions/reference/functions/index.md +103 -0
  138. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  139. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  140. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  141. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  142. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  143. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  144. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  145. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  146. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  147. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  148. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  149. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  150. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  151. package/.framework/src/instructions/reference/functions/security.md +140 -0
  152. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  153. package/.framework/src/instructions/reference/functions/system.md +106 -0
  154. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  155. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  156. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  157. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  158. package/.framework/src/instructions/reference/functions/types.md +146 -0
  159. package/.framework/src/instructions/reference/seeds.md +126 -0
  160. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  161. package/.framework/src/instructions/reference/spine-config.md +114 -0
  162. package/.framework/src/instructions/roles.md +80 -0
  163. package/.framework/src/instructions/runtime-collaboration.md +106 -0
  164. package/.framework/src/instructions/runtime-detail.md +70 -0
  165. package/.framework/src/instructions/runtime-list.md +77 -0
  166. package/.framework/src/instructions/security.md +67 -0
  167. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  168. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  169. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  170. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  171. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  172. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  173. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  174. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  175. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  176. package/.framework/src/instructions/tasks/document-your-app.md +117 -0
  177. package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
  178. package/.framework/src/instructions/tasks/register-an-app.md +137 -0
  179. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  180. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  181. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  182. package/.framework/src/instructions/testing.md +59 -0
  183. package/.framework/src/instructions/timers.md +84 -0
  184. package/.framework/src/instructions/triggers.md +69 -0
  185. package/.framework/src/instructions/types.md +93 -0
  186. package/.framework/src/lib/app-mount.ts +88 -0
  187. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  188. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  189. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  190. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  191. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  192. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  193. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  194. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  195. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  196. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  197. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  198. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  199. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  200. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  201. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  202. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  203. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  204. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  205. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  206. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  207. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  208. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  209. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  210. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  211. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  212. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  213. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  214. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  215. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  216. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  217. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  218. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  219. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  220. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  221. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  222. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  223. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  224. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  225. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  226. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  227. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  228. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  229. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  230. package/.framework/src/pages/auth/LoginPage.tsx +3 -2
  231. package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
  232. package/.framework/templates/agent/AGENTS.md +167 -0
  233. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  234. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  235. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  236. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  237. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
  238. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  239. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
  240. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
  241. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
  242. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  243. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  244. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  245. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  246. package/.framework/templates/project.json +4 -1
  247. package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
  248. package/.framework/tests/unit/agent-files.test.ts +355 -0
  249. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  250. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  251. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  252. package/.framework/tests/unit/app-mount.test.ts +80 -0
  253. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  254. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  255. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  256. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  257. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  258. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  259. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  260. package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
  261. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  262. package/.framework/tests/unit/system-cron.test.ts +240 -0
  263. package/.framework/tests/unit/timers.test.ts +159 -0
  264. package/README.md +125 -166
  265. package/config/tsconfig.cli.json +2 -2
  266. package/package.json +15 -4
  267. package/templates/project/package-lock.json +2 -2
  268. package/.framework/docs/admin-routes.md +0 -76
  269. package/.framework/docs/api-docs-progress.md +0 -38
  270. package/.framework/docs/api-governance.md +0 -146
  271. package/.framework/docs/api-testing-results.md +0 -212
  272. package/.framework/docs/assembly-launch-guide.md +0 -138
  273. package/.framework/docs/audit-results.md +0 -590
  274. package/.framework/docs/db-api-inventory.md +0 -95
  275. package/.framework/docs/gap-remediation-backlog.md +0 -103
  276. package/.framework/docs/ui-adoption-verification.md +0 -111
  277. package/.framework/docs/ui-api-coverage.md +0 -84
  278. package/.framework/docs/v2-compatibility-audit.md +0 -228
  279. package/.framework/mcp/index.ts +0 -133
  280. package/.framework/mcp/load-env.ts +0 -60
  281. package/.framework/mcp/shared/app-manifest.ts +0 -275
  282. package/.framework/mcp/shared/db.ts +0 -477
  283. package/.framework/mcp/shared/kb.ts +0 -118
  284. package/.framework/mcp/tools/architect.ts +0 -431
  285. package/.framework/mcp/tools/cil/context.ts +0 -345
  286. package/.framework/mcp/tools/cil/graph.ts +0 -242
  287. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  288. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  289. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  290. package/.framework/mcp/tools/cil/project.ts +0 -791
  291. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  292. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  293. package/.framework/mcp/tools/cil/search.ts +0 -97
  294. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  295. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  296. package/.framework/mcp/tools/kb/delete.ts +0 -139
  297. package/.framework/mcp/tools/kb/index.ts +0 -52
  298. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  299. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  300. package/.framework/mcp/tools/kb/publish.ts +0 -100
  301. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  302. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  303. package/.framework/mcp/tools/kb/search.ts +0 -101
  304. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  305. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,152 @@
1
+ ---
2
+ title: Design Schema
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/src/types/types.ts
9
+ - .framework/functions/types.ts
10
+ - .framework/functions/admin-data.ts
11
+ - .framework/functions/_shared/schema-utils.ts
12
+ - .framework/functions/_shared/permissions.ts
13
+ - .framework/functions/_shared/resolve-ids.ts
14
+ - .framework/src/components/app-shell/GenericListPage.tsx
15
+ - .framework/src/components/app-shell/GenericDetailPage.tsx
16
+ - .framework/src/hooks/useListSchema.ts
17
+ - .framework/migrations/058_account_data_field_declarations.sql
18
+ - .framework/migrations/060_account_person_record_permissions.sql
19
+ ---
20
+
21
+ # Design Schema
22
+
23
+ `design_schema` is the JSON contract on a `public.types` row that declares
24
+ everything the platform needs to store, validate, permission, and render one
25
+ kind of runtime record — without code. It is the single most important
26
+ structure in Spine: get it right and permissions, validation, and the generic
27
+ UI all work for free; get it wrong or leave it out and records are created
28
+ unpermissionable and unrenderable.
29
+
30
+ The TypeScript shape is `DesignSchema` in `.framework/src/types/types.ts`:
31
+ `fields`, `views`, `record_permissions`, plus optional `scope` and
32
+ `functionality`.
33
+
34
+ ## Where it lives and how it propagates
35
+
36
+ - The **type definition** holds the canonical copy in
37
+ `public.types.design_schema` (created/updated only via
38
+ `.framework/functions/types.ts`, which requires `system_admin`).
39
+ - **Every record created through `admin-data` is stamped** with the type's
40
+ `design_schema` and `validation_schema` at insert time
41
+ (`.framework/functions/admin-data.ts`, `create`). The stamped copy on the
42
+ record — not the type row — is what the permission engine reads at request
43
+ time (`.framework/functions/_shared/permissions.ts`,
44
+ `sanitizeFirstSurfaceRecordData` / `validateFirstSurfaceUpdatePermissions`
45
+ both refuse when the record carries no `record_permissions`).
46
+ - **Editing a type does not restamp existing records.** Migrations that change
47
+ a type schema re-stamp rows explicitly — see
48
+ `.framework/migrations/058_account_data_field_declarations.sql` and
49
+ `.framework/migrations/060_account_person_record_permissions.sql` for the
50
+ pattern.
51
+ - `validation_schema` is a derived artifact: `generateValidationSchema` in
52
+ `.framework/functions/_shared/schema-utils.ts` extracts `data_type`,
53
+ `required`, `validation`, `options`, and `reference_*` from `fields` and runs
54
+ on every type create and on every update that touches `design_schema`
55
+ (`.framework/functions/types.ts`). Never hand-edit it — change
56
+ `design_schema` and let the API regenerate.
57
+
58
+ ## The sections
59
+
60
+ ### `scope`
61
+
62
+ `"account"` (default when omitted) or `"platform"`. At create time `admin-data`
63
+ reads `designSchema.scope ?? 'account'`; `platform` records are stamped with
64
+ the `spine-system` account's id instead of the caller's account. Child records
65
+ created against a parent (via `thread_id`, `target_id`, `parent_id`) also
66
+ inherit the parent's stamped scope. See
67
+ [Scope and Accounts](scope-and-accounts.md).
68
+
69
+ ### `fields`
70
+
71
+ A flat map of `snake_case` field names to `FieldDefinition`
72
+ (`.framework/src/types/types.ts`): `data_type`, `label`, `required`, plus
73
+ optional `system`, `placeholder`, `validation` (pattern, min/max, lengths,
74
+ `reference_kind`/`reference_type`, `currency_code`), `options` (required for
75
+ `select`/`multiselect`/`radio`), and `permissions` (per-role `read`/`write`
76
+ overrides — see [Permissions](permissions.md)).
77
+
78
+ The `system` flag controls storage: `system: true` fields are top-level record
79
+ columns; all others live under `record.data.<field>` — see
80
+ [Runtime Records](runtime-records.md).
81
+
82
+ Known `data_type` values (the authoritative switch is `sanitizeFieldData` in
83
+ `.framework/functions/_shared/schema-utils.ts`): `text`, `textarea`,
84
+ `rich_text`, `email`, `phone`, `url`, `number`, `currency`, `range`, `date`,
85
+ `datetime`, `boolean`, `checkbox`, `select`, `multiselect`, `radio`, `color`,
86
+ `file`, `image`, `json`, `reference`, `address`. Unknown types pass through
87
+ unsanitized — they are not rejected, they are unvalidated.
88
+
89
+ ### `views`
90
+
91
+ A map of view names to view configs. `default_list` and `default_detail` are
92
+ what the generic renderer consumes:
93
+
94
+ - `default_list` (`type: "list"`, `display: "table" | "card" | "board"`,
95
+ `fields` map with `sortable`/`display_type`, `default_sort`, optional
96
+ `filters`/`stats`) becomes columns in `GenericListPage`
97
+ (`.framework/src/components/app-shell/GenericListPage.tsx`) — the schema
98
+ itself resolves via `useListSchema`, which prefers a stamped record's
99
+ `design_schema` and falls back to the types API.
100
+ - `default_detail` (`type: "detail"`, `sections[]` of `{title, fields}`) drives
101
+ `GenericDetailPage`'s `SchemaDetailForm`; each section field resolves its
102
+ `FieldDefinition` and reads `record[field]` or `record.data[field]` by
103
+ `system` (`.framework/src/components/app-shell/GenericDetailPage.tsx`).
104
+
105
+ `display_type` values for view fields come from `ViewFieldConfig` in
106
+ `.framework/src/types/types.ts` (`input`, `text`, `badge`, `timestamp`,
107
+ `select`, `date_picker`, `file_upload`, …). An app with `renderer = "generic"`
108
+ is broken without these views; a `custom` app may omit them but loses the
109
+ generic admin/runtime fallback.
110
+
111
+ ### `record_permissions`
112
+
113
+ `{ "<role_slug>": ["create" | "read" | "update" | "delete", ...] }` — the base
114
+ grant for the record, evaluated per request against the caller's role plus the
115
+ `'all'` wildcard role. This is the contract evaluated in
116
+ `.framework/functions/_shared/permissions.ts`; the full model is in
117
+ [Permissions](permissions.md).
118
+
119
+ If a type has no `record_permissions`, `admin-data` create merges a permissive
120
+ default (`{ all: ["create","read","update","delete"], fields: {} }`) before
121
+ stamping — an explicit empty object would instead deny everything downstream,
122
+ so always author them deliberately.
123
+
124
+ ### `functionality`
125
+
126
+ Optional automation bindings the runtime interprets — e.g.
127
+ `functionality.lineage` (parent/child resolution for items, read in
128
+ `admin-data` list/get) and the integration/constraint bindings declared in
129
+ `FunctionalityBindings` (`field_map` sync, `unique`/`conditional_required`/
130
+ `immutable` constraints) in `.framework/src/types/types.ts`. Set it to `null`
131
+ until the automation binding you need is confirmed live.
132
+
133
+ ## What each consumer reads
134
+
135
+ | Consumer | Reads |
136
+ |---|---|
137
+ | `admin-data` create | `fields`/`record_permissions`/`scope` for stamping, gating, and account resolution |
138
+ | `permissions.ts` | `record_permissions`, `fields.*.permissions` — on the **record's** stamped schema |
139
+ | `schema-utils.ts` | generated `validation_schema` for `sanitizeFieldData` / `formatFieldData` |
140
+ | `GenericListPage` | `views.default_list` (columns, sort, filters, stats) |
141
+ | `GenericDetailPage` | `views.default_detail`, `fields`, `display_field` |
142
+ | `useEntityRecord` | `fields.*.system` to split system vs `data` writes |
143
+
144
+ ## Authoring minimum
145
+
146
+ Every type needs, at minimum: `fields` (each with `data_type`, `label`,
147
+ `required`), `record_permissions` (at least `system_admin`), and — for generic
148
+ apps — `default_list` + `default_detail`. `slug` must be unique per `kind`
149
+ (enforced by the `types_kind_slug_unique` index in
150
+ `.framework/migrations/015_types_kind_slug_unique.sql`), and `kind` must match
151
+ the runtime table the type governs. The entity page
152
+ [../types.md](../types.md) has minimal and full examples.
@@ -0,0 +1,127 @@
1
+ ---
2
+ title: "Integrations and webhooks"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/integrations.ts
9
+ - .framework/functions/integration-outbound.ts
10
+ - .framework/functions/integration-routes.ts
11
+ - .framework/functions/deliveries.ts
12
+ - .framework/functions/_shared/webhook-registration.ts
13
+ - .framework/functions/_shared/webhook-registry.ts
14
+ - .framework/functions/_shared/resolve-credentials.ts
15
+ - .framework/functions/_shared/retry-deliveries.ts
16
+ - .framework/functions/_shared/notify.ts
17
+ - .framework/functions/adapters/generic-webhook-outbound.ts
18
+ - .framework/migrations/004_core_rpc_and_broker.sql
19
+ ---
20
+
21
+ # Integrations and webhooks
22
+
23
+ An **integration** (`public.integrations`) is a named bundle of `config` +
24
+ `credentials` that outbound calls and inbound webhooks share. Spine routes both
25
+ directions through `public.webhook_deliveries`, so every attempt is a durable
26
+ row you can list, inspect, and retry — there is no fire-and-forget.
27
+
28
+ ## The integration row
29
+
30
+ `integrations` (`.framework/functions/integrations.ts`) requires `name`,
31
+ `integration_type`, and `provider` on create; it also carries `app_id`,
32
+ `version`, `config`, `credentials`, `metadata`, and a computed
33
+ `is_configured` flag.
34
+
35
+ Credentials live in a dedicated `credentials` jsonb column (added by migration
36
+ `004`) shaped `{ <field>: { source: "value" | "env_var", value: string } }`.
37
+ `resolve-credentials` resolves them **server-side only**, inside the functions
38
+ runtime: `source: "env_var"` reads `process.env[value]`; `source: "value"`
39
+ returns the literal string. Every resolution (or failure) emits an
40
+ `integration.credential_*` audit event. Non-system-admin, non-machine API reads
41
+ of an integration row are sanitized to minimal fields — credential values are
42
+ never returned to ordinary users, though they are stored in the database (use
43
+ `env_var` sources for the tightest posture).
44
+
45
+ ## Outbound calls
46
+
47
+ `integration-outbound` (`.framework/functions/integration-outbound.ts`) is the
48
+ single egress path — `callOpenAI` in `_shared/llm.ts`, the `http_request`,
49
+ `webhook`, `send_email`, and `agent_inference` pipeline stages, and manual
50
+ calls all go through its `execute`:
51
+
52
+ 1. **Resolve the integration**: account-scoped row matching
53
+ `integration_slug` first, then a system integration (`account_id IS NULL`).
54
+ Both must be `is_active`.
55
+ 2. **Open a delivery row**: inserts `webhook_deliveries` with
56
+ `direction: 'outbound'`, `status: 'pending'`, `attempt_count: 0`, and
57
+ `max_attempts` from `action_config.max_attempts` →
58
+ `integration.config.retry_policy.max_attempts` → default 3.
59
+ 3. **Resolve the credential** (`action_config.credential_field`, default
60
+ `api_token`) — skipped when the entry is absent or an empty
61
+ `source: 'value'` literal, which means "call without auth"; the adapter
62
+ then decides whether a key is required — and **pick an adapter**:
63
+ `action_config.adapter || integration.config.outbound_adapter`. Built-ins:
64
+ `adapters/openai-outbound`, `adapters/generic-webhook-outbound`,
65
+ `adapters/resend-outbound`; anything else is dynamically imported as a
66
+ sibling module exposing `buildRequest`.
67
+ 4. **Send**: the adapter builds `{ url, method, headers, body }`; the function
68
+ fetches it, then updates the delivery to `delivered` (with
69
+ `response_status`, `response_body`, `duration_ms`) or `pending`/`failed`.
70
+ 5. Optionally, `action_config.update_record` stamps
71
+ `last_outbound_status` / `last_outbound_at` back onto the source item.
72
+
73
+ ## Delivery and retry semantics
74
+
75
+ Both directions share the same retry math: `computeNextAttempt` is exponential
76
+ backoff — `30s * 2^attempt`, capped at 1 hour, with up to 20% jitter. A failed
77
+ attempt stays `pending` with `next_attempt_at` set until `attempt_count`
78
+ reaches `max_attempts`, when it becomes `failed`.
79
+
80
+ Retries are driven by `retryPendingDeliveries`
81
+ (`.framework/functions/_shared/retry-deliveries.ts`), which `system-cron` runs
82
+ every tick (see [automation](./automation.md)). It sweeps `webhook_deliveries`
83
+ rows that are `pending`, `attempt_count < 10`, and `next_attempt_at` due (or
84
+ null), up to 100 per pass, and re-invokes `integration-outbound` with
85
+ `delivery_id` under the system principal — the `delivery_id` path reuses the
86
+ existing row instead of inserting a duplicate. You can also retry one delivery
87
+ manually via `POST /api/deliveries?action=retry { id }`
88
+ (`.framework/functions/deliveries.ts`); manual retry is outbound-only. Inbound
89
+ retry is not implemented — inbound failures are counted and logged.
90
+
91
+ `deliveries` list/get are account-scoped reads over the same table, filterable
92
+ by `direction`, `status`, `integration_id`, `api_key_id`.
93
+
94
+ ## Inbound webhooks
95
+
96
+ `integration-routes` (`.framework/functions/integration-routes.ts`) is a
97
+ `createPublicHandler` endpoint — it resolves the caller as an **API-key
98
+ machine principal** (see [auth](./auth-and-api-keys.md)):
99
+
100
+ 1. `?slug=` names the integration; the caller's API key must belong to the
101
+ same account, and keys pinned to `integration_id` or `app_id` can only hit
102
+ those integrations.
103
+ 2. The event name comes from the `x-event-type` header or
104
+ `integration.config.event_path` (a dot-path into the payload).
105
+ 3. `config.handler_name || provider || 'generic-webhook'` is resolved through
106
+ the **webhook registry** (`_shared/webhook-registry.ts`): the
107
+ `webhook_handlers` table maps a handler name to a `function_name`, scoped
108
+ app → account → global fallback. The named module is dynamically imported
109
+ (`custom/…` paths resolve into `custom/`) and must export `adapt`,
110
+ `handler`, or a default function.
111
+ 4. The adapter returns `{ type_slug, data, title?, status? }` — the route then
112
+ creates an `items` record through `admin-data` (full permission/type/audit
113
+ pipeline) and marks the inbound delivery `delivered`. Adapter errors marked
114
+ `retryable` leave it `pending`.
115
+
116
+ Handlers are registered with `registerWebhookHandler`
117
+ (`_shared/webhook-registration.ts`), an upsert on `(app_id, account_id,
118
+ name)`; `deregisterWebhookHandler` soft-deletes (`is_active=false`,
119
+ `is_deleted=true`).
120
+
121
+ ## Notifications
122
+
123
+ `_shared/notify.ts` resolves pipeline `send_notification` recipients: each
124
+ entry is either a person UUID or `role:<slug>`, resolved to active `people`
125
+ rows in the caller's account via the `role_id → roles.slug` join. Anything
126
+ else is skipped with a warning. The resulting ids feed the `notifications`
127
+ insert in the pipeline runner.
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: "Observability: logs, deliveries, executions"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/logs.ts
9
+ - .framework/functions/observability.ts
10
+ - .framework/functions/deliveries.ts
11
+ - .framework/functions/pipeline-executions.ts
12
+ - .framework/functions/item-progress.ts
13
+ - .framework/functions/_shared/logging.ts
14
+ - .framework/functions/_shared/tracing.ts
15
+ - .framework/functions/_shared/audit.ts
16
+ - .framework/migrations/069_log_stats_window.sql
17
+ ---
18
+
19
+ # Observability: logs, deliveries, executions
20
+
21
+ Everything an operator needs lands in a small set of queryable tables. There is
22
+ no external telemetry service: `public.logs` is the unified sink, and the
23
+ `logs`, `observability`, `deliveries`, and `pipeline-executions` functions are
24
+ the read APIs over it.
25
+
26
+ ## What gets written, and where
27
+
28
+ `public.logs` holds three overlapping kinds of rows:
29
+
30
+ - **Audit/log events** — `emitLog`, `emitAudit`, and `emitActivity` in
31
+ `_shared/audit.ts` insert business events (`trigger.fired`,
32
+ `pipeline.completed`, `integration.outbound.failed_http`,
33
+ `api_key.created`, `registration.completed`, …) with actor, target, and
34
+ details.
35
+ - **Spans** — `emitSpan` writes trace spans (`span_name`, `trace_id`,
36
+ `span_id`, `parent_span_id`, `duration_ms`, `outcome`). `_shared/tracing.ts`
37
+ implements W3C `traceparent` parsing/formatting, and `traceDb` wraps the
38
+ request's DB client so every query emits a `db.<operation>` span — a single
39
+ `trace_id` stitches the request, its queries, and the pipeline stages it
40
+ caused.
41
+ - **Operation logs** — `logAdminDataOperation` in `_shared/logging.ts` writes
42
+ admin-data CRUD records, gated by resolved log levels.
43
+
44
+ Log **levels** are `'off' | 'timing' | 'warnings' | 'errors' | 'all'`, resolved
45
+ per request from `system_settings` rows (`key='logging'`, global plus scoped
46
+ overrides); the default is `['timing', 'errors']`. Under the default, errors
47
+ and timing rows are written; `warnings`-level and `all`-level verbosity require
48
+ opt-in settings. A request's `x-log-level` header can raise verbosity for that
49
+ call.
50
+
51
+ ## Reading logs
52
+
53
+ `logs` (`.framework/functions/logs.ts`) is system-admin oriented (system
54
+ admins query with the service client across accounts; account members get
55
+ their RLS-scoped view):
56
+
57
+ - `action=account` / `action=target` / `action=person` — filtered lists.
58
+ - `action=search` — `ILIKE` message search.
59
+ - `action=stats` — calls the `get_log_stats` RPC (totals, error count,
60
+ distinct levels/sources). **Note:** since migration `069`, when no
61
+ `p_date_from` is passed the window defaults to the **last 7 days** — a call
62
+ that omits the date range returns 7-day stats, not all-time stats.
63
+ - `action=cleanup` — deletes rows older than `days_to_keep` (default 30).
64
+ `system-cron` also runs `cleanup_old_logs` on the 00:00 UTC tick, so
65
+ retention is enforced continuously.
66
+
67
+ `observability` (`.framework/functions/observability.ts`) aggregates the same
68
+ rows into dashboard metrics — actions `event_volume`, `error_rate`,
69
+ `latency_percentiles`, `pipeline_stats`, `top_actors`, `ai_support_metrics`,
70
+ `cleanup` — scoped to the caller's account unless the principal is
71
+ system-admin.
72
+
73
+ ## Executions and deliveries
74
+
75
+ Two sibling tables answer "did it run / did it arrive":
76
+
77
+ - `public.pipeline_executions` — one row per pipeline run with `status`
78
+ (`queued` → `running` → `completed`/`failed`/`cancelled`), per-stage results,
79
+ `error_message`, `duration_ms`, and the `trigger_data` that caused it
80
+ (`.framework/functions/pipeline-executions.ts`; `action=running`,
81
+ `action=stats`, `action=cleanup`). Rows that never reach a terminal state
82
+ are swept to `failed` after 10 minutes.
83
+ - `public.webhook_deliveries` — one row per inbound/outbound webhook attempt
84
+ with `direction`, `status` (`pending`/`processing`/`delivered`/`failed`),
85
+ `attempt_count`, `next_attempt_at`, `last_error`, `response_status`, and
86
+ `response_body` (`.framework/functions/deliveries.ts`; `action=retry`).
87
+
88
+ `item-progress` (`.framework/functions/item-progress.ts`) is a different lens:
89
+ per-person, per-item progress rows (`status`, `score`, `attempts`) that follow
90
+ the type's `design_schema.pipeline` stage list (default
91
+ `not_started → in_progress → completed`). Transitions must move forward
92
+ unless `force: true` is passed; `started_at`/`completed_at` are stamped on
93
+ first entry. Use it when you need per-user completion state rather than
94
+ execution forensics.
95
+
96
+ ## Debugging a failed automation
97
+
98
+ Work backwards through the tables:
99
+
100
+ 1. **`pipeline_executions`** — find the run. `status: 'failed'` +
101
+ `error_message` and per-stage results name the failing stage.
102
+ `status: 'queued'` past 10 minutes = dispatch never arrived (sweeper will
103
+ mark it failed); `status: 'running'` stuck = the runner died (same
104
+ sweeper).
105
+ 2. **`logs`** — search by `trace_id` (from the request) or `span_name`
106
+ (`pipeline.run`, `pipeline.stage.<handler>`, `db.<operation>`); audit rows
107
+ `trigger.fired` / `trigger.failed` / `pipeline.completed` /
108
+ `pipeline.failed` bracket the run. Check log levels if expected `timing`
109
+ rows are absent.
110
+ 3. **`webhook_deliveries`** — for outbound stages/integrations, the delivery
111
+ row carries `response_status`, `response_body`, `last_error`, and
112
+ `next_attempt_at`; retry with `action=retry`.
113
+ 4. **`api_key_usage_logs`** (`api-keys?action=usage-logs`) — whether the
114
+ machine caller ever arrived, and from where.
115
+ 5. **`observability` actions** — confirm the blast radius (`error_rate`,
116
+ `pipeline_stats`, `top_actors`) rather than chasing a single event.
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: Permission Model
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/_shared/permissions.ts
9
+ - .framework/functions/_shared/principal.ts
10
+ - .framework/functions/_shared/db.ts
11
+ - .framework/functions/_shared/middleware.ts
12
+ - .framework/functions/admin-data.ts
13
+ - .framework/migrations/001_core_schema.sql
14
+ - .framework/migrations/002_core_rls_and_functions.sql
15
+ - .framework/migrations/003_core_system_data.sql
16
+ ---
17
+
18
+ # Permission Model
19
+
20
+ Spine authorization is two independent layers, and the code says so verbatim:
21
+ **"RLS controls row access; design_schema controls what the principal can do.
22
+ No permissions granted = none given."** (`.framework/functions/_shared/permissions.ts`)
23
+
24
+ - **Row visibility (RLS):** Postgres policies scope every tenant table to the
25
+ accounts the caller can reach — see [Scope and Accounts](scope-and-accounts.md).
26
+ - **Action authorization (`record_permissions`):** per request, the permission
27
+ engine evaluates the caller's role against the schema **stamped on the
28
+ record** to decide create/read/update/delete and per-field read/write.
29
+
30
+ ## Principals
31
+
32
+ `.framework/functions/_shared/principal.ts` resolves two principal types:
33
+
34
+ - **Human** — Supabase JWT → `people` row → `role_id` join to `roles`; the
35
+ principal carries `roles: [slug]` and gets a JWT-scoped DB client (`ctx.db`),
36
+ so RLS applies.
37
+ - **Machine** — `x-api-key` header → `api_keys` row → `scopes` array and the
38
+ key's `account_id`. Machine principals get `adminDb` (service role), which
39
+ **bypasses RLS entirely**; their only authorization is the scope check.
40
+
41
+ ## The three surfaces
42
+
43
+ `detectSurface` in `.framework/functions/_shared/permissions.ts` buckets the
44
+ target by table name:
45
+
46
+ | Surface | Covers | Rule |
47
+ |---|---|---|
48
+ | First (default) | runtime records — items, threads, messages, people, accounts, … | `record_permissions` on the record's stamped `design_schema` |
49
+ | Second | config objects — `apps`, `pipelines`, `triggers`, `ai_agents`, `embeddings`, `timers`, `integrations`, `roles`, `types`, `prompt_configs` | `system_admin` full access; machine principals read-only; everyone else denied |
50
+ | Third | system metadata — `logs`, `pipeline_executions`, `trigger_executions`, `link_types`, `links` | `system_admin`/machine full; humans read only their own/account rows |
51
+
52
+ ## First-surface evaluation
53
+
54
+ `resolveFirstSurfacePermissions` runs on every runtime-record access:
55
+
56
+ 1. **Schema source is the record.** The stamped `design_schema` on the record
57
+ is authoritative; there is deliberately no fallback to the type row — a
58
+ record with no schema or no `record_permissions` is denied.
59
+ 2. **Role resolution.** The caller's role slug comes from
60
+ `people.role_id → roles.slug` (one role per person, cached ~60s). No role,
61
+ no permissions.
62
+ 3. **Record grant.** `record_permissions[roleSlug]` is an array of actions
63
+ (`create`, `read`, `update`, `delete`). The special key `'all'` is a
64
+ wildcard evaluated for every authenticated principal — grants to `all`
65
+ apply to anyone who passed RLS.
66
+ 4. **Field merge.** For each declared field, `fields.<name>.permissions[role]`
67
+ — an array drawn from `read`/`write` only — replaces the default. Fields
68
+ **without** an entry default to `{ read: canRead, write: canUpdate }` from
69
+ the record grant. That is why `record_permissions` is the base, most
70
+ restrictive grant and field entries are *overrides*: an explicit `["read"]`
71
+ on a field removes write access that `update` would otherwise give.
72
+
73
+ > **Role keys are matched verbatim.** `record_permissions` keys must be real
74
+ > `roles.slug` values — nothing normalizes them. The seeded admin slug is
75
+ > `system_admin` (underscore, `.framework/migrations/003_core_system_data.sql`);
76
+ > a `system-admin` key silently matches nobody and denies the admin.
77
+
78
+ ## Enforcement points
79
+
80
+ - **Read** (`sanitizeFirstSurfaceRecordData`): `canRead` gates the record;
81
+ `data` is filtered per field by `fieldPermissions[name].read`; values are
82
+ formatted through `validation_schema` (`formatFieldData`); legacy `metadata`
83
+ is stripped from responses. Records with an empty `fields` map pass through
84
+ whole at the record grant.
85
+ - **Update** (`validateFirstSurfaceUpdatePermissions`): `canUpdate` gates the
86
+ record, then **every key inside `data` must have `write`** — one denied field
87
+ fails the whole update with a per-field error. Values are sanitized through
88
+ `sanitizeFieldData` (type coercion + constraint checks; violations are
89
+ errors, not silent drops). Top-level non-`data` keys pass through the gate.
90
+ Writes therefore require both the record action *and* field-level write —
91
+ undeclared fields have no `fieldPermissions` entry and 403.
92
+ - **Create** (`admin-data` `create`): permission is checked against the type's
93
+ `design_schema` (which becomes the record's stamp) before insert;
94
+ `type_id`/`type_slug` is required. A machine principal with `*:*` scope
95
+ bypasses the create check.
96
+
97
+ ## `system_admin` bypass
98
+
99
+ `isSystemAdmin` (`roles` contains `system_admin`) short-circuits record
100
+ access, sanitization, and update validation in `permissions.ts`, and gates the
101
+ `types.ts`/`roles.ts`/`apps.ts` write APIs entirely. The same slug drives the
102
+ RLS bypass inside `get_accessible_accounts` — system admins see all accounts.
103
+
104
+ ## Machine (API key) scopes
105
+
106
+ `checkMachineScope` grants an action when the key's `scopes` contain
107
+ `resource:action` exactly, `resource:*`, or `*:*` (e.g. `items:read`,
108
+ `items:*`, `*:*`). Scopes are the *only* thing standing between a leaked API
109
+ key and `adminDb`, so issue the narrowest scope that works — `*:*` also
110
+ bypasses the create permission check in `admin-data`.
111
+
112
+ ## Canonical example
113
+
114
+ The seeded pattern from the framework migrations — base grant first, field
115
+ override only where needed:
116
+
117
+ ```json
118
+ {
119
+ "record_permissions": {
120
+ "system_admin": ["create", "read", "update", "delete"],
121
+ "member_admin": ["create", "read", "update"],
122
+ "member": ["create", "read", "update"]
123
+ },
124
+ "fields": {
125
+ "title": { "data_type": "text", "label": "Title", "required": true, "system": true },
126
+ "status": { "data_type": "select", "label": "Status", "required": true,
127
+ "options": ["open", "closed"],
128
+ "permissions": { "member": ["read"] } },
129
+ "internal_notes": { "data_type": "textarea", "label": "Internal Notes",
130
+ "required": false,
131
+ "permissions": { "member": [] } }
132
+ }
133
+ }
134
+ ```
135
+
136
+ Reading it: `member` can create and update records, so every declared field
137
+ defaults to read+write — except `status`, whose `["read"]` override strips
138
+ `member`'s write, and `internal_notes`, whose empty override hides it from
139
+ `member` entirely. `member_admin` touches neither override, so it inherits
140
+ record-level read+write on both. That is the whole discipline: **add a
141
+ `fields.*.permissions` entry only where the base grant is wrong for that
142
+ field** — usually to restrict a sensitive or `system: true` field, occasionally
143
+ to loosen one the base grant closes.
144
+
145
+ ## See also
146
+
147
+ - [Design Schema](design-schema.md) — where `record_permissions` lives
148
+ - [Scope and Accounts](scope-and-accounts.md) — the RLS layer beneath this one
149
+ - [../roles.md](../roles.md) — the role entity (`permissions` on roles bound
150
+ the *subject* side at the app/UI level)
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: Runtime Records
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/admin-data.ts
9
+ - .framework/functions/_shared/resolve-ids.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/migrations/001_core_schema.sql
12
+ - .framework/src/hooks/useEntityRecord.ts
13
+ - .framework/src/components/app-shell/GenericDetailPage.tsx
14
+ ---
15
+
16
+ # Runtime Records
17
+
18
+ Runtime records are the rows your users and apps actually create — as opposed
19
+ to config entities (`types`, `apps`, `roles`, `pipelines`, …) that describe
20
+ the system itself. They share one storage contract: a type, an account, a
21
+ stamped schema, and a split between framework-owned columns and app-owned
22
+ `data`.
23
+
24
+ ## The record kinds
25
+
26
+ `admin-data` maps entity names to type `kind`s via `ENTITY_TO_KIND`
27
+ (`.framework/functions/admin-data.ts`):
28
+
29
+ | Entity (table) | `kind` | What it is |
30
+ |---|---|---|
31
+ | `items` | `item` | The workhorse — tickets, tasks, articles, any typed business object |
32
+ | `threads` | `thread` | A conversation bound to a target (`target_type` + `target_id`); unique per `(target_type, target_id, visibility)` |
33
+ | `messages` | `message` | An entry in a thread (`thread_id` + `sequence`, unique together) |
34
+ | `links` | `link` | A polymorphic edge between two records (`source_type`/`source_id` → `target_type`/`target_id`, optional `link_type`) |
35
+ | `attachments` | `attachment` | A stored file reference (filename, size, mime, `storage_path`) |
36
+ | `watchers` | `watcher` | A person subscribed to a target (unique per target+person) |
37
+ | `people` | `person` | Users/contacts — also carry `auth_uid`, `role_id`, `email` |
38
+ | `accounts` | `account` | Tenants — the only table without `account_id` (it *is* the account) |
39
+ | `item_progress` | `item_progress` | Progress/status snapshots for items |
40
+
41
+ `VALID_ENTITIES` also accepts `logs`, `test_runs`, `observability`, and
42
+ `test_cases` — the `RAW_ENTITIES` path: whitelisted raw-column inserts with no
43
+ type requirement and no `design_schema`. `types` and `link_types` are accepted
44
+ too but are config, not runtime records.
45
+
46
+ ## The shared row shape
47
+
48
+ Every runtime table in `.framework/migrations/001_core_schema.sql` carries the
49
+ same spine:
50
+
51
+ - `id`, `created_at`, `updated_at`, `created_by` (+ `updated_by` on most)
52
+ - `type_id uuid NOT NULL` — every record must reference a type
53
+ - `account_id` — the owning tenant (all except `accounts`)
54
+ - `app_id` — optional attribution to the app that created it
55
+ - `data jsonb` — app-defined field values
56
+ - `design_schema jsonb`, `validation_schema jsonb` — **stamped at creation**
57
+ from the type; the runtime source of truth for permissions and validation
58
+ (see [Design Schema](design-schema.md))
59
+ - `is_active` — soft-delete flag; `remove` flips it rather than deleting
60
+
61
+ `admin-data` create requires `type_id` *or* `type_slug` — `resolveTypeRecord`
62
+ in `.framework/functions/_shared/resolve-ids.ts` resolves `(kind, slug)` to
63
+ the type row, and the record is stamped before permission evaluation and
64
+ insert.
65
+
66
+ ## `system: true` columns vs `record.data`
67
+
68
+ The field declaration's `system` flag decides where a value lives:
69
+
70
+ - **`system: true`** → a top-level record column (`record.title`,
71
+ `record.status`, `record.email`). These are framework-owned or
72
+ schema-critical fields.
73
+ - **`system: false` / omitted** → inside `record.data.<field>`.
74
+
75
+ Consumers split on the flag — this is the authoritative convention:
76
+
77
+ - `useEntityRecord.save` partitions form data into `systemData` (top-level
78
+ keys) and `customData` (packed into `data`) by `fieldDef.system`
79
+ (`.framework/src/hooks/useEntityRecord.ts`).
80
+ - `GenericDetailPage` initializes each field from `record[field]` when
81
+ `fieldDef.system` and `record.data[field]` otherwise
82
+ (`.framework/src/components/app-shell/GenericDetailPage.tsx`).
83
+ - The permission engine's write gate evaluates every key inside `data`
84
+ against `fieldPermissions[name].write` and sanitizes through
85
+ `validation_schema` (`.framework/functions/_shared/permissions.ts`).
86
+
87
+ **Fact:** send system fields as top-level keys and non-system fields inside a
88
+ `data` object. A non-system field sent top-level is stored but invisible to
89
+ schema-driven readers; a system field sent inside `data` never reaches its
90
+ column.
91
+
92
+ ## Reading records back
93
+
94
+ Reads pass through `sanitizeRecordData`: fields the caller can't read are
95
+ dropped from `data`, the legacy `metadata` key is stripped from responses, and
96
+ remaining values are formatted through `validation_schema`
97
+ (`formatFieldData` — e.g. currency, multiselect, dates). A record the caller
98
+ can't read at all collapses to `{id, created_at, updated_at}` — so "record
99
+ exists but looks empty" is a permission answer, not missing data.
100
+
101
+ ## Writing rules of thumb
102
+
103
+ - `type_slug` + `entity` on create; `id` on update/delete.
104
+ - Never write `design_schema`, `validation_schema`, `type_id`, `account_id`,
105
+ or `id` in the body — create strips them before insert (`account_id` in the
106
+ body is honored as a *targeting* hint, see
107
+ [Scope and Accounts](scope-and-accounts.md)).
108
+ - Deletes are soft (`is_active = false`) for the standard entities; only a few
109
+ entities hard-delete.
110
+ - Creates fire triggers (`fireCreateTriggers`) and per-type rate limits after
111
+ permission checks — a custom insert path loses both.