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
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:module_header:module_header" type="module_header" identity="module_header" parent="none"]
|
|
2
|
+
// CIL[DOC-MACRO purpose="Test setup and imports for pipeline runner tests"]
|
|
3
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure test environment and mock dependencies"]
|
|
4
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts"]
|
|
5
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="configured test mocks" errors="none" side_effects="Mocks database and audit modules"]
|
|
6
|
+
// CIL[DOC-MICRO logic_summary="Imports vitest, mocks db and audit, imports pipeline runner" notable_decisions="[]"]
|
|
7
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
8
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
9
|
+
// CIL[DOC-AUDIENCE audience="test maintainers"]
|
|
10
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
11
|
+
import type { CoreContext } from '../../functions/_shared/middleware.ts'
|
|
12
|
+
|
|
13
|
+
vi.mock('../../functions/_shared/db.ts', () => ({
|
|
14
|
+
adminDb: { from: vi.fn(), rpc: vi.fn() }
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
vi.mock('../../functions/_shared/audit.ts', () => ({
|
|
18
|
+
emitAudit: vi.fn().mockResolvedValue(undefined),
|
|
19
|
+
emitSpan: vi.fn().mockResolvedValue(undefined)
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
vi.mock('../../functions/embeddings.ts', () => ({
|
|
23
|
+
revoke: vi.fn().mockResolvedValue({ document_id: 'item-9', revoked_count: 2 })
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
vi.mock('../../functions/ai-interactions.ts', () => ({
|
|
27
|
+
createInteraction: vi.fn().mockResolvedValue({ id: 'ir-1' })
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
import { adminDb } from '../../functions/_shared/db.ts'
|
|
31
|
+
import { emitAudit } from '../../functions/_shared/audit.ts'
|
|
32
|
+
import { runPipeline } from '../../functions/_shared/pipeline-runner.ts'
|
|
33
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Test setup and imports for pipeline runner tests'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
34
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
35
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
36
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
37
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
38
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
39
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:module_header:module_header"]
|
|
40
|
+
|
|
41
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:makeCtx:function" type="function" identity="makeCtx" parent="none"]
|
|
42
|
+
// CIL[DOC-MACRO purpose="Create test context with system principal"]
|
|
43
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Generate test context objects"]
|
|
44
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
45
|
+
// CIL[DOC-CONTRACT inputs="overrides: Partial<CoreContext>" outputs="CoreContext object" errors="none" side_effects="none"]
|
|
46
|
+
// CIL[DOC-MICRO logic_summary="Creates context with system machine principal and test data" notable_decisions="[]"]
|
|
47
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
48
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
49
|
+
function makeCtx(overrides: Partial<CoreContext> = {}): CoreContext {
|
|
50
|
+
return {
|
|
51
|
+
principal: {
|
|
52
|
+
id: 'system', type: 'machine', accountId: null, scopes: ['*:*'],
|
|
53
|
+
provenance: { sourceType: 'manual', createdBy: null, invokedAt: new Date().toISOString() }
|
|
54
|
+
},
|
|
55
|
+
accountId: 'acct-test',
|
|
56
|
+
db: {},
|
|
57
|
+
requestId: 'req-test',
|
|
58
|
+
...overrides
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Create test context with system principal'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
62
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
63
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
64
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
65
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
66
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
67
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:makeCtx:function"]
|
|
68
|
+
|
|
69
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:EXEC_ID:block" type="block" identity="EXEC_ID" parent="none"]
|
|
70
|
+
// CIL[DOC-MACRO purpose="Define test execution ID constant"]
|
|
71
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provide consistent test execution identifier"]
|
|
72
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
73
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="string constant" errors="none" side_effects="none"]
|
|
74
|
+
// CIL[DOC-MICRO logic_summary="Defines execution ID used across tests" notable_decisions="[]"]
|
|
75
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
76
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
77
|
+
const EXEC_ID = 'exec-abc'
|
|
78
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Define test execution ID constant'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
79
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
80
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
81
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
82
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
83
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
84
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:EXEC_ID:block"]
|
|
85
|
+
|
|
86
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockPipeline:function" type="function" identity="mockPipeline" parent="none"]
|
|
87
|
+
// CIL[DOC-MACRO purpose="Mock database to return pipeline and execution records"]
|
|
88
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure database mock for pipeline operations"]
|
|
89
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
90
|
+
// CIL[DOC-CONTRACT inputs="stages: any[]" outputs="mocked adminDb.from" errors="none" side_effects="Mocks adminDb.from"]
|
|
91
|
+
// CIL[DOC-MICRO logic_summary="Sets up adminDb.from mock to return pipeline and execution data" notable_decisions="[]"]
|
|
92
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
93
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
94
|
+
function mockPipeline(stages: any[]) {
|
|
95
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
96
|
+
if (table === 'pipelines') {
|
|
97
|
+
return {
|
|
98
|
+
select: vi.fn().mockReturnThis(),
|
|
99
|
+
eq: vi.fn().mockReturnThis(),
|
|
100
|
+
single: vi.fn().mockResolvedValue({
|
|
101
|
+
data: { id: 'pipe-1', stages, is_active: true },
|
|
102
|
+
error: null
|
|
103
|
+
})
|
|
104
|
+
} as any
|
|
105
|
+
}
|
|
106
|
+
if (table === 'pipeline_executions') {
|
|
107
|
+
return {
|
|
108
|
+
insert: vi.fn().mockReturnThis(),
|
|
109
|
+
update: vi.fn().mockReturnThis(),
|
|
110
|
+
select: vi.fn().mockReturnThis(),
|
|
111
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
112
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
113
|
+
} as any
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
from: vi.fn().mockReturnThis(),
|
|
117
|
+
select: vi.fn().mockReturnThis(),
|
|
118
|
+
insert: vi.fn().mockReturnThis(),
|
|
119
|
+
update: vi.fn().mockReturnThis(),
|
|
120
|
+
eq: vi.fn().mockReturnThis(),
|
|
121
|
+
single: vi.fn().mockResolvedValue({ data: null, error: null })
|
|
122
|
+
} as any
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Mock database to return pipeline and execution records'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
126
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
127
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
128
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
129
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
130
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
131
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockPipeline:function"]
|
|
132
|
+
|
|
133
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockAction:function" type="function" identity="mockAction" parent="none"]
|
|
134
|
+
// CIL[DOC-MACRO purpose="Create test action with default values"]
|
|
135
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Generate test action objects"]
|
|
136
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
137
|
+
// CIL[DOC-CONTRACT inputs="handlerName: string, extras: Record<string, any>" outputs="action object" errors="none" side_effects="none"]
|
|
138
|
+
// CIL[DOC-MICRO logic_summary="Returns default action with handler and config" notable_decisions="[]"]
|
|
139
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
140
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
141
|
+
function mockAction(handlerName: string, extras: Record<string, any> = {}) {
|
|
142
|
+
return {
|
|
143
|
+
id: 'action-1',
|
|
144
|
+
slug: handlerName,
|
|
145
|
+
handler: handlerName,
|
|
146
|
+
handler_module: 'functions',
|
|
147
|
+
config: {},
|
|
148
|
+
is_active: true,
|
|
149
|
+
...extras
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Create test action with default values'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
153
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
154
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
155
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
156
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
157
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
158
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockAction:function"]
|
|
159
|
+
|
|
160
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockPipelineWithAction:function" type="function" identity="mockPipelineWithAction" parent="none"]
|
|
161
|
+
// CIL[DOC-MACRO purpose="Mock database with pipeline and action records"]
|
|
162
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure database mock for pipeline with actions"]
|
|
163
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
164
|
+
// CIL[DOC-CONTRACT inputs="stages: any[], action: any" outputs="mocked adminDb.from" errors="none" side_effects="Mocks adminDb.from"]
|
|
165
|
+
// CIL[DOC-MICRO logic_summary="Sets up mocks for pipeline, execution, and action table queries" notable_decisions="[]"]
|
|
166
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
167
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
168
|
+
function mockPipelineWithAction(stages: any[], action: any) {
|
|
169
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
170
|
+
if (table === 'pipelines') {
|
|
171
|
+
return {
|
|
172
|
+
select: vi.fn().mockReturnThis(),
|
|
173
|
+
eq: vi.fn().mockReturnThis(),
|
|
174
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages, is_active: true }, error: null })
|
|
175
|
+
} as any
|
|
176
|
+
}
|
|
177
|
+
if (table === 'pipeline_executions') {
|
|
178
|
+
return {
|
|
179
|
+
insert: vi.fn().mockReturnThis(),
|
|
180
|
+
update: vi.fn().mockReturnThis(),
|
|
181
|
+
select: vi.fn().mockReturnThis(),
|
|
182
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
183
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
184
|
+
} as any
|
|
185
|
+
}
|
|
186
|
+
if (table === 'actions') {
|
|
187
|
+
return {
|
|
188
|
+
select: vi.fn().mockReturnThis(),
|
|
189
|
+
eq: vi.fn().mockReturnThis(),
|
|
190
|
+
single: vi.fn().mockResolvedValue({ data: action, error: null })
|
|
191
|
+
} as any
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
select: vi.fn().mockReturnThis(),
|
|
195
|
+
insert: vi.fn().mockReturnThis(),
|
|
196
|
+
update: vi.fn().mockReturnThis(),
|
|
197
|
+
eq: vi.fn().mockReturnThis(),
|
|
198
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'r-1' }, error: null })
|
|
199
|
+
} as any
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Mock database with pipeline and action records'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
203
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
204
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
205
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
206
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
207
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
208
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:mockPipelineWithAction:function"]
|
|
209
|
+
|
|
210
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_runPipeline_lifecycle:block" type="block" identity="describe_runPipeline_lifecycle" parent="none"]
|
|
211
|
+
// CIL[DOC-MACRO purpose="Test runPipeline lifecycle and error handling"]
|
|
212
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify pipeline execution flow and error cases"]
|
|
213
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
214
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="test assertions" errors="none" side_effects="Tests pipeline-runner.ts module"]
|
|
215
|
+
// CIL[DOC-MICRO logic_summary="Tests pipeline lookup, execution creation, and audit emission" notable_decisions="[]"]
|
|
216
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
217
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
218
|
+
describe('runPipeline — lifecycle', () => {
|
|
219
|
+
beforeEach(() => vi.clearAllMocks())
|
|
220
|
+
|
|
221
|
+
it('throws when pipeline is not found', async () => {
|
|
222
|
+
vi.mocked(adminDb.from).mockReturnValue({
|
|
223
|
+
select: vi.fn().mockReturnThis(),
|
|
224
|
+
eq: vi.fn().mockReturnThis(),
|
|
225
|
+
single: vi.fn().mockResolvedValue({ data: null, error: { message: 'No rows' } })
|
|
226
|
+
} as any)
|
|
227
|
+
await expect(runPipeline('bad-id', {}, makeCtx())).rejects.toThrow('Pipeline not found or inactive')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('throws when pipeline exists but is_active is null', async () => {
|
|
231
|
+
vi.mocked(adminDb.from).mockReturnValue({
|
|
232
|
+
select: vi.fn().mockReturnThis(),
|
|
233
|
+
eq: vi.fn().mockReturnThis(),
|
|
234
|
+
single: vi.fn().mockResolvedValue({ data: null, error: null })
|
|
235
|
+
} as any)
|
|
236
|
+
await expect(runPipeline('inactive-id', {}, makeCtx())).rejects.toThrow('Pipeline not found or inactive')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('throws when execution record cannot be created', async () => {
|
|
240
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
241
|
+
if (table === 'pipelines') {
|
|
242
|
+
return {
|
|
243
|
+
select: vi.fn().mockReturnThis(),
|
|
244
|
+
eq: vi.fn().mockReturnThis(),
|
|
245
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages: [], is_active: true }, error: null })
|
|
246
|
+
} as any
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
insert: vi.fn().mockReturnThis(),
|
|
250
|
+
select: vi.fn().mockReturnThis(),
|
|
251
|
+
single: vi.fn().mockResolvedValue({ data: null, error: { message: 'DB error' } })
|
|
252
|
+
} as any
|
|
253
|
+
})
|
|
254
|
+
await expect(runPipeline('pipe-1', {}, makeCtx())).rejects.toThrow('Failed to create execution')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('returns completed with zero stages and correct shape', async () => {
|
|
258
|
+
mockPipeline([])
|
|
259
|
+
const result = await runPipeline('pipe-1', { x: 1 }, makeCtx())
|
|
260
|
+
expect(result.status).toBe('completed')
|
|
261
|
+
expect(result.executionId).toBe(EXEC_ID)
|
|
262
|
+
expect(result.pipelineId).toBe('pipe-1')
|
|
263
|
+
expect(result.stages).toHaveLength(0)
|
|
264
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0)
|
|
265
|
+
expect(result.error).toBeUndefined()
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('emits pipeline.completed audit on success', async () => {
|
|
269
|
+
mockPipeline([])
|
|
270
|
+
await runPipeline('pipe-1', {}, makeCtx())
|
|
271
|
+
expect(emitAudit).toHaveBeenCalledWith(
|
|
272
|
+
expect.any(Object),
|
|
273
|
+
'pipeline.completed',
|
|
274
|
+
expect.objectContaining({ type: 'pipeline_execution', id: EXEC_ID }),
|
|
275
|
+
expect.objectContaining({ pipeline_id: 'pipe-1' })
|
|
276
|
+
)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Test runPipeline lifecycle and error handling'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
280
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
281
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
282
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
283
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
284
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
285
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_runPipeline_lifecycle:block"]
|
|
286
|
+
|
|
287
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_runPipeline_stageExecution:block" type="block" identity="describe_runPipeline_stageExecution" parent="none"]
|
|
288
|
+
// CIL[DOC-MACRO purpose="Test runPipeline stage execution and failure handling"]
|
|
289
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify stage execution and error recovery"]
|
|
290
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
291
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="test assertions" errors="none" side_effects="Tests pipeline-runner.ts module"]
|
|
292
|
+
// CIL[DOC-MICRO logic_summary="Tests action lookup, stage failure, and continue_on_error" notable_decisions="[]"]
|
|
293
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
294
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
295
|
+
describe('runPipeline — stage execution', () => {
|
|
296
|
+
beforeEach(() => vi.clearAllMocks())
|
|
297
|
+
|
|
298
|
+
it('returns failed status and error when action not found', async () => {
|
|
299
|
+
const stages = [{ stage_type: 'ghost_action' }]
|
|
300
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
301
|
+
if (table === 'pipelines') {
|
|
302
|
+
return {
|
|
303
|
+
select: vi.fn().mockReturnThis(),
|
|
304
|
+
eq: vi.fn().mockReturnThis(),
|
|
305
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages, is_active: true }, error: null })
|
|
306
|
+
} as any
|
|
307
|
+
}
|
|
308
|
+
if (table === 'pipeline_executions') {
|
|
309
|
+
return {
|
|
310
|
+
insert: vi.fn().mockReturnThis(),
|
|
311
|
+
update: vi.fn().mockReturnThis(),
|
|
312
|
+
select: vi.fn().mockReturnThis(),
|
|
313
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
314
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
315
|
+
} as any
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
select: vi.fn().mockReturnThis(),
|
|
319
|
+
eq: vi.fn().mockReturnThis(),
|
|
320
|
+
single: vi.fn().mockResolvedValue({ data: null, error: { message: 'Not found' } })
|
|
321
|
+
} as any
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
325
|
+
expect(result.status).toBe('failed')
|
|
326
|
+
expect(result.stages[0].status).toBe('failed')
|
|
327
|
+
expect(result.stages[0].error).toMatch(/Action not found/)
|
|
328
|
+
expect(result.error).toMatch(/Stage 0.*failed/)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('returns failed status and emits pipeline.failed audit on stage failure', async () => {
|
|
332
|
+
const stages = [{ stage_type: 'bad_action' }]
|
|
333
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
334
|
+
if (table === 'pipelines') {
|
|
335
|
+
return {
|
|
336
|
+
select: vi.fn().mockReturnThis(),
|
|
337
|
+
eq: vi.fn().mockReturnThis(),
|
|
338
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages, is_active: true }, error: null })
|
|
339
|
+
} as any
|
|
340
|
+
}
|
|
341
|
+
if (table === 'pipeline_executions') {
|
|
342
|
+
return {
|
|
343
|
+
insert: vi.fn().mockReturnThis(),
|
|
344
|
+
update: vi.fn().mockReturnThis(),
|
|
345
|
+
select: vi.fn().mockReturnThis(),
|
|
346
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
347
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
348
|
+
} as any
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
select: vi.fn().mockReturnThis(),
|
|
352
|
+
eq: vi.fn().mockReturnThis(),
|
|
353
|
+
single: vi.fn().mockResolvedValue({ data: null, error: { message: 'nope' } })
|
|
354
|
+
} as any
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
358
|
+
expect(result.status).toBe('failed')
|
|
359
|
+
expect(emitAudit).toHaveBeenCalledWith(
|
|
360
|
+
expect.any(Object),
|
|
361
|
+
'pipeline.failed',
|
|
362
|
+
expect.objectContaining({ id: EXEC_ID }),
|
|
363
|
+
expect.any(Object)
|
|
364
|
+
)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('continues past a failed stage when continue_on_error is true', async () => {
|
|
368
|
+
const stages = [
|
|
369
|
+
{ stage_type: 'bad_one', continue_on_error: true },
|
|
370
|
+
{ stage_type: 'good_one' }
|
|
371
|
+
]
|
|
372
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
373
|
+
if (table === 'pipelines') {
|
|
374
|
+
return {
|
|
375
|
+
select: vi.fn().mockReturnThis(),
|
|
376
|
+
eq: vi.fn().mockReturnThis(),
|
|
377
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages, is_active: true }, error: null })
|
|
378
|
+
} as any
|
|
379
|
+
}
|
|
380
|
+
if (table === 'pipeline_executions') {
|
|
381
|
+
return {
|
|
382
|
+
insert: vi.fn().mockReturnThis(),
|
|
383
|
+
update: vi.fn().mockReturnThis(),
|
|
384
|
+
select: vi.fn().mockReturnThis(),
|
|
385
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
386
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
387
|
+
} as any
|
|
388
|
+
}
|
|
389
|
+
if (table === 'actions') {
|
|
390
|
+
let actionCall = 0
|
|
391
|
+
return {
|
|
392
|
+
select: vi.fn().mockReturnThis(),
|
|
393
|
+
eq: vi.fn().mockImplementation(() => {
|
|
394
|
+
actionCall++
|
|
395
|
+
return {
|
|
396
|
+
eq: vi.fn().mockReturnThis(),
|
|
397
|
+
single: vi.fn().mockResolvedValue(
|
|
398
|
+
actionCall === 1
|
|
399
|
+
? { data: null, error: { message: 'bad action' } }
|
|
400
|
+
: { data: mockAction('good_one'), error: null }
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
} as any
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
select: vi.fn().mockReturnThis(),
|
|
408
|
+
insert: vi.fn().mockReturnThis(),
|
|
409
|
+
update: vi.fn().mockReturnThis(),
|
|
410
|
+
eq: vi.fn().mockReturnThis(),
|
|
411
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'r-1' }, error: null })
|
|
412
|
+
} as any
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
416
|
+
expect(result.stages).toHaveLength(2)
|
|
417
|
+
expect(result.stages[0].status).toBe('failed')
|
|
418
|
+
})
|
|
419
|
+
})
|
|
420
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Test runPipeline stage execution and failure handling'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
421
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
422
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
423
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
424
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
425
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
426
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_runPipeline_stageExecution:block"]
|
|
427
|
+
|
|
428
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_builtInStageHandlers:block" type="block" identity="describe_builtInStageHandlers" parent="none"]
|
|
429
|
+
// CIL[DOC-MACRO purpose="Test built-in stage handler implementations"]
|
|
430
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify built-in handler behavior and validation"]
|
|
431
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
432
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="test assertions" errors="none" side_effects="Tests pipeline-runner.ts module"]
|
|
433
|
+
// CIL[DOC-MICRO logic_summary="Tests update_item, create_record, send_notification, run_pipeline handlers" notable_decisions="[]"]
|
|
434
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
435
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
436
|
+
describe('built-in stage handlers', () => {
|
|
437
|
+
beforeEach(() => vi.clearAllMocks())
|
|
438
|
+
|
|
439
|
+
it('update_item: throws when entity or record_id missing', async () => {
|
|
440
|
+
const stages = [{ stage_type: 'update_item', config: {} }]
|
|
441
|
+
mockPipelineWithAction(stages, mockAction('update_item'))
|
|
442
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
443
|
+
expect(result.stages[0].status).toBe('failed')
|
|
444
|
+
expect(result.stages[0].error).toMatch(/entity is required/)
|
|
445
|
+
|
|
446
|
+
vi.clearAllMocks()
|
|
447
|
+
const stages2 = [{ stage_type: 'update_item', config: { entity: 'items' } }]
|
|
448
|
+
mockPipelineWithAction(stages2, mockAction('update_item'))
|
|
449
|
+
const result2 = await runPipeline('pipe-1', {}, makeCtx())
|
|
450
|
+
expect(result2.stages[0].status).toBe('failed')
|
|
451
|
+
expect(result2.stages[0].error).toMatch(/record_id missing/)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('update_item: succeeds when entity + record_id + data provided', async () => {
|
|
455
|
+
const stages = [{ stage_type: 'update_item', config: { entity: 'items', record_id: 'item-1', data: { status: 'done' } } }]
|
|
456
|
+
mockPipelineWithAction(stages, mockAction('update_item'))
|
|
457
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
458
|
+
expect(result.stages[0].status).toBe('success')
|
|
459
|
+
expect(result.stages[0].output).toMatchObject({ success: true })
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('create_record: throws when entity or data missing', async () => {
|
|
463
|
+
const stages = [{ stage_type: 'create_record', config: { entity: 'items' } }]
|
|
464
|
+
mockPipelineWithAction(stages, mockAction('create_record'))
|
|
465
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
466
|
+
expect(result.stages[0].status).toBe('failed')
|
|
467
|
+
expect(result.stages[0].error).toMatch(/entity and data are required/)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('send_notification: succeeds with empty recipients (no-op)', async () => {
|
|
471
|
+
const stages = [{ stage_type: 'send_notification', config: { message: 'Hello', recipients: [] } }]
|
|
472
|
+
mockPipelineWithAction(stages, mockAction('send_notification'))
|
|
473
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
474
|
+
expect(result.stages[0].status).toBe('success')
|
|
475
|
+
expect(result.stages[0].output).toMatchObject({ success: true, notified_count: 0 })
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('run_pipeline: throws when pipeline_id missing', async () => {
|
|
479
|
+
const stages = [{ stage_type: 'run_pipeline', config: {} }]
|
|
480
|
+
mockPipelineWithAction(stages, mockAction('run_pipeline'))
|
|
481
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
482
|
+
expect(result.stages[0].status).toBe('failed')
|
|
483
|
+
expect(result.stages[0].error).toMatch(/pipeline_id is required/)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('run_pipeline: prevents self-recursion', async () => {
|
|
487
|
+
const stages = [{ stage_type: 'run_pipeline', config: { pipeline_id: 'pipe-1' } }]
|
|
488
|
+
mockPipelineWithAction(stages, mockAction('run_pipeline'))
|
|
489
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
490
|
+
expect(result.stages[0].status).toBe('failed')
|
|
491
|
+
expect(result.stages[0].error).toMatch(/Recursive pipeline execution prevented/)
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
it('revoke_embeddings: throws when document_id missing', async () => {
|
|
495
|
+
const stages = [{ stage_type: 'revoke_embeddings', config: {} }]
|
|
496
|
+
mockPipelineWithAction(stages, mockAction('revoke_embeddings'))
|
|
497
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
498
|
+
expect(result.stages[0].status).toBe('failed')
|
|
499
|
+
expect(result.stages[0].error).toMatch(/document_id is required/)
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('revoke_embeddings: resolves {{trigger.entity_id}} and calls embeddings.revoke', async () => {
|
|
503
|
+
const stages = [{ stage_type: 'revoke_embeddings', config: { document_id: '{{trigger.entity_id}}' } }]
|
|
504
|
+
mockPipelineWithAction(stages, mockAction('revoke_embeddings'))
|
|
505
|
+
const result = await runPipeline('pipe-1', { entity_id: 'item-9' }, makeCtx())
|
|
506
|
+
expect(result.stages[0].status).toBe('success')
|
|
507
|
+
expect(result.stages[0].output).toMatchObject({ document_id: 'item-9', revoked_count: 2 })
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('create_interaction: resolves entity_id and entity thread_id from trigger data', async () => {
|
|
511
|
+
const stages = [{ stage_type: 'create_interaction', config: { ai_message_id: '{{trigger.entity_id}}', thread_id: '{{trigger.thread_id}}' } }]
|
|
512
|
+
mockPipelineWithAction(stages, mockAction('create_interaction'))
|
|
513
|
+
const triggerData = { entity_id: 'msg-1', entity: { data: { thread_id: 'thr-7' } } }
|
|
514
|
+
const result = await runPipeline('pipe-1', triggerData, makeCtx())
|
|
515
|
+
expect(result.stages[0].status).toBe('success')
|
|
516
|
+
const { createInteraction } = await import('../../functions/ai-interactions.ts')
|
|
517
|
+
expect(createInteraction).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ ai_message_id: 'msg-1', thread_id: 'thr-7' }))
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('create_interaction: throws when no ids resolve', async () => {
|
|
521
|
+
const stages = [{ stage_type: 'create_interaction', config: {} }]
|
|
522
|
+
mockPipelineWithAction(stages, mockAction('create_interaction'))
|
|
523
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
524
|
+
expect(result.stages[0].status).toBe('failed')
|
|
525
|
+
expect(result.stages[0].error).toMatch(/could not resolve ai_message_id or thread_id/)
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('throws for unknown handler module', async () => {
|
|
529
|
+
const stages = [{ stage_type: 'mystery_stage' }]
|
|
530
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
531
|
+
if (table === 'pipelines') {
|
|
532
|
+
return {
|
|
533
|
+
select: vi.fn().mockReturnThis(),
|
|
534
|
+
eq: vi.fn().mockReturnThis(),
|
|
535
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'pipe-1', stages, is_active: true }, error: null })
|
|
536
|
+
} as any
|
|
537
|
+
}
|
|
538
|
+
if (table === 'pipeline_executions') {
|
|
539
|
+
return {
|
|
540
|
+
insert: vi.fn().mockReturnThis(),
|
|
541
|
+
update: vi.fn().mockReturnThis(),
|
|
542
|
+
select: vi.fn().mockReturnThis(),
|
|
543
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
544
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
545
|
+
} as any
|
|
546
|
+
}
|
|
547
|
+
if (table === 'actions') {
|
|
548
|
+
return {
|
|
549
|
+
select: vi.fn().mockReturnThis(),
|
|
550
|
+
eq: vi.fn().mockReturnThis(),
|
|
551
|
+
single: vi.fn().mockResolvedValue({
|
|
552
|
+
data: { id: 'a-1', handler: 'mystery', handler_module: 'alien', config: {} },
|
|
553
|
+
error: null
|
|
554
|
+
})
|
|
555
|
+
} as any
|
|
556
|
+
}
|
|
557
|
+
return { select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), single: vi.fn().mockResolvedValue({ data: null, error: null }) } as any
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
561
|
+
expect(result.stages[0].status).toBe('failed')
|
|
562
|
+
expect(result.stages[0].error).toMatch(/Unknown handler module/)
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
describe('send_notification — writes to notifications, not watchers', () => {
|
|
567
|
+
beforeEach(() => vi.clearAllMocks())
|
|
568
|
+
|
|
569
|
+
function mockNotifyPipeline(insertCapture: { rows?: any[]; error?: any }) {
|
|
570
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
571
|
+
if (table === 'pipelines') {
|
|
572
|
+
return {
|
|
573
|
+
select: vi.fn().mockReturnThis(),
|
|
574
|
+
eq: vi.fn().mockReturnThis(),
|
|
575
|
+
single: vi.fn().mockResolvedValue({
|
|
576
|
+
data: { id: 'pipe-1', stages: [{ stage_type: 'send_notification', config: { message: 'Ticket escalated', recipients: ['aaaaaaaa-1111-4111-8111-aaaaaaaaaaaa', 'bbbbbbbb-2222-4222-8222-bbbbbbbbbbbb'], entity_type: 'items', entity_id: '11111111-1111-4111-8111-111111111111' } }], is_active: true },
|
|
577
|
+
error: null
|
|
578
|
+
})
|
|
579
|
+
} as any
|
|
580
|
+
}
|
|
581
|
+
if (table === 'pipeline_executions') {
|
|
582
|
+
return {
|
|
583
|
+
insert: vi.fn().mockReturnThis(),
|
|
584
|
+
update: vi.fn().mockReturnThis(),
|
|
585
|
+
select: vi.fn().mockReturnThis(),
|
|
586
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
587
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
588
|
+
} as any
|
|
589
|
+
}
|
|
590
|
+
if (table === 'actions') {
|
|
591
|
+
return {
|
|
592
|
+
select: vi.fn().mockReturnThis(),
|
|
593
|
+
eq: vi.fn().mockReturnThis(),
|
|
594
|
+
single: vi.fn().mockResolvedValue({
|
|
595
|
+
data: { id: 'a-1', handler: 'send_notification', handler_module: 'functions', config: {} },
|
|
596
|
+
error: null
|
|
597
|
+
})
|
|
598
|
+
} as any
|
|
599
|
+
}
|
|
600
|
+
if (table === 'notifications') {
|
|
601
|
+
return {
|
|
602
|
+
insert: vi.fn((rows: any[]) => {
|
|
603
|
+
insertCapture.rows = rows
|
|
604
|
+
return Promise.resolve({ data: null, error: insertCapture.error || null })
|
|
605
|
+
})
|
|
606
|
+
} as any
|
|
607
|
+
}
|
|
608
|
+
if (table === 'watchers') {
|
|
609
|
+
return {
|
|
610
|
+
insert: vi.fn(() => Promise.resolve({ data: null, error: { message: 'watchers must not receive notification payloads' } }))
|
|
611
|
+
} as any
|
|
612
|
+
}
|
|
613
|
+
return { select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), insert: vi.fn().mockReturnThis(), update: vi.fn().mockReturnThis(), single: vi.fn().mockResolvedValue({ data: null, error: null }) } as any
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
it('inserts one notifications row per recipient with correct fields', async () => {
|
|
618
|
+
const cap: { rows?: any[] } = {}
|
|
619
|
+
mockNotifyPipeline(cap)
|
|
620
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
621
|
+
expect(result.stages[0].status).toBe('success')
|
|
622
|
+
expect(result.stages[0].output.notified_count).toBe(2)
|
|
623
|
+
expect(cap.rows).toHaveLength(2)
|
|
624
|
+
for (const row of cap.rows!) {
|
|
625
|
+
expect(row.account_id).toBe('acct-test')
|
|
626
|
+
expect(row.message).toBe('Ticket escalated')
|
|
627
|
+
expect(row.is_read).toBe(false)
|
|
628
|
+
expect(row.source_type).toBe('pipeline_execution')
|
|
629
|
+
expect(row.target_type).toBe('items')
|
|
630
|
+
expect(row.target_id).toBe('11111111-1111-4111-8111-111111111111')
|
|
631
|
+
}
|
|
632
|
+
expect(cap.rows!.map((r: any) => r.person_id)).toEqual(['aaaaaaaa-1111-4111-8111-aaaaaaaaaaaa', 'bbbbbbbb-2222-4222-8222-bbbbbbbbbbbb'])
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
it('notification insert failure fails the stage', async () => {
|
|
636
|
+
const cap: { rows?: any[]; error?: any } = { error: { message: 'relation notifications does not exist' } }
|
|
637
|
+
mockNotifyPipeline(cap)
|
|
638
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
639
|
+
expect(result.status).toBe('failed')
|
|
640
|
+
expect(result.stages[0].error).toMatch(/Notification failed/)
|
|
641
|
+
})
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Test built-in stage handler implementations'; No verified external callers; imports: .framework/functions/_shared/middleware.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/audit.ts"]
|
|
645
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
646
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
647
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
648
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
649
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
650
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_builtInStageHandlers:block"]
|
|
651
|
+
|
|
652
|
+
// CIL[BEGIN stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_sendNotification_roleRecipients:block" type="block" identity="describe_sendNotification_roleRecipients" parent="none"]
|
|
653
|
+
// CIL[DOC-MACRO purpose="Test send_notification role:<slug> recipient resolution in the pipeline stage"]
|
|
654
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify role: recipients resolve to account-scoped people ids before the notifications insert, UUIDs pass through, and empty role matches yield a valid 0-notification result"]
|
|
655
|
+
// CIL[DOC-CONTEXT module="tests/unit/pipeline-runner" layer="test-unit" related_files=".framework/functions/_shared/notify.ts,.framework/functions/_shared/pipeline-runner.ts,.framework/tests/unit/pipeline-runner.test.ts"]
|
|
656
|
+
// CIL[DOC-CONTRACT inputs="stage configs with role:/UUID recipients; mocked people join" outputs="notifications insert assertions and notified_count" errors="none" side_effects="console.warn on unresolvable recipients"]
|
|
657
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
658
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
659
|
+
describe('send_notification — role:<slug> recipient resolution', () => {
|
|
660
|
+
beforeEach(() => {
|
|
661
|
+
vi.clearAllMocks()
|
|
662
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
function mockRoleNotifyPipeline(recipients: any[], peopleRows: any[] | null, insertCapture: { rows?: any[] }, peopleError?: any) {
|
|
666
|
+
const peopleQ: any = {
|
|
667
|
+
select: vi.fn(() => peopleQ),
|
|
668
|
+
eq: vi.fn(() => peopleQ),
|
|
669
|
+
then: (onF: any, onR: any) =>
|
|
670
|
+
Promise.resolve({ data: peopleRows, error: peopleError ?? null }).then(onF, onR)
|
|
671
|
+
}
|
|
672
|
+
vi.mocked(adminDb.from).mockImplementation((table: string) => {
|
|
673
|
+
if (table === 'pipelines') {
|
|
674
|
+
return {
|
|
675
|
+
select: vi.fn().mockReturnThis(),
|
|
676
|
+
eq: vi.fn().mockReturnThis(),
|
|
677
|
+
single: vi.fn().mockResolvedValue({
|
|
678
|
+
data: { id: 'pipe-1', stages: [{ stage_type: 'send_notification', config: { message: 'Ticket escalated', recipients, entity_type: 'threads' } }], is_active: true },
|
|
679
|
+
error: null
|
|
680
|
+
})
|
|
681
|
+
} as any
|
|
682
|
+
}
|
|
683
|
+
if (table === 'pipeline_executions') {
|
|
684
|
+
return {
|
|
685
|
+
insert: vi.fn().mockReturnThis(),
|
|
686
|
+
update: vi.fn().mockReturnThis(),
|
|
687
|
+
select: vi.fn().mockReturnThis(),
|
|
688
|
+
eq: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
689
|
+
single: vi.fn().mockResolvedValue({ data: { id: EXEC_ID }, error: null })
|
|
690
|
+
} as any
|
|
691
|
+
}
|
|
692
|
+
if (table === 'actions') {
|
|
693
|
+
return {
|
|
694
|
+
select: vi.fn().mockReturnThis(),
|
|
695
|
+
eq: vi.fn().mockReturnThis(),
|
|
696
|
+
single: vi.fn().mockResolvedValue({
|
|
697
|
+
data: { id: 'a-1', handler: 'send_notification', handler_module: 'functions', config: {} },
|
|
698
|
+
error: null
|
|
699
|
+
})
|
|
700
|
+
} as any
|
|
701
|
+
}
|
|
702
|
+
if (table === 'people') return peopleQ
|
|
703
|
+
if (table === 'notifications') {
|
|
704
|
+
return {
|
|
705
|
+
insert: vi.fn((rows: any[]) => {
|
|
706
|
+
insertCapture.rows = rows
|
|
707
|
+
return Promise.resolve({ data: null, error: null })
|
|
708
|
+
})
|
|
709
|
+
} as any
|
|
710
|
+
}
|
|
711
|
+
return { select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), insert: vi.fn().mockReturnThis(), update: vi.fn().mockReturnThis(), single: vi.fn().mockResolvedValue({ data: null, error: null }) } as any
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
it('resolves role:member_admin to the account’s people ids', async () => {
|
|
716
|
+
const cap: { rows?: any[] } = {}
|
|
717
|
+
mockRoleNotifyPipeline(['role:member_admin'], [{ id: 'admin-1' }, { id: 'admin-2' }], cap)
|
|
718
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
719
|
+
expect(result.stages[0].status).toBe('success')
|
|
720
|
+
expect(result.stages[0].output.notified_count).toBe(2)
|
|
721
|
+
expect(cap.rows!.map((r: any) => r.person_id)).toEqual(['admin-1', 'admin-2'])
|
|
722
|
+
expect(cap.rows![0].account_id).toBe('acct-test')
|
|
723
|
+
expect(cap.rows![0].source_type).toBe('pipeline_execution')
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
it('mixes role: handles and UUID recipients in one stage', async () => {
|
|
727
|
+
const cap: { rows?: any[] } = {}
|
|
728
|
+
const uuid = '22222222-2222-4222-8222-222222222222'
|
|
729
|
+
mockRoleNotifyPipeline(['role:member_admin', uuid], [{ id: 'admin-1' }], cap)
|
|
730
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
731
|
+
expect(result.stages[0].status).toBe('success')
|
|
732
|
+
expect(result.stages[0].output.notified_count).toBe(2)
|
|
733
|
+
expect(cap.rows!.map((r: any) => r.person_id)).toEqual(['admin-1', uuid])
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
it('an unknown role is a valid zero-notification result, not an error', async () => {
|
|
737
|
+
const cap: { rows?: any[] } = {}
|
|
738
|
+
mockRoleNotifyPipeline(['role:no_such_role'], [], cap)
|
|
739
|
+
const result = await runPipeline('pipe-1', {}, makeCtx())
|
|
740
|
+
expect(result.stages[0].status).toBe('success')
|
|
741
|
+
expect(result.stages[0].output.notified_count).toBe(0)
|
|
742
|
+
expect(cap.rows).toBeUndefined()
|
|
743
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('role:no_such_role'))
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
it('never writes a literal role: string into person_id', async () => {
|
|
747
|
+
const cap: { rows?: any[] } = {}
|
|
748
|
+
mockRoleNotifyPipeline(['role:member_admin'], [{ id: 'admin-1' }], cap)
|
|
749
|
+
await runPipeline('pipe-1', {}, makeCtx())
|
|
750
|
+
for (const row of cap.rows!) {
|
|
751
|
+
expect(row.person_id).not.toMatch(/^role:/)
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
})
|
|
755
|
+
// CIL[END stable_id="~/.framework/tests/unit/pipeline-runner.test.ts:describe_sendNotification_roleRecipients:block"]
|