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,56 +1,328 @@
1
- /**
2
- * @module embeddings
3
- * @audience core-contributor
4
- * @layer api-handler
5
- * @stability stable
6
- *
7
- * CRUD, lifecycle, and search API for the `embeddings` table. Embeddings store
8
- * vector representations of text chunks for RAG workloads.
9
- *
10
- * **Routed by:** `GET/POST/PATCH /.netlify/functions/embeddings`
11
- *
12
- * **Actions:**
13
- * | method | ?action | handler |
14
- * |--------|-----------------|-------------------|
15
- * | POST | generate | generate |
16
- * | POST | revoke | revoke |
17
- * | POST | approve | approve |
18
- * | GET | stats | getStats |
19
- * | POST | batch-create | batchCreate |
20
- * | POST | delete-document | deleteByDocument |
21
- * | POST | cleanup | cleanup |
22
- * | POST | search-similar | searchSimilar |
23
- * | POST | search-semantic | searchSemantic |
24
- * | GET | ?id | get |
25
- * | GET | (default) | list |
26
- * | POST | — | create |
27
- * | PATCH | — | update |
28
- *
29
- * **Authorization:** All operations use `ctx.db` (RLS-scoped). Account context
30
- * required for writes.
31
- *
32
- * INVARIANT: `generate` is the single entry point for AI-powered embedding
33
- * creation. Custom code must call `generate` — never write to the embeddings
34
- * table directly.
35
- * INVARIANT: `update` only patches `content` and `metadata`.
36
- * INVARIANT: `searchSimilar` requires a pre-computed `query_embedding` vector.
37
- *
38
- * @seeAlso agent-runner.ts (retrieveKnowledge calls match_embeddings RPC)
39
- * @seeAlso pipeline-runner.ts (generate_embeddings stage type)
40
- * @seeAlso audit.ts (emitLog for embedding.* events)
41
- */
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+ // CIL[DOC-MACRO purpose="Create single embedding record"]
24
+ // CIL[DOC-RESPONSIBILITY responsibility="Create embedding with validation and audit logging"]
25
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
26
+ // CIL[DOC-CONTRACT inputs="embedding_data" outputs="created_embedding" errors="validation_errors,db_errors" side_effects="db_write,audit_log"]
27
+ // CIL[DOC-MICRO logic_summary="Validate input, create embedding record, log creation event" notable_decisions="[]"]
28
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
29
+ export const create = createHandler(async (ctx, body) => {
30
+ const { model_id, document_id, chunk_index, content, metadata } = body
31
+
32
+ if (!model_id || !document_id || !content) {
33
+ throw new Error('model_id, document_id, and content are required')
34
+ }
35
+
36
+ if (!ctx.principal || ctx.principal.id === 'anonymous' || !ctx.accountId) {
37
+ throw new Error('User context (person and account) required')
38
+ }
39
+
40
+ const { data, error: err } = await ctx.db
41
+ .from('embeddings')
42
+ .insert({
43
+ model_id,
44
+ document_id,
45
+ chunk_index: chunk_index || 0,
46
+ content,
47
+ metadata: metadata || {},
48
+ account_id: ctx.accountId
49
+ })
50
+ .select()
51
+ .single()
52
+
53
+ if (err) throw err
54
+
55
+ await emitLog(ctx, 'embedding.created',
56
+ { type: 'embedding', id: data.id },
57
+ { after: { document_id, model_id } }
58
+ )
59
+
60
+ return data
61
+ })
62
+
63
+
64
+
65
+
66
+ // CIL[DOC-MACRO purpose="Update embedding record"]
67
+ // CIL[DOC-RESPONSIBILITY responsibility="Modify embedding content or metadata"]
68
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
69
+ // CIL[DOC-CONTRACT inputs="embedding_id,updates" outputs="updated_embedding" errors="not_found,db_errors" side_effects="db_write,audit_log"]
70
+ // CIL[DOC-MICRO logic_summary="Update specified fields, log modification event" notable_decisions="[]"]
71
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
72
+ export const update = createHandler(async (ctx, body) => {
73
+ const id = body?.id || ctx.query?.id
74
+ const { id: _bodyId, content, metadata } = body || {}
75
+
76
+ if (!id) {
77
+ throw new Error('Embedding ID is required')
78
+ }
79
+
80
+ const updateFields: Record<string, any> = {}
81
+ if (content !== undefined) updateFields.content = content
82
+ if (metadata !== undefined) updateFields.metadata = metadata
83
+
84
+ const { data, error: err } = await ctx.db
85
+ .from('embeddings')
86
+ .update(updateFields)
87
+ .eq('id', id)
88
+ .select()
89
+ .single()
90
+
91
+ if (err) throw err
92
+
93
+ await emitLog(ctx, 'embedding.updated',
94
+ { type: 'embedding', id },
95
+ { after: { content_updated: !!content } }
96
+ )
97
+
98
+ return data
99
+ })
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+ // CIL[DOC-MACRO purpose="Generate embeddings for knowledge item"]
129
+ // CIL[DOC-RESPONSIBILITY responsibility="Create vectors for item content using multiple strategies"]
130
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
131
+ // CIL[DOC-CONTRACT inputs="item_id,options" outputs="generation_result" errors="validation_errors,api_errors" side_effects="db_write,api_calls,audit_log"]
132
+ // CIL[DOC-MICRO logic_summary="Extract content, call embedding API, store vectors with metadata" notable_decisions="[]"]
133
+ // CIL[DOC-CONFIDENCE pass="first" score="0.80" flags="complex_logic,external_api"]
134
+ export const generate = createHandler(async (ctx, body) => {
135
+ const { item_id, force_regenerate = false, account_id_override } = body || {}
136
+ if (!item_id) throw new Error('item_id is required')
137
+
138
+
139
+ const { data: item, error: itemErr } = await adminDb
140
+ .from('items')
141
+ .select('*, type:types(design_schema)')
142
+ .eq('id', item_id)
143
+ .single()
144
+
145
+ if (itemErr || !item) throw new Error(`Item ${item_id} not found`)
146
+
147
+ const typeDesign = item.type?.design_schema || {}
148
+ const kbType = item.data?.kb_type || 'article'
149
+
150
+
151
+ let vectorTypes: string[] = []
152
+ const rawVT = typeDesign.vector_types
153
+ if (Array.isArray(rawVT)) {
154
+ vectorTypes = rawVT
155
+ } else if (rawVT && typeof rawVT === 'object') {
156
+ vectorTypes = rawVT[kbType] || rawVT['default'] || ['semantic', 'structure']
157
+ } else {
158
+ vectorTypes = ['semantic', 'structure']
159
+ }
160
+
161
+ const embeddingFields: string[] = typeDesign.embedding_fields?.structure || []
162
+ const resolvedAccountId = account_id_override || item.account_id || await getPlatformAccountId()
163
+ const isPublished = item.status === 'published'
164
+
165
+
166
+ const { data: existing } = await adminDb
167
+ .from('embeddings')
168
+ .select('id, content_hash, chunk_index, metadata')
169
+ .eq('document_id', item_id)
170
+
171
+ const existingMap = new Map<string, any>()
172
+ for (const row of existing || []) {
173
+ const vt = row.metadata?.vector_type || ''
174
+ const ci = row.chunk_index ?? 0
175
+ existingMap.set(`${vt}:${ci}`, row)
176
+ }
177
+
178
+
179
+ if (force_regenerate) {
180
+ await adminDb.from('embeddings').delete().eq('document_id', item_id)
181
+ existingMap.clear()
182
+ }
183
+
184
+ const created: string[] = []
185
+ const skipped: string[] = []
186
+ const errors: string[] = []
187
+
188
+
189
+ const syncVectors = vectorTypes.filter(vt => vt !== 'summary')
190
+ const contents: Array<{ vt: string; text: string; chunkIndex: number }> = []
191
+
192
+ for (const vt of syncVectors) {
193
+ const builder = CONTENT_BUILDERS[vt]
194
+ if (!builder) { errors.push(`Unknown vector type: ${vt}`); continue }
195
+ const text = builder(item, { embedding_fields: embeddingFields })
196
+ if (!text) { skipped.push(`${vt}:empty`); continue }
197
+ contents.push({ vt, text, chunkIndex: 0 })
198
+ }
199
+
200
+
201
+ if (vectorTypes.includes('summary')) {
202
+ const summaryText = await generateAiSummary(item, ctx)
203
+ if (summaryText) contents.push({ vt: 'summary', text: summaryText, chunkIndex: 0 })
204
+ }
205
+
206
+
207
+ const toEmbed = contents.filter(({ vt, text, chunkIndex }) => {
208
+ const hash = contentHash(text)
209
+ const key = `${vt}:${chunkIndex}`
210
+ const row = existingMap.get(key)
211
+ if (row && row.content_hash === hash) { skipped.push(key); return false }
212
+ return true
213
+ })
214
+
215
+ if (!toEmbed.length) {
216
+ return { item_id, embeddings_created: 0, embeddings_skipped: skipped.length, errors }
217
+ }
218
+
219
+
220
+ let vectors: string[] = []
221
+ try {
222
+ vectors = await callEmbeddingApi(toEmbed.map(c => c.text), ctx)
223
+ } catch (e: any) {
224
+ throw new Error(`Embedding API call failed: ${e.message}`)
225
+ }
226
+
227
+ const now = new Date().toISOString()
228
+
229
+
230
+ for (let i = 0; i < toEmbed.length; i++) {
231
+ const { vt, text, chunkIndex } = toEmbed[i]
232
+ const hash = contentHash(text)
233
+ const key = `${vt}:${chunkIndex}`
234
+ const existingRow = existingMap.get(key)
235
+
236
+ const row = {
237
+ account_id: resolvedAccountId,
238
+ model_id: 'text-embedding-3-small',
239
+ document_id: item_id,
240
+ chunk_index: chunkIndex,
241
+ content: text,
242
+ embedding: vectors[i],
243
+ content_hash: hash,
244
+ approved_at: isPublished ? now : null,
245
+ revoked_at: null,
246
+ metadata: {
247
+ vector_type: vt,
248
+ item_type: item.type?.design_schema ? (item.data?.kb_type ? 'kb_article' : 'item') : 'item',
249
+ kb_type: kbType,
250
+ security_level: item.data?.security_level || 'internal',
251
+ source_item_id: item_id,
252
+ },
253
+ }
254
+
255
+ if (existingRow) {
256
+ await adminDb.from('embeddings').update(row).eq('id', existingRow.id)
257
+ } else {
258
+ await adminDb.from('embeddings').insert(row)
259
+ }
260
+ created.push(key)
261
+ }
262
+
263
+ await emitLog(ctx, 'embeddings.generated',
264
+ { type: 'item', id: item_id },
265
+ { after: { vectors_created: created.length, vectors_skipped: skipped.length, is_published: isPublished } }
266
+ )
267
+
268
+ return {
269
+ item_id,
270
+ embeddings_created: created.length,
271
+ embeddings_skipped: skipped.length,
272
+ errors,
273
+ }
274
+ })
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+ // CIL[BEGIN chunk_id="embeddings_moduleheader_1783969696" type="module_header" identity="imports" parent="none"]
289
+ // CIL[DOC-MACRO purpose="Import dependencies for embeddings module"]
290
+ // CIL[DOC-RESPONSIBILITY responsibility="Import crypto, middleware, database, audit, and permissions"]
291
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/migrations/015_simplify_apps_table.sql\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
292
+ // CIL[DOC-CONTRACT inputs="none" outputs="imported_modules" errors="none" side_effects="none"]
293
+ // CIL[DOC-MICRO logic_summary="Import required dependencies for embedding generation and management" notable_decisions="[]"]
294
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
295
+ // CIL[DOC-STABILITY stability="stable"]
296
+ // CIL[DOC-AUDIENCE audience="developer"]
297
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/migrations/015_simplify_apps_table.sql, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, \."]
298
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
299
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
300
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
301
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
302
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
42
303
 
