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
@@ -1,22 +1,17 @@
1
+ // CIL[BEGIN stable_id="~/.framework/src/pages/admin/RolesPage.tsx:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/src/pages/admin/RolesPage.tsx"]
2
+ // CIL[DOC-MACRO purpose="Admin roles management page module: defines the Role interface and the RolesPage React component for listing, searching, filtering, and sorting roles."]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide the top-level TypeScript module exports and role model shape consumed by the admin roles UI."]
4
+ // CIL[DOC-CONTEXT module="admin" layer="pages" related_files=".framework/src/components/admin/AdminListPage.tsx,.framework/src/components/admin/SortableTableHeader.tsx,.framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/hooks/useApi.ts,.framework/src/lib/api.ts,.framework/src/lib/utils.ts"]
5
+ // CIL[DOC-CONTRACT inputs="module imports and role API response data" outputs="Role interface and RolesPage React component" errors="Network or API errors are surfaced by useApi hook" side_effects="none at module scope"]
6
+ // CIL[DOC-MICRO logic_summary="Imports React, hooks, shared components, and utilities; exports Role type and RolesPage component for rendering the admin roles list." notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="admin-frontend"]
10
+
1
11
 
2
12
 
3
13
 
4
14
 
5
- // CIL[BEGIN chunk_id="rolespage_module_header_1783665503" type="module_header" identity="module_header" parent="none"]
6
- // CIL[DOC-MACRO purpose="Import dependencies and define Role interface for admin roles management"]
7
- // CIL[DOC-RESPONSIBILITY responsibility="Provide React imports, UI components, and Role type definition"]
8
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
9
- // CIL[DOC-CONTRACT inputs="none" outputs="React components and Role interface" errors="none" side_effects="none"]
10
- // CIL[DOC-MICRO logic_summary="Imports React hooks, UI components, and defines Role data structure" notable_decisions="[]"]
11
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
12
- // CIL[DOC-STABILITY stability="stable"]
13
- // CIL[DOC-AUDIENCE audience="developer"]
14
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
15
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
16
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
17
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
18
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
19
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
20
15
  import React, { useState } from 'react'
21
16
  import { Plus, ShieldCheck, CheckCircle, XCircle, Settings, FileText, Cog } from 'lucide-react';
22
17
  import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
@@ -27,6 +22,22 @@ import { formatDateTime } from '../../lib/utils'
27
22
  import { useApi } from '../../hooks/useApi'
28
23
  import { apiFetch } from '../../lib/api'
29
24
 
