spine-framework 0.3.124 → 0.3.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,189 +1,145 @@
1
- /**
2
- * @module middleware
3
- * @audience both
4
- * @layer shared-core
5
- * @stability stable
6
- *
7
- * HTTP handler factory and request context types for all Spine Netlify functions.
8
- * This module owns the boundary between raw HTTP events and the typed execution
9
- * context (`RequestContext`) used by every handler. It also provides guard
10
- * utilities (`requireUserContext`, `requireSystemContextWithAudit`) used to
11
- * enforce authentication at the top of handlers.
12
- *
13
- * The key invariant: `createHandler` always resolves a `Principal` via
14
- * `resolvePrincipal` before calling the inner handler. Handlers never receive
15
- * an unauthenticated context — anonymous requests are rejected at the wrapper.
16
- *
17
- * IMPORTANT: `result.data` is never unwrapped in `createHandler`. Handlers
18
- * return records directly. Unwrapping would collide with records that have a
19
- * `.data` JSONB column.
20
- *
21
- * @seeAlso principal.ts (resolvePrincipal, getPrincipalDb, isSystemAdmin)
22
- * @seeAlso audit.ts (emitAudit — called after every successful handler)
23
- * @seeAlso db.ts (adminDb, getUserDb — selected by getPrincipalDb)
24
- * @seeAlso index.ts (re-exports CoreContext, createHandler, requireUserContext)
25
- */
26
-
27
1
  import {
28
2
  Principal,
29
3
  resolvePrincipal,
30
4
  isSystemAdmin,
31
5
  getPrincipalDb
32
6
  } from './principal'
33
- import { emitAudit } from './audit'
34
-
35
- // ─── CONTEXT TYPES ───────────────────────────────────────────────────────────
36
-
37
- /**
38
- * Minimal execution context passed to all Spine core functions.
39
- *
40
- * This is the canonical context for `pipeline-runner`, `trigger-engine`,
41
- * `agent-runner`, `audit`, and any custom code in `v2-custom/`. It contains
42
- * only what core logic needs: identity, account scope, and a DB client.
43
- *
44
- * `RequestContext` (used inside HTTP handlers) extends this with HTTP-specific
45
- * fields (`appId`, `query`). Direct importers and CLI callers construct
46
- * `CoreContext` directly without going through `createHandler`.
47
- *
48
- * @inputSpec principal: Principal — must be a resolved principal (not null)
49
- * @inputSpec accountId: string | null — null is allowed for system-level ops only
50
- * @inputSpec db: SupabaseClient — use adminDb for system ops, getUserDb for RLS
51
- * @inputSpec requestId: string — UUID; ties execution to audit log entries
52
- * @calledBy pipeline-runner.ts, trigger-engine.ts, agent-runner.ts, audit.ts,
53
- * tests/integration/helpers.ts (makeTestCtx), cli/context.ts
54
- *
55
- * @example Import usage (v2-custom/)
56
- * ```ts
57
- * import { CoreContext, adminDb, SYSTEM_PRINCIPAL } from '../_shared/index'
58
- * const ctx: CoreContext = {
59
- * principal: SYSTEM_PRINCIPAL,
60
- * accountId: 'uuid-of-account',
61
- * db: adminDb,
62
- * requestId: crypto.randomUUID()
63
- * }
64
- * await runPipeline(pipelineId, data, ctx)
65
- * ```
66
- *
67
- * @example CLI usage
68
- * ```bash
69
- * # CLI constructs CoreContext from .xenv credentials automatically
70
- * spine pipelines run <pipeline-id>
71
- * ```
72
- */
7
+ import { adminDb } from './db'
8
+ import { emitAudit, emitSpan } from './audit'
9
+ import {
10
+ TraceContext,
11
+ Span,
12
+ startSpan,
13
+ endSpan,
14
+ parseTraceParent,
15
+ parseTraceState,
16
+ formatTraceParent,
17
+ formatTraceState,
18
+ generateSpanId,
19
+ isSampled,
20
+ traceDb
21
+ } from './tracing'
22
+
23
+ // CIL[BEGIN chunk_id="middleware_moduleheader_1784066609" type="module_header" identity="module_header" parent="none"]
24
+ // CIL[DOC-MACRO purpose="Request middleware and context management"]
25
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide API request handling and authentication"]
26
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
27
+ // CIL[DOC-CONTRACT inputs="none" outputs="types and handlers" errors="none" side_effects="request processing"]
28
+ // CIL[DOC-MICRO logic_summary="Defines CoreContext, RequestContext, HandlerFunction and HandlerResult types" notable_decisions="[]"]
29
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
30
+ // CIL[DOC-STABILITY stability="stable"]
31
+ // CIL[DOC-AUDIENCE audience="developer"]
32
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
33
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
34
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
35
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
36
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
37
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
38
+
73
39
  export interface CoreContext {
74
- /** Resolved principal for this execution */
75
40
  principal: Principal
76
- /** Primary account scope — null for system-level operations */
77
41
  accountId: string | null
78
- /** Database client — use adminDb for system ops, getUserDb for RLS-scoped */
79
42
  db: any
80
- /** Unique ID for this execution (used in audit logs) */
81
43
  requestId: string
44
+ source?: string
45
+ logLevel?: string
46
+ trace?: TraceContext
82
47
  }
