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
package/.env.example CHANGED
@@ -6,20 +6,30 @@ VITE_APP_NAME=Spine
6
6
  VITE_ENVIRONMENT=dev
7
7
 
8
8
  # ── Supabase (frontend — public keys only) ───────────────────────
9
+ # All values in both Supabase blocks come from one page:
10
+ # Supabase Dashboard → Project Settings → API Keys
11
+ # (older dashboards: Settings → API / Data API)
12
+ # Key naming: the "Publishable key" (sb_publishable_…) is the anon key, and
13
+ # a "Secret key" (sb_secret_…) is the service-role key. Legacy anon /
14
+ # service_role JWTs work identically — use whichever your project shows.
9
15
  VITE_SUPABASE_URL=https://your-project.supabase.co
10
- VITE_SUPABASE_ANON_KEY=your-anon-key
16
+ VITE_SUPABASE_ANON_KEY=your-anon-key-or-publishable-key
11
17
 
12
18
  # ── Supabase (backend — Netlify Functions) ───────────────────────
13
19
  SUPABASE_URL=https://your-project.supabase.co
14
- SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
20
+ SUPABASE_SERVICE_ROLE_KEY=your-service-role-or-secret-key
15
21
  # Functions build user-scoped PostgREST clients with the anon key.
16
- SUPABASE_ANON_KEY=your-anon-key
22
+ SUPABASE_ANON_KEY=your-anon-key-or-publishable-key
17
23
 
18
24
  # ── Migrations / CLI ─────────────────────────────────────────────
19
25
  # Postgres password used by `spine-framework migrate` and `install-app`
20
26
  # (connects directly to db.<project-ref>.supabase.co:5432).
21
27
  # Get from: Supabase Dashboard → Project Settings → Database.
22
28
  SUPABASE_DB_PASSWORD=
29
+ # Alternative to SUPABASE_DB_PASSWORD: a full Postgres connection string.
30
+ # Use the Session Pooler host when your network can't reach db.<ref>.supabase.co:5432
31
+ # (direct connections are IPv6-only on most Supabase projects).
32
+ # SUPABASE_DB_URL=postgres://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres
23
33
 
24
34
  # ── App URLs ─────────────────────────────────────────────────────
25
35
  # Base URL for invite links and auth redirects; also the allowlist
