spine-framework 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/.framework/README.md +129 -0
  2. package/.framework/cli/bin.cjs +14 -0
  3. package/.framework/cli/commands/agents.ts +153 -0
  4. package/.framework/cli/commands/auth.ts +94 -0
  5. package/.framework/cli/commands/create-app.ts +185 -0
  6. package/.framework/cli/commands/dev.ts +295 -0
  7. package/.framework/cli/commands/doctor.ts +442 -0
  8. package/.framework/cli/commands/generate.ts +332 -0
  9. package/.framework/cli/commands/init.ts +272 -0
  10. package/.framework/cli/commands/install-app.ts +391 -0
  11. package/.framework/cli/commands/items.ts +253 -0
  12. package/.framework/cli/commands/migrations.ts +141 -0
  13. package/.framework/cli/commands/pipelines.ts +166 -0
  14. package/.framework/cli/commands/status.ts +197 -0
  15. package/.framework/cli/commands/system.ts +184 -0
  16. package/.framework/cli/commands/test.ts +227 -0
  17. package/.framework/cli/commands/uninstall-app.ts +166 -0
  18. package/.framework/cli/context.ts +268 -0
  19. package/.framework/cli/env-loader.ts +36 -0
  20. package/.framework/cli/index.ts +106 -0
  21. package/.framework/cli/welcome.cjs +45 -0
  22. package/.framework/docs/API.md +384 -0
  23. package/.framework/docs/STABILITY.md +52 -0
  24. package/.framework/docs/admin-routes.md +76 -0
  25. package/.framework/docs/api-docs-progress.md +38 -0
  26. package/.framework/docs/api-governance.md +146 -0
  27. package/.framework/docs/api-testing-results.md +212 -0
  28. package/.framework/docs/apis/admin-configs.md +567 -0
  29. package/.framework/docs/apis/admin-data.md +272 -0
  30. package/.framework/docs/apis/index.md +231 -0
  31. package/.framework/docs/apis/internal.md +295 -0
  32. package/.framework/docs/apis/runtime.md +537 -0
  33. package/.framework/docs/assembly-launch-guide.md +138 -0
  34. package/.framework/docs/audit-results.md +590 -0
  35. package/.framework/docs/authorization-model.md +170 -0
  36. package/.framework/docs/db-api-inventory.md +95 -0
  37. package/.framework/docs/examples/custom-app/README.md +77 -0
  38. package/.framework/docs/examples/custom-function/README.md +27 -0
  39. package/.framework/docs/examples/custom-function/handler.ts +48 -0
  40. package/.framework/docs/examples/custom-webhook/README.md +68 -0
  41. package/.framework/docs/gap-remediation-backlog.md +103 -0
  42. package/.framework/docs/guides/cli-guide.md +224 -0
  43. package/.framework/docs/guides/getting-started.md +103 -0
  44. package/.framework/docs/guides/import-guide.md +193 -0
  45. package/.framework/docs/guides/testing-guide.md +229 -0
  46. package/.framework/docs/permission-examples.md +326 -0
  47. package/.framework/docs/ui-adoption-verification.md +111 -0
  48. package/.framework/docs/ui-api-coverage.md +84 -0
  49. package/.framework/docs/v2-compatibility-audit.md +228 -0
  50. package/.framework/functions/.gitkeep +1 -0
  51. package/.framework/functions/_shared/agent-runner.ts +1097 -0
  52. package/.framework/functions/_shared/app-manifest.ts +184 -0
  53. package/.framework/functions/_shared/audit.ts +150 -0
  54. package/.framework/functions/_shared/db.ts +174 -0
  55. package/.framework/functions/_shared/index.ts +382 -0
  56. package/.framework/functions/_shared/middleware.ts +490 -0
  57. package/.framework/functions/_shared/permissions.ts +1325 -0
  58. package/.framework/functions/_shared/pipeline-runner.ts +731 -0
  59. package/.framework/functions/_shared/principal.ts +760 -0
  60. package/.framework/functions/_shared/schema-utils.ts +967 -0
  61. package/.framework/functions/_shared/testing.ts +258 -0
  62. package/.framework/functions/_shared/trigger-engine.ts +425 -0
  63. package/.framework/functions/_shared/webhook-registration.ts +168 -0
  64. package/.framework/functions/_shared/webhook-registry.ts +129 -0
  65. package/.framework/functions/account-nodes.ts +111 -0
  66. package/.framework/functions/admin-data.ts +606 -0
  67. package/.framework/functions/ai-agents.ts +323 -0
  68. package/.framework/functions/api-keys.ts +376 -0
  69. package/.framework/functions/apps.ts +483 -0
  70. package/.framework/functions/auth.ts +196 -0
  71. package/.framework/functions/debug-auth.ts +107 -0
  72. package/.framework/functions/embeddings.ts +556 -0
  73. package/.framework/functions/integration-routes.ts +523 -0
  74. package/.framework/functions/integrations.ts +319 -0
  75. package/.framework/functions/item-progress.ts +272 -0
  76. package/.framework/functions/logs.ts +438 -0
  77. package/.framework/functions/observability.ts +275 -0
  78. package/.framework/functions/pipeline-executions.ts +494 -0
  79. package/.framework/functions/pipelines.ts +485 -0
  80. package/.framework/functions/prompt-configs.ts +339 -0
  81. package/.framework/functions/roles.ts +387 -0
  82. package/.framework/functions/system-cron.ts +742 -0
  83. package/.framework/functions/system.ts +323 -0
  84. package/.framework/functions/tests.ts +119 -0
  85. package/.framework/functions/timers.ts +357 -0
  86. package/.framework/functions/triggers.ts +563 -0
  87. package/.framework/functions/types.ts +604 -0
  88. package/.framework/migrations/000_foundation.sql +1256 -0
  89. package/.framework/migrations/001_seed.sql +92 -0
  90. package/.framework/migrations/002_seed_constraints.sql +13 -0
  91. package/.framework/migrations/003_auth_user_trigger.sql +59 -0
  92. package/.framework/src/App.tsx +126 -0
  93. package/.framework/src/apps/admin/index.tsx +173 -0
  94. package/.framework/src/components/AppWrapper.tsx +56 -0
  95. package/.framework/src/components/CustomAppLoader.tsx +116 -0
  96. package/.framework/src/components/admin/AdminListPage.tsx +151 -0
  97. package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
  98. package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
  99. package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
  100. package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
  101. package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
  102. package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
  103. package/.framework/src/components/app-sidebar.tsx +228 -0
  104. package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
  105. package/.framework/src/components/layout/AppShell.tsx +91 -0
  106. package/.framework/src/components/layout/Header.tsx +88 -0
  107. package/.framework/src/components/layout/Layout.tsx +95 -0
  108. package/.framework/src/components/layout/Sidebar.tsx +329 -0
  109. package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
  110. package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
  111. package/.framework/src/components/runtime/DataFilters.tsx +91 -0
  112. package/.framework/src/components/runtime/DataHeader.tsx +68 -0
  113. package/.framework/src/components/runtime/DataListPage.tsx +124 -0
  114. package/.framework/src/components/runtime/DataStats.tsx +70 -0
  115. package/.framework/src/components/runtime/DataTable.tsx +174 -0
  116. package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
  117. package/.framework/src/components/runtime/index.ts +18 -0
  118. package/.framework/src/components/search-form.tsx +29 -0
  119. package/.framework/src/components/shared/AgentView.tsx +213 -0
  120. package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
  121. package/.framework/src/components/shared/SchemaFields.tsx +226 -0
  122. package/.framework/src/components/ui/DataTable.tsx +343 -0
  123. package/.framework/src/components/ui/Form.tsx +281 -0
  124. package/.framework/src/components/ui/ItemCard.tsx +296 -0
  125. package/.framework/src/components/ui/ItemListView.tsx +308 -0
  126. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
  127. package/.framework/src/components/ui/Modal.tsx +61 -0
  128. package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
  129. package/.framework/src/components/ui/accordion.tsx +82 -0
  130. package/.framework/src/components/ui/alert-dialog.tsx +197 -0
  131. package/.framework/src/components/ui/alert.tsx +76 -0
  132. package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
  133. package/.framework/src/components/ui/avatar.tsx +110 -0
  134. package/.framework/src/components/ui/badge.tsx +49 -0
  135. package/.framework/src/components/ui/breadcrumb.tsx +122 -0
  136. package/.framework/src/components/ui/button-group.tsx +83 -0
  137. package/.framework/src/components/ui/button.tsx +65 -0
  138. package/.framework/src/components/ui/calendar.tsx +222 -0
  139. package/.framework/src/components/ui/card.tsx +100 -0
  140. package/.framework/src/components/ui/carousel.tsx +240 -0
  141. package/.framework/src/components/ui/chart.tsx +373 -0
  142. package/.framework/src/components/ui/checkbox.tsx +31 -0
  143. package/.framework/src/components/ui/collapsible.tsx +33 -0
  144. package/.framework/src/components/ui/combobox.tsx +299 -0
  145. package/.framework/src/components/ui/command.tsx +193 -0
  146. package/.framework/src/components/ui/context-menu.tsx +261 -0
  147. package/.framework/src/components/ui/dialog.tsx +165 -0
  148. package/.framework/src/components/ui/direction.tsx +22 -0
  149. package/.framework/src/components/ui/drawer.tsx +132 -0
  150. package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
  151. package/.framework/src/components/ui/empty.tsx +104 -0
  152. package/.framework/src/components/ui/field.tsx +238 -0
  153. package/.framework/src/components/ui/hover-card.tsx +42 -0
  154. package/.framework/src/components/ui/input-group.tsx +153 -0
  155. package/.framework/src/components/ui/input-otp.tsx +87 -0
  156. package/.framework/src/components/ui/input.tsx +19 -0
  157. package/.framework/src/components/ui/item.tsx +196 -0
  158. package/.framework/src/components/ui/kbd.tsx +26 -0
  159. package/.framework/src/components/ui/label.tsx +22 -0
  160. package/.framework/src/components/ui/menubar.tsx +277 -0
  161. package/.framework/src/components/ui/native-select.tsx +61 -0
  162. package/.framework/src/components/ui/navigation-menu.tsx +164 -0
  163. package/.framework/src/components/ui/pagination.tsx +129 -0
  164. package/.framework/src/components/ui/popover.tsx +87 -0
  165. package/.framework/src/components/ui/progress.tsx +31 -0
  166. package/.framework/src/components/ui/radio-group.tsx +42 -0
  167. package/.framework/src/components/ui/resizable.tsx +50 -0
  168. package/.framework/src/components/ui/scroll-area.tsx +53 -0
  169. package/.framework/src/components/ui/select.tsx +195 -0
  170. package/.framework/src/components/ui/separator.tsx +26 -0
  171. package/.framework/src/components/ui/sheet.tsx +145 -0
  172. package/.framework/src/components/ui/sidebar.tsx +706 -0
  173. package/.framework/src/components/ui/skeleton.tsx +13 -0
  174. package/.framework/src/components/ui/slider.tsx +59 -0
  175. package/.framework/src/components/ui/sonner.tsx +47 -0
  176. package/.framework/src/components/ui/spinner.tsx +10 -0
  177. package/.framework/src/components/ui/switch.tsx +33 -0
  178. package/.framework/src/components/ui/table-primitives.tsx +141 -0
  179. package/.framework/src/components/ui/table.tsx +114 -0
  180. package/.framework/src/components/ui/tabs.tsx +90 -0
  181. package/.framework/src/components/ui/textarea.tsx +18 -0
  182. package/.framework/src/components/ui/toggle-group.tsx +89 -0
  183. package/.framework/src/components/ui/toggle.tsx +45 -0
  184. package/.framework/src/components/ui/tooltip.tsx +57 -0
  185. package/.framework/src/contexts/AppContext.tsx +133 -0
  186. package/.framework/src/contexts/AuthContext.tsx +371 -0
  187. package/.framework/src/hooks/use-mobile.ts +19 -0
  188. package/.framework/src/hooks/useApi.ts +526 -0
  189. package/.framework/src/hooks/useApps.ts +114 -0
  190. package/.framework/src/hooks/useEntityList.ts +190 -0
  191. package/.framework/src/hooks/useEntityRecord.ts +308 -0
  192. package/.framework/src/hooks/useForm.ts +307 -0
  193. package/.framework/src/hooks/useListSchema.ts +264 -0
  194. package/.framework/src/hooks/useSchemaRecord.ts +223 -0
  195. package/.framework/src/index.css +128 -0
  196. package/.framework/src/lib/api.ts +156 -0
  197. package/.framework/src/lib/supabase.ts +94 -0
  198. package/.framework/src/lib/utils.ts +317 -0
  199. package/.framework/src/main.tsx +27 -0
  200. package/.framework/src/pages/DashboardPage.tsx +181 -0
  201. package/.framework/src/pages/NotFoundPage.tsx +39 -0
  202. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
  203. package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
  204. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
  205. package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
  206. package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
  207. package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
  208. package/.framework/src/pages/admin/AppsPage.tsx +355 -0
  209. package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
  210. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
  211. package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
  212. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
  213. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
  214. package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
  215. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
  216. package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
  217. package/.framework/src/pages/admin/LogsPage.tsx +283 -0
  218. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
  219. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
  220. package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
  221. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
  222. package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
  223. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
  224. package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
  225. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
  226. package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
  227. package/.framework/src/pages/admin/RolesPage.tsx +292 -0
  228. package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
  229. package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
  230. package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
  231. package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
  232. package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
  233. package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
  234. package/.framework/src/pages/admin/TimersPage.tsx +376 -0
  235. package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
  236. package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
  237. package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
  238. package/.framework/src/pages/admin/TypesPage.tsx +295 -0
  239. package/.framework/src/pages/auth/LoginPage.tsx +188 -0
  240. package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
  241. package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
  242. package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
  243. package/.framework/src/types/auth.ts +125 -0
  244. package/.framework/src/types/types.ts +407 -0
  245. package/STRUCTURE.md +150 -0
  246. package/config/components.json +25 -0
  247. package/config/deno.lock +108 -0
  248. package/config/package-lock.json +17183 -0
  249. package/config/postcss.config.cjs +10 -0
  250. package/config/tailwind.config.cjs +78 -0
  251. package/config/tsconfig.build.json +32 -0
  252. package/config/tsconfig.cli.json +18 -0
  253. package/config/tsconfig.json +41 -0
  254. package/config/tsconfig.node.json +17 -0
  255. package/config/tsconfig.node.tsbuildinfo +1 -0
  256. package/config/tsconfig.tsbuildinfo +1 -0
  257. package/config/typedoc.json +16 -0
  258. package/config/vite.config.d.ts +2 -0
  259. package/config/vite.config.ts +72 -0
  260. package/dist/cli/commands/agents.d.ts +39 -0
  261. package/dist/cli/commands/agents.d.ts.map +1 -0
  262. package/dist/cli/commands/auth.d.ts +36 -0
  263. package/dist/cli/commands/auth.d.ts.map +1 -0
  264. package/dist/cli/commands/create-app.d.ts +23 -0
  265. package/dist/cli/commands/create-app.d.ts.map +1 -0
  266. package/dist/cli/commands/dev.d.ts +39 -0
  267. package/dist/cli/commands/dev.d.ts.map +1 -0
  268. package/dist/cli/commands/doctor.d.ts +42 -0
  269. package/dist/cli/commands/doctor.d.ts.map +1 -0
  270. package/dist/cli/commands/generate.d.ts +36 -0
  271. package/dist/cli/commands/generate.d.ts.map +1 -0
  272. package/dist/cli/commands/init.d.ts +30 -0
  273. package/dist/cli/commands/init.d.ts.map +1 -0
  274. package/dist/cli/commands/install-app.d.ts +30 -0
  275. package/dist/cli/commands/install-app.d.ts.map +1 -0
  276. package/dist/cli/commands/items.d.ts +45 -0
  277. package/dist/cli/commands/items.d.ts.map +1 -0
  278. package/dist/cli/commands/migrations.d.ts +41 -0
  279. package/dist/cli/commands/migrations.d.ts.map +1 -0
  280. package/dist/cli/commands/pipelines.d.ts +40 -0
  281. package/dist/cli/commands/pipelines.d.ts.map +1 -0
  282. package/dist/cli/commands/status.d.ts +23 -0
  283. package/dist/cli/commands/status.d.ts.map +1 -0
  284. package/dist/cli/commands/system.d.ts +29 -0
  285. package/dist/cli/commands/system.d.ts.map +1 -0
  286. package/dist/cli/commands/test.d.ts +46 -0
  287. package/dist/cli/commands/test.d.ts.map +1 -0
  288. package/dist/cli/commands/uninstall-app.d.ts +23 -0
  289. package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
  290. package/dist/cli/context.d.ts +88 -0
  291. package/dist/cli/context.d.ts.map +1 -0
  292. package/dist/cli/env-loader.d.ts +14 -0
  293. package/dist/cli/env-loader.d.ts.map +1 -0
  294. package/dist/cli/index.d.ts +41 -0
  295. package/dist/cli/index.d.ts.map +1 -0
  296. package/dist/functions/_shared/agent-runner.d.ts +156 -0
  297. package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
  298. package/dist/functions/_shared/app-manifest.d.ts +68 -0
  299. package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
  300. package/dist/functions/_shared/audit.d.ts +91 -0
  301. package/dist/functions/_shared/audit.d.ts.map +1 -0
  302. package/dist/functions/_shared/db.d.ts +125 -0
  303. package/dist/functions/_shared/db.d.ts.map +1 -0
  304. package/dist/functions/_shared/index.d.ts +298 -0
  305. package/dist/functions/_shared/index.d.ts.map +1 -0
  306. package/dist/functions/_shared/middleware.d.ts +315 -0
  307. package/dist/functions/_shared/middleware.d.ts.map +1 -0
  308. package/dist/functions/_shared/permissions.d.ts +626 -0
  309. package/dist/functions/_shared/permissions.d.ts.map +1 -0
  310. package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
  311. package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
  312. package/dist/functions/_shared/principal.d.ts +284 -0
  313. package/dist/functions/_shared/principal.d.ts.map +1 -0
  314. package/dist/functions/_shared/schema-utils.d.ts +181 -0
  315. package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
  316. package/dist/functions/_shared/testing.d.ts +172 -0
  317. package/dist/functions/_shared/testing.d.ts.map +1 -0
  318. package/dist/functions/_shared/trigger-engine.d.ts +140 -0
  319. package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
  320. package/dist/functions/_shared/webhook-registration.d.ts +81 -0
  321. package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
  322. package/dist/functions/_shared/webhook-registry.d.ts +57 -0
  323. package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
  324. package/dist/functions/account-nodes.d.ts +48 -0
  325. package/dist/functions/account-nodes.d.ts.map +1 -0
  326. package/dist/functions/admin-data.d.ts +178 -0
  327. package/dist/functions/admin-data.d.ts.map +1 -0
  328. package/dist/functions/ai-agents.d.ts +125 -0
  329. package/dist/functions/ai-agents.d.ts.map +1 -0
  330. package/dist/functions/api-keys.d.ts +140 -0
  331. package/dist/functions/api-keys.d.ts.map +1 -0
  332. package/dist/functions/apps.d.ts +163 -0
  333. package/dist/functions/apps.d.ts.map +1 -0
  334. package/dist/functions/auth.d.ts +74 -0
  335. package/dist/functions/auth.d.ts.map +1 -0
  336. package/dist/functions/debug-auth.d.ts +33 -0
  337. package/dist/functions/debug-auth.d.ts.map +1 -0
  338. package/dist/functions/embeddings.d.ts +205 -0
  339. package/dist/functions/embeddings.d.ts.map +1 -0
  340. package/dist/functions/integration-routes.d.ts +45 -0
  341. package/dist/functions/integration-routes.d.ts.map +1 -0
  342. package/dist/functions/integrations.d.ts +124 -0
  343. package/dist/functions/integrations.d.ts.map +1 -0
  344. package/dist/functions/item-progress.d.ts +41 -0
  345. package/dist/functions/item-progress.d.ts.map +1 -0
  346. package/dist/functions/logs.d.ts +162 -0
  347. package/dist/functions/logs.d.ts.map +1 -0
  348. package/dist/functions/observability.d.ts +123 -0
  349. package/dist/functions/observability.d.ts.map +1 -0
  350. package/dist/functions/pipeline-executions.d.ts +190 -0
  351. package/dist/functions/pipeline-executions.d.ts.map +1 -0
  352. package/dist/functions/pipelines.d.ts +171 -0
  353. package/dist/functions/pipelines.d.ts.map +1 -0
  354. package/dist/functions/prompt-configs.d.ts +125 -0
  355. package/dist/functions/prompt-configs.d.ts.map +1 -0
  356. package/dist/functions/roles.d.ts +118 -0
  357. package/dist/functions/roles.d.ts.map +1 -0
  358. package/dist/functions/system-cron.d.ts +65 -0
  359. package/dist/functions/system-cron.d.ts.map +1 -0
  360. package/dist/functions/system.d.ts +29 -0
  361. package/dist/functions/system.d.ts.map +1 -0
  362. package/dist/functions/tests.d.ts +28 -0
  363. package/dist/functions/tests.d.ts.map +1 -0
  364. package/dist/functions/timers.d.ts +139 -0
  365. package/dist/functions/timers.d.ts.map +1 -0
  366. package/dist/functions/triggers.d.ts +203 -0
  367. package/dist/functions/triggers.d.ts.map +1 -0
  368. package/dist/functions/types.d.ts +151 -0
  369. package/dist/functions/types.d.ts.map +1 -0
  370. package/dist/src/types/types.d.ts +364 -0
  371. package/dist/src/types/types.d.ts.map +1 -0
  372. package/package.json +192 -0
  373. package/scripts/app-install-cli.ts +286 -0
  374. package/scripts/assemble-frontend.sh +79 -0
  375. package/scripts/assemble-functions.sh +62 -0
  376. package/scripts/assemble.sh +35 -0
  377. package/scripts/boundary-check.sh +106 -0
  378. package/scripts/build-manifest.sh +80 -0
  379. package/scripts/check-core-integrity.sh +82 -0
  380. package/scripts/ingest-chunks.cjs +202 -0
  381. package/scripts/kb-chunk-parser.cjs +312 -0
  382. package/scripts/kb-chunk-parser.ts +330 -0
  383. package/scripts/load-test-app-install.ts +484 -0
  384. package/scripts/netlify-dev-wrapper.sh +22 -0
  385. package/scripts/verify-integrity.sh +69 -0