83
48
 
84
- /**
85
- * HTTP-layer execution context — extends `CoreContext` with request-specific fields.
86
- *
87
- * Constructed inside `createHandler` after principal resolution. Not used by
88
- * core logic directly — core functions accept `CoreContext`. The extra fields
89
- * are available to handlers that need them (e.g., `query.action`, `appId`).
90
- *
91
- * @inputSpec appId: string | null — from `x-app-id` header; null if absent
92
- * @inputSpec query: Record<string, string> — parsed queryStringParameters from event
93
- * @calledBy All 19 API handler functions (as the first argument)
94
- * @seeAlso CoreContext (parent interface)
95
- */
96
49
  export interface RequestContext extends CoreContext {
97
- /** App ID from `x-app-id` header — used for app-scoped operations */
98
50
  appId: string | null
99
- /** Parsed query string parameters from the Netlify event */
100
51
  query: Record<string, string>
101
- /** Request path from the Netlify event */
102
52
  requestPath: string
103
53
  }
104
54
 
105
- /**
106
- * Signature for all Spine HTTP handler functions.
107
- *
108
- * Every handler file exports a default that calls `createHandler(myHandler)`.
109
- * The `body` parameter is the parsed JSON body, or null for GET requests.
110
- *
111
- * @inputSpec ctx: RequestContext — fully resolved context; never null
112
- * @inputSpec body: any — parsed JSON body or null; undefined for GET requests
113
- * @outputSpec Promise<any> — return value is wrapped in `{ data: result }` by createHandler
114
- */
115
55
  export type HandlerFunction = (ctx: RequestContext, body?: any) => Promise<any>
116
56
 
117
- /**
118
- * Standard envelope shape returned by `createHandler` to the HTTP client.
119
- *
120
- * On success: `{ data: <handler result>, error: null, meta: { requestId, duration } }`
121
- * On error: `{ data: null, error: <message> }` with appropriate HTTP status code.
122
- *
123
- * @outputSpec data: any — handler return value, never unwrapped
124
- * @outputSpec error: string | undefined — error message; present only on failure
125
- * @outputSpec meta: object | undefined — requestId + duration on success
126
- */
127
57
  export interface HandlerResult {
128
58
  data?: any
129
59
  error?: string
130
60
  meta?: any
131
61
  }
132
62
 
