spine-framework 0.2.1 → 1.0.0

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 (425) hide show
  1. package/LICENSE.md +213 -8
  2. package/README.md +327 -0
  3. package/package.json +107 -217
  4. package/src/cli/commands/init.ts +192 -0
  5. package/src/cli/commands/install.ts +25 -0
  6. package/src/cli/commands/list.ts +33 -0
  7. package/src/cli/commands/migrate.ts +24 -0
  8. package/src/cli/index.ts +46 -0
  9. package/src/components/AppLayout.tsx +21 -0
  10. package/src/components/AuthGuard.tsx +21 -0
  11. package/src/components/RoleGuard.tsx +23 -0
  12. package/src/components/index.ts +3 -0
  13. package/src/contexts/AppContext.tsx +122 -0
  14. package/src/contexts/AuthContext.tsx +87 -0
  15. package/src/contexts/SpineContext.tsx +46 -0
  16. package/src/contexts/index.ts +3 -0
  17. package/src/hooks/index.ts +4 -0
  18. package/src/hooks/useItems.ts +78 -0
  19. package/src/hooks/useThreads.ts +73 -0
  20. package/src/hooks/useWebSocket.ts +97 -0
  21. package/src/index.ts +22 -0
  22. package/src/types/index.ts +163 -0
  23. package/src/utils/api.ts +88 -0
  24. package/src/utils/client.ts +146 -0
  25. package/src/utils/config.ts +20 -0
  26. package/src/utils/index.ts +3 -0
  27. package/.framework/README.md +0 -129
  28. package/.framework/cli/bin.cjs +0 -14
  29. package/.framework/cli/commands/agents.ts +0 -153
  30. package/.framework/cli/commands/auth.ts +0 -94
  31. package/.framework/cli/commands/create-app.ts +0 -185
  32. package/.framework/cli/commands/dev.ts +0 -113
  33. package/.framework/cli/commands/doctor.ts +0 -442
  34. package/.framework/cli/commands/generate.ts +0 -332
  35. package/.framework/cli/commands/init.ts +0 -186
  36. package/.framework/cli/commands/install-app.ts +0 -565
  37. package/.framework/cli/commands/items.ts +0 -253
  38. package/.framework/cli/commands/migrate.ts +0 -139
  39. package/.framework/cli/commands/migrations.ts +0 -141
  40. package/.framework/cli/commands/pipelines.ts +0 -166
  41. package/.framework/cli/commands/status.ts +0 -197
  42. package/.framework/cli/commands/system.ts +0 -184
  43. package/.framework/cli/commands/test.ts +0 -227
  44. package/.framework/cli/commands/uninstall-app.ts +0 -166
  45. package/.framework/cli/context.ts +0 -268
  46. package/.framework/cli/env-loader.ts +0 -36
  47. package/.framework/cli/index.ts +0 -116
  48. package/.framework/cli/welcome.cjs +0 -45
  49. package/.framework/docs/API.md +0 -384
  50. package/.framework/docs/STABILITY.md +0 -52
  51. package/.framework/docs/admin-routes.md +0 -76
  52. package/.framework/docs/api-docs-progress.md +0 -38
  53. package/.framework/docs/api-governance.md +0 -146
  54. package/.framework/docs/api-testing-results.md +0 -212
  55. package/.framework/docs/apis/admin-configs.md +0 -567
  56. package/.framework/docs/apis/admin-data.md +0 -272
  57. package/.framework/docs/apis/index.md +0 -231
  58. package/.framework/docs/apis/internal.md +0 -295
  59. package/.framework/docs/apis/runtime.md +0 -537
  60. package/.framework/docs/assembly-launch-guide.md +0 -138
  61. package/.framework/docs/audit-results.md +0 -590
  62. package/.framework/docs/authorization-model.md +0 -170
  63. package/.framework/docs/db-api-inventory.md +0 -95
  64. package/.framework/docs/examples/custom-app/README.md +0 -77
  65. package/.framework/docs/examples/custom-function/README.md +0 -27
  66. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  67. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  68. package/.framework/docs/gap-remediation-backlog.md +0 -103
  69. package/.framework/docs/guides/cli-guide.md +0 -224
  70. package/.framework/docs/guides/getting-started.md +0 -103
  71. package/.framework/docs/guides/import-guide.md +0 -193
  72. package/.framework/docs/guides/testing-guide.md +0 -229
  73. package/.framework/docs/permission-examples.md +0 -326
  74. package/.framework/docs/ui-adoption-verification.md +0 -111
  75. package/.framework/docs/ui-api-coverage.md +0 -84
  76. package/.framework/docs/v2-compatibility-audit.md +0 -228
  77. package/.framework/functions/.gitkeep +0 -1
  78. package/.framework/functions/_shared/agent-runner.ts +0 -1097
  79. package/.framework/functions/_shared/app-manifest.ts +0 -184
  80. package/.framework/functions/_shared/audit.ts +0 -150
  81. package/.framework/functions/_shared/db.ts +0 -178
  82. package/.framework/functions/_shared/index.ts +0 -391
  83. package/.framework/functions/_shared/middleware.ts +0 -490
  84. package/.framework/functions/_shared/permissions.ts +0 -1325
  85. package/.framework/functions/_shared/pipeline-runner.ts +0 -731
  86. package/.framework/functions/_shared/principal.ts +0 -818
  87. package/.framework/functions/_shared/resolve-ids.ts +0 -106
  88. package/.framework/functions/_shared/schema-utils.ts +0 -967
  89. package/.framework/functions/_shared/testing.ts +0 -258
  90. package/.framework/functions/_shared/trigger-engine.ts +0 -425
  91. package/.framework/functions/_shared/webhook-registration.ts +0 -168
  92. package/.framework/functions/_shared/webhook-registry.ts +0 -129
  93. package/.framework/functions/account-nodes.ts +0 -111
  94. package/.framework/functions/admin-data.ts +0 -606
  95. package/.framework/functions/ai-agents.ts +0 -323
  96. package/.framework/functions/api-keys.ts +0 -376
  97. package/.framework/functions/apps.ts +0 -483
  98. package/.framework/functions/auth.ts +0 -196
  99. package/.framework/functions/debug-auth.ts +0 -107
  100. package/.framework/functions/embeddings.ts +0 -556
  101. package/.framework/functions/integration-routes.ts +0 -523
  102. package/.framework/functions/integrations.ts +0 -319
  103. package/.framework/functions/item-progress.ts +0 -272
  104. package/.framework/functions/logs.ts +0 -438
  105. package/.framework/functions/observability.ts +0 -275
  106. package/.framework/functions/pipeline-executions.ts +0 -494
  107. package/.framework/functions/pipelines.ts +0 -485
  108. package/.framework/functions/prompt-configs.ts +0 -339
  109. package/.framework/functions/roles.ts +0 -387
  110. package/.framework/functions/system-cron.ts +0 -742
  111. package/.framework/functions/system.ts +0 -323
  112. package/.framework/functions/tests.ts +0 -119
  113. package/.framework/functions/timers.ts +0 -357
  114. package/.framework/functions/triggers.ts +0 -563
  115. package/.framework/functions/types.ts +0 -604
  116. package/.framework/index.html +0 -16
  117. package/.framework/migrations/000_foundation.sql +0 -1266
  118. package/.framework/migrations/001_seed.sql +0 -163
  119. package/.framework/migrations/002_seed_constraints.sql +0 -19
  120. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  121. package/.framework/src/App.tsx +0 -133
  122. package/.framework/src/apps/admin/index.tsx +0 -173
  123. package/.framework/src/components/AppWrapper.tsx +0 -56
  124. package/.framework/src/components/CustomAppLoader.tsx +0 -116
  125. package/.framework/src/components/admin/AdminListPage.tsx +0 -151
  126. package/.framework/src/components/admin/AdminSidebar.tsx +0 -166
  127. package/.framework/src/components/admin/AdminStatsCard.tsx +0 -62
  128. package/.framework/src/components/admin/SortableTableHeader.tsx +0 -42
  129. package/.framework/src/components/app-shell/GenericAppShell.tsx +0 -181
  130. package/.framework/src/components/app-shell/GenericDetailPage.tsx +0 -200
  131. package/.framework/src/components/app-shell/GenericListPage.tsx +0 -116
  132. package/.framework/src/components/app-sidebar.tsx +0 -228
  133. package/.framework/src/components/auth/ProtectedRoute.tsx +0 -88
  134. package/.framework/src/components/layout/AppShell.tsx +0 -91
  135. package/.framework/src/components/layout/Header.tsx +0 -88
  136. package/.framework/src/components/layout/Layout.tsx +0 -95
  137. package/.framework/src/components/layout/Sidebar.tsx +0 -329
  138. package/.framework/src/components/runtime/DataDetailHeader.tsx +0 -77
  139. package/.framework/src/components/runtime/DataDetailPage.tsx +0 -171
  140. package/.framework/src/components/runtime/DataFilters.tsx +0 -91
  141. package/.framework/src/components/runtime/DataHeader.tsx +0 -68
  142. package/.framework/src/components/runtime/DataListPage.tsx +0 -124
  143. package/.framework/src/components/runtime/DataStats.tsx +0 -70
  144. package/.framework/src/components/runtime/DataTable.tsx +0 -174
  145. package/.framework/src/components/runtime/SchemaDetailForm.tsx +0 -134
  146. package/.framework/src/components/runtime/index.ts +0 -18
  147. package/.framework/src/components/search-form.tsx +0 -29
  148. package/.framework/src/components/shared/AgentView.tsx +0 -213
  149. package/.framework/src/components/shared/FieldRenderer.tsx +0 -478
  150. package/.framework/src/components/shared/SchemaFields.tsx +0 -226
  151. package/.framework/src/components/ui/DataTable.tsx +0 -343
  152. package/.framework/src/components/ui/Form.tsx +0 -281
  153. package/.framework/src/components/ui/ItemCard.tsx +0 -296
  154. package/.framework/src/components/ui/ItemListView.tsx +0 -308
  155. package/.framework/src/components/ui/LoadingSpinner.tsx +0 -52
  156. package/.framework/src/components/ui/Modal.tsx +0 -61
  157. package/.framework/src/components/ui/RichTextEditor.tsx +0 -210
  158. package/.framework/src/components/ui/accordion.tsx +0 -82
  159. package/.framework/src/components/ui/alert-dialog.tsx +0 -197
  160. package/.framework/src/components/ui/alert.tsx +0 -76
  161. package/.framework/src/components/ui/aspect-ratio.tsx +0 -11
  162. package/.framework/src/components/ui/avatar.tsx +0 -110
  163. package/.framework/src/components/ui/badge.tsx +0 -49
  164. package/.framework/src/components/ui/breadcrumb.tsx +0 -122
  165. package/.framework/src/components/ui/button-group.tsx +0 -83
  166. package/.framework/src/components/ui/button.tsx +0 -65
  167. package/.framework/src/components/ui/calendar.tsx +0 -222
  168. package/.framework/src/components/ui/card.tsx +0 -100
  169. package/.framework/src/components/ui/carousel.tsx +0 -240
  170. package/.framework/src/components/ui/chart.tsx +0 -368
  171. package/.framework/src/components/ui/checkbox.tsx +0 -31
  172. package/.framework/src/components/ui/collapsible.tsx +0 -33
  173. package/.framework/src/components/ui/combobox.tsx +0 -299
  174. package/.framework/src/components/ui/command.tsx +0 -193
  175. package/.framework/src/components/ui/context-menu.tsx +0 -261
  176. package/.framework/src/components/ui/dialog.tsx +0 -165
  177. package/.framework/src/components/ui/direction.tsx +0 -6
  178. package/.framework/src/components/ui/drawer.tsx +0 -132
  179. package/.framework/src/components/ui/dropdown-menu.tsx +0 -269
  180. package/.framework/src/components/ui/empty.tsx +0 -104
  181. package/.framework/src/components/ui/field.tsx +0 -238
  182. package/.framework/src/components/ui/hover-card.tsx +0 -42
  183. package/.framework/src/components/ui/input-group.tsx +0 -153
  184. package/.framework/src/components/ui/input-otp.tsx +0 -87
  185. package/.framework/src/components/ui/input.tsx +0 -19
  186. package/.framework/src/components/ui/item.tsx +0 -196
  187. package/.framework/src/components/ui/kbd.tsx +0 -26
  188. package/.framework/src/components/ui/label.tsx +0 -22
  189. package/.framework/src/components/ui/menubar.tsx +0 -277
  190. package/.framework/src/components/ui/native-select.tsx +0 -61
  191. package/.framework/src/components/ui/navigation-menu.tsx +0 -164
  192. package/.framework/src/components/ui/pagination.tsx +0 -129
  193. package/.framework/src/components/ui/popover.tsx +0 -87
  194. package/.framework/src/components/ui/progress.tsx +0 -31
  195. package/.framework/src/components/ui/radio-group.tsx +0 -42
  196. package/.framework/src/components/ui/resizable.tsx +0 -50
  197. package/.framework/src/components/ui/scroll-area.tsx +0 -53
  198. package/.framework/src/components/ui/select.tsx +0 -195
  199. package/.framework/src/components/ui/separator.tsx +0 -26
  200. package/.framework/src/components/ui/sheet.tsx +0 -145
  201. package/.framework/src/components/ui/sidebar.tsx +0 -706
  202. package/.framework/src/components/ui/skeleton.tsx +0 -13
  203. package/.framework/src/components/ui/slider.tsx +0 -59
  204. package/.framework/src/components/ui/sonner.tsx +0 -47
  205. package/.framework/src/components/ui/spinner.tsx +0 -10
  206. package/.framework/src/components/ui/switch.tsx +0 -33
  207. package/.framework/src/components/ui/table-primitives.tsx +0 -141
  208. package/.framework/src/components/ui/table.tsx +0 -114
  209. package/.framework/src/components/ui/tabs.tsx +0 -90
  210. package/.framework/src/components/ui/textarea.tsx +0 -18
  211. package/.framework/src/components/ui/toggle-group.tsx +0 -89
  212. package/.framework/src/components/ui/toggle.tsx +0 -45
  213. package/.framework/src/components/ui/tooltip.tsx +0 -57
  214. package/.framework/src/contexts/AppContext.tsx +0 -133
  215. package/.framework/src/contexts/AuthContext.tsx +0 -371
  216. package/.framework/src/hooks/use-mobile.ts +0 -19
  217. package/.framework/src/hooks/useApi.ts +0 -526
  218. package/.framework/src/hooks/useApps.ts +0 -114
  219. package/.framework/src/hooks/useEntityList.ts +0 -190
  220. package/.framework/src/hooks/useEntityRecord.ts +0 -308
  221. package/.framework/src/hooks/useForm.ts +0 -307
  222. package/.framework/src/hooks/useListSchema.ts +0 -264
  223. package/.framework/src/hooks/useSchemaRecord.ts +0 -223
  224. package/.framework/src/index.css +0 -128
  225. package/.framework/src/lib/api.ts +0 -156
  226. package/.framework/src/lib/supabase.ts +0 -94
  227. package/.framework/src/lib/utils.ts +0 -317
  228. package/.framework/src/main.tsx +0 -27
  229. package/.framework/src/pages/DashboardPage.tsx +0 -181
  230. package/.framework/src/pages/NotFoundPage.tsx +0 -39
  231. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +0 -161
  232. package/.framework/src/pages/admin/AIAgentsPage.tsx +0 -318
  233. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +0 -199
  234. package/.framework/src/pages/admin/APIKeysPage.tsx +0 -303
  235. package/.framework/src/pages/admin/AlertsConfigPage.tsx +0 -523
  236. package/.framework/src/pages/admin/AppDetailPage.tsx +0 -493
  237. package/.framework/src/pages/admin/AppsPage.tsx +0 -355
  238. package/.framework/src/pages/admin/DesignedPage.tsx +0 -491
  239. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +0 -534
  240. package/.framework/src/pages/admin/EmbeddingsPage.tsx +0 -424
  241. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +0 -176
  242. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +0 -109
  243. package/.framework/src/pages/admin/IntegratedDashboard.tsx +0 -402
  244. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +0 -187
  245. package/.framework/src/pages/admin/IntegrationsPage.tsx +0 -301
  246. package/.framework/src/pages/admin/LogsPage.tsx +0 -283
  247. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +0 -85
  248. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +0 -470
  249. package/.framework/src/pages/admin/PipelineDetailPage.tsx +0 -183
  250. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +0 -279
  251. package/.framework/src/pages/admin/PipelinesPage.tsx +0 -390
  252. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +0 -299
  253. package/.framework/src/pages/admin/PromptConfigsPage.tsx +0 -292
  254. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +0 -434
  255. package/.framework/src/pages/admin/RoleDetailPage.tsx +0 -273
  256. package/.framework/src/pages/admin/RolesPage.tsx +0 -292
  257. package/.framework/src/pages/admin/SelectTestPage.tsx +0 -61
  258. package/.framework/src/pages/admin/ShadcnTestPage.tsx +0 -588
  259. package/.framework/src/pages/admin/SimpleDashboard.tsx +0 -387
  260. package/.framework/src/pages/admin/TestRunDetailPage.tsx +0 -172
  261. package/.framework/src/pages/admin/TestingDashboard.tsx +0 -257
  262. package/.framework/src/pages/admin/TimerDetailPage.tsx +0 -151
  263. package/.framework/src/pages/admin/TimersPage.tsx +0 -376
  264. package/.framework/src/pages/admin/TriggerDetailPage.tsx +0 -149
  265. package/.framework/src/pages/admin/TriggersPage.tsx +0 -381
  266. package/.framework/src/pages/admin/TypeDetailPage.tsx +0 -694
  267. package/.framework/src/pages/admin/TypesPage.tsx +0 -295
  268. package/.framework/src/pages/auth/LoginPage.tsx +0 -187
  269. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  270. package/.framework/src/pages/spine-framework/APIPage.tsx +0 -17
  271. package/.framework/src/pages/spine-framework/CLIPage.tsx +0 -25
  272. package/.framework/src/types/auth.ts +0 -125
  273. package/.framework/src/types/types.ts +0 -407
  274. package/STRUCTURE.md +0 -150
  275. package/bin/spine-framework.cjs +0 -62
  276. package/bin/welcome.cjs +0 -45
  277. package/bin/ws-shim.cjs +0 -8
  278. package/bin/ws-shim.ts +0 -10
  279. package/config/components.json +0 -25
  280. package/config/deno.lock +0 -108
  281. package/config/package-lock.json +0 -17183
  282. package/config/postcss.config.cjs +0 -10
  283. package/config/tailwind.config.cjs +0 -78
  284. package/config/tsconfig.build.json +0 -32
  285. package/config/tsconfig.cli.json +0 -18
  286. package/config/tsconfig.json +0 -41
  287. package/config/tsconfig.node.json +0 -17
  288. package/config/tsconfig.node.tsbuildinfo +0 -1
  289. package/config/tsconfig.tsbuildinfo +0 -1
  290. package/config/typedoc.json +0 -16
  291. package/config/vite.config.d.ts +0 -2
  292. package/config/vite.config.ts +0 -71
  293. package/dist/cli/commands/agents.d.ts +0 -39
  294. package/dist/cli/commands/agents.d.ts.map +0 -1
  295. package/dist/cli/commands/auth.d.ts +0 -36
  296. package/dist/cli/commands/auth.d.ts.map +0 -1
  297. package/dist/cli/commands/create-app.d.ts +0 -23
  298. package/dist/cli/commands/create-app.d.ts.map +0 -1
  299. package/dist/cli/commands/dev.d.ts +0 -24
  300. package/dist/cli/commands/dev.d.ts.map +0 -1
  301. package/dist/cli/commands/doctor.d.ts +0 -42
  302. package/dist/cli/commands/doctor.d.ts.map +0 -1
  303. package/dist/cli/commands/generate.d.ts +0 -36
  304. package/dist/cli/commands/generate.d.ts.map +0 -1
  305. package/dist/cli/commands/init.d.ts +0 -20
  306. package/dist/cli/commands/init.d.ts.map +0 -1
  307. package/dist/cli/commands/install-app.d.ts +0 -30
  308. package/dist/cli/commands/install-app.d.ts.map +0 -1
  309. package/dist/cli/commands/items.d.ts +0 -45
  310. package/dist/cli/commands/items.d.ts.map +0 -1
  311. package/dist/cli/commands/migrate.d.ts +0 -21
  312. package/dist/cli/commands/migrate.d.ts.map +0 -1
  313. package/dist/cli/commands/migrations.d.ts +0 -41
  314. package/dist/cli/commands/migrations.d.ts.map +0 -1
  315. package/dist/cli/commands/pipelines.d.ts +0 -40
  316. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  317. package/dist/cli/commands/status.d.ts +0 -23
  318. package/dist/cli/commands/status.d.ts.map +0 -1
  319. package/dist/cli/commands/system.d.ts +0 -29
  320. package/dist/cli/commands/system.d.ts.map +0 -1
  321. package/dist/cli/commands/test.d.ts +0 -46
  322. package/dist/cli/commands/test.d.ts.map +0 -1
  323. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  324. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  325. package/dist/cli/context.d.ts +0 -88
  326. package/dist/cli/context.d.ts.map +0 -1
  327. package/dist/cli/env-loader.d.ts +0 -14
  328. package/dist/cli/env-loader.d.ts.map +0 -1
  329. package/dist/cli/index.d.ts +0 -41
  330. package/dist/cli/index.d.ts.map +0 -1
  331. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  332. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  333. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  334. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  335. package/dist/functions/_shared/audit.d.ts +0 -91
  336. package/dist/functions/_shared/audit.d.ts.map +0 -1
  337. package/dist/functions/_shared/db.d.ts +0 -125
  338. package/dist/functions/_shared/db.d.ts.map +0 -1
  339. package/dist/functions/_shared/index.d.ts +0 -299
  340. package/dist/functions/_shared/index.d.ts.map +0 -1
  341. package/dist/functions/_shared/middleware.d.ts +0 -315
  342. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  343. package/dist/functions/_shared/permissions.d.ts +0 -626
  344. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  345. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  346. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  347. package/dist/functions/_shared/principal.d.ts +0 -284
  348. package/dist/functions/_shared/principal.d.ts.map +0 -1
  349. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  350. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  351. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  352. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  353. package/dist/functions/_shared/testing.d.ts +0 -172
  354. package/dist/functions/_shared/testing.d.ts.map +0 -1
  355. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  356. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  357. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  358. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  359. package/dist/functions/_shared/webhook-registry.d.ts +0 -57
  360. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  361. package/dist/functions/account-nodes.d.ts +0 -48
  362. package/dist/functions/account-nodes.d.ts.map +0 -1
  363. package/dist/functions/admin-data.d.ts +0 -178
  364. package/dist/functions/admin-data.d.ts.map +0 -1
  365. package/dist/functions/ai-agents.d.ts +0 -125
  366. package/dist/functions/ai-agents.d.ts.map +0 -1
  367. package/dist/functions/api-keys.d.ts +0 -140
  368. package/dist/functions/api-keys.d.ts.map +0 -1
  369. package/dist/functions/apps.d.ts +0 -163
  370. package/dist/functions/apps.d.ts.map +0 -1
  371. package/dist/functions/auth.d.ts +0 -74
  372. package/dist/functions/auth.d.ts.map +0 -1
  373. package/dist/functions/debug-auth.d.ts +0 -33
  374. package/dist/functions/debug-auth.d.ts.map +0 -1
  375. package/dist/functions/embeddings.d.ts +0 -205
  376. package/dist/functions/embeddings.d.ts.map +0 -1
  377. package/dist/functions/integration-routes.d.ts +0 -45
  378. package/dist/functions/integration-routes.d.ts.map +0 -1
  379. package/dist/functions/integrations.d.ts +0 -124
  380. package/dist/functions/integrations.d.ts.map +0 -1
  381. package/dist/functions/item-progress.d.ts +0 -41
  382. package/dist/functions/item-progress.d.ts.map +0 -1
  383. package/dist/functions/logs.d.ts +0 -162
  384. package/dist/functions/logs.d.ts.map +0 -1
  385. package/dist/functions/observability.d.ts +0 -123
  386. package/dist/functions/observability.d.ts.map +0 -1
  387. package/dist/functions/pipeline-executions.d.ts +0 -190
  388. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  389. package/dist/functions/pipelines.d.ts +0 -171
  390. package/dist/functions/pipelines.d.ts.map +0 -1
  391. package/dist/functions/prompt-configs.d.ts +0 -125
  392. package/dist/functions/prompt-configs.d.ts.map +0 -1
  393. package/dist/functions/roles.d.ts +0 -118
  394. package/dist/functions/roles.d.ts.map +0 -1
  395. package/dist/functions/system-cron.d.ts +0 -65
  396. package/dist/functions/system-cron.d.ts.map +0 -1
  397. package/dist/functions/system.d.ts +0 -29
  398. package/dist/functions/system.d.ts.map +0 -1
  399. package/dist/functions/tests.d.ts +0 -28
  400. package/dist/functions/tests.d.ts.map +0 -1
  401. package/dist/functions/timers.d.ts +0 -139
  402. package/dist/functions/timers.d.ts.map +0 -1
  403. package/dist/functions/triggers.d.ts +0 -203
  404. package/dist/functions/triggers.d.ts.map +0 -1
  405. package/dist/functions/types.d.ts +0 -151
  406. package/dist/functions/types.d.ts.map +0 -1
  407. package/dist/src/types/types.d.ts +0 -364
  408. package/dist/src/types/types.d.ts.map +0 -1
  409. package/index.html +0 -13
  410. package/netlify.toml +0 -36
  411. package/package-project.json +0 -71
  412. package/scripts/app-install-cli.ts +0 -286
  413. package/scripts/assemble-frontend.sh +0 -76
  414. package/scripts/assemble-functions.sh +0 -62
  415. package/scripts/assemble.sh +0 -41
  416. package/scripts/boundary-check.sh +0 -106
  417. package/scripts/build-manifest.sh +0 -80
  418. package/scripts/check-core-integrity.sh +0 -82
  419. package/scripts/ingest-chunks.cjs +0 -202
  420. package/scripts/kb-chunk-parser.cjs +0 -312
  421. package/scripts/kb-chunk-parser.ts +0 -330
  422. package/scripts/load-test-app-install.ts +0 -484
  423. package/scripts/netlify-dev-wrapper.sh +0 -22
  424. package/scripts/verify-integrity.sh +0 -69
  425. package/vitest.config.ts +0 -45
