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.
- package/.framework/cli/commands/agents.ts +35 -37
- package/.framework/cli/commands/auth.ts +35 -34
- package/.framework/cli/commands/create-app.ts +104 -40
- package/.framework/cli/commands/dev.ts +104 -69
- package/.framework/cli/commands/doctor.ts +179 -69
- package/.framework/cli/commands/generate.ts +83 -143
- package/.framework/cli/commands/init.ts +72 -34
- package/.framework/cli/commands/install-app.ts +260 -125
- package/.framework/cli/commands/items.ts +33 -44
- package/.framework/cli/commands/migrate.ts +122 -40
- package/.framework/cli/commands/migrations.ts +33 -40
- package/.framework/cli/commands/pipelines.ts +31 -39
- package/.framework/cli/commands/status.ts +47 -23
- package/.framework/cli/commands/system.ts +47 -37
- package/.framework/cli/commands/test.ts +112 -68
- package/.framework/cli/commands/validate-app.ts +215 -121
- package/.framework/cli/context.ts +112 -130
- package/.framework/cli/env-loader.ts +47 -18
- package/.framework/cli/index.ts +81 -42
- package/.framework/docs/runtime-types.md +1024 -0
- package/.framework/functions/_shared/agent-runner.ts +1296 -961
- package/.framework/functions/_shared/app-manifest.ts +82 -68
- package/.framework/functions/_shared/audit.ts +135 -83
- package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
- package/.framework/functions/_shared/db.ts +80 -126
- package/.framework/functions/_shared/hash-api-key.ts +63 -0
- package/.framework/functions/_shared/index.ts +224 -335
- package/.framework/functions/_shared/kb.ts +319 -0
- package/.framework/functions/_shared/llm.ts +92 -0
- package/.framework/functions/_shared/logging.ts +288 -0
- package/.framework/functions/_shared/middleware.ts +329 -365
- package/.framework/functions/_shared/outcomes.ts +36 -0
- package/.framework/functions/_shared/permissions.ts +359 -643
- package/.framework/functions/_shared/pipeline-runner.ts +243 -316
- package/.framework/functions/_shared/principal.ts +376 -468
- package/.framework/functions/_shared/resolve-credentials.ts +102 -0
- package/.framework/functions/_shared/resolve-ids.ts +188 -16
- package/.framework/functions/_shared/retry-deliveries.ts +104 -0
- package/.framework/functions/_shared/schema-utils.ts +611 -373
- package/.framework/functions/_shared/testing.ts +207 -137
- package/.framework/functions/_shared/tracing.ts +276 -0
- package/.framework/functions/_shared/trigger-engine.ts +159 -211
- package/.framework/functions/_shared/webhook-registration.ts +95 -81
- package/.framework/functions/_shared/webhook-registry.ts +153 -75
- package/.framework/functions/account-nodes.ts +31 -53
- package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
- package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
- package/.framework/functions/adapters/openai-outbound.ts +40 -0
- package/.framework/functions/admin-data.ts +685 -537
- package/.framework/functions/admin-funnel.ts +31 -20
- package/.framework/functions/admin-install-funnel.ts +47 -28
- package/.framework/functions/admin-signals.ts +47 -29
- package/.framework/functions/ai-agents.ts +122 -147
- package/.framework/functions/ai-interactions.ts +180 -90
- package/.framework/functions/api-keys.ts +250 -192
- package/.framework/functions/apps.ts +203 -197
- package/.framework/functions/auth.ts +98 -91
- package/.framework/functions/cil-agent-ingest.ts +773 -0
- package/.framework/functions/cil-agent.ts +592 -0
- package/.framework/functions/cil-callmap.ts +1227 -0
- package/.framework/functions/cil-config.ts +583 -0
- package/.framework/functions/cil-context.ts +445 -0
- package/.framework/functions/cil-doc.ts +1752 -0
- package/.framework/functions/cil-embeddings.ts +1202 -0
- package/.framework/functions/cil-graph.ts +585 -0
- package/.framework/functions/cil-ingest.ts +2416 -0
- package/.framework/functions/cil-pipeline.ts +318 -0
- package/.framework/functions/cil-project.ts +499 -0
- package/.framework/functions/cil-validate.ts +469 -0
- package/.framework/functions/debug-auth.ts +73 -53
- package/.framework/functions/deliveries.ts +117 -0
- package/.framework/functions/embeddings.ts +528 -516
- package/.framework/functions/eval-harness.ts +158 -106
- package/.framework/functions/integration-outbound.ts +311 -0
- package/.framework/functions/integration-routes.ts +240 -466
- package/.framework/functions/integrations.ts +132 -140
- package/.framework/functions/invites.ts +401 -201
- package/.framework/functions/item-progress.ts +113 -53
- package/.framework/functions/kb.ts +28 -0
- package/.framework/functions/logs.ts +239 -222
- package/.framework/functions/observability.ts +308 -259
- package/.framework/functions/pipeline-executions.ts +179 -235
- package/.framework/functions/pipelines.ts +163 -205
- package/.framework/functions/prompt-configs.ts +109 -156
- package/.framework/functions/roles.ts +97 -147
- package/.framework/functions/system-cron.ts +132 -218
- package/.framework/functions/system.ts +112 -52
- package/.framework/functions/tests.ts +32 -28
- package/.framework/functions/timers.ts +162 -210
- package/.framework/functions/triggers.ts +282 -346
- package/.framework/functions/types.ts +283 -400
- package/.framework/migrations/001_core_schema.sql +630 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
- package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
- package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
- package/.framework/migrations/005_core_observability.sql +598 -0
- package/.framework/migrations/006_security_hardening_rls.sql +97 -0
- package/.framework/migrations/007_kb_article_list.sql +237 -0
- package/.framework/migrations/008_items_target.sql +8 -0
- package/.framework/migrations/009_roles_slug_unique.sql +11 -0
- package/.framework/public/spine.config.json +91 -0
- package/.framework/scripts/app-install-cli.ts +112 -98
- package/.framework/src/App.tsx +67 -20
- package/.framework/src/apps/admin/index.tsx +97 -27
- package/.framework/src/components/AppWrapper.tsx +50 -11
- package/.framework/src/components/CustomAppLoader.tsx +131 -22
- package/.framework/src/components/admin/AdminListPage.tsx +75 -8
- package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
- package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
- package/.framework/src/components/admin/PageInstructions.tsx +49 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
- package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
- package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
- package/.framework/src/components/app-sidebar.tsx +50 -3
- package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
- package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
- package/.framework/src/components/kb/KBArticleList.tsx +557 -0
- package/.framework/src/components/layout/AppShell.tsx +54 -16
- package/.framework/src/components/layout/Header.tsx +50 -38
- package/.framework/src/components/layout/Layout.tsx +48 -32
- package/.framework/src/components/layout/Sidebar.tsx +167 -16
- package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
- package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
- package/.framework/src/components/runtime/DataFilters.tsx +78 -1
- package/.framework/src/components/runtime/DataHeader.tsx +32 -29
- package/.framework/src/components/runtime/DataListPage.tsx +161 -2
- package/.framework/src/components/runtime/DataStats.tsx +48 -1
- package/.framework/src/components/runtime/DataTable.tsx +153 -1
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
- package/.framework/src/components/runtime/index.ts +16 -9
- package/.framework/src/components/search-form.tsx +33 -1
- package/.framework/src/components/shared/AgentView.tsx +48 -51
- package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
- package/.framework/src/components/shared/InviteManager.tsx +113 -52
- package/.framework/src/components/shared/MemberList.tsx +182 -0
- package/.framework/src/components/shared/SchemaFields.tsx +96 -78
- package/.framework/src/components/ui/DataTable.tsx +86 -56
- package/.framework/src/components/ui/Form.tsx +188 -109
- package/.framework/src/components/ui/ItemCard.tsx +86 -58
- package/.framework/src/components/ui/ItemListView.tsx +88 -61
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
- package/.framework/src/components/ui/Modal.tsx +81 -4
- package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
- package/.framework/src/components/ui/accordion.tsx +97 -5
- package/.framework/src/components/ui/alert-dialog.tsx +225 -13
- package/.framework/src/components/ui/alert.tsx +113 -6
- package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
- package/.framework/src/components/ui/avatar.tsx +129 -7
- package/.framework/src/components/ui/badge.tsx +65 -3
- package/.framework/src/components/ui/breadcrumb.tsx +145 -8
- package/.framework/src/components/ui/button-group.tsx +81 -4
- package/.framework/src/components/ui/button.tsx +49 -2
- package/.framework/src/components/ui/calendar.tsx +65 -3
- package/.framework/src/components/ui/card.tsx +145 -8
- package/.framework/src/components/ui/carousel.tsx +129 -7
- package/.framework/src/components/ui/chart.tsx +161 -10
- package/.framework/src/components/ui/checkbox.tsx +49 -2
- package/.framework/src/components/ui/collapsible.tsx +81 -5
- package/.framework/src/components/ui/combobox.tsx +305 -20
- package/.framework/src/components/ui/command.tsx +177 -11
- package/.framework/src/components/ui/context-menu.tsx +273 -17
- package/.framework/src/components/ui/dialog.tsx +209 -27
- package/.framework/src/components/ui/direction.tsx +64 -4
- package/.framework/src/components/ui/drawer.tsx +193 -12
- package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
- package/.framework/src/components/ui/empty.tsx +145 -9
- package/.framework/src/components/ui/field.tsx +209 -14
- package/.framework/src/components/ui/hover-card.tsx +81 -4
- package/.framework/src/components/ui/input-group.tsx +162 -10
- package/.framework/src/components/ui/input-otp.tsx +98 -5
- package/.framework/src/components/ui/input.tsx +49 -2
- package/.framework/src/components/ui/item.tsx +225 -13
- package/.framework/src/components/ui/kbd.tsx +65 -3
- package/.framework/src/components/ui/label.tsx +49 -2
- package/.framework/src/components/ui/menubar.tsx +289 -17
- package/.framework/src/components/ui/native-select.tsx +97 -5
- package/.framework/src/components/ui/navigation-menu.tsx +177 -10
- package/.framework/src/components/ui/pagination.tsx +161 -9
- package/.framework/src/components/ui/popover.tsx +145 -8
- package/.framework/src/components/ui/progress.tsx +49 -2
- package/.framework/src/components/ui/radio-group.tsx +65 -3
- package/.framework/src/components/ui/resizable.tsx +82 -4
- package/.framework/src/components/ui/scroll-area.tsx +65 -3
- package/.framework/src/components/ui/select.tsx +193 -11
- package/.framework/src/components/ui/separator.tsx +49 -2
- package/.framework/src/components/ui/sheet.tsx +193 -11
- package/.framework/src/components/ui/sidebar.tsx +481 -38
- package/.framework/src/components/ui/skeleton.tsx +49 -2
- package/.framework/src/components/ui/slider.tsx +49 -2
- package/.framework/src/components/ui/sonner.tsx +49 -2
- package/.framework/src/components/ui/spinner.tsx +49 -2
- package/.framework/src/components/ui/switch.tsx +49 -2
- package/.framework/src/components/ui/table-primitives.tsx +160 -33
- package/.framework/src/components/ui/table.tsx +161 -9
- package/.framework/src/components/ui/tabs.tsx +113 -6
- package/.framework/src/components/ui/textarea.tsx +49 -2
- package/.framework/src/components/ui/toggle-group.tsx +81 -4
- package/.framework/src/components/ui/toggle.tsx +65 -3
- package/.framework/src/components/ui/tooltip.tsx +97 -5
- package/.framework/src/contexts/AppContext.tsx +129 -45
- package/.framework/src/contexts/AuthContext.tsx +187 -144
- package/.framework/src/hooks/use-mobile.ts +48 -2
- package/.framework/src/hooks/useApi.ts +208 -159
- package/.framework/src/hooks/useAppPath.ts +32 -23
- package/.framework/src/hooks/useApps.ts +51 -20
- package/.framework/src/hooks/useEntityList.ts +32 -78
- package/.framework/src/hooks/useEntityRecord.ts +35 -96
- package/.framework/src/hooks/useForm.ts +32 -92
- package/.framework/src/hooks/useListSchema.ts +49 -78
- package/.framework/src/hooks/useSchemaRecord.ts +114 -105
- package/.framework/src/lib/api.ts +195 -116
- package/.framework/src/lib/supabase.ts +98 -64
- package/.framework/src/lib/utils.ts +287 -151
- package/.framework/src/main.tsx +49 -3
- package/.framework/src/pages/DashboardPage.tsx +52 -29
- package/.framework/src/pages/NotFoundPage.tsx +32 -10
- package/.framework/src/pages/SetupPage.tsx +32 -12
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
- package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
- package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
- package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
- package/.framework/src/pages/admin/AppsPage.tsx +49 -2
- package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
- package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
- package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
- package/.framework/src/pages/admin/LogsPage.tsx +422 -75
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
- package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
- package/.framework/src/pages/admin/RolesPage.tsx +171 -18
- package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
- package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
- package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
- package/.framework/src/pages/admin/TimersPage.tsx +48 -20
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
- package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
- package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
- package/.framework/src/pages/admin/TypesPage.tsx +48 -20
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
- package/.framework/src/pages/auth/LoginPage.tsx +50 -23
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
- package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
- package/.framework/src/types/auth.ts +128 -71
- package/.framework/src/types/types.ts +373 -157
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
- package/docs/README.md +9 -130
- package/netlify.toml +5 -2
- package/package.json +13 -25
- package/scripts/kb-ingest-docs.ts +203 -0
- package/spine.config.json +65 -4
- package/.framework/migrations/001_schema.sql +0 -425
- package/.framework/migrations/002_rls_and_functions.sql +0 -763
- package/.framework/migrations/004_enhancements.sql +0 -43
- package/.framework/migrations/005_auth_user_trigger.sql +0 -105
- package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
- package/.framework/migrations/016_invites.sql +0 -27
- package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
- package/.framework/migrations/018_apps_required_roles.sql +0 -17
- package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
- package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
- package/.framework/migrations/021_app_domains.sql +0 -112
- package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
- package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
- package/.framework/migrations/024_messages_account_id.sql +0 -9
- package/.framework/migrations/025_retype_threads_messages.sql +0 -240
- package/.framework/migrations/026_fix_rls_policies.sql +0 -47
- package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
- package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
- package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
- package/.framework/migrations/030_messages_updated_by.sql +0 -6
- package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
- package/docs/APPS.md +0 -232
- package/docs/ARCHITECTURE.md +0 -246
- package/docs/INSTALL.md +0 -155
- package/docs/design-schema-spec.md +0 -1837
- package/docs/dev-guides/01-create-an-app.md +0 -183
- package/docs/dev-guides/02-create-a-function.md +0 -178
- package/docs/dev-guides/03-create-a-component.md +0 -178
- package/docs/dev-guides/04-webhook-handlers.md +0 -129
- package/docs/dev-guides/05-testing.md +0 -168
- package/docs/dev-guides/README.md +0 -40
- package/docs/npm-packages-plan.md +0 -288
- package/docs/spine-core--api-reference.md +0 -822
|
@@ -1,56 +1,328 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
186
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
496
|
+
|
|
228
497
|
}
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
//
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|
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
|
-
//
|
|
321
|
-
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
//
|
|
446
|
-
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
//
|
|
493
|
-
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
//
|
|
532
|
-
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
//
|
|
571
|
-
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
617
|
-
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
//
|
|
674
|
-
|
|
675
|
-
//
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
//
|
|
890
|
-
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
//
|
|
984
|
+
// CIL[END chunk_id="embeddings_handler_1783969696"]
|