@@ -0,0 +1,742 @@
1
+ /**
2
+ * @module system-cron
3
+ * @audience core-contributor
4
+ * @layer api-handler
5
+ * @stability stable
6
+ *
7
+ * Scheduled job runner invoked by an external cron service (e.g., AWS
8
+ * EventBridge, Google Cloud Scheduler). On each tick it:
9
+ *
10
+ * 1. Authenticates the request via `SCHEDULER_API_KEY` or machine principal
11
+ * 2. Fetches all schedules due via `get_due_schedules` RPC
12
+ * 3. For each schedule: validates creator, loads action + machine principal,
13
+ * checks scope, executes action, records outcome
14
+ * 4. Fetches all timers due via `get_due_timers` RPC and runs each pipeline
15
+ * 5. Evaluates threshold alerts via `evaluateThresholds`
16
+ * 6. Runs daily log cleanup at 00:00 UTC via `cleanupOldLogs`
17
+ *
18
+ * **Routed by:** `POST /.netlify/functions/system-cron`
19
+ *
20
+ * **Authentication:** Request must supply `SCHEDULER_API_KEY` via `?api_key`
21
+ * or `?scheduler_key`, OR originate from an internal machine principal
22
+ * (type='machine', machineType='internal'). Unauthorized requests receive 403.
23
+ *
24
+ * **Response shape:**
25
+ * ```ts
26
+ * {
27
+ * executed: number
28
+ * success: number
29
+ * failed: number
30
+ * skipped: number
31
+ * thresholds_evaluated: number
32
+ * thresholds_breached: number
33
+ * logs_cleaned: number
34
+ * results: Array<{ scheduleId, actionId, status, error?, durationMs }>
35
+ * }
36
+ * ```
37
+ *
38
+ * INVARIANT: Uses `adminDb` (service-role client, bypasses RLS) because
39
+ * machine principals must access cross-account data.
40
+ * INVARIANT: Log cleanup only runs when the current UTC time is 00:00.
41
+ *
42
+ * @seeAlso timers.ts (timer configuration CRUD)
43
+ * @seeAlso pipelines.ts (pipeline configuration CRUD)
44
+ * @seeAlso pipeline-runner.ts (runPipeline — timer/schedule execution)
45
+ * @seeAlso observability.ts (analytics RPCs used by evaluateThresholds)
46
+ * @seeAlso audit.ts (emitAudit for cron lifecycle events)
47
+ */
48
+
49
+ import { createHandler, RequestContext } from './_shared/middleware'
50
+ import { adminDb } from './_shared/db'
51
+ import { emitAudit } from './_shared/audit'
52
+ import { runPipeline } from './_shared/pipeline-runner'
53
+
54
+ const SCHEDULER_API_KEY: string | undefined = (globalThis as any).process?.env?.SCHEDULER_API_KEY || (globalThis as any).Deno?.env?.get?.('SCHEDULER_API_KEY')
55
+
56
+ // ─── MAIN HANDLER ────────────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * Netlify function entry point — the entire cron tick logic lives here.
60
+ * Returns HTTP 200 with a results summary on success, or HTTP 403/500
61
+ * on auth/runtime failure.
62
+ *
63
+ * @throws Returns 403 JSON if scheduler key is missing or invalid
64
+ * @throws Returns 500 JSON on unhandled top-level error
65
+ * @sideEffects DB read: get_due_schedules, validate_schedule_creator,
66
+ * actions, api_keys, get_due_timers
67
+ * @sideEffects DB write: schedule_executions (INSERT), update_schedule_after_run
68
+ * RPC, update_timer_after_run RPC, cleanup_old_logs RPC
69
+ * @sideEffects pipeline: runPipeline (timers + threshold responses)
70
+ * @sideEffects audit: emitAudit for unauthorized, error, schedule.execute,
71
+ * threshold.breached events
72
+ * @calledBy External cron scheduler (AWS EventBridge / Google Cloud Scheduler)
73
+ */
74
+ export const handler = createHandler(async (ctx: RequestContext) => {
75
+ // ============================================
76
+ // SECURITY: Validate this is an internal request
77
+ // ============================================
78
+
79
+ // Check if request has scheduler authentication
80
+ const requestApiKey = ctx.query?.api_key || ctx.query?.scheduler_key
81
+
82
+ if (requestApiKey !== SCHEDULER_API_KEY) {
83
+ // Also allow if the principal is a system machine (for internal invocations)
84
+ if (ctx.principal?.type !== 'machine' || ctx.principal?.machineType !== 'internal') {
85
+ await emitAudit(ctx, 'system_cron.unauthorized_access', {
86
+ type: 'system',
87
+ account_id: ctx.accountId || undefined
88
+ }, { result: 'denied', error: 'Invalid or missing scheduler authentication' })
89
+
90
+ return {
91
+ statusCode: 403,
92
+ body: JSON.stringify({ error: 'Forbidden - Invalid scheduler authentication' })
93
+ }
94
+ }
95
+ }
96
+
97
+ // ============================================
98
+ // Find and execute due schedules
99
+ // ============================================
100
+
101
+ const results: Array<{
102
+ scheduleId: string
103
+ actionId: string
104
+ status: 'success' | 'failed' | 'skipped'
105
+ error?: string
106
+ durationMs: number
107
+ }> = []
108
+
109
+ try {
110
+ // Get all schedules due for execution
111
+ const { data: dueSchedules, error: schedulesError } = await adminDb.rpc('get_due_schedules', {
112
+ p_now: new Date().toISOString()
113
+ })
114
+
115
+ if (schedulesError) {
116
+ throw new Error(`Failed to fetch due schedules: ${schedulesError.message}`)
117
+ }
118
+
119
+ if (!dueSchedules || dueSchedules.length === 0) {
120
+ return {
121
+ statusCode: 200,
122
+ body: JSON.stringify({
123
+ message: 'No schedules due for execution',
124
+ executed: 0,
125
+ results: []
126
+ })
127
+ }
128
+ }
129
+
130
+ // Execute each due schedule
131
+ for (const schedule of dueSchedules) {
132
+ const startTime = Date.now()
133
+
134
+ try {
135
+ // Validate schedule can run (creator still active)
136
+ const { data: validation, error: validationError } = await adminDb.rpc('validate_schedule_creator', {
137
+ p_schedule_id: schedule.schedule_id
138
+ })
139
+
140
+ if (validationError || !validation?.is_valid) {
141
+ // Schedule was auto-paused by validation function
142
+ results.push({
143
+ scheduleId: schedule.schedule_id,
144
+ actionId: schedule.action_id,
145
+ status: 'skipped',
146
+ error: validation?.error_message || 'Schedule validation failed',
147
+ durationMs: Date.now() - startTime
148
+ })
149
+ continue
150
+ }
151
+
152
+ // Load the action
153
+ const { data: action, error: actionError } = await adminDb
154
+ .from('actions')
155
+ .select('*')
156
+ .eq('id', schedule.action_id)
157
+ .single()
158
+
159
+ if (actionError || !action) {
160
+ throw new Error(`Action not found: ${schedule.action_id}`)
161
+ }
162
+
163
+ // Load the machine principal
164
+ const { data: machine, error: machineError } = await adminDb
165
+ .from('api_keys')
166
+ .select('*')
167
+ .eq('id', schedule.machine_principal_id)
168
+ .single()
169
+
170
+ if (machineError || !machine) {
171
+ throw new Error(`Machine principal not found: ${schedule.machine_principal_id}`)
172
+ }
173
+
174
+ // Create execution context with machine principal
175
+ const executionCtx: RequestContext = {
176
+ requestId: ctx.requestId,
177
+ principal: {
178
+ id: machine.id,
179
+ type: 'machine',
180
+ accountId: machine.account_id,
181
+ scopes: schedule.delegated_scopes || machine.scopes || [],
182
+ machineType: machine.machine_type,
183
+ isInternal: machine.is_internal,
184
+ provenance: {
185
+ sourceType: 'cron',
186
+ createdBy: machine.created_by,
187
+ invokedAt: new Date().toISOString(),
188
+ cronId: schedule.schedule_id
189
+ }
190
+ },
191
+ db: adminDb, // Machines use adminDb (RLS checks their ID)
192
+ accountId: machine.account_id,
193
+ appId: null,
194
+ requestPath: '/.netlify/functions/system-cron',
195
+ query: {}
196
+ }
197
+
198
+ // Check machine has required scope for this action
199
+ const requiredScope = action.required_scopes?.[0] || `${action.handler}:execute`
200
+ const hasScope = executionCtx.principal.scopes?.includes(requiredScope) ||
201
+ executionCtx.principal.scopes?.includes('*:*')
202
+
203
+ if (!hasScope) {
204
+ throw new Error(`Machine lacks required scope: ${requiredScope}`)
205
+ }
206
+
207
+ // Execute the action
208
+ const executionResult = await executeAction(executionCtx, action, schedule.config)
209
+
210
+ // Record execution success
211
+ await adminDb.from('schedule_executions').insert({
212
+ schedule_id: schedule.schedule_id,
213
+ account_id: schedule.account_id,
214
+ machine_principal_id: machine.id,
215
+ status: 'success',
216
+ input_params: schedule.config,
217
+ output_result: executionResult,
218
+ duration_ms: Date.now() - startTime
219
+ })
220
+
221
+ // Update schedule state
222
+ await adminDb.rpc('update_schedule_after_run', {
223
+ p_schedule_id: schedule.schedule_id,
224
+ p_success: true,
225
+ p_error_message: null
226
+ })
227
+
228
+ // Emit audit log
229
+ await emitAudit(executionCtx, 'schedule.execute', {
230
+ type: 'schedule',
231
+ id: schedule.schedule_id,
232
+ account_id: schedule.account_id
233
+ }, {
234
+ action_id: action.id,
235
+ action_handler: action.handler,
236
+ result: 'success'
237
+ })
238
+
239
+ results.push({
240
+ scheduleId: schedule.schedule_id,
241
+ actionId: schedule.action_id,
242
+ status: 'success',
243
+ durationMs: Date.now() - startTime
244
+ })
245
+
246
+ } catch (execError: any) {
247
+ const errorMessage = execError.message || 'Execution failed'
248
+
249
+ // Record execution failure
250
+ await adminDb.from('schedule_executions').insert({
251
+ schedule_id: schedule.schedule_id,
252
+ account_id: schedule.account_id,
253
+ machine_principal_id: schedule.machine_principal_id,
254
+ status: 'failed',
255
+ input_params: schedule.config,
256
+ error_message: errorMessage,
257
+ duration_ms: Date.now() - startTime
258
+ })
259
+
260
+ // Update schedule state
261
+ await adminDb.rpc('update_schedule_after_run', {
262
+ p_schedule_id: schedule.schedule_id,
263
+ p_success: false,
264
+ p_error_message: errorMessage
265
+ })
266
+
267
+ results.push({
268
+ scheduleId: schedule.schedule_id,
269
+ actionId: schedule.action_id,
270
+ status: 'failed',
271
+ error: errorMessage,
272
+ durationMs: Date.now() - startTime
273
+ })
274
+ }
275
+ }
276
+
277
+ // ============================================
278
+ // Execute due timers (pipeline-based timers)
279
+ // ============================================
280
+
281
+ try {
282
+ const { data: dueTimers, error: timersError } = await adminDb.rpc('get_due_timers', {
283
+ p_now: new Date().toISOString()
284
+ })
285
+
286
+ if (!timersError && dueTimers && dueTimers.length > 0) {
287
+ for (const timer of dueTimers) {
288
+ const timerStartTime = Date.now()
289
+
290
+ try {
291
+ // Validate timer can run
292
+ if (!timer.pipeline_id) {
293
+ throw new Error(`Timer ${timer.timer_id} has no pipeline_id`)
294
+ }
295
+
296
+ // Create execution context for timer
297
+ const timerCtx: RequestContext = {
298
+ requestId: ctx.requestId,
299
+ principal: {
300
+ id: 'timer:' + timer.timer_id,
301
+ type: 'machine' as const,
302
+ accountId: timer.account_id,
303
+ scopes: ['pipelines:execute'],
304
+ machineType: 'timer',
305
+ isInternal: true,
306
+ provenance: {
307
+ sourceType: 'timer',
308
+ createdBy: timer.created_by,
309
+ invokedAt: new Date().toISOString(),
310
+ timerId: timer.timer_id
311
+ }
312
+ },
313
+ db: adminDb,
314
+ accountId: timer.account_id,
315
+ appId: timer.app_id || null,
316
+ requestPath: '/.netlify/functions/system-cron',
317
+ query: {}
318
+ }
319
+
320
+ // Run the pipeline
321
+ const result = await runPipeline(timer.pipeline_id, {
322
+ timer_id: timer.timer_id,
323
+ timer_name: timer.name,
324
+ execution_count: timer.execution_count || 0
325
+ }, timerCtx)
326
+
327
+ // Update timer state
328
+ await adminDb.rpc('update_timer_after_run', {
329
+ p_timer_id: timer.timer_id,
330
+ p_success: result.status === 'completed',
331
+ p_error_message: result.error || null
332
+ })
333
+
334
+ results.push({
335
+ scheduleId: timer.timer_id,
336
+ actionId: timer.pipeline_id,
337
+ status: result.status === 'completed' ? 'success' : 'failed',
338
+ error: result.error,
339
+ durationMs: Date.now() - timerStartTime
340
+ })
341
+
342
+ } catch (timerError: any) {
343
+ // Update timer with failure
344
+ await adminDb.rpc('update_timer_after_run', {
345
+ p_timer_id: timer.timer_id,
346
+ p_success: false,
347
+ p_error_message: timerError.message
348
+ })
349
+
350
+ results.push({
351
+ scheduleId: timer.timer_id,
352
+ actionId: timer.pipeline_id,
353
+ status: 'failed',
354
+ error: timerError.message,
355
+ durationMs: Date.now() - timerStartTime
356
+ })
357
+ }
358
+ }
359
+ }
360
+ } catch (timerLoopError) {
361
+ console.error('Timer execution error:', timerLoopError)
362
+ }
363
+
364
+ // ============================================
365
+ // Evaluate threshold alerts (every minute)
366
+ // ============================================
367
+ let thresholdResults: any[] = []
368
+ try {
369
+ thresholdResults = await evaluateThresholds(ctx)
370
+ const breachedCount = thresholdResults.filter(r => r.breached).length
371
+ const firedCount = thresholdResults.filter(r => r.fired).length
372
+
373
+ if (breachedCount > 0) {
374
+ console.log(`Thresholds: ${breachedCount} breached, ${firedCount} pipelines fired`)
375
+ }
376
+ } catch (thresholdError) {
377
+ console.error('Threshold evaluation error:', thresholdError)
378
+ }
379
+
380
+ // ============================================
381
+ // Daily log cleanup (check if it's time)
382
+ // ============================================
383
+ let logsCleaned = 0
384
+ const now = new Date()
385
+ const currentHour = now.getUTCHours()
386
+ const currentMinute = now.getUTCMinutes()
387
+
388
+ // Run cleanup once per day at 00:00 UTC
389
+ if (currentHour === 0 && currentMinute === 0) {
390
+ try {
391
+ logsCleaned = await cleanupOldLogs()
392
+ } catch (cleanupError) {
393
+ console.error('Log cleanup error:', cleanupError)
394
+ }
395
+ }
396
+
397
+ // Return summary
398
+ const successCount = results.filter(r => r.status === 'success').length
399
+ const failedCount = results.filter(r => r.status === 'failed').length
400
+ const skippedCount = results.filter(r => r.status === 'skipped').length
401
+
402
+ return {
403
+ statusCode: 200,
404
+ body: JSON.stringify({
405
+ message: `Executed ${results.length} total (schedules + timers)`,
406
+ executed: results.length,
407
+ success: successCount,
408
+ failed: failedCount,
409
+ skipped: skippedCount,
410
+ thresholds_evaluated: thresholdResults.length,
411
+ thresholds_breached: thresholdResults.filter(r => r.breached).length,
412
+ logs_cleaned: logsCleaned,
413
+ results
414
+ })
415
+ }
416
+
417
+ } catch (error: any) {
418
+ console.error('System cron error:', error)
419
+
420
+ await emitAudit(ctx, 'system_cron.error', {
421
+ type: 'system',
422
+ account_id: ctx.accountId || undefined
423
+ }, {
424
+ result: 'failure',
425
+ error: error.message
426
+ })
427
+
428
+ return {
429
+ statusCode: 500,
430
+ body: JSON.stringify({
431
+ error: 'System cron execution failed',
432
+ message: error.message
433
+ })
434
+ }
435
+ }
436
+ })
437
+
438
+ // ─── PRIVATE HELPERS ───────────────────────────────────────────────────────────
439
+
440
+ /**
441
+ * Dispatches to the appropriate handler module (`functions` | `integrations`
442
+ * | `custom`) based on `action.handler_module`. Merges action-level config
443
+ * with schedule-specific config before invoking.
444
+ *
445
+ * @throws Error('Unknown handler module: <module>') on unrecognized module
446
+ * @throws Error('Custom handlers not yet implemented: <name>')
447
+ */
448
+ async function executeAction(
449
+ ctx: RequestContext,
450
+ action: any,
451
+ config: any
452
+ ): Promise<any> {
453
+ const handlerModule = action.handler_module || 'functions'
454
+ const handlerName = action.handler
455
+
456
+ // Merge action config with schedule-specific config
457
+ const mergedConfig = {
458
+ ...action.config,
459
+ ...config
460
+ }
461
+
462
+ switch (handlerModule) {
463
+ case 'functions':
464
+ return await executeFunctionHandler(ctx, handlerName, mergedConfig)
465
+
466
+ case 'integrations':
467
+ return await executeIntegrationHandler(ctx, handlerName, mergedConfig)
468
+
469
+ case 'custom':
470
+ // Custom handlers would be loaded from v2-custom
471
+ throw new Error(`Custom handlers not yet implemented: ${handlerName}`)
472
+
473
+ default:
474
+ throw new Error(`Unknown handler module: ${handlerModule}`)
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Routes to one of the built-in named handlers: `send_email`,
480
+ * `generate_report`, `notify_watchers`, `run_pipeline`.
481
+ * `run_pipeline` delegates to `runPipeline` from `_shared/pipeline-runner.ts`.
482
+ *
483
+ * @throws Error('Unknown function handler: <name>') on unrecognized name
484
+ * @throws Error('pipeline_id is required for run_pipeline handler')
485
+ */
486
+ async function executeFunctionHandler(
487
+ ctx: RequestContext,
488
+ handlerName: string,
489
+ config: any
490
+ ): Promise<any> {
491
+ // Built-in handlers
492
+ const handlers: Record<string, Function> = {
493
+ 'send_email': async (ctx: RequestContext, config: any) => {
494
+ // Implementation would integrate with email service
495
+ console.log(`[${ctx.requestId}] Sending email:`, config)
496
+ return { sent: true, recipients: config.recipients }
497
+ },
498
+
499
+ 'generate_report': async (ctx: RequestContext, config: any) => {
500
+ // Implementation would generate and deliver report
501
+ console.log(`[${ctx.requestId}] Generating report:`, config)
502
+ return { generated: true, format: config.output_format }
503
+ },
504
+
505
+ 'notify_watchers': async (ctx: RequestContext, config: any) => {
506
+ // Implementation would notify item watchers
507
+ console.log(`[${ctx.requestId}] Notifying watchers:`, config)
508
+ return { notified: true }
509
+ },
510
+
511
+ 'run_pipeline': async (ctx: RequestContext, config: any) => {
512
+ // Execute a pipeline as part of scheduled action
513
+ const { pipeline_id, trigger_data = {} } = config
514
+
515
+ if (!pipeline_id) {
516
+ throw new Error('pipeline_id is required for run_pipeline handler')
517
+ }
518
+
519
+ const result = await runPipeline(pipeline_id, trigger_data, ctx)
520
+ return {
521
+ success: result.status === 'completed',
522
+ execution_id: result.executionId,
523
+ stages_completed: result.stages?.length || 0,
524
+ duration_ms: result.durationMs
525
+ }
526
+ }
527
+ }
528
+
529
+ const handler = handlers[handlerName]
530
+ if (!handler) {
531
+ throw new Error(`Unknown function handler: ${handlerName}`)
532
+ }
533
+
534
+ return await handler(ctx, config)
535
+ }
536
+
537
+ /**
538
+ * Placeholder for integration-based action handlers (external service calls).
539
+ * Currently logs and returns `{ executed: true }` for all handler names.
540
+ */
541
+ async function executeIntegrationHandler(
542
+ ctx: RequestContext,
543
+ handlerName: string,
544
+ config: any
545
+ ): Promise<any> {
546
+ // Integration handlers would call external services
547
+ // This is a placeholder for future implementation
548
+ console.log(`[${ctx.requestId}] Integration handler: ${handlerName}`, config)
549
+ return { executed: true, handler: handlerName }
550
+ }
551
+
552
+ /**
553
+ * Evaluates all active `threshold_alert` items across all accounts.
554
+ * For each threshold, queries the appropriate observability RPC
555
+ * (`get_error_rate`, `get_latency_percentiles`, or `get_pipeline_stats`)
556
+ * and fires the configured pipeline if the threshold is breached.
557
+ *
558
+ * Supported metrics: `error_rate`, `latency_p95`, `pipeline_failure_rate`
559
+ *
560
+ * @returns Array of `{ thresholdId, breached, fired }` results
561
+ * @sideEffects DB read: items (threshold_alert), observability RPCs
562
+ * @sideEffects pipeline: runPipeline when threshold breached and pipeline_id set
563
+ * @sideEffects audit: emitAudit('threshold.breached') for each breached threshold
564
+ * @calledBy handler (every tick)
565
+ */
566
+ async function evaluateThresholds(ctx: RequestContext): Promise<Array<{ thresholdId: string; breached: boolean; fired: boolean }>> {
567
+ const results: Array<{ thresholdId: string; breached: boolean; fired: boolean }> = []
568
+
569
+ try {
570
+ // Load all active threshold alerts from items table
571
+ const { data: thresholds, error } = await adminDb
572
+ .from('items')
573
+ .select('*')
574
+ .eq('type', 'threshold_alert')
575
+ .eq('data->>is_active', 'true')
576
+
577
+ if (error) {
578
+ console.error('Failed to load thresholds:', error)
579
+ return results
580
+ }
581
+
582
+ if (!thresholds || thresholds.length === 0) {
583
+ return results
584
+ }
585
+
586
+ // Evaluate each threshold
587
+ for (const threshold of thresholds) {
588
+ try {
589
+ const config = threshold.data || {}
590
+ const { metric, operator, value, window_minutes, pipeline_id } = config
591
+
592
+ if (!metric || !operator || value === undefined || !window_minutes) {
593
+ console.warn(`Threshold ${threshold.id} missing required fields`)
594
+ continue
595
+ }
596
+
597
+ // Calculate time window
598
+ const now = new Date()
599
+ const from = new Date(now.getTime() - window_minutes * 60 * 1000)
600
+
601
+ let breached = false
602
+ let actualValue: number = 0
603
+
604
+ // Query appropriate RPC based on metric
605
+ switch (metric) {
606
+ case 'error_rate': {
607
+ const { data } = await adminDb.rpc('get_error_rate', {
608
+ p_account_id: threshold.account_id,
609
+ p_from: from.toISOString(),
610
+ p_to: now.toISOString()
611
+ })
612
+ if (data && data.length > 0) {
613
+ actualValue = data[0].rate
614
+ breached = operator === 'gt' ? actualValue > value : actualValue < value
615
+ }
616
+ break
617
+ }
618
+
619
+ case 'latency_p95': {
620
+ const { data } = await adminDb.rpc('get_latency_percentiles', {
621
+ p_account_id: threshold.account_id,
622
+ p_from: from.toISOString(),
623
+ p_to: now.toISOString()
624
+ })
625
+ if (data && data.length > 0) {
626
+ actualValue = data[0].p95
627
+ breached = operator === 'gt' ? actualValue > value : actualValue < value
628
+ }
629
+ break
630
+ }
631
+
632
+ case 'pipeline_failure_rate': {
633
+ const { data } = await adminDb.rpc('get_pipeline_stats', {
634
+ p_account_id: threshold.account_id,
635
+ p_from: from.toISOString(),
636
+ p_to: now.toISOString()
637
+ })
638
+ if (data && data.length > 0) {
639
+ // Calculate overall failure rate across all pipelines
640
+ const totalSuccess = data.reduce((sum: number, p: any) => sum + (parseInt(p.success_count) || 0), 0)
641
+ const totalFailure = data.reduce((sum: number, p: any) => sum + (parseInt(p.failure_count) || 0), 0)
642
+ const total = totalSuccess + totalFailure
643
+ actualValue = total > 0 ? (totalFailure / total) * 100 : 0
644
+ breached = operator === 'gt' ? actualValue > value : actualValue < value
645
+ }
646
+ break
647
+ }
648
+
649
+ default:
650
+ console.warn(`Unknown metric: ${metric}`)
651
+ continue
652
+ }
653
+
654
+ // Fire pipeline if breached and pipeline_id configured
655
+ let fired = false
656
+ if (breached && pipeline_id) {
657
+ try {
658
+ await runPipeline(pipeline_id, {
659
+ threshold_id: threshold.id,
660
+ metric,
661
+ threshold_value: value,
662
+ actual_value: actualValue,
663
+ window_minutes,
664
+ triggered_at: now.toISOString()
665
+ }, ctx)
666
+ fired = true
667
+ } catch (pipelineError: any) {
668
+ console.error(`Failed to fire threshold pipeline ${pipeline_id}:`, pipelineError)
669
+ }
670
+ }
671
+
672
+ // Log threshold event if breached
673
+ if (breached) {
674
+ await emitAudit(ctx, 'threshold.breached', {
675
+ type: 'threshold_alert',
676
+ id: threshold.id,
677
+ account_id: threshold.account_id
678
+ }, {
679
+ metric,
680
+ operator,
681
+ threshold_value: value,
682
+ actual_value: actualValue,
683
+ window_minutes,
684
+ pipeline_fired: fired,
685
+ pipeline_id
686
+ })
687
+ }
688
+
689
+ results.push({
690
+ thresholdId: threshold.id,
691
+ breached,
692
+ fired
693
+ })
694
+
695
+ } catch (thresholdError: any) {
696
+ console.error(`Error evaluating threshold ${threshold.id}:`, thresholdError)
697
+ results.push({
698
+ thresholdId: threshold.id,
699
+ breached: false,
700
+ fired: false
701
+ })
702
+ }
703
+ }
704
+
705
+ return results
706
+
707
+ } catch (error: any) {
708
+ console.error('Threshold evaluation error:', error)
709
+ return results
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Runs the `cleanup_old_logs` RPC with 90-day retention. Called once daily
715
+ * by the handler at 00:00 UTC.
716
+ *
717
+ * @returns Number of log records deleted
718
+ * @sideEffects DB write: cleanup_old_logs RPC (cross-account DELETE)
719
+ * @calledBy handler (daily, 00:00 UTC)
720
+ */
721
+ async function cleanupOldLogs(): Promise<number> {
722
+ try {
723
+ const { data, error } = await adminDb.rpc('cleanup_old_logs', {
724
+ p_retention_days: 90
725
+ })
726
+
727
+ if (error) {
728
+ console.error('Log cleanup failed:', error)
729
+ return 0
730
+ }
731
+
732
+ const deletedCount = data?.[0]?.deleted_count || 0
733
+ if (deletedCount > 0) {
734
+ console.log(`Cleaned up ${deletedCount} old log records`)
735
+ }
736
+
737
+ return deletedCount
738
+ } catch (error: any) {
739
+ console.error('Log cleanup error:', error)
740
+ return 0
741
+ }
742
+ }