spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,15 +1,30 @@
1
- /**
2
- * @module cil-state
3
- * @audience both
4
- * @layer shared-core
5
- * @stability experimental
6
- *
7
- * Shared state-machine helpers for the CIL ingestion pipeline.
8
- */
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
9
15
 
10
16
  import { adminDb } from './db'
11
17
 
12
- export type FileStatus = 'discovered' | 'pass2_complete' | 'pass3_complete' | 'pass4_complete' | 'pass4b_complete' | 'pass5_complete'
18
+ export type FileStatus =
19
+ | '0-discovered'
20
+ | '1-selected'
21
+ | '2-chunked'
22
+ | '3-first-pass-complete'
23
+ | '4-first-pass-embedded'
24
+ | '5-first-pass-mapped'
25
+ | '6-second-pass-complete'
26
+ | '7-second-pass-embedded'
27
+ | '8-second-pass-mapped'
13
28
 
14
29
  export interface FileTreeNode {
15
30
  id?: string | null
@@ -23,10 +38,20 @@ export interface FileTreeNode {
23
38
  selected?: boolean
24
39
  }
25
40
 
26
- /**
27
- * Check whether a file path is inside any of the selected paths.
28
- * A file is selected if its path equals a selected path, or starts with a selected directory path + '/'.
29
- */
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
30
55
  export function isSelectedPath(filePath: string, selectedPaths: string[]): boolean {
31
56
  if (selectedPaths.length === 0) return true
32
57
  return selectedPaths.some(sp => {
@@ -36,32 +61,66 @@ export function isSelectedPath(filePath: string, selectedPaths: string[]): boole
36
61
  })
37
62
  }
38
63
 
39
- /**
40
- * Extract the file status from a cil_file item.
41
- */
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
42
78
  export function getFileStatus(fileItem: any): FileStatus {
43
- return fileItem.data?.status || 'discovered'
79
+ return fileItem.data?.status || '0-discovered'
44
80
  }
45
81
 
46
- /**
47
- * Update the status of a cil_file item.
48
- */
49
- export async function updateFileStatus(ctx: any, fileItem: any, status: FileStatus, extraData: Record<string, any> = {}): Promise<void> {
50
- const { create, update } = await import('../admin-data')
51
- const updateCtx = { ...ctx, query: { ...(ctx.query || {}), entity: 'items', id: fileItem.id } }
52
- await update(updateCtx, {
53
- data: {
54
- ...fileItem.data,
55
- ...extraData,
56
- status,
57
- last_status_at: new Date().toISOString(),
58
- },
59
- })
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+ export async function updateFileStatus(_ctx: any, fileItem: any, status: FileStatus, extraData: Record<string, any> = {}): Promise<void> {
97
+ await adminDb
98
+ .from('items')
99
+ .update({
100
+ data: {
101
+ ...fileItem.data,
102
+ ...extraData,
103
+ status,
104
+ last_status_at: new Date().toISOString(),
105
+ },
106
+ })
107
+ .eq('id', fileItem.id)
60
108
  }
61
109
 
62
- /**
63
- * Build a nested file tree from a flat list of discovered files.
64
- */
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
65
124
  export function buildFileTree(fileRecords: { file_path: string; id?: string; language: string; status?: FileStatus; extension: string }[]): FileTreeNode {
66
125
  const root: FileTreeNode = { name: 'root', path: '', type: 'directory', children: [] }
67
126
 
@@ -97,9 +156,20 @@ export function buildFileTree(fileRecords: { file_path: string; id?: string; lan
97
156
  return root
98
157
  }
99
158
 
100
- /**
101
- * Update the status of a file node in the persisted tree by its file item ID.
102
- */
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
103
173
  export function updateTreeNodeStatus(tree: FileTreeNode, fileId: string, status: FileStatus): void {
104
174
  if (tree.id === fileId) {
105
175
  tree.status = status
@@ -112,10 +182,20 @@ export function updateTreeNodeStatus(tree: FileTreeNode, fileId: string, status:
112
182
  }
113
183
  }
114
184
 
115
- /**
116
- * Update a file node in the persisted tree by its file path.
117
- * Used when the tree is built before file records exist (Pass 1).
118
- */
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
119
199
  export function updateTreeNodeByPath(tree: FileTreeNode, filePath: string, updates: { id?: string; status?: FileStatus }): void {
120
200
  if (tree.type === 'file' && tree.path === filePath) {
121
201
  if (updates.id !== undefined) tree.id = updates.id
@@ -129,20 +209,42 @@ export function updateTreeNodeByPath(tree: FileTreeNode, filePath: string, updat
129
209
  }
130
210
  }
131
211
 
132
- /**
133
- * Persist the updated tree back to the project item.
134
- */
135
- export async function saveProjectTree(ctx: any, projectId: string, tree: FileTreeNode, extraData: Record<string, any> = {}): Promise<void> {
136
- const { update } = await import('../admin-data')
137
- const { data: projItem } = await adminDb.from('items').select('id, data').eq('id', projectId).maybeSingle()
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+ export async function saveProjectTree(
227
+ _ctx: any,
228
+ projectId: string,
229
+ tree: FileTreeNode,
230
+ extraData: Record<string, any> = {},
231
+ projectItem?: any
232
+ ): Promise<void> {
233
+ let projItem = projectItem
234
+ if (!projItem) {
235
+ const { data } = await adminDb.from('items').select('id, data').eq('id', projectId).maybeSingle()
236
+ projItem = data
237
+ }
138
238
  if (!projItem) return
139
- const updateCtx = { ...ctx, query: { ...(ctx.query || {}), entity: 'items', id: projItem.id } }
140
- await update(updateCtx, {
141
- data: {
142
- ...projItem.data,
143
- file_tree: tree,
144
- ...extraData,
145
- last_tree_update: new Date().toISOString(),
146
- },
147
- })
239
+ await adminDb
240
+ .from('items')
241
+ .update({
242
+ data: {
243
+ ...projItem.data,
244
+ file_tree: tree,
245
+ ...extraData,
246
+ last_tree_update: new Date().toISOString(),
247
+ },
248
+ })
249
+ .eq('id', projItem.id)
148
250
  }
@@ -1,29 +1,28 @@
1
- /**
2
- * @module db
3
- * @audience both
4
- * @layer shared-core
5
- * @stability stable
6
- *
7
- * Supabase client factory and PostgREST join helpers. This module owns the
8
- * two-client pattern that is central to Spine's security model: `adminDb`
9
- * bypasses RLS for system operations; `getUserDb` enforces RLS for all
10
- * human-principal requests. Never use `adminDb` for user-scoped queries —
11
- * doing so silently bypasses account isolation.
12
- *
13
- * @seeAlso principal.ts (getPrincipalDb selects between these two clients)
14
- * @seeAlso middleware.ts (ctx.db is set from getPrincipalDb at request time)
15
- */
16
1
 
2
+
3
+
4
+
5
+
6
+ // CIL[BEGIN chunk_id="db_moduleheader_1784066609" type="module_header" identity="module_header" parent="none"]
7
+ // CIL[DOC-MACRO purpose="Database connection setup and configuration"]
8
+ // CIL[DOC-RESPONSIBILITY responsibility="Configure Supabase clients and environment variables"]
9
+ // CIL[DOC-CONTEXT module="_shared/db" layer="infrastructure" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
10
+ // CIL[DOC-CONTRACT inputs="url" outputs="configured clients and constants" errors="invalid URLs" side_effects="global configuration"]
11
+ // CIL[DOC-MICRO logic_summary="Validates environment variables and sets up Supabase connection parameters" notable_decisions="[]"]
12
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
13
+ // CIL[DOC-STABILITY stability="stable"]
14
+ // CIL[DOC-AUDIENCE audience="developer"]
15
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
16
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
17
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
18
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
19
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
20
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
17
21
  import { createClient } from '@supabase/supabase-js'
18
22
  import WebSocket from 'ws'
19
23
 
20
- // ─── ENVIRONMENT RESOLUTION ──────────────────────────────────────────────────
21
-
22
24
  const _env = (globalThis as any).process?.env || {}
23
25
 
24
- // Use a known-valid placeholder URL so that createClient() doesn't throw at
25
- // module load time when env vars haven't been set yet (e.g. during CLI init).
26
- // All real requests validate credentials before hitting the DB.
27
26
  const PLACEHOLDER_URL = 'https://placeholder.supabase.co'
28
27
  const PLACEHOLDER_KEY = 'placeholder'
29
28
 
@@ -36,62 +35,40 @@ const supabaseUrl: string = rawUrl && isValidUrl(rawUrl) ? rawUrl : PLACEHOLDER_
36
35
  const supabaseServiceKey: string = _env.SUPABASE_SERVICE_ROLE_KEY || PLACEHOLDER_KEY
37
36
  const supabaseAnonKey: string = _env.SUPABASE_ANON_KEY || PLACEHOLDER_KEY
38
37
  const dbSchema: string = _env.DB_SCHEMA || 'public'
39
-
40
- // ─── CLIENTS ─────────────────────────────────────────────────────────────────
41
-
42
- /**
43
- * Service-role Supabase client. Bypasses Row Level Security.
44
- *
45
- * Use this ONLY for:
46
- * - System/cron operations that must cross account boundaries (`system-cron.ts`)
47
- * - Principal resolution lookups (`principal.ts` — resolving auth_uid to person)
48
- * - Machine principal validation RPCs
49
- * - Test helpers that need to seed/clean data across accounts
50
- *
51
- * Do NOT use this in request handlers for user-scoped data reads or writes.
52
- * Always prefer `ctx.db` (set by `getPrincipalDb` in middleware) for those.
53
- *
54
- * @inputSpec SUPABASE_URL: string — valid Supabase project URL, required
55
- * @inputSpec SUPABASE_SERVICE_ROLE_KEY: string — service role JWT, required
56
- * @outputSpec SupabaseClient — PostgREST client scoped to `dbSchema`, RLS disabled
57
- * @sideEffects none (client construction only)
58
- * @calledBy principal.ts, middleware.ts, system-cron.ts, permissions.ts,
59
- * tests/integration/helpers.ts
60
- * @calls createClient (supabase-js)
61
- * @testUnit tests/unit/pipeline-runner.test.ts — mocked via vi.mock
62
- * @testIntegration tests/integration/helpers.ts — used directly as adminDb
63
- *
64
- * @example API handler (system operation)
65
- * ```ts
66
- * import { adminDb } from './_shared/db'
67
- * const { data } = await adminDb.from('types').select('*').eq('slug', 'item')
68
- * ```
69
- *
70
- * @example Import usage (v2-custom/ — system-level only)
71
- * ```ts
72
- * import { adminDb } from '../_shared/index'
73
- * // Only use adminDb if your custom code runs as a system/cron principal
74
- * ```
75
- */
38
+ // CIL[END chunk_id="db_moduleheader_1784066609"]
39
+ // CIL[BEGIN chunk_id="db_admindb_1784066609" type="variable" identity="adminDb" parent="none"]
40
+ // CIL[DOC-MACRO purpose="Admin database client with full permissions"]
41
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide database access for admin operations"]
42
+ // CIL[DOC-CONTEXT module="_shared/db" layer="infrastructure" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
43
+ // CIL[DOC-CONTRACT inputs="none" outputs="admin Supabase client" errors="invalid credentials" side_effects="database connection"]
44
+ // CIL[DOC-MICRO logic_summary="Creates Supabase client with service role key for admin access" notable_decisions="[]"]
45
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
46
+ // CIL[DOC-STABILITY stability="stable"]
47
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/IntegrationsPage.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/functions/_shared/principal.ts, \."]
48
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
49
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
50
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
51
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
52
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
76
53
  export const adminDb = createClient(supabaseUrl, supabaseServiceKey, {
77
54
  db: { schema: dbSchema },
78
55
  realtime: { transport: WebSocket as any }
79
56
  })
80
-
81
- // ─── CHUNK_START: SHARED_DB_GET_USER_DB ──────────────────────────────────────────────
82
- /**
83
- * @chunk-id SHARED_DB_GET_USER_DB_1_0_0
84
- * @version 1.0.0
85
- * @hash af3c792634c60ced1c1c4184cfc6c20add90ab97eb62f7e46bdf40ae2899a0f8
86
- * @macro User Database Client Factory
87
- * @micro Creates RLS-enforced Supabase client for specific user JWT
88
- * @inputs jwt: string — Valid Supabase JWT from Authorization header
89
- * @outputs SupabaseClient — PostgREST client with RLS enforced
90
- * @depends-on [createClient, supabaseUrl, supabaseAnonKey, dbSchema]
91
- * @depended-by [principal.ts, middleware.ts]
92
- * @side-effects [Client construction with Authorization header]
93
- * @tags database, supabase, rls, authentication, user-scoped
94
- */
57
+ // CIL[END chunk_id="db_admindb_1784066609"]
58
+ // CIL[BEGIN chunk_id="db_getuserdb_1784066609" type="function" identity="getUserDb" parent="none"]
59
+ // CIL[DOC-MACRO purpose="Create user-scoped database client"]
60
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide authenticated database access for users"]
61
+ // CIL[DOC-CONTEXT module="_shared/db" layer="infrastructure" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
62
+ // CIL[DOC-CONTRACT inputs="none" outputs="user Supabase client" errors="invalid JWT" side_effects="database connection"]
63
+ // CIL[DOC-MICRO logic_summary="Creates Supabase client with user JWT for row-level security" notable_decisions="[]"]
64
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
65
+ // CIL[DOC-STABILITY stability="stable"]
66
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
67
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
68
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
69
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
70
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
71
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
95
72
  export function getUserDb(jwt: string) {
96
73
  return createClient(supabaseUrl, supabaseAnonKey, {
97
74
  db: { schema: dbSchema },
@@ -99,64 +76,40 @@ export function getUserDb(jwt: string) {
99
76
  realtime: { transport: WebSocket as any }
100
77
  })
101
78
  }
102
- // ─── CHUNK_END: SHARED_DB_GET_USER_DB ────────────────────────────────────────────────
103
-
104
- // ─── TYPES ───────────────────────────────────────────────────────────────────
105
-
106
- /**
107
- * Standard shape returned by all Supabase PostgREST queries.
108
- *
109
- * Both `data` and `error` follow the Supabase JS client convention: on success,
110
- * `error` is null; on failure, `data` is null and `error` contains the Postgres
111
- * error details. Always check `error` before using `data`.
112
- *
113
- * @inputSpec T — the expected shape of a successful result row
114
- * @outputSpec data: T | null — the query result, null on error
115
- * @outputSpec error: any — null on success, Postgres/PostgREST error object on failure
116
- * @calledBy used as return type annotation across all functions/*.ts handlers
117
- *
118
- * @example
119
- * ```ts
120
- * const result: DbResult<Item> = await adminDb.from('items').select('*').single()
121
- * if (result.error) throw result.error
122
- * return result.data!
123
- * ```
124
- */
79
+ // CIL[END chunk_id="db_getuserdb_1784066609"]
80
+ // CIL[BEGIN chunk_id="db_dbresult_1784066609" type="type" identity="DbResult" parent="none"]
81
+ // CIL[DOC-MACRO purpose="Database operation result type"]
82
+ // CIL[DOC-RESPONSIBILITY responsibility="Define standard database response structure"]
83
+ // CIL[DOC-CONTEXT module="_shared/db" layer="infrastructure" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
84
+ // CIL[DOC-CONTRACT inputs="none" outputs="DbResult type" errors="none" side_effects="type definition"]
85
+ // CIL[DOC-MICRO logic_summary="Generic type for database operations with data and error fields" notable_decisions="[]"]
86
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
87
+ // CIL[DOC-STABILITY stability="stable"]
88
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/functions/_shared/schema-utils.ts, \."]
89
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
90
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
91
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
92
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
93
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
125
94
  export type DbResult<T> = {
126
95
  data: T | null
127
96
  error: any
128
97
  }
129
-
130
- // ─── JOIN HELPERS ─────────────────────────────────────────────────────────────
131
-
132
- /**
133
- * PostgREST relationship hint strings for all foreign keys in the public schema.
134
- *
135
- * These strings are interpolated into `.select()` calls to eager-load related
136
- * records in a single query. They use explicit `!fk_column` hints to resolve
137
- * ambiguous relationships — required when a table has multiple FKs to the same
138
- * target table, or when the FK column name doesn't follow PostgREST's default
139
- * `tablename_id` inference convention (e.g. `created_by` → `people.id`).
140
- *
141
- * Only add a join here when it is used in two or more handlers. One-off joins
142
- * should be written inline.
143
- *
144
- * @inputSpec none — these are static string constants
145
- * @outputSpec string — valid PostgREST embed expression for use in .select()
146
- * @sideEffects none
147
- * @calledBy types.ts, apps.ts, pipelines.ts, triggers.ts, admin-data.ts, and others
148
- * @testUnit none — these are string constants; incorrect joins fail at runtime
149
- * @testIntegration tests/integration/admin-data-accounts.test.ts — exercises joins.type
150
- *
151
- * @example
152
- * ```ts
153
- * import { joins } from './_shared/db'
154
- * const { data } = await ctx.db
155
- * .from('items')
156
- * .select(`*, ${joins.type}, ${joins.app}`)
157
- * // Returns items with nested type and app objects
158
- * ```
159
- */
98
+ // CIL[END chunk_id="db_dbresult_1784066609"]
99
+ // CIL[BEGIN chunk_id="db_joins_1784066609" type="variable" identity="joins" parent="none"]
100
+ // CIL[DOC-MACRO purpose="Database join configuration for common relationships"]
101
+ // CIL[DOC-RESPONSIBILITY responsibility="Define standard foreign key join patterns"]
102
+ // CIL[DOC-CONTEXT module="_shared/db" layer="infrastructure" related_files="[]"]
103
+ // CIL[DOC-CONTRACT inputs="none" outputs="join configuration object" errors="none" side_effects="query optimization"]
104
+ // CIL[DOC-MICRO logic_summary="Predefined join strings for common table relationships" notable_decisions="[]"]
105
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="external_symbols_unknown"]
106
+ // CIL[DOC-STABILITY stability="stable"]
107
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
108
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
109
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
110
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
111
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
112
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
160
113
  export const joins = {
161
114
  type: 'type:types!type_id(id, slug, name, icon, color, design_schema)',
162
115
  app: 'app:apps!app_id(id, slug, name)',
@@ -166,3 +119,4 @@ export const joins = {
166
119
  role: 'role:roles!role_id(id, slug, name)',
167
120
  pipeline: 'pipeline:pipelines!pipeline_id(id, name)',
168
121
  }
122
+ // CIL[END chunk_id="db_joins_1784066609"]
@@ -0,0 +1,63 @@
1
+ // CIL[BEGIN chunk_id="hash_api_key_module_header_1784200005" type="module_header" identity="imports" parent="none"]
2
+ // CIL[DOC-MACRO purpose="Hash and verify API keys using PBKDF2"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Generate secure, salted key hashes and verify raw keys"]
4
+ // CIL[DOC-CONTEXT module="hash_api_key" layer="infrastructure" related_files="[".framework/functions/api-keys.ts",".framework/functions/integration-routes.ts"]"]
5
+ // CIL[DOC-CONTRACT inputs="raw key" outputs="{ key_prefix, key_hash }" errors="hashing error" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Uses crypto.pbkdf2Sync to derive a 32-byte key from a random salt and the raw key. Stored format is pbkdf2$<iterations>$<salt base64>$<hash base64>." notable_decisions="["PBKDF2 is built into Node; no extra dependency.","Format is self-describing so iterations can be upgraded later.","key_prefix is the first 8 characters of the raw key for lookup."]"]
7
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
8
+ // CIL[DOC-STABILITY stability="experimental"]
9
+ // CIL[DOC-AUDIENCE audience="developer"]
10
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/api-keys.ts, .framework/functions/integration-routes.ts."]
11
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
12
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
13
+ // CIL[DOC-VECTOR type="constraint" content="Uses Node crypto only."]
14
+ // CIL[DOC-VECTOR type="implementation_pattern" content="Hash on key creation; verify on inbound webhook."]
15
+ // CIL[DOC-VECTOR type="user_journey" content="Machine-principal credential lifecycle."]
16
+
17
+ import { randomBytes, pbkdf2Sync, timingSafeEqual } from 'crypto'
18
+
19
+ const ITERATIONS = 100000
20
+ const KEY_LENGTH = 32
21
+ const DIGEST = 'sha256'
22
+
23
+ export function hashApiKey(rawKey: string, iterations = ITERATIONS): { key_prefix: string; key_hash: string; rawKey: string } {
24
+ const key_prefix = rawKey.slice(0, 8)
25
+ const salt = randomBytes(16)
26
+ const hash = pbkdf2Sync(rawKey, salt, iterations, KEY_LENGTH, DIGEST)
27
+ const key_hash = `pbkdf2$${iterations}$${salt.toString('base64url')}$${hash.toString('base64url')}`
28
+
29
+ return { key_prefix, key_hash, rawKey }
30
+ }
31
+
32
+ export function verifyApiKey(rawKey: string, keyHash: string): boolean {
33
+ if (!keyHash || !keyHash.startsWith('pbkdf2$')) {
34
+ return false
35
+ }
36
+
37
+ const parts = keyHash.split('$')
38
+ if (parts.length !== 4) return false
39
+
40
+ const [, iterStr, saltB64, hashB64] = parts
41
+ const iterations = parseInt(iterStr, 10)
42
+ if (Number.isNaN(iterations) || iterations <= 0) return false
43
+
44
+ let salt: Buffer
45
+ let storedHash: Buffer
46
+ try {
47
+ salt = Buffer.from(saltB64, 'base64url')
48
+ storedHash = Buffer.from(hashB64, 'base64url')
49
+ } catch {
50
+ return false
51
+ }
52
+
53
+ const derived = pbkdf2Sync(rawKey, salt, iterations, storedHash.length, DIGEST)
54
+ if (derived.length !== storedHash.length) return false
55
+
56
+ try {
57
+ return timingSafeEqual(derived, storedHash)
58
+ } catch {
59
+ return false
60
+ }
61
+ }
62
+
63
+ // CIL[END chunk_id="hash_api_key_module_header_1784200005"]