spine-framework 0.3.125 → 0.3.127
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.
- package/.framework/cli/commands/agents.ts +35 -37
- package/.framework/cli/commands/auth.ts +35 -34
- package/.framework/cli/commands/create-app.ts +104 -40
- package/.framework/cli/commands/dev.ts +104 -69
- package/.framework/cli/commands/doctor.ts +169 -97
- package/.framework/cli/commands/generate.ts +82 -161
- package/.framework/cli/commands/init.ts +72 -34
- package/.framework/cli/commands/install-app.ts +260 -125
- package/.framework/cli/commands/items.ts +33 -44
- package/.framework/cli/commands/migrate.ts +122 -40
- package/.framework/cli/commands/migrations.ts +33 -40
- package/.framework/cli/commands/pipelines.ts +31 -39
- package/.framework/cli/commands/status.ts +47 -23
- package/.framework/cli/commands/system.ts +47 -37
- package/.framework/cli/commands/test.ts +112 -68
- package/.framework/cli/commands/validate-app.ts +215 -121
- package/.framework/cli/context.ts +112 -130
- package/.framework/cli/env-loader.ts +47 -18
- package/.framework/cli/index.ts +81 -42
- package/.framework/docs/runtime-types.md +1024 -0
- package/.framework/functions/_shared/agent-runner.ts +1296 -961
- package/.framework/functions/_shared/app-manifest.ts +82 -68
- package/.framework/functions/_shared/audit.ts +135 -83
- package/.framework/functions/_shared/{cil-state.ts → cil.ts} +147 -54
- package/.framework/functions/_shared/db.ts +80 -126
- package/.framework/functions/_shared/hash-api-key.ts +63 -0
- package/.framework/functions/_shared/index.ts +222 -347
- package/.framework/functions/_shared/kb.ts +319 -0
- package/.framework/functions/_shared/llm.ts +92 -0
- package/.framework/functions/_shared/logging.ts +288 -0
- package/.framework/functions/_shared/middleware.ts +329 -365
- package/.framework/functions/_shared/outcomes.ts +36 -0
- package/.framework/functions/_shared/permissions.ts +359 -643
- package/.framework/functions/_shared/pipeline-runner.ts +243 -316
- package/.framework/functions/_shared/principal.ts +376 -468
- package/.framework/functions/_shared/resolve-credentials.ts +102 -0
- package/.framework/functions/_shared/resolve-ids.ts +162 -14
- package/.framework/functions/_shared/retry-deliveries.ts +104 -0
- package/.framework/functions/_shared/schema-utils.ts +611 -373
- package/.framework/functions/_shared/testing.ts +207 -137
- package/.framework/functions/_shared/tracing.ts +276 -0
- package/.framework/functions/_shared/trigger-engine.ts +159 -211
- package/.framework/functions/_shared/webhook-registration.ts +95 -81
- package/.framework/functions/_shared/webhook-registry.ts +153 -75
- package/.framework/functions/account-nodes.ts +31 -53
- package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
- package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
- package/.framework/functions/adapters/openai-outbound.ts +40 -0
- package/.framework/functions/admin-data.ts +687 -533
- package/.framework/functions/admin-funnel.ts +31 -20
- package/.framework/functions/admin-install-funnel.ts +47 -28
- package/.framework/functions/admin-signals.ts +47 -29
- package/.framework/functions/ai-agents.ts +122 -147
- package/.framework/functions/ai-interactions.ts +180 -90
- package/.framework/functions/api-keys.ts +250 -192
- package/.framework/functions/apps.ts +203 -197
- package/.framework/functions/auth.ts +98 -91
- package/.framework/functions/cil-agent-ingest.ts +773 -0
- package/.framework/functions/cil-agent.ts +592 -0
- package/.framework/functions/cil-callmap.ts +1227 -0
- package/.framework/functions/cil-config.ts +583 -0
- package/.framework/functions/cil-context.ts +445 -0
- package/.framework/functions/cil-doc.ts +1752 -0
- package/.framework/functions/cil-embeddings.ts +1202 -0
- package/.framework/functions/cil-graph.ts +585 -0
- package/.framework/functions/cil-ingest.ts +2416 -0
- package/.framework/functions/cil-pipeline.ts +318 -0
- package/.framework/functions/cil-project.ts +499 -0
- package/.framework/functions/cil-validate.ts +469 -0
- package/.framework/functions/debug-auth.ts +73 -53
- package/.framework/functions/deliveries.ts +117 -0
- package/.framework/functions/embeddings.ts +528 -516
- package/.framework/functions/eval-harness.ts +158 -106
- package/.framework/functions/integration-outbound.ts +311 -0
- package/.framework/functions/integration-routes.ts +240 -466
- package/.framework/functions/integrations.ts +132 -140
- package/.framework/functions/invites.ts +401 -201
- package/.framework/functions/item-progress.ts +113 -53
- package/.framework/functions/kb.ts +28 -0
- package/.framework/functions/logs.ts +239 -222
- package/.framework/functions/observability.ts +308 -259
- package/.framework/functions/pipeline-executions.ts +179 -235
- package/.framework/functions/pipelines.ts +163 -205
- package/.framework/functions/prompt-configs.ts +109 -156
- package/.framework/functions/roles.ts +97 -147
- package/.framework/functions/system-cron.ts +132 -218
- package/.framework/functions/system.ts +112 -52
- package/.framework/functions/tests.ts +32 -28
- package/.framework/functions/timers.ts +162 -210
- package/.framework/functions/triggers.ts +282 -346
- package/.framework/functions/types.ts +283 -400
- package/.framework/migrations/001_core_schema.sql +630 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
- package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
- package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
- package/.framework/migrations/005_core_observability.sql +598 -0
- package/.framework/migrations/006_security_hardening_rls.sql +97 -0
- package/.framework/migrations/007_kb_article_list.sql +237 -0
- package/.framework/migrations/008_items_target.sql +8 -0
- package/.framework/migrations/009_roles_slug_unique.sql +11 -0
- package/.framework/public/spine.config.json +91 -0
- package/.framework/scripts/app-install-cli.ts +112 -98
- package/.framework/src/App.tsx +67 -20
- package/.framework/src/apps/admin/index.tsx +97 -27
- package/.framework/src/components/AppWrapper.tsx +50 -11
- package/.framework/src/components/CustomAppLoader.tsx +131 -22
- package/.framework/src/components/admin/AdminListPage.tsx +75 -8
- package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
- package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
- package/.framework/src/components/admin/PageInstructions.tsx +49 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
- package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
- package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
- package/.framework/src/components/app-sidebar.tsx +50 -3
- package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
- package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
- package/.framework/src/components/kb/KBArticleList.tsx +557 -0
- package/.framework/src/components/layout/AppShell.tsx +54 -16
- package/.framework/src/components/layout/Header.tsx +50 -38
- package/.framework/src/components/layout/Layout.tsx +48 -32
- package/.framework/src/components/layout/Sidebar.tsx +167 -16
- package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
- package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
- package/.framework/src/components/runtime/DataFilters.tsx +78 -1
- package/.framework/src/components/runtime/DataHeader.tsx +32 -29
- package/.framework/src/components/runtime/DataListPage.tsx +161 -2
- package/.framework/src/components/runtime/DataStats.tsx +48 -1
- package/.framework/src/components/runtime/DataTable.tsx +153 -1
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
- package/.framework/src/components/runtime/index.ts +16 -9
- package/.framework/src/components/search-form.tsx +33 -1
- package/.framework/src/components/shared/AgentView.tsx +48 -51
- package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
- package/.framework/src/components/shared/InviteManager.tsx +113 -52
- package/.framework/src/components/shared/MemberList.tsx +182 -0
- package/.framework/src/components/shared/SchemaFields.tsx +96 -78
- package/.framework/src/components/ui/DataTable.tsx +86 -56
- package/.framework/src/components/ui/Form.tsx +188 -109
- package/.framework/src/components/ui/ItemCard.tsx +86 -58
- package/.framework/src/components/ui/ItemListView.tsx +88 -61
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
- package/.framework/src/components/ui/Modal.tsx +81 -4
- package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
- package/.framework/src/components/ui/accordion.tsx +97 -5
- package/.framework/src/components/ui/alert-dialog.tsx +225 -13
- package/.framework/src/components/ui/alert.tsx +113 -6
- package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
- package/.framework/src/components/ui/avatar.tsx +129 -7
- package/.framework/src/components/ui/badge.tsx +65 -3
- package/.framework/src/components/ui/breadcrumb.tsx +145 -8
- package/.framework/src/components/ui/button-group.tsx +81 -4
- package/.framework/src/components/ui/button.tsx +49 -2
- package/.framework/src/components/ui/calendar.tsx +65 -3
- package/.framework/src/components/ui/card.tsx +145 -8
- package/.framework/src/components/ui/carousel.tsx +129 -7
- package/.framework/src/components/ui/chart.tsx +161 -10
- package/.framework/src/components/ui/checkbox.tsx +49 -2
- package/.framework/src/components/ui/collapsible.tsx +81 -5
- package/.framework/src/components/ui/combobox.tsx +305 -20
- package/.framework/src/components/ui/command.tsx +177 -11
- package/.framework/src/components/ui/context-menu.tsx +273 -17
- package/.framework/src/components/ui/dialog.tsx +209 -27
- package/.framework/src/components/ui/direction.tsx +64 -4
- package/.framework/src/components/ui/drawer.tsx +193 -12
- package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
- package/.framework/src/components/ui/empty.tsx +145 -9
- package/.framework/src/components/ui/field.tsx +209 -14
- package/.framework/src/components/ui/hover-card.tsx +81 -4
- package/.framework/src/components/ui/input-group.tsx +162 -10
- package/.framework/src/components/ui/input-otp.tsx +98 -5
- package/.framework/src/components/ui/input.tsx +49 -2
- package/.framework/src/components/ui/item.tsx +225 -13
- package/.framework/src/components/ui/kbd.tsx +65 -3
- package/.framework/src/components/ui/label.tsx +49 -2
- package/.framework/src/components/ui/menubar.tsx +289 -17
- package/.framework/src/components/ui/native-select.tsx +97 -5
- package/.framework/src/components/ui/navigation-menu.tsx +177 -10
- package/.framework/src/components/ui/pagination.tsx +161 -9
- package/.framework/src/components/ui/popover.tsx +145 -8
- package/.framework/src/components/ui/progress.tsx +49 -2
- package/.framework/src/components/ui/radio-group.tsx +65 -3
- package/.framework/src/components/ui/resizable.tsx +82 -4
- package/.framework/src/components/ui/scroll-area.tsx +65 -3
- package/.framework/src/components/ui/select.tsx +193 -11
- package/.framework/src/components/ui/separator.tsx +49 -2
- package/.framework/src/components/ui/sheet.tsx +193 -11
- package/.framework/src/components/ui/sidebar.tsx +481 -38
- package/.framework/src/components/ui/skeleton.tsx +49 -2
- package/.framework/src/components/ui/slider.tsx +49 -2
- package/.framework/src/components/ui/sonner.tsx +49 -2
- package/.framework/src/components/ui/spinner.tsx +49 -2
- package/.framework/src/components/ui/switch.tsx +49 -2
- package/.framework/src/components/ui/table-primitives.tsx +160 -33
- package/.framework/src/components/ui/table.tsx +161 -9
- package/.framework/src/components/ui/tabs.tsx +113 -6
- package/.framework/src/components/ui/textarea.tsx +49 -2
- package/.framework/src/components/ui/toggle-group.tsx +81 -4
- package/.framework/src/components/ui/toggle.tsx +65 -3
- package/.framework/src/components/ui/tooltip.tsx +97 -5
- package/.framework/src/contexts/AppContext.tsx +129 -45
- package/.framework/src/contexts/AuthContext.tsx +173 -145
- package/.framework/src/hooks/use-mobile.ts +48 -2
- package/.framework/src/hooks/useApi.ts +208 -159
- package/.framework/src/hooks/useAppPath.ts +32 -23
- package/.framework/src/hooks/useApps.ts +51 -20
- package/.framework/src/hooks/useEntityList.ts +32 -78
- package/.framework/src/hooks/useEntityRecord.ts +35 -96
- package/.framework/src/hooks/useForm.ts +32 -92
- package/.framework/src/hooks/useListSchema.ts +49 -78
- package/.framework/src/hooks/useSchemaRecord.ts +114 -105
- package/.framework/src/lib/api.ts +192 -115
- package/.framework/src/lib/supabase.ts +98 -64
- package/.framework/src/lib/utils.ts +287 -151
- package/.framework/src/main.tsx +49 -3
- package/.framework/src/pages/DashboardPage.tsx +52 -29
- package/.framework/src/pages/NotFoundPage.tsx +32 -10
- package/.framework/src/pages/SetupPage.tsx +32 -12
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
- package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
- package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
- package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
- package/.framework/src/pages/admin/AppsPage.tsx +49 -2
- package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
- package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
- package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
- package/.framework/src/pages/admin/LogsPage.tsx +422 -75
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
- package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
- package/.framework/src/pages/admin/RolesPage.tsx +171 -18
- package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
- package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
- package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
- package/.framework/src/pages/admin/TimersPage.tsx +48 -20
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
- package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
- package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
- package/.framework/src/pages/admin/TypesPage.tsx +48 -20
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
- package/.framework/src/pages/auth/LoginPage.tsx +50 -23
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
- package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
- package/.framework/src/types/auth.ts +128 -71
- package/.framework/src/types/types.ts +373 -157
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
- package/docs/README.md +9 -130
- package/netlify.toml +3 -3
- package/package.json +13 -25
- package/scripts/kb-ingest-docs.ts +203 -0
- package/spine.config.json +65 -4
- package/.framework/migrations/001_schema.sql +0 -425
- package/.framework/migrations/002_rls_and_functions.sql +0 -763
- package/.framework/migrations/004_enhancements.sql +0 -43
- package/.framework/migrations/005_auth_user_trigger.sql +0 -105
- package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
- package/.framework/migrations/016_invites.sql +0 -27
- package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
- package/.framework/migrations/018_apps_required_roles.sql +0 -17
- package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
- package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
- package/.framework/migrations/021_app_domains.sql +0 -112
- package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
- package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
- package/.framework/migrations/024_messages_account_id.sql +0 -9
- package/.framework/migrations/025_retype_threads_messages.sql +0 -240
- package/.framework/migrations/026_fix_rls_policies.sql +0 -47
- package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
- package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
- package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
- package/.framework/migrations/030_messages_updated_by.sql +0 -6
- package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
- package/docs/APPS.md +0 -232
- package/docs/ARCHITECTURE.md +0 -246
- package/docs/INSTALL.md +0 -155
- package/docs/design-schema-spec.md +0 -1837
- package/docs/dev-guides/01-create-an-app.md +0 -183
- package/docs/dev-guides/02-create-a-function.md +0 -178
- package/docs/dev-guides/03-create-a-component.md +0 -178
- package/docs/dev-guides/04-webhook-handlers.md +0 -129
- package/docs/dev-guides/05-testing.md +0 -168
- package/docs/dev-guides/README.md +0 -40
- package/docs/npm-packages-plan.md +0 -288
- 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 {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
180
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
151
|
+
principal = await resolvePrincipal(event)
|
|
152
|
+
|
|
197
153
|
if (!principal || principal.id === 'anonymous') {
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
294
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
//
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
//
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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"]
|