spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,58 +1,59 @@
1
- /**
2
- * @module observability
3
- * @audience core-contributor
4
- * @layer api-handler
5
- * @stability stable
6
- *
7
- * Aggregated observability metrics API. All handlers delegate to Postgres
8
- * analytics RPCs that operate over the `v2.logs` table. Results are
9
- * read-only; no writes occur in this module.
10
- *
11
- * **Routed by:** `GET/POST /.netlify/functions/observability`
12
- *
13
- * **Actions (all GET unless noted):**
14
- * | ?action | handler |
15
- * |-----------------------|------------------------|
16
- * | event_volume | getEventVolume |
17
- * | error_rate | getErrorRate |
18
- * | latency_percentiles | getLatencyPercentiles |
19
- * | pipeline_stats | getPipelineStats |
20
- * | top_actors | getTopActors |
21
- * | ai_support_metrics | getAiSupportMetrics |
22
- * | cleanup | cleanupOldLogs (POST) |
23
- *
24
- * **Authorization:** All read endpoints require account context (RLS-scoped
25
- * via `ctx.db`). `cleanup` additionally requires the `system_admin` role.
26
- *
27
- * INVARIANT: Every handler requires both `from` and `to` date params
28
- * (except `cleanup`). Requests without them throw immediately.
29
- *
30
- * @seeAlso logs.ts (raw log access with filter/search)
31
- * @seeAlso system-cron.ts (evaluateThresholds calls observability RPCs)
32
- * @seeAlso pipeline-executions.ts (pipeline_stats RPC aggregates these)
33
- */
34
1
 
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+ // CIL[BEGIN chunk_id="observability_moduleheader_1783968140" type="module_header" identity="imports" parent="none"]
10
+ // CIL[DOC-MACRO purpose="Import dependencies for observability metrics API"]
11
+ // CIL[DOC-RESPONSIBILITY responsibility="Import handler utilities and database access"]
12
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\"\\\"]"]
13
+ // CIL[DOC-CONTRACT inputs="none" outputs="Imported modules" errors="Import failures" side_effects="none"]
14
+ // CIL[DOC-MICRO logic_summary="Imports createHandler, adminDb" notable_decisions="[]"]
15
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
16
+ // CIL[DOC-STABILITY stability="stable"]
17
+ // CIL[DOC-AUDIENCE audience="developer"]
18
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, \."]
19
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
20
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
21
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
22
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
23
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
35
24
  import { createHandler } from './_shared/middleware'
36
25
  import { adminDb } from './_shared/db'
26
+ import { isSystemAdmin } from './_shared/principal'
27
+ import { resolveTypeId } from './_shared/resolve-ids'
28
+ // CIL[END chunk_id="observability_moduleheader_1783968140"]
29
+ // CIL[BEGIN chunk_id="observability_geteventvolume_1783968140" type="function" identity="getEventVolume" parent="none"]
30
+ // CIL[DOC-MACRO purpose="Get event volume metrics with time bucketing"]
31
+ // CIL[DOC-RESPONSIBILITY responsibility="Query aggregated event statistics"]
32
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[\".framework/src/pages/admin/RolesPage.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/db.ts\",\"\\\"]"]
33
+ // CIL[DOC-CONTRACT inputs="none" outputs="Array of event volume data" errors="Account context required, dates required, DB errors" side_effects="DB RPC call"]
34
+ // CIL[DOC-MICRO logic_summary="Calls get_event_volume RPC with date range and bucket parameters" notable_decisions="[]"]
35
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
36
+ // CIL[DOC-STABILITY stability="stable"]
37
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 1. Direct calls: 0. Related files: .framework/src/pages/admin/RolesPage.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/db.ts, \."]
38
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
39
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
40
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
41
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
42
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
43
+ function floorBucket(date: Date, bucket: string): Date {
44
+ const d = new Date(date)
45
+ d.setMilliseconds(0)
46
+ d.setSeconds(0)
47
+ if (bucket === 'hour' || bucket === 'day') d.setMinutes(0)
48
+ if (bucket === 'day') d.setHours(0)
49
+ return d
50
+ }
37
51
 
