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,125 @@
1
+ ---
2
+ title: Database Migrations
3
+ audience: both
4
+ kind: reference
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/migrate.ts
9
+ - .framework/cli/commands/migrations.ts
10
+ - .framework/cli/commands/install-app.ts
11
+ - .framework/cli/commands/upgrade.ts
12
+ - .framework/migrations/001_core_schema.sql
13
+ - .framework/migrations/003_core_system_data.sql
14
+ - .framework/migrations/069_log_stats_window.sql
15
+ ---
16
+
17
+ # Database Migrations
18
+
19
+ Spine's migration system is deliberately simple: numbered `.sql` files applied
20
+ once, in filename order, each inside its own transaction, recorded in a
21
+ `_spine_migrations` ledger table. Everything runs through
22
+ `npx spine-framework migrate` (`.framework/cli/commands/migrate.ts`).
23
+
24
+ ## File layout and numbering
25
+
26
+ - **Framework migrations** live in `.framework/migrations/` — sealed,
27
+ upgrade-synced. Naming is `NNN_snake_case_description.sql` with a
28
+ three-digit prefix; plain lexicographic sort is the apply order.
29
+ - **Custom migrations** live in `custom/migrations/` — project-owned, applied
30
+ **after** all framework files in the same run (skip them with
31
+ `--framework-only`).
32
+ - Files are filtered to `*.sql` not starting with `.`
33
+ (`collectMigrations` in `.framework/cli/commands/migrate.ts`).
34
+ - **Numbering gaps are expected.** `22–31`, `39–41`, and `63` are reserved or
35
+ consolidated numbers — a gap is not a missing migration (see
36
+ [Database Reference](../reference/database.md)). The same number can also
37
+ appear twice across the framework and custom dirs: the ledger key is the
38
+ **filename**, not the numeric prefix, so `062_send_email_action.sql` and a
39
+ custom `062_*.sql` do not collide.
40
+
41
+ ## The `_spine_migrations` ledger
42
+
43
+ `migrate` creates and reads `public._spine_migrations`
44
+ (`id SERIAL`, `filename TEXT UNIQUE NOT NULL`, `applied_at TIMESTAMPTZ`).
45
+
46
+ Per file, inside one transaction: split the SQL into statements, run each,
47
+ insert the ledger row, `COMMIT`. On the **first** statement error the file
48
+ rolls back, the error is printed, and the run stops — later files are never
49
+ attempted. Files already in the ledger are skipped, so reruns are safe.
50
+
51
+ Note there is a **second, unrelated ledger**: `migrations list` /
52
+ `migrations status` read `supabase_migrations.schema_migrations` — the table
53
+ Supabase's own tooling maintains, which `spine-framework migrate` never
54
+ writes. `migrations status` therefore reports local files as `pending` even
55
+ when `_spine_migrations` shows them applied. Use the `_spine_migrations`
56
+ table itself as the authoritative "what ran" record.
57
+
58
+ ## Running migrations
59
+
60
+ ```bash
61
+ npx spine-framework migrate # apply pending framework + custom
62
+ npx spine-framework migrate --framework-only
63
+ npx spine-framework migrate --dry-run
64
+ ```
65
+
66
+ - Connects **directly to Postgres** — `db.<project-ref>.supabase.co:5432`,
67
+ user `postgres`, database `postgres`. `SUPABASE_URL` must be a hosted
68
+ `https://<ref>.supabase.co` URL and `SUPABASE_DB_PASSWORD` must be set
69
+ (`--db-password` overrides; `.env` is preloaded).
70
+ - `--dry-run` prints **every collected file**, not just pending ones — the
71
+ pending filter lives inside `runMigrations`, which dry-run never reaches.
72
+ To see real pending state, query `_spine_migrations` or run it for real
73
+ (applied files skip cleanly).
74
+ - `npx spine-framework install-app` also calls `runMigrations` before
75
+ seeding; a migration failure aborts the install (`--no-seed` installs
76
+ files only and tolerates missing DB credentials).
77
+
78
+ ## Custom migration rules
79
+
80
+ 1. One file per change, `NNN_name.sql`, in `custom/migrations/`.
81
+ 2. **Idempotent by convention.** The ledger makes files apply-once, but
82
+ drift happens (rows applied by hand, shared dev DBs, ledger restores).
83
+ Write `IF NOT EXISTS`, `ON CONFLICT DO NOTHING` / `DO UPDATE`, and
84
+ `CREATE OR REPLACE` so a re-run is survivable — framework files follow
85
+ the same convention.
86
+ 3. Never edit a file after it has been applied anywhere — the ledger keys on
87
+ filename and will not re-run it. Add a new file instead.
88
+ 4. Never put runtime-record seed data (types, roles, pipelines) in a
89
+ migration when an app manifest `seed` block can express it — seeds get
90
+ conflict handling, validation, and reconciliation that raw SQL does not.
91
+
92
+ ## Rolling out schema changes with app updates
93
+
94
+ Two different channels, don't confuse them:
95
+
96
+ - **Framework schema changes** ship inside `.framework/migrations/` in the
97
+ npm package. `npx spine-framework upgrade` syncs the files and *reports*
98
+ new migrations — it never runs them. `npx spine-framework migrate` applies
99
+ them (see [Upgrade](upgrade.md)).
100
+ - **App-level schema** is data, not DDL: `public.types` `design_schema`
101
+ rows, roles, pipelines ship in the app package's `manifest.json` `seed`
102
+ block, applied by `install-app`/`seed-app`. `install-app` copies only
103
+ `index.tsx`, `manifest.json`, `package.json`, `pages`, `components`,
104
+ `hooks`, `utils`, `functions`, `seed`, `public` into `custom/apps/<slug>/`
105
+ — there is no per-app migrations directory. If an app truly needs DDL,
106
+ ship it as a `custom/migrations/` file in the project repo.
107
+
108
+ Production rollout order: deploy code first or migrate first — both work
109
+ because migrations are additive-by-convention, but the safe default is
110
+ **migrate, then deploy**, so new code never meets a schema older than it
111
+ expects.
112
+
113
+ ## Checklist
114
+
115
+ - [ ] `SUPABASE_URL` + `SUPABASE_DB_PASSWORD` set (`.env` or `--db-password`).
116
+ - [ ] `SELECT filename FROM _spine_migrations ORDER BY filename` reviewed for drift before major upgrades.
117
+ - [ ] New custom migration is idempotent and numbered after the highest local prefix.
118
+ - [ ] After `spine-framework upgrade`: `npx spine-framework migrate` run — upgrade only syncs files.
119
+ - [ ] `migrations status` not trusted as the source of truth (it reads `supabase_migrations`).
120
+
121
+ ## Related
122
+
123
+ - [Database Reference](../reference/database.md) — table catalog and numbering gaps
124
+ - [Upgrade](upgrade.md) · [Deployment](deployment.md) · [Troubleshooting](troubleshooting.md)
125
+ - [CLI Reference](../reference/cli.md) — `migrate`, `migrations`, `install-app` flags
@@ -0,0 +1,170 @@
1
+ ---
2
+ title: Security Checklist
3
+ audience: both
4
+ kind: guide
5
+ verified_against: b3b3ae0
6
+ verified_on: 2026-11-01
7
+ source_files:
8
+ - .framework/functions/api-keys.ts
9
+ - .framework/functions/_shared/hash-api-key.ts
10
+ - .framework/functions/_shared/principal.ts
11
+ - .framework/functions/_shared/middleware.ts
12
+ - .framework/functions/_shared/permissions.ts
13
+ - .framework/functions/_shared/resolve-credentials.ts
14
+ - .framework/functions/_shared/security.ts
15
+ - .framework/functions/security.ts
16
+ - .framework/functions/logs.ts
17
+ - .framework/functions/debug-auth.ts
18
+ - .framework/migrations/049_security_blocklist.sql
19
+ - netlify.toml
20
+ - .env.example
21
+ ---
22
+
23
+ # Security Checklist
24
+
25
+ Operational security posture for a Spine deployment — what the platform
26
+ already enforces, and the surfaces you must keep an eye on yourself.
27
+ Related reading: [Auth & API Keys](../concepts/auth-and-api-keys.md),
28
+ [Permissions](../concepts/permissions.md),
29
+ [Environment Variables](../reference/env-vars.md).
30
+
31
+ ## Secrets that must stay server-side
32
+
33
+ - **`SUPABASE_SERVICE_ROLE_KEY` bypasses RLS entirely.** It is read only by
34
+ functions and the CLI (`adminDb` in `.framework/functions/_shared/db.ts`)
35
+ and must never appear under a `VITE_*` name — `VITE_*` vars are compiled
36
+ into the public browser bundle. Older docs recommended
37
+ `VITE_SUPABASE_SERVICE_ROLE_KEY`; that is an anti-pattern the audit
38
+ flagged (S-004). If it ever shipped, rotate the key.
39
+ - `netlify.toml` `SECRETS_SCAN_OMIT_KEYS` whitelists only vars that are
40
+ **meant** to be public (`VITE_SUPABASE_URL`, `VITE_SUPABASE_ANON_KEY`,
41
+ `APP_BASE_URL`, `SITE_URL`, …). Do not add secrets to it.
42
+ - `INTERNAL_INVOKE_SECRET`, `SCHEDULER_API_KEY`, `RESEND_API_KEY`,
43
+ `OPENAI_API_KEY`, `NETLIFY_ACCESS_TOKEN` — server-side Netlify env only.
44
+ - `.env` is gitignored and excluded from the shipped file set; no framework
45
+ command writes it (see [Project Map](../guide/02-project-map.md)).
46
+
47
+ ## API key hygiene
48
+
49
+ - Keys mint as `sk_<uuid>`; only `key_prefix` (first 8 chars) and a
50
+ `pbkdf2$<iters>$<salt>$<hash>` value (100k iterations, SHA-256,
51
+ `timingSafeEqual` verify) are stored
52
+ (`.framework/functions/api-keys.ts`,
53
+ `.framework/functions/_shared/hash-api-key.ts`). The raw key is returned
54
+ once as `raw_key`; `omitSecretFields` strips `key_value`/`key_hash` from
55
+ `list`, `get`, and `revoke` responses.
56
+ - Grant **scopes**, not `*:*`. A machine key with `*:*` resolves to the
57
+ `system_admin` role and effectively becomes an RLS-immune superuser
58
+ (`.framework/functions/_shared/principal.ts`). `is_internal` keys are for platform machinery.
59
+ - Every key has a rate limit (default `100`/60s) plus a per-IP ceiling
60
+ (`SPINE_IP_RATE_LIMIT`, default `300`/60s); usage lands in
61
+ `api_key_usage_logs` — read via `/api/api-keys?action=usage-logs` before
62
+ revoking so you see what the key touched.
63
+ - Revoke, don't delete: `POST /api/api-keys?action=revoke` sets
64
+ `is_active=false`. Audit `api_keys` rows for a non-null `key_value`
65
+ column — a legacy column that can hold plaintext (the S-001 incident
66
+ class); nothing in `_shared` reads it, but `select('*')` surfaces used to
67
+ return it.
68
+
69
+ ## Credential sanitization
70
+
71
+ - Integration credentials resolve server-side only:
72
+ `credentials.<field>.source = 'env_var'` reads `process.env` inside the
73
+ function (`.framework/functions/_shared/resolve-credentials.ts`); literal `value` entries are
74
+ stored in `integrations.credentials` — treat that column as secret.
75
+ - `integrations` is a second-surface table: non-system-admin and machine
76
+ reads are sanitized to minimal fields by `.framework/functions/_shared/permissions.ts`.
77
+ - Outbound stage configs (`http_request`) reference the `generic-http`
78
+ integration — credentials live on the integration row, never in the
79
+ pipeline stage JSON.
80
+
81
+ ## Ban / blocklist surface
82
+
83
+ `/api/security` (`.framework/functions/security.ts`) is gated by
84
+ `requireSystemContextWithAudit` — system-admin only. Actions:
85
+ `flagged_accounts`, `ban_user`, `unban_user`, `blocklist`,
86
+ `blocklist_add`, `blocklist_revoke`. A ban is three layers:
87
+
88
+ 1. `security_blocklist` rows (person/email/account/IP) — the authoritative
89
+ layer; enforced per-request in `createHandler` (`isBlockedRequest`) and
90
+ on public endpoints (`isBlockedIp`).
91
+ 2. Supabase Auth ban (`auth.admin.updateUserById`, ~100y `ban_duration`).
92
+ 3. Netlify edge deny rule `spine-security:blocklist` — only when
93
+ `NETLIFY_ACCESS_TOKEN` + `NETLIFY_SITE_ID` are set; skipped silently
94
+ otherwise (auth/edge failures are audited, not thrown).
95
+
96
+ Caveat: blocklist enforcement **fails open** when
97
+ `security_blocklist` is absent (pre-049 installs) — a missing table means
98
+ no blocking, not an error.
99
+
100
+ ## Signup & first-run surface
101
+
102
+ - **First-registrant claim window.** `handle_new_auth_user` grants
103
+ `system_admin` to a new registrant only when `public.people` is empty —
104
+ the *first* person to register claims the instance. On a public
105
+ deployment, run `npx spine-framework bootstrap` **before** the site is
106
+ reachable (or disable public signup), or the first stranger who finds
107
+ `/register` owns the instance. See
108
+ [Install and first run](../guide/01-install-and-first-run.md).
109
+ - **Post-bootstrap registration is safe to leave on.** With `people`
110
+ non-empty, subsequent registrants land unplaced/pending — no privilege.
111
+ Verify once by registering a second user and checking its role.
112
+ - **Turnstile is conditional.** `VITE_TURNSTILE_SITE_KEY` unset → auth
113
+ pages render with no widget; set → the widget enforces on
114
+ register/login. Confirm both states at least once per environment —
115
+ a site key set without the secret (or vice versa) silently degrades to
116
+ the unset behavior.
117
+ - **Headless access is `x-api-key`, not cookies.** `x-api-key:
118
+ <SPINE_API_KEY>` authenticates `/api/*` for machine callers — no
119
+ Turnstile, no session. Scope it (never `*:*`); the full discipline is in
120
+ "API key hygiene" above.
121
+
122
+ ## Endpoint exposure notes
123
+
124
+ - `debug-auth` (`/api/debug-auth`) is gated by *any authenticated
125
+ principal*, not system-admin — it reports principal type and
126
+ `SET`/`NOT_SET` env flags. Diagnostic, not product surface; consider
127
+ whether it belongs in a hardened production deploy
128
+ (see [debug-auth](../reference/functions/debug-auth.md)).
129
+ - `/api/system` (`manifest`, `health`, `openapi`) likewise answers any
130
+ authenticated principal — the OpenAPI spec and manifest are not
131
+ admin-only information.
132
+ - Public endpoints (`invites`, `integration-routes`, registration flows)
133
+ run through `createPublicHandler` — anonymous by design; their abuse
134
+ controls are IP blocklist, rate limits, and Turnstile, not auth.
135
+
136
+ ## Known ungated surfaces
137
+
138
+ Honest list — the audit found these; mitigate by key discipline until
139
+ they are tightened:
140
+
141
+ - **`logs?action=cleanup`** uses `ctx.db` with no `isSystemAdmin` check and
142
+ no account scoping (`.framework/functions/logs.ts`). Machine principals
143
+ get `adminDb` from `getPrincipalDb` — so **any valid API key can delete
144
+ log rows across all accounts**. Mitigation: issue machine keys narrowly,
145
+ watch `api_key_usage_logs`, rotate on suspicion.
146
+ - **`api-keys` `list`/`get`** select `*` via `adminDb` for system admins —
147
+ and `ctx.db` is *also* `adminDb` for machine principals, so a scoped
148
+ machine key reads `api_keys` metadata cross-account
149
+ (`api_keys` rows are stripped of `key_value`/`key_hash`, but slugs,
150
+ integration links, rate limits, and scopes are visible).
151
+ - **Function-level RLS is uneven** — mutating handlers branch
152
+ `isSystemAdmin ? adminDb : ctx.db`; a scoped machine key still gets
153
+ `adminDb`, so "machine keys are always RLS-bounded" is not a safe
154
+ assumption anywhere `ctx.db` is used without an explicit account filter.
155
+
156
+ ## Checklist
157
+
158
+ - [ ] No `VITE_`-prefixed secret anywhere; service-role key server-only.
159
+ - [ ] `INTERNAL_INVOKE_SECRET`, `SCHEDULER_API_KEY`, `APP_BASE_URL` set; `SITE_URL` pinned to the real origin for CORS.
160
+ - [ ] Machine keys carry least-privilege scopes (never `*:*` for third parties), rate limits, and expiry where possible.
161
+ - [ ] `api_keys` rows audited for legacy `key_value` plaintext.
162
+ - [ ] Blocklist tested end-to-end (a banned IP gets 403); Netlify edge sync env vars set if edge bans are wanted.
163
+ - [ ] `debug-auth` and `/api/system` exposure reviewed for the environment.
164
+ - [ ] Machine-principal surfaces above understood — `ctx.db` = service role for machines.
165
+
166
+ ## Related
167
+
168
+ - [Auth & API Keys](../concepts/auth-and-api-keys.md) · [Permissions](../concepts/permissions.md)
169
+ - [Errors and Envelopes](../reference/api/errors-and-envelopes.md) · [api-keys](../reference/functions/api-keys.md) · [security](../reference/functions/security.md)
170
+ - [Troubleshooting](troubleshooting.md) · [Deployment](deployment.md)
@@ -0,0 +1,170 @@
1
+ ---
2
+ title: Troubleshooting Playbook
3
+ audience: both
4
+ kind: guide
5
+ verified_against: d0c49d4
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/trigger-engine.ts
9
+ - .framework/functions/_shared/pipeline-runner.ts
10
+ - .framework/functions/_shared/resolve-credentials.ts
11
+ - .framework/functions/_shared/permissions.ts
12
+ - .framework/functions/_shared/middleware.ts
13
+ - .framework/functions/_shared/principal.ts
14
+ - .framework/functions/logs.ts
15
+ - .framework/functions/pipeline-executions.ts
16
+ - .framework/functions/deliveries.ts
17
+ - .framework/functions/api-keys.ts
18
+ - .framework/cli/commands/migrate.ts
19
+ - .framework/cli/commands/migrations.ts
20
+ - .framework/migrations/003_core_system_data.sql
21
+ - scripts/assemble-functions.sh
22
+ ---
23
+
24
+ # Troubleshooting Playbook
25
+
26
+ ## Where to look first
27
+
28
+ Everything operational lands in three places (see
29
+ [Observability](../concepts/observability.md) for the model):
30
+
31
+ 1. **`pipeline_executions`** — did the run exist, and where did it stop?
32
+ Admin: **Observability → Executions**; API: `/api/pipeline-executions`.
33
+ 2. **`logs`** — search by `trace_id`/`span_name`, or `action=search` on the
34
+ error text (**Observability → Logs** / `/api/logs`). `trigger.fired` /
35
+ `pipeline.failed` audit rows bracket the run. Netlify function logs
36
+ carry what never reached the DB (missing env vars, dispatch failures).
37
+ 3. **`webhook_deliveries`** — outbound HTTP stages and integrations
38
+ (**Configs → Deliveries** / `/api/deliveries`): `last_error`,
39
+ `response_status`, `next_attempt_at`; `POST /api/deliveries?action=retry`
40
+ re-fires an outbound delivery.
41
+
42
+ ## Common failure signatures
43
+
44
+ ### Trigger-dispatched pipeline never runs
45
+
46
+ Symptom: `pipeline_executions` row sits at `queued`, no spans — until the
47
+ sweeper marks it `failed` (~10 min) with `stale execution: runner died
48
+ before terminal write (swept)`
49
+ (`.framework/functions/_shared/pipeline-runner.ts`). Causes, in order of
50
+ likelihood:
51
+
52
+ 1. **`INTERNAL_INVOKE_SECRET` unset.** `dispatchTriggerPipeline` logs
53
+ `INTERNAL_INVOKE_SECRET not set — cannot dispatch trigger pipeline` and
54
+ returns — the queued row is the only artifact
55
+ (`.framework/functions/_shared/trigger-engine.ts`). Fix: set it
56
+ (`openssl rand -hex 32`), redeploy.
57
+ 2. Wrong `SITE_URL`/`URL` — dispatch POSTs to
58
+ `{SITE_URL}/.netlify/functions/pipeline-runner-background`; a mis-pointed
59
+ base URL sends the run elsewhere (or times out after 5s).
60
+
61
+ ### `integration.credential_*` on `generic-http`
62
+
63
+ These stages run through `executeOutbound` with
64
+ `integration_slug: 'generic-http'`
65
+ (`.framework/functions/_shared/pipeline-runner.ts`). Read the audit event
66
+ first — only one of them is fatal:
67
+
68
+ - `integration.credential_env_missing` **fails the call** — an `env_var`
69
+ entry names a variable that isn't set in the function env
70
+ (`.framework/functions/_shared/resolve-credentials.ts`). Fix the
71
+ integration row (Configs → Integrations) or deploy the env var.
72
+ - `integration.credential_missing` / `credential_empty` with
73
+ `skipped: true` are **informational** — no credential is configured, so
74
+ the request went out with no `Authorization` header
75
+ (`.framework/functions/integration-outbound.ts`). If the *target* then
76
+ 401s, the endpoint actually needs auth: set a real `api_token`
77
+ (`source: "env_var"` preferred) or pass `action_config.headers`.
78
+
79
+ ### `403` for a system admin: the `system-admin` dead role key
80
+
81
+ `record_permissions` keys match `roles.slug` **verbatim** — nothing
82
+ normalizes hyphens. A design schema granting `"system-admin"` silently
83
+ denies the real `system_admin` role
84
+ (`.framework/functions/_shared/permissions.ts`, the `DOC-LEARNING` note on
85
+ `resolveFirstSurfacePermissions`). Valid keys are real slugs
86
+ (`system_admin`, `member`, `support`, …) or the `all` wildcard. Same class
87
+ of bug: a `people.role_id` that resolves to no role gets the default-deny
88
+ result — check the `people` → `roles` join, not the UI.
89
+
90
+ ### `member` grants do nothing
91
+
92
+ `member` and `member_admin` are seeded by
93
+ `.framework/migrations/073_seed_member_roles.sql`. On a database that
94
+ predates that migration the slugs exist only when an app seeds them
95
+ (`install-app` applies `seed/roles.json`) or someone creates them via
96
+ `/api/roles`. With no `member` row in `public.roles`,
97
+ `record_permissions.member` grants nobody anything — a guaranteed-deny
98
+ that looks like a permissions bug.
99
+
100
+ ### Trigger never fires at all
101
+
102
+ - Event names are **singular-kind + `_created|_updated|_deleted`** —
103
+ `item_created`, `message_updated`, `person_created`. `ENTITY_EVENT_NAME`
104
+ maps `items`→`item`, `people`→`person`, etc.
105
+ (`.framework/functions/_shared/trigger-engine.ts`), so a trigger
106
+ configured for `items_created` or `record_created` matches nothing.
107
+ - `triggers.is_active` must be true; `config.entity_type`, `config.type_slug`,
108
+ and `config.filters` further narrow matches — check `trigger_executions`
109
+ and `trigger.fired`/`trigger.failed` audit rows.
110
+ - The admin-data delete path does not fire `item_deleted` triggers.
111
+
112
+ ### HTTP 200 that is actually a denial
113
+
114
+ The envelope is always `{data, error, meta}`; a handler that *returns* (not
115
+ throws) a refusal produces HTTP 200 with the denial inside `data`
116
+ ([Errors and Envelopes](../reference/api/errors-and-envelopes.md)) —
117
+ e.g. `POST /api/api-keys?action=validate` returns
118
+ `{is_valid: false, error: 'Insufficient permissions'}` at 200
119
+ (`.framework/functions/api-keys.ts`). Inspect `data`/`error`, not just the
120
+ status. Conversely, a thrown `Error` without `statusCode` maps to **500**,
121
+ so many 500s are intentional refusals (`Invalid action or method`,
122
+ `Record not found or access denied`), not crashes — read the `error` text.
123
+
124
+ ### Edits to functions "do nothing"
125
+
126
+ `netlify dev` serves the assembled snapshot in
127
+ `.assembled/netlify/functions`, not `.framework/functions`. Re-run
128
+ `npm run assemble` (or restart `dev:netlify`) after editing function code —
129
+ see [Deployment](deployment.md).
130
+
131
+ ### `dev:netlify` fails with "Could not acquire required port"
132
+
133
+ Ports 8888 (`netlify dev`) and 3001 (Vite) are fixed in manifest-protected
134
+ files — there is no supported alternate-port path, and moving only one
135
+ side cross-connects the `/api` proxy to whatever still listens on 8888
136
+ (often a second Spine project's dev stack). `scripts/dev-netlify.sh` only
137
+ kills processes it owns; find the holder (`lsof -i :8888 -i :3001`) and
138
+ stop it yourself. `Failed to fetch AI Gateway token … Unauthorized` in the
139
+ same output is unrelated Netlify CLI noise, not a Spine fault.
140
+
141
+ ### Migration state looks wrong
142
+
143
+ `migrations list`/`status` read the `_spine_migrations` ledger (`filename`,
144
+ `applied_at`) that `migrate` writes and compare it against the collected
145
+ files (`.framework/cli/commands/migrations.ts`). `migrate --dry-run` lists
146
+ every collected file, not only pending ones, and file numbering is
147
+ non-contiguous — retired migrations are removed rather than renumbered, so
148
+ gaps are expected (see [Migrations](migrations.md)).
149
+
150
+ ## Reading `pipeline_executions`
151
+
152
+ | Status | Meaning |
153
+ |---|---|
154
+ | `pending` | created via `/api/pipeline-executions` POST, not yet claimed |
155
+ | `queued` | trigger reserved the row; dispatch may or may not have landed |
156
+ | `running` | `pipeline-runner-background` claimed it |
157
+ | `completed` / `failed` | terminal; `error_message` + per-stage results name the failure |
158
+ | `cancelled` | manually cancelled via PATCH |
159
+
160
+ Any of `pending`/`queued`/`running` untouched for >10 minutes is swept to
161
+ `failed` by `sweepStaleExecutions` — a wedged row is evidence, not the bug;
162
+ find why it never reached its terminal write. Useful queries:
163
+ `/api/pipeline-executions?status=failed&pipeline_id=…`,
164
+ `?action=running`, `?action=stats`.
165
+
166
+ ## Related
167
+
168
+ - [Observability](../concepts/observability.md) — logs/executions/deliveries model
169
+ - [Errors and Envelopes](../reference/api/errors-and-envelopes.md) — the 200-wrapped-denial contract
170
+ - [Security Checklist](security-checklist.md) · [Migrations](migrations.md) · [Deployment](deployment.md)
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: Framework Upgrades
3
+ audience: both
4
+ kind: reference
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/upgrade.ts
9
+ - .framework/cli/lib/project-files.ts
10
+ - scripts/checksum-manifest.mts
11
+ - scripts/verify-integrity.sh
12
+ - .framework/templates/project.json
13
+ - netlify.toml
14
+ ---
15
+
16
+ # Framework Upgrades
17
+
18
+ `npx spine-framework upgrade` syncs a project's framework-owned files to the
19
+ `spine-framework` version installed in `node_modules`
20
+ (`.framework/cli/commands/upgrade.ts`). It is a file sync with an integrity
21
+ gate, not a git operation — and the executing copy is always the **new**
22
+ package's code, since `npx` resolves `node_modules`.
23
+
24
+ ## What is sealed
25
+
26
+ The frozen manifest `.spine-manifest.json` (written by `init`, regenerated
27
+ by `upgrade`) records a sha256 for every file under the protected trees —
28
+ `.framework/`, `config/`, `scripts/`, plus `bin/`
29
+ (`scripts/checksum-manifest.mts`, `PROTECTED_DIRS` + `--include-dir bin`).
30
+ The file set itself is declared by `.framework/templates/project.json`
31
+ (`framework_dirs`, `framework_files`, `config_files`, `root_scripts`,
32
+ `bin`).
33
+
34
+ Also recorded in the manifest: `framework_version`,
35
+ `framework_dependencies` (so the package.json merge knows which deps the
36
+ framework owns), a `tree_digest`, and a separate `agent_files` hash map
37
+ (more below). `npm run verify` re-hashes the tree and fails on drift;
38
+ `SPINE_INTEGRITY=warn` downgrades that to warnings.
39
+
40
+ ## The integrity gate
41
+
42
+ Before writing anything, upgrade re-hashes every manifest entry:
43
+
44
+ - Any **modified or missing** protected file → abort with a per-file list.
45
+ The only way through is `--force`, which overwrites modified files and
46
+ restores missing ones (each is logged).
47
+ - This is the safety rail: overwriting `.framework/` loses nothing the user
48
+ owned, because the gate proves they never changed it. **Never edit sealed
49
+ trees** — the change is either blocked or silently reverted later.
50
+ - `--dry-run` computes the full report (added / changed / removed / agent
51
+ assets / new migrations) and writes nothing.
52
+
53
+ ## What an upgrade does, in order
54
+
55
+ 1. Preconditions: refuses to run from the vendored copy (`package.json`
56
+ `name` must be `spine-framework`), requires `.spine-manifest.json`,
57
+ and refuses to downgrade (`cmpSemver` on `framework_version`).
58
+ 2. Integrity gate (above).
59
+ 3. Collects the new file set, then applies copies; **deletions are confined
60
+ to `.framework/`** — files outside it that upstream dropped are warned
61
+ about and left in place.
62
+ 4. Syncs agent assets (below).
63
+ 5. Merges `package.json`: framework scripts and the dependency set are
64
+ rewritten, `spine-framework` pinned exactly; a dependency is removed
65
+ **only** if the previous framework version owned it and the new one
66
+ doesn't. Customer scripts/deps survive.
67
+ 6. Re-renders `netlify.toml`: framework blocks come from the package;
68
+ `[functions."custom_*"]` sections in the existing file (with their
69
+ attached comments) are re-appended verbatim — that's where app-owned
70
+ scheduled functions live. A changed render leaves `netlify.toml.bak`.
71
+ 7. Regenerates `.spine-manifest.json` at the new version.
72
+ 8. Lists new `.framework/migrations/*.sql` files — reported, **never run**.
73
+ `npx spine-framework migrate` applies them.
74
+
75
+ ## Agent files merge behavior
76
+
77
+ `profile.agent_files` (`.framework/templates/project.json`) declares two
78
+ merge modes, applied by `syncAgentFiles` in
79
+ `.framework/cli/lib/project-files.ts`:
80
+
81
+ - **`framework-block` → `AGENTS.md`.** The template renders
82
+ `{{SPINE_VERSION}}`, then the marker-delimited block —
83
+ `<!-- spine-framework:begin version=X.Y.Z -->` …
84
+ `<!-- spine-framework:end -->` — is replaced in place. Everything outside
85
+ the markers is customer content and is preserved. A file with no markers
86
+ gets the block **prepended** (never overwritten). Hashes go into the
87
+ manifest's `agent_files` map — deliberately not `files`, so edits outside
88
+ the markers don't count as drift.
89
+ - **`dir-replace` → `.devin/skills`.** Only `spine-*` subdirectories are
90
+ removed and re-copied from the package. Sibling dirs with any other name
91
+ are customer-owned and untouched.
92
+
93
+ ## What upgrade never touches
94
+
95
+ `custom/`, `spine.config.json`, `.env`, `.spine/`, `netlify.toml`
96
+ `[functions."custom_*"]` sections, and the customer-owned parts of
97
+ `AGENTS.md` / `.devin/skills`. If a run would destroy your work, the design
98
+ has failed — that is the promise the sealed layer exists to keep (see
99
+ [Core vs Custom Boundary](../concepts/core-vs-custom.md)).
100
+
101
+ ## Upgrade checklist
102
+
103
+ - [ ] `npm run verify` clean *before* starting — a dirty tree only aborts later anyway.
104
+ - [ ] `npm install spine-framework@<version>` first; upgrade syncs to whatever `node_modules` has.
105
+ - [ ] `npx spine-framework upgrade --dry-run`, read the report, then run for real.
106
+ - [ ] Never pass `--force` to make local `.framework/` edits stick — it does the opposite. Move the change to `custom/` first.
107
+ - [ ] `npm install` after (dependency set changed), then `npm run verify`.
108
+ - [ ] `npx spine-framework migrate` for the reported new migrations ([Migrations](migrations.md)).
109
+ - [ ] Re-run `npm run assemble` / restart `netlify dev` so `.assembled/` picks up the new files ([Deployment](deployment.md)).
110
+ - [ ] Commit the result — manifest, `netlify.toml`, `package.json`, `.devin/skills`, `AGENTS.md` are all part of the upgrade diff.
111
+
112
+ ## Related
113
+
114
+ - [Anatomy of an installed project](../guide/02-project-map.md) — sealed vs customer trees
115
+ - [Migrations](migrations.md) · [Deployment](deployment.md) · [Troubleshooting](troubleshooting.md)
116
+ - [CLI Reference](../reference/cli.md) — `upgrade`, `verify`, `doctor`
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: People
3
+ audience: both
4
+ kind: entity
5
+ verified_against: bc17374
6
+ verified_on: 2026-11-02
7
+ source_files:
8
+ - .framework/migrations/001_core_schema.sql
9
+ - .framework/functions/auth.ts
10
+ - .framework/functions/invites.ts
11
+ - .framework/functions/security.ts
12
+ - .framework/src/contexts/AuthContext.tsx
13
+ - .framework/src/pages/auth/RegisterPage.tsx
14
+ ---
15
+
16
+ # People
17
+
18
+ ## What this is for
19
+
20
+ A **person** is a human user — the `public.people` row bound to a Supabase
21
+ auth user. Every signed-in session resolves to exactly one person; that row
22
+ carries the account placement, role, and status that gate everything the
23
+ user can see and do. Watchers, messages, notification recipients, and
24
+ pipeline "who did this" all reference `people.id`.
25
+
26
+ ## Fields and settings
27
+
28
+ | Column | Notes |
29
+ |---|---|
30
+ | `auth_uid` | UNIQUE — the Supabase `auth.users.id`. `NULL` on invite stubs (person created before they register). |
31
+ | `email` | UNIQUE, required. Login + invite identity. |
32
+ | `full_name` | required display name. |
33
+ | `account_id` | required — the account the person is placed in. **Falsy `account_id` = "unplaced"**: the router forces `/register?resume=1` until placement completes. |
34
+ | `role_id` | FK → `roles`. The person's role slug drives app gating and record permissions. |
35
+ | `status` | default `'active'`. `'pending'` users are gated to the pending-approval page by the router. |
36
+ | `type_id` | required — people are typed records like everything else. |
37
+ | `data` | jsonb — non-system fields per the person type's `design_schema`. |
38
+ | `avatar_url`, `phone`, `is_active` | Profile/lifecycle flags. |
39
+
40
+ **Downstream effects**
41
+
42
+ - `useAuth().user` (`@core/contexts/AuthContext`) is derived from this row +
43
+ role join: `roles: string[]`, `account_id`, `is_system_admin`, `status`.
44
+ - The registration lifecycle: register → (email confirm) → person row with
45
+ `account_id` unset = unplaced → `/register?resume=1` finishes placement →
46
+ `status: 'pending'` requires approval → `active`.
47
+ - `handle_new_auth_user` grants `system_admin` only when `people` is empty —
48
+ the first registrant claims the instance (see
49
+ [Security checklist](operations/security-checklist.md)).
50
+ - `people.role` comes back from Supabase as object *or* array depending on
51
+ the join — normalize before reading `.slug`.
52
+
53
+ ## Minimal configuration
54
+
55
+ People rows are created by registration/invites, not by hand — but a valid
56
+ person requires: `email`, `full_name`, `account_id`, `role_id`, `type_id`
57
+ (the person type), `status: 'active'`.
58
+
59
+ ## Full configuration
60
+
61
+ Adds `avatar_url`, `phone`, `data` (type-defined fields), `app_id`
62
+ (which app created them), `is_active`.
63
+
64
+ ## Example
65
+
66
+ An invite flow creates a person stub (`auth_uid: null`, `status: 'pending'`,
67
+ `account_id` + `role_id` set by the invite's `config`); when the invitee
68
+ registers with that email, `auth_uid` binds and they land already placed —
69
+ skipping the unplaced dance entirely.
70
+
71
+ ## Core files
72
+
73
+ - `.framework/migrations/001_core_schema.sql` — table + constraints
74
+ - `.framework/functions/auth.ts` — session → person + role resolution
75
+ - `.framework/functions/invites.ts` — invite stubs and placement config
76
+ - `.framework/src/contexts/AuthContext.tsx` — client `useAuth` shape
77
+ - `.framework/src/pages/auth/RegisterPage.tsx` — placement/resume flow
78
+
79
+ ## Agent notes
80
+
81
+ - Never `INSERT` a person as a substitute for registration — `auth_uid`
82
+ binding and account placement happen through auth/invite paths.
83
+ - `status: 'pending'` + truthy `account_id` → pending-approval gate;
84
+ falsy `account_id` → resume-registration routing. Different fixes.
85
+ - `person_id` is the FK used by `watchers`, `messages`, `notifications`,
86
+ `item_progress` — it's `people.id`, never `auth_uid`.
87
+ - `onAuthStateChange` callbacks must never await Supabase calls —
88
+ auth-js awaits listeners inside `initializePromise`, so
89
+ `fetchUserContext → apiFetch → getAuthHeaders → getSession` deadlocks
90
+ until `SESSION_TIMEOUT_MS` (15 s of spinner on every expired-session
91
+ visit). Defer the work (`setTimeout`) inside the callback.