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,309 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/042_registration_hierarchy.sql:registration_hierarchy:file" type="file" identity="registration_hierarchy" parent="~/.framework/migrations/042_registration_hierarchy.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Make spine-system a staff-only root: null-account signups, people self-read, and uniform actor resolution across all account-scoped RLS policies."]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Stop routing generic signups into spine-system, let people read their own row pre-assignment, replace auth.uid() with public.current_actor_id() in every account-scoped policy, parent existing root accounts under spine-system, and retire demo account expiry markers."]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/002_core_rls_and_functions.sql,.framework/migrations/010_auth_user_trigger.sql,.framework/migrations/006_security_hardening_rls.sql,.framework/functions/invites.ts"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="none" outputs="Updated trigger, RLS policies, and account rows" errors="SQL errors if referenced objects are missing" side_effects="Alters people.account_id nullability; replaces handle_new_auth_user; drops and recreates RLS policies; updates accounts.parent_id and accounts.data; updates apps.config.registration for known slugs"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="people.account_id becomes nullable so non-invited signups carry no account until completeRegistration assigns one; people_access gains self-read; all policies that resolved the actor through auth.uid() now use current_actor_id() so invited users (people.id != auth.users.id) see threads/messages/links/attachments/embeddings consistently; non-system root accounts are re-parented to spine-system and expires_at/demo_expired markers are cleared." notable_decisions="spine-system is reserved for the first user (system_admin) and invited staff; hierarchy visibility flows parent->child via get_accessible_accounts"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
-- Registration + account hierarchy hardening.
|
|
10
|
+
--
|
|
11
|
+
-- Background:
|
|
12
|
+
-- * handle_new_auth_user() (010) placed EVERY signup in spine-system, which
|
|
13
|
+
-- becomes unsafe once customer accounts are children of spine-system —
|
|
14
|
+
-- any stray member would gain descendant visibility.
|
|
15
|
+
-- * Policies created in 002/004/006 resolved the actor via auth.uid(),
|
|
16
|
+
-- which only equals people.id for self-signups. Invited users have a
|
|
17
|
+
-- stub-generated people.id, so they could not read threads/messages/etc.
|
|
18
|
+
-- * Customer accounts were created as roots (no parent_id), so
|
|
19
|
+
-- get_accessible_accounts() never reached them for staff users.
|
|
20
|
+
-- * accounts.data.expires_at drove the demo expiry sweep; demo signups are
|
|
21
|
+
-- real support-portal customers now, so expiry markers are removed.
|
|
22
|
+
|
|
23
|
+
-- ============================================
|
|
24
|
+
-- 1. people.account_id becomes nullable.
|
|
25
|
+
-- Non-invited signups start unassigned; completeRegistration assigns them.
|
|
26
|
+
-- ============================================
|
|
27
|
+
ALTER TABLE public.people ALTER COLUMN account_id DROP NOT NULL;
|
|
28
|
+
|
|
29
|
+
-- ============================================
|
|
30
|
+
-- 2. Signup trigger: only the first user (system_admin) and invited/pending
|
|
31
|
+
-- stubs are attached to an account. Everyone else starts with
|
|
32
|
+
-- account_id = NULL until they complete registration.
|
|
33
|
+
-- ============================================
|
|
34
|
+
CREATE OR REPLACE FUNCTION public.handle_new_auth_user()
|
|
35
|
+
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
|
36
|
+
DECLARE
|
|
37
|
+
v_account_id uuid;
|
|
38
|
+
v_type_id uuid;
|
|
39
|
+
v_role_id uuid;
|
|
40
|
+
v_is_first boolean;
|
|
41
|
+
v_design_schema jsonb;
|
|
42
|
+
v_validation_schema jsonb;
|
|
43
|
+
v_stub_id uuid;
|
|
44
|
+
v_stub_status text;
|
|
45
|
+
BEGIN
|
|
46
|
+
SELECT id, design_schema, validation_schema
|
|
47
|
+
INTO v_type_id, v_design_schema, v_validation_schema
|
|
48
|
+
FROM public.types WHERE kind = 'person' AND slug = 'person' LIMIT 1;
|
|
49
|
+
|
|
50
|
+
IF v_type_id IS NULL THEN
|
|
51
|
+
RAISE WARNING 'handle_new_auth_user: person type not found — skipping';
|
|
52
|
+
RETURN NEW;
|
|
53
|
+
END IF;
|
|
54
|
+
|
|
55
|
+
SELECT id, status
|
|
56
|
+
INTO v_stub_id, v_stub_status
|
|
57
|
+
FROM public.people
|
|
58
|
+
WHERE email = NEW.email
|
|
59
|
+
AND status IN ('invited', 'pending')
|
|
60
|
+
AND auth_uid IS NULL
|
|
61
|
+
LIMIT 1;
|
|
62
|
+
|
|
63
|
+
IF v_stub_id IS NOT NULL THEN
|
|
64
|
+
UPDATE public.people SET
|
|
65
|
+
auth_uid = NEW.id,
|
|
66
|
+
full_name = COALESCE(NEW.raw_user_meta_data->>'full_name', full_name, split_part(NEW.email, '@', 1)),
|
|
67
|
+
status = CASE WHEN v_stub_status = 'invited' THEN 'active' ELSE 'pending' END,
|
|
68
|
+
updated_at = now()
|
|
69
|
+
WHERE id = v_stub_id;
|
|
70
|
+
|
|
71
|
+
RETURN NEW;
|
|
72
|
+
END IF;
|
|
73
|
+
|
|
74
|
+
SELECT NOT EXISTS (SELECT 1 FROM public.people LIMIT 1) INTO v_is_first;
|
|
75
|
+
IF v_is_first THEN
|
|
76
|
+
SELECT id INTO v_role_id FROM public.roles WHERE slug = 'system_admin' LIMIT 1;
|
|
77
|
+
SELECT id INTO v_account_id FROM public.accounts WHERE slug = 'spine-system' LIMIT 1;
|
|
78
|
+
END IF;
|
|
79
|
+
|
|
80
|
+
-- Non-first, non-invited users get no account and no role here.
|
|
81
|
+
-- completeRegistration assigns both from the app's installed config.
|
|
82
|
+
INSERT INTO public.people (
|
|
83
|
+
id,
|
|
84
|
+
auth_uid,
|
|
85
|
+
email,
|
|
86
|
+
full_name,
|
|
87
|
+
account_id,
|
|
88
|
+
type_id,
|
|
89
|
+
role_id,
|
|
90
|
+
status,
|
|
91
|
+
is_active,
|
|
92
|
+
design_schema,
|
|
93
|
+
validation_schema
|
|
94
|
+
) VALUES (
|
|
95
|
+
NEW.id,
|
|
96
|
+
NEW.id,
|
|
97
|
+
NEW.email,
|
|
98
|
+
COALESCE(NEW.raw_user_meta_data->>'full_name', split_part(NEW.email, '@', 1)),
|
|
99
|
+
v_account_id,
|
|
100
|
+
v_type_id,
|
|
101
|
+
v_role_id,
|
|
102
|
+
'active',
|
|
103
|
+
true,
|
|
104
|
+
v_design_schema,
|
|
105
|
+
COALESCE(v_validation_schema, '{}'::jsonb)
|
|
106
|
+
)
|
|
107
|
+
ON CONFLICT (id) DO NOTHING;
|
|
108
|
+
|
|
109
|
+
RETURN NEW;
|
|
110
|
+
END;
|
|
111
|
+
$$;
|
|
112
|
+
|
|
113
|
+
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
114
|
+
CREATE TRIGGER on_auth_user_created
|
|
115
|
+
AFTER INSERT ON auth.users
|
|
116
|
+
FOR EACH ROW EXECUTE FUNCTION public.handle_new_auth_user();
|
|
117
|
+
|
|
118
|
+
-- ============================================
|
|
119
|
+
-- 3. people: a person can always read their own row, even before they have
|
|
120
|
+
-- an account. Without this, null-account users cannot resolve their
|
|
121
|
+
-- principal or /api/auth context to finish registration.
|
|
122
|
+
-- ============================================
|
|
123
|
+
DROP POLICY IF EXISTS people_access ON public.people;
|
|
124
|
+
CREATE POLICY people_access ON public.people FOR ALL
|
|
125
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))
|
|
126
|
+
OR id = public.current_actor_id());
|
|
127
|
+
|
|
128
|
+
-- ============================================
|
|
129
|
+
-- 4. Normalize actor resolution: auth.uid() -> public.current_actor_id().
|
|
130
|
+
-- get_accessible_accounts expects a people.id; auth.uid() only coincides
|
|
131
|
+
-- with it for self-signups, so invited members were invisible to these
|
|
132
|
+
-- policies. This is what makes account access cover tickets AND their
|
|
133
|
+
-- threads/messages/links/attachments/watchers/embeddings uniformly.
|
|
134
|
+
-- ============================================
|
|
135
|
+
|
|
136
|
+
DROP POLICY IF EXISTS threads_access ON public.threads;
|
|
137
|
+
CREATE POLICY threads_access ON public.threads FOR ALL
|
|
138
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
139
|
+
|
|
140
|
+
DROP POLICY IF EXISTS messages_access ON public.messages;
|
|
141
|
+
CREATE POLICY messages_access ON public.messages FOR ALL
|
|
142
|
+
USING (EXISTS (SELECT 1 FROM public.threads t
|
|
143
|
+
WHERE t.id = messages.thread_id
|
|
144
|
+
AND t.account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))));
|
|
145
|
+
|
|
146
|
+
DROP POLICY IF EXISTS links_access ON public.links;
|
|
147
|
+
CREATE POLICY links_access ON public.links FOR ALL
|
|
148
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
149
|
+
|
|
150
|
+
DROP POLICY IF EXISTS attachments_access ON public.attachments;
|
|
151
|
+
CREATE POLICY attachments_access ON public.attachments FOR ALL
|
|
152
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
153
|
+
|
|
154
|
+
DROP POLICY IF EXISTS watchers_access ON public.watchers;
|
|
155
|
+
CREATE POLICY watchers_access ON public.watchers FOR ALL
|
|
156
|
+
USING (account_id IS NULL
|
|
157
|
+
OR account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
158
|
+
|
|
159
|
+
DROP POLICY IF EXISTS link_types_access ON public.link_types;
|
|
160
|
+
CREATE POLICY link_types_access ON public.link_types FOR ALL
|
|
161
|
+
USING (app_id IS NULL
|
|
162
|
+
OR EXISTS (SELECT 1 FROM public.apps a WHERE a.id = link_types.app_id
|
|
163
|
+
AND a.owner_account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))));
|
|
164
|
+
|
|
165
|
+
DROP POLICY IF EXISTS pipelines_access ON public.pipelines;
|
|
166
|
+
CREATE POLICY pipelines_access ON public.pipelines FOR ALL
|
|
167
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
168
|
+
|
|
169
|
+
DROP POLICY IF EXISTS pe_access ON public.pipeline_executions;
|
|
170
|
+
CREATE POLICY pe_access ON public.pipeline_executions FOR ALL
|
|
171
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
172
|
+
|
|
173
|
+
DROP POLICY IF EXISTS triggers_access ON public.triggers;
|
|
174
|
+
CREATE POLICY triggers_access ON public.triggers FOR ALL
|
|
175
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
176
|
+
|
|
177
|
+
DROP POLICY IF EXISTS te_access ON public.trigger_executions;
|
|
178
|
+
CREATE POLICY te_access ON public.trigger_executions FOR ALL
|
|
179
|
+
USING (EXISTS (SELECT 1 FROM public.triggers t
|
|
180
|
+
WHERE t.id = trigger_executions.trigger_id
|
|
181
|
+
AND t.account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))));
|
|
182
|
+
|
|
183
|
+
DROP POLICY IF EXISTS timers_access ON public.timers;
|
|
184
|
+
CREATE POLICY timers_access ON public.timers FOR ALL
|
|
185
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
186
|
+
|
|
187
|
+
DROP POLICY IF EXISTS ai_agents_access ON public.ai_agents;
|
|
188
|
+
CREATE POLICY ai_agents_access ON public.ai_agents FOR ALL
|
|
189
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
190
|
+
|
|
191
|
+
DROP POLICY IF EXISTS embeddings_access ON public.embeddings;
|
|
192
|
+
CREATE POLICY embeddings_access ON public.embeddings FOR ALL
|
|
193
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
194
|
+
|
|
195
|
+
DROP POLICY IF EXISTS integrations_access ON public.integrations;
|
|
196
|
+
CREATE POLICY integrations_access ON public.integrations FOR ALL
|
|
197
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
198
|
+
|
|
199
|
+
DROP POLICY IF EXISTS api_keys_access ON public.api_keys;
|
|
200
|
+
CREATE POLICY api_keys_access ON public.api_keys FOR ALL
|
|
201
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
202
|
+
|
|
203
|
+
DROP POLICY IF EXISTS akul_access ON public.api_key_usage_logs;
|
|
204
|
+
CREATE POLICY akul_access ON public.api_key_usage_logs FOR ALL
|
|
205
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
206
|
+
|
|
207
|
+
DROP POLICY IF EXISTS webhook_handlers_access ON public.webhook_handlers;
|
|
208
|
+
CREATE POLICY webhook_handlers_access ON public.webhook_handlers FOR ALL
|
|
209
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
210
|
+
|
|
211
|
+
DROP POLICY IF EXISTS webhook_deliveries_access ON public.webhook_deliveries;
|
|
212
|
+
CREATE POLICY webhook_deliveries_access ON public.webhook_deliveries FOR ALL
|
|
213
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
214
|
+
|
|
215
|
+
DROP POLICY IF EXISTS logs_access ON public.logs;
|
|
216
|
+
CREATE POLICY logs_access ON public.logs FOR ALL
|
|
217
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
218
|
+
|
|
219
|
+
DROP POLICY IF EXISTS apps_access ON public.apps;
|
|
220
|
+
CREATE POLICY apps_access ON public.apps FOR ALL
|
|
221
|
+
USING (is_system = true OR owner_account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
222
|
+
|
|
223
|
+
DROP POLICY IF EXISTS prompt_configs_access ON public.prompt_configs;
|
|
224
|
+
CREATE POLICY prompt_configs_access ON public.prompt_configs FOR ALL
|
|
225
|
+
USING ((app_id IS NULL OR EXISTS (SELECT 1 FROM public.apps a
|
|
226
|
+
WHERE a.id = prompt_configs.app_id
|
|
227
|
+
AND (a.is_system = true
|
|
228
|
+
OR a.owner_account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))))));
|
|
229
|
+
|
|
230
|
+
-- ============================================
|
|
231
|
+
-- 5. Backfill: existing customer/demo root accounts become children of
|
|
232
|
+
-- spine-system so staff get hierarchical read access. The
|
|
233
|
+
-- update_account_paths trigger maintains the closure table on UPDATE.
|
|
234
|
+
-- ============================================
|
|
235
|
+
UPDATE public.accounts a
|
|
236
|
+
SET parent_id = s.id, updated_at = now()
|
|
237
|
+
FROM public.accounts s
|
|
238
|
+
WHERE s.slug = 'spine-system'
|
|
239
|
+
AND a.parent_id IS NULL
|
|
240
|
+
AND a.id <> s.id;
|
|
241
|
+
|
|
242
|
+
-- ============================================
|
|
243
|
+
-- 6. Demo accounts are real support-portal customers — no expiry sweep.
|
|
244
|
+
-- Clear legacy lifecycle markers (is_demo itself stays for funnel
|
|
245
|
+
-- tagging and the demo rate-limit policy).
|
|
246
|
+
-- ============================================
|
|
247
|
+
UPDATE public.accounts
|
|
248
|
+
SET data = data - 'expires_at' - 'demo_expired' - 'expired_at',
|
|
249
|
+
updated_at = now()
|
|
250
|
+
WHERE data ?| ARRAY['expires_at', 'demo_expired', 'expired_at'];
|
|
251
|
+
|
|
252
|
+
-- ============================================
|
|
253
|
+
-- 7. Mirror each installed app's resolved registration policy onto
|
|
254
|
+
-- apps.config.registration so completeRegistration can resolve
|
|
255
|
+
-- strategy/role/target/parent server-side without trusting the client.
|
|
256
|
+
-- Values match spine.config.json registration.apps.
|
|
257
|
+
-- ============================================
|
|
258
|
+
UPDATE public.apps SET config = COALESCE(config, '{}'::jsonb) || '{
|
|
259
|
+
"registration": {
|
|
260
|
+
"roles": ["member", "member_admin", "support", "support_admin", "sales", "csm", "tam", "engineering", "engineering_admin", "product", "docs", "qa", "devops", "management"],
|
|
261
|
+
"default_role": "member_admin",
|
|
262
|
+
"path": "/portal",
|
|
263
|
+
"enabled": true,
|
|
264
|
+
"account_strategy": "new",
|
|
265
|
+
"parent_account": "spine-system"
|
|
266
|
+
}
|
|
267
|
+
}'::jsonb
|
|
268
|
+
WHERE slug = 'portal';
|
|
269
|
+
|
|
270
|
+
UPDATE public.apps SET config = COALESCE(config, '{}'::jsonb) || '{
|
|
271
|
+
"registration": {
|
|
272
|
+
"roles": ["member", "member_admin"],
|
|
273
|
+
"default_role": "member_admin",
|
|
274
|
+
"path": "/demo",
|
|
275
|
+
"enabled": true,
|
|
276
|
+
"account_strategy": "new",
|
|
277
|
+
"parent_account": "spine-system"
|
|
278
|
+
}
|
|
279
|
+
}'::jsonb
|
|
280
|
+
WHERE slug = 'demo';
|
|
281
|
+
|
|
282
|
+
UPDATE public.apps SET config = COALESCE(config, '{}'::jsonb) || '{
|
|
283
|
+
"registration": {
|
|
284
|
+
"roles": ["support", "support_admin", "sales", "csm", "tam", "engineering", "engineering_admin", "product", "docs", "qa", "devops", "management"],
|
|
285
|
+
"default_role": "support",
|
|
286
|
+
"path": "/cortex",
|
|
287
|
+
"enabled": true,
|
|
288
|
+
"account_strategy": "existing",
|
|
289
|
+
"target_account": "spine-system"
|
|
290
|
+
}
|
|
291
|
+
}'::jsonb
|
|
292
|
+
WHERE slug = 'cortex';
|
|
293
|
+
|
|
294
|
+
UPDATE public.apps SET config = COALESCE(config, '{}'::jsonb) || '{
|
|
295
|
+
"registration": {
|
|
296
|
+
"roles": ["engineering", "engineering_admin", "product", "docs", "qa", "devops", "management", "support", "support_admin", "sales", "csm", "tam"],
|
|
297
|
+
"default_role": "engineering",
|
|
298
|
+
"path": "/workbench",
|
|
299
|
+
"enabled": true,
|
|
300
|
+
"account_strategy": "existing",
|
|
301
|
+
"target_account": "spine-system"
|
|
302
|
+
}
|
|
303
|
+
}'::jsonb
|
|
304
|
+
WHERE slug = 'workbench';
|
|
305
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Make spine-system a staff-only root: null-account signups, people self-read, and uniform actor resolution across all account-scoped RLS policies.'; No verified external callers; imports: none"]
|
|
306
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="Affects every account-scoped RLS policy; invited users gain correct access; anonymous signups lose spine-system placement" status="ready"]
|
|
307
|
+
-- CIL[DOC-VECTOR type="configuration" content="Depends on public.get_accessible_accounts, public.current_actor_id, account_paths trigger from 002" status="ready"]
|
|
308
|
+
-- CIL[DOC-VECTOR type="constraint" content="current_actor_id() requires is_active=true; deactivated people lose self-read" status="ready"]
|
|
309
|
+
-- CIL[END stable_id="~/.framework/migrations/042_registration_hierarchy.sql:registration_hierarchy:file"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/043_policy_actor_cleanup.sql:policy_actor_cleanup:file" type="file" identity="policy_actor_cleanup" parent="~/.framework/migrations/043_policy_actor_cleanup.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Finish actor-resolution normalization: fix the auth.uid() policies missed by 042 and close remaining anonymous-access bypasses."]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Replace auth.uid() with public.current_actor_id() in actions/item_progress/schedules/schedule_executions policies, drop their OR auth.uid() IS NULL bypasses, and replace the webhook_handlers system-admin check that trusted user-editable raw_user_meta_data."]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/042_registration_hierarchy.sql,.framework/migrations/006_security_hardening_rls.sql"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="none" outputs="Recreated RLS policies" errors="SQL errors if referenced objects are missing" side_effects="Drops and recreates five RLS policies"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="actions, item_progress, schedules, and schedule_executions get the standard accessible-accounts predicate via current_actor_id(); webhook_handlers_system_admin switches from auth.users.raw_user_meta_data->>'role' (client-controllable at signup) to person_is_system_admin(current_actor_id())." notable_decisions="OR auth.uid() IS NULL removed for parity with the 006 hardening pattern"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
-- Follow-up to 042: these four account-scoped tables still resolved the actor
|
|
10
|
+
-- via auth.uid() AND retained the `OR auth.uid() IS NULL` anonymous bypass
|
|
11
|
+
-- that 006 removed everywhere else. webhook_handlers_system_admin trusted
|
|
12
|
+
-- raw_user_meta_data.role, which a user can set at signup — replace it with
|
|
13
|
+
-- the people/roles-backed person_is_system_admin() check.
|
|
14
|
+
|
|
15
|
+
DROP POLICY IF EXISTS actions_access ON public.actions;
|
|
16
|
+
CREATE POLICY actions_access ON public.actions FOR ALL
|
|
17
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
18
|
+
|
|
19
|
+
DROP POLICY IF EXISTS item_progress_access ON public.item_progress;
|
|
20
|
+
CREATE POLICY item_progress_access ON public.item_progress FOR ALL
|
|
21
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
22
|
+
|
|
23
|
+
DROP POLICY IF EXISTS schedules_access ON public.schedules;
|
|
24
|
+
CREATE POLICY schedules_access ON public.schedules FOR ALL
|
|
25
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
26
|
+
|
|
27
|
+
DROP POLICY IF EXISTS se_access ON public.schedule_executions;
|
|
28
|
+
CREATE POLICY se_access ON public.schedule_executions FOR ALL
|
|
29
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
30
|
+
|
|
31
|
+
DROP POLICY IF EXISTS webhook_handlers_system_admin ON public.webhook_handlers;
|
|
32
|
+
CREATE POLICY webhook_handlers_system_admin ON public.webhook_handlers FOR ALL
|
|
33
|
+
USING (public.person_is_system_admin(public.current_actor_id()))
|
|
34
|
+
WITH CHECK (public.person_is_system_admin(public.current_actor_id()));
|
|
35
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Finish actor-resolution normalization and close anonymous/user-metadata access bypasses.'; No verified external callers; imports: none"]
|
|
36
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="Anonymous PostgREST access to actions/item_progress/schedules/schedule_executions is revoked; webhook_handlers admin check now uses role table" status="ready"]
|
|
37
|
+
-- CIL[DOC-VECTOR type="configuration" content="Depends on public.current_actor_id and public.person_is_system_admin from 002" status="ready"]
|
|
38
|
+
-- CIL[DOC-VECTOR type="constraint" content="Any flow relying on unauthenticated access to these tables must authenticate; service_role is unaffected" status="ready"]
|
|
39
|
+
-- CIL[END stable_id="~/.framework/migrations/043_policy_actor_cleanup.sql:policy_actor_cleanup:file"]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/044_match_embeddings_filtered_knn.sql:match_embeddings_filtered_knn:file" type="file" identity="match_embeddings_filtered_knn" parent="~/.framework/migrations/044_match_embeddings_filtered_knn.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Fix match_embeddings crowding-out for small tenants by routing selective searches to an exact filtered scan"]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Replace the match_embeddings RPC so account-filtered semantic search returns correct results even when the candidate set is a tiny fraction of the embeddings table"]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/034_match_embeddings_knn.sql,.framework/functions/_shared/agent-runner.ts,.framework/functions/_shared/kb.ts"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="existing public.match_embeddings function and embeddings table" outputs="replaced function with same signature and result shape" errors="requires pgvector >= 0.8 for hnsw.iterative_scan" side_effects="adds one indexed candidate-count query per non-document-targeted search"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="The 034 oversample takes the global top-100 KNN rows and then applies account/type filters; in a table dominated by CIL chunks a small account's rows never reach the candidate window, so selective searches return empty. Now: count rows matching the selective filters via embeddings_active_idx; when the candidate set is <= 5000 run an exact filter-first scan (bounded and correct); otherwise keep the global oversample (dense candidate sets always fill the top-N window)" notable_decisions="candidate_count threshold 5000 keeps the KNN fast path for the ~19k-row platform corpus while restoring correctness for small tenants; document-targeted branch unchanged"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="experimental"]
|
|
9
|
+
-- CIL[DOC-LEARNING topic="filtered_knn" content="KNN-first oversampling is only safe when the filtered candidate set is dense enough to occupy the oversample window; a 10-embedding account in a 19k-row table returns zero rows because the top-100 global neighbours contain no account rows. Exact filter-first scan is cheap precisely when crowding-out is a risk, so route by candidate count"]
|
|
10
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Fix match_embeddings crowding-out for small tenants by routing selective searches to an exact filtered scan'; callers: agent-runner retrieveKnowledge, kb.ts search, cil-embeddings, embeddings.ts, eval-harness, custom_kb-embeddings"]
|
|
11
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="Same signature and result shape as 034; all match_embeddings callers get correct selective-filter results; small-account searches change from empty to populated"]
|
|
12
|
+
-- CIL[DOC-VECTOR type="configuration" content="Requires pgvector 0.8+; function statement_timeout stays 25s; candidate-count query uses embeddings_active_idx (account_id, document_id WHERE approved_and_not_revoked)"]
|
|
13
|
+
-- CIL[DOC-VECTOR type="constraint" content="Result shape and parameter signature unchanged; filter_document_ids branch unchanged; CANDIDATE_SCAN_LIMIT (5000) bounds the exact-scan path"]
|
|
14
|
+
|
|
15
|
+
-- match_embeddings (034) oversamples the global KNN index (top 100+) and then
|
|
16
|
+
-- applies account/vector_type/item_type filters. When the table is dominated
|
|
17
|
+
-- by platform/CIL rows, a small tenant's embeddings never enter the
|
|
18
|
+
-- oversample window — the account filter sees an empty candidate set and the
|
|
19
|
+
-- search returns zero rows even for perfect matches.
|
|
20
|
+
--
|
|
21
|
+
-- The original filter-first form was removed in 034 because the planner
|
|
22
|
+
-- mis-estimates large accounts and picks a btree scan + heapsort. Keep that
|
|
23
|
+
-- fast path for dense candidate sets, but count candidates first (indexed by
|
|
24
|
+
-- embeddings_active_idx): when the set is small, an exact filtered scan is
|
|
25
|
+
-- both correct and cheap, so crowding-out cannot silently empty results.
|
|
26
|
+
|
|
27
|
+
CREATE OR REPLACE FUNCTION public.match_embeddings(
|
|
28
|
+
query_embedding vector(1536),
|
|
29
|
+
match_count int DEFAULT 10,
|
|
30
|
+
filter_account_ids uuid[] DEFAULT NULL,
|
|
31
|
+
filter_vector_type text DEFAULT NULL,
|
|
32
|
+
similarity_threshold float DEFAULT 0.15,
|
|
33
|
+
filter_item_types text[] DEFAULT NULL,
|
|
34
|
+
filter_document_ids text[] DEFAULT NULL
|
|
35
|
+
)
|
|
36
|
+
RETURNS TABLE (
|
|
37
|
+
id uuid,
|
|
38
|
+
document_id text,
|
|
39
|
+
content text,
|
|
40
|
+
metadata jsonb,
|
|
41
|
+
account_id uuid,
|
|
42
|
+
similarity float
|
|
43
|
+
)
|
|
44
|
+
LANGUAGE plpgsql
|
|
45
|
+
SECURITY DEFINER
|
|
46
|
+
SET search_path = public
|
|
47
|
+
SET statement_timeout = '25s'
|
|
48
|
+
AS $$
|
|
49
|
+
DECLARE
|
|
50
|
+
candidate_count bigint;
|
|
51
|
+
BEGIN
|
|
52
|
+
-- Document-targeted lookups stay filter-first: a global KNN oversample
|
|
53
|
+
-- would crowd out the requested documents before they can be scored.
|
|
54
|
+
IF filter_document_ids IS NOT NULL THEN
|
|
55
|
+
RETURN QUERY
|
|
56
|
+
SELECT
|
|
57
|
+
e.id,
|
|
58
|
+
e.document_id,
|
|
59
|
+
e.content,
|
|
60
|
+
e.metadata,
|
|
61
|
+
e.account_id,
|
|
62
|
+
1 - (e.embedding <=> query_embedding) AS similarity
|
|
63
|
+
FROM public.embeddings e
|
|
64
|
+
WHERE
|
|
65
|
+
(filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
|
|
66
|
+
AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
|
|
67
|
+
AND (filter_item_types IS NULL OR e.metadata->>'item_type' = ANY(filter_item_types))
|
|
68
|
+
AND e.document_id = ANY(filter_document_ids)
|
|
69
|
+
AND e.approved_at IS NOT NULL
|
|
70
|
+
AND e.revoked_at IS NULL
|
|
71
|
+
AND (1 - (e.embedding <=> query_embedding)) >= similarity_threshold
|
|
72
|
+
ORDER BY e.embedding <=> query_embedding
|
|
73
|
+
LIMIT match_count;
|
|
74
|
+
RETURN;
|
|
75
|
+
END IF;
|
|
76
|
+
|
|
77
|
+
PERFORM set_config('hnsw.iterative_scan', 'strict_order', true);
|
|
78
|
+
|
|
79
|
+
-- Route by candidate density: a small filtered set would be crowded out of
|
|
80
|
+
-- the global oversample, but is cheap to scan exactly via the account index.
|
|
81
|
+
SELECT count(*) INTO candidate_count
|
|
82
|
+
FROM public.embeddings e
|
|
83
|
+
WHERE
|
|
84
|
+
(filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
|
|
85
|
+
AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
|
|
86
|
+
AND (filter_item_types IS NULL OR e.metadata->>'item_type' = ANY(filter_item_types))
|
|
87
|
+
AND e.approved_at IS NOT NULL
|
|
88
|
+
AND e.revoked_at IS NULL;
|
|
89
|
+
|
|
90
|
+
IF candidate_count <= 5000 THEN
|
|
91
|
+
RETURN QUERY
|
|
92
|
+
SELECT
|
|
93
|
+
e.id,
|
|
94
|
+
e.document_id,
|
|
95
|
+
e.content,
|
|
96
|
+
e.metadata,
|
|
97
|
+
e.account_id,
|
|
98
|
+
1 - (e.embedding <=> query_embedding) AS similarity
|
|
99
|
+
FROM public.embeddings e
|
|
100
|
+
WHERE
|
|
101
|
+
(filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
|
|
102
|
+
AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
|
|
103
|
+
AND (filter_item_types IS NULL OR e.metadata->>'item_type' = ANY(filter_item_types))
|
|
104
|
+
AND e.approved_at IS NOT NULL
|
|
105
|
+
AND e.revoked_at IS NULL
|
|
106
|
+
AND (1 - (e.embedding <=> query_embedding)) >= similarity_threshold
|
|
107
|
+
ORDER BY e.embedding <=> query_embedding
|
|
108
|
+
LIMIT match_count;
|
|
109
|
+
RETURN;
|
|
110
|
+
END IF;
|
|
111
|
+
|
|
112
|
+
-- Dense candidate set: the global KNN oversample is safe — the top-100
|
|
113
|
+
-- window will contain enough matching rows for the post-filters.
|
|
114
|
+
RETURN QUERY
|
|
115
|
+
SELECT
|
|
116
|
+
t.id,
|
|
117
|
+
t.document_id,
|
|
118
|
+
t.content,
|
|
119
|
+
t.metadata,
|
|
120
|
+
t.account_id,
|
|
121
|
+
t.similarity
|
|
122
|
+
FROM (
|
|
123
|
+
SELECT
|
|
124
|
+
e.id,
|
|
125
|
+
e.document_id,
|
|
126
|
+
e.content,
|
|
127
|
+
e.metadata,
|
|
128
|
+
e.account_id,
|
|
129
|
+
e.approved_at,
|
|
130
|
+
e.revoked_at,
|
|
131
|
+
1 - (e.embedding <=> query_embedding) AS similarity
|
|
132
|
+
FROM public.embeddings e
|
|
133
|
+
ORDER BY e.embedding <=> query_embedding
|
|
134
|
+
LIMIT GREATEST(match_count * 10, 100)
|
|
135
|
+
) t
|
|
136
|
+
WHERE
|
|
137
|
+
(filter_account_ids IS NULL OR t.account_id = ANY(filter_account_ids))
|
|
138
|
+
AND (filter_vector_type IS NULL OR t.metadata->>'vector_type' = filter_vector_type)
|
|
139
|
+
AND (filter_item_types IS NULL OR t.metadata->>'item_type' = ANY(filter_item_types))
|
|
140
|
+
AND t.approved_at IS NOT NULL
|
|
141
|
+
AND t.revoked_at IS NULL
|
|
142
|
+
AND t.similarity >= similarity_threshold
|
|
143
|
+
ORDER BY t.similarity DESC
|
|
144
|
+
LIMIT match_count;
|
|
145
|
+
END
|
|
146
|
+
$$;
|
|
147
|
+
|
|
148
|
+
GRANT EXECUTE ON FUNCTION public.match_embeddings(
|
|
149
|
+
vector(1536), int, uuid[], text, float, text[], text[]
|
|
150
|
+
) TO authenticated, service_role;
|
|
151
|
+
-- CIL[END stable_id="~/.framework/migrations/044_match_embeddings_filtered_knn.sql:match_embeddings_filtered_knn:file"]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/045_security_critical_rls.sql:security_critical_rls:file" type="file" identity="security_critical_rls" parent="~/.framework/migrations/045_security_critical_rls.sql"]
|
|
2
|
+
-- CIL[DOC-MACRO purpose="Close the remaining anonymous-access bypasses and the member self-write path on security-critical tables"]
|
|
3
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Drop OR current_actor_id() IS NULL from system_settings/accounts/roles, require authentication for system_settings reads, restrict all role writes to system admins, and split people_access so members can read themselves but cannot self-write role_id/account_id via PostgREST"]
|
|
4
|
+
-- CIL[DOC-CONTEXT module="migrations" layer="database" related_files=".framework/migrations/006_security_hardening_rls.sql,.framework/migrations/043_policy_actor_cleanup.sql,.framework/migrations/042_registration_hierarchy.sql"]
|
|
5
|
+
-- CIL[DOC-CONTRACT inputs="none" outputs="Recreated RLS policies" errors="SQL errors if referenced objects are missing" side_effects="Drops and recreates six RLS policies"]
|
|
6
|
+
-- CIL[DOC-MICRO logic_summary="system_settings gets system-admin write + authenticated read; accounts loses its anonymous read branch; roles_modify becomes system-admin-only for both system and non-system roles; people_access splits into people_read (accessible accounts OR self) and people_write (accessible accounts only — no self-write bypass)" notable_decisions="roles_modify no longer allows any authenticated user to write non-system roles — role metadata is a trust anchor; people write policy has no self branch because admin-data performs record writes via adminDb after field-level permission checks, so no legitimate flow needs direct PostgREST self-update"]
|
|
7
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
8
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
|
|
10
|
+
-- system_settings: anonymous write (OR current_actor_id() IS NULL) allowed any
|
|
11
|
+
-- anon-key PostgREST caller to INSERT/UPDATE/DELETE global config. The read
|
|
12
|
+
-- policy is also USING (true); settings are only consumed server-side via
|
|
13
|
+
-- adminDb (logging.ts), so reads now require an authenticated actor.
|
|
14
|
+
DROP POLICY IF EXISTS system_settings_modify ON public.system_settings;
|
|
15
|
+
CREATE POLICY system_settings_modify ON public.system_settings FOR ALL
|
|
16
|
+
USING (public.person_is_system_admin(public.current_actor_id()))
|
|
17
|
+
WITH CHECK (public.person_is_system_admin(public.current_actor_id()));
|
|
18
|
+
|
|
19
|
+
DROP POLICY IF EXISTS system_settings_read ON public.system_settings;
|
|
20
|
+
CREATE POLICY system_settings_read ON public.system_settings FOR SELECT
|
|
21
|
+
USING (public.current_actor_id() IS NOT NULL);
|
|
22
|
+
|
|
23
|
+
-- accounts: anonymous read exposed the full customer list via the anon key.
|
|
24
|
+
-- All public/invite flows resolve records through adminDb (service role
|
|
25
|
+
-- bypasses RLS), so removing the bypass changes nothing legitimate.
|
|
26
|
+
DROP POLICY IF EXISTS accounts_access ON public.accounts;
|
|
27
|
+
CREATE POLICY accounts_access ON public.accounts FOR ALL
|
|
28
|
+
USING (id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
29
|
+
|
|
30
|
+
-- roles: the previous policy let unauthenticated callers write everything and
|
|
31
|
+
-- let any authenticated user create or modify non-system roles. Role metadata
|
|
32
|
+
-- feeds the permission engine — all writes are now system-admin only.
|
|
33
|
+
-- roles_read (USING (true)) is untouched: slug lookup stays public.
|
|
34
|
+
DROP POLICY IF EXISTS roles_modify ON public.roles;
|
|
35
|
+
CREATE POLICY roles_modify ON public.roles FOR ALL
|
|
36
|
+
USING (public.person_is_system_admin(public.current_actor_id()))
|
|
37
|
+
WITH CHECK (public.person_is_system_admin(public.current_actor_id()));
|
|
38
|
+
|
|
39
|
+
-- people: the single FOR ALL policy's OR (id = current_actor_id()) branch let a
|
|
40
|
+
-- member PATCH their own role_id and account_id directly through PostgREST,
|
|
41
|
+
-- bypassing admin-data's field-level permission checks. Split the policy:
|
|
42
|
+
-- reads keep self-access, writes require the row to sit in an accessible
|
|
43
|
+
-- account. No production flow writes people through the user-scoped client —
|
|
44
|
+
-- admin-data reads via ctx.db but writes via adminDb.
|
|
45
|
+
DROP POLICY IF EXISTS people_access ON public.people;
|
|
46
|
+
|
|
47
|
+
CREATE POLICY people_read ON public.people FOR SELECT
|
|
48
|
+
USING (
|
|
49
|
+
account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))
|
|
50
|
+
OR id = public.current_actor_id()
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE POLICY people_insert ON public.people FOR INSERT
|
|
54
|
+
WITH CHECK (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
55
|
+
|
|
56
|
+
CREATE POLICY people_update ON public.people FOR UPDATE
|
|
57
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())))
|
|
58
|
+
WITH CHECK (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
59
|
+
|
|
60
|
+
CREATE POLICY people_delete ON public.people FOR DELETE
|
|
61
|
+
USING (account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id())));
|
|
62
|
+
|
|
63
|
+
-- RLS is row-level only — it cannot stop a member from writing privileged
|
|
64
|
+
-- columns (role_id, account_id, is_active, schema stamps, identity anchors)
|
|
65
|
+
-- on their own row or a teammate's row inside the same accessible account.
|
|
66
|
+
-- This trigger supplies the missing column-level enforcement:
|
|
67
|
+
-- * service_role/adminDb writes have no actor → skipped
|
|
68
|
+
-- * system_admin → allowed
|
|
69
|
+
-- * self-update of privileged columns → denied
|
|
70
|
+
-- * privileged changes on other rows → admin-capable roles only
|
|
71
|
+
-- Profile fields (names, avatar, phone, status, data) remain self-editable.
|
|
72
|
+
CREATE OR REPLACE FUNCTION public.people_guard_privileged_fields()
|
|
73
|
+
RETURNS trigger
|
|
74
|
+
LANGUAGE plpgsql
|
|
75
|
+
AS $$
|
|
76
|
+
DECLARE
|
|
77
|
+
actor uuid := public.current_actor_id();
|
|
78
|
+
privileged_changed boolean;
|
|
79
|
+
actor_is_admin boolean;
|
|
80
|
+
BEGIN
|
|
81
|
+
IF actor IS NULL THEN
|
|
82
|
+
RETURN NEW;
|
|
83
|
+
END IF;
|
|
84
|
+
|
|
85
|
+
privileged_changed :=
|
|
86
|
+
NEW.role_id IS DISTINCT FROM OLD.role_id
|
|
87
|
+
OR NEW.account_id IS DISTINCT FROM OLD.account_id
|
|
88
|
+
OR NEW.is_active IS DISTINCT FROM OLD.is_active
|
|
89
|
+
OR NEW.auth_uid IS DISTINCT FROM OLD.auth_uid
|
|
90
|
+
OR NEW.app_id IS DISTINCT FROM OLD.app_id
|
|
91
|
+
OR NEW.type_id IS DISTINCT FROM OLD.type_id
|
|
92
|
+
OR NEW.design_schema IS DISTINCT FROM OLD.design_schema
|
|
93
|
+
OR NEW.validation_schema IS DISTINCT FROM OLD.validation_schema
|
|
94
|
+
OR NEW.created_by IS DISTINCT FROM OLD.created_by;
|
|
95
|
+
|
|
96
|
+
IF NOT privileged_changed THEN
|
|
97
|
+
RETURN NEW;
|
|
98
|
+
END IF;
|
|
99
|
+
|
|
100
|
+
IF public.person_is_system_admin(actor) THEN
|
|
101
|
+
RETURN NEW;
|
|
102
|
+
END IF;
|
|
103
|
+
|
|
104
|
+
IF NEW.id = actor THEN
|
|
105
|
+
RAISE EXCEPTION 'Cannot modify privileged fields on your own record';
|
|
106
|
+
END IF;
|
|
107
|
+
|
|
108
|
+
-- Only system admins may assign system roles (system_admin, csm, ...) —
|
|
109
|
+
-- otherwise a member_admin could mint a system admin by setting role_id.
|
|
110
|
+
IF NEW.role_id IS DISTINCT FROM OLD.role_id
|
|
111
|
+
AND EXISTS (SELECT 1 FROM public.roles r WHERE r.id = NEW.role_id AND r.is_system) THEN
|
|
112
|
+
RAISE EXCEPTION 'Only system admins may assign system roles';
|
|
113
|
+
END IF;
|
|
114
|
+
|
|
115
|
+
SELECT EXISTS (
|
|
116
|
+
SELECT 1
|
|
117
|
+
FROM public.people p
|
|
118
|
+
JOIN public.roles r ON r.id = p.role_id
|
|
119
|
+
WHERE p.id = actor
|
|
120
|
+
AND r.slug IN ('support_admin', 'member_admin')
|
|
121
|
+
) INTO actor_is_admin;
|
|
122
|
+
|
|
123
|
+
IF NOT actor_is_admin THEN
|
|
124
|
+
RAISE EXCEPTION 'Insufficient role to modify privileged people fields';
|
|
125
|
+
END IF;
|
|
126
|
+
|
|
127
|
+
RETURN NEW;
|
|
128
|
+
END;
|
|
129
|
+
$$;
|
|
130
|
+
|
|
131
|
+
DROP TRIGGER IF EXISTS people_privileged_fields_guard ON public.people;
|
|
132
|
+
CREATE TRIGGER people_privileged_fields_guard
|
|
133
|
+
BEFORE UPDATE ON public.people
|
|
134
|
+
FOR EACH ROW EXECUTE FUNCTION public.people_guard_privileged_fields();
|
|
135
|
+
-- CIL[DOC-VECTOR type="context" content="purpose='Close anonymous-access bypasses on system_settings/accounts/roles and the member self-write path on people.'; No verified external callers; imports: none"]
|
|
136
|
+
-- CIL[DOC-VECTOR type="blast_radius" content="Anonymous PostgREST access to system_settings/accounts/roles is revoked; non-system role writes restricted to system_admin; members can no longer PATCH their own people row directly — admin-data flows unaffected because writes go through adminDb" status="ready"]
|
|
137
|
+
-- CIL[DOC-VECTOR type="configuration" content="Depends on public.current_actor_id, public.person_is_system_admin, and public.get_accessible_accounts" status="ready"]
|
|
138
|
+
-- CIL[DOC-VECTOR type="constraint" content="Any flow relying on unauthenticated or direct-PostgREST self-writes must authenticate via a privileged path; service_role is unaffected" status="ready"]
|
|
139
|
+
-- CIL[END stable_id="~/.framework/migrations/045_security_critical_rls.sql:security_critical_rls:file"]
|