25
+
26
+ // CIL[DOC-VECTOR type="context" content="purpose='Admin roles management page module: defines the Role interface and the RolesPage React component for listing, searching, filtering, and sorting roles.'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/components/admin/AdminListPage.tsx"]
27
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
28
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
29
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
30
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
31
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
32
+ // CIL[END stable_id="~/.framework/src/pages/admin/RolesPage.tsx:module_header:module_header"]
33
+ // CIL[BEGIN stable_id="~/.framework/src/pages/admin/RolesPage.tsx:Role:interface" type="interface" identity="Role" parent="~/.framework/src/pages/admin/RolesPage.tsx"]
34
+ // CIL[DOC-MACRO purpose="Define the shape of a role record for use in the admin roles list and related operations."]
35
+ // CIL[DOC-RESPONSIBILITY responsibility="Declare the TypeScript interface for role objects returned by the roles API."]
36
+ // CIL[DOC-CONTEXT module="admin" layer="types" related_files=".framework/src/components/admin/AdminListPage.tsx,.framework/src/components/admin/SortableTableHeader.tsx,.framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/hooks/useApi.ts,.framework/src/lib/api.ts,.framework/src/lib/utils.ts,.framework/src/pages/admin/RolesPage.tsx"]
37
+ // CIL[DOC-CONTRACT inputs="none" outputs="Role interface type definition" errors="none" side_effects="none"]
38
+ // CIL[DOC-MICRO logic_summary="Typed interface with required and optional fields representing a role, its permissions, app association, and audit timestamps." notable_decisions="[]"]
39
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
40
+ // CIL[DOC-STABILITY stability="stable"]
30
41
  interface Role {
31
42
  id: string
32
43
  name: string
@@ -40,21 +51,22 @@ interface Role {
40
51
  created_at: string
41
52
  updated_at: string
42
53
  }
43
- // CIL[END chunk_id="rolespage_module_header_1783665503"]
44
- // CIL[BEGIN chunk_id="rolespage_rolespage_1783665503" type="function" identity="RolesPage" parent="none"]
45
- // CIL[DOC-MACRO purpose="Render the complete roles management page UI"]
46
- // CIL[DOC-RESPONSIBILITY responsibility="Display roles list with search, filters, stats, and table"]
47
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/portal/pages/CoursesPage.tsx\"]"]
48
- // CIL[DOC-CONTRACT inputs="none" outputs="AdminListPage JSX" errors="none" side_effects="none"]
49
- // CIL[DOC-MICRO logic_summary="Renders AdminListPage with roles data, empty states, and table" notable_decisions="[]"]
50
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
51
- // CIL[DOC-STABILITY stability="stable"]
52
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/portal/pages/ContentPage.tsx, custom/apps/portal/pages/CoursesPage.tsx."]
53
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
54
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
55
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
54
+ // CIL[DOC-VECTOR type="context" content="purpose='Define the shape of a role record for use in the admin roles list and related operations.'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/components/admin/AdminListPage.tsx"]
55
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
56
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
57
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
56
58
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
57
59
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
60
+ // CIL[END stable_id="~/.framework/src/pages/admin/RolesPage.tsx:Role:interface"]
61
+
62
+ // CIL[BEGIN stable_id="~/.framework/src/pages/admin/RolesPage.tsx:RolesPage:component" type="component" identity="RolesPage" parent="~/.framework/src/pages/admin/RolesPage.tsx"]
63
+ // CIL[DOC-MACRO purpose="Render the admin roles list page with search, filters, sorting, stats, and navigation to role detail."]
64
+ // CIL[DOC-RESPONSIBILITY responsibility="Fetch and display roles; handle search, status/type filters, column sorting, row navigation, and summary statistics."]
65
+ // CIL[DOC-CONTEXT module="admin" layer="pages" related_files=".framework/src/components/admin/AdminListPage.tsx,.framework/src/components/admin/SortableTableHeader.tsx,.framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/hooks/useApi.ts,.framework/src/lib/api.ts,.framework/src/lib/utils.ts,.framework/src/pages/admin/RolesPage.tsx"]
66
+ // CIL[DOC-CONTRACT inputs="none (internal state and useApi fetch)" outputs="JSX admin list page for roles" errors="API fetch error passed to AdminListPage; boolean sort fallback for permissions" side_effects="Navigates to /spine-framework/admin/configs/roles/:id on row click; uses window.location.href"]
67
+ // CIL[DOC-MICRO logic_summary="Uses useApi to load roles, applies search/status/type filters, sorts by selected column, computes stats cards, and renders an AdminListPage with a sortable table and clickable rows." notable_decisions="[]"]
68
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
69
+ // CIL[DOC-STABILITY stability="stable"]
58
70
  export function RolesPage() {
59
71
  const [searchTerm, setSearchTerm] = useState('')
60
72
  const [selectedStatus, setSelectedStatus] = useState('all')
@@ -87,20 +99,6 @@ export function RolesPage() {
87
99
  })
88
100
 
89
101
 