43
304
  import { createHash } from 'crypto'
44
305
  import { createHandler } from './_shared/middleware'
45
306
  import { adminDb } from './_shared/db'
46
307
  import { emitLog } from './_shared/audit'
308
+ import { isSystemAdmin } from './_shared/principal'
47
309
  import { sanitizeRecordData } from './_shared/permissions'
48
-
49
- // ─── UNIVERSAL CONTENT BUILDERS ──────────────────────────────────────────────
50
- // Each builder accepts an item record and returns a plain-text string to embed.
51
- // Custom apps populate item.data with the named fields below; the builder picks
52
- // the first non-empty value from a priority chain.
53
-
310
+ import { callOpenAIEmbeddings, callOpenAIChat } from './_shared/llm'
311
+ // CIL[END chunk_id="embeddings_moduleheader_1783969696"]
312
+ // CIL[BEGIN chunk_id="embeddings_contentbuilders_1783969696" type="function" identity="contentBuilders" parent="none"]
313
+ // CIL[DOC-MACRO purpose="Content extraction utilities for embeddings"]
314
+ // CIL[DOC-RESPONSIBILITY responsibility="Transform item data into embeddable text content"]
315
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"\\\"]"]
316
+ // CIL[DOC-CONTRACT inputs="none" outputs="text_content" errors="none" side_effects="none"]
317
+ // CIL[DOC-MICRO logic_summary="Extract and format various content types from knowledge base items" notable_decisions="[]"]
318
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_logic"]
319
+ // CIL[DOC-STABILITY stability="stable"]
320
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, \."]
321
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
322
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
323
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
324
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
325
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
54
326
  function stripHtml(html: string): string {
55
327
  return html
56
328
  .replace(/<[^>]+>/g, ' ')
@@ -142,56 +414,39 @@ function buildErrorSignalContent(item: any): string {
142
414
  if (Array.isArray(sigs) && sigs.length) return sigs.join('\n')
143
415
  const tags = item.data?.ca_analysis_tags
144
416
  if (Array.isArray(tags) && tags.length) return tags.join('\n')
145
- // Extract code-like patterns from description
417
+
146
418
  const desc = item.description || ''
147
419
  const codeBlocks = desc.match(/```[\s\S]*?```/g) || []
148
420
  return codeBlocks.map((b: string) => b.replace(/```\w*\n?/g, '')).join('\n').trim()
149
421
  }
150
422
 
151
423
  function buildSummaryContentSync(item: any): string {
152
- // Sync fallback — AI-generated summary handled asynchronously in generateSummary()
424
+
153
425
  const desc = stripHtml(item.description || '')
154
426
  return `${item.title || ''}: ${desc.slice(0, 300)}`
155
427
  }
156
428
 
157
- async function generateAiSummary(item: any): Promise<string> {
158
- const apiKey = process.env.OPENAI_API_KEY || process.env.LLM_API_KEY
159
- const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
160
- if (!apiKey) return buildSummaryContentSync(item)
429
+ async function generateAiSummary(item: any, ctx?: any): Promise<string> {
161
430
  const plain = stripHtml(item.description || '').slice(0, 3000)
162
431
  try {
163
- const res = await fetch(`${baseUrl}/chat/completions`, {
164
- method: 'POST',
165
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
166
- body: JSON.stringify({
167
- model: process.env.LLM_DEFAULT_MODEL || 'gpt-4o-mini',
168
- temperature: 0.2,
169
- max_tokens: 120,
170
- messages: [
171
- { role: 'system', content: 'Write one concise paragraph (max 100 words) summarising this knowledge base article for search indexing. Output only the paragraph.' },
172
- { role: 'user', content: `Title: ${item.title}\n\n${plain}` },
173
- ],
174
- }),
175
- })
176
- if (!res.ok) return buildSummaryContentSync(item)
177
- const j: any = await res.json()
432
+ const j = await callOpenAIChat(
433
+ ctx,
434
+ [
435
+ { role: 'system', content: 'Write one concise paragraph (max 100 words) summarising this knowledge base article for search indexing. Output only the paragraph.' },
436
+ { role: 'user', content: `Title: ${item.title}\n\n${plain}` },
437
+ ],
438
+ process.env.LLM_DEFAULT_MODEL || 'gpt-4o-mini',
439
+ { temperature: 0.2, max_tokens: 120 }
440
+ )
178
441
  return j.choices?.[0]?.message?.content?.trim() || buildSummaryContentSync(item)
179
442
  } catch {
180
443
  return buildSummaryContentSync(item)
181
444
  }
182
445
  }
183
446
 
184
- async function callEmbeddingApi(inputs: string[]): Promise<string[]> {
185
- const apiKey = process.env.OPENAI_API_KEY || process.env.LLM_API_KEY
186
- const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
187
- if (!apiKey || !inputs.length) return inputs.map(() => '')
188
- const res = await fetch(`${baseUrl}/embeddings`, {
189
- method: 'POST',
190
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
191
- body: JSON.stringify({ model: 'text-embedding-3-small', input: inputs }),
192
- })
193
- if (!res.ok) throw new Error(`Embedding API error ${res.status}`)
194
- const j: any = await res.json()
447
+ async function callEmbeddingApi(inputs: string[], ctx?: any): Promise<string[]> {
448
+ if (!inputs.length) return inputs.map(() => '')
449
+ const j = await callOpenAIEmbeddings(ctx, inputs, 'text-embedding-3-small')
195
450
  const sorted = (j.data || []).sort((a: any, b: any) => a.index - b.index)
196
451
  return sorted.map((d: any) => `[${d.embedding.join(',')}]`)
197
452
  }
@@ -200,7 +455,7 @@ function contentHash(text: string): string {
200
455
  return createHash('sha256').update(text).digest('hex').slice(0, 16)
201
456
  }
202
457
 
203
- // Resolve platform account ID (spine-system) — memoised per process
458
+
204
459
  let _platformAccountId: string | null = null
205
460
  async function getPlatformAccountId(): Promise<string | null> {
206
461
  if (_platformAccountId) return _platformAccountId
@@ -209,10 +464,24 @@ async function getPlatformAccountId(): Promise<string | null> {
209
464
  return _platformAccountId
210
465
  }
211
466
 
212
- // ─── VECTOR TYPE → CONTENT BUILDER MAP ───────────────────────────────────────
213
- // Maps a vector_type string to a function that produces embeddable text from
214
- // an item record. `summary` is handled separately due to async LLM call.
215
467
 
468
+
469
+
470
+ // CIL[END chunk_id="embeddings_contentbuilders_1783969696"]
471
+ // CIL[BEGIN chunk_id="embeddings_contentbuildersmap_1783969696" type="function" identity="contentBuildersMap" parent="none"]
472
+ // CIL[DOC-MACRO purpose="Content builder registry mapping"]
473
+ // CIL[DOC-RESPONSIBILITY responsibility="Map content types to their extraction functions"]
474
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"\\\"]"]
475
+ // CIL[DOC-CONTRACT inputs="none" outputs="builder_function" errors="none" side_effects="none"]
476
+ // CIL[DOC-MICRO logic_summary="Define mapping between vector types and content extraction functions" notable_decisions="[]"]
477
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
478
+ // CIL[DOC-STABILITY stability="stable"]
479
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, \."]
480
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
481
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
482
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
483
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
484
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
216
485
  type ContentBuilder = (item: any, meta?: any) => string
217
486
 
218
487
  const CONTENT_BUILDERS: Record<string, ContentBuilder> = {
@@ -224,28 +493,23 @@ const CONTENT_BUILDERS: Record<string, ContentBuilder> = {
224
493
  outcome: buildOutcomeContent,
225
494
  context: buildContextContent,
226
495
  error_signal: buildErrorSignalContent,
227
- // summary handled separately
496
+
228
497
  }
229
-
230
- // ─── GENERATE HANDLER ────────────────────────────────────────────────────────
231
-
232
- // ─── HANDLERS ─────────────────────────────────────────────────────────────────
233
-
234
- // ─── CHUNK_START: EMBEDDINGS_LIST ──────────────────────────────────────────────
235
- /**
236
- * @chunk-id EMBEDDINGS_LIST_1_0_0
237
- * @version 1.0.0
238
- * @hash 929d4ac3030c10ac7a31bef9cad565900a5cd11af2e130ce3e4cfc28bc65d5ab
239
- * @macro Embeddings List Handler
240
- * @micro Lists embeddings with filtering, pagination, and sanitization
241
- * @inputs ctx: CoreContext — Request context with principal and database
242
- * @inputs _body: any — Request body (unused for GET)
243
- * @outputs Array of sanitized embedding records
244
- * @depends-on [createHandler, sanitizeRecordData]
245
- * @depended-by [Netlify function routing]
246
- * @side-effects [DB queries, permission sanitization]
247
- * @tags embeddings, list, crud, pagination
248
- */
498
+ // CIL[END chunk_id="embeddings_contentbuildersmap_1783969696"]
499
+ // CIL[BEGIN chunk_id="embeddings_list_1783969696" type="function" identity="list" parent="none"]
500
+ // CIL[DOC-MACRO purpose="List embeddings with filtering"]
501
+ // CIL[DOC-RESPONSIBILITY responsibility="Retrieve and filter embeddings by model and document"]
502
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/db.ts\",\"\\\"]"]
503
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized_embeddings" errors="auth_errors,db_errors" side_effects="none"]
504
+ // CIL[DOC-MICRO logic_summary="Query embeddings with filters, sanitize data, return paginated results" notable_decisions="[]"]
505
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
506
+ // CIL[DOC-STABILITY stability="stable"]
507
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 3. Direct calls: 0. Related files: custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/db.ts, \."]
508
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 3. Total affected: 3."]
509
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
510
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
511
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
512
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
249
513
  export const list = createHandler(async (ctx, _body) => {
250
514
  const { model_id, document_id, limit = 100, offset = 0 } = ctx.query || {}
251
515
 
@@ -253,8 +517,9 @@ export const list = createHandler(async (ctx, _body) => {
253
517
  throw new Error('Account context required')
254
518
  }
255
519
 
256
- // RLS automatically filters to accessible accounts
257
- let query = ctx.db
520
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
521
+
522
+ let query = db
258
523
  .from('embeddings')
259
524
  .select('*')
260
525
  .order('created_at', { ascending: false })
@@ -283,23 +548,21 @@ export const list = createHandler(async (ctx, _body) => {
283
548
 
284
549
  return sanitized
285
550
  })
286
- // ─── CHUNK_END: EMBEDDINGS_LIST ────────────────────────────────────────────────
287
-
288
- // ─── CHUNK_START: EMBEDDINGS_GET ──────────────────────────────────────────────
289
- /**
290
- * @chunk-id EMBEDDINGS_GET_1_0_0
291
- * @version 1.0.0
292
- * @hash 261bfd4dd3107ebfbc0a2ee3d870ac449f543699358a03d8bea27f86ba57b5bc
293
- * @macro Embedding Get Handler
294
- * @micro Returns single embedding record with sanitization
295
- * @inputs ctx: CoreContext — Request context with principal and database
296
- * @inputs _body: any — Request body (unused for GET)
297
- * @outputs Sanitized embedding record
298
- * @depends-on [createHandler, sanitizeRecordData]
299
- * @depended-by [Netlify function routing]
300
- * @side-effects [DB single row query, permission sanitization]
301
- * @tags embeddings, get, crud, single-record
302
- */
551
+ // CIL[END chunk_id="embeddings_list_1783969696"]
552
+ // CIL[BEGIN chunk_id="embeddings_get_1783969696" type="function" identity="get" parent="none"]
553
+ // CIL[DOC-MACRO purpose="Get single embedding by ID"]
554
+ // CIL[DOC-RESPONSIBILITY responsibility="Retrieve individual embedding record"]
555
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
556
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized_embedding" errors="not_found,db_errors" side_effects="none"]
557
+ // CIL[DOC-MICRO logic_summary="Query embedding by ID and sanitize data for response" notable_decisions="[]"]
558
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
559
+ // CIL[DOC-STABILITY stability="stable"]
560
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
561
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
562
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
563
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
564
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
565
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
303
566
  export const get = createHandler(async (ctx, _body) => {
304
567
  const { id } = ctx.query || {}
305
568
 
@@ -307,7 +570,9 @@ export const get = createHandler(async (ctx, _body) => {
307
570
  throw new Error('Embedding ID is required')
308
571
  }
309
572
 
310
- const { data, error: err } = await ctx.db
573
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
574
+
575
+ const { data, error: err } = await db
311
576
  .from('embeddings')
312
577
  .select('*')
313
578
  .eq('id', id)
@@ -317,118 +582,21 @@ export const get = createHandler(async (ctx, _body) => {
317
582
 
318
583
  return await sanitizeRecordData(ctx, data, 'embedding')
319
584
  })
320
- // ─── CHUNK_END: EMBEDDINGS_GET ────────────────────────────────────────────────
321
-
322
- // ─── CHUNK_START: EMBEDDINGS_CREATE ──────────────────────────────────────────────
323
- /**
324
- * @chunk-id EMBEDDINGS_CREATE_1_0_0
325
- * @version 1.0.0
326
- * @hash 26bc1b478138d27fc02ebeede1b0ede362ef43864591bc7353390d5f60ae81db
327
- * @macro Embedding Create Handler
328
- * @micro Creates embedding record with validation and audit logging
329
- * @inputs ctx: CoreContext — Request context with principal and database
330
- * @inputs body: object — Embedding data including model_id, document_id, content
331
- * @outputs Inserted embedding record
332
- * @depends-on [createHandler, emitLog]
333
- * @depended-by [Netlify function routing]
334
- * @side-effects [DB insert, audit logging]
335
- * @tags embeddings, create, crud, audit
336
- */
337
- export const create = createHandler(async (ctx, body) => {
338
- const { model_id, document_id, chunk_index, content, metadata } = body
339
-
340
- if (!model_id || !document_id || !content) {
341
- throw new Error('model_id, document_id, and content are required')
342
- }
343
-
344
- if (!ctx.principal || ctx.principal.id === 'anonymous' || !ctx.accountId) {
345
- throw new Error('User context (person and account) required')
346
- }
347
-
348
- const { data, error: err } = await ctx.db
349
- .from('embeddings')
350
- .insert({
351
- model_id,
352
- document_id,
353
- chunk_index: chunk_index || 0,
354
- content,
355
- metadata: metadata || {},
356
- account_id: ctx.accountId
357
- })
358
- .select()
359
- .single()
360
-
361
- if (err) throw err
362
-
363
- await emitLog(ctx, 'embedding.created',
364
- { type: 'embedding', id: data.id },
365
- { after: { document_id, model_id } }
366
- )
367
-
368
- return data
369
- })
370
- // ─── CHUNK_END: EMBEDDINGS_CREATE ────────────────────────────────────────────────
371
-
372
- // ─── CHUNK_START: EMBEDDINGS_UPDATE ──────────────────────────────────────────────
373
- /**
374
- * @chunk-id EMBEDDINGS_UPDATE_1_0_0
375
- * @version 1.0.0
376
- * @hash e543c87308991159cda8e50f85127884475004bffaedb4508117481375bfaa43
377
- * @macro Embedding Update Handler
378
- * @micro Updates embedding content and/or metadata with audit logging
379
- * @inputs ctx: CoreContext — Request context with principal and database
380
- * @inputs body: object — Embedding updates including id
381
- * @outputs Updated embedding record
382
- * @depends-on [createHandler, emitLog]
383
- * @depended-by [Netlify function routing]
384
- * @side-effects [DB update, audit logging]
385
- * @tags embeddings, update, crud, audit
386
- */
387
- export const update = createHandler(async (ctx, body) => {
388
- const id = body?.id || ctx.query?.id
389
- const { id: _bodyId, content, metadata } = body || {}
390
-
391
- if (!id) {
392
- throw new Error('Embedding ID is required')
393
- }
394
-
395
- const updateFields: Record<string, any> = {}
396
- if (content !== undefined) updateFields.content = content
397
- if (metadata !== undefined) updateFields.metadata = metadata
398
-
399
- const { data, error: err } = await ctx.db
400
- .from('embeddings')
401
- .update(updateFields)
402
- .eq('id', id)
403
- .select()
404
- .single()
405
-
406
- if (err) throw err
407
-
408
- await emitLog(ctx, 'embedding.updated',
409
- { type: 'embedding', id },
410
- { after: { content_updated: !!content } }
411
- )
412
-
413
- return data
414
- })
415
- // ─── CHUNK_END: EMBEDDINGS_UPDATE ────────────────────────────────────────────────
416
-
417
- // ─── CHUNK_START: EMBEDDINGS_GET_STATS ──────────────────────────────────────────────
418
- /**
419
- * @chunk-id EMBEDDINGS_GET_STATS_1_0_0
420
- * @version 1.0.0
421
- * @hash 176a002d14e89cffb0cb993b554c28e2258f66951901be49a4eccbe7855a2d7a
422
- * @macro Embeddings Statistics Handler
423
- * @micro Returns total embedding count for account with RLS scoping
424
- * @inputs ctx: CoreContext — Request context with principal and database
425
- * @inputs _body: any — Request body (unused for GET)
426
- * @outputs {total_embeddings: number} — Total count of embeddings
427
- * @depends-on [createHandler]
428
- * @depended-by [Netlify function routing]
429
- * @side-effects [DB count query]
430
- * @tags embeddings, stats, count, monitoring
431
- */
585
+ // CIL[END chunk_id="embeddings_get_1783969696"]
586
+ // CIL[BEGIN chunk_id="embeddings_getstats_1783969696" type="function" identity="getStats" parent="none"]
587
+ // CIL[DOC-MACRO purpose="Get embedding statistics"]
588
+ // CIL[DOC-RESPONSIBILITY responsibility="Return total count of embeddings"]
589
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
590
+ // CIL[DOC-CONTRACT inputs="none" outputs="embedding_stats" errors="auth_errors,db_errors" side_effects="none"]
591
+ // CIL[DOC-MICRO logic_summary="Count total embeddings in database for account" notable_decisions="[]"]
592
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
593
+ // CIL[DOC-STABILITY stability="stable"]
594
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
595
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
596
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
597
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
598
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
599
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
432
600
  export const getStats = createHandler(async (ctx, _body) => {
433
601
  if (!ctx.accountId) {
434
602
  throw new Error('Account context required')
@@ -442,23 +610,21 @@ export const getStats = createHandler(async (ctx, _body) => {
442
610
 
443
611
  return { total_embeddings: count || 0 }
444
612
  })
445
- // ─── CHUNK_END: EMBEDDINGS_GET_STATS ────────────────────────────────────────────────
446
-
447
- // ─── CHUNK_START: EMBEDDINGS_BATCH_CREATE ──────────────────────────────────────────────
448
- /**
449
- * @chunk-id EMBEDDINGS_BATCH_CREATE_1_0_0
450
- * @version 1.0.0
451
- * @hash 18f603dbb73e044737283100d6e4cc092597a61645905473d6c6ec1ee67a9d7f
452
- * @macro Embeddings Batch Create Handler
453
- * @micro Bulk inserts multiple embeddings with account stamping and audit logging
454
- * @inputs ctx: CoreContext — Request context with principal and database
455
- * @inputs body: object — embeddings_data array of embedding objects
456
- * @outputs Array of inserted embedding records
457
- * @depends-on [createHandler, emitLog]
458
- * @depended-by [Netlify function routing]
459
- * @side-effects [Bulk DB insert, audit logging]
460
- * @tags embeddings, batch-create, bulk, audit
461
- */
613
+ // CIL[END chunk_id="embeddings_getstats_1783969696"]
614
+ // CIL[BEGIN chunk_id="embeddings_batchcreate_1783969696" type="function" identity="batchCreate" parent="none"]
615
+ // CIL[DOC-MACRO purpose="Create multiple embeddings at once"]
616
+ // CIL[DOC-RESPONSIBILITY responsibility="Batch insert embedding records efficiently"]
617
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
618
+ // CIL[DOC-CONTRACT inputs="none" outputs="created_embeddings" errors="validation_errors,db_errors" side_effects="db_write,audit_log"]
619
+ // CIL[DOC-MICRO logic_summary="Bulk insert embeddings with account context, log batch creation" notable_decisions="[]"]
620
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
621
+ // CIL[DOC-STABILITY stability="stable"]
622
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
623
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
624
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
625
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
626
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
627
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
462
628
  export const batchCreate = createHandler(async (ctx, body) => {
463
629
  const { embeddings_data } = body
464
630
 
@@ -489,23 +655,21 @@ export const batchCreate = createHandler(async (ctx, body) => {
489
655
 
490
656
  return data
491
657
  })
492
- // ─── CHUNK_END: EMBEDDINGS_BATCH_CREATE ────────────────────────────────────────────────
493
-
494
- // ─── CHUNK_START: EMBEDDINGS_DELETE_BY_DOCUMENT ──────────────────────────────────────────────
495
- /**
496
- * @chunk-id EMBEDDINGS_DELETE_BY_DOCUMENT_1_0_0
497
- * @version 1.0.0
498
- * @hash 1f74f199f69f563dbc9e72c0f3906412d6315721212261c5a61da4250cb34909
499
- * @macro Embeddings Document Delete Handler
500
- * @micro Deletes all embeddings for a document with audit logging
501
- * @inputs ctx: CoreContext — Request context with principal and database
502
- * @inputs body: object — document_id to delete embeddings for
503
- * @outputs {deleted_count: number} — Number of deleted embeddings
504
- * @depends-on [createHandler, emitLog]
505
- * @depended-by [Netlify function routing]
506
- * @side-effects [DB delete, audit logging]
507
- * @tags embeddings, delete, document, audit
508
- */
658
+ // CIL[END chunk_id="embeddings_batchcreate_1783969696"]
659
+ // CIL[BEGIN chunk_id="embeddings_deletebydocument_1783969696" type="function" identity="deleteByDocument" parent="none"]
660
+ // CIL[DOC-MACRO purpose="Delete all embeddings for a document"]
661
+ // CIL[DOC-RESPONSIBILITY responsibility="Remove embeddings when document is deleted"]
662
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
663
+ // CIL[DOC-CONTRACT inputs="none" outputs="deletion_count" errors="validation_errors,db_errors" side_effects="db_write,audit_log"]
664
+ // CIL[DOC-MICRO logic_summary="Delete embeddings by document ID, log deletion event" notable_decisions="[]"]
665
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
666
+ // CIL[DOC-STABILITY stability="stable"]
667
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
668
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
669
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
670
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
671
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
672
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
509
673
  export const deleteByDocument = createHandler(async (ctx, body) => {
510
674
  const { document_id } = body
511
675
 
@@ -528,23 +692,21 @@ export const deleteByDocument = createHandler(async (ctx, body) => {
528
692
 
529
693
  return { deleted_count: data?.length || 0 }
530
694
  })
531
- // ─── CHUNK_END: EMBEDDINGS_DELETE_BY_DOCUMENT ────────────────────────────────────────────────
532
-
533
- // ─── CHUNK_START: EMBEDDINGS_CLEANUP ──────────────────────────────────────────────
534
- /**
535
- * @chunk-id EMBEDDINGS_CLEANUP_1_0_0
536
- * @version 1.0.0
537
- * @hash b5caa85d93d7eb232eaf7f81c63f7adc4269898397c4cc1d9186ce868a056223
538
- * @macro Embeddings Cleanup Handler
539
- * @micro Deletes old embeddings beyond retention period with audit logging
540
- * @inputs ctx: CoreContext — Request context with principal and database
541
- * @inputs body: object — days_to_keep (default 365)
542
- * @outputs {deleted_count: number} — Number of deleted embeddings
543
- * @depends-on [createHandler, emitLog]
544
- * @depended-by [Netlify function routing]
545
- * @side-effects [DB delete by date, audit logging]
546
- * @tags embeddings, cleanup, retention, audit
547
- */
695
+ // CIL[END chunk_id="embeddings_deletebydocument_1783969696"]
696
+ // CIL[BEGIN chunk_id="embeddings_cleanup_1783969696" type="function" identity="cleanup" parent="none"]
697
+ // CIL[DOC-MACRO purpose="Clean up old embeddings"]
698
+ // CIL[DOC-RESPONSIBILITY responsibility="Remove embeddings older than specified days"]
699
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
700
+ // CIL[DOC-CONTRACT inputs="none" outputs="deletion_count" errors="db_errors" side_effects="db_write,audit_log"]
701
+ // CIL[DOC-MICRO logic_summary="Delete embeddings older than cutoff date, log cleanup" notable_decisions="[]"]
702
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
703
+ // CIL[DOC-STABILITY stability="stable"]
704
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
705
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
706
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
707
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
708
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
709
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
548
710
  export const cleanup = createHandler(async (ctx, body) => {
549
711
  const { days_to_keep } = body
550
712
  const daysToKeep = days_to_keep || 365
@@ -567,23 +729,21 @@ export const cleanup = createHandler(async (ctx, body) => {
567
729
 
568
730
  return { deleted_count: data?.length || 0 }
569
731
  })
570
- // ─── CHUNK_END: EMBEDDINGS_CLEANUP ────────────────────────────────────────────────
571
-
572
- // ─── CHUNK_START: EMBEDDINGS_SEARCH_SIMILAR ──────────────────────────────────────────────
573
- /**
574
- * @chunk-id EMBEDDINGS_SEARCH_SIMILAR_1_0_0
575
- * @version 1.0.0
576
- * @hash e34df64fa1db62cdef551da14b15e6f7183915562ca68763b5ebe28fe889fff7
577
- * @macro Embeddings Vector Search Handler
578
- * @micro Performs vector similarity search using cosine similarity via RPC
579
- * @inputs ctx: CoreContext — Request context with principal and database
580
- * @inputs body: object — query_embedding vector and search parameters
581
- * @outputs {results, count, model_id, threshold} — Search results with metadata
582
- * @depends-on [createHandler]
583
- * @depended-by [Netlify function routing, agent-runner.ts]
584
- * @side-effects [RPC call for vector similarity search]
585
- * @tags embeddings, search, vector, similarity, rpc
586
- */
732
+ // CIL[END chunk_id="embeddings_cleanup_1783969696"]
733
+ // CIL[BEGIN chunk_id="embeddings_searchsimilar_1783969696" type="function" identity="searchSimilar" parent="none"]
734
+ // CIL[DOC-MACRO purpose="Search similar embeddings by vector"]
735
+ // CIL[DOC-RESPONSIBILITY responsibility="Find embeddings similar to query vector"]
736
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
737
+ // CIL[DOC-CONTRACT inputs="none" outputs="similar_results" errors="validation_errors,db_errors" side_effects="none"]
738
+ // CIL[DOC-MICRO logic_summary="Call stored procedure for vector similarity search with threshold" notable_decisions="[]"]
739
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="external_symbols_unknown"]
740
+ // CIL[DOC-STABILITY stability="stable"]
741
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
742
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
743
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
744
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
745
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
746
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
587
747
  export const searchSimilar = createHandler(async (ctx, body) => {
588
748
  const { query_embedding, model_id, limit = 10, threshold = 0.7 } = body || {}
589
749
 
@@ -595,7 +755,7 @@ export const searchSimilar = createHandler(async (ctx, body) => {
595
755
  throw new Error('query_embedding is required for similarity search')
596
756
  }
597
757
 
598
- // Call the RPC function for vector similarity search
758
+
599
759
  const { data, error: err } = await ctx.db.rpc('search_similar_embeddings', {
600
760
  p_account_id: ctx.accountId,
601
761
  p_model_id: model_id || 'text-embedding-ada-002',
@@ -613,23 +773,21 @@ export const searchSimilar = createHandler(async (ctx, body) => {
613
773
  threshold: parseFloat(threshold.toString())
614
774
  }
615
775
  })
616
- // ─── CHUNK_END: EMBEDDINGS_SEARCH_SIMILAR ────────────────────────────────────────────────
617
-
618
- // ─── CHUNK_START: EMBEDDINGS_SEARCH_SEMANTIC ──────────────────────────────────────────────
619
- /**
620
- * @chunk-id EMBEDDINGS_SEARCH_SEMANTIC_1_0_0
621
- * @version 1.0.0
622
- * @hash 26c9cb4da3ddfa47c34afd1c60c2568f760a97d07719a58e9ae2e3d3926e84ce
623
- * @macro Embeddings Text Search Handler
624
- * @micro Performs full-text search on embedding content using Postgres websearch
625
- * @inputs ctx: CoreContext — Request context with principal and database
626
- * @inputs body: object — query string and optional filters
627
- * @outputs {results, count, method, query} — Search results with metadata
628
- * @depends-on [createHandler]
629
- * @depended-by [Netlify function routing]
630
- * @side-effects [DB full-text search query]
631
- * @tags embeddings, search, text-search, fallback
632
- */
776
+ // CIL[END chunk_id="embeddings_searchsimilar_1783969696"]
777
+ // CIL[BEGIN chunk_id="embeddings_searchsemantic_1783969696" type="function" identity="searchSemantic" parent="none"]
778
+ // CIL[DOC-MACRO purpose="Search embeddings by text query"]
779
+ // CIL[DOC-RESPONSIBILITY responsibility="Find embeddings matching text search query"]
780
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
781
+ // CIL[DOC-CONTRACT inputs="none" outputs="search_results" errors="validation_errors,db_errors" side_effects="none"]
782
+ // CIL[DOC-MICRO logic_summary="Use database full-text search on embedding content" notable_decisions="[]"]
783
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
784
+ // CIL[DOC-STABILITY stability="stable"]
785
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
786
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
787
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
788
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
789
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
790
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
633
791
  export const searchSemantic = createHandler(async (ctx, body) => {
634
792
  const { query, model_id, document_ids, limit = 10 } = body || {}
635
793
 
@@ -670,172 +828,21 @@ export const searchSemantic = createHandler(async (ctx, body) => {
670
828
  query
671
829
  }
672
830
  })
673
- // ─── CHUNK_END: EMBEDDINGS_SEARCH_SEMANTIC ────────────────────────────────────────────────
674
-
675
- // ─── GENERATE ────────────────────────────────────────────────────────────────
676
- /**
677
- * Generates embeddings for an item using the universal vector vocabulary.
678
- * Reads design_schema.vector_types from the item's type record to determine
679
- * which vectors to produce. Skips chunks whose content_hash is unchanged.
680
- * Sets approved_at = now() for published items, NULL for drafts.
681
- *
682
- * Body: item_id (required), force_regenerate (bool), account_id_override (uuid)
683
- *
684
- * This is the ONLY sanctioned way for custom code to write to the embeddings
685
- * table. Direct writes from custom functions are a hard-rule violation.
686
- */
687
- export const generate = createHandler(async (ctx, body) => {
688
- const { item_id, force_regenerate = false, account_id_override } = body || {}
689
- if (!item_id) throw new Error('item_id is required')
690
-
691
- // Load item + type
692
- const { data: item, error: itemErr } = await adminDb
693
- .from('items')
694
- .select('*, type:types(design_schema)')
695
- .eq('id', item_id)
696
- .single()
697
-
698
- if (itemErr || !item) throw new Error(`Item ${item_id} not found`)
699
-
700
- const typeDesign = item.type?.design_schema || {}
701
- const kbType = item.data?.kb_type || 'article'
702
-
703
- // Resolve which vector types to generate
704
- // design_schema.vector_types can be a plain array (all kb_types share the same list)
705
- // or a keyed object { postmortem: [...], how_to: [...] } to vary by kb_type sub-type
706
- let vectorTypes: string[] = []
707
- const rawVT = typeDesign.vector_types
708
- if (Array.isArray(rawVT)) {
709
- vectorTypes = rawVT
710
- } else if (rawVT && typeof rawVT === 'object') {
711
- vectorTypes = rawVT[kbType] || rawVT['default'] || ['semantic', 'structure']
712
- } else {
713
- vectorTypes = ['semantic', 'structure']
714
- }
715
-
716
- const embeddingFields: string[] = typeDesign.embedding_fields?.structure || []
717
- const resolvedAccountId = account_id_override || item.account_id || await getPlatformAccountId()
718
- const isPublished = item.status === 'published'
719
-
720
- // Load existing embeddings for hash comparison
721
- const { data: existing } = await adminDb
722
- .from('embeddings')
723
- .select('id, content_hash, chunk_index, metadata')
724
- .eq('document_id', item_id)
725
-
726
- const existingMap = new Map<string, any>()
727
- for (const row of existing || []) {
728
- const vt = row.metadata?.vector_type || ''
729
- const ci = row.chunk_index ?? 0
730
- existingMap.set(`${vt}:${ci}`, row)
731
- }
732
-
733
- // If force_regenerate, delete all current embeddings first
734
- if (force_regenerate) {
735
- await adminDb.from('embeddings').delete().eq('document_id', item_id)
736
- existingMap.clear()
737
- }
738
-
739
- const created: string[] = []
740
- const skipped: string[] = []
741
- const errors: string[] = []
742
-
743
- // Build content for non-summary vectors synchronously
744
- const syncVectors = vectorTypes.filter(vt => vt !== 'summary')
745
- const contents: Array<{ vt: string; text: string; chunkIndex: number }> = []
746
-
747
- for (const vt of syncVectors) {
748
- const builder = CONTENT_BUILDERS[vt]
749
- if (!builder) { errors.push(`Unknown vector type: ${vt}`); continue }
750
- const text = builder(item, { embedding_fields: embeddingFields })
751
- if (!text) { skipped.push(`${vt}:empty`); continue }
752
- contents.push({ vt, text, chunkIndex: 0 })
753
- }
754
-
755
- // Summary vector — async LLM call
756
- if (vectorTypes.includes('summary')) {
757
- const summaryText = await generateAiSummary(item)
758
- if (summaryText) contents.push({ vt: 'summary', text: summaryText, chunkIndex: 0 })
759
- }
760
-
761
- // Filter out unchanged chunks (by content hash)
762
- const toEmbed = contents.filter(({ vt, text, chunkIndex }) => {
763
- const hash = contentHash(text)
764
- const key = `${vt}:${chunkIndex}`
765
- const row = existingMap.get(key)
766
- if (row && row.content_hash === hash) { skipped.push(key); return false }
767
- return true
768
- })
769
-
770
- if (!toEmbed.length) {
771
- return { item_id, embeddings_created: 0, embeddings_skipped: skipped.length, errors }
772
- }
773
-
774
- // Single batched API call for all vectors that need updating
775
- let vectors: string[] = []
776
- try {
777
- vectors = await callEmbeddingApi(toEmbed.map(c => c.text))
778
- } catch (e: any) {
779
- throw new Error(`Embedding API call failed: ${e.message}`)
780
- }
781
-
782
- const now = new Date().toISOString()
783
-
784
- // Upsert each vector
785
- for (let i = 0; i < toEmbed.length; i++) {
786
- const { vt, text, chunkIndex } = toEmbed[i]
787
- const hash = contentHash(text)
788
- const key = `${vt}:${chunkIndex}`
789
- const existingRow = existingMap.get(key)
790
-
791
- const row = {
792
- account_id: resolvedAccountId,
793
- model_id: 'text-embedding-3-small',
794
- document_id: item_id,
795
- chunk_index: chunkIndex,
796
- content: text,
797
- embedding: vectors[i],
798
- content_hash: hash,
799
- approved_at: isPublished ? now : null,
800
- revoked_at: null,
801
- metadata: {
802
- vector_type: vt,
803
- item_type: item.type?.design_schema ? (item.data?.kb_type ? 'kb_article' : 'item') : 'item',
804
- kb_type: kbType,
805
- security_level: item.data?.security_level || 'internal',
806
- source_item_id: item_id,
807
- },
808
- }
809
-
810
- if (existingRow) {
811
- await adminDb.from('embeddings').update(row).eq('id', existingRow.id)
812
- } else {
813
- await adminDb.from('embeddings').insert(row)
814
- }
815
- created.push(key)
816
- }
817
-
818
- await emitLog(ctx, 'embeddings.generated',
819
- { type: 'item', id: item_id },
820
- { after: { vectors_created: created.length, vectors_skipped: skipped.length, is_published: isPublished } }
821
- )
822
-
823
- return {
824
- item_id,
825
- embeddings_created: created.length,
826
- embeddings_skipped: skipped.length,
827
- errors,
828
- }
829
- })
830
-
831
- // ─── REVOKE ───────────────────────────────────────────────────────────────────
832
- /**
833
- * Sets revoked_at = now() on all embeddings for a document_id.
834
- * Embeddings are NOT deleted — the row stays for audit trail.
835
- * match_embeddings will not return them (revoked_at IS NULL is a hard filter).
836
- *
837
- * Body: document_id (required)
838
- */
831
+ // CIL[END chunk_id="embeddings_searchsemantic_1783969696"]
832
+ // CIL[BEGIN chunk_id="embeddings_revoke_1783969696" type="function" identity="revoke" parent="none"]
833
+ // CIL[DOC-MACRO purpose="Revoke embeddings for document"]
834
+ // CIL[DOC-RESPONSIBILITY responsibility="Mark embeddings as revoked for compliance"]
835
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
836
+ // CIL[DOC-CONTRACT inputs="none" outputs="revocation_count" errors="validation_errors,db_errors" side_effects="db_write,audit_log"]
837
+ // CIL[DOC-MICRO logic_summary="Set revoked_at timestamp for document embeddings" notable_decisions="[]"]
838
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
839
+ // CIL[DOC-STABILITY stability="stable"]
840
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
841
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
842
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
843
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
844
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
845
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
839
846
  export const revoke = createHandler(async (ctx, body) => {
840
847
  const { document_id } = body || {}
841
848
  if (!document_id) throw new Error('document_id is required')
@@ -856,15 +863,21 @@ export const revoke = createHandler(async (ctx, body) => {
856
863
 
857
864
  return { document_id, revoked_count: data?.length || 0 }
858
865
  })
859
-
860
- // ─── APPROVE ─────────────────────────────────────────────────────────────────
861
- /**
862
- * Clears revoked_at and refreshes approved_at on all embeddings for a document_id.
863
- * Called when an item re-enters published state (also handled automatically by
864
- * the items_embedding_sync DB trigger, but available as an explicit API action).
865
- *
866
- * Body: document_id (required)
867
- */
866
+ // CIL[END chunk_id="embeddings_revoke_1783969696"]
867
+ // CIL[BEGIN chunk_id="embeddings_approve_1783969696" type="function" identity="approve" parent="none"]
868
+ // CIL[DOC-MACRO purpose="Approve embeddings for document"]
869
+ // CIL[DOC-RESPONSIBILITY responsibility="Mark embeddings as approved for search"]
870
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
871
+ // CIL[DOC-CONTRACT inputs="none" outputs="approval_count" errors="validation_errors,db_errors" side_effects="db_write,audit_log"]
872
+ // CIL[DOC-MICRO logic_summary="Set approved_at timestamp and clear revoked_at" notable_decisions="[]"]
873
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
874
+ // CIL[DOC-STABILITY stability="stable"]
875
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
876
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
877
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
878
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
879
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
880
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
868
881
  export const approve = createHandler(async (ctx, body) => {
869
882
  const { document_id } = body || {}
870
883
  if (!document_id) throw new Error('document_id is required')
@@ -885,24 +898,23 @@ export const approve = createHandler(async (ctx, body) => {
885
898
 
886
899
  return { document_id, approved_count: data?.length || 0 }
887
900
  })
888
-
889
- // ─── MAIN HANDLER ────────────────────────────────────────────────────────────
890
-
891
- // ─── CHUNK_START: EMBEDDINGS_HANDLER ──────────────────────────────────────────────
892
- /**
893
- * @chunk-id EMBEDDINGS_HANDLER_1_0_0
894
- * @version 1.0.0
895
- * @hash 5c4c550bcf413d5975c54a1f1cbfbc4641425d663127fa55fac1616085a43644
896
- * @macro Embeddings Router
897
- * @micro Routes HTTP methods and actions to appropriate handlers
898
- * @inputs ctx: CoreContext — Request context with principal and database
899
- * @inputs body: any — Request body for POST/PATCH operations
900
- * @outputs Varies — Depends on routed handler (list/get/create/update/batch-create/delete-document/cleanup/search-similar/search-semantic/stats)
901
- * @depends-on [createHandler, list, get, create, update, getStats, batchCreate, deleteByDocument, cleanup, searchSimilar, searchSemantic]
902
- * @depended-by [Netlify function routing]
903
- * @side-effects [Delegates to appropriate handler]
904
- * @tags embeddings, router, crud, netlify-function
905
- */
901
+ // CIL[END chunk_id="embeddings_approve_1783969696"]
902
+ // CIL[BEGIN chunk_id="embeddings_handler_1783969696" type="function" identity="handler" parent="none"]
903
+ // CIL[DOC-LEARNING topic="runtime-type:generic/embeddings" content="The embeddings generate action reads item.type.design_schema.vector_types to decide which content builders to run and design_schema.embedding_fields.structure to pick fields for the structure vector. Defaults are [semantic, structure] when vector_types is absent."]
904
+ // CIL[DOC-NOTE type="design_schema_caveat" content="Unknown vector_types entries are logged as errors and skipped. The embeddings function expects the type join to expose design_schema; items created without a type_id will not produce embeddings."]
905
+ // CIL[DOC-MACRO purpose="Route embeddings HTTP methods"]
906
+ // CIL[DOC-RESPONSIBILITY responsibility="Dispatch requests to embedding operations"]
907
+ // CIL[DOC-CONTEXT module="embeddings" layer="backend_functions" related_files="[]"]
908
+ // CIL[DOC-CONTRACT inputs="none" outputs="function_responses" errors="method_errors" side_effects="none"]
909
+ // CIL[DOC-MICRO logic_summary="Route based on action and method to specific embedding functions" notable_decisions="[]"]
910
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
911
+ // CIL[DOC-STABILITY stability="stable"]
912
+ // CIL[DOC-VECTOR type="context" content="Layer: backend_functions. Direct callers: 0. Direct calls: 0. Related files: none."]
913
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
914
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
915
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
916
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
917
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
906
918
  export const handler = createHandler(async (ctx, body) => {
907
919
  const { action } = ctx.query || {}
908
920
  const method = ctx.query?.method || 'GET'
@@ -969,4 +981,4 @@ export const handler = createHandler(async (ctx, body) => {
969
981
 
970
982
  throw new Error('Invalid action or method')
971
983
  })
972
- // ─── CHUNK_END: EMBEDDINGS_HANDLER ────────────────────────────────────────────────
984
+ // CIL[END chunk_id="embeddings_handler_1783969696"]