133
- // ─── HANDLER FACTORY ─────────────────────────────────────────────────────────
134
-
135
- /**
136
- * Wraps a handler function with principal resolution, request parsing, audit
137
- * logging, and error handling. This is the entry point for every Netlify function.
138
- *
139
- * Execution flow:
140
- * 1. Detect nested calls (event already has requestId + principal) → pass through
141
- * 2. Generate `requestId`, parse headers, query params
142
- * 3. Call `resolvePrincipal(event)` → reject anonymous with 401
143
- * 4. Call `getPrincipalDb(principal)` → select correct DB client
144
- * 5. Build `RequestContext`, parse + merge body
145
- * 6. Call inner handler, measure duration
146
- * 7. Emit `request.<method>` audit log (account-scoped requests only)
147
- * 8. Return `json({ data: result, error: null, meta })` envelope
148
- * 9. On any thrown error → return `error(message, 500)`
149
- *
150
- * @param handler - The inner handler function implementing the route logic
151
- * @returns Netlify-compatible async function `(event, context) => Response`
152
- * @throws never — all errors are caught and returned as HTTP 500
153
- * @inputSpec handler: HandlerFunction — must return a Promise
154
- * @outputSpec Netlify Lambda response object with statusCode, headers, body
155
- * @sideEffects DB read: principal resolution (people, api_keys tables)
156
- * @sideEffects DB write: emitAudit to logs table (account-scoped requests only)
157
- * @calledBy Every function in functions/*.ts as the default export wrapper
158
- * @calls resolvePrincipal, getPrincipalDb, emitAudit, json, error
159
- * @testIntegration tests/integration/admin-data-accounts.test.ts
160
- *
161
- * @example API handler file pattern
162
- * ```ts
163
- * import { createHandler, RequestContext } from './_shared/middleware'
164
- * export const handler = createHandler(async (ctx: RequestContext, body) => {
165
- * const action = ctx.query.action || 'list'
166
- * if (action === 'list') return listItems(ctx)
167
- * throw new Error(`Unknown action: ${action}`)
168
- * })
169
- * ```
170
- */
63
+ function isValidUuid(value: any): value is string {
64
+ if (typeof value !== 'string') return false
65
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-9a-f][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
66
+ return uuidRegex.test(value)
67
+ }
68
+
69
+ function buildTraceContext(event: any, requestId: string): TraceContext {
70
+ const traceparentHeader =
71
+ event.headers?.['traceparent'] ||
72
+ event.headers?.['Traceparent'] ||
73
+ event.headers?.['TRACEPARENT']
74
+ const tracestateHeader =
75
+ event.headers?.['tracestate'] ||
76
+ event.headers?.['Tracestate'] ||
77
+ event.headers?.['TRACESTATE']
78
+
79
+ const parsed = parseTraceParent(traceparentHeader)
80
+ if (parsed) {
81
+ const traceState = parseTraceState(tracestateHeader)
82
+ return {
83
+ traceId: parsed.traceId,
84
+ spanId: generateSpanId(),
85
+ parentSpanId: parsed.spanId,
86
+ traceFlags: parsed.traceFlags,
87
+ traceState: formatTraceState(traceState),
88
+ sampled: isSampled(parsed.traceFlags)
89
+ }
90
+ }
91
+
92
+ return {
93
+ traceId: requestId.replace(/-/g, '').toLowerCase(),
94
+ spanId: generateSpanId(),
95
+ traceFlags: 1,
96
+ sampled: true
97
+ }
98
+ }
99
+
100
+ function traceResponseHeaders(trace?: TraceContext, requestId?: string): Record<string, string> {
101
+ const headers: Record<string, string> = {}
102
+ if (requestId) headers['X-Request-ID'] = requestId
103
+ if (trace) headers['traceparent'] = formatTraceParent(trace.traceId, trace.spanId, trace.traceFlags)
104
+ if (trace?.traceState) headers['tracestate'] = trace.traceState
105
+ return headers
106
+ }
107
+
108
+ function mergeHeaders(base: Record<string, string>, extra?: Record<string, string>): Record<string, string> {
109
+ return { ...base, ...extra }
110
+ }
111
+ // CIL[END chunk_id="middleware_moduleheader_1784066609"]
112
+ // CIL[BEGIN chunk_id="middleware_createhandler_1784066609" type="function" identity="createHandler" parent="none"]
113
+ // CIL[DOC-MACRO purpose="Create authenticated request handler"]
114
+ // CIL[DOC-RESPONSIBILITY responsibility="Wrap handlers with authentication and context"]
115
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[\".framework/functions/_shared/principal.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
116
+ // CIL[DOC-CONTRACT inputs="none" outputs="wrapped handler" errors="auth errors" side_effects="audit logging"]
117
+ // CIL[DOC-MICRO logic_summary="Wraps handler with authentication, context building, and audit logging" notable_decisions="[]"]
118
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
119
+ // CIL[DOC-STABILITY stability="stable"]
120
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
121
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
122
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
123
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
124
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
125
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
171
126
  export function createHandler(handler: HandlerFunction) {
172
127
  return async (event: any, context: any) => {
173
- // Detect nested call: if event is already a RequestContext with a principal,
174
- // skip event parsing and call the raw handler directly
175
128
  if (event && event.requestId && event.principal) {
176
129
  return handler(event, context)
177
130
  }
178
131
 
179
- const requestId = crypto.randomUUID()
180
- const startTime = Date.now()
181
-
132
+ const rawRequestId = event.headers?.['x-request-id'] || event.headers?.['X-Request-Id']
133
+ const requestId = isValidUuid(rawRequestId) ? rawRequestId : crypto.randomUUID()
134
+ const traceContext = buildTraceContext(event, requestId)
135
+ const span = startSpan({ trace: traceContext }, 'request')
136
+ let principal: Principal | null = null
137
+
182
138
  try {
183
- // Parse headers
184
139
  const appId = event.headers?.['x-app-id'] || event.headers?.['X-App-Id']
185
-
186
- // Parse query string parameters
140
+ const source = event.headers?.['x-source'] || event.headers?.['X-Source']
141
+ const logLevel = event.headers?.['x-log-level'] || event.headers?.['X-Log-Level']
142
+
187
143
  const queryParams: Record<string, string> = {}
188
144
  if (event.queryStringParameters) {
189
145
  Object.assign(queryParams, event.queryStringParameters)
@@ -192,16 +148,19 @@ export function createHandler(handler: HandlerFunction) {
192
148
  queryParams.method = event.httpMethod
193
149
  }
194
150
 
195
- const principal = await resolvePrincipal(event)
196
-
151
+ principal = await resolvePrincipal(event)
152
+
197
153
  if (!principal || principal.id === 'anonymous') {
198
- return error('Authentication required', 401)
154
+ endSpan(span, { status: 'error', attributes: { error: 'Authentication required' } })
155
+ await emitSpan({ db: adminDb, principal: principal as any, accountId: null, requestId, trace: traceContext } as any, span, {
156
+ action: 'request.unauthenticated',
157
+ target: { type: 'request', id: requestId }
158
+ })
159
+ return error('Authentication required', 401, requestId, traceContext)
199
160
  }
200
-
201
- // Get RLS-scoped database client based on principal type
161
+
202
162
  const ctxDb = getPrincipalDb(principal)
203
-
204
- // Build request context
163
+
205
164
  const ctx: RequestContext = {
206
165
  requestId,
207
166
  principal,
@@ -210,15 +169,21 @@ export function createHandler(handler: HandlerFunction) {
210
169
  appId: appId || null,
211
170
  query: queryParams,
212
171
  requestPath: event.path || '/',
172
+ source,
173
+ logLevel,
174
+ trace: traceContext
213
175
  }
214
-
215
- // Parse body
176
+
177
+ ctx.db = traceDb(ctx, ctxDb, emitSpan)
178
+
216
179
  let body = null
217
180
  if (event.body) {
218
181
  try {
219
182
  body = JSON.parse(event.body)
220
183
  } catch (e) {
221
- return error('Invalid JSON in request body', 400)
184
+ endSpan(span, { status: 'error', attributes: { error: 'Invalid JSON in request body' } })
185
+ await emitSpan(ctx, span, { action: 'request.parse_error', target: { type: 'request', id: requestId, account_id: ctx.accountId } })
186
+ return error('Invalid JSON in request body', 400, requestId, ctx.trace)
222
187
  }
223
188
  }
224
189
  if (ctx.query.id) {
@@ -229,75 +194,86 @@ export function createHandler(handler: HandlerFunction) {
229
194
  body.id = ctx.query.id
230
195
  }
231
196
  }
232
-
233
- // Capture request details for audit
197
+
234
198
  const httpMethod = event.httpMethod || queryParams.method || 'GET'
235
199
  const requestPath = event.path || '/'
236
-
237
- // Execute handler
200
+
238
201
  const result = await handler(ctx, body)
239
- const durationMs = Date.now() - startTime
240
-
241
- // Emit audit log for successful request
242
- if (ctx.accountId) {
243
- await emitAudit(ctx, `request.${httpMethod.toLowerCase()}`, {
244
- type: 'request',
245
- id: requestId,
246
- account_id: ctx.accountId
247
- }, {
202
+ endSpan(span, { status: 'ok' })
203
+
204
+ await emitSpan(ctx, span, {
205
+ action: `request.${httpMethod.toLowerCase()}`,
206
+ target: { type: 'request', id: requestId, account_id: ctx.accountId },
207
+ metadata: {
248
208
  path: requestPath,
249
209
  method: httpMethod,
250
- duration_ms: durationMs,
251
210
  principal_type: principal.type,
252
211
  principal_id: principal.id,
253
212
  app_id: ctx.appId
254
- })
255
- }
256
-
257
- // Return success response.
258
- // Never unwrap result.data here — handlers return the record directly.
259
- // Using result.data would collide with records that have a .data JSONB column.
213
+ }
214
+ })
215
+
260
216
  return json({
261
217
  data: result,
262
218
  error: null,
263
219
  meta: {
264
220
  requestId,
265
- duration: durationMs
221
+ duration: span.durationMs
266
222
  }
267
- })
268
-
223
+ }, 200, requestId, ctx.trace)
224
+
269
225
  } catch (err: any) {
270
226
  console.error(`[${requestId}] Handler error:`, err)
271
227
  const status = typeof err.statusCode === 'number' ? err.statusCode : 500
272
- return error(err.message || 'Internal server error', status)
228
+ endSpan(span, { status: 'error', attributes: { error: err.message || 'Internal server error' } })
229
+ const fallbackCtx: CoreContext = {
230
+ db: adminDb,
231
+ principal: principal || ({} as Principal),
232
+ accountId: principal?.accountId || null,
233
+ requestId,
234
+ trace: traceContext
235
+ }
236
+ await emitSpan(fallbackCtx, span, {
237
+ action: 'request.error',
238
+ target: { type: 'request', id: requestId, account_id: fallbackCtx.accountId },
239
+ metadata: { status, error: err.message || 'Internal server error' }
240
+ })
241
+ return error(err.message || 'Internal server error', status, requestId, traceContext)
273
242
  }
274
243
  }
275
244
  }
276
-
277
- // ─── PUBLIC HANDLER FACTORY ────────────────────────────────────────────────────
278
-
279
- /**
280
- * Wraps a handler function without requiring authentication.
281
- * Used for public endpoints like check-first-user, check-domain, validate-token.
282
- *
283
- * Similar to createHandler but allows anonymous principals.
284
- */
245
+ // CIL[END chunk_id="middleware_createhandler_1784066609"]
246
+ // CIL[BEGIN chunk_id="middleware_createpublichandler_1784066609" type="function" identity="createPublicHandler" parent="none"]
247
+ // CIL[DOC-MACRO purpose="Create public request handler"]
248
+ // CIL[DOC-RESPONSIBILITY responsibility="Wrap handlers without authentication requirement"]
249
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\".framework/migrations/025_retype_threads_messages.sql\",\"\\\"]"]
250
+ // CIL[DOC-CONTRACT inputs="none" outputs="wrapped handler" errors="request errors" side_effects="context building"]
251
+ // CIL[DOC-MICRO logic_summary="Wraps handler with context building but no authentication" notable_decisions="[]"]
252
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
253
+ // CIL[DOC-STABILITY stability="stable"]
254
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/components/ui/dialog.tsx, .framework/migrations/025_retype_threads_messages.sql, \."]
255
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
256
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
257
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
258
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
259
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
285
260
  export function createPublicHandler(handler: HandlerFunction) {
286
261
  return async (event: any, context: any) => {
287
- // Detect nested call: if event is already a RequestContext with a principal,
288
- // skip event parsing and call the raw handler directly
289
262
  if (event && event.requestId && event.principal) {
290
263
  return handler(event, context)
291
264
  }
292
265
 
293
- const requestId = crypto.randomUUID()
294
- const startTime = Date.now()
295
-
266
+ const rawRequestId = event.headers?.['x-request-id'] || event.headers?.['X-Request-Id']
267
+ const requestId = isValidUuid(rawRequestId) ? rawRequestId : crypto.randomUUID()
268
+ const traceContext = buildTraceContext(event, requestId)
269
+ const span = startSpan({ trace: traceContext }, 'request')
270
+ let principal: Principal | null = null
271
+
296
272
  try {
297
- // Parse headers
298
273
  const appId = event.headers?.['x-app-id'] || event.headers?.['X-App-Id']
299
-
300
- // Parse query string parameters
274
+ const source = event.headers?.['x-source'] || event.headers?.['X-Source']
275
+ const logLevel = event.headers?.['x-log-level'] || event.headers?.['X-Log-Level']
276
+
301
277
  const queryParams: Record<string, string> = {}
302
278
  if (event.queryStringParameters) {
303
279
  Object.assign(queryParams, event.queryStringParameters)
@@ -306,14 +282,10 @@ export function createPublicHandler(handler: HandlerFunction) {
306
282
  queryParams.method = event.httpMethod
307
283
  }
308
284
 
309
- // Resolve principal - may be anonymous for public endpoints
310
- const principal = await resolvePrincipal(event)
311
-
312
- // Get RLS-scoped database client based on principal type
313
- // For anonymous users, this returns adminDb (service role)
285
+ principal = await resolvePrincipal(event)
286
+
314
287
  const ctxDb = getPrincipalDb(principal)
315
-
316
- // Build request context
288
+
317
289
  const ctx: RequestContext = {
318
290
  requestId,
319
291
  principal,
@@ -322,15 +294,21 @@ export function createPublicHandler(handler: HandlerFunction) {
322
294
  appId: appId || null,
323
295
  query: queryParams,
324
296
  requestPath: event.path || '/',
297
+ source,
298
+ logLevel,
299
+ trace: traceContext
325
300
  }
326
-
327
- // Parse body
301
+
302
+ ctx.db = traceDb(ctx, ctxDb, emitSpan)
303
+
328
304
  let body = null
329
305
  if (event.body) {
330
306
  try {
331
307
  body = JSON.parse(event.body)
332
308
  } catch (e) {
333
- return error('Invalid JSON in request body', 400)
309
+ endSpan(span, { status: 'error', attributes: { error: 'Invalid JSON in request body' } })
310
+ await emitSpan(ctx, span, { action: 'request.parse_error', target: { type: 'request', id: requestId, account_id: ctx.accountId } })
311
+ return error('Invalid JSON in request body', 400, requestId, ctx.trace)
334
312
  }
335
313
  }
336
314
  if (ctx.query.id) {
@@ -341,100 +319,122 @@ export function createPublicHandler(handler: HandlerFunction) {
341
319
  body.id = ctx.query.id
342
320
  }
343
321
  }
344
-
345
- // Execute handler
322
+
346
323
  const result = await handler(ctx, body)
347
- const durationMs = Date.now() - startTime
348
-
349
- // Return success response
324
+ endSpan(span, { status: 'ok' })
325
+
326
+ await emitSpan(ctx, span, {
327
+ action: 'request.public',
328
+ target: { type: 'request', id: requestId, account_id: ctx.accountId },
329
+ metadata: {
330
+ path: event.path || '/',
331
+ method: event.httpMethod || 'GET',
332
+ principal_type: principal?.type,
333
+ principal_id: principal?.id,
334
+ app_id: ctx.appId
335
+ }
336
+ })
337
+
350
338
  return json({
351
339
  data: result,
352
340
  error: null,
353
341
  meta: {
354
342
  requestId,
355
- duration: durationMs
343
+ duration: span.durationMs
356
344
  }
357
- })
358
-
345
+ }, 200, requestId, ctx.trace)
346
+
359
347
  } catch (err: any) {
360
348
  console.error(`[${requestId}] Public handler error:`, err)
361
349
  const status = typeof err.statusCode === 'number' ? err.statusCode : 500
362
- return error(err.message || 'Internal server error', status)
350
+ endSpan(span, { status: 'error', attributes: { error: err.message || 'Internal server error' } })
351
+ const fallbackCtx: CoreContext = {
352
+ db: adminDb,
353
+ principal: principal || ({} as Principal),
354
+ accountId: principal?.accountId || null,
355
+ requestId,
356
+ trace: traceContext
357
+ }
358
+ await emitSpan(fallbackCtx, span, {
359
+ action: 'request.error',
360
+ target: { type: 'request', id: requestId, account_id: fallbackCtx.accountId },
361
+ metadata: { status, error: err.message || 'Internal server error' }
362
+ })
363
+ return error(err.message || 'Internal server error', status, requestId, traceContext)
363
364
  }
364
365
  }
365
366
  }
