spine-framework 0.4.3 → 0.5.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/agent-runner.ts +1000 -81
  15. package/.framework/functions/_shared/cil.ts +2 -1
  16. package/.framework/functions/_shared/db.ts +3 -6
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/kb.ts +61 -7
  19. package/.framework/functions/_shared/logging.ts +562 -224
  20. package/.framework/functions/_shared/message-sequence.ts +53 -0
  21. package/.framework/functions/_shared/middleware.ts +47 -44
  22. package/.framework/functions/_shared/pipeline-runner.ts +150 -37
  23. package/.framework/functions/_shared/principal.ts +9 -6
  24. package/.framework/functions/_shared/trigger-engine.ts +149 -40
  25. package/.framework/functions/admin-data.ts +48 -31
  26. package/.framework/functions/ai-agents.ts +103 -33
  27. package/.framework/functions/api-keys.ts +301 -122
  28. package/.framework/functions/apps.ts +43 -26
  29. package/.framework/functions/cil-ingest.ts +48 -15
  30. package/.framework/functions/debug-auth.ts +14 -8
  31. package/.framework/functions/deliveries.ts +39 -12
  32. package/.framework/functions/embeddings.ts +145 -76
  33. package/.framework/functions/integration-outbound.ts +67 -27
  34. package/.framework/functions/integrations.ts +254 -127
  35. package/.framework/functions/logs.ts +273 -133
  36. package/.framework/functions/observability.ts +277 -147
  37. package/.framework/functions/pipeline-executions.ts +1 -1
  38. package/.framework/functions/pipeline-runner-background.ts +120 -0
  39. package/.framework/functions/pipelines.ts +14 -9
  40. package/.framework/functions/prompt-configs.ts +7 -4
  41. package/.framework/functions/roles.ts +8 -5
  42. package/.framework/functions/security.ts +30 -17
  43. package/.framework/functions/system-cron.ts +250 -128
  44. package/.framework/functions/tests.ts +7 -6
  45. package/.framework/functions/timers.ts +310 -107
  46. package/.framework/functions/trigger-scheduler.ts +65 -5
  47. package/.framework/functions/triggers.ts +28 -13
  48. package/.framework/functions/types.ts +13 -10
  49. package/.framework/migrations/064_episode_leases.sql +196 -0
  50. package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
  51. package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
  52. package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
  53. package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
  54. package/.framework/migrations/069_log_stats_window.sql +54 -0
  55. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  56. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  57. package/.framework/migrations/072_slug_autofill.sql +77 -0
  58. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  59. package/.framework/schemas/spine.config.schema.json +62 -0
  60. package/.framework/src/App.tsx +3 -1
  61. package/.framework/src/apps/admin/index.tsx +1141 -775
  62. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  63. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  64. package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
  65. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  66. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  67. package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
  68. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  69. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  70. package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
  71. package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
  72. package/.framework/src/components/layout/Layout.tsx +4 -2
  73. package/.framework/src/components/layout/Sidebar.tsx +304 -179
  74. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  75. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  76. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  77. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  78. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  79. package/.framework/src/components/ui/sheet.tsx +24 -26
  80. package/.framework/src/components/ui/textarea.tsx +74 -48
  81. package/.framework/src/hooks/useBranding.ts +47 -0
  82. package/.framework/src/hooks/useEntityList.ts +12 -9
  83. package/.framework/src/hooks/useListSchema.ts +108 -58
  84. package/.framework/src/instructions/README.md +57 -0
  85. package/.framework/src/instructions/ai-agents.md +69 -0
  86. package/.framework/src/instructions/alerts.md +80 -0
  87. package/.framework/src/instructions/api-keys.md +87 -0
  88. package/.framework/src/instructions/apps.md +77 -0
  89. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  90. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  91. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  92. package/.framework/src/instructions/concepts/automation.md +186 -0
  93. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  94. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  95. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  96. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  97. package/.framework/src/instructions/concepts/observability.md +116 -0
  98. package/.framework/src/instructions/concepts/permissions.md +150 -0
  99. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  100. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  101. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  102. package/.framework/src/instructions/deliveries.md +59 -0
  103. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  104. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  105. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  106. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  107. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  108. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  109. package/.framework/src/instructions/integrations.md +96 -0
  110. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  111. package/.framework/src/instructions/invites.md +60 -0
  112. package/.framework/src/instructions/kb.md +80 -0
  113. package/.framework/src/instructions/logs.md +77 -0
  114. package/.framework/src/instructions/observability.md +61 -0
  115. package/.framework/src/instructions/operations/deployment.md +115 -0
  116. package/.framework/src/instructions/operations/migrations.md +125 -0
  117. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  118. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  119. package/.framework/src/instructions/operations/upgrade.md +116 -0
  120. package/.framework/src/instructions/pipeline-executions.md +62 -0
  121. package/.framework/src/instructions/pipelines.md +78 -0
  122. package/.framework/src/instructions/prompts.md +95 -0
  123. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  124. package/.framework/src/instructions/reference/api/auth.md +113 -0
  125. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  126. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  127. package/.framework/src/instructions/reference/cli.md +124 -0
  128. package/.framework/src/instructions/reference/database.md +132 -0
  129. package/.framework/src/instructions/reference/design-schema.md +280 -0
  130. package/.framework/src/instructions/reference/env-vars.md +133 -0
  131. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  132. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  133. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  134. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  135. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  136. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  137. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  138. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  139. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  140. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  141. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  142. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  143. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  144. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  145. package/.framework/src/instructions/reference/functions/index.md +103 -0
  146. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  147. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  148. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  149. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  150. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  151. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  152. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  153. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  154. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  155. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  156. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  157. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  158. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  159. package/.framework/src/instructions/reference/functions/security.md +140 -0
  160. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  161. package/.framework/src/instructions/reference/functions/system.md +106 -0
  162. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  163. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  164. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  165. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  166. package/.framework/src/instructions/reference/functions/types.md +146 -0
  167. package/.framework/src/instructions/reference/seeds.md +126 -0
  168. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  169. package/.framework/src/instructions/reference/spine-config.md +114 -0
  170. package/.framework/src/instructions/roles.md +80 -0
  171. package/.framework/src/instructions/runtime-detail.md +70 -0
  172. package/.framework/src/instructions/runtime-list.md +77 -0
  173. package/.framework/src/instructions/security.md +67 -0
  174. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  175. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  176. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  177. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  178. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  179. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  180. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  181. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  182. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  183. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  184. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  185. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  186. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  187. package/.framework/src/instructions/testing.md +59 -0
  188. package/.framework/src/instructions/timers.md +82 -0
  189. package/.framework/src/instructions/triggers.md +69 -0
  190. package/.framework/src/instructions/types.md +93 -0
  191. package/.framework/src/lib/branding.ts +128 -0
  192. package/.framework/src/pages/NoAppPage.tsx +3 -1
  193. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  194. package/.framework/src/pages/SetupPage.tsx +5 -2
  195. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  196. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  197. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  198. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  199. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  200. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  201. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  202. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  203. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  204. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  205. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  206. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  207. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  208. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  209. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  210. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  211. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  212. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  213. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  214. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  215. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  216. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  217. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  218. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  219. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  220. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  221. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  222. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  223. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  224. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  225. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  226. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  227. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  228. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  229. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  230. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  231. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  232. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  233. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  234. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  235. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  236. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  237. package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
  238. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
  239. package/.framework/src/pages/auth/LoginPage.tsx +4 -2
  240. package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
  241. package/.framework/templates/agent/AGENTS.md +150 -0
  242. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  243. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  244. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  245. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  246. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  247. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  248. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  249. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  250. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  251. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  252. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  253. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  254. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  255. package/.framework/templates/project.json +5 -1
  256. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  257. package/.framework/tests/unit/agent-files.test.ts +355 -0
  258. package/.framework/tests/unit/agent-runner.test.ts +496 -47
  259. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  260. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  261. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  262. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  263. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  264. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  265. package/.framework/tests/unit/episode-lease.test.ts +695 -0
  266. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  267. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  268. package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
  269. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  270. package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
  271. package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
  272. package/.framework/tests/unit/principal-cache.test.ts +15 -0
  273. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  274. package/.framework/tests/unit/system-cron.test.ts +240 -0
  275. package/.framework/tests/unit/timers.test.ts +159 -0
  276. package/.framework/tests/unit/trigger-engine.test.ts +278 -91
  277. package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
  278. package/README.md +125 -166
  279. package/config/tsconfig.cli.json +2 -2
  280. package/netlify.toml +5 -0
  281. package/package.json +15 -4
  282. package/templates/project/package-lock.json +2 -2
  283. package/.framework/docs/admin-routes.md +0 -76
  284. package/.framework/docs/api-docs-progress.md +0 -38
  285. package/.framework/docs/api-governance.md +0 -146
  286. package/.framework/docs/api-testing-results.md +0 -212
  287. package/.framework/docs/assembly-launch-guide.md +0 -138
  288. package/.framework/docs/audit-results.md +0 -590
  289. package/.framework/docs/db-api-inventory.md +0 -95
  290. package/.framework/docs/gap-remediation-backlog.md +0 -103
  291. package/.framework/docs/ui-adoption-verification.md +0 -111
  292. package/.framework/docs/ui-api-coverage.md +0 -84
  293. package/.framework/docs/v2-compatibility-audit.md +0 -228
  294. package/.framework/mcp/index.ts +0 -133
  295. package/.framework/mcp/load-env.ts +0 -60
  296. package/.framework/mcp/shared/app-manifest.ts +0 -275
  297. package/.framework/mcp/shared/db.ts +0 -477
  298. package/.framework/mcp/shared/kb.ts +0 -118
  299. package/.framework/mcp/tools/architect.ts +0 -431
  300. package/.framework/mcp/tools/cil/context.ts +0 -345
  301. package/.framework/mcp/tools/cil/graph.ts +0 -242
  302. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  303. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  304. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  305. package/.framework/mcp/tools/cil/project.ts +0 -791
  306. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  307. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  308. package/.framework/mcp/tools/cil/search.ts +0 -97
  309. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  310. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  311. package/.framework/mcp/tools/kb/delete.ts +0 -139
  312. package/.framework/mcp/tools/kb/index.ts +0 -52
  313. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  314. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  315. package/.framework/mcp/tools/kb/publish.ts +0 -100
  316. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  317. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  318. package/.framework/mcp/tools/kb/search.ts +0 -101
  319. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  320. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,118 @@
