spine-framework 0.3.144 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/.env.example +76 -0
  2. package/.framework/.spine-manifest.json +19 -0
  3. package/.framework/cli/commands/agents.ts +61 -33
  4. package/.framework/cli/commands/auth.ts +60 -28
  5. package/.framework/cli/commands/create-app.ts +212 -80
  6. package/.framework/cli/commands/dev.ts +184 -68
  7. package/.framework/cli/commands/doctor.ts +324 -152
  8. package/.framework/cli/commands/generate.ts +119 -68
  9. package/.framework/cli/commands/init.ts +141 -242
  10. package/.framework/cli/commands/install-app.ts +1336 -314
  11. package/.framework/cli/commands/items.ts +31 -29
  12. package/.framework/cli/commands/migrate.ts +299 -156
  13. package/.framework/cli/commands/migrations.ts +62 -30
  14. package/.framework/cli/commands/pipelines.ts +31 -29
  15. package/.framework/cli/commands/status.ts +105 -45
  16. package/.framework/cli/commands/system.ts +47 -42
  17. package/.framework/cli/commands/test.ts +161 -102
  18. package/.framework/cli/commands/upgrade.ts +260 -0
  19. package/.framework/cli/commands/validate-app.ts +517 -161
  20. package/.framework/cli/context.ts +117 -85
  21. package/.framework/cli/env-loader.ts +74 -43
  22. package/.framework/cli/index.ts +70 -46
  23. package/.framework/cli/lib/project-files.ts +404 -0
  24. package/.framework/docs/guides/testing-guide.md +1 -1
  25. package/.framework/docs/runtime-types.md +72 -72
  26. package/.framework/functions/_shared/access.ts +84 -0
  27. package/.framework/functions/_shared/agent-runner.ts +5051 -1026
  28. package/.framework/functions/_shared/app-manifest.ts +214 -94
  29. package/.framework/functions/_shared/ast.ts +214 -0
  30. package/.framework/functions/_shared/audit.ts +91 -55
  31. package/.framework/functions/_shared/cil.ts +368 -127
  32. package/.framework/functions/_shared/db.ts +337 -68
  33. package/.framework/functions/_shared/funnel.ts +228 -0
  34. package/.framework/functions/_shared/geo.ts +105 -0
  35. package/.framework/functions/_shared/hash-api-key.ts +94 -17
  36. package/.framework/functions/_shared/index.ts +22 -224
  37. package/.framework/functions/_shared/kb.ts +832 -58
  38. package/.framework/functions/_shared/llm.ts +118 -18
  39. package/.framework/functions/_shared/logging.ts +317 -19
  40. package/.framework/functions/_shared/middleware.ts +701 -131
  41. package/.framework/functions/_shared/notify.ts +70 -0
  42. package/.framework/functions/_shared/outcomes.ts +34 -19
  43. package/.framework/functions/_shared/permissions.ts +501 -315
  44. package/.framework/functions/_shared/pipeline-runner.ts +704 -155
  45. package/.framework/functions/_shared/principal.ts +436 -263
  46. package/.framework/functions/_shared/rate-limit.ts +233 -0
  47. package/.framework/functions/_shared/resolve-credentials.ts +86 -19
  48. package/.framework/functions/_shared/resolve-ids.ts +203 -139
  49. package/.framework/functions/_shared/retry-deliveries.ts +51 -16
  50. package/.framework/functions/_shared/schema-utils.ts +602 -521
  51. package/.framework/functions/_shared/security.ts +345 -0
  52. package/.framework/functions/_shared/testing.ts +197 -172
  53. package/.framework/functions/_shared/tracing.ts +351 -21
  54. package/.framework/functions/_shared/trigger-engine.ts +425 -136
  55. package/.framework/functions/_shared/webhook-registration.ts +71 -70
  56. package/.framework/functions/_shared/webhook-registry.ts +126 -83
  57. package/.framework/functions/account-nodes.ts +30 -30
  58. package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
  59. package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
  60. package/.framework/functions/adapters/openai-outbound.ts +15 -16
  61. package/.framework/functions/admin-data.ts +596 -173
  62. package/.framework/functions/admin-funnel.ts +64 -60
  63. package/.framework/functions/admin-install-funnel.ts +84 -66
  64. package/.framework/functions/admin-signals.ts +69 -51
  65. package/.framework/functions/ai-agents.ts +431 -106
  66. package/.framework/functions/ai-interactions.ts +186 -112
  67. package/.framework/functions/api-keys.ts +107 -107
  68. package/.framework/functions/apps.ts +154 -136
  69. package/.framework/functions/auth.ts +83 -65
  70. package/.framework/functions/cil-agent-ingest.ts +189 -232
  71. package/.framework/functions/cil-agent.ts +433 -177
  72. package/.framework/functions/cil-callmap.ts +642 -430
  73. package/.framework/functions/cil-config.ts +139 -135
  74. package/.framework/functions/cil-context.ts +214 -127
  75. package/.framework/functions/cil-doc.ts +587 -458
  76. package/.framework/functions/cil-embeddings.ts +453 -364
  77. package/.framework/functions/cil-graph.ts +427 -142
  78. package/.framework/functions/cil-ingest.ts +1418 -1154
  79. package/.framework/functions/cil-pipeline.ts +276 -21
  80. package/.framework/functions/cil-project.ts +659 -132
  81. package/.framework/functions/cil-validate.ts +198 -164
  82. package/.framework/functions/debug-auth.ts +57 -55
  83. package/.framework/functions/deliveries.ts +80 -21
  84. package/.framework/functions/embeddings.ts +1023 -318
  85. package/.framework/functions/eval-harness.ts +146 -108
  86. package/.framework/functions/integration-outbound.ts +205 -75
  87. package/.framework/functions/integration-routes.ts +128 -23
  88. package/.framework/functions/integrations.ts +125 -100
  89. package/.framework/functions/invites.ts +1095 -460
  90. package/.framework/functions/item-progress.ts +158 -102
  91. package/.framework/functions/kb.ts +34 -10
  92. package/.framework/functions/logs.ts +133 -133
  93. package/.framework/functions/observability.ts +152 -122
  94. package/.framework/functions/pipeline-executions.ts +147 -132
  95. package/.framework/functions/pipelines.ts +134 -119
  96. package/.framework/functions/prompt-configs.ts +96 -80
  97. package/.framework/functions/roles.ts +112 -82
  98. package/.framework/functions/security.ts +450 -0
  99. package/.framework/functions/system-cron.ts +117 -80
  100. package/.framework/functions/system.ts +158 -98
  101. package/.framework/functions/tests.ts +29 -29
  102. package/.framework/functions/timers.ts +94 -95
  103. package/.framework/functions/trigger-scheduler.ts +10 -0
  104. package/.framework/functions/triggers.ts +179 -149
  105. package/.framework/functions/types.ts +137 -125
  106. package/.framework/mcp/index.ts +133 -0
  107. package/.framework/mcp/load-env.ts +60 -0
  108. package/.framework/mcp/shared/app-manifest.ts +275 -0
  109. package/.framework/mcp/shared/db.ts +477 -0
  110. package/.framework/mcp/shared/kb.ts +118 -0
  111. package/.framework/mcp/tools/architect.ts +431 -0
  112. package/.framework/mcp/tools/cil/context.ts +345 -0
  113. package/.framework/mcp/tools/cil/graph.ts +242 -0
  114. package/.framework/mcp/tools/cil/ingest.ts +1217 -0
  115. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
  116. package/.framework/mcp/tools/cil/pipeline.ts +767 -0
  117. package/.framework/mcp/tools/cil/project.ts +791 -0
  118. package/.framework/mcp/tools/cil/retrieval.ts +336 -0
  119. package/.framework/mcp/tools/cil/search-core.ts +454 -0
  120. package/.framework/mcp/tools/cil/search.ts +97 -0
  121. package/.framework/mcp/tools/cil/suggest.ts +186 -0
  122. package/.framework/mcp/tools/cil/validate.ts +1369 -0
  123. package/.framework/mcp/tools/kb/delete.ts +139 -0
  124. package/.framework/mcp/tools/kb/index.ts +52 -0
  125. package/.framework/mcp/tools/kb/ingest.ts +84 -0
  126. package/.framework/mcp/tools/kb/list-types.ts +102 -0
  127. package/.framework/mcp/tools/kb/publish.ts +100 -0
  128. package/.framework/mcp/tools/kb/reindex.ts +86 -0
  129. package/.framework/mcp/tools/kb/revoke.ts +85 -0
  130. package/.framework/mcp/tools/kb/search.ts +101 -0
  131. package/.framework/mcp/tools/kb/upsert.ts +125 -0
  132. package/.framework/mcp/tools/observability.ts +510 -0
  133. package/.framework/migrations/001_core_schema.sql +15 -0
  134. package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
  135. package/.framework/migrations/003_core_system_data.sql +16 -1
  136. package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
  137. package/.framework/migrations/005_core_observability.sql +17 -1
  138. package/.framework/migrations/006_security_hardening_rls.sql +15 -0
  139. package/.framework/migrations/007_kb_article_list.sql +17 -1
  140. package/.framework/migrations/008_items_target.sql +15 -0
  141. package/.framework/migrations/009_roles_slug_unique.sql +15 -0
  142. package/.framework/migrations/010_auth_user_trigger.sql +15 -0
  143. package/.framework/migrations/011_spine_system_account.sql +16 -0
  144. package/.framework/migrations/012_service_role_grants.sql +16 -0
  145. package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
  146. package/.framework/migrations/014_apps_required_roles.sql +16 -0
  147. package/.framework/migrations/015_types_kind_slug_unique.sql +20 -0
  148. package/.framework/migrations/016_cil_types.sql +281 -0
  149. package/.framework/migrations/017_cil_stable_ids.sql +37 -0
  150. package/.framework/migrations/018_runtime_tables.sql +129 -0
  151. package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
  152. package/.framework/migrations/020_schema_reconciliation.sql +67 -0
  153. package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
  154. package/.framework/migrations/032_cil_status_migration.sql +73 -0
  155. package/.framework/migrations/033_cil_versioning.sql +73 -0
  156. package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
  157. package/.framework/migrations/035_rate_limits.sql +55 -0
  158. package/.framework/migrations/036_demo_record_flags.sql +72 -0
  159. package/.framework/migrations/037_kb_types_canonical.sql +244 -0
  160. package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
  161. package/.framework/migrations/042_registration_hierarchy.sql +309 -0
  162. package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
  163. package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
  164. package/.framework/migrations/045_security_critical_rls.sql +139 -0
  165. package/.framework/migrations/046_security_missing_rls.sql +79 -0
  166. package/.framework/migrations/047_people_updated_by.sql +27 -0
  167. package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
  168. package/.framework/migrations/049_security_blocklist.sql +148 -0
  169. package/.framework/migrations/050_notifications.sql +49 -0
  170. package/.framework/migrations/051_message_agent_fields.sql +74 -0
  171. package/.framework/migrations/052_registration_security.sql +421 -0
  172. package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
  173. package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
  174. package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
  175. package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
  176. package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
  177. package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
  178. package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
  179. package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
  180. package/.framework/migrations/061_fk_dedup.sql +32 -0
  181. package/.framework/public/spine-logo.svg +10 -3
  182. package/.framework/scripts/app-install-cli.ts +190 -93
  183. package/.framework/scripts/assemble-frontend.sh +127 -44
  184. package/.framework/scripts/assemble-functions.sh +128 -30
  185. package/.framework/scripts/boundary-check.sh +6 -2
  186. package/.framework/scripts/build-manifest.sh +24 -68
  187. package/.framework/scripts/check-core-integrity.sh +15 -76
  188. package/.framework/scripts/checksum-manifest.mts +343 -0
  189. package/.framework/scripts/cil-migrate-markers.ts +403 -0
  190. package/.framework/scripts/fix-cil-markers.mjs +176 -0
  191. package/.framework/scripts/verify-integrity.sh +21 -57
  192. package/.framework/scripts/verify-manifest.mts +305 -0
  193. package/.framework/seeds/types/funnel_signal_use.json +74 -21
  194. package/.framework/seeds/types/spine_instance.json +73 -21
  195. package/.framework/src/App.tsx +118 -72
  196. package/.framework/src/apps/admin/index.tsx +855 -58
  197. package/.framework/src/components/AppWrapper.tsx +43 -45
  198. package/.framework/src/components/CustomAppLoader.tsx +160 -116
  199. package/.framework/src/components/admin/AdminListPage.tsx +46 -44
  200. package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
  201. package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
  202. package/.framework/src/components/admin/PageInstructions.tsx +98 -2
  203. package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
  204. package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
  205. package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
  206. package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
  207. package/.framework/src/components/app-sidebar.tsx +42 -45
  208. package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
  209. package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
  210. package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
  211. package/.framework/src/components/kb/KBArticleList.tsx +251 -10
  212. package/.framework/src/components/layout/AppShell.tsx +51 -45
  213. package/.framework/src/components/layout/Header.tsx +42 -43
  214. package/.framework/src/components/layout/Layout.tsx +46 -46
  215. package/.framework/src/components/layout/Sidebar.tsx +196 -96
  216. package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
  217. package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
  218. package/.framework/src/components/runtime/DataFilters.tsx +49 -74
  219. package/.framework/src/components/runtime/DataHeader.tsx +53 -32
  220. package/.framework/src/components/runtime/DataListPage.tsx +51 -155
  221. package/.framework/src/components/runtime/DataStats.tsx +66 -44
  222. package/.framework/src/components/runtime/DataTable.tsx +49 -149
  223. package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
  224. package/.framework/src/components/runtime/index.ts +119 -16
  225. package/.framework/src/components/search-form.tsx +46 -29
  226. package/.framework/src/components/shared/AgentView.tsx +65 -48
  227. package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
  228. package/.framework/src/components/shared/InviteManager.tsx +207 -97
  229. package/.framework/src/components/shared/MemberList.tsx +97 -0
  230. package/.framework/src/components/shared/SchemaFields.tsx +107 -86
  231. package/.framework/src/components/ui/DataTable.tsx +80 -68
  232. package/.framework/src/components/ui/Form.tsx +157 -146
  233. package/.framework/src/components/ui/ItemCard.tsx +74 -67
  234. package/.framework/src/components/ui/ItemListView.tsx +73 -68
  235. package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
  236. package/.framework/src/components/ui/Modal.tsx +78 -68
  237. package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
  238. package/.framework/src/components/ui/accordion.tsx +85 -86
  239. package/.framework/src/components/ui/alert-dialog.tsx +197 -184
  240. package/.framework/src/components/ui/alert.tsx +105 -94
  241. package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
  242. package/.framework/src/components/ui/avatar.tsx +122 -107
  243. package/.framework/src/components/ui/badge.tsx +60 -55
  244. package/.framework/src/components/ui/breadcrumb.tsx +127 -128
  245. package/.framework/src/components/ui/button-group.tsx +86 -72
  246. package/.framework/src/components/ui/button.tsx +71 -50
  247. package/.framework/src/components/ui/calendar.tsx +72 -57
  248. package/.framework/src/components/ui/card.tsx +136 -129
  249. package/.framework/src/components/ui/carousel.tsx +226 -107
  250. package/.framework/src/components/ui/chart.tsx +252 -147
  251. package/.framework/src/components/ui/checkbox.tsx +43 -44
  252. package/.framework/src/components/ui/collapsible.tsx +86 -71
  253. package/.framework/src/components/ui/combobox.tsx +282 -249
  254. package/.framework/src/components/ui/command.tsx +158 -146
  255. package/.framework/src/components/ui/context-menu.tsx +239 -240
  256. package/.framework/src/components/ui/dialog.tsx +174 -157
  257. package/.framework/src/components/ui/direction.tsx +72 -56
  258. package/.framework/src/components/ui/drawer.tsx +168 -169
  259. package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
  260. package/.framework/src/components/ui/empty.tsx +131 -120
  261. package/.framework/src/components/ui/field.tsx +205 -173
  262. package/.framework/src/components/ui/hover-card.tsx +71 -72
  263. package/.framework/src/components/ui/input-group.tsx +148 -133
  264. package/.framework/src/components/ui/input-otp.tsx +99 -86
  265. package/.framework/src/components/ui/input.tsx +43 -41
  266. package/.framework/src/components/ui/item.tsx +207 -185
  267. package/.framework/src/components/ui/kbd.tsx +57 -56
  268. package/.framework/src/components/ui/label.tsx +45 -41
  269. package/.framework/src/components/ui/menubar.tsx +268 -236
  270. package/.framework/src/components/ui/native-select.tsx +85 -86
  271. package/.framework/src/components/ui/navigation-menu.tsx +166 -156
  272. package/.framework/src/components/ui/pagination.tsx +131 -141
  273. package/.framework/src/components/ui/popover.tsx +125 -128
  274. package/.framework/src/components/ui/progress.tsx +47 -44
  275. package/.framework/src/components/ui/radio-group.tsx +60 -55
  276. package/.framework/src/components/ui/resizable.tsx +85 -72
  277. package/.framework/src/components/ui/scroll-area.tsx +57 -58
  278. package/.framework/src/components/ui/select.tsx +182 -169
  279. package/.framework/src/components/ui/separator.tsx +42 -43
  280. package/.framework/src/components/ui/sheet.tsx +170 -159
  281. package/.framework/src/components/ui/sidebar.tsx +395 -398
  282. package/.framework/src/components/ui/skeleton.tsx +42 -43
  283. package/.framework/src/components/ui/slider.tsx +61 -44
  284. package/.framework/src/components/ui/sonner.tsx +44 -44
  285. package/.framework/src/components/ui/spinner.tsx +47 -42
  286. package/.framework/src/components/ui/switch.tsx +56 -42
  287. package/.framework/src/components/ui/table-primitives.tsx +272 -132
  288. package/.framework/src/components/ui/table.tsx +140 -142
  289. package/.framework/src/components/ui/tabs.tsx +110 -93
  290. package/.framework/src/components/ui/textarea.tsx +43 -44
  291. package/.framework/src/components/ui/toggle-group.tsx +87 -71
  292. package/.framework/src/components/ui/toggle.tsx +56 -56
  293. package/.framework/src/components/ui/tooltip.tsx +99 -85
  294. package/.framework/src/contexts/AppContext.tsx +166 -114
  295. package/.framework/src/contexts/AuthContext.tsx +232 -137
  296. package/.framework/src/hooks/use-mobile.ts +43 -44
  297. package/.framework/src/hooks/useApi.ts +185 -172
  298. package/.framework/src/hooks/useAppPath.ts +31 -29
  299. package/.framework/src/hooks/useApps.ts +77 -42
  300. package/.framework/src/hooks/useEntityList.ts +66 -29
  301. package/.framework/src/hooks/useEntityRecord.ts +76 -32
  302. package/.framework/src/hooks/useForm.ts +62 -30
  303. package/.framework/src/hooks/useListSchema.ts +60 -43
  304. package/.framework/src/hooks/useSchemaRecord.ts +115 -95
  305. package/.framework/src/lib/api.ts +258 -134
  306. package/.framework/src/lib/registration.ts +181 -0
  307. package/.framework/src/lib/supabase.ts +118 -81
  308. package/.framework/src/lib/utils.ts +266 -266
  309. package/.framework/src/main.tsx +43 -44
  310. package/.framework/src/pages/DashboardPage.tsx +44 -43
  311. package/.framework/src/pages/NoAppPage.tsx +50 -0
  312. package/.framework/src/pages/NotFoundPage.tsx +32 -29
  313. package/.framework/src/pages/SetupPage.tsx +34 -33
  314. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
  315. package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
  316. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
  317. package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
  318. package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
  319. package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
  320. package/.framework/src/pages/admin/AppsPage.tsx +45 -42
  321. package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
  322. package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
  323. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
  324. package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
  325. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
  326. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
  327. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
  328. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
  329. package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
  330. package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
  331. package/.framework/src/pages/admin/LogsPage.tsx +87 -137
  332. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
  333. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
  334. package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
  335. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
  336. package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
  337. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
  338. package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
  339. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
  340. package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
  341. package/.framework/src/pages/admin/RolesPage.tsx +47 -164
  342. package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
  343. package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
  344. package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
  345. package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
  346. package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
  347. package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
  348. package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
  349. package/.framework/src/pages/admin/TimersPage.tsx +44 -42
  350. package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
  351. package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
  352. package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
  353. package/.framework/src/pages/admin/TypesPage.tsx +45 -42
  354. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
  355. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
  356. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
  357. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
  358. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
  359. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
  360. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
  361. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
  362. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
  363. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
  364. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
  365. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
  366. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  367. package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
  368. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
  369. package/.framework/src/pages/auth/LoginPage.tsx +38 -32
  370. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
  371. package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
  372. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
  373. package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
  374. package/.framework/src/types/auth.ts +101 -93
  375. package/.framework/src/types/types.ts +283 -262
  376. package/.framework/templates/project.json +87 -0
  377. package/.framework/templates/spine.config.neutral.json +7 -0
  378. package/.framework/tests/api/api-surface.test.ts +381 -0
  379. package/.framework/tests/fixtures/seed.ts +427 -0
  380. package/.framework/tests/fixtures/teardown.ts +228 -0
  381. package/.framework/tests/reporter.ts +351 -0
  382. package/.framework/tests/setup.ts +135 -0
  383. package/.framework/tests/ui/ui-sweep.ts +278 -0
  384. package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
  385. package/.framework/tests/unit/agent-runner.test.ts +2389 -0
  386. package/.framework/tests/unit/ai-interactions.test.ts +81 -0
  387. package/.framework/tests/unit/app-manifest.test.ts +359 -0
  388. package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
  389. package/.framework/tests/unit/cil-determinism.test.ts +85 -0
  390. package/.framework/tests/unit/core-isolation.test.ts +153 -0
  391. package/.framework/tests/unit/funnel-signals.test.ts +135 -0
  392. package/.framework/tests/unit/install-app.test.ts +1759 -0
  393. package/.framework/tests/unit/invites-security.test.ts +572 -0
  394. package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
  395. package/.framework/tests/unit/kb-search.test.ts +119 -0
  396. package/.framework/tests/unit/migrate.test.ts +509 -0
  397. package/.framework/tests/unit/notify.test.ts +136 -0
  398. package/.framework/tests/unit/permissions.test.ts +269 -0
  399. package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
  400. package/.framework/tests/unit/principal-cache.test.ts +138 -0
  401. package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
  402. package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
  403. package/.framework/tests/unit/schema-utils.test.ts +225 -0
  404. package/.framework/tests/unit/trigger-engine.test.ts +513 -0
  405. package/.framework/tests/unit/validate-app.test.ts +527 -0
  406. package/README.md +34 -3
  407. package/bin/spine-framework.cjs +1 -0
  408. package/config/tailwind.config.cjs +7 -0
  409. package/config/tsconfig.functions.json +42 -0
  410. package/config/tsconfig.json +37 -9
  411. package/config/vite.config.ts +31 -2
  412. package/netlify.toml +31 -3
  413. package/package.json +46 -29
  414. package/scripts/assemble-frontend.sh +127 -44
  415. package/scripts/assemble-functions.sh +127 -29
  416. package/scripts/boundary-check.sh +6 -2
  417. package/scripts/build-manifest.sh +24 -68
  418. package/scripts/checksum-manifest.mts +345 -0
  419. package/scripts/dev-clean.sh +86 -0
  420. package/scripts/dev-netlify.sh +49 -0
  421. package/scripts/netlify-dev-wrapper.sh +2 -0
  422. package/scripts/verify-install.ts +358 -0
  423. package/scripts/verify-integrity.sh +21 -57
  424. package/scripts/verify-manifest.mts +305 -0
  425. package/templates/project/gitignore +45 -0
  426. package/{config → templates/project}/package-lock.json +22335 -9366
  427. package/vitest.config.ts +46 -0
  428. package/.framework/public/spine.config.json +0 -91
  429. package/config/deno.lock +0 -108
  430. package/config/tsconfig.node.tsbuildinfo +0 -1
  431. package/config/tsconfig.tsbuildinfo +0 -1
  432. package/config/vite.config.d.ts +0 -2
  433. package/custom/.devin/AGENT.md +0 -357
  434. package/custom/.devin/skills/install-app/SKILL.md +0 -39
  435. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
  436. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
  437. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
  438. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
  439. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
  440. package/custom/.devin/workflows/app-install.yaml +0 -83
  441. package/custom/README.md +0 -79
  442. package/docs/README.md +0 -15
  443. package/scripts/app-install-cli.ts +0 -286
  444. package/scripts/check-core-integrity.sh +0 -82
  445. package/scripts/ingest-chunks.cjs +0 -202
  446. package/scripts/kb-chunk-parser.cjs +0 -312
  447. package/scripts/kb-chunk-parser.ts +0 -330
  448. package/scripts/kb-ingest-docs.ts +0 -203
  449. package/scripts/load-test-app-install.ts +0 -484
  450. package/scripts/test-login.js +0 -33
  451. 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"]