367
+ // CIL[END chunk_id="middleware_createpublichandler_1784066609"]
368
+ // CIL[BEGIN chunk_id="middleware_json_1784066609" type="function" identity="json" parent="none"]
369
+ // CIL[DOC-MACRO purpose="Create JSON response"]
370
+ // CIL[DOC-RESPONSIBILITY responsibility="Format API responses as JSON"]
371
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
372
+ // CIL[DOC-CONTRACT inputs="none" outputs="HTTP response" errors="none" side_effects="none"]
373
+ // CIL[DOC-MICRO logic_summary="Creates HTTP response with JSON body and CORS headers" notable_decisions="[]"]
374
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
375
+ // CIL[DOC-STABILITY stability="stable"]
376
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
377
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
378
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
379
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
380
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
381
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
382
+ export function json(data: any, status: number = 200, requestId?: string, trace?: TraceContext) {
383
+ const headers: Record<string, string> = {
384
+ 'Content-Type': 'application/json',
385
+ 'Access-Control-Allow-Origin': '*',
386
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Account-Id, X-App-Id, X-Request-ID, X-Source, X-Log-Level, traceparent, tracestate',
387
+ 'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS'
388
+ }
389
+
390
+ if (requestId) headers['X-Request-ID'] = requestId
391
+ if (trace) {
392
+ headers['traceparent'] = formatTraceParent(trace.traceId, trace.spanId, trace.traceFlags)
393
+ if (trace.traceState) headers['tracestate'] = trace.traceState
394
+ }
366
395
 
367
- // ─── RESPONSE HELPERS ────────────────────────────────────────────────────────
368
-
369
- /**
370
- * Builds a JSON HTTP response object compatible with Netlify Functions.
371
- *
372
- * Always includes CORS headers permitting requests from any origin. Used
373
- * internally by `createHandler` and directly by handlers that need a custom
374
- * status code (e.g., 201 Created).
375
- *
376
- * @param data - Any JSON-serializable value to include as the response body
377
- * @param status - HTTP status code (default: 200)
378
- * @returns Netlify Lambda response object
379
- * @throws never
380
- * @inputSpec data: any — must be JSON-serializable; circular refs will throw at stringify
381
- * @inputSpec status: number — valid HTTP status code (default 200)
382
- * @outputSpec { statusCode, headers, body: string } — body is JSON.stringify(data)
383
- * @sideEffects none
384
- * @calledBy createHandler, error, cors, and directly by some handlers
385
- * @testUnit none — trivial; verified by integration tests on every request
386
- */
387
- export function json(data: any, status: number = 200) {
388
396
  return {
389
397
  statusCode: status,
390
- headers: {
391
- 'Content-Type': 'application/json',
392
- 'Access-Control-Allow-Origin': '*',
393
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Account-Id, X-App-Id',
394
- 'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS'
395
- },
398
+ headers,
396
399
  body: JSON.stringify(data)
397
400
  }
398
401
  }
399
-
400
- /**
401
- * Builds a JSON error response with `{ data: null, error: message }` shape.
402
- *
403
- * Use this to return structured error responses from handlers. The message
404
- * is safe to surface to clients — do not pass internal error details.
405
- *
406
- * @param message - Human-readable error message safe to return to client
407
- * @param status - HTTP status code (default: 400)
408
- * @returns Netlify Lambda response object
409
- * @throws never
410
- * @inputSpec message: string — client-safe error description
411
- * @inputSpec status: number — HTTP status code (400, 401, 403, 404, 500)
412
- * @outputSpec { statusCode: status, body: '{"data":null,"error":"<message>"}' }
413
- * @sideEffects none
414
- * @calledBy createHandler (on caught errors), requireUserContext,
415
- * requireSystemContextWithAudit, and many individual handlers
416
- */
417
- export function error(message: string, status: number = 400) {
402
+ // CIL[END chunk_id="middleware_json_1784066609"]
403
+ // CIL[BEGIN chunk_id="middleware_error_1784066609" type="function" identity="error" parent="none"]
404
+ // CIL[DOC-MACRO purpose="Create error response"]
405
+ // CIL[DOC-RESPONSIBILITY responsibility="Format API error responses"]
406
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/components/ui/dialog.tsx\"]"]
407
+ // CIL[DOC-CONTRACT inputs="none" outputs="HTTP error response" errors="none" side_effects="none"]
408
+ // CIL[DOC-MICRO logic_summary="Creates JSON error response with status code" notable_decisions="[]"]
409
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
410
+ // CIL[DOC-STABILITY stability="stable"]
411
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/components/ui/dialog.tsx."]
412
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
413
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
414
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
415
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
416
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
417
+ export function error(message: string, status: number = 400, requestId?: string, trace?: TraceContext) {
418
418
  return json({
419
419
  data: null,
420
420
  error: message
421
- }, status)
421
+ }, status, requestId, trace)
422
422
  }
