spine-framework 0.3.145 → 0.4.0

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