38
- // ─── HANDLERS ─────────────────────────────────────────────────────────────────
39
-
40
- /**
41
- * Returns time-bucketed event volume counts via the `get_event_volume` RPC.
42
- *
43
- * Query params: `from` (required, ISO), `to` (required, ISO),
44
- * `bucket` ('minute'|'hour'|'day', default 'hour')
45
- *
46
- * @returns Array of `{ bucket_time, event_type, count }` rows
47
- * @throws Error('Account context required')
48
- * @throws Error('from and to dates are required')
49
- * @sideEffects DB read: get_event_volume RPC
50
- * @calledBy handler (?action=event_volume)
51
- */
52
52
  export const getEventVolume = createHandler(async (ctx, body) => {
53
53
  const { from, to, bucket = 'hour' } = ctx.query || {}
54
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
54
55
 
55
- if (!ctx.accountId) {
56
+ if (!isSystemAdmin(ctx.principal) && !ctx.accountId) {
56
57
  throw new Error('Account context required')
57
58
  }
58
59
 
@@ -60,37 +61,50 @@ export const getEventVolume = createHandler(async (ctx, body) => {
60
61
  throw new Error('from and to dates are required')
61
62
  }
62
63
 
63
- const { data, error: err } = await ctx.db
64
- .rpc('get_event_volume', {
65
- p_account_id: ctx.accountId,
66
- p_event_type: null,
67
- p_bucket: bucket,
68
- p_from: from,
69
- p_to: to,
70
- })
64
+ let query = db
65
+ .from('logs')
66
+ .select('created_at')
67
+ .gte('created_at', from)
68
+ .lte('created_at', to)
69
+
70
+ if (!isSystemAdmin(ctx.principal) && ctx.accountId) {
71
+ query = query.eq('account_id', ctx.accountId)
72
+ }
73
+
74
+ const { data, error: err } = await query.order('created_at', { ascending: true })
71
75
 
72
76
  if (err) throw err
73
- return data || []
74
- })
75
77
 
76
- // ── Error Rate ───────────────────────────────────────────
77
- /**
78
- * Returns aggregate error rate for the account time window via the
79
- * `get_error_rate` RPC.
80
- *
81
- * Query params: `from` (required, ISO), `to` (required, ISO)
82
- *
83
- * @returns `{ total: number, errors: number, rate: number }` (0.0–1.0)
84
- * @throws Error('Account context required')
85
- * @throws Error('from and to dates are required')
86
- * @sideEffects DB read: get_error_rate RPC
87
- * @calledBy handler (?action=error_rate)
88
- * @calledBy system-cron.ts evaluateThresholds (metric='error_rate')
89
- */
78
+ const counts: Record<string, number> = {}
79
+ for (const row of data || []) {
80
+ const key = floorBucket(new Date(row.created_at), bucket).toISOString()
81
+ counts[key] = (counts[key] || 0) + 1
82
+ }
83
+
84
+ return Object.entries(counts)
85
+ .map(([bucket, count]) => ({ bucket, count }))
86
+ .sort((a, b) => new Date(a.bucket).getTime() - new Date(b.bucket).getTime())
87
+ })
88
+ // CIL[END chunk_id="observability_geteventvolume_1783968140"]
89
+ // CIL[BEGIN chunk_id="observability_geterrorrate_1783968140" type="function" identity="getErrorRate" parent="none"]
90
+ // CIL[DOC-MACRO purpose="Get error rate metrics for date range"]
91
+ // CIL[DOC-RESPONSIBILITY responsibility="Query aggregated error statistics"]
92
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/db.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"\\\"]"]
93
+ // CIL[DOC-CONTRACT inputs="none" outputs="Error rate data object" errors="Account context required, dates required, DB errors" side_effects="DB RPC call"]
94
+ // CIL[DOC-MICRO logic_summary="Calls get_error_rate RPC with date range parameters" notable_decisions="[]"]
95
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
96
+ // CIL[DOC-STABILITY stability="stable"]
97
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 1. Direct calls: 0. Related files: .framework/src/pages/admin/RolesPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/db.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, \."]
98
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
99
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
100
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
101
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
102
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
90
103
  export const getErrorRate = createHandler(async (ctx, body) => {
91
104
  const { from, to } = ctx.query || {}
105
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
92
106
 
93
- if (!ctx.accountId) {
107
+ if (!isSystemAdmin(ctx.principal) && !ctx.accountId) {
94
108
  throw new Error('Account context required')
95
109
  }
96
110
 
@@ -98,35 +112,58 @@ export const getErrorRate = createHandler(async (ctx, body) => {
98
112
  throw new Error('from and to dates are required')
99
113
  }
100
114
 
101
- const { data, error: err } = await ctx.db
102
- .rpc('get_error_rate', {
103
- p_account_id: ctx.accountId,
104
- p_from: from,
105
- p_to: to,
106
- })
115
+ let query = db
116
+ .from('logs')
117
+ .select('outcome, level')
118
+ .gte('created_at', from)
119
+ .lte('created_at', to)
120
+
121
+ if (!isSystemAdmin(ctx.principal) && ctx.accountId) {
122
+ query = query.eq('account_id', ctx.accountId)
123
+ }
124
+
125
+ const { data, error: err } = await query
107
126
 
108
127
  if (err) throw err
109
- return data?.[0] || { total: 0, errors: 0, rate: 0 }
128
+
129
+ const rows = data || []
130
+ const total = rows.length
131
+ const errors = rows.filter((r: any) => r.outcome === 'failure' || r.level === 'error').length
132
+ return {
133
+ total,
134
+ errors,
135
+ rate: total > 0 ? errors / total : 0,
136
+ }
110
137
  })
138
+ // CIL[END chunk_id="observability_geterrorrate_1783968140"]
139
+ // CIL[BEGIN chunk_id="observability_getlatencypercentiles_1783968140" type="function" identity="getLatencyPercentiles" parent="none"]
140
+ // CIL[DOC-MACRO purpose="Get latency percentile metrics for performance monitoring"]
141
+ // CIL[DOC-RESPONSIBILITY responsibility="Query aggregated latency statistics"]
142
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
143
+ // CIL[DOC-CONTRACT inputs="none" outputs="Latency percentile data" errors="Account context required, dates required, DB errors" side_effects="DB RPC call"]
144
+ // CIL[DOC-MICRO logic_summary="Calls get_latency_percentiles RPC with source and date range" 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: api-handler. Direct callers: 1. Direct calls: 0. Related files: .framework/src/pages/admin/RolesPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, \."]
148
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
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
+ function percentile(sorted: number[], p: number): number {
154
+ if (sorted.length === 0) return 0
155
+ const idx = (p / 100) * (sorted.length - 1)
156
+ const lower = Math.floor(idx)
157
+ const upper = Math.ceil(idx)
158
+ const weight = idx - lower
159
+ return sorted[lower] * (1 - weight) + sorted[upper] * weight
160
+ }
111
161
 
112
- /**
113
- * Returns request latency percentiles (p50/p90/p99) via the
114
- * `get_latency_percentiles` RPC.
115
- *
116
- * Query params: `from` (required, ISO), `to` (required, ISO),
117
- * `source` (log source filter, default 'request')
118
- *
119
- * @returns `{ p50: number, p90: number, p99: number }` (milliseconds)
120
- * @throws Error('Account context required')
121
- * @throws Error('from and to dates are required')
122
- * @sideEffects DB read: get_latency_percentiles RPC
123
- * @calledBy handler (?action=latency_percentiles)
124
- * @calledBy system-cron.ts evaluateThresholds (metric='latency_p95')
125
- */
126
162
  export const getLatencyPercentiles = createHandler(async (ctx, body) => {
127
163
  const { from, to, source = 'request' } = ctx.query || {}
164
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
128
165
 
129
- if (!ctx.accountId) {
166
+ if (!isSystemAdmin(ctx.principal) && !ctx.accountId) {
130
167
  throw new Error('Account context required')
131
168
  }
132
169
 
@@ -134,35 +171,54 @@ export const getLatencyPercentiles = createHandler(async (ctx, body) => {
134
171
  throw new Error('from and to dates are required')
135
172
  }
136
173
 
137
- const { data, error: err } = await ctx.db
138
- .rpc('get_latency_percentiles', {
139
- p_account_id: ctx.accountId,
140
- p_source: source,
141
- p_from: from,
142
- p_to: to,
143
- })
174
+ let query = db
175
+ .from('logs')
176
+ .select('duration_ms')
177
+ .not('duration_ms', 'is', null)
178
+ .gte('created_at', from)
179
+ .lte('created_at', to)
180
+
181
+ if (source) query = query.eq('span_name', source)
182
+ if (!isSystemAdmin(ctx.principal) && ctx.accountId) {
183
+ query = query.eq('account_id', ctx.accountId)
184
+ }
185
+
186
+ const { data, error: err } = await query
144
187
 
145
188
  if (err) throw err
146
- return data?.[0] || { p50: 0, p90: 0, p99: 0 }
147
- })
148
189
 
149
- /**
150
- * Returns per-pipeline execution statistics (success/failure counts,
151
- * average duration) via the `get_pipeline_stats` RPC.
152
- *
153
- * Query params: `from` (required, ISO), `to` (required, ISO)
154
- *
155
- * @returns Array of `{ pipeline_id, name, success_count, failure_count, avg_duration_ms }`
156
- * @throws Error('Account context required')
157
- * @throws Error('from and to dates are required')
158
- * @sideEffects DB read: get_pipeline_stats RPC
159
- * @calledBy handler (?action=pipeline_stats)
160
- * @calledBy system-cron.ts evaluateThresholds (metric='pipeline_failure_rate')
161
- */
190
+ const values = (data || [])
191
+ .map((r: any) => Number(r.duration_ms))
192
+ .filter((v: number) => !isNaN(v) && v > 0)
193
+ .sort((a: number, b: number) => a - b)
194
+
195
+ return {
196
+ p50: percentile(values, 50),
197
+ p90: percentile(values, 90),
198
+ p95: percentile(values, 95),
199
+ p99: percentile(values, 99),
200
+ }
201
+ })
202
+ // CIL[END chunk_id="observability_getlatencypercentiles_1783968140"]
203
+ // CIL[BEGIN chunk_id="observability_getpipelinestats_1783968140" type="function" identity="getPipelineStats" parent="none"]
204
+ // CIL[DOC-MACRO purpose="Get pipeline execution statistics for monitoring"]
205
+ // CIL[DOC-RESPONSIBILITY responsibility="Query aggregated pipeline metrics"]
206
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[]"]
207
+ // CIL[DOC-CONTRACT inputs="none" outputs="Array of pipeline statistics" errors="Account context required, dates required, DB errors" side_effects="DB RPC call"]
208
+ // CIL[DOC-MICRO logic_summary="Calls get_pipeline_stats RPC with date range parameters" notable_decisions="[]"]
209
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
210
+ // CIL[DOC-STABILITY stability="stable"]
211
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
212
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
213
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
214
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
215
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
216
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
162
217
  export const getPipelineStats = createHandler(async (ctx, body) => {
163
218
  const { from, to } = ctx.query || {}
219
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
164
220
 
165
- if (!ctx.accountId) {
221
+ if (!isSystemAdmin(ctx.principal) && !ctx.accountId) {
166
222
  throw new Error('Account context required')
167
223
  }
168
224
 
@@ -170,34 +226,62 @@ export const getPipelineStats = createHandler(async (ctx, body) => {
170
226
  throw new Error('from and to dates are required')
171
227
  }
172
228
 
173
- const { data, error: err } = await ctx.db
174
- .rpc('get_pipeline_stats', {
175
- p_account_id: ctx.accountId,
176
- p_from: from,
177
- p_to: to,
178
- })
229
+ let query = db
230
+ .from('pipeline_executions')
231
+ .select('pipeline_id, status, started_at, completed_at')
232
+ .gte('created_at', from)
233
+ .lte('created_at', to)
234
+
235
+ if (!isSystemAdmin(ctx.principal) && ctx.accountId) {
236
+ query = query.eq('account_id', ctx.accountId)
237
+ }
238
+
239
+ const { data, error: err } = await query
179
240
 
180
241
  if (err) throw err
181
- return data || []
182
- })
183
242
 
184
- /**
185
- * Returns the most active principals (persons) by event count via the
186
- * `get_top_actors` RPC.
187
- *
188
- * Query params: `from` (required, ISO), `to` (required, ISO),
189
- * `limit` (default 5)
190
- *
191
- * @returns Array of `{ person_id, full_name, event_count }` ordered by count desc
192
- * @throws Error('Account context required')
193
- * @throws Error('from and to dates are required')
194
- * @sideEffects DB read: get_top_actors RPC
195
- * @calledBy handler (?action=top_actors)
196
- */
243
+ const stats: Record<string, any> = {}
244
+ for (const row of data || []) {
245
+ const s = stats[row.pipeline_id] || { pipeline_id: row.pipeline_id, success_count: 0, failure_count: 0, total_duration_ms: 0, duration_count: 0 }
246
+ if (row.status === 'completed') s.success_count++
247
+ if (row.status === 'failed') s.failure_count++
248
+ if (row.started_at && row.completed_at) {
249
+ const duration = new Date(row.completed_at).getTime() - new Date(row.started_at).getTime()
250
+ if (duration > 0) {
251
+ s.total_duration_ms += duration
252
+ s.duration_count++
253
+ }
254
+ }
255
+ stats[row.pipeline_id] = s
256
+ }
257
+
258
+ return Object.values(stats).map((s: any) => ({
259
+ pipeline_id: s.pipeline_id,
260
+ success_count: s.success_count,
261
+ failure_count: s.failure_count,
262
+ avg_duration_ms: s.duration_count > 0 ? Math.round(s.total_duration_ms / s.duration_count) : 0,
263
+ }))
264
+ })
265
+ // CIL[END chunk_id="observability_getpipelinestats_1783968140"]
266
+ // CIL[BEGIN chunk_id="observability_gettopactors_1783968140" type="function" identity="getTopActors" parent="none"]
267
+ // CIL[DOC-MACRO purpose="Get top actors by activity volume for monitoring"]
268
+ // CIL[DOC-RESPONSIBILITY responsibility="Query most active users/system actors"]
269
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[]"]
270
+ // CIL[DOC-CONTRACT inputs="none" outputs="Array of actor activity data" errors="Account context required, dates required, DB errors" side_effects="DB RPC call"]
271
+ // CIL[DOC-MICRO logic_summary="Calls get_top_actors RPC with date range and limit parameters" notable_decisions="[]"]
272
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
273
+ // CIL[DOC-STABILITY stability="stable"]
274
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
275
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
276
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
277
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
278
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
279
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
197
280
  export const getTopActors = createHandler(async (ctx, body) => {
198
281
  const { from, to, limit = 5 } = ctx.query || {}
282
+ const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
199
283
 
200
- if (!ctx.accountId) {
284
+ if (!isSystemAdmin(ctx.principal) && !ctx.accountId) {
201
285
  throw new Error('Account context required')
202
286
  }
203
287
 
@@ -205,35 +289,49 @@ export const getTopActors = createHandler(async (ctx, body) => {
205
289
  throw new Error('from and to dates are required')
206
290
  }
207
291
 
208
- const { data, error: err } = await ctx.db
209
- .rpc('get_top_actors', {
210
- p_account_id: ctx.accountId,
211
- p_from: from,
212
- p_to: to,
213
- p_limit: parseInt(limit.toString()) || 5,
214
- })
292
+ let query = db
293
+ .from('logs')
294
+ .select('created_by')
295
+ .gte('created_at', from)
296
+ .lte('created_at', to)
297
+
298
+ if (!isSystemAdmin(ctx.principal) && ctx.accountId) {
299
+ query = query.eq('account_id', ctx.accountId)
300
+ }
301
+
302
+ const { data, error: err } = await query
215
303
 
216
304
  if (err) throw err
217
- return data || []
218
- })
219
305
 
220
- // ── Cleanup Old Logs (manual trigger) ────────────────────
221
- /**
222
- * Manually triggers cross-account log cleanup via the `cleanup_old_logs`
223
- * RPC. Requires `system_admin` role — not available to regular users.
224
- *
225
- * Query params: `days` (optional, default 90)
226
- *
227
- * @returns `{ deleted_count: number }`
228
- * @throws Error('System admin required') if principal lacks system_admin role
229
- * @sideEffects DB write: cleanup_old_logs RPC (cross-account DELETE)
230
- * @calledBy handler (?action=cleanup)
231
- * @calledBy system-cron.ts cleanupOldLogs (automated daily rotation)
232
- */
306
+ const counts: Record<string, number> = {}
307
+ for (const row of data || []) {
308
+ const key = row.created_by || 'anonymous'
309
+ counts[key] = (counts[key] || 0) + 1
310
+ }
311
+
312
+ return Object.entries(counts)
313
+ .map(([principal_id, event_count]) => ({ principal_id, event_count }))
314
+ .sort((a, b) => b.event_count - a.event_count)
315
+ .slice(0, parseInt(limit.toString()) || 5)
316
+ })
317
+ // CIL[END chunk_id="observability_gettopactors_1783968140"]
318
+ // CIL[BEGIN chunk_id="observability_cleanupoldlogs_1783968140" type="function" identity="cleanupOldLogs" parent="none"]
319
+ // CIL[DOC-MACRO purpose="Delete old log entries for storage management"]
320
+ // CIL[DOC-RESPONSIBILITY responsibility="Perform log retention cleanup"]
321
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[]"]
322
+ // CIL[DOC-CONTRACT inputs="none" outputs="{deleted_count: number}" errors="System admin required, DB errors" side_effects="DB RPC call, data deletion"]
323
+ // CIL[DOC-MICRO logic_summary="Calls cleanup_old_logs RPC with retention days parameter" notable_decisions="[]"]
324
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
325
+ // CIL[DOC-STABILITY stability="stable"]
326
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
327
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
328
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
329
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
330
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
331
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
233
332
  export const cleanupOldLogs = createHandler(async (ctx, body) => {
234
333
  const { days = 90 } = ctx.query || {}
235
334
 
236
- // Only system admins can trigger manual cleanup
237
335
  if (!ctx.principal?.roles?.includes('system_admin')) {
238
336
  throw new Error('System admin required')
239
337
  }
@@ -246,28 +344,21 @@ export const cleanupOldLogs = createHandler(async (ctx, body) => {
246
344
  if (err) throw err
247
345
  return { deleted_count: data?.[0]?.deleted_count || 0 }
248
346
  })
249
-
250
- // ─── AI SUPPORT METRICS ──────────────────────────────────────────────────────
251
-
252
- /**
253
- * Returns the AI support loop health metrics.
254
- * All values are derived from existing tables — no new table required.
255
- *
256
- * Metrics:
257
- * - ai_attempted: AI agent messages sent
258
- * - ai_resolved: Tickets resolved without escalation
259
- * - human_escalations: Tickets assigned to humans
260
- * - thumbs_down_rate: Ratio of not_helpful / total AI messages (0–1)
261
- * - medium_confidence: AI messages with confidence 0.60–0.85
262
- * - kb_articles_created: Postmortem KB articles
263
- * - kb_reuse_count: Total reuse_count across ai_feedback_record items
264
- * - knowledge_gaps: Interactions with kb_candidate=false + not_helpful feedback
265
- *
266
- * Query params: from, to (ISO dates, optional — defaults to last 30 days)
267
- *
268
- * @sideEffects DB reads: items, messages tables
269
- * @calledBy handler (?action=ai_support_metrics)
270
- */
347
+ // CIL[END chunk_id="observability_cleanupoldlogs_1783968140"]
348
+ // CIL[BEGIN chunk_id="observability_getaisupportmetrics_1783968140" type="function" identity="getAiSupportMetrics" parent="none"]
349
+ // CIL[DOC-MACRO purpose="Get AI support metrics and knowledge gaps analysis"]
350
+ // CIL[DOC-RESPONSIBILITY responsibility="Query AI performance and coverage statistics"]
351
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[]"]
352
+ // CIL[DOC-CONTRACT inputs="none" outputs="AI metrics object with knowledge gaps" errors="Account context required, DB errors" side_effects="DB queries"]
353
+ // CIL[DOC-MICRO logic_summary="Queries AI metrics and knowledge gaps, returns aggregated analysis" notable_decisions="[]"]
354
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
355
+ // CIL[DOC-STABILITY stability="stable"]
356
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
357
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
358
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
359
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
360
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
361
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
271
362
  export const getAiSupportMetrics = createHandler(async (ctx, _body) => {
272
363
  if (!ctx.accountId) throw new Error('Account context required')
273
364
 
@@ -276,26 +367,22 @@ export const getAiSupportMetrics = createHandler(async (ctx, _body) => {
276
367
  const from = ctx.query?.from || thirtyDaysAgo
277
368
  const to = ctx.query?.to || now.toISOString()
278
369
 
279
- // Run all queries in parallel for performance
370
+ const feedbackTypeId = await resolveTypeId('item', 'ai_feedback_record')
371
+
280
372
  const [
281
- aiMessagesResult,
373
+ feedbackResult,
282
374
  resolvedResult,
283
375
  escalatedResult,
284
- notHelpfulResult,
285
- mediumConfResult,
286
- kbCreatedResult,
287
- feedbackRecordsResult,
288
376
  ] = await Promise.allSettled([
289
- // AI agent messages
290
377
  adminDb
291
- .from('messages')
292
- .select('id, data', { count: 'exact', head: false })
378
+ .from('items')
379
+ .select('data')
293
380
  .eq('account_id', ctx.accountId)
294
- .eq('data->>sender_type', 'agent')
381
+ .eq('type_id', feedbackTypeId)
295
382
  .gte('created_at', from)
296
- .lte('created_at', to),
383
+ .lte('created_at', to)
384
+ .limit(5000),
297
385
 
298
- // Tickets resolved without AI escalation
299
386
  adminDb
300
387
  .from('items')
301
388
  .select('id', { count: 'exact', head: true })
@@ -305,7 +392,6 @@ export const getAiSupportMetrics = createHandler(async (ctx, _body) => {
305
392
  .gte('created_at', from)
306
393
  .lte('created_at', to),
307
394
 
308
- // Tickets escalated to humans
309
395
  adminDb
310
396
  .from('items')
311
397
  .select('id', { count: 'exact', head: true })
@@ -313,66 +399,23 @@ export const getAiSupportMetrics = createHandler(async (ctx, _body) => {
313
399
  .eq('status', 'human_assigned')
314
400
  .gte('created_at', from)
315
401
  .lte('created_at', to),
316
-
317
- // Not-helpful AI messages
318
- adminDb
319
- .from('messages')
320
- .select('id', { count: 'exact', head: true })
321
- .eq('account_id', ctx.accountId)
322
- .eq('data->>sender_type', 'agent')
323
- .eq('data->>feedback', 'not_helpful')
324
- .gte('created_at', from)
325
- .lte('created_at', to),
326
-
327
- // Medium-confidence AI messages
328
- adminDb
329
- .from('messages')
330
- .select('id', { count: 'exact', head: true })
331
- .eq('account_id', ctx.accountId)
332
- .eq('data->>sender_type', 'agent')
333
- .gte('data->>confidence', '0.60')
334
- .lte('data->>confidence', '0.85')
335
- .gte('created_at', from)
336
- .lte('created_at', to),
337
-
338
- // Postmortem KB articles created
339
- adminDb
340
- .from('items')
341
- .select('id', { count: 'exact', head: true })
342
- .eq('account_id', ctx.accountId)
343
- .eq('data->>kb_type', 'postmortem')
344
- .gte('created_at', from)
345
- .lte('created_at', to),
346
-
347
- // AI feedback records for reuse + gaps
348
- adminDb
349
- .from('items')
350
- .select('data')
351
- .eq('account_id', ctx.accountId)
352
- .gte('created_at', from)
353
- .lte('created_at', to)
354
- .limit(500),
355
402
  ])
356
403
 
357
- // Extract values safely
358
- const aiMessages = aiMessagesResult.status === 'fulfilled' ? (aiMessagesResult.value.data || []) : []
359
- const aiAttempted = aiMessages.length
360
-
361
- const aiResolved = resolvedResult.status === 'fulfilled' ? (resolvedResult.value.count || 0) : 0
362
- const escalated = escalatedResult.status === 'fulfilled' ? (escalatedResult.value.count || 0) : 0
363
- const notHelpful = notHelpfulResult.status === 'fulfilled' ? (notHelpfulResult.value.count || 0) : 0
364
- const mediumConf = mediumConfResult.status === 'fulfilled' ? (mediumConfResult.value.count || 0) : 0
365
- const kbCreated = kbCreatedResult.status === 'fulfilled' ? (kbCreatedResult.value.count || 0) : 0
366
-
367
- const feedbackRows = feedbackRecordsResult.status === 'fulfilled'
368
- ? (feedbackRecordsResult.value.data || [])
404
+ const feedbackRows = feedbackResult.status === 'fulfilled'
405
+ ? (feedbackResult.value.data || [])
369
406
  : []
370
407
 
408
+ const aiAttempted = feedbackRows.length
409
+ const notHelpful = feedbackRows.filter((r: any) => r.data?.customer_feedback === 'not_helpful').length
410
+ const kbCreated = feedbackRows.filter((r: any) => r.data?.kb_article_id).length
371
411
  const kbReuseCount = feedbackRows.reduce((sum: number, r: any) => sum + (Number(r.data?.reuse_count) || 0), 0)
372
412
  const knowledgeGaps = feedbackRows.filter((r: any) =>
373
413
  r.data?.kb_candidate === false && r.data?.customer_feedback === 'not_helpful'
374
414
  ).length
375
415
 
416
+ const aiResolved = resolvedResult.status === 'fulfilled' ? (resolvedResult.value.count || 0) : 0
417
+ const escalated = escalatedResult.status === 'fulfilled' ? (escalatedResult.value.count || 0) : 0
418
+
376
419
  const thumbsDownRate = aiAttempted > 0 ? notHelpful / aiAttempted : 0
377
420
 
378
421
  return {
@@ -381,21 +424,26 @@ export const getAiSupportMetrics = createHandler(async (ctx, _body) => {
381
424
  ai_resolved: aiResolved,
382
425
  human_escalations: escalated,
383
426
  thumbs_down_rate: Math.round(thumbsDownRate * 1000) / 1000,
384
- medium_confidence: mediumConf,
385
427
  kb_articles_created: kbCreated,
386
428
  kb_reuse_count: kbReuseCount,
387
429
  knowledge_gaps: knowledgeGaps,
388
430
  }
389
431
  })
390
-
391
- // ─── MAIN HANDLER ────────────────────────────────────────────────────────────
392
-
393
- /**
394
- * Netlify function entry point. Dispatches to analytics handler by ?action.
395
- * All valid ?action values are listed in the module dispatch table.
396
- * @throws Error('Unknown action: <action>. Valid actions: ...') on mismatch
397
- * @calledBy Netlify function routing
398
- */
432
+ // CIL[END chunk_id="observability_getaisupportmetrics_1783968140"]
433
+ // CIL[BEGIN chunk_id="observability_handler_1783968140" type="function" identity="handler" parent="none"]
434
+ // CIL[DOC-MACRO purpose="Route observability actions to appropriate metric handlers"]
435
+ // CIL[DOC-RESPONSIBILITY responsibility="Dispatch monitoring requests to correct metric functions"]
436
+ // CIL[DOC-CONTEXT module="observability" layer="api-handler" related_files="[]"]
437
+ // CIL[DOC-CONTRACT inputs="none" outputs="Varies by action" errors="Unknown action" side_effects="Delegates to handlers"]
438
+ // CIL[DOC-MICRO logic_summary="Switch statement routes action parameter to appropriate metric handler" notable_decisions="[]"]
439
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
440
+ // CIL[DOC-STABILITY stability="stable"]
441
+ // CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
442
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
443
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
444
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
445
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
446
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
399
447
  export const handler = createHandler(async (ctx, body) => {
400
448
  const { action } = ctx.query || {}
401
449
 
@@ -418,3 +466,4 @@ export const handler = createHandler(async (ctx, body) => {
418
466
  throw new Error(`Unknown action: ${action}. Valid actions: event_volume, error_rate, latency_percentiles, pipeline_stats, top_actors, ai_support_metrics, cleanup`)
419
467
  }
420
468
  })
469
+ // CIL[END chunk_id="observability_handler_1783968140"]