423
-
424
- /**
425
- * Parses the JSON body from a Netlify event object.
426
- *
427
- * Returns `null` if there is no body. Throws a descriptive error on malformed
428
- * JSON so the error surfaces cleanly from `createHandler`'s catch block.
429
- *
430
- * @param event - Raw Netlify event object
431
- * @returns Parsed body object or null
432
- * @throws Error('Invalid JSON in request body') — when body is present but not valid JSON
433
- * @inputSpec event.body: string | null | undefined — raw JSON string from HTTP request
434
- * @outputSpec any — parsed JSON object, or null if no body
435
- * @sideEffects none
436
- * @calledBy Handlers that need body outside of createHandler's automatic parsing
437
- */
423
+ // CIL[END chunk_id="middleware_error_1784066609"]
424
+ // CIL[BEGIN chunk_id="middleware_parsebody_1784066609" type="function" identity="parseBody" parent="none"]
425
+ // CIL[DOC-MACRO purpose="Parse request body JSON"]
426
+ // CIL[DOC-RESPONSIBILITY responsibility="Extract and validate JSON from request"]
427
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[]"]
428
+ // CIL[DOC-CONTRACT inputs="none" outputs="parsed body" errors="invalid JSON" side_effects="none"]
429
+ // CIL[DOC-MICRO logic_summary="Parses JSON body or throws error for invalid JSON" notable_decisions="[]"]
430
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
431
+ // CIL[DOC-STABILITY stability="stable"]
432
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
433
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
434
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
435
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
436
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
437
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
438
438
  export function parseBody(event: any): any {
439
439
  if (!event.body) return null
440
440
 
@@ -444,43 +444,21 @@ export function parseBody(event: any): any {
444
444
  throw new Error('Invalid JSON in request body')
445
445
  }
446
446
  }
