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,345 @@
1
+ /**
2
+ * @module functions/_shared
3
+ * @audience internal
4
+ * @layer security
5
+ * @stability stable
6
+ *
7
+ * Shared security enforcement: denylist lookups (security_blocklist),
8
+ * client geo stamping, prompt-injection detection, and app security
9
+ * config resolution. INVARIANT: every check fails OPEN when the backing
10
+ * table/RPC is absent so installs without migration 049 keep working.
11
+ *
12
+ * @seeAlso .framework/functions/_shared/middleware.ts (enforcement point)
13
+ * @seeAlso .framework/functions/_shared/rate-limit.ts (violation signal)
14
+ * @seeAlso .framework/migrations/049_security_blocklist.sql (schema)
15
+ * @seeAlso .framework/functions/security.ts (admin actions)
16
+ *
17
+ * @cil-generated
18
+ */
19
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/_shared/security.ts"]
20
+ // CIL[DOC-MACRO purpose="Shared security enforcement: denylist lookups, geo stamping, injection detection, app security config"]
21
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide fail-open helpers for blocklist enforcement (request + registration), people.data geo stamping via RPC, prompt-injection pattern detection, and cached apps.config reads"]
22
+ // CIL[DOC-CONTEXT module="functions/_shared" layer="security" related_files=".framework/functions/_shared/middleware.ts,.framework/functions/_shared/rate-limit.ts,.framework/functions/_shared/geo.ts,.framework/migrations/049_security_blocklist.sql,.framework/functions/security.ts"]
23
+ // CIL[DOC-CONTRACT inputs="RequestContext / person id / email / ip / appId" outputs="boolean blocked, matched pattern, void RPC stamps, app config" errors="never throws on infra failure — fail-open" side_effects="security_blocklist reads, people.data writes, logs writes via emitAudit"]
24
+ // CIL[DOC-MICRO logic_summary="Blocklist queries filter type+value with revoked/expired exclusion in JS; geo stamping uses SECURITY DEFINER RPCs to merge people.data keys; app config cached 60s per warm instance" notable_decisions="Fail-open everywhere: a missing 049 table must never deny legitimate traffic; IP block entries are also enforced at the Netlify edge but the app-layer check covers direct function hits"]
25
+ // CIL[DOC-STABILITY stability="stable"]
26
+ // CIL[DOC-AUDIENCE audience="internal"]
27
+
28
+ import { adminDb } from './db'
29
+ import { getClientGeo } from './geo'
30
+ import { checkRateLimit } from './rate-limit'
31
+ import type { RateLimitPolicy } from './rate-limit'
32
+ import { emitAudit } from './audit'
33
+ import type { RequestContext } from './middleware'
34
+ import type { Principal } from './principal'
35
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:module_header:module_header"]
36
+
37
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:injection_patterns:constant" type="constant" identity="INJECTION_PATTERNS" parent="~/.framework/functions/_shared/security.ts"]
38
+ // CIL[DOC-MACRO purpose="Prompt-injection detection patterns"]
39
+ // CIL[DOC-RESPONSIBILITY responsibility="Named regex set matching known instruction-override phrasings"]
40
+ // CIL[DOC-CONTRACT inputs="none" outputs="[name, RegExp][]" errors="none" side_effects="none"]
41
+ // CIL[DOC-MICRO logic_summary="Each entry pairs a stable label (stored on the flagged record) with a case-insensitive pattern" notable_decisions="First-pass heuristic — deliberately narrow to keep false positives low; catches verbatim 'ignore previous instructions' style attacks, not obfuscated variants"]
42
+ // CIL[DOC-STABILITY stability="stable"]
43
+ export const INJECTION_PATTERNS: Array<[string, RegExp]> = [
44
+ ['ignore_instructions', /ignore\s+(all\s+)?(the\s+)?(previous|prior|above|earlier)\s+(instruction|prompt|rule|context)s?/i],
45
+ ['role_reassignment', /you\s+are\s+now\s+(a\s+|an\s+)?(system\s+)?(administrator|admin|root|developer|system\s+prompt)/i],
46
+ ['exfil_everything', /(output|print|return|dump|reveal|leak)\s+(the\s+)?(contents|full\s+text|entire|all)\s+(of\s+)?(every|all|the\s+system)/i],
47
+ ['disregard_prompt', /disregard\s+(your\s+)?(previous|prior|system|all)\s+(prompt|instruction|rule|guideline)s?/i],
48
+ ['dan_jailbreak', /\bDAN\s+(mode|jailbreak)\b|\bdo\s+anything\s+now\b/i],
49
+ ['fake_system_prefix', /^\s*system\s*:\s*/im],
50
+ ['prompt_echo', /repeat\s+(back\s+)?(your\s+)?(system\s+)?(prompt|instructions)\s+(verbatim|word\s+for\s+word|exactly)/i],
51
+ ]
52
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:injection_patterns:constant"]
53
+
54
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:detectinjection:function" type="function" identity="detectInjection" parent="~/.framework/functions/_shared/security.ts"]
55
+ // CIL[DOC-MACRO purpose="Detect prompt-injection phrasing in user text"]
56
+ // CIL[DOC-RESPONSIBILITY responsibility="Return the matched pattern name or null"]
57
+ // CIL[DOC-CONTRACT inputs="text: string" outputs="string | null (pattern name)" errors="none" side_effects="none"]
58
+ // CIL[DOC-MICRO logic_summary="Linear scan over INJECTION_PATTERNS; returns first match label" notable_decisions="[]"]
59
+ // CIL[DOC-STABILITY stability="stable"]
60
+ export function detectInjection(text: string | null | undefined): string | null {
61
+ if (!text || typeof text !== 'string') return null
62
+ for (const [name, re] of INJECTION_PATTERNS) {
63
+ if (re.test(text)) return name
64
+ }
65
+ return null
66
+ }
67
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:detectinjection:function"]
68
+
69
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:isactiverule:function" type="function" identity="isActiveRule" parent="~/.framework/functions/_shared/security.ts"]
70
+ // CIL[DOC-MACRO purpose="Filter revoked/expired blocklist rows in JS"]
71
+ // CIL[DOC-RESPONSIBILITY responsibility="Evaluate revoked_at and expires_at client-side"]
72
+ // CIL[DOC-CONTRACT inputs="row: {revoked_at, expires_at}" outputs="boolean" errors="none" side_effects="none"]
73
+ // CIL[DOC-MICRO logic_summary="Active = not revoked and (no expiry or expiry in future)" notable_decisions="[]"]
74
+ // CIL[DOC-STABILITY stability="stable"]
75
+ function isActiveRule(row: { revoked_at?: string | null; expires_at?: string | null }): boolean {
76
+ if (row.revoked_at) return false
77
+ if (row.expires_at && Date.parse(row.expires_at) <= Date.now()) return false
78
+ return true
79
+ }
80
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:isactiverule:function"]
81
+
82
+ // Blocklist decision cache — every authenticated request hits lookupBlocklist,
83
+ // which is a DB round-trip even when the table is empty. Cache the decision per
84
+ // candidate set for a bounded window: a newly-added hard block takes effect
85
+ // within BLOCKLIST_CACHE_TTL_MS, and a revoked block releases within the same
86
+ // window. Promise memoization means concurrent requests share one query.
87
+ const BLOCKLIST_CACHE_TTL_MS = 15_000
88
+ const BLOCKLIST_CACHE_MAX = 1000
89
+ const blocklistCache = new Map<string, { value: Promise<any>; expiresAt: number }>()
90
+
91
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:lookupblocklist:function" type="function" identity="lookupBlocklist" parent="~/.framework/functions/_shared/security.ts"]
92
+ // CIL[DOC-MACRO purpose="Query security_blocklist for a set of (type,value) candidates"]
93
+ // CIL[DOC-RESPONSIBILITY responsibility="Single .or() query across candidate identities; returns the matching active row or null"]
94
+ // CIL[DOC-CONTRACT inputs="checks: {type,value}[], severity filter implicit ('hard')" outputs="blocklist row | null" errors="returns null on any query failure — fail-open" side_effects="DB read"]
95
+ // CIL[DOC-MICRO logic_summary="Builds a PostgREST or() of and(type.eq.,value.eq.) clauses; filters revoked/expired in JS" notable_decisions="severity 'hard' only — 'soft' entries are advisory signals for the report, not request blocks"]
96
+ // CIL[DOC-STABILITY stability="stable"]
97
+ async function lookupBlocklist(
98
+ checks: Array<{ type: string; value: string }>,
99
+ ): Promise<{ type: string; value: string; reason: string | null } | null> {
100
+ if (!checks.length) return null
101
+ try {
102
+ const or = checks
103
+ .map(c => `and(type.eq.${c.type},value.eq.${c.value})`)
104
+ .join(',')
105
+ const { data, error } = await adminDb
106
+ .from('security_blocklist')
107
+ .select('type, value, reason, severity, revoked_at, expires_at')
108
+ .eq('severity', 'hard')
109
+ .or(or)
110
+ .limit(5)
111
+ if (error) {
112
+ console.warn('blocklist lookup failed (fail-open):', error.message)
113
+ return null
114
+ }
115
+ return (data || []).find(isActiveRule) ?? null
116
+ } catch (err: any) {
117
+ console.warn('blocklist lookup error (fail-open):', err?.message)
118
+ return null
119
+ }
120
+ }
121
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:lookupblocklist:function"]
122
+
123
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:isblockedrequest:function" type="function" identity="isBlockedRequest" parent="~/.framework/functions/_shared/security.ts"]
124
+ // CIL[DOC-MACRO purpose="Check whether the current request identity is hard-blocked"]
125
+ // CIL[DOC-RESPONSIBILITY responsibility="Match person_id / account_id / email / client IP against security_blocklist"]
126
+ // CIL[DOC-CONTRACT inputs="ctx: RequestContext (headers required for IP)" outputs="blocking row | null" errors="never throws" side_effects="DB read"]
127
+ // CIL[DOC-MICRO logic_summary="Skips machine principals and anonymous callers; candidates assembled then one lookupBlocklist call" notable_decisions="Machine principals (cron/api-key/system) are never blocklist-denied — they are infrastructure, not users"]
128
+ // CIL[DOC-STABILITY stability="stable"]
129
+ export async function isBlockedRequest(ctx: RequestContext): Promise<{ type: string; value: string; reason: string | null } | null> {
130
+ const p: Principal | null = ctx.principal as any
131
+ const { ip } = getClientGeo(ctx)
132
+
133
+ const checks: Array<{ type: string; value: string }> = []
134
+ if (p && p.type === 'human' && p.id && p.id !== 'anonymous') {
135
+ checks.push({ type: 'person_id', value: p.id })
136
+ if (p.accountId) checks.push({ type: 'account_id', value: p.accountId })
137
+ if (p.email) checks.push({ type: 'email', value: p.email.toLowerCase() })
138
+ }
139
+ if (ip) checks.push({ type: 'ip', value: ip })
140
+ if (!checks.length) return null
141
+
142
+ const key = checks.map(c => `${c.type}=${c.value}`).sort().join('|')
143
+ const hit = blocklistCache.get(key)
144
+ if (hit && hit.expiresAt > Date.now()) return hit.value
145
+ blocklistCache.delete(key)
146
+ const pending = lookupBlocklist(checks)
147
+ if (blocklistCache.size >= BLOCKLIST_CACHE_MAX) {
148
+ const oldest = blocklistCache.keys().next().value
149
+ if (oldest !== undefined) blocklistCache.delete(oldest)
150
+ }
151
+ blocklistCache.set(key, { value: pending, expiresAt: Date.now() + BLOCKLIST_CACHE_TTL_MS })
152
+ pending.catch(() => blocklistCache.delete(key))
153
+ return pending
154
+ }
155
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:isblockedrequest:function"]
156
+
157
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:isblockedip:function" type="function" identity="isBlockedIp" parent="~/.framework/functions/_shared/security.ts"]
158
+ // CIL[DOC-MACRO purpose="Check whether a raw client IP is hard-blocked"]
159
+ // CIL[DOC-RESPONSIBILITY responsibility="IP-only variant for public/unauthenticated paths"]
160
+ // CIL[DOC-CONTRACT inputs="ip: string | undefined" outputs="blocking row | null" errors="never throws" side_effects="DB read"]
161
+ // CIL[DOC-MICRO logic_summary="Wraps lookupBlocklist with a single ip candidate" notable_decisions="[]"]
162
+ // CIL[DOC-STABILITY stability="stable"]
163
+ export async function isBlockedIp(ip: string | undefined): Promise<{ type: string; value: string; reason: string | null } | null> {
164
+ if (!ip) return null
165
+ return lookupBlocklist([{ type: 'ip', value: ip }])
166
+ }
167
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:isblockedip:function"]
168
+
169
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:checkregistrationblocklist:function" type="function" identity="checkRegistrationBlocklist" parent="~/.framework/functions/_shared/security.ts"]
170
+ // CIL[DOC-MACRO purpose="Deny registration for blocklisted email, domain, or IP"]
171
+ // CIL[DOC-RESPONSIBILITY responsibility="Throw a 403 when the signup email, its domain, or the client IP is on the denylist; emit a security audit event on hit"]
172
+ // CIL[DOC-CONTRACT inputs="ctx, email, ip" outputs="void" errors="Error (403) when blocked" side_effects="DB read; audit log on hit"]
173
+ // CIL[DOC-MICRO logic_summary="Candidates: email (lowercased), email_domain, ip — one lookup; emits security.registration_blocked audit on match" notable_decisions="Error messages deliberately generic — do not tell an attacker which signal matched"]
174
+ // CIL[DOC-STABILITY stability="stable"]
175
+ export async function checkRegistrationBlocklist(
176
+ ctx: RequestContext,
177
+ email: string,
178
+ ip?: string,
179
+ ): Promise<void> {
180
+ const normalized = (email || '').toLowerCase()
181
+ const domain = normalized.split('@')[1]
182
+ const checks: Array<{ type: string; value: string }> = [
183
+ { type: 'email', value: normalized },
184
+ ]
185
+ if (domain) checks.push({ type: 'email_domain', value: domain })
186
+ if (ip) checks.push({ type: 'ip', value: ip })
187
+
188
+ const hit = await lookupBlocklist(checks)
189
+ if (!hit) return
190
+
191
+ void emitAudit(ctx, 'security.registration_blocked', { type: 'registration' }, {
192
+ result: 'denied',
193
+ blocked_type: hit.type,
194
+ reason: hit.reason,
195
+ })
196
+ const err: any = new Error('This registration is not eligible. Contact support if you believe this is an error.')
197
+ err.statusCode = 403
198
+ throw err
199
+ }
200
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:checkregistrationblocklist:function"]
201
+
202
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:geo_touch_throttle:comment" type="comment" identity="geoTouchThrottle" parent="~/.framework/functions/_shared/security.ts"]
203
+ // CIL[DOC-MACRO purpose="Per-person throttle state for geo last-seen writes"]
204
+ // CIL[DOC-RESPONSIBILITY responsibility="Bound person_touch_geo RPC writes to at most once per GEO_TOUCH_TTL_MS per person per warm instance"]
205
+ // CIL[DOC-CONTEXT module=".framework/functions/_shared/security.ts" layer="shared-core" related_files=".framework/functions/_shared/middleware.ts"]
206
+ // CIL[DOC-CONTRACT inputs="none" outputs="module-level Map<personId, epochMs>" errors="none" side_effects="in-memory throttle state; resets on cold start, which is acceptable"]
207
+ // CIL[DOC-MICRO logic_summary="Chatty clients (polling UIs) would otherwise trigger one RPC write per request; last_seen_at does not need sub-minute granularity" notable_decisions="per warm instance so no extra reads are needed; entries are small so growth is bounded by unique persons per instance"]
208
+ // CIL[DOC-AUDIENCE audience="framework"]
209
+ // CIL[DOC-STABILITY stability="stable"]
210
+ const GEO_TOUCH_TTL_MS = 5 * 60 * 1000
211
+ const geoTouchCache = new Map<string, number>()
212
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:geo_touch_throttle:comment"]
213
+
214
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:touchpersongeo:function" type="function" identity="touchPersonGeo" parent="~/.framework/functions/_shared/security.ts"]
215
+ // CIL[DOC-MACRO purpose="Stamp last-seen IP/country onto a person row"]
216
+ // CIL[DOC-RESPONSIBILITY responsibility="Fire-and-forget RPC merge of last_ip/last_country/last_seen_at into people.data, throttled per person per warm instance"]
217
+ // CIL[DOC-CONTRACT inputs="personId, ctx (headers for geo)" outputs="void" errors="never throws — swallowed with warn" side_effects="people.data update at most once per GEO_TOUCH_TTL_MS per person per instance"]
218
+ // CIL[DOC-MICRO logic_summary="Calls person_touch_geo RPC only when geo headers resolved; no-op in local dev without Netlify geo headers; in-memory Map throttles repeat touches inside GEO_TOUCH_TTL_MS" notable_decisions="Missing RPC (pre-049 install) is swallowed — callers must not await for correctness; throttle is per warm instance so it needs no extra reads"]
219
+ // CIL[DOC-STABILITY stability="stable"]
220
+ export async function touchPersonGeo(personId: string, ctx: RequestContext): Promise<void> {
221
+ try {
222
+ if (!personId || personId === 'anonymous') return
223
+ const { ip, country } = getClientGeo(ctx)
224
+ if (!ip && !country) return
225
+ // Chatty clients (polling UIs) otherwise trigger one RPC write per
226
+ // request. last_seen_at doesn't need sub-minute granularity — skip the
227
+ // write if this warm instance stamped the person recently.
228
+ const last = geoTouchCache.get(personId) || 0
229
+ if (Date.now() - last < GEO_TOUCH_TTL_MS) return
230
+ geoTouchCache.set(personId, Date.now())
231
+ await adminDb.rpc('person_touch_geo', {
232
+ p_person_id: personId,
233
+ p_ip: ip ?? null,
234
+ p_country: country ?? null,
235
+ })
236
+ } catch (err: any) {
237
+ console.warn('touchPersonGeo failed (non-fatal):', err?.message)
238
+ }
239
+ }
240
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:touchpersongeo:function"]
241
+
242
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:recordregistrationgeo:function" type="function" identity="recordRegistrationGeo" parent="~/.framework/functions/_shared/security.ts"]
243
+ // CIL[DOC-MACRO purpose="Stamp first-seen registration IP/country onto a person row"]
244
+ // CIL[DOC-RESPONSIBILITY responsibility="RPC merge of registration_ip/registration_country that never overwrites existing values"]
245
+ // CIL[DOC-CONTRACT inputs="personId, ctx" outputs="void" errors="never throws" side_effects="people.data update"]
246
+ // CIL[DOC-MICRO logic_summary="Calls person_record_registration_geo (COALESCE keeps first values)" notable_decisions="[]"]
247
+ // CIL[DOC-STABILITY stability="stable"]
248
+ export async function recordRegistrationGeo(personId: string, ctx: RequestContext): Promise<void> {
249
+ try {
250
+ if (!personId || personId === 'anonymous') return
251
+ const { ip, country } = getClientGeo(ctx)
252
+ if (!ip && !country) return
253
+ await adminDb.rpc('person_record_registration_geo', {
254
+ p_person_id: personId,
255
+ p_ip: ip ?? null,
256
+ p_country: country ?? null,
257
+ })
258
+ } catch (err: any) {
259
+ console.warn('recordRegistrationGeo failed (non-fatal):', err?.message)
260
+ }
261
+ }
262
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:recordregistrationgeo:function"]
263
+
264
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:appconfigcache:comment" type="comment" identity="appConfigCache" parent="~/.framework/functions/_shared/security.ts"]
265
+ // CIL[DOC-MACRO purpose="60s per-instance cache of apps.config to keep hot paths cheap"]
266
+ // CIL[DOC-RESPONSIBILITY responsibility="Avoid an apps-table read on every admin-data create when no rate limits are configured"]
267
+ // CIL[DOC-MICRO logic_summary="Map<slug,{config,at}> with TTL; stale entries refreshed lazily" notable_decisions="Cache lives only as long as the warm lambda; config changes propagate within 60s"]
268
+ // CIL[DOC-CONTRACT inputs="appId (getAppConfig)" outputs="apps.config JSON or null" errors="returns null on lookup failure" side_effects="one apps-table read per slug per TTL window"]
269
+ // CIL[DOC-STABILITY stability="stable"]
270
+ const APP_CONFIG_TTL_MS = 60_000
271
+ const appConfigCache = new Map<string, { config: any; at: number }>()
272
+
273
+ async function getAppConfig(appId: string | null | undefined): Promise<any | null> {
274
+ if (!appId) return null
275
+ const hit = appConfigCache.get(appId)
276
+ if (hit && Date.now() - hit.at < APP_CONFIG_TTL_MS) return hit.config
277
+ try {
278
+ const { data } = await adminDb
279
+ .from('apps')
280
+ .select('config')
281
+ .eq('slug', appId)
282
+ .maybeSingle()
283
+ const config = data?.config ?? null
284
+ appConfigCache.set(appId, { config, at: Date.now() })
285
+ return config
286
+ } catch {
287
+ return null
288
+ }
289
+ }
290
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:appconfigcache:comment"]
291
+
292
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:getappsecurityconfig:function" type="function" identity="getAppSecurityConfig" parent="~/.framework/functions/_shared/security.ts"]
293
+ // CIL[DOC-MACRO purpose="Resolve an app's security config block"]
294
+ // CIL[DOC-CONTRACT inputs="appId" outputs="config.security object | null" errors="never throws" side_effects="cached apps read"]
295
+ // CIL[DOC-MICRO logic_summary="Returns apps.config.security — prompt_injection mode ('flag'|'block'|'off'), etc." notable_decisions="[]"]
296
+ // CIL[DOC-STABILITY stability="stable"]
297
+ export async function getAppSecurityConfig(appId: string | null | undefined): Promise<{
298
+ prompt_injection?: 'flag' | 'block' | 'off'
299
+ [key: string]: any
300
+ } | null> {
301
+ const config = await getAppConfig(appId)
302
+ return config?.security ?? null
303
+ }
304
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:getappsecurityconfig:function"]
305
+
306
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:checktypeslugestatelimit:function" type="function" identity="checkTypeSlugRateLimit" parent="~/.framework/functions/_shared/security.ts"]
307
+ // CIL[DOC-MACRO purpose="Rate-limit an admin-data create by type slug when the calling app declares a policy"]
308
+ // CIL[DOC-RESPONSIBILITY responsibility="Zero-cost pass-through unless config.rate_limits has a '<type_slug>_create' policy for the calling app"]
309
+ // CIL[DOC-CONTRACT inputs="ctx, typeSlug" outputs="void" errors="RateLimitError (429) when over a configured policy" side_effects="rate_limits upsert when policy exists"]
310
+ // CIL[DOC-MICRO logic_summary="Cached app config → policies lookup; only invokes checkRateLimit when a policy is actually declared so unconfigured creates pay no extra DB reads" notable_decisions="Convention over config: '<type_slug>_create' action names keep admin-data generic — apps opt in via manifest config.rate_limits"]
311
+ // CIL[DOC-STABILITY stability="stable"]
312
+ export async function checkTypeSlugRateLimit(ctx: RequestContext, typeSlug: string | null | undefined): Promise<void> {
313
+ if (!typeSlug) return
314
+ const action = `${typeSlug}_create`
315
+ const config = await getAppConfig(ctx.appId)
316
+ const raw = config?.rate_limits?.[action]
317
+ const policies: RateLimitPolicy[] = (Array.isArray(raw) ? raw : raw ? [raw] : [])
318
+ .filter((p: any) => typeof p?.limit === 'number' && typeof p?.window_seconds === 'number')
319
+ if (!policies.length) return
320
+ await checkRateLimit(ctx, action, { policies })
321
+ }
322
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:checktypeslugestatelimit:function"]
323
+
324
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/security.ts:emitsecurityevent:function" type="function" identity="emitSecurityEvent" parent="~/.framework/functions/_shared/security.ts"]
325
+ // CIL[DOC-MACRO purpose="Emit a security.* audit event"]
326
+ // CIL[DOC-RESPONSIBILITY responsibility="Write a logs row via emitAudit with adminDb so the security report can aggregate signals"]
327
+ // CIL[DOC-CONTRACT inputs="ctx, action ('security.x'), target, metadata" outputs="void" errors="never throws" side_effects="logs insert"]
328
+ // CIL[DOC-MICRO logic_summary="Thin wrapper forcing ctx.db=adminDb — security events must be written even when the caller's RLS db lacks logs insert" notable_decisions="[]"]
329
+ // CIL[DOC-STABILITY stability="stable"]
330
+ export async function emitSecurityEvent(
331
+ ctx: RequestContext,
332
+ action: string,
333
+ target?: { type: string; id?: string; account_id?: string },
334
+ metadata?: Record<string, any>,
335
+ ): Promise<void> {
336
+ try {
337
+ await emitAudit({ ...ctx, db: adminDb } as any, action, target || { type: 'security' }, {
338
+ result: 'denied',
339
+ ...metadata,
340
+ })
341
+ } catch (err: any) {
342
+ console.warn('emitSecurityEvent failed:', err?.message)
343
+ }
344
+ }
345
+ // CIL[END stable_id="~/.framework/functions/_shared/security.ts:emitsecurityevent:function"]