spine-framework 0.3.145 → 0.4.1
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/.env.example +76 -0
- package/.framework/.spine-manifest.json +19 -0
- package/.framework/cli/commands/agents.ts +61 -33
- package/.framework/cli/commands/auth.ts +60 -28
- package/.framework/cli/commands/create-app.ts +212 -80
- package/.framework/cli/commands/dev.ts +184 -68
- package/.framework/cli/commands/doctor.ts +324 -152
- package/.framework/cli/commands/generate.ts +119 -68
- package/.framework/cli/commands/init.ts +141 -242
- package/.framework/cli/commands/install-app.ts +1336 -314
- package/.framework/cli/commands/items.ts +31 -29
- package/.framework/cli/commands/migrate.ts +299 -156
- package/.framework/cli/commands/migrations.ts +62 -30
- package/.framework/cli/commands/pipelines.ts +31 -29
- package/.framework/cli/commands/status.ts +105 -45
- package/.framework/cli/commands/system.ts +47 -42
- package/.framework/cli/commands/test.ts +161 -102
- package/.framework/cli/commands/upgrade.ts +260 -0
- package/.framework/cli/commands/validate-app.ts +517 -161
- package/.framework/cli/context.ts +117 -85
- package/.framework/cli/env-loader.ts +74 -43
- package/.framework/cli/index.ts +70 -46
- package/.framework/cli/lib/project-files.ts +404 -0
- package/.framework/docs/guides/testing-guide.md +1 -1
- package/.framework/docs/runtime-types.md +72 -72
- package/.framework/functions/_shared/access.ts +84 -0
- package/.framework/functions/_shared/agent-runner.ts +5067 -1026
- package/.framework/functions/_shared/app-manifest.ts +214 -94
- package/.framework/functions/_shared/ast.ts +214 -0
- package/.framework/functions/_shared/audit.ts +91 -55
- package/.framework/functions/_shared/cil.ts +368 -127
- package/.framework/functions/_shared/db.ts +337 -68
- package/.framework/functions/_shared/funnel.ts +228 -0
- package/.framework/functions/_shared/geo.ts +105 -0
- package/.framework/functions/_shared/hash-api-key.ts +94 -17
- package/.framework/functions/_shared/index.ts +22 -224
- package/.framework/functions/_shared/kb.ts +832 -58
- package/.framework/functions/_shared/llm.ts +118 -18
- package/.framework/functions/_shared/logging.ts +317 -19
- package/.framework/functions/_shared/middleware.ts +701 -131
- package/.framework/functions/_shared/notify.ts +70 -0
- package/.framework/functions/_shared/outcomes.ts +34 -19
- package/.framework/functions/_shared/permissions.ts +501 -315
- package/.framework/functions/_shared/pipeline-runner.ts +704 -155
- package/.framework/functions/_shared/principal.ts +436 -263
- package/.framework/functions/_shared/rate-limit.ts +233 -0
- package/.framework/functions/_shared/resolve-credentials.ts +86 -19
- package/.framework/functions/_shared/resolve-ids.ts +203 -139
- package/.framework/functions/_shared/retry-deliveries.ts +51 -16
- package/.framework/functions/_shared/schema-utils.ts +602 -521
- package/.framework/functions/_shared/security.ts +345 -0
- package/.framework/functions/_shared/testing.ts +197 -172
- package/.framework/functions/_shared/tracing.ts +351 -21
- package/.framework/functions/_shared/trigger-engine.ts +425 -136
- package/.framework/functions/_shared/webhook-registration.ts +71 -70
- package/.framework/functions/_shared/webhook-registry.ts +126 -83
- package/.framework/functions/account-nodes.ts +30 -30
- package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
- package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
- package/.framework/functions/adapters/openai-outbound.ts +15 -16
- package/.framework/functions/admin-data.ts +596 -173
- package/.framework/functions/admin-funnel.ts +64 -60
- package/.framework/functions/admin-install-funnel.ts +84 -66
- package/.framework/functions/admin-signals.ts +69 -51
- package/.framework/functions/ai-agents.ts +431 -106
- package/.framework/functions/ai-interactions.ts +186 -112
- package/.framework/functions/api-keys.ts +107 -107
- package/.framework/functions/apps.ts +154 -136
- package/.framework/functions/auth.ts +83 -65
- package/.framework/functions/cil-agent-ingest.ts +189 -232
- package/.framework/functions/cil-agent.ts +433 -177
- package/.framework/functions/cil-callmap.ts +642 -430
- package/.framework/functions/cil-config.ts +139 -135
- package/.framework/functions/cil-context.ts +214 -127
- package/.framework/functions/cil-doc.ts +587 -458
- package/.framework/functions/cil-embeddings.ts +453 -364
- package/.framework/functions/cil-graph.ts +427 -142
- package/.framework/functions/cil-ingest.ts +1418 -1154
- package/.framework/functions/cil-pipeline.ts +276 -21
- package/.framework/functions/cil-project.ts +659 -132
- package/.framework/functions/cil-validate.ts +198 -164
- package/.framework/functions/debug-auth.ts +57 -55
- package/.framework/functions/deliveries.ts +80 -21
- package/.framework/functions/embeddings.ts +1023 -318
- package/.framework/functions/eval-harness.ts +146 -108
- package/.framework/functions/integration-outbound.ts +205 -75
- package/.framework/functions/integration-routes.ts +128 -23
- package/.framework/functions/integrations.ts +125 -100
- package/.framework/functions/invites.ts +1095 -460
- package/.framework/functions/item-progress.ts +158 -102
- package/.framework/functions/kb.ts +34 -10
- package/.framework/functions/logs.ts +133 -133
- package/.framework/functions/observability.ts +152 -122
- package/.framework/functions/pipeline-executions.ts +147 -132
- package/.framework/functions/pipelines.ts +134 -119
- package/.framework/functions/prompt-configs.ts +96 -80
- package/.framework/functions/roles.ts +112 -82
- package/.framework/functions/security.ts +450 -0
- package/.framework/functions/system-cron.ts +117 -80
- package/.framework/functions/system.ts +158 -98
- package/.framework/functions/tests.ts +29 -29
- package/.framework/functions/timers.ts +94 -95
- package/.framework/functions/trigger-scheduler.ts +10 -0
- package/.framework/functions/triggers.ts +179 -149
- package/.framework/functions/types.ts +137 -125
- package/.framework/mcp/index.ts +133 -0
- package/.framework/mcp/load-env.ts +60 -0
- package/.framework/mcp/shared/app-manifest.ts +275 -0
- package/.framework/mcp/shared/db.ts +477 -0
- package/.framework/mcp/shared/kb.ts +118 -0
- package/.framework/mcp/tools/architect.ts +431 -0
- package/.framework/mcp/tools/cil/context.ts +345 -0
- package/.framework/mcp/tools/cil/graph.ts +242 -0
- package/.framework/mcp/tools/cil/ingest.ts +1217 -0
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
- package/.framework/mcp/tools/cil/pipeline.ts +767 -0
- package/.framework/mcp/tools/cil/project.ts +791 -0
- package/.framework/mcp/tools/cil/retrieval.ts +336 -0
- package/.framework/mcp/tools/cil/search-core.ts +454 -0
- package/.framework/mcp/tools/cil/search.ts +97 -0
- package/.framework/mcp/tools/cil/suggest.ts +186 -0
- package/.framework/mcp/tools/cil/validate.ts +1369 -0
- package/.framework/mcp/tools/kb/delete.ts +139 -0
- package/.framework/mcp/tools/kb/index.ts +52 -0
- package/.framework/mcp/tools/kb/ingest.ts +84 -0
- package/.framework/mcp/tools/kb/list-types.ts +102 -0
- package/.framework/mcp/tools/kb/publish.ts +100 -0
- package/.framework/mcp/tools/kb/reindex.ts +86 -0
- package/.framework/mcp/tools/kb/revoke.ts +85 -0
- package/.framework/mcp/tools/kb/search.ts +101 -0
- package/.framework/mcp/tools/kb/upsert.ts +125 -0
- package/.framework/mcp/tools/observability.ts +510 -0
- package/.framework/migrations/001_core_schema.sql +15 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
- package/.framework/migrations/003_core_system_data.sql +16 -1
- package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
- package/.framework/migrations/005_core_observability.sql +17 -1
- package/.framework/migrations/006_security_hardening_rls.sql +15 -0
- package/.framework/migrations/007_kb_article_list.sql +17 -1
- package/.framework/migrations/008_items_target.sql +15 -0
- package/.framework/migrations/009_roles_slug_unique.sql +15 -0
- package/.framework/migrations/010_auth_user_trigger.sql +15 -0
- package/.framework/migrations/011_spine_system_account.sql +16 -0
- package/.framework/migrations/012_service_role_grants.sql +16 -0
- package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
- package/.framework/migrations/014_apps_required_roles.sql +16 -0
- package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
- package/.framework/migrations/016_cil_types.sql +281 -0
- package/.framework/migrations/017_cil_stable_ids.sql +37 -0
- package/.framework/migrations/018_runtime_tables.sql +129 -0
- package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
- package/.framework/migrations/020_schema_reconciliation.sql +67 -0
- package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
- package/.framework/migrations/032_cil_status_migration.sql +73 -0
- package/.framework/migrations/033_cil_versioning.sql +73 -0
- package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
- package/.framework/migrations/035_rate_limits.sql +55 -0
- package/.framework/migrations/036_demo_record_flags.sql +72 -0
- package/.framework/migrations/037_kb_types_canonical.sql +244 -0
- package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
- package/.framework/migrations/042_registration_hierarchy.sql +309 -0
- package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
- package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
- package/.framework/migrations/045_security_critical_rls.sql +139 -0
- package/.framework/migrations/046_security_missing_rls.sql +79 -0
- package/.framework/migrations/047_people_updated_by.sql +27 -0
- package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
- package/.framework/migrations/049_security_blocklist.sql +148 -0
- package/.framework/migrations/050_notifications.sql +49 -0
- package/.framework/migrations/051_message_agent_fields.sql +74 -0
- package/.framework/migrations/052_registration_security.sql +421 -0
- package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
- package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
- package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
- package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
- package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
- package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
- package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
- package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
- package/.framework/migrations/061_fk_dedup.sql +32 -0
- package/.framework/public/spine-logo.svg +10 -3
- package/.framework/scripts/app-install-cli.ts +190 -93
- package/.framework/scripts/assemble-frontend.sh +127 -44
- package/.framework/scripts/assemble-functions.sh +128 -30
- package/.framework/scripts/boundary-check.sh +6 -2
- package/.framework/scripts/build-manifest.sh +24 -68
- package/.framework/scripts/check-core-integrity.sh +15 -76
- package/.framework/scripts/checksum-manifest.mts +343 -0
- package/.framework/scripts/cil-migrate-markers.ts +403 -0
- package/.framework/scripts/fix-cil-markers.mjs +176 -0
- package/.framework/scripts/verify-integrity.sh +21 -57
- package/.framework/scripts/verify-manifest.mts +305 -0
- package/.framework/seeds/types/funnel_signal_use.json +74 -21
- package/.framework/seeds/types/spine_instance.json +73 -21
- package/.framework/src/App.tsx +118 -72
- package/.framework/src/apps/admin/index.tsx +855 -58
- package/.framework/src/components/AppWrapper.tsx +43 -45
- package/.framework/src/components/CustomAppLoader.tsx +160 -116
- package/.framework/src/components/admin/AdminListPage.tsx +46 -44
- package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
- package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
- package/.framework/src/components/admin/PageInstructions.tsx +98 -2
- package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
- package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
- package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
- package/.framework/src/components/app-sidebar.tsx +42 -45
- package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
- package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
- package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
- package/.framework/src/components/kb/KBArticleList.tsx +251 -10
- package/.framework/src/components/layout/AppShell.tsx +51 -45
- package/.framework/src/components/layout/Header.tsx +42 -43
- package/.framework/src/components/layout/Layout.tsx +46 -46
- package/.framework/src/components/layout/Sidebar.tsx +196 -96
- package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
- package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
- package/.framework/src/components/runtime/DataFilters.tsx +49 -74
- package/.framework/src/components/runtime/DataHeader.tsx +53 -32
- package/.framework/src/components/runtime/DataListPage.tsx +51 -155
- package/.framework/src/components/runtime/DataStats.tsx +66 -44
- package/.framework/src/components/runtime/DataTable.tsx +49 -149
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
- package/.framework/src/components/runtime/index.ts +119 -16
- package/.framework/src/components/search-form.tsx +46 -29
- package/.framework/src/components/shared/AgentView.tsx +65 -48
- package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
- package/.framework/src/components/shared/InviteManager.tsx +207 -97
- package/.framework/src/components/shared/MemberList.tsx +97 -0
- package/.framework/src/components/shared/SchemaFields.tsx +107 -86
- package/.framework/src/components/ui/DataTable.tsx +80 -68
- package/.framework/src/components/ui/Form.tsx +157 -146
- package/.framework/src/components/ui/ItemCard.tsx +74 -67
- package/.framework/src/components/ui/ItemListView.tsx +73 -68
- package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
- package/.framework/src/components/ui/Modal.tsx +78 -68
- package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
- package/.framework/src/components/ui/accordion.tsx +85 -86
- package/.framework/src/components/ui/alert-dialog.tsx +197 -184
- package/.framework/src/components/ui/alert.tsx +105 -94
- package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
- package/.framework/src/components/ui/avatar.tsx +122 -107
- package/.framework/src/components/ui/badge.tsx +60 -55
- package/.framework/src/components/ui/breadcrumb.tsx +127 -128
- package/.framework/src/components/ui/button-group.tsx +86 -72
- package/.framework/src/components/ui/button.tsx +71 -50
- package/.framework/src/components/ui/calendar.tsx +72 -57
- package/.framework/src/components/ui/card.tsx +136 -129
- package/.framework/src/components/ui/carousel.tsx +226 -107
- package/.framework/src/components/ui/chart.tsx +252 -147
- package/.framework/src/components/ui/checkbox.tsx +43 -44
- package/.framework/src/components/ui/collapsible.tsx +86 -71
- package/.framework/src/components/ui/combobox.tsx +282 -249
- package/.framework/src/components/ui/command.tsx +158 -146
- package/.framework/src/components/ui/context-menu.tsx +239 -240
- package/.framework/src/components/ui/dialog.tsx +174 -157
- package/.framework/src/components/ui/direction.tsx +72 -56
- package/.framework/src/components/ui/drawer.tsx +168 -169
- package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
- package/.framework/src/components/ui/empty.tsx +131 -120
- package/.framework/src/components/ui/field.tsx +205 -173
- package/.framework/src/components/ui/hover-card.tsx +71 -72
- package/.framework/src/components/ui/input-group.tsx +148 -133
- package/.framework/src/components/ui/input-otp.tsx +99 -86
- package/.framework/src/components/ui/input.tsx +43 -41
- package/.framework/src/components/ui/item.tsx +207 -185
- package/.framework/src/components/ui/kbd.tsx +57 -56
- package/.framework/src/components/ui/label.tsx +45 -41
- package/.framework/src/components/ui/menubar.tsx +268 -236
- package/.framework/src/components/ui/native-select.tsx +85 -86
- package/.framework/src/components/ui/navigation-menu.tsx +166 -156
- package/.framework/src/components/ui/pagination.tsx +131 -141
- package/.framework/src/components/ui/popover.tsx +125 -128
- package/.framework/src/components/ui/progress.tsx +47 -44
- package/.framework/src/components/ui/radio-group.tsx +60 -55
- package/.framework/src/components/ui/resizable.tsx +85 -72
- package/.framework/src/components/ui/scroll-area.tsx +57 -58
- package/.framework/src/components/ui/select.tsx +182 -169
- package/.framework/src/components/ui/separator.tsx +42 -43
- package/.framework/src/components/ui/sheet.tsx +170 -159
- package/.framework/src/components/ui/sidebar.tsx +395 -398
- package/.framework/src/components/ui/skeleton.tsx +42 -43
- package/.framework/src/components/ui/slider.tsx +61 -44
- package/.framework/src/components/ui/sonner.tsx +44 -44
- package/.framework/src/components/ui/spinner.tsx +47 -42
- package/.framework/src/components/ui/switch.tsx +56 -42
- package/.framework/src/components/ui/table-primitives.tsx +272 -132
- package/.framework/src/components/ui/table.tsx +140 -142
- package/.framework/src/components/ui/tabs.tsx +110 -93
- package/.framework/src/components/ui/textarea.tsx +43 -44
- package/.framework/src/components/ui/toggle-group.tsx +87 -71
- package/.framework/src/components/ui/toggle.tsx +56 -56
- package/.framework/src/components/ui/tooltip.tsx +99 -85
- package/.framework/src/contexts/AppContext.tsx +166 -114
- package/.framework/src/contexts/AuthContext.tsx +232 -137
- package/.framework/src/hooks/use-mobile.ts +43 -44
- package/.framework/src/hooks/useApi.ts +185 -172
- package/.framework/src/hooks/useAppPath.ts +31 -29
- package/.framework/src/hooks/useApps.ts +77 -42
- package/.framework/src/hooks/useEntityList.ts +66 -29
- package/.framework/src/hooks/useEntityRecord.ts +76 -32
- package/.framework/src/hooks/useForm.ts +62 -30
- package/.framework/src/hooks/useListSchema.ts +60 -43
- package/.framework/src/hooks/useSchemaRecord.ts +115 -95
- package/.framework/src/lib/api.ts +258 -134
- package/.framework/src/lib/registration.ts +181 -0
- package/.framework/src/lib/supabase.ts +118 -81
- package/.framework/src/lib/utils.ts +266 -266
- package/.framework/src/main.tsx +43 -44
- package/.framework/src/pages/DashboardPage.tsx +44 -43
- package/.framework/src/pages/NoAppPage.tsx +50 -0
- package/.framework/src/pages/NotFoundPage.tsx +32 -29
- package/.framework/src/pages/SetupPage.tsx +34 -33
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
- package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
- package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
- package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
- package/.framework/src/pages/admin/AppsPage.tsx +45 -42
- package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
- package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
- package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
- package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
- package/.framework/src/pages/admin/LogsPage.tsx +87 -137
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
- package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
- package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
- package/.framework/src/pages/admin/RolesPage.tsx +47 -164
- package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
- package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
- package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
- package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
- package/.framework/src/pages/admin/TimersPage.tsx +44 -42
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
- package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
- package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
- package/.framework/src/pages/admin/TypesPage.tsx +45 -42
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
- package/.framework/src/pages/auth/LoginPage.tsx +38 -32
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
- package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
- package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
- package/.framework/src/types/auth.ts +101 -93
- package/.framework/src/types/types.ts +283 -262
- package/.framework/templates/project.json +87 -0
- package/.framework/templates/spine.config.neutral.json +7 -0
- package/.framework/tests/api/api-surface.test.ts +381 -0
- package/.framework/tests/fixtures/seed.ts +427 -0
- package/.framework/tests/fixtures/teardown.ts +228 -0
- package/.framework/tests/reporter.ts +351 -0
- package/.framework/tests/setup.ts +135 -0
- package/.framework/tests/ui/ui-sweep.ts +278 -0
- package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
- package/.framework/tests/unit/agent-runner.test.ts +2389 -0
- package/.framework/tests/unit/ai-interactions.test.ts +81 -0
- package/.framework/tests/unit/app-manifest.test.ts +359 -0
- package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
- package/.framework/tests/unit/cil-determinism.test.ts +85 -0
- package/.framework/tests/unit/core-isolation.test.ts +153 -0
- package/.framework/tests/unit/funnel-signals.test.ts +135 -0
- package/.framework/tests/unit/install-app.test.ts +1759 -0
- package/.framework/tests/unit/invites-security.test.ts +572 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
- package/.framework/tests/unit/kb-search.test.ts +119 -0
- package/.framework/tests/unit/migrate.test.ts +509 -0
- package/.framework/tests/unit/notify.test.ts +136 -0
- package/.framework/tests/unit/permissions.test.ts +269 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
- package/.framework/tests/unit/principal-cache.test.ts +138 -0
- package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
- package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
- package/.framework/tests/unit/schema-utils.test.ts +225 -0
- package/.framework/tests/unit/trigger-engine.test.ts +513 -0
- package/.framework/tests/unit/validate-app.test.ts +527 -0
- package/README.md +34 -3
- package/bin/spine-framework.cjs +1 -0
- package/config/tailwind.config.cjs +7 -0
- package/config/tsconfig.functions.json +42 -0
- package/config/tsconfig.json +37 -9
- package/config/vite.config.ts +31 -2
- package/netlify.toml +31 -3
- package/package.json +46 -29
- package/scripts/assemble-frontend.sh +127 -44
- package/scripts/assemble-functions.sh +127 -29
- package/scripts/boundary-check.sh +6 -2
- package/scripts/build-manifest.sh +24 -68
- package/scripts/checksum-manifest.mts +345 -0
- package/scripts/dev-clean.sh +86 -0
- package/scripts/dev-netlify.sh +49 -0
- package/scripts/netlify-dev-wrapper.sh +2 -0
- package/scripts/verify-install.ts +358 -0
- package/scripts/verify-integrity.sh +21 -57
- package/scripts/verify-manifest.mts +305 -0
- package/templates/project/gitignore +45 -0
- package/{config → templates/project}/package-lock.json +22335 -9366
- package/vitest.config.ts +46 -0
- package/.framework/public/spine.config.json +0 -91
- package/config/deno.lock +0 -108
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/vite.config.d.ts +0 -2
- package/custom/.devin/AGENT.md +0 -357
- package/custom/.devin/skills/install-app/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
- package/custom/.devin/workflows/app-install.yaml +0 -83
- package/custom/README.md +0 -79
- package/docs/README.md +0 -15
- package/scripts/app-install-cli.ts +0 -286
- package/scripts/check-core-integrity.sh +0 -82
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/kb-ingest-docs.ts +0 -203
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/test-login.js +0 -33
- package/spine.config.json +0 -91
|
@@ -1,25 +1,177 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/_shared/kb.ts"]
|
|
2
|
+
// CIL[DOC-MACRO purpose="Shared knowledge base helpers for generating embeddings, chunking text, and searching vector embeddings."]
|
|
3
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provides shared utilities for KB ingestion, revocation, reindexing, and semantic search."]
|
|
4
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
5
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="[]" errors="[]" side_effects="[]"]
|
|
6
|
+
// CIL[DOC-MICRO logic_summary="Module header: imports database, middleware, permissions, and LLM helpers." notable_decisions="[]"]
|
|
7
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
8
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
// CIL[DOC-AUDIENCE audience="internal"]
|
|
10
|
+
|
|
11
|
+
import { createHash } from 'crypto'
|
|
1
12
|
import { adminDb } from './db'
|
|
2
13
|
import type { CoreContext } from './middleware'
|
|
3
14
|
import { sanitizeRecordData } from './permissions'
|
|
4
15
|
import { callOpenAIEmbeddings } from './llm'
|
|
16
|
+
// admin-data is imported lazily — a static edge closes the cycle
|
|
17
|
+
// middleware → security → rate-limit → funnel → trigger-engine →
|
|
18
|
+
// pipeline-runner → kb → admin-data → middleware, leaving createHandler
|
|
19
|
+
// undefined during module evaluation.
|
|
20
|
+
const adminUpdate = async (ctx: any, body: any) =>
|
|
21
|
+
(await import('../admin-data')).update(ctx, body)
|
|
22
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Shared knowledge base helpers for generating embeddings, chunking text, and searching vector embeddings.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
23
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
24
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
25
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
26
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
27
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
28
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:module_header:module_header"]
|
|
5
29
|
|
|
30
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:EMBEDDING_MODEL:constant" type="constant" identity="EMBEDDING_MODEL" parent="~/.framework/functions/_shared/kb.ts"]
|
|
31
|
+
// CIL[DOC-MACRO purpose="OpenAI embedding model identifier used for generating text embeddings."]
|
|
32
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the default embedding model string."]
|
|
33
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
34
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
35
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
36
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="string" errors="[]" side_effects="[]"]
|
|
37
|
+
// CIL[DOC-MICRO logic_summary="Constant string 'text-embedding-3-small'." notable_decisions="[]"]
|
|
6
38
|
const EMBEDDING_MODEL = 'text-embedding-3-small'
|
|
39
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:EMBEDDING_MODEL:constant"]
|
|
40
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_CHUNK_SIZE:constant" type="constant" identity="DEFAULT_CHUNK_SIZE" parent="~/.framework/functions/_shared/kb.ts"]
|
|
41
|
+
// CIL[DOC-VECTOR type="context" content="purpose='OpenAI embedding model identifier used for generating text embeddings.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
42
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
43
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
44
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
45
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
46
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
47
|
+
// CIL[DOC-MACRO purpose="Default maximum number of characters per KB text chunk."]
|
|
48
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the default chunk size for text splitting."]
|
|
49
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
50
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
51
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
52
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="number" errors="[]" side_effects="[]"]
|
|
53
|
+
// CIL[DOC-MICRO logic_summary="Constant number 800." notable_decisions="[]"]
|
|
7
54
|
const DEFAULT_CHUNK_SIZE = 800
|
|
55
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_CHUNK_SIZE:constant"]
|
|
56
|
+
// CIL[DOC-CONTEXT module="unknown" layer="unknown" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
57
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Default maximum number of characters per KB text chunk.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
58
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
59
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
60
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
61
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
62
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
63
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_CHUNK_OVERLAP:constant" type="constant" identity="DEFAULT_CHUNK_OVERLAP" parent="~/.framework/functions/_shared/kb.ts"]
|
|
64
|
+
// CIL[DOC-MACRO purpose="Default overlap in characters between consecutive text chunks."]
|
|
65
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the default chunk overlap for sliding window chunking."]
|
|
66
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
67
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
68
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
69
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="number" errors="[]" side_effects="[]"]
|
|
70
|
+
// CIL[DOC-MICRO logic_summary="Constant number 100." notable_decisions="[]"]
|
|
8
71
|
const DEFAULT_CHUNK_OVERLAP = 100
|
|
72
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_CHUNK_OVERLAP:constant"]
|
|
73
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:SIMILARITY_THRESHOLD:constant" type="constant" identity="SIMILARITY_THRESHOLD" parent="~/.framework/functions/_shared/kb.ts"]
|
|
74
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Default overlap in characters between consecutive text chunks.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
75
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
76
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
77
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
78
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
79
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
80
|
+
// CIL[DOC-MACRO purpose="Default cosine-similarity threshold for vector search matches."]
|
|
81
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the minimum similarity score used to filter search results."]
|
|
82
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
83
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="number" errors="[]" side_effects="[]"]
|
|
84
|
+
// CIL[DOC-MICRO logic_summary="Constant number 0.15." notable_decisions="[]"]
|
|
85
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
86
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
9
87
|
const SIMILARITY_THRESHOLD = 0.15
|
|
88
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Default cosine-similarity threshold for vector search matches.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
89
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
90
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
91
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
92
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
93
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
94
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:SIMILARITY_THRESHOLD:constant"]
|
|
95
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_MATCH_COUNT:constant" type="constant" identity="DEFAULT_MATCH_COUNT" parent="~/.framework/functions/_shared/kb.ts"]
|
|
96
|
+
// CIL[DOC-MACRO purpose="Default number of vector search results to consider before pagination."]
|
|
97
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the default match count used in semantic queries."]
|
|
98
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
99
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="number" errors="[]" side_effects="[]"]
|
|
100
|
+
// CIL[DOC-MICRO logic_summary="Constant number 10." notable_decisions="[]"]
|
|
101
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
102
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
10
103
|
const DEFAULT_MATCH_COUNT = 10
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
104
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Default number of vector search results to consider before pagination.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
105
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
106
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
107
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
108
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
109
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
110
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:DEFAULT_MATCH_COUNT:constant"]
|
|
111
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:resolveSecurityLevel:function" type="function" identity="resolveSecurityLevel" parent="~/.framework/functions/_shared/kb.ts"]
|
|
112
|
+
// CIL[DOC-MACRO purpose="Resolve the vector security level for a KB item type from its declared design_schema."]
|
|
113
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Reads functionality.kb.security_level off a type's design_schema; falls back to 'internal' (deny-by-default) when undeclared."]
|
|
114
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
115
|
+
// CIL[DOC-CONTRACT inputs="designSchema: any — a type's design_schema (or a record's stamped copy)" outputs="string — 'restricted' | 'internal' | 'external'" errors="[]" side_effects="[]"]
|
|
116
|
+
// CIL[DOC-MICRO logic_summary="Returns functionality.kb.security_level when it is a non-empty string; 'internal' otherwise." notable_decisions="Security level is declared per KB type in seed design_schema (functionality.kb.security_level); undeclared types deny by default to 'internal' — no hardcoded slug map."]
|
|
117
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
118
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
119
|
+
export function resolveSecurityLevel(designSchema: any): string {
|
|
120
|
+
const declared = designSchema?.functionality?.kb?.security_level
|
|
121
|
+
return typeof declared === 'string' && declared ? declared : 'internal'
|
|
16
122
|
}
|
|
123
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve the vector security level for a KB item type from its declared design_schema.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
124
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
125
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
126
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
127
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
128
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
129
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:resolveSecurityLevel:function"]
|
|
130
|
+
|
|
17
131
|
|
|
132
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:generateEmbedding:function" type="function" identity="generateEmbedding" parent="~/.framework/functions/_shared/kb.ts"]
|
|
133
|
+
// CIL[DOC-MACRO purpose="Generates an embedding for the provided text using an external API."]
|
|
134
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Transforms textual data into a numerical format suitable for machine learning tasks."]
|
|
135
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
136
|
+
// CIL[DOC-CONTRACT inputs="text: string (required), ctx: CoreContext (optional)" outputs="Promise of number array" errors="[]" side_effects="calls OpenAI API for embeddings"]
|
|
137
|
+
// CIL[DOC-MICRO logic_summary="Calls callOpenAIEmbeddings and returns the first embedding or an empty array." notable_decisions="Returns empty array if embedding not found."]
|
|
138
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
139
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
140
|
+
/**
|
|
141
|
+
* Generates an embedding for the provided text using an external API. This function is essential for transforming textual data into a numerical format suitable for machine learning tasks.
|
|
142
|
+
*
|
|
143
|
+
* @macro Async function that retrieves embeddings from an external service.
|
|
144
|
+
* @micro Calls an external API to obtain the embedding for the given text.
|
|
145
|
+
* @inputSpec
|
|
146
|
+
* text: string (required) — the input text to generate an embedding for.
|
|
147
|
+
* ctx: CoreContext (optional) — optional context for the API call.
|
|
148
|
+
* @outputSpec Promise<number[]> — an array of numbers representing the embedding, or an empty array if not found.
|
|
149
|
+
* @sideEffects calls OpenAI API for embeddings
|
|
150
|
+
* @stability internal
|
|
151
|
+
* @calls callOpenAIEmbeddings
|
|
152
|
+
* @cil-generated
|
|
153
|
+
*/
|
|
18
154
|
async function generateEmbedding(text: string, ctx?: CoreContext): Promise<number[]> {
|
|
19
155
|
const json = await callOpenAIEmbeddings(ctx, [text], EMBEDDING_MODEL)
|
|
20
156
|
return json.data?.[0]?.embedding || []
|
|
21
157
|
}
|
|
158
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Generates an embedding for the provided text using an external API.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
159
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
160
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
161
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
162
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
163
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
164
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:generateEmbedding:function"]
|
|
165
|
+
|
|
22
166
|
|
|
167
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:chunkText:function" type="function" identity="chunkText" parent="~/.framework/functions/_shared/kb.ts"]
|
|
168
|
+
// CIL[DOC-MACRO purpose="Splits text into overlapping chunks of a given size."]
|
|
169
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements sliding-window chunking for long text fields."]
|
|
170
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
171
|
+
// CIL[DOC-CONTRACT inputs="text: string, chunkSize: number, overlap: number" outputs="string array" errors="[]" side_effects="[]"]
|
|
172
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
173
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
174
|
+
// CIL[DOC-MICRO logic_summary="Iterates through text with a stride of chunkSize minus overlap and accumulates slices." notable_decisions="[]"]
|
|
23
175
|
function chunkText(text: string, chunkSize: number, overlap: number): string[] {
|
|
24
176
|
if (!text) return []
|
|
25
177
|
if (text.length <= chunkSize) return [text]
|
|
@@ -35,7 +187,23 @@ function chunkText(text: string, chunkSize: number, overlap: number): string[] {
|
|
|
35
187
|
}
|
|
36
188
|
return chunks
|
|
37
189
|
}
|
|
190
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:chunkText:function"]
|
|
38
191
|
|
|
192
|
+
|
|
193
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Splits text into overlapping chunks of a given size.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
194
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
195
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
196
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
197
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
198
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
199
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:getFieldValue:function" type="function" identity="getFieldValue" parent="~/.framework/functions/_shared/kb.ts"]
|
|
200
|
+
// CIL[DOC-MACRO purpose="Retrieves a flattened string value from a record or its data object."]
|
|
201
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Normalizes record fields into a searchable text representation."]
|
|
202
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
203
|
+
// CIL[DOC-CONTRACT inputs="record: any, field: string" outputs="string" errors="[]" side_effects="[]"]
|
|
204
|
+
// CIL[DOC-MICRO logic_summary="Checks record[field] then record.data[field]; joins arrays with spaces and coerces to string." notable_decisions="[]"]
|
|
205
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
206
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
39
207
|
function getFieldValue(record: any, field: string): string {
|
|
40
208
|
if (record[field] !== undefined && record[field] !== null) {
|
|
41
209
|
const value = record[field]
|
|
@@ -50,14 +218,49 @@ function getFieldValue(record: any, field: string): string {
|
|
|
50
218
|
}
|
|
51
219
|
return ''
|
|
52
220
|
}
|
|
221
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Retrieves a flattened string value from a record or its data object.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
222
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
223
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
224
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
225
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
226
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
227
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:getFieldValue:function"]
|
|
228
|
+
|
|
53
229
|
|
|
230
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
231
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
232
|
+
// CIL[DOC-CONTEXT module="unknown" layer="unknown" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
233
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:getRawFieldValue:function" type="function" identity="getRawFieldValue" parent="~/.framework/functions/_shared/kb.ts"]
|
|
234
|
+
// CIL[DOC-MACRO purpose="Retrieves the raw value of a record field without string coercion."]
|
|
235
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Returns the original value from record or record.data."]
|
|
236
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
237
|
+
// CIL[DOC-CONTRACT inputs="record: any, field: string" outputs="any" errors="[]" side_effects="[]"]
|
|
238
|
+
// CIL[DOC-MICRO logic_summary="Checks record[field] then record.data[field]; returns undefined if not present." notable_decisions="[]"]
|
|
239
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
240
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
54
241
|
function getRawFieldValue(record: any, field: string): any {
|
|
55
242
|
if (record[field] !== undefined && record[field] !== null) return record[field]
|
|
56
243
|
const data = record.data || {}
|
|
57
244
|
if (data[field] !== undefined && data[field] !== null) return data[field]
|
|
58
245
|
return undefined
|
|
59
246
|
}
|
|
247
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Retrieves the raw value of a record field without string coercion.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
248
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
249
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
250
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
251
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
252
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
253
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:getRawFieldValue:function"]
|
|
254
|
+
|
|
60
255
|
|
|
256
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:matchesFilters:function" type="function" identity="matchesFilters" parent="~/.framework/functions/_shared/kb.ts"]
|
|
257
|
+
// CIL[DOC-MACRO purpose="Checks whether a record matches a set of filter values."]
|
|
258
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Filters vector search results by field values."]
|
|
259
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
260
|
+
// CIL[DOC-CONTRACT inputs="record: any, filters: Record<string, any>" outputs="boolean" errors="[]" side_effects="[]"]
|
|
261
|
+
// CIL[DOC-MICRO logic_summary="Compares expected values to actual values; returns false if any expected value is absent." notable_decisions="[]"]
|
|
262
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
263
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
61
264
|
function matchesFilters(record: any, filters: Record<string, any>): boolean {
|
|
62
265
|
if (!filters || Object.keys(filters).length === 0) return true
|
|
63
266
|
|
|
@@ -73,25 +276,358 @@ function matchesFilters(record: any, filters: Record<string, any>): boolean {
|
|
|
73
276
|
|
|
74
277
|
return true
|
|
75
278
|
}
|
|
279
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Checks whether a record matches a set of filter values.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
280
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
281
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
282
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
283
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
284
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
285
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:matchesFilters:function"]
|
|
76
286
|
|
|
287
|
+
|
|
288
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:ensurePlatformAccountId:function" type="function" identity="ensurePlatformAccountId" parent="~/.framework/functions/_shared/kb.ts"]
|
|
289
|
+
// CIL[DOC-MACRO purpose="Resolves the global spine-system account ID from the accounts table."]
|
|
290
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provides the platform account ID for cross-account KB search."]
|
|
291
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
292
|
+
// CIL[DOC-CONTRACT inputs="[]" outputs="string or null" errors="[]" side_effects="[]"]
|
|
293
|
+
// CIL[DOC-MICRO logic_summary="Queries the accounts table for slug spine-system and returns the id or null." notable_decisions="[]"]
|
|
294
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
295
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
77
296
|
async function ensurePlatformAccountId(): Promise<string | null> {
|
|
78
|
-
const { data } = await adminDb
|
|
297
|
+
const { data, error } = await adminDb
|
|
79
298
|
.from('accounts')
|
|
80
299
|
.select('id')
|
|
81
300
|
.eq('slug', 'spine-system')
|
|
82
301
|
.maybeSingle()
|
|
302
|
+
if (error) throw new Error(`platform_account_lookup_failed: ${error.message}`)
|
|
83
303
|
return data?.id ?? null
|
|
84
304
|
}
|
|
305
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:ensurePlatformAccountId:function"]
|
|
306
|
+
|
|
307
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:knowledgeenvelope:interface" type="interface" identity="KnowledgeEnvelope" parent="~/.framework/functions/_shared/kb.ts"]
|
|
308
|
+
// CIL[DOC-MACRO purpose="App-level knowledge access envelope declared in manifest config.knowledge"]
|
|
309
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Declares the maximum set of KB/platform/CIL corpora any agent in an app may reach; deny-by-default when absent"]
|
|
310
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/agent-runner.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/cli/commands/install-app.ts"]
|
|
311
|
+
// CIL[DOC-CONTRACT inputs="apps.config.knowledge (persisted from manifest.config.knowledge)" outputs="KnowledgeEnvelope" errors="none" side_effects="none"]
|
|
312
|
+
// CIL[DOC-MICRO logic_summary="account gates own/descendant corpora (item_types supports trailing-* prefixes, scope self|self_and_descendants); platform.sources allowlists platform-account item types; cil requires explicit project_ids and governs every cil_chunk row" notable_decisions="missing envelope = no access; envelope is the ceiling, per-agent retrieval_policy picks may only narrow it"]
|
|
313
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
314
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
315
|
+
export interface KnowledgeEnvelope {
|
|
316
|
+
/** Own-account corpus. true/object enables; object may carry
|
|
317
|
+
* scope ('self' | 'self_and_descendants') and item_types patterns
|
|
318
|
+
* (exact slug or 'prefix*'). Omitted/false = no account KB access. */
|
|
319
|
+
account?: boolean | { scope?: 'self' | 'self_and_descendants'; item_types?: string[] }
|
|
320
|
+
/** Platform (spine-system) corpus. sources = allowed item_type patterns. */
|
|
321
|
+
platform?: boolean | { sources?: string[] }
|
|
322
|
+
/** CIL corpus. Only granted when an explicit project id list resolves. */
|
|
323
|
+
cil?: boolean | { project_ids?: string[] }
|
|
324
|
+
}
|
|
325
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:knowledgeenvelope:interface"]
|
|
326
|
+
|
|
327
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:knowledgescope:interface" type="interface" identity="KnowledgeScope" parent="~/.framework/functions/_shared/kb.ts"]
|
|
328
|
+
// CIL[DOC-MACRO purpose="Resolved effective knowledge access for one request/run"]
|
|
329
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Carries the envelope∩pick intersection: permitted account ids, per-corpus item type allowlists, CIL project ids, and out-of-envelope violations"]
|
|
330
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/kb.ts"]
|
|
331
|
+
// CIL[DOC-CONTRACT inputs="KnowledgeEnvelope + agent retrieval_policy pick + caller account tree" outputs="KnowledgeScope" errors="violations[] for picks outside the envelope" side_effects="none"]
|
|
332
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
333
|
+
export interface KnowledgeScope {
|
|
334
|
+
allowed: boolean
|
|
335
|
+
deniedReason?: string
|
|
336
|
+
/** All account ids permitted to appear in results (self + descendants + platform when granted). */
|
|
337
|
+
accountIds: string[]
|
|
338
|
+
/** Self (+ descendants under hierarchy scope). */
|
|
339
|
+
selfAccountIds: string[]
|
|
340
|
+
platformAccountId: string | null
|
|
341
|
+
/** null = unrestricted within the corpus; [] = corpus disabled. */
|
|
342
|
+
accountItemTypes: string[] | null
|
|
343
|
+
platformItemTypes: string[] | null
|
|
344
|
+
/** null = CIL denied. Always an explicit list when enabled. */
|
|
345
|
+
cilProjectIds: string[] | null
|
|
346
|
+
/** Safe filter_item_types for match_embeddings, or null when a wildcard/unrestricted corpus exists. */
|
|
347
|
+
rpcItemTypes: string[] | null
|
|
348
|
+
/** Pick entries outside the envelope — callers must fail visibly, not clamp. */
|
|
349
|
+
violations: string[]
|
|
350
|
+
/** Set when the platform account lookup errored — callers may surface an error span while continuing with self scope. */
|
|
351
|
+
platformLookupError?: string
|
|
352
|
+
}
|
|
353
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:knowledgescope:interface"]
|
|
354
|
+
|
|
355
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
356
|
+
|
|
357
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:matchitemtype:function" type="function" identity="matchItemType" parent="~/.framework/functions/_shared/kb.ts"]
|
|
358
|
+
// CIL[DOC-MACRO purpose="Match an item_type against a list of exact slugs or 'prefix*' wildcard patterns"]
|
|
359
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements pattern matching for knowledge envelope item_type lists"]
|
|
360
|
+
// CIL[DOC-CONTRACT inputs="patterns: string[] | null (null = unrestricted, [] = match nothing), itemType" outputs="boolean" errors="none" side_effects="none"]
|
|
361
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
362
|
+
export function matchItemType(patterns: string[] | null | undefined, itemType: string | undefined | null): boolean {
|
|
363
|
+
if (patterns == null) return true
|
|
364
|
+
if (!itemType) return false
|
|
365
|
+
return patterns.some((p) =>
|
|
366
|
+
p === '*' ? true : p.endsWith('*') ? itemType.startsWith(p.slice(0, -1)) : p === itemType
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:matchitemtype:function"]
|
|
370
|
+
|
|
371
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:knowledgerowallowed:function" type="function" identity="knowledgeRowAllowed" parent="~/.framework/functions/_shared/kb.ts"]
|
|
372
|
+
// CIL[DOC-MACRO purpose="Decide whether a single embeddings/items row is inside the resolved knowledge scope"]
|
|
373
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Applies per-corpus rules: account rows match accountItemTypes, platform rows match platformItemTypes, and cil_chunk rows are governed solely by cilProjectIds regardless of corpus"]
|
|
374
|
+
// CIL[DOC-CONTRACT inputs="scope: KnowledgeScope, row: { account_id?, metadata? }" outputs="boolean" errors="none" side_effects="none"]
|
|
375
|
+
// CIL[DOC-MICRO logic_summary="cil_chunk rows require an explicit project grant even in unrestricted account corpora — this prevents the platform codebase leaking through a broad account allowlist" notable_decisions="cil_chunk is always project-gated"]
|
|
376
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
377
|
+
export function knowledgeRowAllowed(scope: KnowledgeScope, row: { account_id?: string | null; metadata?: any }): boolean {
|
|
378
|
+
const meta = row.metadata || {}
|
|
379
|
+
if (meta.item_type === 'cil_chunk') {
|
|
380
|
+
return !!scope.cilProjectIds?.length && !!meta.project_id && scope.cilProjectIds.includes(meta.project_id)
|
|
381
|
+
}
|
|
382
|
+
const isPlatformRow = !!scope.platformAccountId && row.account_id === scope.platformAccountId
|
|
383
|
+
const isOwnRow = scope.selfAccountIds.includes(row.account_id || '')
|
|
384
|
+
if (isPlatformRow && !isOwnRow) return matchItemType(scope.platformItemTypes, meta.item_type)
|
|
385
|
+
if (isOwnRow || isPlatformRow) return matchItemType(scope.accountItemTypes, meta.item_type)
|
|
386
|
+
return false
|
|
387
|
+
}
|
|
388
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:knowledgerowallowed:function"]
|
|
389
|
+
|
|
390
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:resolveknowledgescope:function" type="function" identity="resolveKnowledgeScope" parent="~/.framework/functions/_shared/kb.ts"]
|
|
391
|
+
// CIL[DOC-MACRO purpose="Resolve the effective knowledge scope for a request/agent run from the app's installed envelope and the agent's pick"]
|
|
392
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Server-side enforcement point: loads apps.config.knowledge, intersects it with the per-agent retrieval pick, and returns the account ids + per-corpus allowlists that callers must apply"]
|
|
393
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/agent-runner.ts,.framework/functions/_shared/pipeline-runner.ts"]
|
|
394
|
+
// CIL[DOC-CONTRACT inputs="ctx: { accountId?, principal?, appId? }, appRef: app slug or uuid, pick: { item_types?, project_ids? }" outputs="KnowledgeScope (allowed=false when no envelope or nothing permitted)" errors="never throws for missing config — fails closed" side_effects="DB reads: apps, accounts, get_accessible_accounts"]
|
|
395
|
+
// CIL[DOC-MICRO logic_summary="Missing app/envelope => denied. account corpus adds self (+descendants when scope=self_and_descendants via get_accessible_accounts). platform corpus adds the spine-system account with its own item_type allowlist. cil only resolves when an explicit non-empty project list survives the envelope∩pick intersection. Pick item_types are intersected per corpus and any pick entry outside the envelope is recorded in violations." notable_decisions="deny-by-default; violations are surfaced for visible failure rather than silently clamped"]
|
|
396
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
397
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
398
|
+
export async function resolveKnowledgeScope(
|
|
399
|
+
ctx: { accountId?: string | null; appId?: string | null; principal?: { id?: string } },
|
|
400
|
+
appRef?: string | null,
|
|
401
|
+
pick: { item_types?: string[]; project_ids?: string[] } = {}
|
|
402
|
+
): Promise<KnowledgeScope> {
|
|
403
|
+
const denied = (reason: string): KnowledgeScope => ({
|
|
404
|
+
allowed: false,
|
|
405
|
+
deniedReason: reason,
|
|
406
|
+
accountIds: [],
|
|
407
|
+
selfAccountIds: [],
|
|
408
|
+
platformAccountId: null,
|
|
409
|
+
accountItemTypes: [],
|
|
410
|
+
platformItemTypes: [],
|
|
411
|
+
cilProjectIds: null,
|
|
412
|
+
rpcItemTypes: null,
|
|
413
|
+
violations: [],
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const ref = appRef || ctx.appId || null
|
|
417
|
+
if (!ref) return denied('no_app: cannot resolve the app for knowledge access')
|
|
418
|
+
if (!ctx.accountId) return denied('no_account: no account context for knowledge access')
|
|
419
|
+
|
|
420
|
+
let app: any = null
|
|
421
|
+
try {
|
|
422
|
+
const res = await adminDb
|
|
423
|
+
.from('apps')
|
|
424
|
+
.select('id, slug, config, required_roles')
|
|
425
|
+
.eq(UUID_RE.test(ref) ? 'id' : 'slug', ref)
|
|
426
|
+
.maybeSingle()
|
|
427
|
+
app = res?.data
|
|
428
|
+
} catch (e: any) {
|
|
429
|
+
console.warn('resolveKnowledgeScope: apps lookup failed:', e?.message)
|
|
430
|
+
return denied('envelope_lookup_failed: could not load app config')
|
|
431
|
+
}
|
|
432
|
+
if (!app) return denied(`unknown_app: '${ref}' is not an installed app`)
|
|
433
|
+
|
|
434
|
+
// The caller's app claim (x-app-id header) is client-controllable, so bind
|
|
435
|
+
// it: when the app declares required_roles, the principal must hold at least
|
|
436
|
+
// one. Otherwise a low-privilege caller could claim a staff app's envelope.
|
|
437
|
+
const requiredRoles: string[] = Array.isArray(app.required_roles) ? app.required_roles : []
|
|
438
|
+
const principalRoles: string[] = Array.isArray(ctx.principal?.roles) ? ctx.principal!.roles! : []
|
|
439
|
+
const norm = (r: string) => String(r).replace(/-/g, '_')
|
|
440
|
+
// system_admin is the platform superuser — same bypass convention as
|
|
441
|
+
// useApps/AppWrapper/get_account_apps; a god-mode principal must not be
|
|
442
|
+
// blocked by an app's required_roles envelope binding.
|
|
443
|
+
const isSystemAdmin = principalRoles.some((pr) => norm(pr) === 'system_admin')
|
|
444
|
+
if (requiredRoles.length && principalRoles.length && !isSystemAdmin) {
|
|
445
|
+
const ok = requiredRoles.some((rr) => principalRoles.some((pr) => norm(pr) === norm(rr)))
|
|
446
|
+
if (!ok) return denied(`app_role_mismatch: principal lacks any required role for app '${app.slug}'`)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const envelope = (app.config as any)?.knowledge as KnowledgeEnvelope | undefined
|
|
450
|
+
if (!envelope || typeof envelope !== 'object') {
|
|
451
|
+
return denied(`no_knowledge_envelope: app '${app.slug}' declares no knowledge access`)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const accountEnabled = !!envelope.account
|
|
455
|
+
const accountBlock = envelope.account === true ? {} : (envelope.account || {})
|
|
456
|
+
const platformEnabled = !!envelope.platform
|
|
457
|
+
const platformBlock = envelope.platform === true ? {} : (envelope.platform || {})
|
|
458
|
+
const cilEnabled = !!envelope.cil
|
|
459
|
+
const cilBlock = envelope.cil === true ? {} : (envelope.cil || {})
|
|
460
|
+
|
|
461
|
+
// Account corpus: self, or self + descendants under hierarchy scope.
|
|
462
|
+
let selfAccountIds: string[] = accountEnabled ? [ctx.accountId] : []
|
|
463
|
+
if (accountEnabled && accountBlock.scope === 'self_and_descendants' && ctx.principal?.id) {
|
|
464
|
+
try {
|
|
465
|
+
const { data } = await adminDb.rpc('get_accessible_accounts', { actor_id: ctx.principal.id })
|
|
466
|
+
const ids = (data || []).map((r: any) => r.account_id ?? r).filter(Boolean)
|
|
467
|
+
if (ids.length) selfAccountIds = ids
|
|
468
|
+
} catch (e: any) {
|
|
469
|
+
console.warn('resolveKnowledgeScope: get_accessible_accounts failed, using self only:', e?.message)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// CIL: only granted when an explicit project list survives envelope ∩ pick.
|
|
474
|
+
// `cil: true` without project_ids grants nothing — the pick (thread data)
|
|
475
|
+
// is caller-influenceable, so an id-less envelope must not pass it through.
|
|
476
|
+
const envCilIds = cilEnabled ? (cilBlock.project_ids ?? []) : null
|
|
477
|
+
const pickCilIds = pick.project_ids?.length ? pick.project_ids : null
|
|
478
|
+
const effectiveCilIds = envCilIds?.length && pickCilIds
|
|
479
|
+
? pickCilIds.filter((id) => envCilIds.includes(id))
|
|
480
|
+
: (envCilIds?.length ? envCilIds : [])
|
|
481
|
+
const cilProjectIds = cilEnabled && effectiveCilIds?.length ? effectiveCilIds : null
|
|
482
|
+
const cilDeniedPicks = pickCilIds?.filter((id) => !cilProjectIds?.includes(id)) ?? []
|
|
483
|
+
|
|
484
|
+
let platformAccountId: string | null = null
|
|
485
|
+
let platformLookupError: string | undefined
|
|
486
|
+
if (platformEnabled || cilProjectIds) {
|
|
487
|
+
try {
|
|
488
|
+
platformAccountId = await ensurePlatformAccountId()
|
|
489
|
+
} catch (e: any) {
|
|
490
|
+
platformLookupError = e?.message || String(e)
|
|
491
|
+
console.warn('resolveKnowledgeScope: platform account lookup failed:', platformLookupError)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Envelope item types per corpus (null = unrestricted within that corpus).
|
|
496
|
+
const envAccountTypes = accountEnabled ? (accountBlock.item_types ?? null) : []
|
|
497
|
+
const envPlatformTypes = platformEnabled ? (platformBlock.sources ?? null) : []
|
|
498
|
+
|
|
499
|
+
// Intersect the agent's pick with the envelope per corpus; record pick
|
|
500
|
+
// entries that match nothing the app permits.
|
|
501
|
+
const violations: string[] = []
|
|
502
|
+
const pickTypes = pick.item_types?.length ? pick.item_types : null
|
|
503
|
+
if (pickTypes) {
|
|
504
|
+
for (const t of pickTypes) {
|
|
505
|
+
const ok =
|
|
506
|
+
matchItemType(envAccountTypes, t) ||
|
|
507
|
+
matchItemType(envPlatformTypes, t) ||
|
|
508
|
+
(t === 'cil_chunk' && !!cilProjectIds?.length)
|
|
509
|
+
if (!ok) violations.push(t)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const accountItemTypes = pickTypes ? pickTypes.filter((t) => matchItemType(envAccountTypes, t)) : envAccountTypes
|
|
513
|
+
const platformItemTypes = pickTypes ? pickTypes.filter((t) => matchItemType(envPlatformTypes, t)) : envPlatformTypes
|
|
514
|
+
if (cilDeniedPicks.length) violations.push(`cil projects outside envelope: ${cilDeniedPicks.join(',')}`)
|
|
515
|
+
|
|
516
|
+
const accountIds = [
|
|
517
|
+
...(selfAccountIds),
|
|
518
|
+
...(platformAccountId && !selfAccountIds.includes(platformAccountId) ? [platformAccountId] : []),
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
// Best-effort SQL-level item_type filter: only expressible when every
|
|
522
|
+
// enabled corpus has an explicit, wildcard-free list.
|
|
523
|
+
let rpcItemTypes: string[] | null
|
|
524
|
+
if (violations.length || (!accountEnabled && !platformEnabled && !cilProjectIds)) {
|
|
525
|
+
rpcItemTypes = []
|
|
526
|
+
} else {
|
|
527
|
+
const parts: string[] = []
|
|
528
|
+
let unrestricted = false
|
|
529
|
+
if (accountEnabled) {
|
|
530
|
+
if (accountItemTypes == null) unrestricted = true
|
|
531
|
+
else parts.push(...accountItemTypes)
|
|
532
|
+
}
|
|
533
|
+
if (platformEnabled) {
|
|
534
|
+
if (platformItemTypes == null) unrestricted = true
|
|
535
|
+
else parts.push(...platformItemTypes)
|
|
536
|
+
}
|
|
537
|
+
if (cilProjectIds?.length) parts.push('cil_chunk')
|
|
538
|
+
const uniq = [...new Set(parts)]
|
|
539
|
+
rpcItemTypes = unrestricted || uniq.some((t) => t.includes('*')) ? null : uniq
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const allowed = accountIds.length > 0 &&
|
|
543
|
+
(accountEnabled || platformEnabled || !!cilProjectIds?.length)
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
allowed,
|
|
547
|
+
deniedReason: allowed
|
|
548
|
+
? (violations.length ? `pick_outside_envelope: ${violations.join(',')}` : undefined)
|
|
549
|
+
: 'empty_scope: knowledge envelope permits no corpora',
|
|
550
|
+
accountIds,
|
|
551
|
+
selfAccountIds,
|
|
552
|
+
platformAccountId,
|
|
553
|
+
accountItemTypes,
|
|
554
|
+
platformItemTypes,
|
|
555
|
+
cilProjectIds,
|
|
556
|
+
rpcItemTypes,
|
|
557
|
+
violations,
|
|
558
|
+
platformLookupError,
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:resolveknowledgescope:function"]
|
|
562
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolves the global spine-system account ID from the accounts table.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
563
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
564
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
565
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
566
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
567
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
568
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:ensurePlatformAccountId:function"]
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:clearembeddingsforitem:function" type="function" identity="clearEmbeddingsForItem" parent="~/.framework/functions/_shared/kb.ts"]
|
|
572
|
+
// CIL[DOC-MACRO purpose="Revoke + delete every embeddings row for a document without touching document_leases."]
|
|
573
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Internal embedding-clearing primitive shared by kbRevoke (which additionally clears the lease row) and kbIngest (which must NOT delete the lease it just acquired)."]
|
|
574
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/kb.ts,.framework/migrations/054_kb_ingest_lease.sql"]
|
|
575
|
+
// CIL[DOC-CONTRACT inputs="documentId: string (items.id / embeddings.document_id)" outputs="revoked count" errors="rpc/delete errors propagate" side_effects="revoke_embeddings_for_item RPC + embeddings row delete"]
|
|
576
|
+
// CIL[DOC-MICRO logic_summary="Calls revoke_embeddings_for_item then hard-deletes embeddings rows for document_id." notable_decisions="Extracted so kbIngest can clear stale embeddings inside its lease critical section — routing through kbRevoke would delete the caller's own document_leases row and break mutual exclusion."]
|
|
577
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
578
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
579
|
+
async function clearEmbeddingsForItem(documentId: string): Promise<number> {
|
|
580
|
+
const { data, error } = await adminDb.rpc('revoke_embeddings_for_item', { p_item_id: documentId })
|
|
581
|
+
if (error) throw error
|
|
582
|
+
|
|
583
|
+
const { error: deleteError } = await adminDb
|
|
584
|
+
.from('embeddings')
|
|
585
|
+
.delete()
|
|
586
|
+
.eq('document_id', documentId)
|
|
587
|
+
|
|
588
|
+
if (deleteError) throw deleteError
|
|
589
|
+
return data || 0
|
|
590
|
+
}
|
|
591
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:clearembeddingsforitem:function"]
|
|
85
592
|
|
|
86
|
-
|
|
593
|
+
|
|
594
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:kbIngest:function" type="function" identity="kbIngest" parent="~/.framework/functions/_shared/kb.ts"]
|
|
595
|
+
// CIL[DOC-MACRO purpose="Ingests a knowledge base item by generating embeddings from its content and storing them in the database."]
|
|
596
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Processes a published KB item, chunks vector fields, and persists embeddings under a content-hash document lease."]
|
|
597
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts,.framework/migrations/054_kb_ingest_lease.sql"]
|
|
598
|
+
// CIL[DOC-CONTRACT inputs="ctx: CoreContext, body with id: string and optional account_id" outputs="ok: boolean; message: string; count?: number; in_progress?: boolean" errors="id is required; record not found; database errors" side_effects="revokes previous embeddings; inserts rows into embeddings table; acquires/releases a document_leases row; archives+revokes superseded documents"]
|
|
599
|
+
// CIL[DOC-MICRO logic_summary="Fetches the item, validates status, hashes the declared vector-field values, acquires a document lease ('reused' skips unchanged work, 'processing' reports another worker owns it), then inside try/finally clears stale embeddings, batches chunks into one embeddings call, inserts, and runs supersede_keys-driven supersession before releasing the lease token." notable_decisions="Revokes existing embeddings via clearEmbeddingsForItem (not kbRevoke — that would delete the caller's own lease); supersession is driven by functionality.kb.supersede_keys declared data fields instead of a hardcoded type-slug gate; a fenced release (token mismatch) warns without throwing — the work was written but ownership was lost to a superseding acquire."]
|
|
600
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
601
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
602
|
+
// CIL[DOC-NOTE type="contract" content="Lease outcomes: 'reused' returns a live-embeddings count so callers may stamp freshness; 'processing' returns in_progress:true with NO success marker — callers that stamp embedding state must not stamp success or failure for it, the owning worker's completion reconciles the corpus."]
|
|
603
|
+
/**
|
|
604
|
+
* The kbIngest function processes a knowledge base item by generating embeddings from its content and storing them in a database. It ensures the item is published before proceeding with embedding generation.
|
|
605
|
+
*
|
|
606
|
+
* @macro Async function responsible for ingesting knowledge base items and generating embeddings.
|
|
607
|
+
* @micro Retrieves item details from the database, processes its content into chunks, and generates embeddings for each chunk.
|
|
608
|
+
* @inputSpec
|
|
609
|
+
* - ctx: CoreContext (required) — the context containing account information.
|
|
610
|
+
* - body: { id: string } (required) — an object containing the ID of the knowledge base item.
|
|
611
|
+
* @outputSpec Promise<{ ok: boolean; message: string; count?: number; in_progress?: boolean }> — indicates success status, a message, and an optional count of embeddings generated; in_progress marks a lease 'processing' outcome another worker owns.
|
|
612
|
+
* @throws Error — id is required; Error — record not found; Error — error from database operations.
|
|
613
|
+
* @sideEffects Writes embeddings to the database and may revoke previous embeddings; acquires and releases a document_leases row.
|
|
614
|
+
* @stability internal
|
|
615
|
+
* @calls adminDb.from('items'), adminDb.from('types'), adminDb.rpc('acquire_document_lease'), clearEmbeddingsForItem, callOpenAIEmbeddings, adminDb.from('embeddings').insert, adminDb.rpc('release_document_lease')
|
|
616
|
+
* @cil-generated
|
|
617
|
+
*/
|
|
618
|
+
export async function kbIngest(ctx: CoreContext, body: { id: string; account_id?: string }): Promise<{ ok: boolean; message: string; count?: number; in_progress?: boolean }> {
|
|
87
619
|
const { id } = body
|
|
88
620
|
if (!id) throw new Error('id is required')
|
|
89
621
|
|
|
622
|
+
// Multi-tenant support callers ingest articles owned by
|
|
623
|
+
// customer accounts — the caller passes the record's account_id explicitly.
|
|
624
|
+
// Default remains the caller's own account.
|
|
625
|
+
const recordAccountId = body.account_id || ctx.accountId
|
|
90
626
|
const { data: record, error } = await adminDb
|
|
91
627
|
.from('items')
|
|
92
628
|
.select('*')
|
|
93
629
|
.eq('id', id)
|
|
94
|
-
.eq('account_id',
|
|
630
|
+
.eq('account_id', recordAccountId)
|
|
95
631
|
.maybeSingle()
|
|
96
632
|
|
|
97
633
|
if (error) throw error
|
|
@@ -118,37 +654,87 @@ export async function kbIngest(ctx: CoreContext, body: { id: string }): Promise<
|
|
|
118
654
|
|
|
119
655
|
const typeSlug = typeRecord?.slug || ''
|
|
120
656
|
const appId = record.app_id || typeRecord?.app_id || null
|
|
121
|
-
|
|
657
|
+
// Apps declare the corpus security level on their type's
|
|
658
|
+
// design_schema.functionality.kb.security_level; 'internal' is the
|
|
659
|
+
// deny-by-default fallback when undeclared.
|
|
660
|
+
const securityLevel = resolveSecurityLevel(designSchema)
|
|
661
|
+
|
|
662
|
+
// Content-hash lease (document_leases, migration 054): sha256 over the
|
|
663
|
+
// declared vector-field values in order — the same text that feeds the
|
|
664
|
+
// embedder. The lease dedupes unchanged re-embeds and fences concurrent
|
|
665
|
+
// workers (publish trigger + detached ingest both arrive here).
|
|
666
|
+
const contentHash = createHash('sha256')
|
|
667
|
+
.update(vectorFields.map((field: string) => getFieldValue(record, field)).join('\n'))
|
|
668
|
+
.digest('hex')
|
|
669
|
+
|
|
670
|
+
const { data: lease, error: leaseError } = await adminDb.rpc('acquire_document_lease', {
|
|
671
|
+
p_document_id: id,
|
|
672
|
+
p_account_id: record.account_id,
|
|
673
|
+
p_content_hash: contentHash,
|
|
674
|
+
})
|
|
675
|
+
if (leaseError) throw leaseError
|
|
676
|
+
|
|
677
|
+
if (lease?.outcome === 'reused') {
|
|
678
|
+
// Same-hash completed work exists — reuse it. Count live embeddings so
|
|
679
|
+
// callers that stamp freshness get an honest count.
|
|
680
|
+
const { count } = await adminDb
|
|
681
|
+
.from('embeddings')
|
|
682
|
+
.select('id', { count: 'exact', head: true })
|
|
683
|
+
.eq('document_id', id)
|
|
684
|
+
.is('revoked_at', null)
|
|
685
|
+
return { ok: true, message: 'embeddings reused (unchanged content)', count: count ?? 0 }
|
|
686
|
+
}
|
|
122
687
|
|
|
123
|
-
|
|
688
|
+
if (lease?.outcome === 'processing') {
|
|
689
|
+
// A live worker owns this content's embed — nothing to do. The caller
|
|
690
|
+
// must NOT stamp success; in_progress lets stampers keep their pending
|
|
691
|
+
// marker instead of writing a false terminal state.
|
|
692
|
+
return { ok: true, message: 'embedding already in progress', in_progress: true }
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const leaseId: string | null = lease?.lease_id || null
|
|
696
|
+
let succeeded = false
|
|
697
|
+
try {
|
|
698
|
+
// Clear prior embeddings WITHOUT deleting the lease row we just hold —
|
|
699
|
+
// kbRevoke clears document_leases for external callers (so a revoked
|
|
700
|
+
// doc re-ingests cleanly); routing through it here would release our
|
|
701
|
+
// own lease mid-flight and reopen the race the lease exists to close.
|
|
702
|
+
await clearEmbeddingsForItem(id)
|
|
124
703
|
|
|
125
|
-
|
|
126
|
-
|
|
704
|
+
const toEmbed: Array<{ field: string; chunk: string }> = []
|
|
705
|
+
for (const field of vectorFields) {
|
|
706
|
+
const raw = getFieldValue(record, field)
|
|
707
|
+
if (!raw.trim()) continue
|
|
127
708
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!raw.trim()) continue
|
|
709
|
+
const isChunked = field === chunkField && raw.length > chunkSize
|
|
710
|
+
const chunks = isChunked ? chunkText(raw, chunkSize, chunkOverlap) : [raw]
|
|
131
711
|
|
|
132
|
-
|
|
133
|
-
|
|
712
|
+
for (const chunk of chunks) {
|
|
713
|
+
if (chunk.trim()) toEmbed.push({ field, chunk })
|
|
714
|
+
}
|
|
715
|
+
}
|
|
134
716
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
717
|
+
// One batched embedding call per item — a sequential call per chunk makes
|
|
718
|
+
// bulk reindex (N items × ~10 chunks) blow past the function timeout.
|
|
719
|
+
const embedJson = await callOpenAIEmbeddings(ctx, toEmbed.map(t => t.chunk), EMBEDDING_MODEL)
|
|
720
|
+
const vectors: number[][] = (embedJson.data || []).map((d: any) => d.embedding)
|
|
139
721
|
|
|
722
|
+
const embeddingsToInsert: any[] = []
|
|
723
|
+
for (let i = 0; i < toEmbed.length; i++) {
|
|
724
|
+
const embedding = vectors[i]
|
|
725
|
+
if (!embedding?.length) continue
|
|
140
726
|
embeddingsToInsert.push({
|
|
141
727
|
account_id: record.account_id,
|
|
142
728
|
model_id: EMBEDDING_MODEL,
|
|
143
729
|
document_id: id,
|
|
144
|
-
chunk_index:
|
|
145
|
-
content: chunk,
|
|
730
|
+
chunk_index: i,
|
|
731
|
+
content: toEmbed[i].chunk,
|
|
146
732
|
embedding: JSON.stringify(embedding),
|
|
147
733
|
metadata: {
|
|
148
734
|
type_slug: typeSlug,
|
|
149
735
|
item_type: typeSlug,
|
|
150
736
|
app_id: appId,
|
|
151
|
-
vector_type: field,
|
|
737
|
+
vector_type: toEmbed[i].field,
|
|
152
738
|
security_level: securityLevel,
|
|
153
739
|
status: record.status,
|
|
154
740
|
},
|
|
@@ -156,36 +742,138 @@ export async function kbIngest(ctx: CoreContext, body: { id: string }): Promise<
|
|
|
156
742
|
revoked_at: null,
|
|
157
743
|
})
|
|
158
744
|
}
|
|
159
|
-
}
|
|
160
745
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
746
|
+
if (embeddingsToInsert.length === 0) {
|
|
747
|
+
succeeded = true
|
|
748
|
+
return { ok: true, message: 'no content to embed', count: 0 }
|
|
749
|
+
}
|
|
164
750
|
|
|
165
|
-
|
|
166
|
-
|
|
751
|
+
const { error: insertError } = await adminDb.from('embeddings').insert(embeddingsToInsert)
|
|
752
|
+
if (insertError) throw insertError
|
|
753
|
+
|
|
754
|
+
// Supersession: a newly published document with the same logical
|
|
755
|
+
// identity replaces older published versions. Types declare the data
|
|
756
|
+
// fields that identify a logical document via
|
|
757
|
+
// design_schema.functionality.kb.supersede_keys (e.g. ['slug'],
|
|
758
|
+
// ['source_ticket_id']); undeclared types skip supersession entirely.
|
|
759
|
+
const supersedeKeys: string[] = Array.isArray(kbConfig.supersede_keys) ? kbConfig.supersede_keys : []
|
|
760
|
+
const orParts: string[] = []
|
|
761
|
+
for (const key of supersedeKeys) {
|
|
762
|
+
const value = record.data?.[key]
|
|
763
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
764
|
+
orParts.push(`data->>${key}.eq.${value}`)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (orParts.length) {
|
|
768
|
+
const { data: stale } = await adminDb
|
|
769
|
+
.from('items')
|
|
770
|
+
.select('id, status, data')
|
|
771
|
+
.eq('account_id', record.account_id)
|
|
772
|
+
.eq('type_id', record.type_id)
|
|
773
|
+
.eq('status', 'published')
|
|
774
|
+
.neq('id', id)
|
|
775
|
+
.or(orParts.join(','))
|
|
776
|
+
for (const old of stale || []) {
|
|
777
|
+
await adminDb
|
|
778
|
+
.from('items')
|
|
779
|
+
.update({
|
|
780
|
+
status: 'archived',
|
|
781
|
+
data: { ...(old.data || {}), superseded_by: id, superseded_at: new Date().toISOString() },
|
|
782
|
+
updated_at: new Date().toISOString(),
|
|
783
|
+
})
|
|
784
|
+
.eq('id', old.id)
|
|
785
|
+
await kbRevoke(ctx, { id: old.id }).catch((err) =>
|
|
786
|
+
console.warn(`supersession revoke failed for ${old.id}:`, err?.message)
|
|
787
|
+
)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
167
790
|
|
|
168
|
-
|
|
791
|
+
succeeded = true
|
|
792
|
+
return { ok: true, message: 'embeddings generated', count: embeddingsToInsert.length }
|
|
793
|
+
} finally {
|
|
794
|
+
// Token-gated release. A false return means a superseding acquire took
|
|
795
|
+
// ownership mid-flight (new content hash or expired lease) — the work
|
|
796
|
+
// was already written but ownership was lost; warn, do not throw, and
|
|
797
|
+
// let the next run reconcile.
|
|
798
|
+
const { data: released, error: releaseError } = await adminDb.rpc('release_document_lease', {
|
|
799
|
+
p_document_id: id,
|
|
800
|
+
p_lease_id: leaseId,
|
|
801
|
+
p_status: succeeded ? 'completed' : 'failed',
|
|
802
|
+
})
|
|
803
|
+
if (releaseError) {
|
|
804
|
+
console.warn(`release_document_lease failed for ${id}:`, releaseError.message)
|
|
805
|
+
} else if (released === false) {
|
|
806
|
+
console.warn(`document lease fenced for ${id} — a superseding acquire took ownership before release`)
|
|
807
|
+
}
|
|
808
|
+
}
|
|
169
809
|
}
|
|
810
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:kbIngest:function"]
|
|
170
811
|
|
|
812
|
+
|
|
813
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Ingests a knowledge base item by generating embeddings from its content and storing them in the database.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
814
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
815
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
816
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
817
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
818
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
819
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:kbRevoke:function" type="function" identity="kbRevoke" parent="~/.framework/functions/_shared/kb.ts"]
|
|
820
|
+
// CIL[DOC-MACRO purpose="Revokes all embeddings associated with a given knowledge base item."]
|
|
821
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Removes embeddings for an item from the vector store."]
|
|
822
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
823
|
+
// CIL[DOC-CONTRACT inputs="ctx: CoreContext, body with id: string" outputs="ok: boolean; message: string; count?: number" errors="id is required; database errors" side_effects="deletes rows from embeddings table; deletes the document_leases row so a revoked doc re-ingests cleanly"]
|
|
824
|
+
// CIL[DOC-MICRO logic_summary="Clears embeddings via clearEmbeddingsForItem, then deletes the document_leases row (warn-on-error) so a 'completed' lease cannot force 'reused' on a now-empty corpus." notable_decisions="Lease delete is unconditional — an in-flight ingest racing an external revoke is already racy at the status level."]
|
|
825
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
826
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
827
|
+
/**
|
|
828
|
+
* The kbRevoke function revokes embeddings associated with a specific item ID. It ensures that the item ID is provided and performs database operations to delete the embeddings.
|
|
829
|
+
*
|
|
830
|
+
* @inputSpec
|
|
831
|
+
* ctx: CoreContext (required) — the context object for core operations.
|
|
832
|
+
* body: { id: string } (required) — an object containing the ID of the item whose embeddings are to be revoked.
|
|
833
|
+
* @outputSpec Promise<{ ok: boolean; message: string; count?: number }> — returns an object indicating success, a message, and an optional count of revoked embeddings.
|
|
834
|
+
* @throws Error — id is required, Error — error from revoke_embeddings_for_item, Error — deleteError from embeddings deletion
|
|
835
|
+
* @sideEffects DB writes to revoke embeddings, delete from the embeddings table, and delete the document_leases row.
|
|
836
|
+
* @stability internal
|
|
837
|
+
* @calls clearEmbeddingsForItem, adminDb.from('document_leases').delete()
|
|
838
|
+
* @cil-generated
|
|
839
|
+
*/
|
|
171
840
|
export async function kbRevoke(ctx: CoreContext, body: { id: string }): Promise<{ ok: boolean; message: string; count?: number }> {
|
|
172
841
|
const { id } = body
|
|
173
842
|
if (!id) throw new Error('id is required')
|
|
174
843
|
|
|
175
|
-
const
|
|
176
|
-
if (error) throw error
|
|
844
|
+
const count = await clearEmbeddingsForItem(id)
|
|
177
845
|
|
|
178
|
-
|
|
179
|
-
|
|
846
|
+
// Clear any stored lease — a 'completed' lease would otherwise force
|
|
847
|
+
// 'reused' on the next ingest of this now-empty corpus. Warn-only: a
|
|
848
|
+
// lease-delete failure must not fail revocation.
|
|
849
|
+
const { error: leaseDeleteError } = await adminDb
|
|
850
|
+
.from('document_leases')
|
|
180
851
|
.delete()
|
|
181
852
|
.eq('document_id', id)
|
|
853
|
+
if (leaseDeleteError) {
|
|
854
|
+
console.warn(`document_leases delete failed for ${id}:`, leaseDeleteError.message)
|
|
855
|
+
}
|
|
182
856
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return { ok: true, message: 'embeddings revoked', count: data || 0 }
|
|
857
|
+
return { ok: true, message: 'embeddings revoked', count }
|
|
186
858
|
}
|
|
859
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Revokes all embeddings associated with a given knowledge base item.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
860
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
861
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
862
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
863
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
864
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
865
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:kbRevoke:function"]
|
|
866
|
+
|
|
187
867
|
|
|
188
|
-
|
|
868
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:kbReindex:function" type="function" identity="kbReindex" parent="~/.framework/functions/_shared/kb.ts"]
|
|
869
|
+
// CIL[DOC-MACRO purpose="Re-ingests all published items of a given type to refresh their embeddings."]
|
|
870
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Iterates over published items for a type and re-runs kbIngest."]
|
|
871
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
872
|
+
// CIL[DOC-CONTRACT inputs="ctx: CoreContext, body with type_slug: string, app_id?: string" outputs="ok: boolean; count: number" errors="type_slug is required; type not found; database errors" side_effects="[]"]
|
|
873
|
+
// CIL[DOC-MICRO logic_summary="Resolves the type ID, queries published items, and calls kbIngest for each." notable_decisions="[]"]
|
|
874
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
875
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
876
|
+
export async function kbReindex(ctx: CoreContext, body: { type_slug: string; app_id?: string }): Promise<{ ok: boolean; count: number; errors?: Array<{ id: string; error: string }> }> {
|
|
189
877
|
const { type_slug } = body
|
|
190
878
|
if (!type_slug) throw new Error('type_slug is required')
|
|
191
879
|
|
|
@@ -203,16 +891,41 @@ export async function kbReindex(ctx: CoreContext, body: { type_slug: string; app
|
|
|
203
891
|
const { data: records, error } = await query
|
|
204
892
|
if (error) throw error
|
|
205
893
|
|
|
894
|
+
// Bounded parallelism — sequential ingest of a full corpus exceeds the
|
|
895
|
+
// function timeout, and one bad record should not abort the whole reindex.
|
|
896
|
+
const BATCH = 10
|
|
206
897
|
let count = 0
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
898
|
+
const errors: Array<{ id: string; error: string }> = []
|
|
899
|
+
for (let i = 0; i < (records || []).length; i += BATCH) {
|
|
900
|
+
const batch = (records || []).slice(i, i + BATCH)
|
|
901
|
+
const results = await Promise.allSettled(batch.map(r => kbIngest(ctx, { id: r.id })))
|
|
902
|
+
for (let j = 0; j < results.length; j++) {
|
|
903
|
+
const r = results[j]
|
|
904
|
+
if (r.status === 'fulfilled') count++
|
|
905
|
+
else errors.push({ id: batch[j].id, error: String(r.reason?.message || r.reason) })
|
|
906
|
+
}
|
|
210
907
|
}
|
|
211
908
|
|
|
212
|
-
return { ok:
|
|
909
|
+
return { ok: errors.length === 0, count, errors }
|
|
213
910
|
}
|
|
911
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Re-ingests all published items of a given type to refresh their embeddings.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
912
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
913
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
914
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
915
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
916
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
917
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:kbReindex:function"]
|
|
214
918
|
|
|
215
|
-
|
|
919
|
+
|
|
920
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:kbSearch:function" type="function" identity="kbSearch" parent="~/.framework/functions/_shared/kb.ts"]
|
|
921
|
+
// CIL[DOC-MACRO purpose="Performs semantic search over knowledge base embeddings and returns matching published records."]
|
|
922
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Executes vector search, filters by metadata, and returns sanitized results."]
|
|
923
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/kb.ts,.framework/functions/_shared/llm.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
924
|
+
// CIL[DOC-CONTRACT inputs="ctx: CoreContext, body: any" outputs="results array; count: number" errors="query is required; database errors" side_effects="[]"]
|
|
925
|
+
// CIL[DOC-MICRO logic_summary="Generates query embedding, calls match_embeddings, filters, deduplicates, and builds result objects." notable_decisions="Includes platform account in search; limits to best match per document."]
|
|
926
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
927
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
928
|
+
export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results: any[]; count: number; error?: string }> {
|
|
216
929
|
const query = body.query
|
|
217
930
|
const typeSlugs = body.type_slugs || []
|
|
218
931
|
const sources = body.sources || ['kb']
|
|
@@ -225,19 +938,24 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
225
938
|
|
|
226
939
|
if (!query) throw new Error('query is required')
|
|
227
940
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
941
|
+
// App knowledge envelope is the ceiling; body.type_slugs is the caller's
|
|
942
|
+
// pick within it. Deny-by-default: no envelope, no retrieval.
|
|
943
|
+
const scope = await resolveKnowledgeScope(ctx, (ctx as any).appId || body.app_id, { item_types: typeSlugs })
|
|
944
|
+
if (!scope.allowed) {
|
|
945
|
+
return { results: [], count: 0, error: `knowledge_denied: ${scope.deniedReason}` }
|
|
946
|
+
}
|
|
947
|
+
if (scope.violations.length) {
|
|
948
|
+
return { results: [], count: 0, error: `knowledge_denied: sources outside app envelope: ${scope.violations.join(', ')}` }
|
|
232
949
|
}
|
|
950
|
+
const accountIds = scope.accountIds
|
|
233
951
|
|
|
234
952
|
const queryEmbedding = await generateEmbedding(query, ctx)
|
|
235
953
|
if (!queryEmbedding.length) return { results: [], count: 0 }
|
|
236
954
|
|
|
237
955
|
const allowedVectorTypes = new Set(vectorFields)
|
|
238
956
|
const allowedTypeSlugs = new Set(typeSlugs)
|
|
239
|
-
const includeKb = sources.includes('kb')
|
|
240
|
-
const includeCode = sources.includes('code')
|
|
957
|
+
const includeKb = !sources.length || sources.includes('kb')
|
|
958
|
+
const includeCode = !sources.length || sources.includes('code')
|
|
241
959
|
|
|
242
960
|
const allMatches: any[] = []
|
|
243
961
|
const vectorTypesToSearch = vectorFields.length ? vectorFields : [null]
|
|
@@ -250,7 +968,7 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
250
968
|
filter_account_ids: accountIds,
|
|
251
969
|
filter_vector_type: vt,
|
|
252
970
|
similarity_threshold: threshold,
|
|
253
|
-
filter_item_types:
|
|
971
|
+
filter_item_types: scope.rpcItemTypes,
|
|
254
972
|
})
|
|
255
973
|
|
|
256
974
|
if (rpcError) throw new Error(`rpc_${rpcError.message}`)
|
|
@@ -259,6 +977,7 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
259
977
|
|
|
260
978
|
const filtered = allMatches.filter((m: any) => {
|
|
261
979
|
if (m.similarity < threshold) return false
|
|
980
|
+
if (!knowledgeRowAllowed(scope, m)) return false
|
|
262
981
|
const meta = m.metadata || {}
|
|
263
982
|
const vectorType = meta.vector_type || ''
|
|
264
983
|
const matchAppId = meta.app_id || null
|
|
@@ -267,6 +986,10 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
267
986
|
|
|
268
987
|
if (appId && matchAppId !== appId) return false
|
|
269
988
|
|
|
989
|
+
const isCode = meta.item_type === 'cil_chunk'
|
|
990
|
+
if (isCode && !includeCode) return false
|
|
991
|
+
if (!isCode && !includeKb) return false
|
|
992
|
+
|
|
270
993
|
return true
|
|
271
994
|
})
|
|
272
995
|
|
|
@@ -279,17 +1002,42 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
279
1002
|
const documentIds = Array.from(bestByDoc.keys()).slice(offset, offset + limit)
|
|
280
1003
|
if (documentIds.length === 0) return { results: [], count: 0 }
|
|
281
1004
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
1005
|
+
// document_id is a UUID for regular records but a CIL stable_id for
|
|
1006
|
+
// cil_chunk embeddings — feeding stable_ids to .in('id', ...) errors with
|
|
1007
|
+
// 'invalid input syntax for type uuid', so split the lookup like
|
|
1008
|
+
// retrieveKnowledge does.
|
|
1009
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
1010
|
+
const uuidIds = documentIds.filter((id) => UUID_RE.test(id))
|
|
1011
|
+
const stableIds = documentIds.filter((id) => !UUID_RE.test(id))
|
|
1012
|
+
const records: any[] = []
|
|
1013
|
+
if (uuidIds.length) {
|
|
1014
|
+
const { data, error } = await (ctx.db || adminDb)
|
|
1015
|
+
.from('items')
|
|
1016
|
+
.select('*, types!inner(slug)')
|
|
1017
|
+
.in('id', uuidIds)
|
|
1018
|
+
.in('account_id', accountIds)
|
|
1019
|
+
if (error) throw error
|
|
1020
|
+
records.push(...(data || []))
|
|
1021
|
+
}
|
|
1022
|
+
if (stableIds.length) {
|
|
1023
|
+
const { data, error } = await (ctx.db || adminDb)
|
|
1024
|
+
.from('items')
|
|
1025
|
+
.select('*, types!inner(slug)')
|
|
1026
|
+
.in('data->>stable_id', stableIds)
|
|
1027
|
+
.in('account_id', accountIds)
|
|
1028
|
+
if (error) throw error
|
|
1029
|
+
records.push(...(data || []))
|
|
1030
|
+
}
|
|
287
1031
|
|
|
288
|
-
|
|
1032
|
+
const recordByDocId = new Map<string, any>()
|
|
1033
|
+
for (const r of records) {
|
|
1034
|
+
recordByDocId.set(r.id, r)
|
|
1035
|
+
if (r.data?.stable_id) recordByDocId.set(r.data.stable_id, r)
|
|
1036
|
+
}
|
|
289
1037
|
|
|
290
1038
|
const results: any[] = []
|
|
291
1039
|
for (const match of documentIds.map((id) => bestByDoc.get(id))) {
|
|
292
|
-
const record =
|
|
1040
|
+
const record = recordByDocId.get(match.document_id)
|
|
293
1041
|
if (!record) continue
|
|
294
1042
|
if (record.status !== 'published') continue
|
|
295
1043
|
if (!matchesFilters(record, filters)) continue
|
|
@@ -302,6 +1050,7 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
302
1050
|
const source = match.metadata?.item_type === 'cil_chunk' ? 'code' : 'kb'
|
|
303
1051
|
results.push({
|
|
304
1052
|
id: record.id,
|
|
1053
|
+
document_id: match.document_id,
|
|
305
1054
|
type_slug: recordTypeSlug,
|
|
306
1055
|
title: sanitized.title || record.title,
|
|
307
1056
|
snippet: match.content,
|
|
@@ -316,4 +1065,29 @@ export async function kbSearch(ctx: CoreContext, body: any): Promise<{ results:
|
|
|
316
1065
|
|
|
317
1066
|
return { results: results.sort((a, b) => b.similarity - a.similarity), count: results.length }
|
|
318
1067
|
}
|
|
1068
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Performs semantic search over knowledge base embeddings and returns matching published records.'; No verified external callers; imports: .framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts"]
|
|
1069
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
1070
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
1071
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
1072
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
1073
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
1074
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:kbSearch:function"]
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/kb.ts:kbPublish:function" type="function" identity="kbPublish" parent="~/.framework/functions/_shared/kb.ts"]
|
|
1078
|
+
// CIL[DOC-MACRO purpose="Publish a KB record through admin-data and immediately ingest it for vector search"]
|
|
1079
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements kbPublish"]
|
|
1080
|
+
// CIL[DOC-CONTEXT module="functions/_shared" layer="kb" related_files=".framework/functions/_shared/kb.ts,.framework/functions/admin-data.ts"]
|
|
1081
|
+
// CIL[DOC-CONTRACT inputs="ctx: CoreContext, body with id: string" outputs="{ record, ingest }" errors="id is required; admin-data validation/permission errors" side_effects="PATCHes items.status to 'published' via admin-data; writes/revokes embeddings via kbIngest"]
|
|
1082
|
+
// CIL[DOC-MICRO logic_summary="Builds a synthetic PATCH context targeting items/:id, flips status to published through admin-data so permissions/audit/triggers run, then calls kbIngest to embed the published record" notable_decisions="Publish goes through admin-data rather than adminDb so field-level permissions and triggers apply"]
|
|
1083
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
1084
|
+
export async function kbPublish(ctx: CoreContext, body: { id: string }) {
|
|
1085
|
+
const { id } = body
|
|
1086
|
+
if (!id) throw new Error('id is required')
|
|
1087
|
+
const updateCtx = { ...ctx, query: { ...ctx.query, entity: 'items', id, method: 'PATCH' } }
|
|
1088
|
+
const record = await adminUpdate(updateCtx as any, { status: 'published' })
|
|
1089
|
+
const ingest = await kbIngest(ctx, { id })
|
|
1090
|
+
return { record, ingest }
|
|
1091
|
+
}
|
|
1092
|
+
// CIL[END stable_id="~/.framework/functions/_shared/kb.ts:kbPublish:function"]
|
|
319
1093
|
|