447
-
448
- // ─── AUTH GUARDS ─────────────────────────────────────────────────────────────
449
-
450
- /**
451
- * Overloaded guard that requires a resolved human principal with an account scope.
452
- *
453
- * **Overload 1 — wrapper:** wrap a handler to enforce auth before it runs.
454
- * **Overload 2 — inline:** call with `ctx` directly; returns an error response
455
- * object if auth is missing, or `null` if auth is present (allowing the
456
- * caller to do `const authErr = requireUserContext(ctx); if (authErr) return authErr`).
457
- *
458
- * Rejects requests where:
459
- * - `ctx.principal` is absent
460
- * - `ctx.principal.id === 'anonymous'`
461
- * - `ctx.accountId` is null or empty (machine principals without an account)
462
- *
463
- * @inputSpec ctx or handler: RequestContext or HandlerFunction
464
- * @outputSpec HandlerFunction (overload 1) or HandlerResult | null (overload 2)
465
- * @throws Error('User context required') — in wrapper mode if not authenticated
466
- * @sideEffects none
467
- * @calledBy API handlers that require an authenticated human with account scope
468
- * @testIntegration tests/integration/isolation.test.ts
469
- *
470
- * @example Inline guard pattern (preferred)
471
- * ```ts
472
- * const authErr = requireUserContext(ctx)
473
- * if (authErr) return authErr
474
- * // ctx.principal and ctx.accountId are guaranteed non-null below here
475
- * ```
476
- *
477
- * @example Wrapper pattern
478
- * ```ts
479
- * export const handler = createHandler(requireUserContext(async (ctx, body) => {
480
- * return listItems(ctx)
481
- * }))
482
- * ```
483
- */
447
+ // CIL[END chunk_id="middleware_parsebody_1784066609"]
448
+ // CIL[BEGIN chunk_id="middleware_requireusercontext_1784066609" type="function" identity="requireUserContext" parent="none"]
449
+ // CIL[DOC-MACRO purpose="Require authenticated user context"]
450
+ // CIL[DOC-RESPONSIBILITY responsibility="Enforce user authentication and account"]
451
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[]"]
452
+ // CIL[DOC-CONTRACT inputs="none" outputs="wrapped handler or error" errors="auth errors" side_effects="none"]
453
+ // CIL[DOC-MICRO logic_summary="Ensures principal is authenticated and has account context" notable_decisions="[]"]
454
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
455
+ // CIL[DOC-STABILITY stability="stable"]
456
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
457
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
458
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
459
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
460
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
461
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
484
462
  export function requireUserContext(handler: HandlerFunction): HandlerFunction