@@ -1,818 +0,0 @@
1
- /**
2
- * @module principal
3
- * @audience both
4
- * @layer shared-core
5
- * @stability stable
6
- *
7
- * Unified identity abstraction for all actors in Spine v2. Every request —
8
- * whether from a human via JWT, an integration via API key, a scheduled cron
9
- * job, or an internal trigger — resolves to a single `Principal` object before
10
- * any permission check or DB query occurs.
11
- *
12
- * Resolution order in `resolvePrincipal`:
13
- * 1. `x-api-key` header → machine principal (external integration)
14
- * 2. `x-cron-id` header → machine principal (scheduled job)
15
- * 3. `x-trigger-id` header → machine principal (event trigger)
16
- * 4. `Authorization: Bearer <jwt>` → human principal
17
- * 5. (none) → ANONYMOUS_PRINCIPAL (rejected by createHandler)
18
- *
19
- * INVARIANT: `resolvePrincipal` always returns a Principal, never null.
20
- * ANONYMOUS_PRINCIPAL is the sentinel for unauthenticated requests and is
21
- * rejected by `createHandler` before the handler runs.
22
- * INVARIANT: `adminDb` is used for all principal resolution lookups to avoid
23
- * circular dependencies with RLS (which itself depends on the resolved principal).
24
- * INVARIANT: never store `authContext.jwt` or `authContext.apiKey` in logs.
25
- * Use `formatPrincipalForAudit` which strips these fields.
26
- *
27
- * @seeAlso db.ts (adminDb, getUserDb — used for resolution and client selection)
28
- * @seeAlso middleware.ts (createHandler calls resolvePrincipal, getPrincipalDb)
29
- * @seeAlso permissions.ts (PermissionEngine.isSystemAdmin, canPrincipalAccessRecord)
30
- * @seeAlso audit.ts (formatPrincipalForAudit — safe audit serialization)
31
- */
32
-
33
- import { getUserDb, adminDb } from './db'
34
- import { createClient } from '@supabase/supabase-js'
35
- import WebSocket from 'ws'
36
-
37
- // WebSocket constructor for Supabase Realtime in Node.js environment
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const RealtimeWebSocket = WebSocket as any
40
-
41
- // ─── AUTH CLIENT ─────────────────────────────────────────────────────────────
42
-
43
- // Supabase anon client used only for JWT validation (supabase.auth.getUser).
44
- // A separate client is used here to avoid importing the RLS-scoped client
45
- // before the principal is resolved.
46
- const env = (globalThis as any).process?.env || {}
47
- const supabase = createClient(
48
- env.SUPABASE_URL!,
49
- env.SUPABASE_ANON_KEY!,
50
- {
51
- auth: {
52
- autoRefreshToken: false,
53
- persistSession: false
54
- },
55
- realtime: { transport: RealtimeWebSocket } // WebSocket for Node.js 20+
56
- }
57
- )
58
-
59
- // ─── PRINCIPAL INTERFACE ─────────────────────────────────────────────────────
60
-
61
- /**
62
- * Unified identity abstraction for all actors in Spine.
63
- *
64
- * Every request resolves to a `Principal` before any permission or DB access.
65
- * The `type` field gates which optional fields are populated:
66
- * - `'human'` → `roles`, `displayName`, `email`, `authContext.jwt`
67
- * - `'machine'` → `scopes`, `machineType`, `isInternal`, `authContext.apiKey`
68
- *
69
- * The `provenance` object is always populated and is the primary audit trail
70
- * field. It must not be modified after resolution.
71
- *
72
- * @inputSpec none — this is a pure type definition
73
- * @calledBy middleware.ts (RequestContext.principal), permissions.ts (all methods),
74
- * audit.ts (formatPrincipalForAudit), tests/integration/helpers.ts (makeTestCtx)
75
- */
76
- export interface Principal {
77
- /** Unique identifier — person UUID (human) or machine principal UUID (machine) */
78
- id: string
79
-
80
- /** Actor type — gates which optional fields are populated */
81
- type: 'human' | 'machine'
82
-
83
- /** Primary account context; null for internal system principals */
84
- accountId: string | null
85
-
86
- // ─ Human-specific (only populated when type === 'human') ─────────────────
87
- /** Role slugs from people.role_id → roles.slug; used by PermissionEngine */
88
- roles?: string[]
89
-
90
- /** Display name from people.display_name or people.email */
91
- displayName?: string
92
-
93
- /** Email address from people.email or Supabase auth user */
94
- email?: string
95
-
96
- // ─ Machine-specific (only populated when type === 'machine') ─────────────
97
- /** Explicit permission grants (e.g., ['items:read', 'people:write', '*:*']) */
98
- scopes?: string[]
99
-
100
- /** Machine classification — determines UI visibility and default scopes */
101
- machineType?: 'integration' | 'service_account' | 'internal' | 'timer'
102
-
103
- /** Internal machines (cron, trigger, pipeline) are hidden from the UI */
104
- isInternal?: boolean
105
-
106
- // ─ Universal provenance — always populated, never modified after resolution
107
- provenance: {
108
- /** How this principal was authenticated */
109
- sourceType: 'jwt' | 'api_key' | 'cron' | 'trigger' | 'manual' | 'webhook' | 'timer'
110
-
111
- /** Person who authorized this principal (may be self for humans) */
112
- createdBy: string | null
113
-
114
- /** Chain ID for trigger/pipeline sequences */
115
- parentExecutionId?: string
116
-
117
- /** When this principal context was created */
118
- invokedAt: string
119
-
120
- // Source-specific context
121
- /** API key ID (for api_key source) */
122
- apiKeyId?: string
123
-
124
- /** Schedule ID (for cron source) */
125
- cronId?: string
126
-
127
- /** Trigger ID (for trigger source) */
128
- triggerId?: string
129
-
130
- /** Timer ID (for timer source) */
131
- timerId?: string
132
-
133
- /** Event ID that triggered this execution */
134
- eventId?: string
135
-
136
- /** IP address of the requester */
137
- ipAddress?: string
138
-
139
- /** User agent string */
140
- userAgent?: string
141
- }
142
-
143
- // ============================================
144
- // Authentication context (for RLS client selection)
145
- // ============================================
146
- authContext?: {
147
- /** JWT token for human-scoped DB client */
148
- jwt?: string
149
-
150
- /** API key value for machine verification */
151
- apiKey?: string
152
- }
153
- }
154
-
155
- // ─── PRINCIPAL CONSTANTS ─────────────────────────────────────────────────────
156
-
157
- /**
158
- * Sentinel principal for unauthenticated requests.
159
- *
160
- * Returned by `resolvePrincipal` when no auth header is present. `createHandler`
161
- * checks for `principal.id === 'anonymous'` and rejects the request with 401.
162
- * Never use this principal for any DB access — it has no scopes or accountId.
163
- *
164
- * @stability stable
165
- * @calledBy resolvePrincipal (returned when no auth header is present)
166
- * @calledBy middleware.ts, requireUserContext, requireSystemContextWithAudit (checked against)
167
- * @calledBy permissions.ts (all surface methods check for 'anonymous' and deny)
168
- */
169
- export const ANONYMOUS_PRINCIPAL: Principal = {
170
- id: 'anonymous',
171
- type: 'machine',
172
- accountId: null,
173
- scopes: [],
174
- provenance: {
175
- sourceType: 'manual',
176
- createdBy: null,
177
- invokedAt: new Date().toISOString()
178
- }
179
- }
180
-
181
- /**
182
- * System principal for internal Spine operations (cron, pipeline runner, trigger engine).
183
- *
184
- * Has `'*:*'` scope (all resources, all actions) and `isInternal: true`. When
185
- * a `CoreContext` is constructed for a system operation (e.g. in v2-custom/ or
186
- * the CLI), use `SYSTEM_PRINCIPAL` as the principal and `adminDb` as the db.
187
- *
188
- * Never expose this principal in an HTTP response or log the `authContext` field.
189
- *
190
- * @stability stable
191
- * @calledBy tests/integration/helpers.ts (makeTestCtx)
192
- * @calledBy CLI context construction
193
- * @calledBy v2-custom/ system-level import callers
194
- *
195
- * @example Import usage (v2-custom/)
196
- * ```ts
197
- * import { CoreContext, SYSTEM_PRINCIPAL, adminDb } from '../_shared/index'
198
- * const ctx: CoreContext = {
199
- * principal: SYSTEM_PRINCIPAL,
200
- * accountId: MY_ACCOUNT_ID,
201
- * db: adminDb,
202
- * requestId: crypto.randomUUID()
203
- * }
204
- * ```
205
- */
206
- export const SYSTEM_PRINCIPAL: Principal = {
207
- id: 'system',
208
- type: 'machine',
209
- accountId: null,
210
- scopes: ['*:*'], // All scopes
211
- machineType: 'internal',
212
- isInternal: true,
213
- provenance: {
214
- sourceType: 'manual',
215
- createdBy: null,
216
- invokedAt: new Date().toISOString()
217
- }
218
- }
219
-
220
- // ─── RESOLUTION FUNCTIONS ────────────────────────────────────────────────────
221
-
222
- /**
223
- * Main entry point for principal resolution. Called by `createHandler` on every
224
- * HTTP request before the handler runs.
225
- *
226
- * Examines request headers in priority order and delegates to the appropriate
227
- * resolver. Always returns a `Principal` — never throws for missing auth
228
- * (returns `ANONYMOUS_PRINCIPAL` instead). Throws only on invalid/expired
229
- * credentials (invalid API key, expired JWT, etc.).
230
- *
231
- * Resolution order:
232
- * 1. `x-api-key` / `X-Api-Key` header → `resolveMachinePrincipal`
233
- * 2. `x-cron-id` / `X-Cron-Id` header → `resolveCronPrincipal`
234
- * 3. `x-trigger-id` / `X-Trigger-Id` header → `resolveTriggerPrincipal`
235
- * 4. `Authorization: Bearer <jwt>` → `resolveHumanPrincipal`
236
- * 5. (none matched) → `ANONYMOUS_PRINCIPAL`
237
- *
238
- * @param event - Raw Netlify event object with `headers` and optional `body`
239
- * @returns Promise<Principal> — always resolves; throws on invalid credentials
240
- * @throws Error — on invalid API key, invalid JWT, or missing DB records
241
- * @inputSpec event.headers: Record<string, string> — HTTP request headers
242
- * @outputSpec Principal — fully resolved principal with provenance populated
243
- * @sideEffects DB reads: api_keys, schedules, triggers, people tables via sub-resolvers
244
- * @calledBy middleware.ts (createHandler) — once per HTTP request
245
- * @calls resolveMachinePrincipal | resolveCronPrincipal | resolveTriggerPrincipal |
246
- * resolveHumanPrincipal
247
- * @testUnit tests/unit/principal.test.ts — 'resolvePrincipal' describe block
248
- * @testIntegration tests/integration/auth.test.ts
249
- */
250
- export async function resolvePrincipal(event: any): Promise<Principal> {
251
- // Check for API key (external machine)
252
- const apiKey = event.headers?.['x-api-key'] || event.headers?.['X-Api-Key']
253
- if (apiKey) {
254
- return resolveMachinePrincipal(apiKey, event)
255
- }
256
-
257
- // Check for internal cron header
258
- const cronId = event.headers?.['x-cron-id'] || event.headers?.['X-Cron-Id']
259
- if (cronId) {
260
- return resolveCronPrincipal(cronId)
261
- }
262
-
263
- // Check for internal trigger header
264
- const triggerId = event.headers?.['x-trigger-id'] || event.headers?.['X-Trigger-Id']
265
- if (triggerId) {
266
- return resolveTriggerPrincipal(triggerId, event)
267
- }
268
-
269
- // Check for JWT Bearer (human)
270
- const authHeader = event.headers?.authorization || event.headers?.Authorization
271
- if (authHeader?.startsWith('Bearer ')) {
272
- return resolveHumanPrincipal(authHeader.replace('Bearer ', ''), event)
273
- }
274
-
275
- // No authentication - return anonymous
276
- return ANONYMOUS_PRINCIPAL
277
- }
278
-
279
- /**
280
- * Resolves a machine principal by validating an API key against the DB.
281
- *
282
- * Calls the `validate_machine_principal` RPC which checks the key hash,
283
- * expiry, and active status in one query. Throws on any validation failure —
284
- * invalid keys are not silently demoted to anonymous.
285
- *
286
- * @param apiKey - Raw API key string from `x-api-key` header
287
- * @param event - Raw Netlify event (used for IP/user-agent provenance)
288
- * @returns Promise<Principal> — machine principal with scopes from the DB record
289
- * @throws Error — on invalid, expired, or inactive API key
290
- * @inputSpec apiKey: string — raw key value; hashed by the RPC for comparison
291
- * @outputSpec Principal with type='machine', scopes from DB, provenance.sourceType='api_key'
292
- * @sideEffects DB read: validate_machine_principal RPC (api_keys table)
293
- * @calledBy resolvePrincipal (api_key branch)
294
- * @calls adminDb.rpc('validate_machine_principal')
295
- */
296
- async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Principal> {
297
- // Validate the API key using the database function
298
- const { data: rows, error } = await adminDb.rpc('validate_machine_principal', {
299
- p_key_value: apiKey,
300
- p_required_scope: null // No specific scope required for resolution
301
- })
302
- const machine = Array.isArray(rows) ? rows[0] : rows
303
-
304
- if (error || !machine || !machine.is_valid) {
305
- throw new Error(machine?.error_message || 'Invalid or inactive machine principal')
306
- }
307
-
308
- return {
309
- id: machine.machine_id,
310
- type: 'machine',
311
- accountId: machine.account_id,
312
- scopes: machine.scopes || [],
313
- machineType: machine.machine_type as any,
314
- isInternal: machine.is_internal,
315
- provenance: {
316
- sourceType: 'api_key',
317
- createdBy: machine.created_by,
318
- invokedAt: new Date().toISOString(),
319
- apiKeyId: machine.machine_id,
320
- ipAddress: getClientIp(event),
321
- userAgent: event.headers?.['user-agent'] || event.headers?.['User-Agent']
322
- },
323
- authContext: { apiKey }
324
- }
325
- }
326
-
327
- /**
328
- * Resolves a machine principal for a scheduled cron job execution.
329
- *
330
- * Loads the schedule record (with its linked machine principal) and validates
331
- * that the schedule creator is still active via `validate_schedule_creator` RPC.
332
- * Uses the schedule's `delegated_scopes` if set, falling back to the machine's
333
- * own scopes. This allows scopes to be narrowed per-schedule for least-privilege.
334
- *
335
- * @param scheduleId - UUID of the schedule from `x-cron-id` header
336
- * @returns Promise<Principal> — machine principal with delegated or own scopes
337
- * @throws Error — on invalid/inactive schedule, missing machine, or validation failure
338
- * @inputSpec scheduleId: string — valid UUID in the schedules table
339
- * @outputSpec Principal with type='machine', provenance.sourceType='cron', cronId set
340
- * @sideEffects DB reads: schedules table, validate_schedule_creator RPC
341
- * @calledBy resolvePrincipal (cron branch)
342
- * @calls adminDb.from('schedules'), adminDb.rpc('validate_schedule_creator')
343
- */
344
- async function resolveCronPrincipal(scheduleId: string): Promise<Principal> {
345
- // Load the schedule with its machine principal
346
- const { data: schedule, error: scheduleError } = await adminDb
347
- .from('schedules')
348
- .select(`
349
- *,
350
- machine:machine_principal_id (*)
351
- `)
352
- .eq('id', scheduleId)
353
- .single()
354
-
355
- if (scheduleError || !schedule) {
356
- throw new Error('Invalid or inactive schedule: ' + scheduleId)
357
- }
358
-
359
- // Validate the schedule creator is still active
360
- const { data: validation, error: validationError } = await adminDb.rpc('validate_schedule_creator', {
361
- p_schedule_id: scheduleId
362
- })
363
-
364
- if (validationError || !validation.is_valid) {
365
- throw new Error(validation.error_message || 'Schedule validation failed')
366
- }
367
-
368
- const machine = schedule.machine
369
-
370
- return {
371
- id: machine.id,
372
- type: 'machine',
373
- accountId: machine.account_id,
374
- scopes: schedule.delegated_scopes || machine.scopes || [],
375
- machineType: machine.machine_type,
376
- isInternal: machine.is_internal,
377
- provenance: {
378
- sourceType: 'cron',
379
- createdBy: machine.created_by,
380
- invokedAt: new Date().toISOString(),
381
- cronId: scheduleId
382
- }
383
- }
384
- }
385
-
386
- /**
387
- * Resolves a machine principal for an event trigger execution.
388
- *
389
- * Loads the trigger record and its associated action. The action must have a
390
- * `default_machine_principal_id` configured — this is the API key record that
391
- * provides identity and scopes for the trigger's execution context.
392
- *
393
- * @param triggerId - UUID of the trigger from `x-trigger-id` header
394
- * @param event - Raw Netlify event (used for eventId and provenance)
395
- * @returns Promise<Principal> — machine principal from the trigger's action config
396
- * @throws Error — on invalid trigger, missing action config, or missing machine
397
- * @inputSpec triggerId: string — valid UUID in the triggers table
398
- * @outputSpec Principal with type='machine', provenance.sourceType='trigger', triggerId set
399
- * @sideEffects DB reads: triggers table, api_keys table
400
- * @calledBy resolvePrincipal (trigger branch)
401
- * @calls adminDb.from('triggers'), adminDb.from('api_keys')
402
- */
403
- async function resolveTriggerPrincipal(triggerId: string, event: any): Promise<Principal> {
404
- // Load the trigger
405
- const { data: trigger, error: triggerError } = await adminDb
406
- .from('triggers')
407
- .select(`
408
- *,
409
- action:target_id (*)
410
- `)
411
- .eq('id', triggerId)
412
- .single()
413
-
414
- if (triggerError || !trigger) {
415
- throw new Error('Invalid trigger: ' + triggerId)
416
- }
417
-
418
- // Get the action's default machine principal
419
- const action = trigger.action
420
- if (!action?.default_machine_principal_id) {
421
- throw new Error('Trigger action has no machine principal configured')
422
- }
423
-
424
- const { data: machine, error: machineError } = await adminDb
425
- .from('api_keys')
426
- .select('*')
427
- .eq('id', action.default_machine_principal_id)
428
- .single()
429
-
430
- if (machineError || !machine) {
431
- throw new Error('Machine principal not found: ' + action.default_machine_principal_id)
432
- }
433
-
434
- return {
435
- id: machine.id,
436
- type: 'machine',
437
- accountId: machine.account_id,
438
- scopes: machine.scopes || [],
439
- machineType: machine.machine_type,
440
- isInternal: machine.is_internal,
441
- provenance: {
442
- sourceType: 'trigger',
443
- createdBy: machine.created_by,
444
- invokedAt: new Date().toISOString(),
445
- triggerId: triggerId,
446
- eventId: event.body?.eventId || event.headers?.['x-event-id']
447
- }
448
- }
449
- }
450
-
451
- /**
452
- * Resolves a human principal from a Supabase JWT Bearer token.
453
- *
454
- * Validates the token with `supabase.auth.getUser`, resolves the internal
455
- * person UUID from the auth user, then loads the person's role via `role_id` FK.
456
- * Role slugs are the source of truth for `PermissionEngine.isSystemAdmin` checks.
457
- *
458
- * If the person's `auth_uid` is not yet set, it is backfilled on first login
459
- * (side effect on the people table).
460
- *
461
- * @param token - Raw JWT string extracted from `Authorization: Bearer <token>`
462
- * @param event - Raw Netlify event (used for IP/user-agent provenance)
463
- * @returns Promise<Principal> — human principal with roles from the DB
464
- * @throws Error('Invalid authentication token') — on expired or invalid JWT
465
- * @throws Error('Person not found') — if people record doesn't exist for this auth user
466
- * @inputSpec token: string — valid non-expired Supabase JWT
467
- * @outputSpec Principal with type='human', roles from people.role_id → roles.slug
468
- * @sideEffects DB reads: supabase.auth.getUser, people table (with role join)
469
- * @sideEffects DB write (conditional): people.auth_uid backfill on first login
470
- * @calledBy resolvePrincipal (jwt branch)
471
- * @calls supabase.auth.getUser, resolveInternalPersonId, adminDb.from('people')
472
- * @testIntegration tests/integration/auth.test.ts
473
- */
474
- async function resolveHumanPrincipal(token: string, event: any): Promise<Principal> {
475
- // Validate JWT with Supabase
476
- const { data: { user }, error } = await supabase.auth.getUser(token)
477
-
478
- if (error || !user) {
479
- return ANONYMOUS_PRINCIPAL
480
- }
481
-
482
- // Resolve internal person ID from auth user
483
- const personId = await resolveInternalPersonId(user.id, user.email)
484
-
485
- // Load person details
486
- const { data: person, error: personError } = await adminDb
487
- .from('people')
488
- .select('*, role:role_id(slug, name, is_system, is_protected)')
489
- .eq('id', personId)
490
- .single()
491
-
492
- if (personError || !person) {
493
- throw new Error('Person not found: ' + personId)
494
- }
495
-
496
- // Resolve role from role_id
497
- const roleSlugs = person.role?.slug ? [person.role.slug] : []
498
-
499
- return {
500
- id: personId,
501
- type: 'human',
502
- accountId: person.account_id || null,
503
- roles: roleSlugs,
504
- displayName: person.full_name || person.email,
505
- email: person.email,
506
- provenance: {
507
- sourceType: 'jwt',
508
- createdBy: personId, // Self-created through auth
509
- invokedAt: new Date().toISOString(),
510
- ipAddress: getClientIp(event),
511
- userAgent: event.headers?.['user-agent'] || event.headers?.['User-Agent']
512
- },
513
- authContext: { jwt: token }
514
- }
515
- }
516
-
517
- /**
518
- * Resolves the internal people table UUID from a Supabase auth user ID.
519
- *
520
- * Lookup strategy (in order):
521
- * 1. Match by `email` — most reliable; also backfills `auth_uid` if missing
522
- * 2. Fallback: match by `auth_uid` directly
523
- * 3. Last resort: return `authUserId` as-is (person not yet in people table)
524
- *
525
- * The email-first strategy handles the case where a person was created manually
526
- * in the people table before the user completed Supabase Auth registration.
527
- *
528
- * @param authUserId - UUID from Supabase auth.users (supabase.auth.getUser result)
529
- * @param email - Email address from the Supabase auth user (optional but preferred)
530
- * @returns Promise<string> — internal person UUID, or authUserId as fallback
531
- * @throws never — graceful fallback to authUserId on any lookup failure
532
- * @inputSpec authUserId: string — valid Supabase auth user UUID
533
- * @inputSpec email: string | undefined — used for primary lookup
534
- * @outputSpec string — internal people.id UUID
535
- * @sideEffects DB reads: people table (by email, then by auth_uid)
536
- * @sideEffects DB write (conditional): people.auth_uid backfill when found by email
537
- * @calledBy resolveHumanPrincipal
538
- * @calls adminDb.from('people')
539
- */
540
- async function resolveInternalPersonId(authUserId: string, email?: string): Promise<string> {
541
- // Try to find by email first (more reliable)
542
- if (email) {
543
- const { data: byEmail } = await adminDb
544
- .from('people')
545
- .select('id, auth_uid')
546
- .eq('email', email)
547
- .maybeSingle()
548
-
549
- if (byEmail) {
550
- // Update auth_uid if not set
551
- if (!byEmail.auth_uid) {
552
- await adminDb
553
- .from('people')
554
- .update({ auth_uid: authUserId })
555
- .eq('id', byEmail.id)
556
- }
557
- return byEmail.id
558
- }
559
- }
560
-
561
- // Fallback: try by auth_uid
562
- const { data: byAuthId } = await adminDb
563
- .from('people')
564
- .select('id')
565
- .eq('auth_uid', authUserId)
566
- .maybeSingle()
567
-
568
- if (byAuthId) return byAuthId.id
569
-
570
- // Not found — auto-create the person (handles cases where auth user pre-existed DB reset
571
- // or trigger fired before seed data was in place)
572
- const { data: account } = await adminDb
573
- .from('accounts')
574
- .select('id')
575
- .eq('slug', 'spine-system')
576
- .single()
577
-
578
- const { data: type } = await adminDb
579
- .from('types')
580
- .select('id')
581
- .eq('kind', 'person')
582
- .eq('slug', 'person')
583
- .single()
584
-
585
- if (!account || !type) {
586
- throw new Error('Cannot auto-create person: spine-system account or person type not found in DB')
587
- }
588
-
589
- // First person ever gets system_admin role
590
- const { count } = await adminDb
591
- .from('people')
592
- .select('id', { count: 'exact', head: true })
593
-
594
- let roleId: string | null = null
595
- if ((count ?? 0) === 0) {
596
- const { data: adminRole } = await adminDb
597
- .from('roles')
598
- .select('id')
599
- .eq('slug', 'system_admin')
600
- .single()
601
- roleId = adminRole?.id ?? null
602
- }
603
-
604
- const { data: newPerson, error: createError } = await adminDb
605
- .from('people')
606
- .upsert({
607
- id: authUserId,
608
- auth_uid: authUserId,
609
- email: email ?? '',
610
- full_name: email ? email.split('@')[0] : 'Unknown',
611
- account_id: account.id,
612
- type_id: type.id,
613
- role_id: roleId,
614
- is_active: true
615
- }, { onConflict: 'id' })
616
- .select('id')
617
- .single()
618
-
619
- if (createError || !newPerson) {
620
- throw new Error('Failed to auto-create person for auth user ' + authUserId + ': ' + createError?.message)
621
- }
622
-
623
- return newPerson.id
624
- }
625
-
626
- // ─── HELPER FUNCTIONS ────────────────────────────────────────────────────────
627
-
628
- /**
629
- * Extracts the client IP address from event headers.
630
- *
631
- * Checks headers in priority order: `x-forwarded-for` (load balancer),
632
- * `x-real-ip` (proxy), then `requestContext.identity.sourceIp` (API Gateway).
633
- * Returns `undefined` if none are present.
634
- *
635
- * @param event - Raw Netlify event object
636
- * @returns string | undefined — first non-empty IP found
637
- * @throws never
638
- * @inputSpec event.headers: Record<string, string> | undefined
639
- * @outputSpec string — IPv4 or IPv6 address; may contain comma-separated list
640
- * from x-forwarded-for (take first value if parsing is needed)
641
- * @sideEffects none
642
- * @calledBy resolveMachinePrincipal, resolveHumanPrincipal (for provenance)
643
- */
644
- function getClientIp(event: any): string | undefined {
645
- return event.headers?.['x-forwarded-for'] ||
646
- event.headers?.['X-Forwarded-For'] ||
647
- event.headers?.['x-real-ip'] ||
648
- event.headers?.['X-Real-Ip'] ||
649
- event.requestContext?.identity?.sourceIp
650
- }
651
-
652
- /**
653
- * Checks whether a machine principal has been granted a specific scope.
654
- *
655
- * Scope matching supports three patterns:
656
- * 1. Exact: `'items:read'` matches only `'items:read'`
657
- * 2. Wildcard action: `'items:*'` matches `'items:read'`, `'items:write'`, etc.
658
- * 3. Global wildcard: `'*:*'` matches any scope
659
- *
660
- * Returns `false` for non-machine principals — role-based checks use `humanHasRole`.
661
- *
662
- * @param principal - The principal to check
663
- * @param scope - The required scope string in `'resource:action'` format
664
- * @returns boolean — true if any of the principal's scopes grant the required scope
665
- * @throws never
666
- * @inputSpec principal.type: 'machine' — returns false for human principals
667
- * @inputSpec scope: string — must be in 'resource:action' format
668
- * @inputSpec principal.scopes: string[] — list of granted scope strings
669
- * @outputSpec boolean
670
- * @sideEffects none
671
- * @calledBy permissions.ts (checkMachineScope), any custom code doing scope checks
672
- * @testUnit tests/unit/principal.test.ts — 'machineHasScope' describe block
673
- *
674
- * @example
675
- * ```ts
676
- * import { machineHasScope } from '../_shared/index'
677
- * if (!machineHasScope(principal, 'items:write')) {
678
- * return { error: 'Insufficient scope' }
679
- * }
680
- * ```
681
- */
682
- export function machineHasScope(principal: Principal, scope: string): boolean {
683
- if (principal.type !== 'machine') return false
684
-
685
- const scopes = principal.scopes || []
686
- const [resource, action] = scope.split(':')
687
-
688
- // Exact match
689
- if (scopes.includes(scope)) return true
690
-
691
- // Wildcard resource
692
- if (scopes.includes(`${resource}:*`)) return true
693
-
694
- // Global wildcard
695
- if (scopes.includes('*:*')) return true
696
-
697
- return false
698
- }
699
-
700
- /**
701
- * Checks whether a human principal has been assigned a specific role.
702
- *
703
- * Returns `false` for non-human (machine) principals — scope checks use
704
- * `machineHasScope`. Role slugs come from `people.role_id → roles.slug`
705
- * and are loaded at resolution time in `resolveHumanPrincipal`.
706
- *
707
- * @param principal - The principal to check
708
- * @param roleSlug - The role slug to look for (e.g. 'system_admin', 'agent')
709
- * @returns boolean — true if principal.roles includes the given slug
710
- * @throws never
711
- * @inputSpec principal.type: 'human' — returns false for machine principals
712
- * @inputSpec roleSlug: string — must match exactly (case-sensitive)
713
- * @inputSpec principal.roles: string[] | undefined
714
- * @outputSpec boolean
715
- * @sideEffects none
716
- * @calledBy isSystemAdmin, permissions.ts (PermissionEngine.isSystemAdmin)
717
- * @testUnit tests/unit/principal.test.ts — 'humanHasRole' describe block
718
- */
719
- export function humanHasRole(principal: Principal, roleSlug: string): boolean {
720
- if (principal.type !== 'human') return false
721
- return principal.roles?.includes(roleSlug) || false
722
- }
723
-
724
- /**
725
- * Returns true if the principal holds the `system_admin` role.
726
- *
727
- * This is the canonical system admin check used by `middleware.ts`
728
- * (`requireSystemContextWithAudit`) and re-exported from `permissions.ts`
729
- * (`PermissionEngine.isSystemAdmin`). system_admin bypasses all three
730
- * permission surfaces.
731
- *
732
- * @param principal - The principal to check
733
- * @returns boolean — true only if type='human' and roles includes 'system_admin'
734
- * @throws never
735
- * @inputSpec principal: Principal — any resolved principal
736
- * @outputSpec boolean — false for machine principals, anonymous, or missing roles
737
- * @sideEffects none
738
- * @calledBy middleware.ts (requireSystemContextWithAudit), permissions.ts (PermissionEngine),
739
- * any handler doing system-admin-only checks
740
- * @calls humanHasRole
741
- * @testUnit tests/unit/principal.test.ts — 'isSystemAdmin' describe block
742
- */
743
- export function isSystemAdmin(principal: Principal): boolean {
744
- return humanHasRole(principal, 'system_admin')
745
- }
746
-
747
- /**
748
- * Selects the correct Supabase database client for the given principal.
749
- *
750
- * This is the only place in the codebase where the two-client selection
751
- * decision is made. The result is stored in `ctx.db` and used for all
752
- * subsequent DB queries in the request.
753
- *
754
- * Selection logic:
755
- * - Human principal with JWT → `getUserDb(jwt)` — enforces RLS
756
- * - Machine principal → `adminDb` — RLS policies check machine ID in policies
757
- * - Anonymous → `adminDb` (but anonymous requests are rejected before DB access)
758
- *
759
- * @param principal - The resolved principal
760
- * @returns SupabaseClient — RLS-scoped for humans, admin for machines
761
- * @throws never
762
- * @inputSpec principal.type: 'human' | 'machine'
763
- * @inputSpec principal.authContext.jwt: string | undefined — required for human client
764
- * @outputSpec SupabaseClient — getUserDb result (human) or adminDb (machine)
765
- * @sideEffects none (client construction only)
766
- * @calledBy middleware.ts (createHandler — `const ctxDb = getPrincipalDb(principal)`)
767
- * @calls getUserDb (db.ts), adminDb (db.ts)
768
- * @testUnit tests/unit/principal.test.ts — 'getPrincipalDb' describe block
769
- */
770
- export function getPrincipalDb(principal: Principal) {
771
- if (principal.type === 'human' && principal.authContext?.jwt) {
772
- return getUserDb(principal.authContext.jwt)
773
- }
774
-
775
- // Machines use admin client - RLS policies check their ID
776
- return adminDb
777
- }
778
-
779
- /**
780
- * Serializes a principal into a safe, structured object for audit log entries.
781
- *
782
- * Strips `authContext` entirely (never log JWT or API key values). The returned
783
- * object is safe to store in the `metadata` JSONB column of the `logs` table.
784
- *
785
- * @param principal - Any resolved principal including ANONYMOUS_PRINCIPAL
786
- * @returns object — audit-safe principal summary
787
- * @throws never
788
- * @inputSpec principal: Principal — any resolved principal
789
- * @outputSpec { id, type, account_id } + role/scope fields depending on type
790
- * @outputSpec authContext is NEVER included in output
791
- * @sideEffects none
792
- * @calledBy audit.ts (emitAudit — in metadata.principal)
793
- * @testUnit tests/unit/principal.test.ts — 'formatPrincipalForAudit' describe block
794
- *
795
- * @example
796
- * ```ts
797
- * await emitAudit(ctx, 'items.delete', { type: 'item', id }, {
798
- * principal_snapshot: formatPrincipalForAudit(ctx.principal)
799
- * })
800
- * ```
801
- */
802
- export function formatPrincipalForAudit(principal: Principal): object {
803
- return {
804
- id: principal.id,
805
- type: principal.type,
806
- account_id: principal.accountId,
807
- ...(principal.type === 'human' && {
808
- roles: principal.roles,
809
- display_name: principal.displayName
810
- }),
811
- ...(principal.type === 'machine' && {
812
- machine_type: principal.machineType,
813
- is_internal: principal.isInternal,
814
- scopes: principal.scopes
815
- }),
816
- provenance: principal.provenance
817
- }
818
- }