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
@@ -0,0 +1,598 @@
1
+ -- Spine v2 consolidated observability and knowledge search
2
+ -- Logs trace columns, observability metric RPCs, KB item RLS, and updated match_embeddings.
3
+
4
+ -- Migration: extend public.logs to store W3C Trace Context and span records.
5
+ -- Logs become the unified runtime evidence table: events, audit records, and spans.
6
+
7
+ ALTER TABLE public.logs
8
+ ADD COLUMN IF NOT EXISTS trace_id text,
9
+ ADD COLUMN IF NOT EXISTS span_id text,
10
+ ADD COLUMN IF NOT EXISTS parent_span_id text,
11
+ ADD COLUMN IF NOT EXISTS trace_flags smallint,
12
+ ADD COLUMN IF NOT EXISTS tracestate text,
13
+ ADD COLUMN IF NOT EXISTS span_name text,
14
+ ADD COLUMN IF NOT EXISTS started_at timestamptz,
15
+ ADD COLUMN IF NOT EXISTS ended_at timestamptz,
16
+ ADD COLUMN IF NOT EXISTS duration_ms numeric,
17
+ ADD COLUMN IF NOT EXISTS sampled boolean DEFAULT true,
18
+ ADD COLUMN IF NOT EXISTS kind text,
19
+ ADD COLUMN IF NOT EXISTS outcome text,
20
+ ADD COLUMN IF NOT EXISTS event_category text;
21
+
22
+ CREATE INDEX IF NOT EXISTS idx_logs_trace_id ON public.logs (trace_id);
23
+ CREATE INDEX IF NOT EXISTS idx_logs_span_id ON public.logs (span_id);
24
+ CREATE INDEX IF NOT EXISTS idx_logs_parent_span_id ON public.logs (parent_span_id);
25
+ CREATE INDEX IF NOT EXISTS idx_logs_started_at ON public.logs (started_at);
26
+ CREATE INDEX IF NOT EXISTS idx_logs_account_started_at ON public.logs (account_id, started_at);
27
+ CREATE INDEX IF NOT EXISTS idx_logs_account_trace ON public.logs (account_id, trace_id);
28
+
29
+ -- Add a lightweight check to keep trace IDs valid hex length where present.
30
+ ALTER TABLE public.logs
31
+ ADD CONSTRAINT chk_trace_id_hex CHECK (trace_id IS NULL OR trace_id ~ '^[0-9a-f]{32}$'),
32
+ ADD CONSTRAINT chk_span_id_hex CHECK (span_id IS NULL OR span_id ~ '^[0-9a-f]{16}$'),
33
+ ADD CONSTRAINT chk_parent_span_id_hex CHECK (parent_span_id IS NULL OR parent_span_id ~ '^[0-9a-f]{16}$');
34
+
35
+ -- Migration: add SQL aggregation functions for traces and derived metrics.
36
+
37
+ CREATE OR REPLACE FUNCTION public.get_trace_tree(
38
+ p_account_id uuid,
39
+ p_trace_id text
40
+ )
41
+ RETURNS TABLE (
42
+ span_id text,
43
+ parent_span_id text,
44
+ span_name text,
45
+ kind text,
46
+ level text,
47
+ source text,
48
+ started_at timestamptz,
49
+ ended_at timestamptz,
50
+ duration_ms numeric,
51
+ status text,
52
+ outcome text,
53
+ message text,
54
+ metadata jsonb
55
+ )
56
+ LANGUAGE plpgsql
57
+ SECURITY INVOKER
58
+ AS $$
59
+ BEGIN
60
+ RETURN QUERY
61
+ SELECT
62
+ l.span_id,
63
+ l.parent_span_id,
64
+ l.span_name,
65
+ l.kind,
66
+ l.level,
67
+ l.source,
68
+ l.started_at,
69
+ l.ended_at,
70
+ l.duration_ms,
71
+ CASE
72
+ WHEN l.level ILIKE '%error%' OR l.outcome = 'failure' THEN 'error'
73
+ ELSE 'ok'
74
+ END AS status,
75
+ l.outcome,
76
+ l.message,
77
+ COALESCE(to_jsonb(l.metadata), '{}'::jsonb) AS metadata
78
+ FROM public.logs l
79
+ WHERE l.account_id = p_account_id
80
+ AND l.trace_id = p_trace_id
81
+ ORDER BY l.started_at ASC, l.span_name ASC;
82
+ END;
83
+ $$;
84
+
85
+ CREATE OR REPLACE FUNCTION public.get_trace_stats(
86
+ p_account_id uuid,
87
+ p_date_from timestamptz DEFAULT NULL,
88
+ p_date_to timestamptz DEFAULT NULL
89
+ )
90
+ RETURNS TABLE (
91
+ trace_id text,
92
+ root_span_name text,
93
+ started_at timestamptz,
94
+ duration_ms numeric,
95
+ span_count bigint,
96
+ error_count bigint,
97
+ outcome text
98
+ )
99
+ LANGUAGE plpgsql
100
+ SECURITY INVOKER
101
+ AS $$
102
+ BEGIN
103
+ RETURN QUERY
104
+ SELECT
105
+ l.trace_id,
106
+ MAX(l.span_name) FILTER (WHERE l.parent_span_id IS NULL) AS root_span_name,
107
+ MIN(l.started_at) AS started_at,
108
+ MAX(l.duration_ms) FILTER (WHERE l.parent_span_id IS NULL) AS duration_ms,
109
+ COUNT(*) AS span_count,
110
+ COUNT(*) FILTER (WHERE l.level ILIKE '%error%' OR l.outcome = 'failure') AS error_count,
111
+ MAX(l.outcome) AS outcome
112
+ FROM public.logs l
113
+ WHERE l.account_id = p_account_id
114
+ AND l.trace_id IS NOT NULL
115
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
116
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
117
+ GROUP BY l.trace_id
118
+ ORDER BY started_at DESC;
119
+ END;
120
+ $$;
121
+
122
+ CREATE OR REPLACE FUNCTION public.get_metric_series(
123
+ p_account_id uuid,
124
+ p_metric_name text,
125
+ p_granularity text DEFAULT '1 hour',
126
+ p_date_from timestamptz DEFAULT NULL,
127
+ p_date_to timestamptz DEFAULT NULL
128
+ )
129
+ RETURNS TABLE (
130
+ bucket timestamptz,
131
+ value numeric
132
+ )
133
+ LANGUAGE plpgsql
134
+ SECURITY INVOKER
135
+ AS $$
136
+ BEGIN
137
+ IF p_metric_name = 'request_count' THEN
138
+ RETURN QUERY
139
+ SELECT
140
+ date_trunc(p_granularity, l.started_at) AS bucket,
141
+ COUNT(*)::numeric AS value
142
+ FROM public.logs l
143
+ WHERE l.account_id = p_account_id
144
+ AND l.span_name = 'request'
145
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
146
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
147
+ GROUP BY bucket
148
+ ORDER BY bucket;
149
+
150
+ ELSIF p_metric_name = 'error_rate' THEN
151
+ RETURN QUERY
152
+ SELECT
153
+ date_trunc(p_granularity, l.started_at) AS bucket,
154
+ CASE WHEN COUNT(*) > 0 THEN
155
+ (COUNT(*) FILTER (WHERE l.level ILIKE '%error%' OR l.outcome = 'failure')::numeric / COUNT(*)::numeric)
156
+ ELSE 0 END AS value
157
+ FROM public.logs l
158
+ WHERE l.account_id = p_account_id
159
+ AND l.trace_id IS NOT NULL
160
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
161
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
162
+ GROUP BY bucket
163
+ ORDER BY bucket;
164
+
165
+ ELSIF p_metric_name = 'avg_handler_duration' THEN
166
+ RETURN QUERY
167
+ SELECT
168
+ date_trunc(p_granularity, l.started_at) AS bucket,
169
+ AVG(l.duration_ms) AS value
170
+ FROM public.logs l
171
+ WHERE l.account_id = p_account_id
172
+ AND l.span_name = 'request'
173
+ AND l.duration_ms IS NOT NULL
174
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
175
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
176
+ GROUP BY bucket
177
+ ORDER BY bucket;
178
+
179
+ ELSIF p_metric_name = 'agent_tool_count' THEN
180
+ RETURN QUERY
181
+ SELECT
182
+ date_trunc(p_granularity, l.started_at) AS bucket,
183
+ COUNT(*)::numeric AS value
184
+ FROM public.logs l
185
+ WHERE l.account_id = p_account_id
186
+ AND l.span_name = 'agent.tool'
187
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
188
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
189
+ GROUP BY bucket
190
+ ORDER BY bucket;
191
+
192
+ ELSIF p_metric_name = 'pipeline_failure_rate' THEN
193
+ RETURN QUERY
194
+ SELECT
195
+ date_trunc(p_granularity, l.started_at) AS bucket,
196
+ CASE WHEN COUNT(*) > 0 THEN
197
+ (COUNT(*) FILTER (WHERE l.span_name LIKE 'pipeline%' AND (l.level ILIKE '%error%' OR l.outcome = 'failure'))::numeric / NULLIF(COUNT(*) FILTER (WHERE l.span_name LIKE 'pipeline%'), 0))
198
+ ELSE 0 END AS value
199
+ FROM public.logs l
200
+ WHERE l.account_id = p_account_id
201
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
202
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
203
+ GROUP BY bucket
204
+ ORDER BY bucket;
205
+
206
+ ELSE
207
+ RETURN QUERY
208
+ SELECT
209
+ date_trunc(p_granularity, l.started_at) AS bucket,
210
+ COUNT(*)::numeric AS value
211
+ FROM public.logs l
212
+ WHERE l.account_id = p_account_id
213
+ AND l.span_name = p_metric_name
214
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
215
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
216
+ GROUP BY bucket
217
+ ORDER BY bucket;
218
+ END IF;
219
+ END;
220
+ $$;
221
+
222
+ CREATE OR REPLACE FUNCTION public.get_business_outcomes(
223
+ p_account_id uuid,
224
+ p_date_from timestamptz DEFAULT NULL,
225
+ p_date_to timestamptz DEFAULT NULL
226
+ )
227
+ RETURNS TABLE (
228
+ intent text,
229
+ outcome text,
230
+ count bigint
231
+ )
232
+ LANGUAGE plpgsql
233
+ SECURITY INVOKER
234
+ AS $$
235
+ BEGIN
236
+ RETURN QUERY
237
+ SELECT
238
+ l.span_name AS intent,
239
+ l.outcome,
240
+ COUNT(*) AS count
241
+ FROM public.logs l
242
+ WHERE l.account_id = p_account_id
243
+ AND l.event_category = 'business_outcome'
244
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
245
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
246
+ GROUP BY l.span_name, l.outcome
247
+ ORDER BY count DESC;
248
+ END;
249
+ $$;
250
+
251
+ -- Ensure functions are callable from the authenticated role used by supabase-js.
252
+ GRANT EXECUTE ON FUNCTION public.get_trace_tree(uuid, text) TO authenticated;
253
+ GRANT EXECUTE ON FUNCTION public.get_trace_stats(uuid, timestamptz, timestamptz) TO authenticated;
254
+ GRANT EXECUTE ON FUNCTION public.get_metric_series(uuid, text, text, timestamptz, timestamptz) TO authenticated;
255
+ GRANT EXECUTE ON FUNCTION public.get_business_outcomes(uuid, timestamptz, timestamptz) TO authenticated;
256
+
257
+
258
+
259
+ -- Fix get_metric_series to accept human-readable granularities such as '1 hour', '1 day', '7 days', '30 days'.
260
+ -- date_trunc does not accept interval-style strings, so use date_bin with a fixed UTC origin instead.
261
+
262
+ CREATE OR REPLACE FUNCTION public.get_metric_series(
263
+ p_account_id uuid,
264
+ p_metric_name text,
265
+ p_granularity text DEFAULT '1 hour',
266
+ p_date_from timestamptz DEFAULT NULL,
267
+ p_date_to timestamptz DEFAULT NULL
268
+ )
269
+ RETURNS TABLE (
270
+ bucket timestamptz,
271
+ value numeric
272
+ )
273
+ LANGUAGE plpgsql
274
+ SECURITY INVOKER
275
+ AS $$
276
+ DECLARE
277
+ v_stride interval;
278
+ v_origin timestamptz := '2000-01-01 00:00:00+00'::timestamptz;
279
+ BEGIN
280
+ v_stride := p_granularity::interval;
281
+
282
+ IF p_metric_name = 'request_count' THEN
283
+ RETURN QUERY
284
+ SELECT
285
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
286
+ COUNT(*)::numeric AS value
287
+ FROM public.logs l
288
+ WHERE l.account_id = p_account_id
289
+ AND l.span_name = 'request'
290
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
291
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
292
+ GROUP BY bucket
293
+ ORDER BY bucket;
294
+
295
+ ELSIF p_metric_name = 'error_rate' THEN
296
+ RETURN QUERY
297
+ SELECT
298
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
299
+ CASE WHEN COUNT(*) > 0 THEN
300
+ (COUNT(*) FILTER (WHERE l.level ILIKE '%error%' OR l.outcome = 'failure')::numeric / COUNT(*)::numeric)
301
+ ELSE 0 END AS value
302
+ FROM public.logs l
303
+ WHERE l.account_id = p_account_id
304
+ AND l.trace_id IS NOT NULL
305
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
306
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
307
+ GROUP BY bucket
308
+ ORDER BY bucket;
309
+
310
+ ELSIF p_metric_name = 'avg_handler_duration' THEN
311
+ RETURN QUERY
312
+ SELECT
313
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
314
+ AVG(l.duration_ms) AS value
315
+ FROM public.logs l
316
+ WHERE l.account_id = p_account_id
317
+ AND l.span_name = 'request'
318
+ AND l.duration_ms IS NOT NULL
319
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
320
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
321
+ GROUP BY bucket
322
+ ORDER BY bucket;
323
+
324
+ ELSIF p_metric_name = 'agent_tool_count' THEN
325
+ RETURN QUERY
326
+ SELECT
327
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
328
+ COUNT(*)::numeric AS value
329
+ FROM public.logs l
330
+ WHERE l.account_id = p_account_id
331
+ AND l.span_name = 'agent.tool'
332
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
333
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
334
+ GROUP BY bucket
335
+ ORDER BY bucket;
336
+
337
+ ELSIF p_metric_name = 'pipeline_failure_rate' THEN
338
+ RETURN QUERY
339
+ SELECT
340
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
341
+ CASE WHEN COUNT(*) > 0 THEN
342
+ (COUNT(*) FILTER (WHERE l.span_name LIKE 'pipeline%' AND (l.level ILIKE '%error%' OR l.outcome = 'failure'))::numeric / NULLIF(COUNT(*) FILTER (WHERE l.span_name LIKE 'pipeline%'), 0))
343
+ ELSE 0 END AS value
344
+ FROM public.logs l
345
+ WHERE l.account_id = p_account_id
346
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
347
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
348
+ GROUP BY bucket
349
+ ORDER BY bucket;
350
+
351
+ ELSE
352
+ RETURN QUERY
353
+ SELECT
354
+ date_bin(v_stride, l.started_at, v_origin) AS bucket,
355
+ COUNT(*)::numeric AS value
356
+ FROM public.logs l
357
+ WHERE l.account_id = p_account_id
358
+ AND l.span_name = p_metric_name
359
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
360
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
361
+ GROUP BY bucket
362
+ ORDER BY bucket;
363
+ END IF;
364
+ END;
365
+ $$;
366
+
367
+ -- Re-apply grants in case they were not preserved by CREATE OR REPLACE FUNCTION in older Postgres versions.
368
+ GRANT EXECUTE ON FUNCTION public.get_metric_series(uuid, text, text, timestamptz, timestamptz) TO authenticated;
369
+
370
+
371
+ -- Add optional limit/offset/total support to get_trace_stats so the UI can paginate trace lists.
372
+ CREATE OR REPLACE FUNCTION public.get_trace_stats(
373
+ p_account_id uuid,
374
+ p_date_from timestamptz DEFAULT NULL,
375
+ p_date_to timestamptz DEFAULT NULL,
376
+ p_limit bigint DEFAULT NULL,
377
+ p_offset bigint DEFAULT NULL,
378
+ p_sort text DEFAULT 'started'
379
+ )
380
+ RETURNS TABLE (
381
+ trace_id text,
382
+ root_span_name text,
383
+ started_at timestamptz,
384
+ duration_ms numeric,
385
+ span_count bigint,
386
+ error_count bigint,
387
+ outcome text,
388
+ total bigint
389
+ )
390
+ LANGUAGE plpgsql
391
+ SECURITY INVOKER
392
+ AS $$
393
+ BEGIN
394
+ RETURN QUERY
395
+ WITH grouped AS (
396
+ SELECT
397
+ l.trace_id,
398
+ MAX(l.span_name) FILTER (WHERE l.parent_span_id IS NULL) AS root_span_name,
399
+ MIN(l.started_at) AS started_at,
400
+ MAX(l.duration_ms) FILTER (WHERE l.parent_span_id IS NULL) AS duration_ms,
401
+ COUNT(*) AS span_count,
402
+ COUNT(*) FILTER (WHERE l.level ILIKE '%error%' OR l.outcome = 'failure') AS error_count,
403
+ MAX(l.outcome) AS outcome
404
+ FROM public.logs l
405
+ WHERE l.account_id = p_account_id
406
+ AND l.trace_id IS NOT NULL
407
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
408
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
409
+ GROUP BY l.trace_id
410
+ )
411
+ SELECT
412
+ g.*,
413
+ COUNT(*) OVER() AS total
414
+ FROM grouped g
415
+ ORDER BY
416
+ CASE WHEN p_sort = 'duration' THEN g.duration_ms END DESC NULLS LAST,
417
+ g.started_at DESC
418
+ LIMIT p_limit OFFSET p_offset;
419
+ END;
420
+ $$;
421
+
422
+ -- Add optional limit/offset/total support to get_business_outcomes.
423
+ CREATE OR REPLACE FUNCTION public.get_business_outcomes(
424
+ p_account_id uuid,
425
+ p_date_from timestamptz DEFAULT NULL,
426
+ p_date_to timestamptz DEFAULT NULL,
427
+ p_limit bigint DEFAULT NULL,
428
+ p_offset bigint DEFAULT NULL
429
+ )
430
+ RETURNS TABLE (
431
+ intent text,
432
+ outcome text,
433
+ count bigint,
434
+ total bigint
435
+ )
436
+ LANGUAGE plpgsql
437
+ SECURITY INVOKER
438
+ AS $$
439
+ BEGIN
440
+ RETURN QUERY
441
+ WITH grouped AS (
442
+ SELECT
443
+ l.span_name AS intent,
444
+ l.outcome,
445
+ COUNT(*) AS count
446
+ FROM public.logs l
447
+ WHERE l.account_id = p_account_id
448
+ AND l.event_category = 'business_outcome'
449
+ AND (p_date_from IS NULL OR l.started_at >= p_date_from)
450
+ AND (p_date_to IS NULL OR l.started_at <= p_date_to)
451
+ GROUP BY l.span_name, l.outcome
452
+ )
453
+ SELECT
454
+ g.*,
455
+ COUNT(*) OVER() AS total
456
+ FROM grouped g
457
+ ORDER BY g.count DESC
458
+ LIMIT p_limit OFFSET p_offset;
459
+ END;
460
+ $$;
461
+
462
+ -- Re-apply grants for the new signatures.
463
+ GRANT EXECUTE ON FUNCTION public.get_trace_stats(uuid, timestamptz, timestamptz, bigint, bigint, text) TO authenticated;
464
+
465
+ GRANT EXECUTE ON FUNCTION public.get_business_outcomes(uuid, timestamptz, timestamptz, bigint, bigint) TO authenticated;
466
+
467
+
468
+ -- Postgres created overloaded variants because CREATE OR REPLACE FUNCTION cannot change signatures.
469
+ -- Drop the old 3-argument versions so callers using three arguments resolve to the new paginated variants.
470
+ DROP FUNCTION IF EXISTS public.get_trace_stats(uuid, timestamptz, timestamptz);
471
+ DROP FUNCTION IF EXISTS public.get_business_outcomes(uuid, timestamptz, timestamptz);
472
+
473
+ DROP POLICY IF EXISTS items_access ON public.items;
474
+
475
+ CREATE POLICY items_access ON public.items FOR ALL
476
+ USING (
477
+ account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))
478
+ OR (design_schema->>'scope' = 'platform' AND public.current_actor_id() IS NOT NULL)
479
+ OR public.current_actor_id() IS NULL
480
+ );
481
+
482
+ CREATE INDEX IF NOT EXISTS items_status_idx ON public.items (status);
483
+
484
+ -- Update match_embeddings to support item-type/document filtering and to
485
+ -- exclude revoked or unapproved embeddings.
486
+
487
+ CREATE OR REPLACE FUNCTION public.match_embeddings(
488
+ query_embedding vector(1536),
489
+ match_count int DEFAULT 10,
490
+ filter_account_ids uuid[] DEFAULT NULL,
491
+ filter_vector_type text DEFAULT NULL,
492
+ similarity_threshold float DEFAULT 0.15,
493
+ filter_item_types text[] DEFAULT NULL,
494
+ filter_document_ids text[] DEFAULT NULL
495
+ )
496
+ RETURNS TABLE (
497
+ id uuid,
498
+ document_id text,
499
+ content text,
500
+ metadata jsonb,
501
+ account_id uuid,
502
+ similarity float
503
+ )
504
+ LANGUAGE plpgsql
505
+ SECURITY DEFINER
506
+ SET search_path = public
507
+ AS $$
508
+ BEGIN
509
+ RETURN QUERY
510
+ SELECT
511
+ e.id,
512
+ e.document_id,
513
+ e.content,
514
+ e.metadata,
515
+ e.account_id,
516
+ 1 - (e.embedding <=> query_embedding) AS similarity
517
+ FROM public.embeddings e
518
+ WHERE
519
+ (filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
520
+ AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
521
+ AND (filter_item_types IS NULL OR e.metadata->>'item_type' = ANY(filter_item_types))
522
+ AND (filter_document_ids IS NULL OR e.document_id = ANY(filter_document_ids))
523
+ AND e.approved_at IS NOT NULL
524
+ AND e.revoked_at IS NULL
525
+ AND (1 - (e.embedding <=> query_embedding)) >= similarity_threshold
526
+ ORDER BY e.embedding <=> query_embedding
527
+ LIMIT match_count;
528
+ END
529
+ $$;
530
+
531
+ GRANT EXECUTE ON FUNCTION public.match_embeddings(
532
+ vector(1536), int, uuid[], text, float, text[], text[]
533
+ ) TO authenticated, service_role;
534
+
535
+ -- =====================================================
536
+ -- DATA RETENTION
537
+ -- =====================================================
538
+
539
+ CREATE OR REPLACE FUNCTION public.prune_logs(
540
+ p_account_id uuid
541
+ )
542
+ RETURNS TABLE (deleted_count bigint)
543
+ LANGUAGE plpgsql
544
+ SECURITY INVOKER
545
+ SET search_path = public
546
+ AS $$
547
+ DECLARE
548
+ v_retention_days int;
549
+ v_cutoff timestamptz;
550
+ v_count bigint;
551
+ BEGIN
552
+ SELECT COALESCE(a.logs_retention_days, 30) INTO v_retention_days
553
+ FROM public.accounts a
554
+ WHERE a.id = p_account_id;
555
+
556
+ v_cutoff := now() - (v_retention_days || ' days')::interval;
557
+
558
+ DELETE FROM public.logs l
559
+ WHERE l.account_id = p_account_id
560
+ AND l.started_at < v_cutoff;
561
+
562
+ GET DIAGNOSTICS v_count = ROW_COUNT;
563
+ RETURN QUERY SELECT v_count;
564
+ END
565
+ $$;
566
+
567
+ CREATE OR REPLACE FUNCTION public.prune_observability(
568
+ p_account_id uuid
569
+ )
570
+ RETURNS TABLE (deleted_count bigint)
571
+ LANGUAGE plpgsql
572
+ SECURITY INVOKER
573
+ SET search_path = public
574
+ AS $$
575
+ DECLARE
576
+ v_retention_days int;
577
+ v_cutoff timestamptz;
578
+ v_count bigint;
579
+ BEGIN
580
+ SELECT COALESCE(a.observability_retention_days, 90) INTO v_retention_days
581
+ FROM public.accounts a
582
+ WHERE a.id = p_account_id;
583
+
584
+ v_cutoff := now() - (v_retention_days || ' days')::interval;
585
+
586
+ DELETE FROM public.observability o
587
+ WHERE o.account_id = p_account_id
588
+ AND o.created_at < v_cutoff;
589
+
590
+ GET DIAGNOSTICS v_count = ROW_COUNT;
591
+ RETURN QUERY SELECT v_count;
592
+ END
593
+ $$;
594
+
595
+ GRANT EXECUTE ON FUNCTION public.prune_logs(uuid) TO authenticated;
596
+ GRANT EXECUTE ON FUNCTION public.prune_logs(uuid) TO service_role;
597
+ GRANT EXECUTE ON FUNCTION public.prune_observability(uuid) TO authenticated;
598
+ GRANT EXECUTE ON FUNCTION public.prune_observability(uuid) TO service_role;
@@ -0,0 +1,97 @@
1
+ -- Security hardening: close RLS bypasses that allowed unauthenticated
2
+ -- (no-JWT / anon-key-only) requests to read and write sensitive tables
3
+ -- directly via PostgREST, and remove `OR true` policies that made
4
+ -- `types`/`watchers` writable by anyone.
5
+ --
6
+ -- Context: service_role (adminDb) already has explicit, dedicated
7
+ -- `*_service_role` bypass policies (see 004_core_rpc_and_broker.sql), so
8
+ -- none of the clauses removed here were needed for legitimate backend
9
+ -- access. They only ever widened access for requests made with the
10
+ -- public anon key and no Supabase Auth JWT (auth.uid() IS NULL /
11
+ -- public.current_actor_id() IS NULL), which is exactly the case for an
12
+ -- unauthenticated browser calling PostgREST directly.
13
+
14
+ -- ============================================
15
+ -- integrations / api_keys / api_key_usage_logs / webhook_handlers /
16
+ -- webhook_deliveries: remove the "OR auth.uid() IS NULL" bypass
17
+ -- introduced in 004_core_rpc_and_broker.sql.
18
+ -- ============================================
19
+
20
+ DROP POLICY IF EXISTS integrations_access ON public.integrations;
21
+ CREATE POLICY integrations_access ON public.integrations FOR ALL
22
+ USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid())));
23
+
24
+ DROP POLICY IF EXISTS api_keys_access ON public.api_keys;
25
+ CREATE POLICY api_keys_access ON public.api_keys FOR ALL
26
+ USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid())));
27
+
28
+ DROP POLICY IF EXISTS akul_access ON public.api_key_usage_logs;
29
+ CREATE POLICY akul_access ON public.api_key_usage_logs FOR ALL
30
+ USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid())));
31
+
32
+ DROP POLICY IF EXISTS webhook_handlers_access ON public.webhook_handlers;
33
+ CREATE POLICY webhook_handlers_access ON public.webhook_handlers FOR ALL
34
+ USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid())));
35
+
36
+ DROP POLICY IF EXISTS webhook_deliveries_access ON public.webhook_deliveries;
37
+ CREATE POLICY webhook_deliveries_access ON public.webhook_deliveries FOR ALL
38
+ USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid())));
39
+
40
+ -- ============================================
41
+ -- items: remove the "OR public.current_actor_id() IS NULL" bypass
42
+ -- introduced in 005_core_observability.sql. Keep the platform-scope
43
+ -- clause for authenticated users.
44
+ -- ============================================
45
+
46
+ DROP POLICY IF EXISTS items_access ON public.items;
47
+ CREATE POLICY items_access ON public.items FOR ALL
48
+ USING (
49
+ account_id IN (SELECT public.get_accessible_accounts(public.current_actor_id()))
50
+ OR (design_schema->>'scope' = 'platform' AND public.current_actor_id() IS NOT NULL)
51
+ );
52
+
53
+ -- ============================================
54
+ -- types: was `USING (public.current_actor_id() IS NOT NULL OR true)`,
55
+ -- i.e. always true regardless of the left-hand side, so ANY caller
56
+ -- (including unauthenticated anon-key requests) could INSERT/UPDATE/
57
+ -- DELETE type/design-schema definitions directly, bypassing the
58
+ -- system-admin check that `types.ts` enforces at the application layer.
59
+ -- Split into a public read policy (matches existing `roles_read`
60
+ -- pattern; type/schema metadata is not sensitive) and a system-admin
61
+ -- write policy.
62
+ -- ============================================
63
+
64
+ DROP POLICY IF EXISTS types_access ON public.types;
65
+
66
+ CREATE POLICY types_read ON public.types FOR SELECT USING (true);
67
+
68
+ CREATE POLICY types_insert ON public.types FOR INSERT
69
+ WITH CHECK (public.person_is_system_admin(public.current_actor_id()));
70
+
71
+ CREATE POLICY types_update ON public.types FOR UPDATE
72
+ USING (public.person_is_system_admin(public.current_actor_id()));
73
+
74
+ CREATE POLICY types_delete ON public.types FOR DELETE
75
+ USING (public.person_is_system_admin(public.current_actor_id()));
76
+
77
+ -- Service role (adminDb) always bypasses RLS regardless of policy, but
78
+ -- keep an explicit policy for parity with other tables and future-proofing
79
+ -- against BYPASSRLS being revoked from service_role.
80
+ DROP POLICY IF EXISTS types_service_role ON public.types;
81
+ CREATE POLICY types_service_role ON public.types FOR ALL TO service_role USING (true) WITH CHECK (true);
82
+
83
+ -- ============================================
84
+ -- watchers: was `USING (auth.uid() IS NOT NULL OR true)`, same always-true
85
+ -- bug as types. Scope to the caller's accessible accounts like other
86
+ -- account-scoped runtime tables.
87
+ -- ============================================
88
+
89
+ DROP POLICY IF EXISTS watchers_access ON public.watchers;
90
+ CREATE POLICY watchers_access ON public.watchers FOR ALL
91
+ USING (
92
+ account_id IS NULL
93
+ OR account_id IN (SELECT public.get_accessible_accounts(auth.uid()))
94
+ );
95
+
96
+ DROP POLICY IF EXISTS watchers_service_role ON public.watchers;
97
+ CREATE POLICY watchers_service_role ON public.watchers FOR ALL TO service_role USING (true) WITH CHECK (true);