485
463
  export function requireUserContext(ctx: RequestContext): HandlerResult | null
486
464
  export function requireUserContext(arg: HandlerFunction | RequestContext): HandlerFunction | HandlerResult | null {
@@ -500,35 +478,21 @@ export function requireUserContext(arg: HandlerFunction | RequestContext): Handl
500
478
  }
501
479
  return null
502
480
  }
503
-
504
- /**
505
- * Overloaded guard that requires a `system_admin` principal.
506
- *
507
- * **Overload 1 — wrapper:** wrap a handler; throws if not system_admin.
508
- * **Overload 2 — inline:** call with `ctx`; returns error response or null.
509
- * Also accepts an optional `triggeredBy` string to set on the context for
510
- * audit trail chaining (e.g. pipeline execution ID).
511
- *
512
- * Rejects requests where:
513
- * - `ctx.principal` is absent or anonymous
514
- * - `ctx.principal` does not have the `system_admin` role
515
- *
516
- * @param arg - Handler to wrap, or RequestContext to validate inline
517
- * @param triggeredBy - Optional: ID of the triggering entity (set on ctx)
518
- * @returns HandlerFunction (overload 1) or HandlerResult | null (overload 2)
519
- * @throws Error('System context required') — in wrapper mode if not system_admin
520
- * @inputSpec ctx.principal.roles: string[] — must include 'system_admin'
521
- * @outputSpec HandlerFunction | HandlerResult | null
522
- * @sideEffects sets `ctx.triggeredBy` when validation passes (inline mode)
523
- * @calledBy system-cron.ts, pipeline-executions.ts, and admin-only handlers
524
- * @testIntegration tests/integration/isolation.test.ts
525
- *
526
- * @example Inline guard pattern
527
- * ```ts
528
- * const authErr = requireSystemContextWithAudit(ctx, 'cron-job-id')
529
- * if (authErr) return authErr
530
- * ```
531
- */
481
+ // CIL[END chunk_id="middleware_requireusercontext_1784066609"]
482
+ // CIL[BEGIN chunk_id="middleware_requiresystemcontextwithaudit_1784066609" type="function" identity="requireSystemContextWithAudit" parent="none"]
483
+ // CIL[DOC-MACRO purpose="Require system admin context with audit"]
484
+ // CIL[DOC-RESPONSIBILITY responsibility="Enforce system admin authentication"]
485
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[]"]
486
+ // CIL[DOC-CONTRACT inputs="none" outputs="wrapped handler or error" errors="auth errors" side_effects="audit tracking"]
487
+ // CIL[DOC-MICRO logic_summary="Ensures principal is system admin and tracks triggeredBy" notable_decisions="[]"]
488
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
489
+ // CIL[DOC-STABILITY stability="stable"]
490
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
491
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
492
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
493
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
494
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
495
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
532
496
  export function requireSystemContextWithAudit(handler: HandlerFunction): HandlerFunction
