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