90
- // CIL[BEGIN chunk_id="rolespage_getstatusbadge_1783665503" type="function" identity="getStatusBadge" parent="rolespage_rolespage_1783665503"]
91
- // CIL[DOC-MACRO purpose="Generate status badge component based on role properties"]
92
- // CIL[DOC-RESPONSIBILITY responsibility="Handle getStatusBadge"]
93
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\".framework/src/components/ui/dialog.tsx\",\".framework/src/components/layout/Sidebar.tsx\",\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/portal/pages/CoursesPage.tsx\",\"\\\"]"]
94
- // CIL[DOC-CONTRACT inputs="none" outputs="JSX badge element" errors="none" side_effects="none"]
95
- // CIL[DOC-MICRO logic_summary="Checks is_active and is_system flags to determine badge styling" notable_decisions="[]"]
96
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
97
- // CIL[DOC-STABILITY stability="stable"]
98
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 4. Direct calls: 0. Related files: .framework/src/components/ui/dialog.tsx, .framework/src/components/layout/Sidebar.tsx, custom/apps/portal/pages/ContentPage.tsx, custom/apps/portal/pages/CoursesPage.tsx, \."]
99
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 4. Total affected: 4."]
100
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
101
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
102
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
103
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
104
102
  const getStatusBadge = (role: Role) => {
105
103
  if (!role.is_active) {
106
104
  return <span className="inline-flex px-2 py-0.5 text-xs font-medium rounded-md bg-slate-100 text-slate-600">Inactive</span>
@@ -110,22 +108,7 @@ export function RolesPage() {
110
108
  }
111
109
  return <span className="inline-flex px-2 py-0.5 text-xs font-medium rounded-md bg-blue-100 text-blue-700">Custom</span>
112
110
  }
113
- // CIL[END chunk_id="rolespage_getstatusbadge_1783665503"]
114
111
 
115
- // CIL[BEGIN chunk_id="rolespage_handlesort_1783665503" type="function" identity="handleSort" parent="rolespage_rolespage_1783665503"]
116
- // CIL[DOC-MACRO purpose="Handle table column sorting with direction toggle"]
117
- // CIL[DOC-RESPONSIBILITY responsibility="Handle handleSort"]
118
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
119
- // CIL[DOC-CONTRACT inputs="none" outputs="state updates" errors="none" side_effects="Updates component state"]
120
- // CIL[DOC-MICRO logic_summary="Toggles direction if same key, sets new key and asc direction otherwise" notable_decisions="[]"]
121
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
122
- // CIL[DOC-STABILITY stability="stable"]
123
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
124
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
125
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
126
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
127
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
128
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
129
112
  const handleSort = (key: string) => {
130
113
  if (sortKey === key) {
131
114
  setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc')
@@ -134,41 +117,11 @@ export function RolesPage() {
134
117
  setSortDirection('asc')
135
118
  }
136
119
  }
137
- // CIL[END chunk_id="rolespage_handlesort_1783665503"]
138
120
 
139
- // CIL[BEGIN chunk_id="rolespage_handlerowclick_1783665503" type="function" identity="handleRowClick" parent="rolespage_rolespage_1783665503"]
140
- // CIL[DOC-MACRO purpose="Navigate to role detail page when table row is clicked"]
141
- // CIL[DOC-RESPONSIBILITY responsibility="Handle handleRowClick"]
142
- // CIL[DOC-CONTEXT module="handleRowClick" layer="unknown" related_files="[]"]
143
- // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
144
- // CIL[DOC-MICRO logic_summary="Implements handleRowClick" notable_decisions="[]"]
145
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
146
- // CIL[DOC-STABILITY stability="stable"]
147
- // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
148
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
149
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
150
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
151
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
152
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
153
121
  const handleRowClick = (role: Role) => {
154
122
  window.location.href = `/spine-framework/admin/configs/roles/${role.id}`
155
123
  }
156
- // CIL[END chunk_id="rolespage_handlerowclick_1783665503"]
157
124
 
158
- // CIL[BEGIN chunk_id="rolespage_sortedroles_1783665503" type="variable" identity="sortedRoles" parent="rolespage_rolespage_1783665503"]
159
- // CIL[DOC-MACRO purpose="Sort filtered roles based on current sort key and direction"]
160
- // CIL[DOC-RESPONSIBILITY responsibility="Handle sortedRoles"]
161
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/portal/pages/CoursesPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
162
- // CIL[DOC-CONTRACT inputs="none" outputs="sorted array" errors="none" side_effects="none"]
163
- // CIL[DOC-MICRO logic_summary="Handles string and boolean sorting with localeCompare" notable_decisions="[]"]
164
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
165
- // CIL[DOC-STABILITY stability="stable"]
166
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/portal/pages/CoursesPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
167
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
168
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
169
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
170
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
171
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
172
125
  const sortedRoles = [...(filteredRoles || [])].sort((a, b) => {
173
126
  let aValue: any = a[sortKey as keyof Role]
174
127
  let bValue: any = b[sortKey as keyof Role]
@@ -185,22 +138,7 @@ export function RolesPage() {
185
138
 
186
139
  return 0
187
140
  })
188
- // CIL[END chunk_id="rolespage_sortedroles_1783665503"]
189
141
 
190
- // CIL[BEGIN chunk_id="rolespage_statcards_1783665503" type="variable" identity="statsCards" parent="rolespage_rolespage_1783665503"]
191
- // CIL[DOC-MACRO purpose="Generate statistics cards for roles dashboard"]
192
- // CIL[DOC-RESPONSIBILITY responsibility="Handle statsCards"]
193
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
194
- // CIL[DOC-CONTRACT inputs="none" outputs="stats cards array" errors="none" side_effects="none"]
195
- // CIL[DOC-MICRO logic_summary="Calculates total, active, system, and custom role counts" notable_decisions="[]"]
196
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
197
- // CIL[DOC-STABILITY stability="stable"]
198
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
199
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
200
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
201
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
202
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
203
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
204
142
  const statsCards = [
205
143
  {
206
144
  title: 'Total Roles',
@@ -227,64 +165,19 @@ export function RolesPage() {
227
165
  iconColor: 'text-orange-500'
228
166
  }
229
167
  ]
230
- // CIL[END chunk_id="rolespage_statcards_1783665503"]
231
168
 
232
- // CIL[BEGIN chunk_id="rolespage_statusoptions_1783665503" type="variable" identity="statusOptions" parent="rolespage_rolespage_1783665503"]
233
- // CIL[DOC-MACRO purpose="Define filter options for role status dropdown"]
234
- // CIL[DOC-RESPONSIBILITY responsibility="Handle statusOptions"]
235
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\".framework/functions/_shared/principal.ts\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\"\\\"]"]
236
- // CIL[DOC-CONTRACT inputs="none" outputs="status options array" errors="none" side_effects="none"]
237
- // CIL[DOC-MICRO logic_summary="Static array of status filter options" notable_decisions="[]"]
238
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
239
- // CIL[DOC-STABILITY stability="stable"]
240
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, .framework/src/pages/admin/IntegrationsPage.tsx, \."]
241
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
242
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
243
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
244
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
245
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
246
169
  const statusOptions = [
247
170
  { value: 'all', label: 'All Status' },
248
171
  { value: 'active', label: 'Active' },
249
172
  { value: 'inactive', label: 'Inactive' }
250
173
  ]
251
- // CIL[END chunk_id="rolespage_statusoptions_1783665503"]
252
174
 
253
- // CIL[BEGIN chunk_id="rolespage_typeoptions_1783665503" type="variable" identity="typeOptions" parent="rolespage_rolespage_1783665503"]
254
- // CIL[DOC-MACRO purpose="Define filter options for role type dropdown"]
255
- // CIL[DOC-RESPONSIBILITY responsibility="Handle typeOptions"]
256
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
257
- // CIL[DOC-CONTRACT inputs="none" outputs="type options array" errors="none" side_effects="none"]
258
- // CIL[DOC-MICRO logic_summary="Static array of role type filter options" notable_decisions="[]"]
259
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
260
- // CIL[DOC-STABILITY stability="stable"]
261
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/schema-utils.ts, \."]
262
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
263
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
264
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
265
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
266
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
267
175
  const typeOptions = [
268
176
  { value: 'all', label: 'All Types' },
269
177
  { value: 'system', label: 'System' },
270
178
  { value: 'custom', label: 'Custom' }
271
179
  ]
272
- // CIL[END chunk_id="rolespage_typeoptions_1783665503"]
273
180
 
274
- // CIL[BEGIN chunk_id="rolespage_filters_1783665503" type="variable" identity="filters" parent="rolespage_rolespage_1783665503"]
275
- // CIL[DOC-MACRO purpose="Combine status and type filters for AdminListPage component"]
276
- // CIL[DOC-RESPONSIBILITY responsibility="Handle filters"]
277
- // CIL[DOC-CONTEXT module="RolesPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/layout/Sidebar.tsx\",\"\\\"]"]
278
- // CIL[DOC-CONTRACT inputs="none" outputs="filters array" errors="none" side_effects="none"]
279
- // CIL[DOC-MICRO logic_summary="Maps filter options to AdminListPage filter format" notable_decisions="[]"]
280
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
281
- // CIL[DOC-STABILITY stability="stable"]
282
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/layout/Sidebar.tsx, \."]
283
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
284
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
285
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
286
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
287
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
288
181
  const filters = [
289
182
  {
290
183
  label: 'Status',
@@ -299,22 +192,7 @@ export function RolesPage() {
299
192
  onChange: setSelectedType
300
193
  }
301
194
  ]
302
- // CIL[END chunk_id="rolespage_filters_1783665503"]
303
195
 
304
- // CIL[BEGIN chunk_id="rolespage_return_1783665503" type="jsx" identity="return" parent="rolespage_rolespage_1783665503"]
305
- // CIL[DOC-MACRO purpose="Render the complete roles management page UI"]
306
- // CIL[DOC-RESPONSIBILITY responsibility="Handle return"]
307
- // CIL[DOC-CONTEXT module="return" layer="unknown" related_files="[]"]
308
- // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
309
- // CIL[DOC-MICRO logic_summary="Implements return" notable_decisions="[]"]
310
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
311
- // CIL[DOC-STABILITY stability="stable"]
312
- // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
313
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
314
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
315
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
316
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
317
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
318
196
  return (
319
197
  <AdminListPage
320
198
  title="Roles"
@@ -440,6 +318,11 @@ export function RolesPage() {
440
318
  )}
441
319
  </AdminListPage>
442
320
  )
443
- // CIL[END chunk_id="rolespage_return_1783665503"]
444
321
  }
445
- // CIL[END chunk_id="rolespage_rolespage_1783665503"]
322
+ // CIL[DOC-VECTOR type="context" content="purpose='Render the admin roles list page with search, filters, sorting, stats, and navigation to role detail.'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/components/ui/button.tsx,.framework/src/components/admin/AdminListPage.tsx"]
323
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
324
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
325
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
326
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
327
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
328
+ // CIL[END stable_id="~/.framework/src/pages/admin/RolesPage.tsx:RolesPage:component"]
@@ -0,0 +1,310 @@
1
+ // CIL[BEGIN stable_id="~/.framework/src/pages/admin/SecurityPage.tsx:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/src/pages/admin/SecurityPage.tsx"]
2
+ // CIL[DOC-MACRO purpose="System-admin security console: flagged actors report + blacklist management"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Render the flagged-accounts report from /api/security, let admins select rows via per-row checkboxes, and apply or lift three-layer bans; also list/revoke blocklist entries"]
4
+ // CIL[DOC-CONTEXT module="admin" layer="presentation" related_files=".framework/functions/security.ts,.framework/src/lib/api.ts,.framework/src/components/ui/checkbox.tsx,.framework/src/components/ui/table.tsx"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="Security admin page JSX" errors="API errors surfaced inline" side_effects="GET flagged_accounts + blocklist; POST ban_user/unban_user/blocklist_revoke via apiFetch"]
6
+ // CIL[DOC-MICRO logic_summary="Load report + blocklist; checkbox Set<string> keyed by row key (person_id or acct:account_id); Add to Blacklist issues one ban_user per selected row and reports per-layer results; refresh after actions" notable_decisions="ban_user accepts person_id OR email OR ip so anonymous-signal rows (no person) still ban by account/ip; all calls go through apiFetch — service role never leaves the server"]
7
+ // CIL[DOC-STABILITY stability="experimental"]
8
+ // CIL[DOC-AUDIENCE audience="internal"]
9
+
10
+ import { useCallback, useEffect, useMemo, useState } from 'react'
11
+ import { apiFetch } from '../../lib/api'
12
+ import { Button } from '../../components/ui/button'
13
+ import { Checkbox } from '../../components/ui/checkbox'
14
+ import { Badge } from '../../components/ui/badge'
15
+ import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
16
+ import {
17
+ Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
18
+ } from '../../components/ui/table'
19
+
20
+ type FlaggedRow = {
21
+ person_id: string | null
22
+ email: string | null
23
+ account_id: string | null
24
+ account_name: string | null
25
+ last_ip: string | null
26
+ last_country: string | null
27
+ person_status: string | null
28
+ signals: number
29
+ kinds: string[]
30
+ patterns: string[]
31
+ last_seen_at: string | null
32
+ banned: boolean
33
+ }
34
+
35
+ type BlocklistRow = {
36
+ id: string
37
+ type: string
38
+ value: string
39
+ severity: string
40
+ reason: string | null
41
+ expires_at: string | null
42
+ revoked_at: string | null
43
+ created_at: string
44
+ }
45
+
46
+ function rowKey(r: FlaggedRow): string {
47
+ return r.person_id || `acct:${r.account_id || 'unknown'}`
48
+ }
49
+
50
+ async function callSecurity(action: string, body?: any) {
51
+ const res = await apiFetch(`/api/security?action=${action}`, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify(body || {}),
55
+ })
56
+ const data = await res.json().catch(() => ({}))
57
+ if (!res.ok) throw new Error(data?.error || data?.message || `${action} failed (${res.status})`)
58
+ return data
59
+ }
60
+
61
+ export function SecurityPage() {
62
+ const [rows, setRows] = useState<FlaggedRow[]>([])
63
+ const [blocklist, setBlocklist] = useState<BlocklistRow[]>([])
64
+ const [loading, setLoading] = useState(true)
65
+ const [error, setError] = useState<string | null>(null)
66
+ const [selected, setSelected] = useState<Set<string>>(new Set())
67
+ const [actionBusy, setActionBusy] = useState(false)
68
+ const [actionResult, setActionResult] = useState<string | null>(null)
69
+
70
+ const load = useCallback(async () => {
71
+ setLoading(true)
72
+ setError(null)
73
+ try {
74
+ const [flagged, bl] = await Promise.all([
75
+ callSecurity('flagged_accounts'),
76
+ callSecurity('blocklist'),
77
+ ])
78
+ setRows(flagged?.rows || [])
79
+ setBlocklist(bl?.rows || [])
80
+ } catch (e: any) {
81
+ setError(e?.message || 'Failed to load security data')
82
+ } finally {
83
+ setLoading(false)
84
+ }
85
+ }, [])
86
+
87
+ useEffect(() => { load() }, [load])
88
+
89
+ const rowByKey = useMemo(() => new Map(rows.map(r => [rowKey(r), r])), [rows])
90
+ const selectedRows = useMemo(
91
+ () => [...selected].map(k => rowByKey.get(k)).filter(Boolean) as FlaggedRow[],
92
+ [selected, rowByKey]
93
+ )
94
+
95
+ const toggle = (key: string, checked: boolean) => {
96
+ setSelected(prev => {
97
+ const next = new Set(prev)
98
+ if (checked) next.add(key); else next.delete(key)
99
+ return next
100
+ })
101
+ }
102
+
103
+ const toggleAll = (checked: boolean) => {
104
+ setSelected(checked ? new Set(rows.map(rowKey)) : new Set())
105
+ }
106
+
107
+ const banSelected = async () => {
108
+ if (!selectedRows.length) return
109
+ setActionBusy(true)
110
+ setActionResult(null)
111
+ const results: string[] = []
112
+ for (const r of selectedRows) {
113
+ try {
114
+ const res = await callSecurity('ban_user', {
115
+ person_id: r.person_id || undefined,
116
+ email: r.email || undefined,
117
+ ip: r.last_ip || undefined,
118
+ reason: `Security admin action — signals: ${r.kinds.join(', ')}`,
119
+ })
120
+ results.push(`${r.email || r.account_name || rowKey(r)}: banned (auth:${res.layers?.auth} edge:${res.layers?.edge})`)
121
+ } catch (e: any) {
122
+ results.push(`${r.email || rowKey(r)}: FAILED — ${e?.message}`)
123
+ }
124
+ }
125
+ setActionResult(results.join('\n'))
126
+ setSelected(new Set())
127
+ setActionBusy(false)
128
+ void load()
129
+ }
130
+
131
+ const unbanRow = async (r: FlaggedRow) => {
132
+ if (!r.person_id) { setActionResult('Cannot unban — no person id on this row'); return }
133
+ setActionBusy(true)
134
+ try {
135
+ const res = await callSecurity('unban_user', { person_id: r.person_id })
136
+ setActionResult(`${r.email || r.person_id}: unbanned (auth:${res.layers?.auth} edge:${res.layers?.edge})`)
137
+ } catch (e: any) {
138
+ setActionResult(`Unban failed: ${e?.message}`)
139
+ }
140
+ setActionBusy(false)
141
+ void load()
142
+ }
143
+
144
+ const revokeEntry = async (entry: BlocklistRow) => {
145
+ setActionBusy(true)
146
+ try {
147
+ await callSecurity('blocklist_revoke', { id: entry.id })
148
+ } catch (e: any) {
149
+ setActionResult(`Revoke failed: ${e?.message}`)
150
+ }
151
+ setActionBusy(false)
152
+ void load()
153
+ }
154
+
155
+ const allChecked = rows.length > 0 && selected.size === rows.length
156
+ const activeBlocklist = blocklist.filter(b => !b.revoked_at)
157
+
158
+ return (
159
+ <div className="p-6 space-y-8">
160
+ <div>
161
+ <h1 className="text-2xl font-semibold">Security</h1>
162
+ <p className="text-sm text-muted-foreground mt-1">
163
+ Flagged actors and platform blocklist. "Add to Blacklist" applies all
164
+ three layers: internal blocklist, Supabase Auth ban, and Netlify edge IP deny.
165
+ </p>
166
+ </div>
167
+
168
+ {error && (
169
+ <div className="rounded-md bg-red-50 p-4 text-sm text-red-800">{error}</div>
170
+ )}
171
+ {actionResult && (
172
+ <pre className="rounded-md bg-slate-100 border border-slate-200 p-3 text-xs whitespace-pre-wrap">{actionResult}</pre>
173
+ )}
174
+
175
+ <section className="space-y-3">
176
+ <div className="flex items-center justify-between">
177
+ <h2 className="text-lg font-medium">Flagged actors</h2>
178
+ <div className="flex items-center gap-2">
179
+ <Button variant="outline" size="sm" onClick={() => load()} disabled={loading}>
180
+ Refresh
181
+ </Button>
182
+ <Button
183
+ size="sm"
184
+ variant="destructive"
185
+ disabled={!selected.size || actionBusy}
186
+ onClick={banSelected}
187
+ >
188
+ {actionBusy ? <LoadingSpinner size="sm" /> : null}
189
+ Add to Blacklist{selected.size ? ` (${selected.size})` : ''}
190
+ </Button>
191
+ </div>
192
+ </div>
193
+
194
+ {loading ? (
195
+ <div className="flex justify-center py-12"><LoadingSpinner /></div>
196
+ ) : rows.length === 0 ? (
197
+ <div className="rounded-md border border-dashed border-slate-300 p-8 text-center text-sm text-muted-foreground">
198
+ No security signals yet. Rate-limit violations, prompt-injection detections,
199
+ and blocked registrations appear here.
200
+ </div>
201
+ ) : (
202
+ <Table>
203
+ <TableHeader>
204
+ <TableRow>
205
+ <TableHead className="w-10">
206
+ <Checkbox
207
+ checked={allChecked}
208
+ onCheckedChange={(c) => toggleAll(c === true)}
209
+ aria-label="Select all"
210
+ />
211
+ </TableHead>
212
+ <TableHead>Actor</TableHead>
213
+ <TableHead>Account</TableHead>
214
+ <TableHead>IP / Country</TableHead>
215
+ <TableHead>Signals</TableHead>
216
+ <TableHead>Last seen</TableHead>
217
+ <TableHead>Status</TableHead>
218
+ <TableHead className="w-24"></TableHead>
219
+ </TableRow>
220
+ </TableHeader>
221
+ <TableBody>
222
+ {rows.map((r) => (
223
+ <TableRow key={rowKey(r)}>
224
+ <TableCell>
225
+ <Checkbox
226
+ checked={selected.has(rowKey(r))}
227
+ onCheckedChange={(c) => toggle(rowKey(r), c === true)}
228
+ aria-label={`Select ${r.email || rowKey(r)}`}
229
+ />
230
+ </TableCell>
231
+ <TableCell className="font-medium">
232
+ {r.email || <span className="text-muted-foreground">(no person)</span>}
233
+ </TableCell>
234
+ <TableCell>{r.account_name || '—'}</TableCell>
235
+ <TableCell className="text-xs">
236
+ {r.last_ip || '—'}{r.last_country ? ` · ${r.last_country.toUpperCase()}` : ''}
237
+ </TableCell>
238
+ <TableCell>
239
+ <div className="flex flex-wrap gap-1 items-center">
240
+ <Badge variant="secondary">{r.signals}×</Badge>
241
+ {r.kinds.map(k => (
242
+ <Badge key={k} variant="outline" className="text-xs">{k}</Badge>
243
+ ))}
244
+ </div>
245
+ </TableCell>
246
+ <TableCell className="text-xs text-muted-foreground">
247
+ {r.last_seen_at ? new Date(r.last_seen_at).toLocaleString() : '—'}
248
+ </TableCell>
249
+ <TableCell>
250
+ {r.banned
251
+ ? <Badge variant="destructive">banned</Badge>
252
+ : <Badge variant="outline">{r.person_status || 'active'}</Badge>}
253
+ </TableCell>
254
+ <TableCell>
255
+ {r.banned && r.person_id ? (
256
+ <Button variant="ghost" size="sm" disabled={actionBusy} onClick={() => unbanRow(r)}>
257
+ Unban
258
+ </Button>
259
+ ) : null}
260
+ </TableCell>
261
+ </TableRow>
262
+ ))}
263
+ </TableBody>
264
+ </Table>
265
+ )}
266
+ </section>
267
+
268
+ <section className="space-y-3">
269
+ <h2 className="text-lg font-medium">Active blocklist</h2>
270
+ {activeBlocklist.length === 0 ? (
271
+ <div className="rounded-md border border-dashed border-slate-300 p-6 text-center text-sm text-muted-foreground">
272
+ No active blocklist entries.
273
+ </div>
274
+ ) : (
275
+ <Table>
276
+ <TableHeader>
277
+ <TableRow>
278
+ <TableHead>Type</TableHead>
279
+ <TableHead>Value</TableHead>
280
+ <TableHead>Severity</TableHead>
281
+ <TableHead>Reason</TableHead>
282
+ <TableHead>Created</TableHead>
283
+ <TableHead className="w-24"></TableHead>
284
+ </TableRow>
285
+ </TableHeader>
286
+ <TableBody>
287
+ {activeBlocklist.map((b) => (
288
+ <TableRow key={b.id}>
289
+ <TableCell><Badge variant="outline">{b.type}</Badge></TableCell>
290
+ <TableCell className="font-mono text-xs">{b.value}</TableCell>
291
+ <TableCell>{b.severity}</TableCell>
292
+ <TableCell className="text-xs text-muted-foreground">{b.reason || '—'}</TableCell>
293
+ <TableCell className="text-xs text-muted-foreground">
294
+ {new Date(b.created_at).toLocaleDateString()}
295
+ </TableCell>
296
+ <TableCell>
297
+ <Button variant="ghost" size="sm" disabled={actionBusy} onClick={() => revokeEntry(b)}>
298
+ Revoke
299
+ </Button>
300
+ </TableCell>
301
+ </TableRow>
302
+ ))}
303
+ </TableBody>
304
+ </Table>
305
+ )}
306
+ </section>
307
+ </div>
308
+ )
309
+ }
310
+ // CIL[END stable_id="~/.framework/src/pages/admin/SecurityPage.tsx:module_header:module_header"]