533
497
  export function requireSystemContextWithAudit(ctx: RequestContext, triggeredBy?: string): HandlerResult | null
534
498
  export function requireSystemContextWithAudit(
@@ -559,22 +523,22 @@ export function requireSystemContextWithAudit(
559
523
  ;(ctx as any).triggeredBy = triggeredBy || ctx.principal.id
560
524
  return null
561
525
  }
562
-
563
- // ─── UTILITY ─────────────────────────────────────────────────────────────────
564
-
565
- /**
566
- * Returns a 200 JSON response for CORS preflight requests.
567
- *
568
- * Netlify automatically handles OPTIONS at the CDN level for most routes, but
569
- * handlers that need to explicitly handle OPTIONS can call this.
570
- *
571
- * @returns json({ message: 'CORS enabled' }, 200) with CORS headers
572
- * @throws never
573
- * @inputSpec none
574
- * @outputSpec Netlify Lambda response with CORS headers
575
- * @sideEffects none
576
- * @calledBy Handler files that explicitly handle OPTIONS method
577
- */
526
+ // CIL[END chunk_id="middleware_requiresystemcontextwithaudit_1784066609"]
527
+ // CIL[BEGIN chunk_id="middleware_cors_1784066609" type="function" identity="cors" parent="none"]
528
+ // CIL[DOC-MACRO purpose="Handle CORS preflight requests"]
529
+ // CIL[DOC-RESPONSIBILITY responsibility="Return CORS-enabled response"]
530
+ // CIL[DOC-CONTEXT module="_shared/middleware" layer="infrastructure" related_files="[]"]
531
+ // CIL[DOC-CONTRACT inputs="none" outputs="CORS response" errors="none" side_effects="none"]
532
+ // CIL[DOC-MICRO logic_summary="Returns simple CORS response for preflight requests" notable_decisions="[]"]
533
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
534
+ // CIL[DOC-STABILITY stability="stable"]
535
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
536
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
537
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
538
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
539
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
540
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
578
541
  export function cors() {
579
542
  return json({ message: 'CORS enabled' }, 200)
580
543
  }
544
+ // CIL[END chunk_id="middleware_cors_1784066609"]