1
+ ---
2
+ title: Build a custom page
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/components/CustomAppLoader.tsx
9
+ - .framework/src/components/AppWrapper.tsx
10
+ - .framework/src/components/app-shell/GenericAppShell.tsx
11
+ - .framework/src/lib/api.ts
12
+ - .framework/cli/commands/create-app.ts
13
+ - scripts/assemble.sh
14
+ ---
15
+
16
+ # Build a custom page
17
+
18
+ ## Goal
19
+
20
+ Ship a React page inside a custom app (`renderer: 'custom'`) instead of the
21
+ generic list/detail renderer. See
22
+ [Apps and renderers](../concepts/apps-and-renderers.md) for the model.
23
+
24
+ ## When to go custom
25
+
26
+ Stay on `renderer: 'generic'` when the app is CRUD over typed records — the
27
+ generic renderer gives you list/detail/create/sort/permission-aware editing
28
+ for free from `views.default_list` / `views.default_detail`. Go custom for
29
+ dashboards, wizards, bespoke layouts, or anything not expressible as
30
+ schema-driven list/detail — `GenericAppShell` only generates routes for
31
+ nav items carrying `type_slug`
32
+ (`.framework/src/components/app-shell/GenericAppShell.tsx`).
33
+
34
+ ## Steps
35
+
36
+ 1. **Scaffold the app** (or reuse one):
37
+
38
+ ```bash
39
+ npx spine-framework create-app <slug> --role <role-slug>
40
+ ```
41
+
42
+ Writes `custom/apps/<slug>/` with `manifest.json`, `index.tsx`, and
43
+ `components/`, and inserts the `apps` row with `renderer: 'custom'`
44
+ (`.framework/cli/commands/create-app.ts`). Pass `--role` a slug
45
+ that exists in `roles` — the default `member` is seeded by
46
+ `073_seed_member_roles.sql`.
47
+
48
+ 2. **Add pages under `custom/apps/<slug>/pages/`.** `CustomAppLoader`
49
+ lazy-imports only `custom/apps/<slug>/index.tsx`
50
+ (`import.meta.glob('../../../custom/apps/*/index.tsx')`,
51
+ `.framework/src/components/CustomAppLoader.tsx`) — `index.tsx`
52
+ default-exports the component (usually its own `<Routes>`) and imports
53
+ the page components itself.
54
+
55
+ ```tsx
56
+ // custom/apps/<slug>/pages/DashboardPage.tsx
57
+ export default function DashboardPage() { /* ... */ }
58
+ ```
59
+
60
+ 3. **Set the app row.** `renderer: 'custom'`, `route_prefix: '/<slug>'`,
61
+ `min_role` / `required_roles`, `nav_items` for the sidebar. `AppWrapper`
62
+ enforces auth + role before mounting your app
63
+ (`.framework/src/components/AppWrapper.tsx`).
64
+
65
+ 4. **Fetch data through `/api/admin-data`** — never write a bespoke CRUD
66
+ function:
67
+
68
+ ```ts
69
+ import { apiFetch } from '@core/lib/api'
70
+
71
+ const res = await apiFetch(
72
+ '/api/admin-data?action=list&entity=items&type_slug=ticket&limit=50')
73
+ const { data } = await res.json()
74
+ ```
75
+
76
+ `apiFetch` attaches the Supabase Bearer token and the `X-Account-Id`
77
+ header (`.framework/src/lib/api.ts`). Find-or-create related records
78
+ (e.g. a thread for a work item) through the same endpoint — see
79
+ [Build your first app — Step 4](../guide/04-first-app.md) for the full
80
+ call shapes.
81
+
82
+ 5. **Rebuild and run.** `npm run assemble` (or `npm run dev` / `dev:netlify`,
83
+ which run assembly automatically). Custom functions go in
84
+ `custom/functions/` prefixed `custom_` — assembly hard-fails on a
85
+ basename collision with a framework function
86
+ (`scripts/assemble-functions.sh`).
87
+
88
+ ## Verify
89
+
90
+ - Navigate to `/<slug>` — `CustomAppLoader` resolves your `index.tsx`
91
+ (a missing entry shows the `App "<slug>" not found` error naming the
92
+ expected path).
93
+ - `GET /api/apps?action=get&slug=<slug>` shows `renderer: 'custom'` and the
94
+ right `route_prefix`.
95
+ - `npm run typecheck` compiles the page.
96
+
97
+ ## Common mistakes
98
+
99
+ - **A bespoke CRUD endpoint.** Custom Netlify functions that
100
+ create/update/list runtime records bypass `admin-data`'s permission,
101
+ type-resolution, audit, and trigger logic — call `/api/admin-data` from
102
+ the client instead (see [Write a function](./write-a-function.md) for the
103
+ cases where a custom function *is* allowed).
104
+ - **Forgetting `index.tsx` is the only entry.** New pages in `pages/` are
105
+ invisible until `index.tsx` routes to them.
106
+ - **`type_slug` nav items on a custom app** — they only do something under
107
+ the generic renderer; use plain `path` nav items and route them yourself.
108
+ - **Editing before re-assembling** — `CustomAppLoader`'s glob is evaluated
109
+ at build time; new files need an assemble/dev restart.
110
+
111
+ ## Reference
112
+
113
+ - [Apps and renderers](../concepts/apps-and-renderers.md) ·
114
+ [Runtime records](../concepts/runtime-records.md)
115
+ - [App manifest reference](../reference/app-manifest.md) ·
116
+ [admin-data function reference](../reference/functions/admin-data.md)
117
+ - [Build your first app](../guide/04-first-app.md) ·
118
+ [Anatomy of an installed project](../guide/02-project-map.md)
@@ -0,0 +1,127 @@
1
+ ---
2
+ title: Package and install an app
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/install-app.ts
9
+ - .framework/cli/commands/validate-app.ts
10
+ - .framework/functions/_shared/app-manifest.ts
11
+ - .framework/cli/commands/migrate.ts
12
+ ---
13
+
14
+ # Package and install an app
15
+
16
+ ## Goal
17
+
18
+ Package a `custom/apps/<slug>/` directory as an npm package and install it
19
+ into a Spine project — files copied, `apps` row upserted, migrations run,
20
+ seeds applied. Field-level manifest semantics are in
21
+ [App manifest reference](../reference/app-manifest.md) and
22
+ [Seeds reference](../reference/seeds.md).
23
+
24
+ ## Steps
25
+
26
+ 1. **Author `manifest.json`.** Required fields the installer validates:
27
+ `slug` (must equal the directory name and match
28
+ `^[a-z][a-z0-9-]*$`), plus `name`, `route_prefix`, `renderer`,
29
+ `min_role` / `required_roles`, `nav_items`, `config`, `registration`.
30
+ See [App manifest reference](../reference/app-manifest.md) for every
31
+ field.
32
+
33
+ 2. **Author seed files.** The `seed` block is a list of
34
+ `{ file, table, conflict, inject_app_id?, ignore_duplicates?, permissions_patch? }`
35
+ entries, each pointing at a JSON file of rows
36
+ (`.framework/cli/commands/install-app.ts`):
37
+
38
+ - `conflict` — the `onConflict` column list for the upsert. Types use
39
+ `conflict: "kind,slug"` with `inject_app_id: false` — types are
40
+ **global**, not duplicated per install.
41
+ - `inject_app_id` — stamps `app_id` on each seeded row (default `true`);
42
+ set `false` for global tables like `types`.
43
+ - `ignore_duplicates` — `ignoreDuplicates` on the upsert; `types` rows
44
+ always get it (`.framework/cli/commands/install-app.ts`).
45
+ - `permissions_patch` — a types-table entry that merges
46
+ `record_permissions` into existing design schemas instead of
47
+ overwriting; ranks just after `types` in apply order.
48
+ - If no `seed` array is declared, the conventional files
49
+ `seed/roles.json`, `seed/types.json`, `seed/link-types.json` are used
50
+ (`.framework/cli/commands/install-app.ts`).
51
+
52
+ Seeds apply in dependency order — `roles`, `types`, `link_types`,
53
+ `actions`, `ai_agents`, `prompt_configs`, `pipelines`, `triggers` —
54
+ regardless of declaration order
55
+ (`.framework/cli/commands/install-app.ts`). Pipeline seeds are
56
+ validated against `public.actions` stage slugs, and trigger seeds
57
+ resolve `pipeline_slug` → `pipeline_id`.
58
+
59
+ 3. **Package it.** Ship the app directory as an npm package whose files
60
+ include `index.tsx`, `manifest.json`, and optionally `pages/`,
61
+ `components/`, `hooks/`, `utils/`, `functions/`, `seed/`, `public/` —
62
+ the installer copies exactly that allowlist
63
+ (`.framework/cli/commands/install-app.ts`). Validate locally
64
+ with `npx spine-framework validate-app`.
65
+
66
+ 4. **Install.**
67
+
68
+ ```bash
69
+ npx spine-framework install-app <package>
70
+ ```
71
+
72
+ Flow: `npm install <package>` → resolve slug → copy files to
73
+ `custom/apps/<slug>/` (a reinstall preserves the local `manifest.json`)
74
+ → `runMigrations` → `seedApp` → sync the `registration` block into
75
+ `apps.config` and `spine.config.json`
76
+ (`.framework/cli/commands/install-app.ts`). Requires
77
+ `SUPABASE_URL` + `SUPABASE_DB_PASSWORD` + `SUPABASE_SERVICE_ROLE_KEY`;
78
+ `--no-seed` skips all DB work.
79
+
80
+ 5. **Update paths.**
81
+
82
+ - `update-app <package>` — `npm install @latest` + copy files + sync
83
+ registration. **It does not re-run migrations or seeds.**
84
+ - `update-db-app <slug>` — upserts the `apps` row from the local
85
+ `manifest.json` and syncs registration; no npm, no file changes.
86
+ - `seed-app <slug>` — re-runs just the seed block for a local app;
87
+ `--reconcile-owned-types` interactively updates `manifest.owned_types`
88
+ slugs whose DB `design_schema` drifted from the seed.
89
+
90
+ 6. **Uninstall.** `uninstall-app <package>` removes
91
+ `custom/apps/<slug>/` and runs `npm uninstall` — and nothing else. The
92
+ `--keep-data` flag is parsed but never read: seeded rows are **not**
93
+ deleted either way (`.framework/cli/commands/install-app.ts`).
94
+
95
+ ## Verify
96
+
97
+ - `npx spine-framework status` lists the installed app and version.
98
+ - `GET /api/apps?action=get&slug=<slug>` shows the upserted row;
99
+ `migrations status` shows migration state.
100
+ - Seeded rows exist: `GET /api/types?action=list` (or the seeded table's
101
+ endpoint) includes the app's types/roles.
102
+
103
+ ## Common mistakes
104
+
105
+ - **Types not global.** Forgetting `inject_app_id: false` on a types seed
106
+ injects `app_id` into global type rows — and `conflict: "kind,slug"`
107
+ means a second app with the same slug silently skips or errors on
108
+ schema drift. Prefix slugs.
109
+ - **Expecting `uninstall --keep-data` to matter** — it is a no-op; seeded
110
+ rows always survive uninstall.
111
+ - **`update-app` doesn't re-seed** — new seed rows in the package never
112
+ reach the DB; run `seed-app <slug>` or `update-db-app` after.
113
+ - **Seed rows referencing unseeded slugs** — `owned_types`,
114
+ `pipeline_slug`, and `send_notification` recipients are validated
115
+ fail-closed before any DB writes; fix the manifest, don't retry.
116
+ - **`SUPABASE_URL` must be a hosted `https://<ref>.supabase.co` URL** —
117
+ the migrator rejects anything else before connecting
118
+ (`.framework/cli/commands/migrate.ts`).
119
+
120
+ ## Reference
121
+
122
+ - [App manifest reference](../reference/app-manifest.md) ·
123
+ [Seeds reference](../reference/seeds.md) ·
124
+ [CLI reference](../reference/cli.md) ·
125
+ [spine.config.json reference](../reference/spine-config.md)
126
+ - [Apps and renderers](../concepts/apps-and-renderers.md) ·
127
+ [Write a migration](./write-a-migration.md)
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Test your code
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - vitest.config.ts
9
+ - .framework/tests/setup.ts
10
+ - .framework/tests/reporter.ts
11
+ - package.json
12
+ - config/tsconfig.json
13
+ - config/tsconfig.functions.json
14
+ ---
15
+
16
+ # Test your code
17
+
18
+ ## Goal
19
+
20
+ Run the Vitest suite and the TypeScript checks, and place new tests where
21
+ the runner picks them up.
22
+
23
+ ## Steps
24
+
25
+ 1. **Know the runners** (`package.json` scripts):
26
+
27
+ - `npm run test` → `vitest run` — everything matching the `include`
28
+ globs in `vitest.config.ts`: `.framework/tests/unit/**/*.test.ts`,
29
+ `.framework/tests/api/**/*.test.ts`, `.framework/tests/integration/**/*.test.ts`,
30
+ and `custom/tests/**/*.test.ts`.
31
+ - `npm run test:unit` — the unit suite only.
32
+ - `npm run test:integration` — the integration suite (needs real env,
33
+ see below).
34
+ - `npm run test:core` — the core-isolation suite.
35
+ - `npm run typecheck` → `tsc -b config/tsconfig.json` — frontend +
36
+ CLI. Functions are **not** covered here: check them separately with
37
+ `npx tsc --noEmit -p config/tsconfig.functions.json` after
38
+ `npm run assemble` (it typechecks the assembled layout).
39
+ - `npx spine-framework test [suite]` — the CLI wrapper
40
+ (`--watch`, `--reporter`, `--json`; see [CLI reference](../reference/cli.md)).
41
+
42
+ 2. **Put tests where the globs look.** New project tests belong in
43
+ `custom/tests/**/*.test.ts` — picked up automatically, and they keep
44
+ `.framework/` untouched. Framework-level additions go under
45
+ `.framework/tests/unit/`. Test files inside `custom/functions/` are
46
+ excluded from function assembly (`*-test.ts`, `*.test.ts` are skipped
47
+ by `scripts/assemble-functions.sh`), but naming convention is still
48
+ `*.test.ts`.
49
+
50
+ 3. **Understand the env layering.** `vitest.config.ts` loads a root
51
+ `.env.test` file (if present) into `process.env` **before** any module
52
+ graph runs — that is how integration/API tests get real Supabase
53
+ credentials before `db.ts`/`principal.ts` call `createClient` at import
54
+ time. `.framework/tests/setup.ts` then stubs `SUPABASE_URL`,
55
+ `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY` so unit tests never
56
+ crash on missing env. Integration helpers additionally require
57
+ `SPINE_TEST_ACCOUNT_ID`; API-surface tests use `SPINE_DEV_URL`,
58
+ `SPINE_DEV_JWT`, `SPINE_TEST_API_KEY` (see
59
+ [Environment variables](../reference/env-vars.md)).
60
+
61
+ 4. **Mock at module boundaries.** The config sets `isolate: true` — every
62
+ test file gets a fresh module registry, so `vi.mock` on
63
+ `./_shared/db`-style imports works per file. Existing unit suites mock
64
+ the Supabase client, principal resolution, and the LLM/embedding calls
65
+ rather than hitting the network (browse `.framework/tests/unit/` for
66
+ patterns — `pipeline-runner.test.ts`, `permissions.test.ts`,
67
+ `principal-cache.test.ts`).
68
+
69
+ 5. **Integration tests need a real project.** They exercise admin-data,
70
+ triggers, pipelines, and schema validation against a live Supabase
71
+ project using a service-role client and a seeded test account —
72
+ `custom/tests` and the unit suite run with no credentials, the
73
+ integration suite does not. `passWithNoTests: true` means a missing
74
+ env never fails the suite outright, so check that tests actually ran.
75
+
76
+ ## Verify
77
+
78
+ - `npm run test` exits 0; `vitest` prints per-file pass counts. A custom
79
+ reporter that persists runs to `public.test_runs` exists at
80
+ `.framework/tests/reporter.ts` but is **not** registered — the config
81
+ uses `reporters: ['default']`, so opt in with `--reporter` if you want
82
+ persisted runs.
83
+ - `npm run typecheck` and
84
+ `npx tsc --noEmit -p config/tsconfig.functions.json` are both clean.
85
+ - `npx spine-framework doctor` sanity-checks the environment when tests
86
+ behave oddly ([CLI reference](../reference/cli.md)).
87
+
88
+ ## Common mistakes
89
+
90
+ - **`typecheck` doesn't cover functions.** `config/tsconfig.json` excludes
91
+ the functions project by design — always run the
92
+ `tsconfig.functions.json` check too, and only after `npm run assemble`.
93
+ - **Assuming integration tests ran.** With no `.env.test`, integration
94
+ files that can't build a client may skip silently — grep the output for
95
+ your file's pass count.
96
+ - **Importing `createClient` at module top-level in a unit test.** Setup
97
+ stubs the env, but a module that builds a client at import time before
98
+ `setupFiles` runs still fails — mock the module or import lazily.
99
+ - **Writing repo-only paths into `custom/tests`** — tests there must run
100
+ in an installed project too; import from `custom/` and assembled
101
+ aliases, not from repo internals.
102
+
103
+ ## Reference
104
+
105
+ - [CLI reference](../reference/cli.md) — `test`, `doctor`
106
+ - [Environment variables](../reference/env-vars.md) — `SPINE_TEST_*`,
107
+ `SPINE_DEV_*`, `SUPABASE_*`
108
+ - [Write a function](./write-a-function.md) — assembled-layout imports
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: Write a function
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/middleware.ts
9
+ - .framework/functions/_shared/db.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/functions/_shared/logging.ts
12
+ - .framework/functions/timers.ts
13
+ - .framework/public/_redirects
14
+ - scripts/assemble-functions.sh
15
+ ---
16
+
17
+ # Write a function
18
+
19
+ ## Goal
20
+
21
+ Add a custom Netlify function when — and only when — the generic
22
+ `/api/admin-data` endpoint cannot cover the case: specialized auth, inbound
23
+ webhooks, heavy compute, or bespoke orchestration. Record CRUD must go
24
+ through `admin-data` so permission, validation, audit, and trigger logic
25
+ stay shared ([Runtime records](../concepts/runtime-records.md)).
26
+
27
+ ## Steps
28
+
29
+ 1. **Decide it is warranted.** `admin-data` already covers list/get/create/
30
+ update/remove/stats across every runtime table with permissions and
31
+ triggers enforced ([admin-data reference](../reference/functions/admin-data.md)).
32
+ Legitimate custom functions: public webhook receivers, scheduled
33
+ sweeps, compute-heavy transforms, auth-bridging endpoints.
34
+
35
+ 2. **Create the file.** Put it in `custom/functions/` (or
36
+ `custom/apps/<slug>/functions/` for app-scoped code) with a `custom_`
37
+ prefix — assembly copies `.framework/functions`, `custom/functions`,
38
+ and `custom/apps/*/functions` into one flat Netlify dir and **fails the
39
+ build on a basename collision**
40
+ (`scripts/assemble-functions.sh`). All functions are reachable at
41
+ `/api/<basename>` via the `_redirects` rule
42
+ (`.framework/public/_redirects`).
43
+
44
+ Imports resolve in the **assembled** layout, not the source tree:
45
+ `./_shared/*` and `./<other-function>` are sibling paths inside
46
+ `.assembled/netlify/functions/`, and `@shared/*` is an alias to the
47
+ same dir (`config/tsconfig.functions.json`). A function's own
48
+ `functions/_shared -> .framework/functions/_shared` symlink (as the
49
+ existing app functions dirs carry) makes `./_shared` resolve in the
50
+ source tree too.
51
+
52
+ ```ts
53
+ // custom/functions/custom_my-endpoint.ts
54
+ import { createHandler, error, json } from './_shared/middleware'
55
+ import { adminDb } from './_shared/db'
56
+ import { PermissionEngine } from './_shared/permissions'
57
+ import { emitLog } from './_shared/logging'
58
+
59
+ export const handler = createHandler(async (ctx, body) => {
60
+ // ctx.requestId, ctx.principal, ctx.db, ctx.accountId, ctx.appId,
61
+ // ctx.query, ctx.headers, ctx.trace — all populated by middleware
62
+ if (!PermissionEngine.isSystemAdmin(ctx)) {
63
+ throw new Error('System admin required')
64
+ }
65
+ const { data, error: err } = await ctx.db
66
+ .from('items').select('id,title').eq('is_active', true).limit(10)
67
+ if (err) throw err
68
+ return data
69
+ })
70
+ ```
71
+
72
+ 3. **Follow the `createHandler` contract** (`.framework/functions/_shared/middleware.ts`):
73
+
74
+ - The middleware resolves the principal (`x-api-key` → internal secret →
75
+ Bearer JWT), builds `RequestContext`, and wraps your return value in
76
+ the `{ data, error, meta }` envelope. Thrown `Error`s become
77
+ `{ error }` responses — don't build response objects yourself.
78
+ - `ctx.db` is the principal-scoped client (humans get a user-scoped,
79
+ RLS-bound client; machines/anonymous get `adminDb`).
80
+ - `ctx.query` merges `queryStringParameters`; `ctx.query.method` carries
81
+ the HTTP method. Functions dispatching on `?action=` use a
82
+ `switch (ctx.query.action)` (see `.framework/functions/timers.ts`).
83
+ - For endpoints that must accept anonymous or raw requests, use
84
+ `createPublicHandler` instead (same file, `createPublicHandler`).
85
+
86
+ 4. **Use the `_shared` modules, don't reinvent them** — `db` (`adminDb`,
87
+ `getUserDb`), `permissions` (`PermissionEngine`, `isSystemAdmin`,
88
+ `sanitizeRecordData`), `principal` (`resolvePrincipal`,
89
+ `SYSTEM_PRINCIPAL`), `audit`/`logging` (`emitLog`, `emitAudit`),
90
+ `resolve-ids`, `rate-limit` — see
91
+ [Shared modules reference](../reference/shared-modules.md) and
92
+ [Functions index](../reference/functions/index.md).
93
+
94
+ 5. **Assemble and run.** `npm run assemble && npm run dev:netlify`, then
95
+ `curl http://localhost:8888/api/custom_my-endpoint` — see
96
+ [Install and first run](../guide/01-install-and-first-run.md).
97
+ Function typecheck is separate from `npm run typecheck`:
98
+ `npx tsc --noEmit -p config/tsconfig.functions.json` checks the
99
+ assembled output (run it after `assemble`).
100
+
101
+ ## Verify
102
+
103
+ - `GET /api/custom_my-endpoint` returns the `{ data, error, meta }`
104
+ envelope; unauthenticated calls return `401 Authentication required`
105
+ from the middleware, not from your code.
106
+ - `npm run assemble` passes (no basename collision error) and
107
+ `npm run typecheck` compiles.
108
+
109
+ ## Common mistakes
110
+
111
+ - **Duplicating a framework basename.** Assembly hard-fails naming both
112
+ sources — prefix custom files `custom_`.
113
+ - **Returning raw Response objects.** `createHandler` expects a return
114
+ value (serialized into `data`) or a thrown error — don't emit Netlify
115
+ `{statusCode, body}` shapes yourself inside a `createHandler` function.
116
+ - **Using `adminDb` for user-facing reads.** It bypasses RLS — prefer
117
+ `ctx.db` unless you intentionally need service-role access.
118
+ - **Putting secrets in the response.** `sanitizeRecordData` /
119
+ `omitSecretFields` exist for a reason — strip credentials before
120
+ returning.
121
+ - **Writing record CRUD** — if the function creates/updates `items`,
122
+ `threads`, etc., you are bypassing `admin-data`; call it via
123
+ `adminDataCreate`/`/api/admin-data` instead.
124
+
125
+ ## Reference
126
+
127
+ - [Shared modules reference](../reference/shared-modules.md) ·
128
+ [Functions index](../reference/functions/index.md) ·
129
+ [Auth and API keys](../concepts/auth-and-api-keys.md)
130
+ - [admin-data reference](../reference/functions/admin-data.md) ·
131
+ [Build a custom page](./build-a-custom-page.md) ·
132
+ [Anatomy of an installed project](../guide/02-project-map.md)
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Write a migration
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/migrate.ts
9
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
10
+ - .framework/migrations/053_builtin_action_seeds.sql
11
+ ---
12
+
13
+ # Write a migration
14
+
15
+ ## Goal
16
+
17
+ Add a `.sql` file that the `migrate` command applies exactly once, in order,
18
+ tracked in the `_spine_migrations` ledger table. See
19
+ [Database reference](../reference/database.md) for the table map.
20
+
21
+ ## Steps
22
+
23
+ 1. **Pick the directory.**
24
+
25
+ - `custom/migrations/` — project/app migrations (**default choice**).
26
+ `collectMigrations` reads `.framework/migrations/*.sql` first, then
27
+ `custom/migrations/*.sql`, each sorted lexically — custom migrations
28
+ always run after every framework migration
29
+ (`.framework/cli/commands/migrate.ts`).
30
+ - `.framework/migrations/` — only for framework-owned changes (you are
31
+ editing the framework itself).
32
+
33
+ 2. **Pick the number.** Framework files use `NNN_name.sql` with zero-padded
34
+ sequence numbers. Occupied ranges today: `001`–`021`, `032`–`038`,
35
+ `042`–`062`, `064`–`069`; gaps `22`–`31`, `39`–`41`, `63` are reserved/
36
+ consolidated — **do not reuse gap numbers** (see
37
+ [Database reference](../reference/database.md)). Custom migrations get
38
+ their own files in `custom/migrations/`; filenames are tracked verbatim
39
+ in `_spine_migrations`, so keep them globally unique.
40
+
41
+ 3. **Write idempotent SQL.** Migrations run once per filename — but
42
+ installs re-run them against databases in unknown drifted states, so
43
+ guard everything:
44
+
45
+ ```sql
46
+ ALTER TABLE public.my_table
47
+ ADD COLUMN IF NOT EXISTS my_col text;
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_my_table_col
50
+ ON public.my_table (my_col);
51
+
52
+ INSERT INTO public.roles (slug, name, is_active)
53
+ SELECT 'my_role', 'My Role', true
54
+ WHERE NOT EXISTS (SELECT 1 FROM public.roles WHERE slug = 'my_role');
55
+ ```
56
+
57
+ `CREATE TABLE IF NOT EXISTS`, `ADD COLUMN IF NOT EXISTS`,
58
+ `DROP … IF EXISTS`, `ON CONFLICT … DO NOTHING`, and
59
+ `INSERT … SELECT … WHERE NOT EXISTS` are the house patterns (see
60
+ `.framework/migrations/053_builtin_action_seeds.sql`).
61
+
62
+ 4. **Apply it.**
63
+
64
+ ```bash
65
+ npx spine-framework migrate # framework + custom
66
+ npx spine-framework migrate --dry-run # list pending only
67
+ npx spine-framework migrate --framework-only
68
+ ```
69
+
70
+ Requires `SUPABASE_URL` (a hosted `https://<ref>.supabase.co` URL — the
71
+ CLI rejects anything else before connecting) and
72
+ `SUPABASE_DB_PASSWORD` or `--db-password`
73
+ (`.framework/cli/commands/migrate.ts`).
74
+
75
+ 5. **Check state.** `npx spine-framework migrations list` reads the
76
+ `_spine_migrations` ledger; `migrations status` diffs local files vs
77
+ applied — see [CLI reference](../reference/cli.md).
78
+
79
+ ## Verify
80
+
81
+ - `_spine_migrations` contains the filename: each file runs in a
82
+ transaction, records `INSERT INTO _spine_migrations (filename)`, and the
83
+ runner **stops at the first failure** — fix and re-run
84
+ (`.framework/cli/commands/migrate.ts`).
85
+ - Re-running `migrate` reports the file as skipped.
86
+
87
+ ## Common mistakes
88
+
89
+ - **Renaming a file after it ran.** Tracking is by filename — a rename
90
+ makes it a new migration and re-applies the SQL.
91
+ - **Non-idempotent DDL.** `ALTER TABLE … ADD COLUMN` without
92
+ `IF NOT EXISTS` fails on the second run (e.g. a fresh install that
93
+ already got the column from a later reconciliation migration).
94
+ - **Gap numbers.** `022`–`031`, `039`–`041`, `063` are reserved — using
95
+ them collides with framework history.
96
+ - **Depending on framework ordering.** Custom files all sort *after* every
97
+ framework file; you cannot interleave a custom migration between
98
+ framework ones — express dependencies inside the SQL (e.g.
99
+ `CREATE OR REPLACE`, existence checks).
100
+ - **`SUPABASE_URL` with a port, path, or credentials** — rejected by the
101
+ CLI before it ever connects.
102
+
103
+ ## Reference
104
+
105
+ - [Database reference](../reference/database.md) — table map, numbering
106
+ gaps
107
+ - [CLI reference](../reference/cli.md) — `migrate`, `migrations list`,
108
+ `migrations status`
109
+ - [Package and install an app](./package-and-install-an-app.md) — app
110
+ installers run `migrate` automatically before seeding
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Testing
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/pages/admin/TestingDashboard.tsx
9
+ - .framework/src/pages/admin/TestRunDetailPage.tsx
10
+ - .framework/functions/tests.ts
11
+ - .framework/migrations/001_core_schema.sql
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ The **Testing** page (`/spine-framework/admin/testing`) is a dashboard over `test_runs`/`test_results` — rows written by external test harnesses (e.g. suite runs triggered from the IDE). It summarizes per-suite health and lists recent runs so you can spot a red suite without opening the CI output.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Suite cards** — one per suite (`unit`, `integration`, `api`, `ui`): last status, last run time, last pass/fail/total counts, and total runs (last 30 days).
21
+ - **Recent runs table** — last 20 runs: suite, status (`passed`/`failed`/`running`/etc.), started time, passed, failed, total, duration.
22
+ - **Row click** — navigates to `testing/:run_id` for the individual test-case results (`name`, `status`, `message`, `duration_ms` per case).
23
+ - No actions on this page — runs are produced by harnesses, not created here.
24
+
25
+ **Downstream effects**
26
+
27
+ - Backed by `/api/tests` (function `tests`): `action=stats` aggregates the last 30 days by suite; `action=list` returns recent runs; `action=get&id=` returns one run plus its ordered `test_results`. All require system context.
28
+ - `system` health checks (`/api/system`) and `spine-framework doctor` also read `test_runs` — a stale suite shows up in deploy health.
29
+ - Rows are append-only; there is no cleanup action on this API today.
30
+
31
+ ## Minimal configuration
32
+
33
+ Nothing on the page — have your harness insert `test_runs` (+ `test_results` per case) and the dashboard populates itself.
34
+
35
+ ## Full configuration
36
+
37
+ ```
38
+ GET /.netlify/functions/tests?action=stats # per-suite 30-day rollup
39
+ GET /.netlify/functions/tests?action=list&limit=20
40
+ GET /.netlify/functions/tests?action=get&id=<run uuid> # run + ordered test_results
41
+ ```
42
+
43
+ ## Example
44
+
45
+ A nightly job runs the `integration` suite and writes a `test_runs` row with `failed: 2`. The dashboard's Integration card flips red with last-run counts; click the row to see which two `test_results` failed and their messages.
46
+
47
+ ## Core files
48
+
49
+ - `.framework/src/pages/admin/TestingDashboard.tsx` — this page
50
+ - `.framework/src/pages/admin/TestRunDetailPage.tsx` — run detail route (`testing/:run_id`)
51
+ - `.framework/functions/tests.ts` — API (`stats`, `list`, `get`)
52
+ - Tables: `public.test_runs`, `public.test_results`
53
+
54
+ ## Agent notes
55
+
56
+ - Known mismatch: `TestRunDetailPage` fetches `/api/test-runs?action=...`, but the shipped function is `tests` — the detail route currently fails to load. Use `tests?action=get&id=` until the page is repointed.
57
+ - The suites list is hardcoded (`unit`, `integration`, `api`, `ui`) — a run with a different `suite` value won't get a card, though it still appears in Recent runs.
58
+ - `stats` is a fixed 30-day window capped at 200 rows — high-volume suites need direct table queries for longer trends.
59
+ - Nothing here triggers runs; the "no runs yet" empty state points at running suites from your IDE/CI.