@@ -0,0 +1,392 @@
1
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/cli/commands/bootstrap.ts"]
2
+ /**
3
+ * @module bootstrap
4
+ * @audience engineers
5
+ * @layer cli
6
+ * @stability stable
7
+ *
8
+ * `spine-framework bootstrap` — scriptable first-run provisioning. Creates
9
+ * the first `system_admin` person (bound to the `spine-system` account,
10
+ * mirroring the `handle_new_auth_user` trigger columns) and mints the first
11
+ * `sk_` API key, entirely without a browser. With `--email`/`--password` it
12
+ * additionally provisions a login-able Supabase auth user via the admin API.
13
+ *
14
+ * INVARIANT: the raw API key is printed exactly once and only its PBKDF2 hash
15
+ * is persisted — identical to the `api-keys` create path.
16
+ *
17
+ * @seeAlso .framework/migrations/052_registration_security.sql (trigger row shape)
18
+ * @seeAlso .framework/functions/api-keys.ts (key minting/hash logic)
19
+ */
20
+
21
+ import type { Command } from 'commander'
22
+ import { existsSync, readFileSync } from 'fs'
23
+ import { join } from 'path'
24
+ import { createClient } from '@supabase/supabase-js'
25
+ import { hashApiKey } from '../../functions/_shared/hash-api-key.ts'
26
+ import { emitLog } from '../../functions/_shared/audit.ts'
27
+ import { SYSTEM_PRINCIPAL } from '../../functions/_shared/principal.ts'
28
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:module_header:module_header"]
29
+
30
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:PROJECT_ROOT:constant" type="constant" identity="PROJECT_ROOT" parent="~/.framework/cli/commands/bootstrap.ts"]
31
+ const PROJECT_ROOT = process.cwd()
32
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:PROJECT_ROOT:constant"]
33
+
34
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:BootstrapOptions:interface" type="interface" identity="BootstrapOptions" parent="~/.framework/cli/commands/bootstrap.ts"]
35
+ /**
36
+ * Options accepted by `runBootstrap`. `scopes` entries of `system_admin`
37
+ * normalize to the `*:*` wildcard — `resolveMachinePrincipal` maps `*:*` to
38
+ * the `system_admin` role on the machine principal.
39
+ */
40
+ export interface BootstrapOptions {
41
+ email?: string
42
+ password?: string
43
+ fullName?: string
44
+ keyName?: string
45
+ scopes?: string[]
46
+ }
47
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:BootstrapOptions:interface"]
48
+
49
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:BootstrapResult:interface" type="interface" identity="BootstrapResult" parent="~/.framework/cli/commands/bootstrap.ts"]
50
+ export interface BootstrapResult {
51
+ /** 'created' when a new admin person was provisioned, 'existing' when a
52
+ system_admin already occupied the spine-system account (idempotent skip). */
53
+ admin: 'created' | 'existing'
54
+ personId: string
55
+ personEmail: string
56
+ /** True when a Supabase auth user was created via the admin API — without
57
+ it the person row exists but cannot log in through the UI. */
58
+ authUserCreated: boolean
59
+ apiKey: {
60
+ id: string
61
+ name: string
62
+ key_prefix: string
63
+ scopes: string[]
64
+ /** Raw `sk_` key — returned once, never stored. */
65
+ raw_key: string
66
+ }
67
+ }
68
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:BootstrapResult:interface"]
69
+
70
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:loadEnv:function" type="function" identity="loadEnv" parent="~/.framework/cli/commands/bootstrap.ts"]
71
+ /**
72
+ * Loads key=value pairs from the project .env into process.env without
73
+ * overwriting already-set variables. Mirrors the helper in migrate.ts so the
74
+ * command works when its module is exercised outside cli/index.ts.
75
+ */
76
+ function loadEnv() {
77
+ const envPath = join(PROJECT_ROOT, '.env')
78
+ if (!existsSync(envPath)) return
79
+ const lines = readFileSync(envPath, 'utf8').split('\n')
80
+ for (const line of lines) {
81
+ const trimmed = line.trim()
82
+ if (!trimmed || trimmed.startsWith('#')) continue
83
+ const eqIdx = trimmed.indexOf('=')
84
+ if (eqIdx === -1) continue
85
+ const key = trimmed.slice(0, eqIdx).trim()
86
+ const val = trimmed.slice(eqIdx + 1).trim()
87
+ if (key && !(key in process.env)) process.env[key] = val
88
+ }
89
+ }
90
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:loadEnv:function"]
91
+
92
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:normalizeScopes:function" type="function" identity="normalizeScopes" parent="~/.framework/cli/commands/bootstrap.ts"]
93
+ /** Normalize requested scopes; default is the system_admin wildcard. */
94
+ function normalizeScopes(scopes?: string[]): string[] {
95
+ const requested = (scopes || []).filter(Boolean)
96
+ if (requested.length === 0) return ['*:*']
97
+ return requested.map((s) => (s === 'system_admin' ? '*:*' : s))
98
+ }
99
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:normalizeScopes:function"]
100
+
101
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:runBootstrap:function" type="function" identity="runBootstrap" parent="~/.framework/cli/commands/bootstrap.ts"]
102
+ /**
103
+ * Provision the first system admin and mint the first API key.
104
+ *
105
+ * Idempotent on the admin side: when a `people` row carrying the
106
+ * `system_admin` role already exists in the `spine-system` account, creation
107
+ * is skipped and that person is reused as the key's `created_by`. A new API
108
+ * key is minted on every run — re-running produces an additional key, never
109
+ * an error, and the raw key is only ever returned to the caller once.
110
+ *
111
+ * @throws Error when the spine-system account, system_admin role, or person
112
+ * type seeds are missing (i.e. `migrate` has not run), or when the auth
113
+ * admin API / people upsert fails.
114
+ */
115
+ export async function runBootstrap(db: any, options: BootstrapOptions = {}): Promise<BootstrapResult> {
116
+ if (options.password && !options.email) {
117
+ throw new Error('--password requires --email')
118
+ }
119
+
120
+ // --- Required seeds (created by migrate / framework seeds) ---
121
+ const { data: account, error: accountErr } = await db
122
+ .from('accounts')
123
+ .select('id')
124
+ .eq('slug', 'spine-system')
125
+ .maybeSingle()
126
+ if (accountErr) throw accountErr
127
+ if (!account) {
128
+ throw new Error('spine-system account not found — run `npx spine-framework migrate` first')
129
+ }
130
+
131
+ const { data: role, error: roleErr } = await db
132
+ .from('roles')
133
+ .select('id')
134
+ .eq('slug', 'system_admin')
135
+ .maybeSingle()
136
+ if (roleErr) throw roleErr
137
+ if (!role) {
138
+ throw new Error('system_admin role not found — run `npx spine-framework migrate` first')
139
+ }
140
+
141
+ const { data: personType, error: typeErr } = await db
142
+ .from('types')
143
+ .select('id, design_schema, validation_schema')
144
+ .eq('kind', 'person')
145
+ .eq('slug', 'person')
146
+ .maybeSingle()
147
+ if (typeErr) throw typeErr
148
+ if (!personType) {
149
+ throw new Error('person type (kind=person, slug=person) not found — run framework seeds first')
150
+ }
151
+
152
+ const auditCtx = {
153
+ principal: SYSTEM_PRINCIPAL,
154
+ accountId: account.id,
155
+ db,
156
+ requestId: crypto.randomUUID(),
157
+ }
158
+
159
+ // --- Idempotent admin check ---
160
+ const { data: existingAdmins, error: existErr } = await db
161
+ .from('people')
162
+ .select('id, email')
163
+ .eq('account_id', account.id)
164
+ .eq('role_id', role.id)
165
+ .limit(1)
166
+ if (existErr) throw existErr
167
+
168
+ let admin: 'created' | 'existing' = 'existing'
169
+ let personId: string | undefined = existingAdmins?.[0]?.id
170
+ let personEmail: string = existingAdmins?.[0]?.email
171
+ let authUserCreated = false
172
+
173
+ if (!personId) {
174
+ admin = 'created'
175
+ const email = options.email || 'admin@spine.local'
176
+ const fullName = options.fullName || email.split('@')[0]
177
+
178
+ // Optional login-able auth user. handle_new_auth_user fires on insert and
179
+ // may create the people row itself (first user gets system_admin); the
180
+ // upsert below converges the row to the same binding either way, so the
181
+ // command does not depend on the trigger being installed.
182
+ let authUid: string | null = null
183
+ if (options.email && options.password) {
184
+ const { data: created, error: createErr } = await db.auth.admin.createUser({
185
+ email,
186
+ password: options.password,
187
+ email_confirm: true,
188
+ user_metadata: { full_name: fullName },
189
+ })
190
+ if (createErr) {
191
+ // An already-registered email still gets a bound person record — the
192
+ // auth user can be re-linked or password-reset later.
193
+ if (/already|exists|registered/i.test(createErr.message || '')) {
194
+ console.warn(` ⚠ auth user for ${email} already exists — binding a person record without touching auth`)
195
+ } else {
196
+ throw new Error(`Failed to create auth user: ${createErr.message}`)
197
+ }
198
+ } else {
199
+ authUid = created?.user?.id || null
200
+ authUserCreated = !!authUid
201
+ }
202
+ }
203
+
204
+ // Mirror the handle_new_auth_user INSERT columns exactly (052), keyed on
205
+ // people.email's UNIQUE constraint: claim/upgrade an existing row or
206
+ // insert a fresh one stamped with the person type's schemas.
207
+ const { data: existingPerson, error: personLookupErr } = await db
208
+ .from('people')
209
+ .select('id')
210
+ .eq('email', email)
211
+ .maybeSingle()
212
+ if (personLookupErr) throw personLookupErr
213
+
214
+ if (existingPerson) {
215
+ const { error: updateErr } = await db
216
+ .from('people')
217
+ .update({
218
+ account_id: account.id,
219
+ role_id: role.id,
220
+ type_id: personType.id,
221
+ status: 'active',
222
+ is_active: true,
223
+ design_schema: personType.design_schema || {},
224
+ validation_schema: personType.validation_schema || {},
225
+ ...(authUid ? { auth_uid: authUid } : {}),
226
+ updated_at: new Date().toISOString(),
227
+ })
228
+ .eq('id', existingPerson.id)
229
+ if (updateErr) throw updateErr
230
+ personId = existingPerson.id
231
+ } else {
232
+ const { data: inserted, error: insertErr } = await db
233
+ .from('people')
234
+ .insert({
235
+ id: authUid || crypto.randomUUID(),
236
+ auth_uid: authUid,
237
+ email,
238
+ full_name: fullName,
239
+ account_id: account.id,
240
+ type_id: personType.id,
241
+ role_id: role.id,
242
+ status: 'active',
243
+ is_active: true,
244
+ design_schema: personType.design_schema || {},
245
+ validation_schema: personType.validation_schema || {},
246
+ })
247
+ .select('id')
248
+ .single()
249
+ if (insertErr) throw insertErr
250
+ personId = inserted.id
251
+ }
252
+ personEmail = email
253
+
254
+ await emitLog(auditCtx as any, 'person.created', { type: 'person', id: personId! }, {
255
+ after: { email, role: 'system_admin', account: 'spine-system', auth_user: authUserCreated },
256
+ }, { source: 'cli bootstrap' })
257
+ }
258
+
259
+ // --- Mint the API key (always — each run yields one new credential) ---
260
+ const scopes = normalizeScopes(options.scopes)
261
+ const keyName = options.keyName || 'bootstrap-admin'
262
+ const slug = `${keyName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'bootstrap'}-${crypto.randomUUID().slice(0, 6)}`
263
+
264
+ const rawKey = `sk_${crypto.randomUUID().replace(/-/g, '')}`
265
+ const { key_prefix, key_hash } = hashApiKey(rawKey)
266
+
267
+ const { data: key, error: keyErr } = await db
268
+ .from('api_keys')
269
+ .insert({
270
+ integration_id: null,
271
+ app_id: null,
272
+ account_id: account.id,
273
+ slug,
274
+ name: keyName,
275
+ key_prefix,
276
+ key_hash,
277
+ key_type: 'cli',
278
+ permissions: [],
279
+ scopes,
280
+ rate_limit: 100,
281
+ expires_at: null,
282
+ metadata: { source: 'cli bootstrap' },
283
+ machine_type: 'service_account',
284
+ is_internal: false,
285
+ is_active: true,
286
+ created_by: personId,
287
+ })
288
+ .select('id, name, key_prefix, scopes')
289
+ .single()
290
+ if (keyErr) throw keyErr
291
+
292
+ await emitLog(auditCtx as any, 'api_key.created', { type: 'api_key', id: key.id }, {
293
+ after: { name: keyName, key_type: 'cli', scopes, machine_type: 'service_account' },
294
+ }, { source: 'cli bootstrap' })
295
+
296
+ return {
297
+ admin,
298
+ personId: personId!,
299
+ personEmail,
300
+ authUserCreated,
301
+ apiKey: {
302
+ id: key.id,
303
+ name: key.name,
304
+ key_prefix: key.key_prefix,
305
+ scopes: key.scopes,
306
+ raw_key: rawKey,
307
+ },
308
+ }
309
+ }
310
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:runBootstrap:function"]
311
+
312
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:bootstrapCommand:function" type="function" identity="bootstrapCommand" parent="~/.framework/cli/commands/bootstrap.ts"]
313
+ async function bootstrapCommand(options: {
314
+ email?: string
315
+ password?: string
316
+ fullName?: string
317
+ name?: string
318
+ scope?: string[]
319
+ json?: boolean
320
+ }): Promise<void> {
321
+ console.log('\nBootstrapping Spine instance...\n')
322
+
323
+ loadEnv()
324
+
325
+ const supabaseUrl = process.env.SUPABASE_URL
326
+ const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY
327
+ if (!supabaseUrl || !serviceRoleKey) {
328
+ console.error('❌ SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY must be set')
329
+ console.error(' Set them in .env or export them before running this command.')
330
+ process.exit(1)
331
+ }
332
+
333
+ const db = createClient(supabaseUrl, serviceRoleKey, { auth: { persistSession: false } })
334
+ const result = await runBootstrap(db, {
335
+ email: options.email,
336
+ password: options.password,
337
+ fullName: options.fullName,
338
+ keyName: options.name,
339
+ scopes: options.scope,
340
+ })
341
+
342
+ if (options.json) {
343
+ console.log(JSON.stringify(result, null, 2))
344
+ return
345
+ }
346
+
347
+ if (result.admin === 'existing') {
348
+ console.log(` ✓ system_admin already exists (${result.personEmail}) — skipped person creation`)
349
+ } else {
350
+ console.log(` ✓ Created system_admin person ${result.personEmail} in the spine-system account`)
351
+ if (result.authUserCreated) {
352
+ console.log(' ✓ Auth user created and email confirmed — sign in at /login')
353
+ } else {
354
+ console.log(' ℹ No auth user created. UI login requires registration or an')
355
+ console.log(' admin-supplied auth user (rerun with --email/--password, or')
356
+ console.log(' create one in Supabase Dashboard → Authentication → Users).')
357
+ }
358
+ }
359
+
360
+ console.log(`\n ✓ Minted API key "${result.apiKey.name}" (scopes: ${result.apiKey.scopes.join(', ')})`)
361
+ console.log('\n ' + '─'.repeat(52))
362
+ console.log(` API key (shown once — store it now): ${result.apiKey.raw_key}`)
363
+ console.log(' ' + '─'.repeat(52))
364
+ console.log('\n Use it as: SPINE_CLI_API_KEY / x-api-key header / SPINE_API_KEY')
365
+ console.log('\n✅ Bootstrap complete.\n')
366
+ }
367
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:bootstrapCommand:function"]
368
+
369
+ // CIL[BEGIN stable_id="~/.framework/cli/commands/bootstrap.ts:registerBootstrapCommands:function" type="function" identity="registerBootstrapCommands" parent="~/.framework/cli/commands/bootstrap.ts"]
370
+ export function registerBootstrapCommands(program: Command) {
371
+ const collect = (value: string, previous: string[]) => previous.concat([value])
372
+
373
+ program
374
+ .command('bootstrap')
375
+ .description('Create the first system_admin and mint the first API key — no browser required')
376
+ .option('--email <email>', 'Email for the admin person (default: admin@spine.local)')
377
+ .option('--password <password>', 'With --email, also creates a confirmed login-able auth user')
378
+ .option('--full-name <name>', 'Display name for the admin person (default: email local part)')
379
+ .option('--name <name>', 'API key name', 'bootstrap-admin')
380
+ .option('--scope <scope>', 'API key scope, repeatable (default: system_admin i.e. *:*)', collect, [] as string[])
381
+ .option('--json', 'Output as JSON (includes the raw key — capture it now)')
382
+ .action(async (opts) => {
383
+ try {
384
+ await bootstrapCommand(opts)
385
+ } catch (err: any) {
386
+ console.error('Error:', err.message)
387
+ if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
388
+ process.exit(1)
389
+ }
390
+ })
391
+ }
392
+ // CIL[END stable_id="~/.framework/cli/commands/bootstrap.ts:registerBootstrapCommands:function"]