spine-framework 0.3.145 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +76 -0
- package/.framework/.spine-manifest.json +19 -0
- package/.framework/cli/commands/agents.ts +61 -33
- package/.framework/cli/commands/auth.ts +60 -28
- package/.framework/cli/commands/create-app.ts +212 -80
- package/.framework/cli/commands/dev.ts +184 -68
- package/.framework/cli/commands/doctor.ts +324 -152
- package/.framework/cli/commands/generate.ts +119 -68
- package/.framework/cli/commands/init.ts +141 -242
- package/.framework/cli/commands/install-app.ts +1336 -314
- package/.framework/cli/commands/items.ts +31 -29
- package/.framework/cli/commands/migrate.ts +299 -156
- package/.framework/cli/commands/migrations.ts +62 -30
- package/.framework/cli/commands/pipelines.ts +31 -29
- package/.framework/cli/commands/status.ts +105 -45
- package/.framework/cli/commands/system.ts +47 -42
- package/.framework/cli/commands/test.ts +161 -102
- package/.framework/cli/commands/upgrade.ts +260 -0
- package/.framework/cli/commands/validate-app.ts +517 -161
- package/.framework/cli/context.ts +117 -85
- package/.framework/cli/env-loader.ts +74 -43
- package/.framework/cli/index.ts +70 -46
- package/.framework/cli/lib/project-files.ts +404 -0
- package/.framework/docs/guides/testing-guide.md +1 -1
- package/.framework/docs/runtime-types.md +72 -72
- package/.framework/functions/_shared/access.ts +84 -0
- package/.framework/functions/_shared/agent-runner.ts +5067 -1026
- package/.framework/functions/_shared/app-manifest.ts +214 -94
- package/.framework/functions/_shared/ast.ts +214 -0
- package/.framework/functions/_shared/audit.ts +91 -55
- package/.framework/functions/_shared/cil.ts +368 -127
- package/.framework/functions/_shared/db.ts +337 -68
- package/.framework/functions/_shared/funnel.ts +228 -0
- package/.framework/functions/_shared/geo.ts +105 -0
- package/.framework/functions/_shared/hash-api-key.ts +94 -17
- package/.framework/functions/_shared/index.ts +22 -224
- package/.framework/functions/_shared/kb.ts +832 -58
- package/.framework/functions/_shared/llm.ts +118 -18
- package/.framework/functions/_shared/logging.ts +317 -19
- package/.framework/functions/_shared/middleware.ts +701 -131
- package/.framework/functions/_shared/notify.ts +70 -0
- package/.framework/functions/_shared/outcomes.ts +34 -19
- package/.framework/functions/_shared/permissions.ts +501 -315
- package/.framework/functions/_shared/pipeline-runner.ts +704 -155
- package/.framework/functions/_shared/principal.ts +436 -263
- package/.framework/functions/_shared/rate-limit.ts +233 -0
- package/.framework/functions/_shared/resolve-credentials.ts +86 -19
- package/.framework/functions/_shared/resolve-ids.ts +203 -139
- package/.framework/functions/_shared/retry-deliveries.ts +51 -16
- package/.framework/functions/_shared/schema-utils.ts +602 -521
- package/.framework/functions/_shared/security.ts +345 -0
- package/.framework/functions/_shared/testing.ts +197 -172
- package/.framework/functions/_shared/tracing.ts +351 -21
- package/.framework/functions/_shared/trigger-engine.ts +425 -136
- package/.framework/functions/_shared/webhook-registration.ts +71 -70
- package/.framework/functions/_shared/webhook-registry.ts +126 -83
- package/.framework/functions/account-nodes.ts +30 -30
- package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
- package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
- package/.framework/functions/adapters/openai-outbound.ts +15 -16
- package/.framework/functions/admin-data.ts +596 -173
- package/.framework/functions/admin-funnel.ts +64 -60
- package/.framework/functions/admin-install-funnel.ts +84 -66
- package/.framework/functions/admin-signals.ts +69 -51
- package/.framework/functions/ai-agents.ts +431 -106
- package/.framework/functions/ai-interactions.ts +186 -112
- package/.framework/functions/api-keys.ts +107 -107
- package/.framework/functions/apps.ts +154 -136
- package/.framework/functions/auth.ts +83 -65
- package/.framework/functions/cil-agent-ingest.ts +189 -232
- package/.framework/functions/cil-agent.ts +433 -177
- package/.framework/functions/cil-callmap.ts +642 -430
- package/.framework/functions/cil-config.ts +139 -135
- package/.framework/functions/cil-context.ts +214 -127
- package/.framework/functions/cil-doc.ts +587 -458
- package/.framework/functions/cil-embeddings.ts +453 -364
- package/.framework/functions/cil-graph.ts +427 -142
- package/.framework/functions/cil-ingest.ts +1418 -1154
- package/.framework/functions/cil-pipeline.ts +276 -21
- package/.framework/functions/cil-project.ts +659 -132
- package/.framework/functions/cil-validate.ts +198 -164
- package/.framework/functions/debug-auth.ts +57 -55
- package/.framework/functions/deliveries.ts +80 -21
- package/.framework/functions/embeddings.ts +1023 -318
- package/.framework/functions/eval-harness.ts +146 -108
- package/.framework/functions/integration-outbound.ts +205 -75
- package/.framework/functions/integration-routes.ts +128 -23
- package/.framework/functions/integrations.ts +125 -100
- package/.framework/functions/invites.ts +1095 -460
- package/.framework/functions/item-progress.ts +158 -102
- package/.framework/functions/kb.ts +34 -10
- package/.framework/functions/logs.ts +133 -133
- package/.framework/functions/observability.ts +152 -122
- package/.framework/functions/pipeline-executions.ts +147 -132
- package/.framework/functions/pipelines.ts +134 -119
- package/.framework/functions/prompt-configs.ts +96 -80
- package/.framework/functions/roles.ts +112 -82
- package/.framework/functions/security.ts +450 -0
- package/.framework/functions/system-cron.ts +117 -80
- package/.framework/functions/system.ts +158 -98
- package/.framework/functions/tests.ts +29 -29
- package/.framework/functions/timers.ts +94 -95
- package/.framework/functions/trigger-scheduler.ts +10 -0
- package/.framework/functions/triggers.ts +179 -149
- package/.framework/functions/types.ts +137 -125
- package/.framework/mcp/index.ts +133 -0
- package/.framework/mcp/load-env.ts +60 -0
- package/.framework/mcp/shared/app-manifest.ts +275 -0
- package/.framework/mcp/shared/db.ts +477 -0
- package/.framework/mcp/shared/kb.ts +118 -0
- package/.framework/mcp/tools/architect.ts +431 -0
- package/.framework/mcp/tools/cil/context.ts +345 -0
- package/.framework/mcp/tools/cil/graph.ts +242 -0
- package/.framework/mcp/tools/cil/ingest.ts +1217 -0
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
- package/.framework/mcp/tools/cil/pipeline.ts +767 -0
- package/.framework/mcp/tools/cil/project.ts +791 -0
- package/.framework/mcp/tools/cil/retrieval.ts +336 -0
- package/.framework/mcp/tools/cil/search-core.ts +454 -0
- package/.framework/mcp/tools/cil/search.ts +97 -0
- package/.framework/mcp/tools/cil/suggest.ts +186 -0
- package/.framework/mcp/tools/cil/validate.ts +1369 -0
- package/.framework/mcp/tools/kb/delete.ts +139 -0
- package/.framework/mcp/tools/kb/index.ts +52 -0
- package/.framework/mcp/tools/kb/ingest.ts +84 -0
- package/.framework/mcp/tools/kb/list-types.ts +102 -0
- package/.framework/mcp/tools/kb/publish.ts +100 -0
- package/.framework/mcp/tools/kb/reindex.ts +86 -0
- package/.framework/mcp/tools/kb/revoke.ts +85 -0
- package/.framework/mcp/tools/kb/search.ts +101 -0
- package/.framework/mcp/tools/kb/upsert.ts +125 -0
- package/.framework/mcp/tools/observability.ts +510 -0
- package/.framework/migrations/001_core_schema.sql +15 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
- package/.framework/migrations/003_core_system_data.sql +16 -1
- package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
- package/.framework/migrations/005_core_observability.sql +17 -1
- package/.framework/migrations/006_security_hardening_rls.sql +15 -0
- package/.framework/migrations/007_kb_article_list.sql +17 -1
- package/.framework/migrations/008_items_target.sql +15 -0
- package/.framework/migrations/009_roles_slug_unique.sql +15 -0
- package/.framework/migrations/010_auth_user_trigger.sql +15 -0
- package/.framework/migrations/011_spine_system_account.sql +16 -0
- package/.framework/migrations/012_service_role_grants.sql +16 -0
- package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
- package/.framework/migrations/014_apps_required_roles.sql +16 -0
- package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
- package/.framework/migrations/016_cil_types.sql +281 -0
- package/.framework/migrations/017_cil_stable_ids.sql +37 -0
- package/.framework/migrations/018_runtime_tables.sql +129 -0
- package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
- package/.framework/migrations/020_schema_reconciliation.sql +67 -0
- package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
- package/.framework/migrations/032_cil_status_migration.sql +73 -0
- package/.framework/migrations/033_cil_versioning.sql +73 -0
- package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
- package/.framework/migrations/035_rate_limits.sql +55 -0
- package/.framework/migrations/036_demo_record_flags.sql +72 -0
- package/.framework/migrations/037_kb_types_canonical.sql +244 -0
- package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
- package/.framework/migrations/042_registration_hierarchy.sql +309 -0
- package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
- package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
- package/.framework/migrations/045_security_critical_rls.sql +139 -0
- package/.framework/migrations/046_security_missing_rls.sql +79 -0
- package/.framework/migrations/047_people_updated_by.sql +27 -0
- package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
- package/.framework/migrations/049_security_blocklist.sql +148 -0
- package/.framework/migrations/050_notifications.sql +49 -0
- package/.framework/migrations/051_message_agent_fields.sql +74 -0
- package/.framework/migrations/052_registration_security.sql +421 -0
- package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
- package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
- package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
- package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
- package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
- package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
- package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
- package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
- package/.framework/migrations/061_fk_dedup.sql +32 -0
- package/.framework/public/spine-logo.svg +10 -3
- package/.framework/scripts/app-install-cli.ts +190 -93
- package/.framework/scripts/assemble-frontend.sh +127 -44
- package/.framework/scripts/assemble-functions.sh +128 -30
- package/.framework/scripts/boundary-check.sh +6 -2
- package/.framework/scripts/build-manifest.sh +24 -68
- package/.framework/scripts/check-core-integrity.sh +15 -76
- package/.framework/scripts/checksum-manifest.mts +343 -0
- package/.framework/scripts/cil-migrate-markers.ts +403 -0
- package/.framework/scripts/fix-cil-markers.mjs +176 -0
- package/.framework/scripts/verify-integrity.sh +21 -57
- package/.framework/scripts/verify-manifest.mts +305 -0
- package/.framework/seeds/types/funnel_signal_use.json +74 -21
- package/.framework/seeds/types/spine_instance.json +73 -21
- package/.framework/src/App.tsx +118 -72
- package/.framework/src/apps/admin/index.tsx +855 -58
- package/.framework/src/components/AppWrapper.tsx +43 -45
- package/.framework/src/components/CustomAppLoader.tsx +160 -116
- package/.framework/src/components/admin/AdminListPage.tsx +46 -44
- package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
- package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
- package/.framework/src/components/admin/PageInstructions.tsx +98 -2
- package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
- package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
- package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
- package/.framework/src/components/app-sidebar.tsx +42 -45
- package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
- package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
- package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
- package/.framework/src/components/kb/KBArticleList.tsx +251 -10
- package/.framework/src/components/layout/AppShell.tsx +51 -45
- package/.framework/src/components/layout/Header.tsx +42 -43
- package/.framework/src/components/layout/Layout.tsx +46 -46
- package/.framework/src/components/layout/Sidebar.tsx +196 -96
- package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
- package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
- package/.framework/src/components/runtime/DataFilters.tsx +49 -74
- package/.framework/src/components/runtime/DataHeader.tsx +53 -32
- package/.framework/src/components/runtime/DataListPage.tsx +51 -155
- package/.framework/src/components/runtime/DataStats.tsx +66 -44
- package/.framework/src/components/runtime/DataTable.tsx +49 -149
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
- package/.framework/src/components/runtime/index.ts +119 -16
- package/.framework/src/components/search-form.tsx +46 -29
- package/.framework/src/components/shared/AgentView.tsx +65 -48
- package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
- package/.framework/src/components/shared/InviteManager.tsx +207 -97
- package/.framework/src/components/shared/MemberList.tsx +97 -0
- package/.framework/src/components/shared/SchemaFields.tsx +107 -86
- package/.framework/src/components/ui/DataTable.tsx +80 -68
- package/.framework/src/components/ui/Form.tsx +157 -146
- package/.framework/src/components/ui/ItemCard.tsx +74 -67
- package/.framework/src/components/ui/ItemListView.tsx +73 -68
- package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
- package/.framework/src/components/ui/Modal.tsx +78 -68
- package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
- package/.framework/src/components/ui/accordion.tsx +85 -86
- package/.framework/src/components/ui/alert-dialog.tsx +197 -184
- package/.framework/src/components/ui/alert.tsx +105 -94
- package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
- package/.framework/src/components/ui/avatar.tsx +122 -107
- package/.framework/src/components/ui/badge.tsx +60 -55
- package/.framework/src/components/ui/breadcrumb.tsx +127 -128
- package/.framework/src/components/ui/button-group.tsx +86 -72
- package/.framework/src/components/ui/button.tsx +71 -50
- package/.framework/src/components/ui/calendar.tsx +72 -57
- package/.framework/src/components/ui/card.tsx +136 -129
- package/.framework/src/components/ui/carousel.tsx +226 -107
- package/.framework/src/components/ui/chart.tsx +252 -147
- package/.framework/src/components/ui/checkbox.tsx +43 -44
- package/.framework/src/components/ui/collapsible.tsx +86 -71
- package/.framework/src/components/ui/combobox.tsx +282 -249
- package/.framework/src/components/ui/command.tsx +158 -146
- package/.framework/src/components/ui/context-menu.tsx +239 -240
- package/.framework/src/components/ui/dialog.tsx +174 -157
- package/.framework/src/components/ui/direction.tsx +72 -56
- package/.framework/src/components/ui/drawer.tsx +168 -169
- package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
- package/.framework/src/components/ui/empty.tsx +131 -120
- package/.framework/src/components/ui/field.tsx +205 -173
- package/.framework/src/components/ui/hover-card.tsx +71 -72
- package/.framework/src/components/ui/input-group.tsx +148 -133
- package/.framework/src/components/ui/input-otp.tsx +99 -86
- package/.framework/src/components/ui/input.tsx +43 -41
- package/.framework/src/components/ui/item.tsx +207 -185
- package/.framework/src/components/ui/kbd.tsx +57 -56
- package/.framework/src/components/ui/label.tsx +45 -41
- package/.framework/src/components/ui/menubar.tsx +268 -236
- package/.framework/src/components/ui/native-select.tsx +85 -86
- package/.framework/src/components/ui/navigation-menu.tsx +166 -156
- package/.framework/src/components/ui/pagination.tsx +131 -141
- package/.framework/src/components/ui/popover.tsx +125 -128
- package/.framework/src/components/ui/progress.tsx +47 -44
- package/.framework/src/components/ui/radio-group.tsx +60 -55
- package/.framework/src/components/ui/resizable.tsx +85 -72
- package/.framework/src/components/ui/scroll-area.tsx +57 -58
- package/.framework/src/components/ui/select.tsx +182 -169
- package/.framework/src/components/ui/separator.tsx +42 -43
- package/.framework/src/components/ui/sheet.tsx +170 -159
- package/.framework/src/components/ui/sidebar.tsx +395 -398
- package/.framework/src/components/ui/skeleton.tsx +42 -43
- package/.framework/src/components/ui/slider.tsx +61 -44
- package/.framework/src/components/ui/sonner.tsx +44 -44
- package/.framework/src/components/ui/spinner.tsx +47 -42
- package/.framework/src/components/ui/switch.tsx +56 -42
- package/.framework/src/components/ui/table-primitives.tsx +272 -132
- package/.framework/src/components/ui/table.tsx +140 -142
- package/.framework/src/components/ui/tabs.tsx +110 -93
- package/.framework/src/components/ui/textarea.tsx +43 -44
- package/.framework/src/components/ui/toggle-group.tsx +87 -71
- package/.framework/src/components/ui/toggle.tsx +56 -56
- package/.framework/src/components/ui/tooltip.tsx +99 -85
- package/.framework/src/contexts/AppContext.tsx +166 -114
- package/.framework/src/contexts/AuthContext.tsx +232 -137
- package/.framework/src/hooks/use-mobile.ts +43 -44
- package/.framework/src/hooks/useApi.ts +185 -172
- package/.framework/src/hooks/useAppPath.ts +31 -29
- package/.framework/src/hooks/useApps.ts +77 -42
- package/.framework/src/hooks/useEntityList.ts +66 -29
- package/.framework/src/hooks/useEntityRecord.ts +76 -32
- package/.framework/src/hooks/useForm.ts +62 -30
- package/.framework/src/hooks/useListSchema.ts +60 -43
- package/.framework/src/hooks/useSchemaRecord.ts +115 -95
- package/.framework/src/lib/api.ts +258 -134
- package/.framework/src/lib/registration.ts +181 -0
- package/.framework/src/lib/supabase.ts +118 -81
- package/.framework/src/lib/utils.ts +266 -266
- package/.framework/src/main.tsx +43 -44
- package/.framework/src/pages/DashboardPage.tsx +44 -43
- package/.framework/src/pages/NoAppPage.tsx +50 -0
- package/.framework/src/pages/NotFoundPage.tsx +32 -29
- package/.framework/src/pages/SetupPage.tsx +34 -33
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
- package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
- package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
- package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
- package/.framework/src/pages/admin/AppsPage.tsx +45 -42
- package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
- package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
- package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
- package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
- package/.framework/src/pages/admin/LogsPage.tsx +87 -137
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
- package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
- package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
- package/.framework/src/pages/admin/RolesPage.tsx +47 -164
- package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
- package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
- package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
- package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
- package/.framework/src/pages/admin/TimersPage.tsx +44 -42
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
- package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
- package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
- package/.framework/src/pages/admin/TypesPage.tsx +45 -42
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
- package/.framework/src/pages/auth/LoginPage.tsx +38 -32
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
- package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
- package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
- package/.framework/src/types/auth.ts +101 -93
- package/.framework/src/types/types.ts +283 -262
- package/.framework/templates/project.json +87 -0
- package/.framework/templates/spine.config.neutral.json +7 -0
- package/.framework/tests/api/api-surface.test.ts +381 -0
- package/.framework/tests/fixtures/seed.ts +427 -0
- package/.framework/tests/fixtures/teardown.ts +228 -0
- package/.framework/tests/reporter.ts +351 -0
- package/.framework/tests/setup.ts +135 -0
- package/.framework/tests/ui/ui-sweep.ts +278 -0
- package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
- package/.framework/tests/unit/agent-runner.test.ts +2389 -0
- package/.framework/tests/unit/ai-interactions.test.ts +81 -0
- package/.framework/tests/unit/app-manifest.test.ts +359 -0
- package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
- package/.framework/tests/unit/cil-determinism.test.ts +85 -0
- package/.framework/tests/unit/core-isolation.test.ts +153 -0
- package/.framework/tests/unit/funnel-signals.test.ts +135 -0
- package/.framework/tests/unit/install-app.test.ts +1759 -0
- package/.framework/tests/unit/invites-security.test.ts +572 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
- package/.framework/tests/unit/kb-search.test.ts +119 -0
- package/.framework/tests/unit/migrate.test.ts +509 -0
- package/.framework/tests/unit/notify.test.ts +136 -0
- package/.framework/tests/unit/permissions.test.ts +269 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
- package/.framework/tests/unit/principal-cache.test.ts +138 -0
- package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
- package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
- package/.framework/tests/unit/schema-utils.test.ts +225 -0
- package/.framework/tests/unit/trigger-engine.test.ts +513 -0
- package/.framework/tests/unit/validate-app.test.ts +527 -0
- package/README.md +34 -3
- package/bin/spine-framework.cjs +1 -0
- package/config/tailwind.config.cjs +7 -0
- package/config/tsconfig.functions.json +42 -0
- package/config/tsconfig.json +37 -9
- package/config/vite.config.ts +31 -2
- package/netlify.toml +31 -3
- package/package.json +46 -29
- package/scripts/assemble-frontend.sh +127 -44
- package/scripts/assemble-functions.sh +127 -29
- package/scripts/boundary-check.sh +6 -2
- package/scripts/build-manifest.sh +24 -68
- package/scripts/checksum-manifest.mts +345 -0
- package/scripts/dev-clean.sh +86 -0
- package/scripts/dev-netlify.sh +49 -0
- package/scripts/netlify-dev-wrapper.sh +2 -0
- package/scripts/verify-install.ts +358 -0
- package/scripts/verify-integrity.sh +21 -57
- package/scripts/verify-manifest.mts +305 -0
- package/templates/project/gitignore +45 -0
- package/{config → templates/project}/package-lock.json +22335 -9366
- package/vitest.config.ts +46 -0
- package/.framework/public/spine.config.json +0 -91
- package/config/deno.lock +0 -108
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/vite.config.d.ts +0 -2
- package/custom/.devin/AGENT.md +0 -357
- package/custom/.devin/skills/install-app/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
- package/custom/.devin/workflows/app-install.yaml +0 -83
- package/custom/README.md +0 -79
- package/docs/README.md +0 -15
- package/scripts/app-install-cli.ts +0 -286
- package/scripts/check-core-integrity.sh +0 -82
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/kb-ingest-docs.ts +0 -203
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/test-login.js +0 -33
- package/spine.config.json +0 -91
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/_shared/principal.ts"]
|
|
2
|
+
// CIL[DOC-MACRO purpose="Shared principal and authorization module for Spine functions"]
|
|
3
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Defines the Principal model, resolves request principals from headers and tokens, and provides role/scope predicates"]
|
|
4
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts"]
|
|
5
|
+
// CIL[DOC-CONTRACT inputs="HTTP event headers and tokens" outputs="Principal objects and authorization booleans" errors="Invalid or inactive credentials, rate limit exceeded, lookup errors" side_effects="Writes API key usage logs; updates people.auth_uid when linking by email"]
|
|
6
|
+
// CIL[DOC-MICRO logic_summary="Imports shared db/supabase clients and crypto; declares the Principal interface and anonymous/system constants; exports resolvers and helper functions" notable_decisions="[]"]
|
|
7
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
8
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
// CIL[DOC-AUDIENCE audience="framework developers, ai agents, security reviewers"]
|
|
10
|
+
|
|
11
|
+
|
|
1
12
|
|
|
2
13
|
|
|
3
14
|
|
|
@@ -14,36 +25,26 @@
|
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
|
|
17
|
-
// CIL[BEGIN chunk_id="principal_imports_1784020784" type="module_header" identity="imports" parent="none"]
|
|
18
|
-
// CIL[DOC-MACRO purpose="Import database, auth client, and WebSocket dependencies"]
|
|
19
|
-
// CIL[DOC-RESPONSIBILITY responsibility="Import required modules for principal resolution"]
|
|
20
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
|
|
21
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="database, auth client, WebSocket" errors="none" side_effects="none"]
|
|
22
|
-
// CIL[DOC-MICRO logic_summary="Import database connections, Supabase client, and WebSocket" notable_decisions="[]"]
|
|
23
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
24
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
25
|
-
// CIL[DOC-AUDIENCE audience="developer"]
|
|
26
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
|
|
27
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
28
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
29
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
30
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
31
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
32
28
|
import { getUserDb, adminDb } from './db'
|
|
33
29
|
import { createClient } from '@supabase/supabase-js'
|
|
34
30
|
import WebSocket from 'ws'
|
|
35
|
-
import { timingSafeEqual } from 'crypto'
|
|
36
|
-
// CIL[
|
|
37
|
-
// CIL[
|
|
38
|
-
// CIL[DOC-
|
|
39
|
-
// CIL[DOC-
|
|
40
|
-
// CIL[DOC-
|
|
41
|
-
// CIL[DOC-
|
|
42
|
-
// CIL[
|
|
43
|
-
|
|
31
|
+
import { timingSafeEqual, createHash } from 'crypto'
|
|
32
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Shared principal and authorization module for Spine functions'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
33
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
34
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
35
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
36
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
37
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
38
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:module_header:module_header"]
|
|
39
|
+
|
|
40
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:isInternalInvokeAuthorized:function" type="function" identity="isInternalInvokeAuthorized" parent="~/.framework/functions/_shared/principal.ts"]
|
|
41
|
+
// CIL[DOC-MACRO purpose="Verify that a request carries the trusted internal invocation secret"]
|
|
42
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate shared secret header using timing-safe comparison"]
|
|
43
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
44
|
+
// CIL[DOC-CONTRACT inputs="event object with headers" outputs="boolean" errors="none (returns false on any failure)" side_effects="none"]
|
|
45
|
+
// CIL[DOC-MICRO logic_summary="Compare x-internal-secret against INTERNAL_INVOKE_SECRET using timingSafeEqual" notable_decisions="[]"]
|
|
46
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
44
47
|
// CIL[DOC-STABILITY stability="stable"]
|
|
45
|
-
// CIL[DOC-AUDIENCE audience="developer"]
|
|
46
|
-
// CIL[DOC-NOTE type="caution" content="INTERNAL_INVOKE_SECRET must be set wherever the framework itself issues x-cron-id/x-trigger-id headers (currently no code path does), or those callers will silently fall back to ANONYMOUS_PRINCIPAL."]
|
|
47
48
|
function isInternalInvokeAuthorized(event: any): boolean {
|
|
48
49
|
const expected = (globalThis as any).process?.env?.INTERNAL_INVOKE_SECRET
|
|
49
50
|
if (!expected) return false
|
|
@@ -51,32 +52,56 @@ function isInternalInvokeAuthorized(event: any): boolean {
|
|
|
51
52
|
const provided = event.headers?.['x-internal-secret'] || event.headers?.['X-Internal-Secret']
|
|
52
53
|
if (!provided || typeof provided !== 'string') return false
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Hash both sides so the comparison always runs on equal-length (32-byte)
|
|
56
|
+
// inputs — an early-exit on length mismatch would leak the expected secret's
|
|
57
|
+
// length via response timing.
|
|
58
|
+
const expectedHash = createHash('sha256').update(expected).digest()
|
|
59
|
+
const providedHash = createHash('sha256').update(provided).digest()
|
|
57
60
|
|
|
58
61
|
try {
|
|
59
|
-
return timingSafeEqual(
|
|
62
|
+
return timingSafeEqual(expectedHash, providedHash)
|
|
60
63
|
} catch {
|
|
61
64
|
return false
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
|
-
// CIL[
|
|
65
|
-
// CIL[
|
|
66
|
-
// CIL[DOC-
|
|
67
|
-
// CIL[DOC-
|
|
68
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/layout/Sidebar.tsx\",\"\\\"]"]
|
|
69
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="supabase client" errors="none" side_effects="none"]
|
|
70
|
-
// CIL[DOC-MICRO logic_summary="Configures Supabase client with auth and realtime settings" notable_decisions="[]"]
|
|
71
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
72
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
73
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/layout/Sidebar.tsx, \."]
|
|
74
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
75
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
76
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
67
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Verify that a request carries the trusted internal invocation secret'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
68
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
69
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
70
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
77
71
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
78
72
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
73
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:isInternalInvokeAuthorized:function"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:env:constant" type="constant" identity="env" parent="~/.framework/functions/_shared/principal.ts"]
|
|
78
|
+
// CIL[DOC-MACRO purpose="Access process environment variables"]
|
|
79
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provide a safe fallback for environment lookups"]
|
|
80
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
81
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Record of environment variables" errors="none" side_effects="none"]
|
|
82
|
+
// CIL[DOC-MICRO logic_summary="Return global process.env or an empty object" notable_decisions="[]"]
|
|
83
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
84
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
79
85
|
const env = (globalThis as any).process?.env || {}
|
|
86
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Access process environment variables'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
87
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
88
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
89
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
90
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
91
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
92
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:env:constant"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:supabase:constant" type="constant" identity="supabase" parent="~/.framework/functions/_shared/principal.ts"]
|
|
98
|
+
// CIL[DOC-MACRO purpose="Create a Supabase client configured for function use"]
|
|
99
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provide an authenticated Supabase client with realtime transport"]
|
|
100
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
101
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Supabase client" errors="none" side_effects="none"]
|
|
102
|
+
// CIL[DOC-MICRO logic_summary="Instantiate createClient with URL, anon key and auth/realtime options" notable_decisions="[]"]
|
|
103
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
104
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
80
105
|
const supabase = createClient(
|
|
81
106
|
env.SUPABASE_URL || 'https://placeholder.supabase.co',
|
|
82
107
|
env.SUPABASE_ANON_KEY || 'placeholder',
|
|
@@ -84,22 +109,26 @@ const supabase = createClient(
|
|
|
84
109
|
auth: { autoRefreshToken: false, persistSession: false },
|
|
85
110
|
realtime: { transport: WebSocket as any }
|
|
86
111
|
}
|
|
112
|
+
|
|
113
|
+
|
|
87
114
|
)
|
|
88
|
-
// CIL[
|
|
89
|
-
// CIL[
|
|
90
|
-
// CIL[DOC-
|
|
91
|
-
// CIL[DOC-
|
|
92
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/components/layout/Sidebar.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"\\\"]"]
|
|
93
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="Principal interface" errors="none" side_effects="none"]
|
|
94
|
-
// CIL[DOC-MICRO logic_summary="Interface with identity, permissions, provenance, and auth context" notable_decisions="[]"]
|
|
95
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
96
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
97
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, .framework/src/components/layout/Sidebar.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, \."]
|
|
98
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
99
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
100
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
115
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Create a Supabase client configured for function use'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
116
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
117
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
118
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
101
119
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
102
120
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
121
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:supabase:constant"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:Principal:interface" type="interface" identity="Principal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
125
|
+
// CIL[DOC-MACRO purpose="Define the canonical identity shape used across Spine"]
|
|
126
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Describe a principal's id, type, account, roles, scopes, machine metadata and provenance"]
|
|
127
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
128
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="TypeScript interface" errors="none" side_effects="none"]
|
|
129
|
+
// CIL[DOC-MICRO logic_summary="Declare human or machine identity with provenance and optional auth context" notable_decisions="[]"]
|
|
130
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
131
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
103
132
|
export interface Principal {
|
|
104
133
|
id: string
|
|
105
134
|
type: 'human' | 'machine'
|
|
@@ -123,26 +152,36 @@ export interface Principal {
|
|
|
123
152
|
ipAddress?: string
|
|
124
153
|
userAgent?: string
|
|
125
154
|
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
// SECURITY: authContext carries raw credentials needed to build the
|
|
158
|
+
// user-scoped DB client (getPrincipalDb). It is transport-only — never pass
|
|
159
|
+
// a Principal to JSON.stringify, emitAudit, or any logging path. Use
|
|
160
|
+
// formatPrincipalForAudit() for serialized views. apiKey is deliberately
|
|
161
|
+
// absent: raw API keys are verified and discarded during resolution and must
|
|
162
|
+
// never be stored on objects that may be cached or serialized.
|
|
126
163
|
authContext?: {
|
|
127
164
|
jwt?: string
|
|
128
|
-
apiKey?: string
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
|
-
// CIL[
|
|
132
|
-
// CIL[
|
|
133
|
-
// CIL[DOC-
|
|
134
|
-
// CIL[DOC-
|
|
135
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[\".framework/src/components/layout/Sidebar.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\"\\\"]"]
|
|
136
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="ANONYMOUS_PRINCIPAL constant" errors="none" side_effects="none"]
|
|
137
|
-
// CIL[DOC-MICRO logic_summary="Machine principal with no permissions and manual provenance" notable_decisions="[]"]
|
|
138
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
139
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
140
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/src/components/layout/Sidebar.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/IntegrationsPage.tsx, \."]
|
|
141
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
142
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
143
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
167
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Define the canonical identity shape used across Spine'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
168
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
169
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
170
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
144
171
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
145
172
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
173
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:Principal:interface"]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:ANONYMOUS_PRINCIPAL:constant" type="constant" identity="ANONYMOUS_PRINCIPAL" parent="~/.framework/functions/_shared/principal.ts"]
|
|
178
|
+
// CIL[DOC-MACRO purpose="Provide a default principal for unauthenticated requests"]
|
|
179
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Represent an anonymous machine principal with no scopes"]
|
|
180
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
181
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="none" side_effects="none"]
|
|
182
|
+
// CIL[DOC-MICRO logic_summary="Return an anonymous machine principal with manual provenance" notable_decisions="[]"]
|
|
183
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
184
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
146
185
|
export const ANONYMOUS_PRINCIPAL: Principal = {
|
|
147
186
|
id: 'anonymous',
|
|
148
187
|
type: 'machine',
|
|
@@ -154,21 +193,27 @@ export const ANONYMOUS_PRINCIPAL: Principal = {
|
|
|
154
193
|
invokedAt: new Date().toISOString()
|
|
155
194
|
}
|
|
156
195
|
}
|
|
157
|
-
// CIL[
|
|
158
|
-
// CIL[
|
|
159
|
-
// CIL[DOC-
|
|
160
|
-
// CIL[DOC-
|
|
161
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/layout/Sidebar.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\"]"]
|
|
162
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="SYSTEM_PRINCIPAL constant" errors="none" side_effects="none"]
|
|
163
|
-
// CIL[DOC-MICRO logic_summary="Internal machine principal with full permissions" notable_decisions="[]"]
|
|
164
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
165
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
166
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/layout/Sidebar.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx."]
|
|
167
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
168
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
169
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
196
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Provide a default principal for unauthenticated requests'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
197
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
198
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
199
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
170
200
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
171
201
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
202
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:ANONYMOUS_PRINCIPAL:constant"]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:SYSTEM_PRINCIPAL:constant" type="constant" identity="SYSTEM_PRINCIPAL" parent="~/.framework/functions/_shared/principal.ts"]
|
|
210
|
+
// CIL[DOC-MACRO purpose="Provide a privileged internal principal for system operations"]
|
|
211
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Represent the system machine principal with full scope"]
|
|
212
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
213
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="none" side_effects="none"]
|
|
214
|
+
// CIL[DOC-MICRO logic_summary="Return a machine principal with wildcard scope and internal flag" notable_decisions="[]"]
|
|
215
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
216
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
172
217
|
export const SYSTEM_PRINCIPAL: Principal = {
|
|
173
218
|
id: 'system',
|
|
174
219
|
type: 'machine',
|
|
@@ -182,21 +227,26 @@ export const SYSTEM_PRINCIPAL: Principal = {
|
|
|
182
227
|
invokedAt: new Date().toISOString()
|
|
183
228
|
}
|
|
184
229
|
}
|
|
185
|
-
// CIL[
|
|
186
|
-
// CIL[
|
|
187
|
-
// CIL[DOC-
|
|
188
|
-
// CIL[DOC-
|
|
189
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
190
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="authentication failures" side_effects="none"]
|
|
191
|
-
// CIL[DOC-MICRO logic_summary="Checks API key, then (only if isInternalInvokeAuthorized) cron/trigger, then JWT auth in priority order" notable_decisions="[\"cron/trigger headers require INTERNAL_INVOKE_SECRET to be honored, closing a header-spoofing auth bypass\"]"]
|
|
192
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
193
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
194
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
195
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
196
|
-
// CIL[DOC-VECTOR type="configuration" content="Requires INTERNAL_INVOKE_SECRET env var for cron/trigger principal resolution."]
|
|
197
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
230
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Provide a privileged internal principal for system operations'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
231
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
232
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
233
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
198
234
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
199
235
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
236
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:SYSTEM_PRINCIPAL:constant"]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolvePrincipal:function" type="function" identity="resolvePrincipal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
243
|
+
// CIL[DOC-MACRO purpose="Resolve a Principal from incoming request headers"]
|
|
244
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Dispatch to machine, cron, trigger or human resolution based on headers"]
|
|
245
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
246
|
+
// CIL[DOC-CONTRACT inputs="event object with headers" outputs="Principal" errors="Invalid or inactive credentials" side_effects="none"]
|
|
247
|
+
// CIL[DOC-MICRO logic_summary="Check x-api-key, internal cron/trigger headers, or Bearer token, falling back to ANONYMOUS_PRINCIPAL" notable_decisions="[]"]
|
|
248
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
249
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
200
250
|
export async function resolvePrincipal(event: any): Promise<Principal> {
|
|
201
251
|
const apiKey = event.headers?.['x-api-key'] || event.headers?.['X-Api-Key']
|
|
202
252
|
if (apiKey) {
|
|
@@ -228,21 +278,22 @@ export async function resolvePrincipal(event: any): Promise<Principal> {
|
|
|
228
278
|
|
|
229
279
|
return ANONYMOUS_PRINCIPAL
|
|
230
280
|
}
|
|
231
|
-
// CIL[
|
|
232
|
-
// CIL[
|
|
233
|
-
// CIL[DOC-
|
|
234
|
-
// CIL[DOC-
|
|
235
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
236
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="invalid API key" side_effects="database query"]
|
|
237
|
-
// CIL[DOC-MICRO logic_summary="Validates machine principal via RPC and builds principal object" notable_decisions="[]"]
|
|
238
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
239
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
240
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
241
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
242
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
243
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
281
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve a Principal from incoming request headers'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
282
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
283
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
284
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
244
285
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
245
286
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
287
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolvePrincipal:function"]
|
|
288
|
+
|
|
289
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolveMachinePrincipal:function" type="function" identity="resolveMachinePrincipal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
290
|
+
// CIL[DOC-MACRO purpose="Resolve a machine principal from an API key"]
|
|
291
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate key prefix and hash, enforce rate limits, and return the principal"]
|
|
292
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
293
|
+
// CIL[DOC-CONTRACT inputs="apiKey: string, event: any" outputs="Principal" errors="Invalid or inactive machine principal, rate limit exceeded" side_effects="Inserts api_key_usage_logs row"]
|
|
294
|
+
// CIL[DOC-MICRO logic_summary="Query api_keys by prefix, verify hash, count recent usage, log request, and build the principal" notable_decisions="[]"]
|
|
295
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
296
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
246
297
|
async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Principal> {
|
|
247
298
|
const keyPrefix = apiKey.slice(0, 8)
|
|
248
299
|
|
|
@@ -251,14 +302,18 @@ async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Prin
|
|
|
251
302
|
.select('*')
|
|
252
303
|
.eq('key_prefix', keyPrefix)
|
|
253
304
|
.eq('is_active', true)
|
|
254
|
-
.
|
|
255
|
-
.limit(
|
|
305
|
+
.order('created_at', { ascending: false })
|
|
306
|
+
.limit(10)
|
|
256
307
|
|
|
257
308
|
if (error || !rows || rows.length === 0) {
|
|
258
309
|
throw new Error('Invalid or inactive machine principal')
|
|
259
310
|
}
|
|
260
311
|
|
|
261
|
-
const
|
|
312
|
+
const now = Date.now()
|
|
313
|
+
const key = rows.find((k: any) => !k.expires_at || new Date(k.expires_at).getTime() >= now)
|
|
314
|
+
if (!key) {
|
|
315
|
+
throw new Error('Invalid or inactive machine principal')
|
|
316
|
+
}
|
|
262
317
|
const { verifyApiKey } = await import('./hash-api-key')
|
|
263
318
|
if (!verifyApiKey(apiKey, key.key_hash)) {
|
|
264
319
|
throw new Error('Invalid or inactive machine principal')
|
|
@@ -278,6 +333,32 @@ async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Prin
|
|
|
278
333
|
throw new Error('Rate limit exceeded for this API key')
|
|
279
334
|
}
|
|
280
335
|
|
|
336
|
+
// Secondary per-IP ceiling: the per-key limit alone cannot catch a caller
|
|
337
|
+
// rotating through many valid keys. rate_limit_hit (migration 035) is the
|
|
338
|
+
// atomic DB counter — key on the trusted client IP + endpoint so expensive
|
|
339
|
+
// endpoints can be tuned separately. Fail-open: a limiter outage must not
|
|
340
|
+
// take down machine authentication.
|
|
341
|
+
const clientIp = getClientIp(event)
|
|
342
|
+
if (clientIp) {
|
|
343
|
+
const ipWindowSeconds = 60
|
|
344
|
+
const ipLimit = Number(env.SPINE_IP_RATE_LIMIT || 300)
|
|
345
|
+
const ipWindowStart = new Date(
|
|
346
|
+
Math.floor(now / (ipWindowSeconds * 1000)) * ipWindowSeconds * 1000
|
|
347
|
+
).toISOString()
|
|
348
|
+
try {
|
|
349
|
+
const { data: ipCount } = await adminDb.rpc('rate_limit_hit', {
|
|
350
|
+
p_key: `ip:${clientIp}:${event.path || '/'}`,
|
|
351
|
+
p_window_start: ipWindowStart,
|
|
352
|
+
})
|
|
353
|
+
if (typeof ipCount === 'number' && ipCount > ipLimit) {
|
|
354
|
+
throw new Error('Rate limit exceeded for this address')
|
|
355
|
+
}
|
|
356
|
+
} catch (err: any) {
|
|
357
|
+
if (err?.message === 'Rate limit exceeded for this address') throw err
|
|
358
|
+
console.warn('IP rate limit check failed (fail-open):', err?.message)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
281
362
|
await adminDb.from('api_key_usage_logs').insert({
|
|
282
363
|
api_key_id: key.id,
|
|
283
364
|
account_id: key.account_id,
|
|
@@ -289,11 +370,14 @@ async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Prin
|
|
|
289
370
|
metadata: { source: 'resolve_machine_principal' }
|
|
290
371
|
})
|
|
291
372
|
|
|
373
|
+
const isGlobal = (key.scopes || []).includes('*:*')
|
|
374
|
+
|
|
292
375
|
return {
|
|
293
376
|
id: key.id,
|
|
294
377
|
type: 'machine',
|
|
295
378
|
accountId: key.account_id,
|
|
296
379
|
scopes: key.scopes || [],
|
|
380
|
+
roles: isGlobal ? ['system_admin'] : [],
|
|
297
381
|
machineType: (key.machine_type as any) || 'integration',
|
|
298
382
|
isInternal: key.is_internal,
|
|
299
383
|
provenance: {
|
|
@@ -303,27 +387,30 @@ async function resolveMachinePrincipal(apiKey: string, event: any): Promise<Prin
|
|
|
303
387
|
apiKeyId: key.id,
|
|
304
388
|
ipAddress: getClientIp(event),
|
|
305
389
|
userAgent: event.headers?.['user-agent'] || event.headers?.['User-Agent']
|
|
306
|
-
}
|
|
307
|
-
|
|
390
|
+
}
|
|
391
|
+
// The raw API key is intentionally NOT stored on the principal — the key is
|
|
392
|
+
// verified above and discarded. getPrincipalDb() returns adminDb for machine
|
|
393
|
+
// principals regardless, so no downstream consumer needs the credential.
|
|
308
394
|
}
|
|
309
395
|
}
|
|
396
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve a machine principal from an API key'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
397
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
398
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
399
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
400
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
401
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
402
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolveMachinePrincipal:function"]
|
|
403
|
+
|
|
310
404
|
|
|
311
405
|
|
|
312
|
-
// CIL[
|
|
313
|
-
// CIL[
|
|
314
|
-
// CIL[DOC-
|
|
315
|
-
// CIL[DOC-
|
|
316
|
-
// CIL[DOC-
|
|
317
|
-
// CIL[DOC-
|
|
318
|
-
// CIL[DOC-
|
|
319
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
406
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolveCronPrincipal:function" type="function" identity="resolveCronPrincipal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
407
|
+
// CIL[DOC-MACRO purpose="Resolve a machine principal for a cron schedule"]
|
|
408
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Lookup a schedule, validate its creator, and return its associated machine principal"]
|
|
409
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
410
|
+
// CIL[DOC-CONTRACT inputs="scheduleId string" outputs="Principal" errors="Invalid or inactive schedule, validation failed" side_effects="none"]
|
|
411
|
+
// CIL[DOC-MICRO logic_summary="Fetch schedule with machine join, call validate_schedule_creator RPC, and build principal with cron provenance" notable_decisions="[]"]
|
|
412
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
320
413
|
// CIL[DOC-STABILITY stability="stable"]
|
|
321
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
322
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
323
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
324
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
325
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
326
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
327
414
|
async function resolveCronPrincipal(scheduleId: string): Promise<Principal> {
|
|
328
415
|
const { data: schedule, error: scheduleError } = await adminDb
|
|
329
416
|
.from('schedules')
|
|
@@ -363,21 +450,22 @@ async function resolveCronPrincipal(scheduleId: string): Promise<Principal> {
|
|
|
363
450
|
}
|
|
364
451
|
}
|
|
365
452
|
}
|
|
366
|
-
// CIL[
|
|
367
|
-
// CIL[
|
|
368
|
-
// CIL[DOC-
|
|
369
|
-
// CIL[DOC-
|
|
370
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
371
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="invalid trigger" side_effects="database query"]
|
|
372
|
-
// CIL[DOC-MICRO logic_summary="Validates trigger, finds action's machine principal, returns principal" notable_decisions="[]"]
|
|
373
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
374
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
375
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
376
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
377
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
378
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
453
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve a machine principal for a cron schedule'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
454
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
455
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
456
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
379
457
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
380
458
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
459
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolveCronPrincipal:function"]
|
|
460
|
+
|
|
461
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolveTriggerPrincipal:function" type="function" identity="resolveTriggerPrincipal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
462
|
+
// CIL[DOC-MACRO purpose="Resolve a machine principal for a trigger invocation"]
|
|
463
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Load trigger, find its action's default machine, and return the principal"]
|
|
464
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
465
|
+
// CIL[DOC-CONTRACT inputs="triggerId: string, event: any" outputs="Principal" errors="Invalid trigger, no machine configured" side_effects="none"]
|
|
466
|
+
// CIL[DOC-MICRO logic_summary="Fetch trigger with action join, resolve default machine from api_keys, and build trigger provenance" notable_decisions="[]"]
|
|
467
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
468
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
381
469
|
async function resolveTriggerPrincipal(triggerId: string, event: any): Promise<Principal> {
|
|
382
470
|
const { data: trigger, error: triggerError } = await adminDb
|
|
383
471
|
.from('triggers')
|
|
@@ -423,22 +511,57 @@ async function resolveTriggerPrincipal(triggerId: string, event: any): Promise<P
|
|
|
423
511
|
}
|
|
424
512
|
}
|
|
425
513
|
}
|
|
426
|
-
// CIL[
|
|
427
|
-
// CIL[
|
|
428
|
-
// CIL[DOC-
|
|
429
|
-
// CIL[DOC-
|
|
430
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
431
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="Principal" errors="invalid token" side_effects="database query"]
|
|
432
|
-
// CIL[DOC-MICRO logic_summary="Validates JWT, finds person record, builds principal with role" notable_decisions="[]"]
|
|
433
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
434
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
435
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
436
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
437
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
438
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
514
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve a machine principal for a trigger invocation'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
515
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
516
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
517
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
439
518
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
440
519
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
520
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolveTriggerPrincipal:function"]
|
|
521
|
+
|
|
522
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolveHumanPrincipal:function" type="function" identity="resolveHumanPrincipal" parent="~/.framework/functions/_shared/principal.ts"]
|
|
523
|
+
// CIL[DOC-MACRO purpose="Resolve a human principal from a JWT bearer token"]
|
|
524
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate Supabase user, map to internal person, and build the principal"]
|
|
525
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
526
|
+
// CIL[DOC-CONTRACT inputs="token: string, event: any" outputs="Principal" errors="Person lookup error, person not found" side_effects="none"]
|
|
527
|
+
// CIL[DOC-MICRO logic_summary="Get Supabase user, resolve internal person id, load person with role, and construct human principal; result cached ~60s keyed by token hash, provenance refreshed on hit; principals lacking both account_id and role_id are never cached" notable_decisions="cache avoids three sequential auth/people lookups on every polled request; provenance timestamps are refreshed per request so audit stays honest; unplaced (mid-registration, both fields null) principals skip the cache so placement lands on the next request instead of stalling 60s"]
|
|
528
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
529
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
530
|
+
// Module-level cache: sha256(token) → resolved human principal. Resolution
|
|
531
|
+
// costs 3 sequential round-trips (supabase.auth.getUser, person-id mapping,
|
|
532
|
+
// people+role join) and runs on every request — polling-heavy clients were
|
|
533
|
+
// paying ~1s of auth overhead per poll. 60s TTL bounds staleness for
|
|
534
|
+
// role/account changes. The authContext.jwt in the cached entry is only ever
|
|
535
|
+
// served back to the request that presented the same token. Principals for
|
|
536
|
+
// people missing BOTH account_id and role_id (registration placement still
|
|
537
|
+
// in flight) are never cached — see the comment at the cache write.
|
|
538
|
+
//
|
|
539
|
+
// SECURITY NOTE: role revocations, account deactivations, and account
|
|
540
|
+
// reassignments take up to 60 seconds to propagate to in-flight requests on
|
|
541
|
+
// warm function instances. This is a deliberate performance trade-off. Incident
|
|
542
|
+
// response: a full function redeploy clears the in-memory cache immediately. A
|
|
543
|
+
// DB-backed invalidation channel (e.g. a version counter on the people row)
|
|
544
|
+
// would close the window but costs a round-trip per cached hit.
|
|
545
|
+
const humanPrincipalCache = new Map<string, { principal: Principal; expiresAt: number }>()
|
|
546
|
+
const PRINCIPAL_CACHE_TTL_MS = 60_000
|
|
547
|
+
const PRINCIPAL_CACHE_MAX = 500
|
|
548
|
+
|
|
441
549
|
async function resolveHumanPrincipal(token: string, event: any): Promise<Principal> {
|
|
550
|
+
const cacheKey = createHash('sha256').update(token).digest('hex')
|
|
551
|
+
const cached = humanPrincipalCache.get(cacheKey)
|
|
552
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
553
|
+
// Refresh request-scoped provenance so audit/log rows keep honest metadata
|
|
554
|
+
return {
|
|
555
|
+
...cached.principal,
|
|
556
|
+
provenance: {
|
|
557
|
+
...cached.principal.provenance,
|
|
558
|
+
invokedAt: new Date().toISOString(),
|
|
559
|
+
ipAddress: getClientIp(event),
|
|
560
|
+
userAgent: event.headers?.['user-agent'] || event.headers?.['User-Agent']
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
442
565
|
const { data: { user }, error } = await supabase.auth.getUser(token)
|
|
443
566
|
|
|
444
567
|
if (error || !user) {
|
|
@@ -464,7 +587,7 @@ async function resolveHumanPrincipal(token: string, event: any): Promise<Princip
|
|
|
464
587
|
const role = Array.isArray(person.role) ? person.role[0] : person.role
|
|
465
588
|
const roleSlugs = role?.slug ? [role.slug] : []
|
|
466
589
|
|
|
467
|
-
|
|
590
|
+
const principal: Principal = {
|
|
468
591
|
id: personId,
|
|
469
592
|
type: 'human',
|
|
470
593
|
accountId: person.account_id || null,
|
|
@@ -480,32 +603,70 @@ async function resolveHumanPrincipal(token: string, event: any): Promise<Princip
|
|
|
480
603
|
},
|
|
481
604
|
authContext: { jwt: token }
|
|
482
605
|
}
|
|
606
|
+
|
|
607
|
+
// Never cache an unplaced principal. Between signUp (people row created)
|
|
608
|
+
// and complete_registration_placement (account_id + role_id stamped
|
|
609
|
+
// atomically), requests would otherwise pin accountId:null for the full
|
|
610
|
+
// TTL — the documented ~60s "Account context required" stall. Both-null
|
|
611
|
+
// is the exact signature of "placement hasn't run yet" — a contact with
|
|
612
|
+
// an account but no role is a valid steady state and still caches.
|
|
613
|
+
if (!person.account_id && !person.role_id) {
|
|
614
|
+
return principal
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (humanPrincipalCache.size >= PRINCIPAL_CACHE_MAX) {
|
|
618
|
+
for (const [key, entry] of humanPrincipalCache) {
|
|
619
|
+
if (entry.expiresAt <= Date.now()) humanPrincipalCache.delete(key)
|
|
620
|
+
}
|
|
621
|
+
if (humanPrincipalCache.size >= PRINCIPAL_CACHE_MAX) humanPrincipalCache.clear()
|
|
622
|
+
}
|
|
623
|
+
humanPrincipalCache.set(cacheKey, { principal, expiresAt: Date.now() + PRINCIPAL_CACHE_TTL_MS })
|
|
624
|
+
|
|
625
|
+
return principal
|
|
483
626
|
}
|
|
484
|
-
// CIL[
|
|
485
|
-
// CIL[
|
|
486
|
-
// CIL[DOC-
|
|
487
|
-
// CIL[DOC-
|
|
488
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
489
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="internal person ID" errors="none" side_effects="database update"]
|
|
490
|
-
// CIL[DOC-MICRO logic_summary="Looks up by email first, then auth ID, updates auth_uid if missing" notable_decisions="[]"]
|
|
491
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
492
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
493
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
494
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
495
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
496
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
627
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Resolve a human principal from a JWT bearer token'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
628
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
629
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
630
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
497
631
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
498
632
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
499
|
-
|
|
633
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolveHumanPrincipal:function"]
|
|
634
|
+
|
|
635
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:resolveInternalPersonId:function" type="function" identity="resolveInternalPersonId" parent="~/.framework/functions/_shared/principal.ts"]
|
|
636
|
+
// CIL[DOC-MACRO purpose="Map a Supabase auth user to an internal person id"]
|
|
637
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Link auth user to person by email or auth_uid, falling back to the auth id"]
|
|
638
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
639
|
+
// CIL[DOC-CONTRACT inputs="authUserId: string, email?: string" outputs="person id string" errors="none" side_effects="Updates people.auth_uid when linking by email"]
|
|
640
|
+
// CIL[DOC-MICRO logic_summary="auth_uid lookup first (authoritative); email fallback only binds rows that are NOT invite stubs — invited/pending status or a stored invite_token is never claimed by email alone" notable_decisions="Invite stubs are claimed exclusively by the token-gated path in handle_new_auth_user (migration 052); binding one here by email would bypass token/expiry/revocation checks"]
|
|
641
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
642
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
643
|
+
// CIL[DOC-NOTE type="security" content="Registration/invitation hardening: previously this bound auth_uid to ANY person row matching the JWT email with auth_uid NULL — including unclaimed invite stubs — bypassing the invite token entirely."]
|
|
644
|
+
export async function resolveInternalPersonId(authUserId: string, email?: string): Promise<string> {
|
|
645
|
+
// auth_uid binding is authoritative — resolve it before any email fallback.
|
|
646
|
+
const { data: byAuthId } = await adminDb
|
|
647
|
+
.from('people')
|
|
648
|
+
.select('id')
|
|
649
|
+
.eq('auth_uid', authUserId)
|
|
650
|
+
.maybeSingle()
|
|
651
|
+
|
|
652
|
+
if (byAuthId) return byAuthId.id
|
|
653
|
+
|
|
500
654
|
if (email) {
|
|
501
655
|
const { data: byEmail } = await adminDb
|
|
502
656
|
.from('people')
|
|
503
|
-
.select('id, auth_uid')
|
|
657
|
+
.select('id, auth_uid, status, data')
|
|
504
658
|
.eq('email', email)
|
|
505
659
|
.maybeSingle()
|
|
506
|
-
|
|
660
|
+
|
|
507
661
|
if (byEmail) {
|
|
508
662
|
if (!byEmail.auth_uid) {
|
|
663
|
+
// Never claim an invite stub by email alone — only bind rows that
|
|
664
|
+
// are not invited/pending and carry no stored invite token.
|
|
665
|
+
const isInviteStub =
|
|
666
|
+
byEmail.status === 'invited' ||
|
|
667
|
+
byEmail.status === 'pending' ||
|
|
668
|
+
!!(byEmail.data && (byEmail.data as any).invite_token)
|
|
669
|
+
if (isInviteStub) return authUserId
|
|
509
670
|
await adminDb
|
|
510
671
|
.from('people')
|
|
511
672
|
.update({ auth_uid: authUserId })
|
|
@@ -514,54 +675,54 @@ async function resolveInternalPersonId(authUserId: string, email?: string): Prom
|
|
|
514
675
|
return byEmail.id
|
|
515
676
|
}
|
|
516
677
|
}
|
|
517
|
-
|
|
518
|
-
const { data: byAuthId } = await adminDb
|
|
519
|
-
.from('people')
|
|
520
|
-
.select('id')
|
|
521
|
-
.eq('auth_uid', authUserId)
|
|
522
|
-
.maybeSingle()
|
|
523
|
-
|
|
524
|
-
if (byAuthId) return byAuthId.id
|
|
525
|
-
|
|
678
|
+
|
|
526
679
|
return authUserId
|
|
527
680
|
}
|
|
528
|
-
// CIL[
|
|
529
|
-
// CIL[
|
|
530
|
-
// CIL[DOC-
|
|
531
|
-
// CIL[DOC-
|
|
532
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
533
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="IP string or undefined" errors="none" side_effects="none"]
|
|
534
|
-
// CIL[DOC-MICRO logic_summary="Checks common IP headers in priority order" notable_decisions="[]"]
|
|
535
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
536
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
537
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
538
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
539
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
540
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
681
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Map a Supabase auth user to an internal person id'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
682
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
683
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
684
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
541
685
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
542
686
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
687
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:resolveInternalPersonId:function"]
|
|
688
|
+
|
|
689
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:getClientIp:function" type="function" identity="getClientIp" parent="~/.framework/functions/_shared/principal.ts"]
|
|
690
|
+
// CIL[DOC-MACRO purpose="Extract the client IP from request headers or context"]
|
|
691
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Return the most likely originating IP for a request"]
|
|
692
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
693
|
+
// CIL[DOC-CONTRACT inputs="event with headers and requestContext" outputs="ip string or undefined" errors="none" side_effects="none"]
|
|
694
|
+
// CIL[DOC-MICRO logic_summary="Prefer Netlify's x-nf-client-connection-ip (set by the edge, not client-controllable), then x-forwarded-for's first hop, x-real-ip, and requestContext.identity.sourceIp" notable_decisions="x-nf-client-connection-ip first because x-forwarded-for is client-spoofable and feeds rate limiting/provenance"]
|
|
695
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
696
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
543
697
|
function getClientIp(event: any): string | undefined {
|
|
544
|
-
|
|
545
|
-
|
|
698
|
+
// x-nf-client-connection-ip is set by Netlify's edge and cannot be spoofed by
|
|
699
|
+
// the client; x-forwarded-for's first hop is client-controllable, so it is a
|
|
700
|
+
// fallback only. Used for provenance AND IP rate limiting — spoofability
|
|
701
|
+
// would let a caller mint fresh buckets by rotating the header.
|
|
702
|
+
return event.headers?.['x-nf-client-connection-ip'] ||
|
|
703
|
+
event.headers?.['X-Nf-Client-Connection-Ip'] ||
|
|
704
|
+
(event.headers?.['x-forwarded-for'] || event.headers?.['X-Forwarded-For'] || '').split(',')[0].trim() ||
|
|
546
705
|
event.headers?.['x-real-ip'] ||
|
|
547
706
|
event.headers?.['X-Real-Ip'] ||
|
|
548
|
-
event.requestContext?.identity?.sourceIp
|
|
707
|
+
event.requestContext?.identity?.sourceIp ||
|
|
708
|
+
undefined
|
|
549
709
|
}
|
|
550
|
-
// CIL[
|
|
551
|
-
// CIL[
|
|
552
|
-
// CIL[DOC-
|
|
553
|
-
// CIL[DOC-
|
|
554
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
555
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="boolean" errors="none" side_effects="none"]
|
|
556
|
-
// CIL[DOC-MICRO logic_summary="Checks exact scope, resource wildcard, or global wildcard" notable_decisions="[]"]
|
|
557
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
558
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
559
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
560
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
561
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
562
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
710
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Extract the client IP from request headers or context'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
711
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
712
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
713
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
563
714
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
564
715
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
716
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:getClientIp:function"]
|
|
717
|
+
|
|
718
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:machineHasScope:function" type="function" identity="machineHasScope" parent="~/.framework/functions/_shared/principal.ts"]
|
|
719
|
+
// CIL[DOC-MACRO purpose="Check whether a machine principal holds a given scope"]
|
|
720
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Evaluate scope including resource wildcard and global wildcard matches"]
|
|
721
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
722
|
+
// CIL[DOC-CONTRACT inputs="principal: Principal, scope: string" outputs="boolean" errors="none" side_effects="none"]
|
|
723
|
+
// CIL[DOC-MICRO logic_summary="Return true if scope, resource wildcard, or global wildcard is present" notable_decisions="[]"]
|
|
724
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
725
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
565
726
|
export function machineHasScope(principal: Principal, scope: string): boolean {
|
|
566
727
|
if (principal.type !== 'machine') return false
|
|
567
728
|
|
|
@@ -576,58 +737,61 @@ export function machineHasScope(principal: Principal, scope: string): boolean {
|
|
|
576
737
|
|
|
577
738
|
return false
|
|
578
739
|
}
|
|
579
|
-
// CIL[
|
|
580
|
-
// CIL[
|
|
581
|
-
// CIL[DOC-
|
|
582
|
-
// CIL[DOC-
|
|
583
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
584
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="boolean" errors="none" side_effects="none"]
|
|
585
|
-
// CIL[DOC-MICRO logic_summary="Checks if principal type is human and role is in roles array" notable_decisions="[]"]
|
|
586
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
587
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
588
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
589
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
590
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
591
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
740
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Check whether a machine principal holds a given scope'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
741
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
742
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
743
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
592
744
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
593
745
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
746
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:machineHasScope:function"]
|
|
747
|
+
|
|
748
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:humanHasRole:function" type="function" identity="humanHasRole" parent="~/.framework/functions/_shared/principal.ts"]
|
|
749
|
+
// CIL[DOC-MACRO purpose="Check whether a human principal has a role"]
|
|
750
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Test principal roles array for a role slug"]
|
|
751
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
752
|
+
// CIL[DOC-CONTRACT inputs="principal: Principal, roleSlug: string" outputs="boolean" errors="none" side_effects="none"]
|
|
753
|
+
// CIL[DOC-MICRO logic_summary="Return true if principal is human and roles includes the slug" notable_decisions="[]"]
|
|
754
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
755
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
594
756
|
export function humanHasRole(principal: Principal, roleSlug: string): boolean {
|
|
595
757
|
if (principal.type !== 'human') return false
|
|
596
758
|
return principal.roles?.includes(roleSlug) || false
|
|
597
759
|
}
|
|
598
|
-
// CIL[
|
|
599
|
-
// CIL[
|
|
600
|
-
// CIL[DOC-
|
|
601
|
-
// CIL[DOC-
|
|
602
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
603
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="boolean" errors="none" side_effects="none"]
|
|
604
|
-
// CIL[DOC-MICRO logic_summary="Checks if human principal has system_admin role" notable_decisions="[]"]
|
|
605
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
606
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
607
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
608
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
609
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
610
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
760
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Check whether a human principal has a role'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
761
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
762
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
763
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
611
764
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
612
765
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
766
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:humanHasRole:function"]
|
|
767
|
+
|
|
768
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:isSystemAdmin:function" type="function" identity="isSystemAdmin" parent="~/.framework/functions/_shared/principal.ts"]
|
|
769
|
+
// CIL[DOC-MACRO purpose="Determine if a principal is a system administrator"]
|
|
770
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Check roles for system_admin by slug or object shape"]
|
|
771
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
772
|
+
// CIL[DOC-CONTRACT inputs="principal" outputs="boolean" errors="none" side_effects="none"]
|
|
773
|
+
// CIL[DOC-MICRO logic_summary="Return true if roles contains system_admin as a string or slug property" notable_decisions="[]"]
|
|
774
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
775
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
613
776
|
export function isSystemAdmin(principal: Principal): boolean {
|
|
614
|
-
return
|
|
777
|
+
return principal?.roles?.some(r => r === 'system_admin' || r?.slug === 'system_admin') || false
|
|
615
778
|
}
|
|
616
|
-
// CIL[
|
|
617
|
-
// CIL[
|
|
618
|
-
// CIL[DOC-
|
|
619
|
-
// CIL[DOC-
|
|
620
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
621
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="database connection" errors="none" side_effects="none"]
|
|
622
|
-
// CIL[DOC-MICRO logic_summary="Returns user DB for authenticated humans, admin DB otherwise" notable_decisions="[]"]
|
|
623
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
624
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
625
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
626
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
627
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
628
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
779
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Determine if a principal is a system administrator'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
780
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
781
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
782
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
629
783
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
630
784
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
785
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:isSystemAdmin:function"]
|
|
786
|
+
|
|
787
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:getPrincipalDb:function" type="function" identity="getPrincipalDb" parent="~/.framework/functions/_shared/principal.ts"]
|
|
788
|
+
// CIL[DOC-MACRO purpose="Select the appropriate database client for a principal"]
|
|
789
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Return user-scoped db for humans with JWT, otherwise admin db"]
|
|
790
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
791
|
+
// CIL[DOC-CONTRACT inputs="principal" outputs="Supabase client" errors="none" side_effects="none"]
|
|
792
|
+
// CIL[DOC-MICRO logic_summary="Use getUserDb with JWT for humans, otherwise return adminDb" notable_decisions="[]"]
|
|
793
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
794
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
631
795
|
export function getPrincipalDb(principal: Principal) {
|
|
632
796
|
if (principal.type === 'human' && principal.authContext?.jwt) {
|
|
633
797
|
return getUserDb(principal.authContext.jwt)
|
|
@@ -635,21 +799,24 @@ export function getPrincipalDb(principal: Principal) {
|
|
|
635
799
|
|
|
636
800
|
return adminDb
|
|
637
801
|
}
|
|
638
|
-
// CIL[
|
|
639
|
-
// CIL[
|
|
640
|
-
// CIL[DOC-
|
|
641
|
-
// CIL[DOC-
|
|
642
|
-
// CIL[DOC-CONTEXT module="principal" layer="shared" related_files="[]"]
|
|
643
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="audit object" errors="none" side_effects="none"]
|
|
644
|
-
// CIL[DOC-MICRO logic_summary="Returns principal fields formatted for audit logs" notable_decisions="[]"]
|
|
645
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
646
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
647
|
-
// CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
648
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
649
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
650
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
802
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Select the appropriate database client for a principal'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
803
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
804
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
805
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
651
806
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
652
807
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
808
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:getPrincipalDb:function"]
|
|
809
|
+
|
|
810
|
+
// CIL[BEGIN stable_id="~/.framework/functions/_shared/principal.ts:formatPrincipalForAudit:function" type="function" identity="formatPrincipalForAudit" parent="~/.framework/functions/_shared/principal.ts"]
|
|
811
|
+
// CIL[DOC-MACRO purpose="Serialize a principal for audit logs"]
|
|
812
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Produce a safe, structured view of principal identity and provenance"]
|
|
813
|
+
// CIL[DOC-CONTEXT module="functions/_shared/principal" layer="shared" related_files=".framework/functions/_shared/db.ts,.framework/functions/_shared/principal.ts"]
|
|
814
|
+
// CIL[DOC-CONTRACT inputs="principal" outputs="audit object" errors="none" side_effects="none"]
|
|
815
|
+
// CIL[DOC-MICRO logic_summary="Return id, type, account_id, human/machine fields, and provenance spread" notable_decisions="[]"]
|
|
816
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
817
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
818
|
+
// Deliberately omits authContext — it holds the raw credential (JWT) and must
|
|
819
|
+
// never reach audit rows, log output, or any serialized payload.
|
|
653
820
|
export function formatPrincipalForAudit(principal: Principal): object {
|
|
654
821
|
return {
|
|
655
822
|
id: principal.id,
|
|
@@ -667,4 +834,10 @@ export function formatPrincipalForAudit(principal: Principal): object {
|
|
|
667
834
|
provenance: principal.provenance
|
|
668
835
|
}
|
|
669
836
|
}
|
|
670
|
-
// CIL[
|
|
837
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Serialize a principal for audit logs'; No verified external callers; imports: .framework/functions/_shared/db.ts"]
|
|
838
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
839
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
840
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
841
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
842
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
843
|
+
// CIL[END stable_id="~/.framework/functions/_shared/principal.ts:formatPrincipalForAudit:function"]
|