spine-framework 0.3.145 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +5051 -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
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/052_registration_security.sql:registration_security:file" type="file" identity="registration_security" parent="~/.framework/migrations/052_registration_security.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Registration and invite hardening: advisory-locked, token-gated stub claiming in handle_new_auth_user and an atomic, service-role-only complete_registration_placement RPC."]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Serialize signup processing and invite-stub claims, fail clearly on missing seed dependencies, and move account placement/role assignment out of the request path into a locked SECURITY DEFINER RPC."]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/010_auth_user_trigger.sql,.framework/migrations/042_registration_hierarchy.sql,.framework/functions/invites.ts"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="NEW auth.users row; p_person_id/p_app_slug/p_path/p_account_name" outputs="people rows inserted/updated; jsonb {status,account_slug,role_slug}" errors="RAISE EXCEPTION on missing person type, unclaimed invite stub, missing first-user seeds, invalid app/path/role/account" side_effects="Replaces handle_new_auth_user; creates complete_registration_placement; revokes EXECUTE from PUBLIC/anon/authenticated and grants to service_role"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="handle_new_auth_user takes pg_advisory_xact_lock('spine.registration') before any reads, claims invited/pending stubs only when raw_user_meta_data.invite_token matches the stub's stored token and the invite is unblocked/unexpired, raises when an unbound stub exists but the token is absent/invalid, and raises on missing seed dependencies for the first user. complete_registration_placement locks per person, is idempotent, resolves apps.config->'registration' server-side, and assigns account_id/role_id/status atomically for new_account, existing_account, and join_request paths." notable_decisions="Advisory xact locks serialize first-user detection and stub claims; the RPC is the only placement writer so client-supplied role/target values are never trusted"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
|
|
10
|
+
-- Registration/invitation server-side hardening (Phase D).
|
|
11
|
+
--
|
|
12
|
+
-- * handle_new_auth_user() previously bound ANY unbound invited/pending
|
|
13
|
+
-- stub by email alone — anyone who registered with the invitee's email
|
|
14
|
+
-- claimed the invite without the token. Claims now require the invite
|
|
15
|
+
-- token carried in raw_user_meta_data.invite_token (set by the invite
|
|
16
|
+
-- link signup flow).
|
|
17
|
+
-- * The first-user check (NOT EXISTS people) raced under concurrent
|
|
18
|
+
-- signups; an advisory lock now serializes it.
|
|
19
|
+
-- * complete_registration_placement() is the single atomic writer for
|
|
20
|
+
-- registration placement. The invites endpoint calls it with the
|
|
21
|
+
-- server-resolved person id — role, account strategy, and join target
|
|
22
|
+
-- all come from the app's installed config, never the request body.
|
|
23
|
+
|
|
24
|
+
-- ============================================
|
|
25
|
+
-- 1. handle_new_auth_user() v3
|
|
26
|
+
-- ============================================
|
|
27
|
+
CREATE OR REPLACE FUNCTION public.handle_new_auth_user()
|
|
28
|
+
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
|
29
|
+
DECLARE
|
|
30
|
+
v_account_id uuid;
|
|
31
|
+
v_type_id uuid;
|
|
32
|
+
v_role_id uuid;
|
|
33
|
+
v_is_first boolean;
|
|
34
|
+
v_design_schema jsonb;
|
|
35
|
+
v_validation_schema jsonb;
|
|
36
|
+
v_stub_id uuid;
|
|
37
|
+
v_stub_status text;
|
|
38
|
+
v_token text;
|
|
39
|
+
BEGIN
|
|
40
|
+
-- Serialize first-user detection AND invite-stub claims. Without this,
|
|
41
|
+
-- two concurrent signups could both pass the NOT EXISTS people check or
|
|
42
|
+
-- race to claim the same stub.
|
|
43
|
+
PERFORM pg_advisory_xact_lock(hashtext('spine.registration'));
|
|
44
|
+
|
|
45
|
+
SELECT id, design_schema, validation_schema
|
|
46
|
+
INTO v_type_id, v_design_schema, v_validation_schema
|
|
47
|
+
FROM public.types WHERE kind = 'person' AND slug = 'person' LIMIT 1;
|
|
48
|
+
|
|
49
|
+
-- Fail clearly: an auth user with no resolvable person type can never be
|
|
50
|
+
-- placed — better to error than silently create an unresolvable account.
|
|
51
|
+
IF v_type_id IS NULL THEN
|
|
52
|
+
RAISE EXCEPTION 'handle_new_auth_user: person type (kind=person, slug=person) not found — run framework type seeds before registering';
|
|
53
|
+
END IF;
|
|
54
|
+
|
|
55
|
+
-- Invite-stub claims REQUIRE the token. The stub's token lives in
|
|
56
|
+
-- people.data->>'invite_token' (written by createInvite); the client
|
|
57
|
+
-- echoes it back via raw_user_meta_data.invite_token at signup.
|
|
58
|
+
v_token := NEW.raw_user_meta_data->>'invite_token';
|
|
59
|
+
|
|
60
|
+
SELECT id, status
|
|
61
|
+
INTO v_stub_id, v_stub_status
|
|
62
|
+
FROM public.people
|
|
63
|
+
WHERE email = NEW.email
|
|
64
|
+
AND auth_uid IS NULL
|
|
65
|
+
AND status IN ('invited', 'pending')
|
|
66
|
+
AND data->>'invite_token' = v_token
|
|
67
|
+
AND data->>'invite_blocked_at' IS NULL
|
|
68
|
+
AND (data->>'invite_expires_at' IS NULL
|
|
69
|
+
OR (data->>'invite_expires_at')::timestamptz > now())
|
|
70
|
+
ORDER BY created_at
|
|
71
|
+
LIMIT 1;
|
|
72
|
+
|
|
73
|
+
IF v_stub_id IS NOT NULL THEN
|
|
74
|
+
UPDATE public.people SET
|
|
75
|
+
auth_uid = NEW.id,
|
|
76
|
+
full_name = COALESCE(NEW.raw_user_meta_data->>'full_name', full_name, split_part(NEW.email, '@', 1)),
|
|
77
|
+
status = CASE WHEN v_stub_status = 'invited' THEN 'active' ELSE 'pending' END,
|
|
78
|
+
data = (COALESCE(data, '{}'::jsonb) - 'invite_token')
|
|
79
|
+
|| jsonb_build_object('invite_claimed_at', now()),
|
|
80
|
+
updated_at = now()
|
|
81
|
+
WHERE id = v_stub_id;
|
|
82
|
+
|
|
83
|
+
RETURN NEW;
|
|
84
|
+
END IF;
|
|
85
|
+
|
|
86
|
+
-- The email matches an unbound invited/pending stub but the token was
|
|
87
|
+
-- absent, wrong, blocked, or expired. people.email is UNIQUE so no second
|
|
88
|
+
-- row can be created — fail loudly instead of silently orphaning the auth
|
|
89
|
+
-- user or letting them bypass the invite gate.
|
|
90
|
+
IF EXISTS (
|
|
91
|
+
SELECT 1 FROM public.people
|
|
92
|
+
WHERE email = NEW.email
|
|
93
|
+
AND auth_uid IS NULL
|
|
94
|
+
AND status IN ('invited', 'pending')
|
|
95
|
+
) THEN
|
|
96
|
+
RAISE EXCEPTION 'A pending invitation exists for this email; register via your invite link';
|
|
97
|
+
END IF;
|
|
98
|
+
|
|
99
|
+
-- First-user detection stays inside the advisory lock so concurrent
|
|
100
|
+
-- signups cannot both claim system_admin.
|
|
101
|
+
SELECT NOT EXISTS (SELECT 1 FROM public.people LIMIT 1) INTO v_is_first;
|
|
102
|
+
IF v_is_first THEN
|
|
103
|
+
SELECT id INTO v_role_id FROM public.roles WHERE slug = 'system_admin' LIMIT 1;
|
|
104
|
+
SELECT id INTO v_account_id FROM public.accounts WHERE slug = 'spine-system' LIMIT 1;
|
|
105
|
+
|
|
106
|
+
-- Missing seeds are unrecoverable here — fail clearly and name the
|
|
107
|
+
-- dependency instead of creating a rootless first user.
|
|
108
|
+
IF v_account_id IS NULL AND v_role_id IS NULL THEN
|
|
109
|
+
RAISE EXCEPTION 'handle_new_auth_user: first-user bootstrap requires the ''spine-system'' account and ''system_admin'' role seeds — run framework seeds before registering';
|
|
110
|
+
ELSIF v_account_id IS NULL THEN
|
|
111
|
+
RAISE EXCEPTION 'handle_new_auth_user: first-user bootstrap requires the ''spine-system'' account seed — run framework seeds before registering';
|
|
112
|
+
ELSIF v_role_id IS NULL THEN
|
|
113
|
+
RAISE EXCEPTION 'handle_new_auth_user: first-user bootstrap requires the ''system_admin'' role seed — run framework seeds before registering';
|
|
114
|
+
END IF;
|
|
115
|
+
END IF;
|
|
116
|
+
|
|
117
|
+
-- Non-first, non-invited users get no account and no role here.
|
|
118
|
+
-- complete_registration_placement assigns both from the app's installed
|
|
119
|
+
-- config via the invites endpoint.
|
|
120
|
+
INSERT INTO public.people (
|
|
121
|
+
id,
|
|
122
|
+
auth_uid,
|
|
123
|
+
email,
|
|
124
|
+
full_name,
|
|
125
|
+
account_id,
|
|
126
|
+
type_id,
|
|
127
|
+
role_id,
|
|
128
|
+
status,
|
|
129
|
+
is_active,
|
|
130
|
+
design_schema,
|
|
131
|
+
validation_schema
|
|
132
|
+
) VALUES (
|
|
133
|
+
NEW.id,
|
|
134
|
+
NEW.id,
|
|
135
|
+
NEW.email,
|
|
136
|
+
COALESCE(NEW.raw_user_meta_data->>'full_name', split_part(NEW.email, '@', 1)),
|
|
137
|
+
v_account_id,
|
|
138
|
+
v_type_id,
|
|
139
|
+
v_role_id,
|
|
140
|
+
'active',
|
|
141
|
+
true,
|
|
142
|
+
v_design_schema,
|
|
143
|
+
COALESCE(v_validation_schema, '{}'::jsonb)
|
|
144
|
+
)
|
|
145
|
+
ON CONFLICT (id) DO NOTHING;
|
|
146
|
+
|
|
147
|
+
RETURN NEW;
|
|
148
|
+
END;
|
|
149
|
+
$$;
|
|
150
|
+
|
|
151
|
+
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
152
|
+
CREATE TRIGGER on_auth_user_created
|
|
153
|
+
AFTER INSERT ON auth.users
|
|
154
|
+
FOR EACH ROW EXECUTE FUNCTION public.handle_new_auth_user();
|
|
155
|
+
|
|
156
|
+
-- ============================================
|
|
157
|
+
-- 2. complete_registration_placement()
|
|
158
|
+
-- Atomic, idempotent placement writer. Service-role only — the invites
|
|
159
|
+
-- endpoint calls it as adminDb; PUBLIC/anon/authenticated get nothing.
|
|
160
|
+
-- ============================================
|
|
161
|
+
CREATE OR REPLACE FUNCTION public.complete_registration_placement(
|
|
162
|
+
p_person_id uuid,
|
|
163
|
+
p_app_slug text,
|
|
164
|
+
p_path text,
|
|
165
|
+
p_account_name text
|
|
166
|
+
)
|
|
167
|
+
RETURNS jsonb
|
|
168
|
+
LANGUAGE plpgsql
|
|
169
|
+
SECURITY DEFINER
|
|
170
|
+
SET search_path = public
|
|
171
|
+
AS $$
|
|
172
|
+
DECLARE
|
|
173
|
+
v_person public.people%ROWTYPE;
|
|
174
|
+
v_app_reg jsonb;
|
|
175
|
+
v_role_slug text;
|
|
176
|
+
v_role public.roles%ROWTYPE;
|
|
177
|
+
v_account_id uuid;
|
|
178
|
+
v_account_slug text;
|
|
179
|
+
v_account_type_id uuid;
|
|
180
|
+
v_parent_id uuid;
|
|
181
|
+
v_type_id uuid;
|
|
182
|
+
v_design_schema jsonb;
|
|
183
|
+
v_validation_schema jsonb;
|
|
184
|
+
v_base text;
|
|
185
|
+
v_domain text;
|
|
186
|
+
v_status text;
|
|
187
|
+
v_target record;
|
|
188
|
+
v_personal_domains text[] := ARRAY[
|
|
189
|
+
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com',
|
|
190
|
+
'aol.com', 'protonmail.com', 'me.com', 'mac.com', 'live.com',
|
|
191
|
+
'msn.com', 'ymail.com', 'googlemail.com', 'fastmail.com'
|
|
192
|
+
];
|
|
193
|
+
BEGIN
|
|
194
|
+
IF p_person_id IS NULL OR p_app_slug IS NULL OR p_path IS NULL THEN
|
|
195
|
+
RAISE EXCEPTION 'person, app, and path are required';
|
|
196
|
+
END IF;
|
|
197
|
+
|
|
198
|
+
-- Per-person lock serializes concurrent/duplicate placement calls.
|
|
199
|
+
PERFORM pg_advisory_xact_lock(hashtext('spine.placement.' || p_person_id::text));
|
|
200
|
+
|
|
201
|
+
SELECT * INTO v_person FROM public.people WHERE id = p_person_id FOR UPDATE;
|
|
202
|
+
IF NOT FOUND THEN
|
|
203
|
+
RAISE EXCEPTION 'Person not found';
|
|
204
|
+
END IF;
|
|
205
|
+
|
|
206
|
+
-- Idempotent: a retry after a lost response returns the placement that
|
|
207
|
+
-- already committed instead of creating a second account or failing.
|
|
208
|
+
IF v_person.account_id IS NOT NULL AND v_person.role_id IS NOT NULL THEN
|
|
209
|
+
RETURN (
|
|
210
|
+
SELECT jsonb_build_object(
|
|
211
|
+
'status', v_person.status,
|
|
212
|
+
'account_slug', a.slug,
|
|
213
|
+
'role_slug', r.slug
|
|
214
|
+
)
|
|
215
|
+
FROM public.accounts a, public.roles r
|
|
216
|
+
WHERE a.id = v_person.account_id AND r.id = v_person.role_id
|
|
217
|
+
);
|
|
218
|
+
END IF;
|
|
219
|
+
|
|
220
|
+
-- Resolve the installed registration policy server-side.
|
|
221
|
+
SELECT config->'registration' INTO v_app_reg
|
|
222
|
+
FROM public.apps
|
|
223
|
+
WHERE slug = p_app_slug AND is_active = true
|
|
224
|
+
LIMIT 1;
|
|
225
|
+
|
|
226
|
+
IF v_app_reg IS NULL THEN
|
|
227
|
+
RAISE EXCEPTION 'Unknown or uninstalled app ''%'' or no registration policy configured', p_app_slug;
|
|
228
|
+
END IF;
|
|
229
|
+
IF COALESCE((v_app_reg->>'enabled')::boolean, false) = false THEN
|
|
230
|
+
RAISE EXCEPTION 'Registration is not enabled for app ''%''', p_app_slug;
|
|
231
|
+
END IF;
|
|
232
|
+
|
|
233
|
+
-- Role comes from the app's installed config — NEVER the request.
|
|
234
|
+
-- join_request defaults to 'member' when the app permits it.
|
|
235
|
+
v_role_slug := CASE
|
|
236
|
+
WHEN p_path = 'join_request' AND (v_app_reg->'roles' ? 'member') THEN 'member'
|
|
237
|
+
ELSE v_app_reg->>'default_role'
|
|
238
|
+
END;
|
|
239
|
+
|
|
240
|
+
IF v_role_slug IS NULL THEN
|
|
241
|
+
RAISE EXCEPTION 'App ''%'' registration has no default_role configured', p_app_slug;
|
|
242
|
+
END IF;
|
|
243
|
+
IF v_app_reg->'roles' IS NOT NULL AND NOT (v_app_reg->'roles' ? v_role_slug) THEN
|
|
244
|
+
RAISE EXCEPTION 'Role ''%'' is not permitted in this context', v_role_slug;
|
|
245
|
+
END IF;
|
|
246
|
+
IF v_role_slug = 'system_admin' THEN
|
|
247
|
+
RAISE EXCEPTION 'Role ''system_admin'' cannot be assigned through registration';
|
|
248
|
+
END IF;
|
|
249
|
+
|
|
250
|
+
SELECT * INTO v_role FROM public.roles WHERE slug = v_role_slug LIMIT 1;
|
|
251
|
+
IF NOT FOUND THEN
|
|
252
|
+
RAISE EXCEPTION 'Role ''%'' not found', v_role_slug;
|
|
253
|
+
END IF;
|
|
254
|
+
IF v_role.is_system = true THEN
|
|
255
|
+
RAISE EXCEPTION 'Role ''%'' is not permitted in this context', v_role_slug;
|
|
256
|
+
END IF;
|
|
257
|
+
|
|
258
|
+
-- Post-lock precondition: another writer already assigned a role. Return
|
|
259
|
+
-- the existing placement rather than overwrite (lost-response resume).
|
|
260
|
+
IF v_person.role_id IS NOT NULL THEN
|
|
261
|
+
RETURN (
|
|
262
|
+
SELECT jsonb_build_object(
|
|
263
|
+
'status', v_person.status,
|
|
264
|
+
'account_slug', a.slug,
|
|
265
|
+
'role_slug', r.slug
|
|
266
|
+
)
|
|
267
|
+
FROM public.roles r
|
|
268
|
+
LEFT JOIN public.accounts a ON a.id = v_person.account_id
|
|
269
|
+
WHERE r.id = v_person.role_id
|
|
270
|
+
);
|
|
271
|
+
END IF;
|
|
272
|
+
|
|
273
|
+
SELECT id, design_schema, validation_schema
|
|
274
|
+
INTO v_type_id, v_design_schema, v_validation_schema
|
|
275
|
+
FROM public.types WHERE kind = 'person' AND slug = 'person' LIMIT 1;
|
|
276
|
+
IF v_type_id IS NULL THEN
|
|
277
|
+
RAISE EXCEPTION 'Person type (kind=person, slug=person) not found — run framework type seeds';
|
|
278
|
+
END IF;
|
|
279
|
+
|
|
280
|
+
IF p_path = 'new_account' THEN
|
|
281
|
+
IF p_account_name IS NULL OR btrim(p_account_name) = '' THEN
|
|
282
|
+
RAISE EXCEPTION 'account_name is required for new_account path';
|
|
283
|
+
END IF;
|
|
284
|
+
|
|
285
|
+
v_parent_id := NULL;
|
|
286
|
+
IF v_app_reg->>'parent_account' IS NOT NULL THEN
|
|
287
|
+
SELECT id INTO v_parent_id FROM public.accounts
|
|
288
|
+
WHERE slug = v_app_reg->>'parent_account' LIMIT 1;
|
|
289
|
+
IF v_parent_id IS NULL THEN
|
|
290
|
+
RAISE EXCEPTION 'Parent account ''%'' not found', v_app_reg->>'parent_account';
|
|
291
|
+
END IF;
|
|
292
|
+
END IF;
|
|
293
|
+
|
|
294
|
+
SELECT id INTO v_account_type_id FROM public.types
|
|
295
|
+
WHERE kind = 'account' AND slug = 'account' LIMIT 1;
|
|
296
|
+
IF v_account_type_id IS NULL THEN
|
|
297
|
+
RAISE EXCEPTION 'Account type (kind=account, slug=account) not found — run framework type seeds';
|
|
298
|
+
END IF;
|
|
299
|
+
|
|
300
|
+
-- Slug pattern matches generateAccountSlug: lower, non-alphanumeric runs
|
|
301
|
+
-- -> '-', trimmed, 40-char base, '-xxxxxx' random suffix, retried on
|
|
302
|
+
-- collision until unique.
|
|
303
|
+
v_base := left(btrim(lower(regexp_replace(p_account_name, '[^a-z0-9]+', '-', 'g')), '-'), 40);
|
|
304
|
+
IF v_base = '' THEN v_base := 'account'; END IF;
|
|
305
|
+
LOOP
|
|
306
|
+
v_account_slug := v_base || '-' || substr(md5(random()::text), 1, 6);
|
|
307
|
+
EXIT WHEN NOT EXISTS (SELECT 1 FROM public.accounts WHERE slug = v_account_slug);
|
|
308
|
+
END LOOP;
|
|
309
|
+
|
|
310
|
+
INSERT INTO public.accounts (slug, display_name, type_id, parent_id, is_active)
|
|
311
|
+
VALUES (v_account_slug, btrim(p_account_name), v_account_type_id, v_parent_id, true)
|
|
312
|
+
RETURNING id INTO v_account_id;
|
|
313
|
+
|
|
314
|
+
v_status := 'active';
|
|
315
|
+
|
|
316
|
+
UPDATE public.people SET
|
|
317
|
+
account_id = v_account_id,
|
|
318
|
+
role_id = v_role.id,
|
|
319
|
+
status = 'active',
|
|
320
|
+
type_id = v_type_id,
|
|
321
|
+
design_schema = v_design_schema,
|
|
322
|
+
validation_schema = COALESCE(v_validation_schema, '{}'::jsonb),
|
|
323
|
+
updated_at = now()
|
|
324
|
+
WHERE id = v_person.id
|
|
325
|
+
AND role_id IS NULL;
|
|
326
|
+
|
|
327
|
+
ELSIF p_path = 'existing_account' THEN
|
|
328
|
+
IF COALESCE(v_app_reg->>'account_strategy', '') <> 'existing' THEN
|
|
329
|
+
RAISE EXCEPTION 'App ''%'' does not support joining an existing account', p_app_slug;
|
|
330
|
+
END IF;
|
|
331
|
+
IF v_app_reg->>'target_account' IS NULL THEN
|
|
332
|
+
RAISE EXCEPTION 'No target_account configured for this app registration';
|
|
333
|
+
END IF;
|
|
334
|
+
|
|
335
|
+
SELECT id, slug INTO v_target FROM public.accounts
|
|
336
|
+
WHERE slug = v_app_reg->>'target_account' LIMIT 1;
|
|
337
|
+
IF NOT FOUND THEN
|
|
338
|
+
RAISE EXCEPTION 'Target account ''%'' not found', v_app_reg->>'target_account';
|
|
339
|
+
END IF;
|
|
340
|
+
v_account_id := v_target.id;
|
|
341
|
+
v_account_slug := v_target.slug;
|
|
342
|
+
v_status := 'active';
|
|
343
|
+
|
|
344
|
+
UPDATE public.people SET
|
|
345
|
+
account_id = v_account_id,
|
|
346
|
+
role_id = v_role.id,
|
|
347
|
+
status = 'active',
|
|
348
|
+
type_id = v_type_id,
|
|
349
|
+
design_schema = v_design_schema,
|
|
350
|
+
validation_schema = COALESCE(v_validation_schema, '{}'::jsonb),
|
|
351
|
+
data = '{}'::jsonb,
|
|
352
|
+
updated_at = now()
|
|
353
|
+
WHERE id = v_person.id
|
|
354
|
+
AND role_id IS NULL;
|
|
355
|
+
|
|
356
|
+
ELSIF p_path = 'join_request' THEN
|
|
357
|
+
-- Opt-in only: apps must declare allow_domain_join. The target account
|
|
358
|
+
-- is derived server-side from the person's email domain — the client
|
|
359
|
+
-- never supplies target_account_id.
|
|
360
|
+
IF COALESCE((v_app_reg->>'allow_domain_join')::boolean, false) = false THEN
|
|
361
|
+
RAISE EXCEPTION 'App ''%'' does not allow domain-based join requests', p_app_slug;
|
|
362
|
+
END IF;
|
|
363
|
+
|
|
364
|
+
v_domain := lower(split_part(v_person.email, '@', 2));
|
|
365
|
+
IF v_domain IS NULL OR v_domain = '' OR v_domain = ANY(v_personal_domains) THEN
|
|
366
|
+
RAISE EXCEPTION 'No account found for this email domain';
|
|
367
|
+
END IF;
|
|
368
|
+
|
|
369
|
+
SELECT p2.account_id INTO v_account_id
|
|
370
|
+
FROM public.people p2
|
|
371
|
+
WHERE lower(split_part(p2.email, '@', 2)) = v_domain
|
|
372
|
+
AND p2.status = 'active'
|
|
373
|
+
AND p2.account_id IS NOT NULL
|
|
374
|
+
ORDER BY p2.created_at
|
|
375
|
+
LIMIT 1;
|
|
376
|
+
|
|
377
|
+
IF v_account_id IS NULL THEN
|
|
378
|
+
RAISE EXCEPTION 'No account found for this email domain';
|
|
379
|
+
END IF;
|
|
380
|
+
SELECT slug INTO v_account_slug FROM public.accounts WHERE id = v_account_id;
|
|
381
|
+
|
|
382
|
+
v_status := 'pending';
|
|
383
|
+
|
|
384
|
+
UPDATE public.people SET
|
|
385
|
+
account_id = v_account_id,
|
|
386
|
+
role_id = v_role.id,
|
|
387
|
+
status = 'pending',
|
|
388
|
+
type_id = v_type_id,
|
|
389
|
+
design_schema = v_design_schema,
|
|
390
|
+
validation_schema = COALESCE(v_validation_schema, '{}'::jsonb),
|
|
391
|
+
data = COALESCE(data, '{}'::jsonb) || jsonb_build_object(
|
|
392
|
+
'invite_kind', 'access_request',
|
|
393
|
+
'invite_invited_by', null,
|
|
394
|
+
'invite_token', null),
|
|
395
|
+
updated_at = now()
|
|
396
|
+
WHERE id = v_person.id
|
|
397
|
+
AND role_id IS NULL;
|
|
398
|
+
|
|
399
|
+
ELSE
|
|
400
|
+
RAISE EXCEPTION 'Unknown registration path: %', p_path;
|
|
401
|
+
END IF;
|
|
402
|
+
|
|
403
|
+
RETURN jsonb_build_object(
|
|
404
|
+
'status', v_status,
|
|
405
|
+
'account_slug', v_account_slug,
|
|
406
|
+
'role_slug', v_role.slug
|
|
407
|
+
);
|
|
408
|
+
END;
|
|
409
|
+
$$;
|
|
410
|
+
|
|
411
|
+
REVOKE ALL ON FUNCTION public.complete_registration_placement(uuid, text, text, text) FROM PUBLIC;
|
|
412
|
+
REVOKE ALL ON FUNCTION public.complete_registration_placement(uuid, text, text, text) FROM anon;
|
|
413
|
+
REVOKE ALL ON FUNCTION public.complete_registration_placement(uuid, text, text, text) FROM authenticated;
|
|
414
|
+
GRANT EXECUTE ON FUNCTION public.complete_registration_placement(uuid, text, text, text) TO service_role;
|
|
415
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Registration and invite hardening: advisory-locked, token-gated stub claiming in handle_new_auth_user and an atomic, service-role-only complete_registration_placement RPC.'; No verified external callers; imports: none" status="ready"]
|
|
416
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="All new signups; invites.completeRegistration delegates placement to the RPC; invited stubs can only be claimed with a valid token" status="ready"]
|
|
417
|
+
-- CIL[DOC-VECTOR type="configuration" content="Requires people.data invite_* keys written by createInvite; apps.config->'registration' installed per app (042); optional allow_domain_join flag for join_request" status="ready"]
|
|
418
|
+
-- CIL[DOC-VECTOR type="constraint" content="complete_registration_placement is service-role only; role must be declared in appReg.roles, non-system_admin, and is_system=false" status="ready"]
|
|
419
|
+
-- CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
420
|
+
-- CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
421
|
+
-- CIL[END stable_id="~/.framework/migrations/052_registration_security.sql:registration_security:file"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/053_builtin_action_seeds.sql:builtin_action_seeds:file" type="file" identity="builtin_action_seeds" parent="~/.framework/migrations/053_builtin_action_seeds.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Idempotently seed the 11 built-in pipeline action rows that the pipeline-runner stageHandlers map requires."]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Insert one public.actions row per built-in stage type (update_item, create_record, http_request, send_notification, run_pipeline, search_knowledge, query_items, agent_inference, generate_embeddings, revoke_embeddings, create_interaction) only when no row with that slug exists."]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/018_runtime_tables.sql,.framework/functions/_shared/pipeline-runner.ts,custom/apps/cortex/seed/actions.json"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="none" outputs="11 global actions rows (account_id NULL, ownership platform, is_system true)" errors="none — INSERT ... SELECT ... WHERE NOT EXISTS is a no-op when a slug is already present" side_effects="Fresh installs gain the action rows runPipeline resolves via .eq('slug', stage_type).eq('is_active', true).single()"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="Each INSERT ... SELECT ... WHERE NOT EXISTS checks the slug alone (actions.slug is deliberately NOT UNIQUE — no constraint added, existing DBs may carry dupes). Names/descriptions mirror custom/apps/cortex/seed/actions.json wording, which no manifest seeds, so this migration is the only writer of built-in action rows." notable_decisions="Per-row NOT EXISTS keeps the file re-runnable and leaves pre-existing rows untouched; global rows carry account_id NULL"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
|
|
10
|
+
-- Built-in pipeline action seeds. runPipeline resolves every stage's action by
|
|
11
|
+
-- slug + is_active via .single(); without these rows a fresh install cannot run
|
|
12
|
+
-- any pipeline stage. cortex/seed/actions.json documents the same shape but no
|
|
13
|
+
-- manifest references it, so the framework migration owns the built-ins.
|
|
14
|
+
-- Each row is guarded by NOT EXISTS on slug — idempotent across re-runs and
|
|
15
|
+
-- additive-only on databases that already carry equivalent rows.
|
|
16
|
+
|
|
17
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
18
|
+
SELECT 'update_item', 'Update Item', 'Core pipeline stage: update fields on a runtime record.', 'update_item', 'functions', 'platform', true, true
|
|
19
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'update_item');
|
|
20
|
+
|
|
21
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
22
|
+
SELECT 'create_record', 'Create Record', 'Core pipeline stage: create a runtime record in a whitelisted entity table.', 'create_record', 'functions', 'platform', true, true
|
|
23
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'create_record');
|
|
24
|
+
|
|
25
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
26
|
+
SELECT 'http_request', 'HTTP Request', 'Core pipeline stage: perform an outbound HTTP request.', 'http_request', 'functions', 'platform', true, true
|
|
27
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'http_request');
|
|
28
|
+
|
|
29
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
30
|
+
SELECT 'send_notification', 'Send Notification', 'Core pipeline stage: send a notification to role- or person-scoped recipients.', 'send_notification', 'functions', 'platform', true, true
|
|
31
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'send_notification');
|
|
32
|
+
|
|
33
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
34
|
+
SELECT 'run_pipeline', 'Run Pipeline', 'Core pipeline stage: invoke another pipeline by id or slug.', 'run_pipeline', 'functions', 'platform', true, true
|
|
35
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'run_pipeline');
|
|
36
|
+
|
|
37
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
38
|
+
SELECT 'search_knowledge', 'Search Knowledge', 'Core pipeline stage: hybrid retrieval over the knowledge base.', 'search_knowledge', 'functions', 'platform', true, true
|
|
39
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'search_knowledge');
|
|
40
|
+
|
|
41
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
42
|
+
SELECT 'query_items', 'Query Items', 'Core pipeline stage: query runtime items with filters.', 'query_items', 'functions', 'platform', true, true
|
|
43
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'query_items');
|
|
44
|
+
|
|
45
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
46
|
+
SELECT 'agent_inference', 'Agent Inference', 'Core pipeline stage: run an agent inference step.', 'agent_inference', 'functions', 'platform', true, true
|
|
47
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'agent_inference');
|
|
48
|
+
|
|
49
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
50
|
+
SELECT 'generate_embeddings', 'Generate Embeddings', 'Core pipeline stage: generate embeddings for a runtime item via the core embedding engine.', 'generate_embeddings', 'functions', 'platform', true, true
|
|
51
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'generate_embeddings');
|
|
52
|
+
|
|
53
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
54
|
+
SELECT 'revoke_embeddings', 'Revoke Embeddings', 'Core pipeline stage: revoke embeddings for a runtime item via the core embedding engine.', 'revoke_embeddings', 'functions', 'platform', true, true
|
|
55
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'revoke_embeddings');
|
|
56
|
+
|
|
57
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
58
|
+
SELECT 'create_interaction', 'Create AI Interaction', 'Core pipeline stage: capture an AI interaction record via ai-interactions.createInteraction.', 'create_interaction', 'functions', 'platform', true, true
|
|
59
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'create_interaction');
|
|
60
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Idempotently seed the 11 built-in pipeline action rows the pipeline-runner stageHandlers map requires.'; No verified external callers; imports: none" status="ready"]
|
|
61
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="Fresh installs can execute every built-in pipeline stage; existing databases keep their action rows untouched" status="ready"]
|
|
62
|
+
-- CIL[DOC-VECTOR type="configuration" content="Runs after 018 (actions table); supersedes the unreferenced custom/apps/cortex/seed/actions.json for built-ins" status="ready"]
|
|
63
|
+
-- CIL[DOC-VECTOR type="constraint" content="NOT EXISTS guard is on slug only; no unique constraint is added because existing DBs may already hold duplicate slugs" status="ready"]
|
|
64
|
+
-- CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
65
|
+
-- CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
66
|
+
-- CIL[END stable_id="~/.framework/migrations/053_builtin_action_seeds.sql:builtin_action_seeds:file"]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/054_kb_ingest_lease.sql:kb_ingest_lease:file" type="file" identity="kb_ingest_lease" parent="~/.framework/migrations/054_kb_ingest_lease.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Content-hash document lease for kbIngest: document_leases table, atomic service-role-only acquire/release RPCs, and kb_ingest/kb_revoke pipeline action seeds."]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Create public.document_leases (one row per embedded document), provide acquire_document_lease/release_document_lease as the single atomic writers, and seed the generic kb_ingest/kb_revoke pipeline action rows."]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/functions/_shared/kb.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/migrations/053_builtin_action_seeds.sql,.framework/migrations/049_security_blocklist.sql"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="acquire: p_document_id/p_account_id/p_content_hash/p_ttl_seconds; release: p_document_id/p_lease_id/p_status" outputs="acquire jsonb {outcome: acquired|reused|processing, lease_id?}; release boolean" errors="none — all branches return" side_effects="Upserts/updates document_leases; kbIngest callers use the lease to skip unchanged re-embeds and fence concurrent workers"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="acquire_document_lease INSERTs ON CONFLICT DO NOTHING; on conflict it SELECTs FOR UPDATE and returns 'reused' for a completed same-hash row, 'processing' for a live same-hash in-flight row, and takes over (new lease_id, new hash, fresh TTL) for expired/failed/different-hash rows. release_document_lease updates only on (document_id, lease_id) match so a superseded worker's release is fenced by the token." notable_decisions="Same-hash completed work is reused without extending the lease; a new content_hash supersedes an in-flight worker whose later release then fails the token check; RLS grants service_role FOR ALL and EXECUTE goes to service_role only, matching 049/052's service-only convention"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
|
|
10
|
+
-- ── document_leases ────────────────────────────────────────────────
|
|
11
|
+
-- One row per embedded document (items.id). kbIngest acquires before
|
|
12
|
+
-- embedding: 'reused' skips unchanged content, 'processing' fences a
|
|
13
|
+
-- second worker, 'acquired' grants a lease_id token the holder must
|
|
14
|
+
-- present to release.
|
|
15
|
+
CREATE TABLE IF NOT EXISTS public.document_leases (
|
|
16
|
+
document_id uuid PRIMARY KEY,
|
|
17
|
+
account_id uuid NOT NULL,
|
|
18
|
+
content_hash text NOT NULL,
|
|
19
|
+
lease_id uuid NOT NULL,
|
|
20
|
+
status text NOT NULL DEFAULT 'processing',
|
|
21
|
+
expires_at timestamptz NOT NULL,
|
|
22
|
+
created_at timestamptz DEFAULT now(),
|
|
23
|
+
updated_at timestamptz DEFAULT now()
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
ALTER TABLE public.document_leases ENABLE ROW LEVEL SECURITY;
|
|
27
|
+
|
|
28
|
+
-- Service-role only: all reads/writes flow through _shared/kb.ts
|
|
29
|
+
-- (adminDb) and the two RPCs below.
|
|
30
|
+
DROP POLICY IF EXISTS document_leases_service_role ON public.document_leases;
|
|
31
|
+
CREATE POLICY document_leases_service_role ON public.document_leases
|
|
32
|
+
FOR ALL TO service_role USING (true) WITH CHECK (true);
|
|
33
|
+
|
|
34
|
+
-- ── acquire_document_lease ─────────────────────────────────────────
|
|
35
|
+
-- Atomic claim. Returns {outcome, lease_id?}:
|
|
36
|
+
-- acquired — caller owns the work; must release with the token
|
|
37
|
+
-- reused — same-hash completed embeddings exist; skip re-embed
|
|
38
|
+
-- processing — a live worker holds the same hash; do not stamp success
|
|
39
|
+
CREATE OR REPLACE FUNCTION public.acquire_document_lease(
|
|
40
|
+
p_document_id uuid,
|
|
41
|
+
p_account_id uuid,
|
|
42
|
+
p_content_hash text,
|
|
43
|
+
p_ttl_seconds integer DEFAULT 300
|
|
44
|
+
)
|
|
45
|
+
RETURNS jsonb
|
|
46
|
+
LANGUAGE plpgsql
|
|
47
|
+
SECURITY DEFINER
|
|
48
|
+
SET search_path = public
|
|
49
|
+
AS $$
|
|
50
|
+
DECLARE
|
|
51
|
+
v_lease public.document_leases%ROWTYPE;
|
|
52
|
+
v_new_lease_id uuid := gen_random_uuid();
|
|
53
|
+
BEGIN
|
|
54
|
+
-- Fast path: claim the empty slot. The document_id PK serializes
|
|
55
|
+
-- concurrent first-writers — the loser's INSERT is a no-op.
|
|
56
|
+
INSERT INTO public.document_leases (document_id, account_id, content_hash, lease_id, status, expires_at)
|
|
57
|
+
VALUES (
|
|
58
|
+
p_document_id,
|
|
59
|
+
p_account_id,
|
|
60
|
+
p_content_hash,
|
|
61
|
+
v_new_lease_id,
|
|
62
|
+
'processing',
|
|
63
|
+
now() + make_interval(secs => p_ttl_seconds)
|
|
64
|
+
)
|
|
65
|
+
ON CONFLICT (document_id) DO NOTHING;
|
|
66
|
+
|
|
67
|
+
IF FOUND THEN
|
|
68
|
+
RETURN jsonb_build_object('outcome', 'acquired', 'lease_id', v_new_lease_id);
|
|
69
|
+
END IF;
|
|
70
|
+
|
|
71
|
+
-- Conflict path: lock the row so a concurrent release/takeover cannot
|
|
72
|
+
-- interleave between our read and our verdict.
|
|
73
|
+
SELECT * INTO v_lease
|
|
74
|
+
FROM public.document_leases
|
|
75
|
+
WHERE document_id = p_document_id
|
|
76
|
+
FOR UPDATE;
|
|
77
|
+
|
|
78
|
+
-- Same-hash completed work is reused — do NOT extend the lease.
|
|
79
|
+
IF v_lease.status = 'completed' AND v_lease.content_hash = p_content_hash THEN
|
|
80
|
+
RETURN jsonb_build_object('outcome', 'reused');
|
|
81
|
+
END IF;
|
|
82
|
+
|
|
83
|
+
-- A live worker owns the same hash — the caller must not stamp success.
|
|
84
|
+
IF v_lease.status = 'processing'
|
|
85
|
+
AND v_lease.expires_at > now()
|
|
86
|
+
AND v_lease.content_hash = p_content_hash THEN
|
|
87
|
+
RETURN jsonb_build_object('outcome', 'processing', 'lease_id', null);
|
|
88
|
+
END IF;
|
|
89
|
+
|
|
90
|
+
-- Expired, failed, or a different content hash: take over. A new hash
|
|
91
|
+
-- supersedes — the displaced worker's release will fail the token check.
|
|
92
|
+
UPDATE public.document_leases SET
|
|
93
|
+
lease_id = v_new_lease_id,
|
|
94
|
+
status = 'processing',
|
|
95
|
+
content_hash = p_content_hash,
|
|
96
|
+
account_id = p_account_id,
|
|
97
|
+
expires_at = now() + make_interval(secs => p_ttl_seconds),
|
|
98
|
+
updated_at = now()
|
|
99
|
+
WHERE document_id = p_document_id;
|
|
100
|
+
|
|
101
|
+
RETURN jsonb_build_object('outcome', 'acquired', 'lease_id', v_new_lease_id);
|
|
102
|
+
END;
|
|
103
|
+
$$;
|
|
104
|
+
|
|
105
|
+
REVOKE ALL ON FUNCTION public.acquire_document_lease(uuid, uuid, text, integer) FROM PUBLIC;
|
|
106
|
+
REVOKE ALL ON FUNCTION public.acquire_document_lease(uuid, uuid, text, integer) FROM anon;
|
|
107
|
+
REVOKE ALL ON FUNCTION public.acquire_document_lease(uuid, uuid, text, integer) FROM authenticated;
|
|
108
|
+
GRANT EXECUTE ON FUNCTION public.acquire_document_lease(uuid, uuid, text, integer) TO service_role;
|
|
109
|
+
|
|
110
|
+
-- ── release_document_lease ─────────────────────────────────────────
|
|
111
|
+
-- Token-gated completion. Returns false when the lease_id no longer
|
|
112
|
+
-- matches (a superseding acquire took over) — the caller must treat
|
|
113
|
+
-- that as "do not stamp success", not as an error.
|
|
114
|
+
CREATE OR REPLACE FUNCTION public.release_document_lease(
|
|
115
|
+
p_document_id uuid,
|
|
116
|
+
p_lease_id uuid,
|
|
117
|
+
p_status text
|
|
118
|
+
)
|
|
119
|
+
RETURNS boolean
|
|
120
|
+
LANGUAGE plpgsql
|
|
121
|
+
SECURITY DEFINER
|
|
122
|
+
SET search_path = public
|
|
123
|
+
AS $$
|
|
124
|
+
BEGIN
|
|
125
|
+
UPDATE public.document_leases SET
|
|
126
|
+
status = p_status,
|
|
127
|
+
expires_at = now(),
|
|
128
|
+
updated_at = now()
|
|
129
|
+
WHERE document_id = p_document_id
|
|
130
|
+
AND lease_id = p_lease_id;
|
|
131
|
+
|
|
132
|
+
RETURN FOUND;
|
|
133
|
+
END;
|
|
134
|
+
$$;
|
|
135
|
+
|
|
136
|
+
REVOKE ALL ON FUNCTION public.release_document_lease(uuid, uuid, text) FROM PUBLIC;
|
|
137
|
+
REVOKE ALL ON FUNCTION public.release_document_lease(uuid, uuid, text) FROM anon;
|
|
138
|
+
REVOKE ALL ON FUNCTION public.release_document_lease(uuid, uuid, text) FROM authenticated;
|
|
139
|
+
GRANT EXECUTE ON FUNCTION public.release_document_lease(uuid, uuid, text) TO service_role;
|
|
140
|
+
|
|
141
|
+
-- ── kb_ingest / kb_revoke action seeds ─────────────────────────────
|
|
142
|
+
-- Generic typed-path stages (wrap _shared/kb.ts kbIngest/kbRevoke) —
|
|
143
|
+
-- same WHERE-NOT-EXISTS idempotency as 053's built-in seeds.
|
|
144
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
145
|
+
SELECT 'kb_ingest', 'KB Ingest', 'Core pipeline stage: ingest a runtime item into the knowledge base via the canonical typed path (content-hash lease + declared vector fields).', 'kb_ingest', 'functions', 'platform', true, true
|
|
146
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'kb_ingest');
|
|
147
|
+
|
|
148
|
+
INSERT INTO public.actions (slug, name, description, handler, handler_module, ownership, is_system, is_active)
|
|
149
|
+
SELECT 'kb_revoke', 'KB Revoke', 'Core pipeline stage: revoke a runtime item from the knowledge base via the canonical typed path (embeddings + document lease).', 'kb_revoke', 'functions', 'platform', true, true
|
|
150
|
+
WHERE NOT EXISTS (SELECT 1 FROM public.actions WHERE slug = 'kb_revoke');
|
|
151
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Content-hash document lease for kbIngest plus generic kb_ingest/kb_revoke action seeds.'; No verified external callers; imports: none" status="ready"]
|
|
152
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="kbIngest skips unchanged re-embeds and fences concurrent workers; pipelines gain generic kb_ingest/kb_revoke stages on fresh installs" status="ready"]
|
|
153
|
+
-- CIL[DOC-VECTOR type="configuration" content="Runs after 018 (actions) and 020 (embeddings); consumed by _shared/kb.ts via adminDb.rpc" status="ready"]
|
|
154
|
+
-- CIL[DOC-VECTOR type="constraint" content="document_leases and both RPCs are service-role only; release is fenced by lease_id token; same-hash completed rows return reused without TTL extension" status="ready"]
|
|
155
|
+
-- CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
156
|
+
-- CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
157
|
+
-- CIL[END stable_id="~/.framework/migrations/054_kb_ingest_lease.sql:kb_ingest_lease:file"]
|