diff --git a/client/src/pages/app/AdminAeoFastLane.tsx b/client/src/pages/app/AdminAeoFastLane.tsx index 2e8d26d..970dc66 100644 --- a/client/src/pages/app/AdminAeoFastLane.tsx +++ b/client/src/pages/app/AdminAeoFastLane.tsx @@ -11,8 +11,8 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { trpc } from "@/lib/trpc"; -import { Award, RefreshCw, Zap } from "lucide-react"; -import { useState } from "react"; +import { Award, RefreshCw, Sparkles, Zap } from "lucide-react"; +import { useMemo, useState } from "react"; function QueryError({ message }: { message?: string }) { return

{message ?? "Failed to load."}

; @@ -20,11 +20,45 @@ function QueryError({ message }: { message?: string }) { export default function AdminAeoFastLane() { const [tab, setTab] = useState("exporters"); + const [showShadow, setShowShadow] = useState(false); const exporters = trpc.aeoFastLane.admin.accreditedExporters.useQuery({}); const queue = trpc.aeoFastLane.queue.prioritized.useQuery({}); const drawback = trpc.aeoFastLane.drawback.fastTrackQueue.useQuery({}); const origin = trpc.aeoFastLane.origin.fastPathQueue.useQuery({}); + // Phase 18: RL queue-policy SHADOW suggestion. Loaded only when the + // officer opts in via the toggle; never auto-applied to the queue. + const shadow = trpc.queuePolicy.suggestion.useQuery({}, { enabled: showShadow, retry: false }); + const recordDecision = trpc.queuePolicy.recordDecision.useMutation(); + const utils = trpc.useUtils(); + + // Map declaration id -> 1-based suggested position for badge lookup. + const suggestedPosition = useMemo(() => { + const map = new Map(); + shadow.data?.suggestedOrder.forEach((id, i) => map.set(id, i + 1)); + return map; + }, [shadow.data]); + + /** Honest untrained/unconfigured state is a first-class rendering. */ + const shadowRefusal = + shadow.error?.message?.includes("QUEUE_POLICY_NOT_TRAINED") ? "untrained" + : shadow.error?.message?.includes("QUEUE_POLICY_NOT_CONFIGURED") ? "not-configured" + : null; + + function logDecision(declarationId: number, authoritativePosition: number, decision: "accepted" | "overrode") { + if (!shadow.data) return; + recordDecision.mutate( + { + declarationId, + policyVersion: shadow.data.policyVersion, + suggestedPosition: suggestedPosition.get(declarationId) ?? authoritativePosition, + authoritativePosition, + decision, + }, + { onSuccess: () => utils.queuePolicy.suggestion.invalidate() } + ); + } + return (
@@ -78,30 +112,102 @@ export default function AdminAeoFastLane() { Prioritized export declaration queue AEO-certified exporters first (tier rank, then FIFO).
- +
+ + +
+ {showShadow && shadow.isLoading && } + {showShadow && shadowRefusal === "untrained" && ( +
+

Policy not trained

+

+ The ml-stack has no promoted queue policy yet (honest refusal). The authoritative + AEO/FIFO order below remains in force — no suggestion is shown or fabricated. +

+
+ )} + {showShadow && shadowRefusal === "not-configured" && ( +
+

Shadow policy not configured

+

+ This deployment has not enabled the RL shadow policy (ML_STACK_HTTP_URL / + RL_QUEUE_POLICY_SHADOW_ENABLED). The authoritative order below remains in force. +

+
+ )} + {showShadow && shadow.error && shadowRefusal === null && ( + + )} + {showShadow && shadow.data && ( +

+ Shadow policy {shadow.data.policyVersion} suggested an order for{" "} + {shadow.data.suggestedOrder.length} queued declarations + {shadow.data.opeScore != null ? ` · OPE score ${shadow.data.opeScore.toFixed(3)}` : ""}. + Advisory only — it is never applied automatically; accepting or overriding is logged. +

+ )} {queue.isLoading && } {queue.error && } {queue.data && queue.data.items.length === 0 && (

No export declarations in the queue.

)} - {queue.data?.items.map((d) => ( -
-
-

{d.declarationNumber}

-

- {d.traderName ?? `Trader #${d.traderId}`} · {d.hsCode ?? "—"} → {d.countryOfDestination ?? "—"} -

+ {queue.data?.items.map((d, idx) => { + const suggested = suggestedPosition.get(d.id); + const differs = suggested !== undefined && suggested !== idx + 1; + return ( +
+
+

{d.declarationNumber}

+

+ {d.traderName ?? `Trader #${d.traderId}`} · {d.hsCode ?? "—"} → {d.countryOfDestination ?? "—"} +

+
+
+ {showShadow && shadow.data && suggested !== undefined && ( + + suggested #{suggested}{differs ? ` (auth #${idx + 1})` : ""} + + )} + {showShadow && shadow.data && ( + <> + + + + )} + {d.fastLane && AEO fast-lane{d.aeoTier ? ` · ${d.aeoTier}` : ""}} + {d.status} +
-
- {d.fastLane && AEO fast-lane{d.aeoTier ? ` · ${d.aeoTier}` : ""}} - {d.status} -
-
- ))} + ); + })} diff --git a/drizzle/migrations/0070_phase18_queue_policy_decisions.sql b/drizzle/migrations/0070_phase18_queue_policy_decisions.sql new file mode 100644 index 0000000..22ffb07 --- /dev/null +++ b/drizzle/migrations/0070_phase18_queue_policy_decisions.sql @@ -0,0 +1,22 @@ +-- Phase 18 (W3): officer decisions on RL queue-policy SHADOW suggestions. +-- Appended to the chain; no history rewritten. Hand-written minimal delta +-- (snapshot chain only reaches 0048), following the 0069 precedent. +-- queue_policy_decisions is an append-only log for future offline-RL reward +-- joins: did the officer accept or override the shadow policy's suggested +-- position for a declaration, keyed by policy_version. +CREATE TABLE "queue_policy_decisions" ( + "id" serial PRIMARY KEY NOT NULL, + "officer_id" integer NOT NULL, + "declaration_id" integer NOT NULL, + "policy_version" varchar(64) NOT NULL, + "suggested_position" integer NOT NULL, + "authoritative_position" integer NOT NULL, + "decision" varchar(16) NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "queue_policy_decisions_decision_check" CHECK ("decision" IN ('accepted', 'overrode')) +); +--> statement-breakpoint +ALTER TABLE "queue_policy_decisions" ADD CONSTRAINT "queue_policy_decisions_officer_id_users_id_fk" FOREIGN KEY ("officer_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "queue_policy_decisions" ADD CONSTRAINT "queue_policy_decisions_declaration_id_declarations_id_fk" FOREIGN KEY ("declaration_id") REFERENCES "public"."declarations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_qpd_declaration" ON "queue_policy_decisions" USING btree ("declaration_id");--> statement-breakpoint +CREATE INDEX "idx_qpd_officer" ON "queue_policy_decisions" USING btree ("officer_id"); diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index 5c22551..9d82478 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -491,6 +491,13 @@ "when": 1788600000000, "tag": "0069_phase16_aeo_fastlane_transshipment", "breakpoints": true + }, + { + "idx": 70, + "version": "7", + "when": 1788700000000, + "tag": "0070_phase18_queue_policy_decisions", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 57f9785..e6ede4b 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -3976,3 +3976,28 @@ export const bondedTransfers = pgTable("bonded_transfers", { }, (t) => [index("idx_btr_link").on(t.transshipmentLinkId)]); export type BondedTransfer = typeof bondedTransfers.$inferSelect; export type InsertBondedTransfer = typeof bondedTransfers.$inferInsert; + +/** + * Phase 18: officer decisions on the RL queue-policy SHADOW suggestions. + * Append-only log for future offline-RL reward joins (accept/override of a + * suggested queue position vs the authoritative FIFO/AEO order). The + * suggestion is never auto-applied — this table records what the officer + * actually did, keyed by the policy version that made the suggestion. + */ +export const queuePolicyDecisions = pgTable("queue_policy_decisions", { + id: serial("id").primaryKey(), + officerId: integer("officer_id").notNull().references(() => users.id), + declarationId: integer("declaration_id").notNull().references(() => declarations.id), + policyVersion: varchar("policy_version", { length: 64 }).notNull(), + /** Position (1-based) the shadow policy suggested for this declaration. */ + suggestedPosition: integer("suggested_position").notNull(), + /** Position (1-based) in the authoritative FIFO/AEO queue at decision time. */ + authoritativePosition: integer("authoritative_position").notNull(), + decision: varchar("decision", { length: 16 }).notNull(), // 'accepted' | 'overrode' + createdAt: timestamp("created_at").defaultNow().notNull(), +}, (t) => [ + index("idx_qpd_declaration").on(t.declarationId), + index("idx_qpd_officer").on(t.officerId), +]); +export type QueuePolicyDecision = typeof queuePolicyDecisions.$inferSelect; +export type InsertQueuePolicyDecision = typeof queuePolicyDecisions.$inferInsert; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bf2888..b926490 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,6 +198,9 @@ importers: bcryptjs: specifier: ^3.0.3 version: 3.0.3 + cesium: + specifier: 1.117.0 + version: 1.117.0 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -273,6 +276,9 @@ importers: lucide-react: specifier: ^0.453.0 version: 0.453.0(react@19.2.1) + maplibre-gl: + specifier: 4.7.1 + version: 4.7.1 multer: specifier: ^2.2.0 version: 2.2.0 @@ -376,6 +382,12 @@ importers: '@tailwindcss/vite': specifier: ^4.3.3 version: 4.3.3(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) + '@testing-library/dom': + specifier: 10.4.1 + version: 10.4.1 + '@testing-library/react': + specifier: 16.3.3 + version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/cors': specifier: ^2.8.19 version: 2.8.19 @@ -427,6 +439,9 @@ importers: js-yaml: specifier: ^5.0.0 version: 5.0.0 + jsdom: + specifier: 26.1.0 + version: 26.1.0(supports-color@8.1.1) playwright: specifier: ^1.58.2 version: 1.58.2 @@ -454,9 +469,12 @@ importers: vite: specifier: ^8.0.16 version: 8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0) + vite-plugin-cesium: + specifier: 1.2.23 + version: 1.2.23(cesium@1.117.0)(rollup@4.63.1)(supports-color@8.1.1)(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) vitest: specifier: ^4.1.5 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(supports-color@8.1.1))(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) packages: @@ -466,6 +484,9 @@ packages: '@antfu/utils@9.3.0': resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@aws-sdk/checksums@3.1000.18': resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==} engines: {node: '>=20.0.0'} @@ -542,6 +563,10 @@ packages: resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -550,6 +575,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} @@ -570,9 +599,45 @@ packages: '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@cesium/engine@9.2.0': + resolution: {integrity: sha512-vFVITo7UwIY1H8zK53etljv1w0CVaFSzOcLyItrM88r47zyPL7/RgooVKqZXnM5V8prs33S5TO/JSqJmvpyMTQ==} + engines: {node: '>=14.0.0'} + + '@cesium/widgets@6.1.2': + resolution: {integrity: sha512-RNgogbRqlzjZxxG7icAUIDFZ0rm4OpHJ9b9lS/TGVm44PVVNG0Umhp4Tlf9U6r4ggwayKUkGV5dDIDa+pJGcVQ==} + engines: {node: '>=14.0.0'} + '@chevrotain/types@11.1.2': resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@date-fns/tz@1.5.0': resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} @@ -971,9 +1036,44 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@mapbox/geojson-rewind@0.5.2': + resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} + hasBin: true + + '@mapbox/jsonlint-lines-primitives@2.0.3': + resolution: {integrity: sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==} + engines: {node: '>= 22'} + + '@mapbox/point-geometry@0.1.0': + resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==} + + '@mapbox/tiny-sdf@2.2.0': + resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==} + + '@mapbox/unitbezier@0.0.1': + resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} + + '@mapbox/vector-tile@1.3.1': + resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==} + + '@mapbox/whoots-js@3.1.0': + resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} + engines: {node: '>=6.0.0'} + + '@maplibre/maplibre-gl-style-spec@20.4.0': + resolution: {integrity: sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==} + hasBin: true + '@mermaid-js/parser@1.2.1': resolution: {integrity: sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -2211,6 +2311,148 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/rollup-android-arm-eabi@4.63.1': + resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.63.1': + resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.63.1': + resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.63.1': + resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.63.1': + resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.63.1': + resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.63.1': + resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.63.1': + resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.63.1': + resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.63.1': + resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.63.1': + resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.63.1': + resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.63.1': + resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.63.1': + resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==} + cpu: [x64] + os: [win32] + '@shikijs/core@3.14.0': resolution: {integrity: sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==} @@ -2372,6 +2614,25 @@ packages: peerDependencies: react: ^18 || ^19 + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.3': + resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@trpc/client@11.18.0': resolution: {integrity: sha512-wOqeg3Fvl25V1ZisQhUD3K8G60ZJDlSGJNSyeXrLH24xAo5w6GSR2Kzb1cSNY9Y+IQ2YZvYGZstBU+V/ulo/ow==} hasBin: true @@ -2394,9 +2655,15 @@ packages: peerDependencies: typescript: '>=5.7.2' + '@tweenjs/tween.js@23.1.3': + resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/aws-lambda@8.10.162': resolution: {integrity: sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==} @@ -2524,6 +2791,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -2534,6 +2804,9 @@ packages: resolution: {integrity: sha512-tfmcyHn1Pp9YHAO5r40+UuZUPAZbUEgqTel3EuEKpmF9hPkXgR4l41853raliXnb4gwyPNoQOfvgGGlHN5WSog==} deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed. + '@types/geojson-vt@3.2.5': + resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -2556,6 +2829,12 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/mapbox__point-geometry@0.1.4': + resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} + + '@types/mapbox__vector-tile@1.3.4': + resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -2589,6 +2868,9 @@ packages: '@types/oracledb@6.5.2': resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} + '@types/pbf@3.0.5': + resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} + '@types/pdfkit@0.17.5': resolution: {integrity: sha512-T3ZHnvF91HsEco5ClhBCOuBwobZfPcI2jaiSHybkkKYq4KhVIIurod94JVKvDIG0JXT6o3KiERC0X0//m8dyrg==} @@ -2627,6 +2909,9 @@ packages: '@types/serve-static@1.15.9': resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==} + '@types/supercluster@7.1.3': + resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} + '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} @@ -2703,6 +2988,10 @@ packages: '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@zip.js/zip.js@2.13.1': + resolution: {integrity: sha512-D+iRyi2/uBCXPDQBhz2Z+Emcj2Cd8K/FG0y0l8WFeZJAZiP46m7cCajsGW3ktSq1HWov3nogZxyQ3FBisxOQSQ==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -2731,6 +3020,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} @@ -2753,6 +3046,9 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2766,10 +3062,18 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + attr-accept@2.2.5: resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} engines: {node: '>=4'} + autolinker@4.1.5: + resolution: {integrity: sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==} + engines: {pnpm: '>=10.10.0'} + autoprefixer@10.5.4: resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==} engines: {node: ^10 || ^12 || >=14} @@ -2826,6 +3130,9 @@ packages: bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + bitmap-sdf@1.0.4: + resolution: {integrity: sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -2897,6 +3204,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + cesium@1.117.0: + resolution: {integrity: sha512-gJ4J8oLgDTAYEGL69vK395MTjYDzxdLw4MqBXAXi9G2bhAp0U2DfxwuRqDcyW1SFdJVHj6ekcOBHszTMbmBOOA==} + engines: {node: '>=14.0.0'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -3060,6 +3371,10 @@ packages: cssfilter@0.0.10: resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -3223,6 +3538,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} @@ -3235,6 +3554,14 @@ packages: dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3251,6 +3578,9 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -3273,6 +3603,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -3289,6 +3623,9 @@ packages: dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -3299,6 +3636,9 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + drizzle-kit@0.31.9: resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} hasBin: true @@ -3402,6 +3742,12 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + earcut@2.2.4: + resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + + earcut@3.2.3: + resolution: {integrity: sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -3493,6 +3839,9 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -3619,6 +3968,10 @@ packages: react-dom: optional: true + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -3626,6 +3979,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3655,6 +4012,9 @@ packages: generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + geojson-vt@4.0.3: + resolution: {integrity: sha512-jR1MwkLaZGa8Zftct9ZFruyWFrdl9ZyD2OliXNy9Qq5bBPeg5wHVpBQF9p5GjnicSDQqvBVpysxTPKmWdsfWMA==} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -3671,13 +4031,24 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-tsconfig@4.12.0: resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + global-prefix@4.0.0: + resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==} + engines: {node: '>=16'} + globals@15.15.0: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} @@ -3693,6 +4064,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -3759,6 +4133,10 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3779,6 +4157,10 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -3819,6 +4201,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -3867,12 +4253,18 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} @@ -3880,6 +4272,10 @@ packages: isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -3913,13 +4309,32 @@ packages: resolution: {integrity: sha512-GSvaPUbk1U+FMZ7rJzF+F8e5YVtu7KnD40et/5rBXXRBv2jCO9L3qCewvIDDdudC0QycTFlf6EAA+h3kxBsuUw==} hasBin: true + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + json11@2.0.2: resolution: {integrity: sha512-HIrd50UPYmP6sqLuLbFVm75g16o0oZrVfxrsY0EEys22klz8mRoWlX9KAEDOSOR9Q34rcxsyC8oDveGrCz5uLQ==} hasBin: true + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -3935,12 +4350,22 @@ packages: resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true + kdbush@4.1.0: + resolution: {integrity: sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==} + khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + ktx-parse@0.7.1: + resolution: {integrity: sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==} + layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -3951,6 +4376,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + lerc@2.0.0: + resolution: {integrity: sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -4104,6 +4532,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -4122,6 +4553,13 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -4132,6 +4570,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + maplibre-gl@4.7.1: + resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==} + engines: {node: '>=16.14.0', npm: '>=8.1.0'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -4207,6 +4649,12 @@ packages: mermaid@11.17.1: resolution: {integrity: sha512-G1BP6qU4BRwEGk2SdsxgDk1cSuApimT32Nkb52YRSv+856FrmB8qo28BaNk9Ee8Fvuon3OxpXDJ4L6cD5AykXg==} + mersenne-twister@1.1.0: + resolution: {integrity: sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==} + + meshoptimizer@0.20.0: + resolution: {integrity: sha512-olcJ1q+YVnjroRJpCL1Dj5aZxr2JMr2hRutMUwhuHZvpAL7SIZgOT6eMlFF4TbBGSR89tawE/gqB79J/LrW/Nw==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -4310,6 +4758,11 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -4344,6 +4797,9 @@ packages: motion-utils@12.23.6: resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4351,6 +4807,9 @@ packages: resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} engines: {node: '>= 10.16.0'} + murmurhash-js@1.0.0: + resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} + mysql2@3.19.1: resolution: {integrity: sha512-yn4zh+Uxu5J3Zvi6Ao96lJ7BSBRkspHflWQAmOPND+htbpIKDQw99TTvPzgihKO/QyMickZopO4OsnixnpcUwA==} engines: {node: '>= 8.0'} @@ -4406,6 +4865,12 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + nosleep.js@0.12.0: + resolution: {integrity: sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==} + + nwsapi@2.2.27: + resolution: {integrity: sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4451,6 +4916,9 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + pako@2.2.0: + resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} @@ -4478,6 +4946,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pbf@3.3.0: + resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==} + hasBin: true + pdfkit@0.17.2: resolution: {integrity: sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw==} @@ -4591,11 +5063,18 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + potpack@2.1.0: + resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} + prettier@3.6.2: resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -4617,6 +5096,9 @@ packages: resolution: {integrity: sha512-uu52JNxLh3vsL7tXU/h0gDaywufvuUCTbGSi0NKQKBZ2ZopkmrWQJSQO/EFqzu/5YhiwgVM8rq/a/iVpx4eZ0g==} engines: {node: '>=12.0.0'} + protocol-buffers-schema@3.6.1: + resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4625,6 +5107,10 @@ packages: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} @@ -4637,6 +5123,12 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quickselect@2.0.0: + resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} + + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -4645,6 +5137,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + rbush@3.0.1: + resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} + react-day-picker@10.0.1: resolution: {integrity: sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==} engines: {node: '>=18'} @@ -4691,6 +5186,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -4836,6 +5334,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + restructure@3.0.2: resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} @@ -4856,6 +5357,16 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rollup-plugin-external-globals@0.6.1: + resolution: {integrity: sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==} + peerDependencies: + rollup: '>=4.59.0' + + rollup@4.63.1: + resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -4863,6 +5374,9 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} @@ -4882,6 +5396,10 @@ packages: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -4893,10 +5411,18 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} @@ -4953,6 +5479,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -5019,6 +5549,9 @@ packages: stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + supercluster@8.0.1: + resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + superjson@1.13.3: resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==} engines: {node: '>=10'} @@ -5031,6 +5564,9 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + systeminformation@5.33.6: resolution: {integrity: sha512-hMOQG/eRUzuopuYGGdl8ntkau0nEC7fOaRoTUg1RSr2GTQIk2VNa76DA0+ApajkGfzmcgAupgIP/vt+jtoe5EA==} engines: {node: '>=10.0.0'} @@ -5080,10 +5616,20 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + tmp@0.2.7: resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} @@ -5092,6 +5638,18 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -5176,6 +5734,10 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -5189,6 +5751,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -5247,6 +5812,12 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite-plugin-cesium@1.2.23: + resolution: {integrity: sha512-x9A8ZCEoegceXg/E+LnxKr0XBsI9CR4cgYWQ2Dd3cUEYwKcTnHQ3kBfpol7BUcGtgQnQos/mtVrRmuVQBXFjHw==} + peerDependencies: + cesium: ^1.95.0 + vite: '>=2.7.1' + vite@8.1.4: resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5335,6 +5906,13 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vt-pbf@3.1.3: + resolution: {integrity: sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -5342,9 +5920,31 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -5378,6 +5978,10 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -5437,6 +6041,14 @@ snapshots: '@antfu/utils@9.3.0': {} + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + '@aws-sdk/checksums@3.1000.18': dependencies: '@aws-sdk/core': 3.975.3 @@ -5611,10 +6223,18 @@ snapshots: '@aws/lambda-invoke-store@0.3.0': {} + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 @@ -5630,8 +6250,55 @@ snapshots: '@braintree/sanitize-url@7.1.2': {} + '@cesium/engine@9.2.0': + dependencies: + '@tweenjs/tween.js': 23.1.3 + '@zip.js/zip.js': 2.13.1 + autolinker: 4.1.5 + bitmap-sdf: 1.0.4 + dompurify: 3.4.14 + draco3d: 1.5.7 + earcut: 2.2.4 + grapheme-splitter: 1.0.4 + jsep: 1.4.0 + kdbush: 4.1.0 + ktx-parse: 0.7.1 + lerc: 2.0.0 + mersenne-twister: 1.1.0 + meshoptimizer: 0.20.0 + pako: 2.2.0 + protobufjs: 8.7.0 + rbush: 3.0.1 + topojson-client: 3.1.0 + urijs: 1.19.11 + + '@cesium/widgets@6.1.2': + dependencies: + '@cesium/engine': 9.2.0 + nosleep.js: 0.12.0 + '@chevrotain/types@11.1.2': {} + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + '@date-fns/tz@1.5.0': {} '@drizzle-team/brocli@0.10.2': {} @@ -5898,21 +6565,53 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@mapbox/geojson-rewind@0.5.2': + dependencies: + get-stream: 6.0.1 + minimist: 1.2.8 + + '@mapbox/jsonlint-lines-primitives@2.0.3': {} + + '@mapbox/point-geometry@0.1.0': {} - '@jridgewell/sourcemap-codec@1.5.5': {} + '@mapbox/tiny-sdf@2.2.0': {} - '@jridgewell/trace-mapping@0.3.31': + '@mapbox/unitbezier@0.0.1': {} + + '@mapbox/vector-tile@1.3.1': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@mapbox/point-geometry': 0.1.0 - '@js-sdsl/ordered-map@4.4.2': {} + '@mapbox/whoots-js@3.1.0': {} + + '@maplibre/maplibre-gl-style-spec@20.4.0': + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.3 + '@mapbox/unitbezier': 0.0.1 + json-stringify-pretty-compact: 4.0.0 + minimist: 1.2.8 + quickselect: 2.0.0 + rw: 1.3.3 + tinyqueue: 3.0.0 '@mermaid-js/parser@1.2.1': dependencies: '@chevrotain/types': 11.1.2 + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -7342,6 +8041,86 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 4.0.5 + + '@rollup/rollup-android-arm-eabi@4.63.1': + optional: true + + '@rollup/rollup-android-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-x64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.63.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.63.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.63.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.63.1': + optional: true + '@shikijs/core@3.14.0': dependencies: '@shikijs/types': 3.14.0 @@ -7496,6 +8275,27 @@ snapshots: '@tanstack/query-core': 5.101.2 react: 19.2.1 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.1 + '@types/react-dom': 19.2.1(@types/react@19.2.1) + '@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@trpc/server': 11.18.0(typescript@5.9.3) @@ -7513,11 +8313,15 @@ snapshots: dependencies: typescript: 5.9.3 + '@tweenjs/tween.js@23.1.3': {} + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/aws-lambda@8.10.162': {} '@types/bcryptjs@3.0.0': @@ -7675,6 +8479,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 24.7.0 @@ -7693,6 +8499,10 @@ snapshots: dependencies: form-data: 4.0.6 + '@types/geojson-vt@3.2.5': + dependencies: + '@types/geojson': 7946.0.16 + '@types/geojson@7946.0.16': {} '@types/google.maps@3.58.1': {} @@ -7711,6 +8521,14 @@ snapshots: '@types/katex@0.16.7': {} + '@types/mapbox__point-geometry@0.1.4': {} + + '@types/mapbox__vector-tile@1.3.4': + dependencies: + '@types/geojson': 7946.0.16 + '@types/mapbox__point-geometry': 0.1.4 + '@types/pbf': 3.0.5 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -7747,6 +8565,8 @@ snapshots: dependencies: '@types/node': 24.7.0 + '@types/pbf@3.0.5': {} + '@types/pdfkit@0.17.5': dependencies: '@types/node': 24.7.0 @@ -7798,6 +8618,10 @@ snapshots: '@types/node': 24.7.0 '@types/send': 0.17.5 + '@types/supercluster@7.1.3': + dependencies: + '@types/geojson': 7946.0.16 + '@types/tedious@4.0.14': dependencies: '@types/node': 24.7.0 @@ -7839,7 +8663,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(supports-color@8.1.1))(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) '@vitest/expect@4.1.5': dependencies: @@ -7882,6 +8706,8 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@zip.js/zip.js@2.13.1': {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -7905,6 +8731,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + append-field@1.0.0: {} archiver-utils@2.1.0: @@ -7949,6 +8777,10 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + assertion-error@2.0.1: {} ast-v8-to-istanbul@1.0.0: @@ -7961,8 +8793,14 @@ snapshots: asynckit@0.4.0: {} + at-least-node@1.0.0: {} + attr-accept@2.2.5: {} + autolinker@4.1.5: + dependencies: + tslib: 2.8.1 + autoprefixer@10.5.4(postcss@8.5.20): dependencies: browserslist: 4.28.6 @@ -8012,6 +8850,8 @@ snapshots: bintrees@1.0.2: {} + bitmap-sdf@1.0.4: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -8091,6 +8931,11 @@ snapshots: ccount@2.0.1: {} + cesium@1.117.0: + dependencies: + '@cesium/engine': 9.2.0 + '@cesium/widgets': 6.1.2 + chai@6.2.2: {} chainsaw@0.1.0: @@ -8237,6 +9082,11 @@ snapshots: cssfilter@0.0.10: {} + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): @@ -8425,6 +9275,11 @@ snapshots: data-uri-to-buffer@4.0.1: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + date-fns@4.1.0: {} date-fns@4.4.0: {} @@ -8433,6 +9288,12 @@ snapshots: dayjs@1.11.21: {} + debug@2.6.9(supports-color@8.1.1): + dependencies: + ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 @@ -8443,6 +9304,8 @@ snapshots: decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -8459,6 +9322,8 @@ snapshots: dequal@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -8471,6 +9336,8 @@ snapshots: dijkstrajs@1.0.3: {} + dom-accessibility-api@0.5.16: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.28.4 @@ -8482,6 +9349,8 @@ snapshots: dotenv@17.3.1: {} + draco3d@1.5.7: {} + drizzle-kit@0.31.9(supports-color@8.1.1): dependencies: '@drizzle-team/brocli': 0.10.2 @@ -8508,6 +9377,10 @@ snapshots: dependencies: readable-stream: 2.3.8 + earcut@2.2.4: {} + + earcut@3.2.3: {} + ee-first@1.1.1: {} electron-to-chromium@1.5.393: {} @@ -8633,6 +9506,8 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -8789,10 +9664,19 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + fresh@0.5.2: {} + fresh@2.0.0: {} fs-constants@1.0.0: {} + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + fsevents@2.3.2: optional: true @@ -8830,6 +9714,8 @@ snapshots: is-property: 1.0.2 optional: true + geojson-vt@4.0.3: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -8852,16 +9738,26 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-tsconfig@4.12.0: dependencies: resolve-pkg-maps: 1.0.0 + gl-matrix@3.4.4: {} + glob@13.0.6: dependencies: minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 + global-prefix@4.0.0: + dependencies: + ini: 4.1.3 + kind-of: 6.0.3 + which: 4.0.0 + globals@15.15.0: {} google-logging-utils@1.1.3: {} @@ -8870,6 +9766,8 @@ snapshots: graceful-fs@4.2.11: {} + grapheme-splitter@1.0.4: {} + hachure-fill@0.5.2: {} has-flag@4.0.0: {} @@ -9008,6 +9906,10 @@ snapshots: hpagent@1.2.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-escaper@2.0.2: {} html-parse-stringify@3.0.1: @@ -9034,6 +9936,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-proxy-agent@7.0.2(supports-color@8.1.1): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.1(supports-color@8.1.1): dependencies: agent-base: 6.0.2(supports-color@8.1.1) @@ -9078,6 +9987,8 @@ snapshots: inherits@2.0.4: {} + ini@4.1.3: {} + inline-style-parser@0.2.4: {} input-otp@1.4.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1): @@ -9122,15 +10033,23 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} is-property@1.0.2: optional: true + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.8 + is-what@4.1.16: {} isarray@1.0.0: {} + isexe@3.1.5: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -9158,12 +10077,49 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@26.1.0(supports-color@8.1.1): + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2(supports-color@8.1.1) + https-proxy-agent: 7.0.6(supports-color@8.1.1) + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.27 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.21.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsep@1.4.0: {} + json-bigint@1.0.0: dependencies: bignumber.js: 9.3.1 + json-stringify-pretty-compact@4.0.0: {} + json11@2.0.2: {} + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -9181,10 +10137,16 @@ snapshots: dependencies: commander: 8.3.0 + kdbush@4.1.0: {} + khroma@2.1.0: {} + kind-of@6.0.3: {} + kolorist@1.8.0: {} + ktx-parse@0.7.1: {} + layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -9193,6 +10155,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + lerc@2.0.0: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -9305,6 +10269,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@10.4.3: {} + lru-cache@11.5.2: {} lru.min@1.1.4: @@ -9318,6 +10284,12 @@ snapshots: dependencies: react: 19.2.1 + lz-string@1.5.0: {} + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -9332,6 +10304,35 @@ snapshots: dependencies: semver: 7.7.4 + maplibre-gl@4.7.1: + dependencies: + '@mapbox/geojson-rewind': 0.5.2 + '@mapbox/jsonlint-lines-primitives': 2.0.3 + '@mapbox/point-geometry': 0.1.0 + '@mapbox/tiny-sdf': 2.2.0 + '@mapbox/unitbezier': 0.0.1 + '@mapbox/vector-tile': 1.3.1 + '@mapbox/whoots-js': 3.1.0 + '@maplibre/maplibre-gl-style-spec': 20.4.0 + '@types/geojson': 7946.0.16 + '@types/geojson-vt': 3.2.5 + '@types/mapbox__point-geometry': 0.1.4 + '@types/mapbox__vector-tile': 1.3.4 + '@types/pbf': 3.0.5 + '@types/supercluster': 7.1.3 + earcut: 3.2.3 + geojson-vt: 4.0.3 + gl-matrix: 3.4.4 + global-prefix: 4.0.0 + kdbush: 4.1.0 + murmurhash-js: 1.0.0 + pbf: 3.3.0 + potpack: 2.1.0 + quickselect: 3.0.0 + supercluster: 8.0.1 + tinyqueue: 3.0.0 + vt-pbf: 3.1.3 + markdown-table@3.0.4: {} marked@16.4.1: {} @@ -9536,6 +10537,10 @@ snapshots: transitivePeerDependencies: - supports-color + mersenne-twister@1.1.0: {} + + meshoptimizer@0.20.0: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -9749,6 +10754,8 @@ snapshots: dependencies: mime-db: 1.54.0 + mime@1.6.0: {} + minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 @@ -9782,6 +10789,8 @@ snapshots: motion-utils@12.23.6: {} + ms@2.0.0: {} + ms@2.1.3: {} multer@2.2.0: @@ -9791,6 +10800,8 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 + murmurhash-js@1.0.0: {} + mysql2@3.19.1(@types/node@24.7.0): dependencies: '@types/node': 24.7.0 @@ -9836,6 +10847,10 @@ snapshots: normalize-path@3.0.0: {} + nosleep.js@0.12.0: {} + + nwsapi@2.2.27: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -9874,6 +10889,8 @@ snapshots: pako@1.0.11: {} + pako@2.2.0: {} + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -9903,6 +10920,11 @@ snapshots: pathe@2.0.3: {} + pbf@3.3.0: + dependencies: + ieee754: 1.2.1 + resolve-protobuf-schema: 2.1.0 + pdfkit@0.17.2: dependencies: crypto-js: 4.2.0 @@ -10014,8 +11036,16 @@ snapshots: dependencies: xtend: 4.0.2 + potpack@2.1.0: {} + prettier@3.6.2: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + process-nextick-args@2.0.1: {} prom-client@15.1.3: @@ -10037,6 +11067,8 @@ snapshots: dependencies: long: 5.3.2 + protocol-buffers-schema@3.6.1: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -10044,6 +11076,8 @@ snapshots: proxy-from-env@2.1.0: {} + punycode@2.3.1: {} + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 @@ -10057,6 +11091,10 @@ snapshots: quansync@0.2.11: {} + quickselect@2.0.0: {} + + quickselect@3.0.0: {} + range-parser@1.2.1: {} raw-body@3.0.2: @@ -10066,6 +11104,10 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + rbush@3.0.1: + dependencies: + quickselect: 2.0.0 + react-day-picker@10.0.1(@types/react@19.2.1)(react@19.2.1): dependencies: '@date-fns/tz': 1.5.0 @@ -10103,6 +11145,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} react-markdown@10.1.0(@types/react@19.2.1)(react@19.2.1)(supports-color@8.1.1): @@ -10314,6 +11358,10 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve-protobuf-schema@2.1.0: + dependencies: + protocol-buffers-schema: 3.6.1 + restructure@3.0.2: {} rimraf@2.7.1: @@ -10347,6 +11395,46 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 + rollup-plugin-external-globals@0.6.1(rollup@4.63.1): + dependencies: + '@rollup/pluginutils': 4.2.1 + estree-walker: 2.0.2 + is-reference: 1.2.1 + magic-string: 0.25.9 + rollup: 4.63.1 + + rollup@4.63.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.63.1 + '@rollup/rollup-android-arm64': 4.63.1 + '@rollup/rollup-darwin-arm64': 4.63.1 + '@rollup/rollup-darwin-x64': 4.63.1 + '@rollup/rollup-freebsd-arm64': 4.63.1 + '@rollup/rollup-freebsd-x64': 4.63.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.1 + '@rollup/rollup-linux-arm-musleabihf': 4.63.1 + '@rollup/rollup-linux-arm64-gnu': 4.63.1 + '@rollup/rollup-linux-arm64-musl': 4.63.1 + '@rollup/rollup-linux-loong64-gnu': 4.63.1 + '@rollup/rollup-linux-loong64-musl': 4.63.1 + '@rollup/rollup-linux-ppc64-gnu': 4.63.1 + '@rollup/rollup-linux-ppc64-musl': 4.63.1 + '@rollup/rollup-linux-riscv64-gnu': 4.63.1 + '@rollup/rollup-linux-riscv64-musl': 4.63.1 + '@rollup/rollup-linux-s390x-gnu': 4.63.1 + '@rollup/rollup-linux-x64-gnu': 4.63.1 + '@rollup/rollup-linux-x64-musl': 4.63.1 + '@rollup/rollup-openbsd-x64': 4.63.1 + '@rollup/rollup-openharmony-arm64': 4.63.1 + '@rollup/rollup-win32-arm64-msvc': 4.63.1 + '@rollup/rollup-win32-ia32-msvc': 4.63.1 + '@rollup/rollup-win32-x64-gnu': 4.63.1 + '@rollup/rollup-win32-x64-msvc': 4.63.1 + fsevents: 2.3.3 + roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -10364,6 +11452,8 @@ snapshots: transitivePeerDependencies: - supports-color + rrweb-cssom@0.8.0: {} + rw@1.3.3: {} rxjs@7.8.2: @@ -10380,12 +11470,34 @@ snapshots: dependencies: xmlchars: 2.2.0 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} secure-json-parse@2.7.0: {} semver@7.7.4: {} + send@0.19.2(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + send@1.2.1(supports-color@8.1.1): dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -10402,6 +11514,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@1.16.3(supports-color@8.1.1): + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + serve-static@2.2.1(supports-color@8.1.1): dependencies: encodeurl: 2.0.0 @@ -10474,6 +11595,8 @@ snapshots: source-map@0.6.1: {} + sourcemap-codec@1.4.8: {} + space-separated-tokens@2.0.2: {} split2@4.2.0: {} @@ -10548,6 +11671,10 @@ snapshots: stylis@4.3.6: {} + supercluster@8.0.1: + dependencies: + kdbush: 4.1.0 + superjson@1.13.3: dependencies: copy-anything: 3.0.5 @@ -10560,6 +11687,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + systeminformation@5.33.6: {} tailwind-merge@3.3.1: {} @@ -10602,12 +11731,32 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyqueue@3.0.0: {} + tinyrainbow@3.1.0: {} + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + tmp@0.2.7: {} toidentifier@1.0.1: {} + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + traverse@0.3.9: {} tree-kill@1.2.2: {} @@ -10707,6 +11856,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} + unpipe@1.0.0: {} unzipper@0.10.14: @@ -10728,6 +11879,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + urijs@1.19.11: {} + use-callback-ref@1.3.3(@types/react@19.2.1)(react@19.2.1): dependencies: react: 19.2.1 @@ -10796,6 +11949,17 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-plugin-cesium@1.2.23(cesium@1.117.0)(rollup@4.63.1)(supports-color@8.1.1)(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)): + dependencies: + cesium: 1.117.0 + fs-extra: 9.1.0 + rollup-plugin-external-globals: 0.6.1(rollup@4.63.1) + serve-static: 1.16.3(supports-color@8.1.1) + vite: 8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0) + transitivePeerDependencies: + - rollup + - supports-color + vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -10811,7 +11975,7 @@ snapshots: tsx: 4.20.6 yaml: 2.9.0 - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.7.0)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(supports-color@8.1.1))(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 '@vitest/mocker': 4.1.5(vite@8.1.4(@types/node@24.7.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.20.6)(yaml@2.9.0)) @@ -10837,17 +12001,45 @@ snapshots: '@opentelemetry/api': 1.9.1 '@types/node': 24.7.0 '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + jsdom: 26.1.0(supports-color@8.1.1) transitivePeerDependencies: - msw void-elements@3.1.0: {} + vt-pbf@3.1.3: + dependencies: + '@mapbox/point-geometry': 0.1.0 + '@mapbox/vector-tile': 1.3.1 + pbf: 3.3.0 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which-module@2.0.1: {} + which@4.0.0: + dependencies: + isexe: 3.1.5 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -10876,6 +12068,8 @@ snapshots: ws@8.21.0: {} + xml-name-validator@5.0.0: {} + xmlchars@2.2.0: {} xss@1.0.15: diff --git a/scripts/check-permify-coverage.mjs b/scripts/check-permify-coverage.mjs index 65b7f1b..72cc70b 100644 --- a/scripts/check-permify-coverage.mjs +++ b/scripts/check-permify-coverage.mjs @@ -121,6 +121,11 @@ const EXEMPT_ROUTERS = new Set([ // owner-scoped reads); trader-facing procedures manage only the caller's // own records (same posture as the exempt pcs router). "aeoFastLane", "transshipment", + // Phase 18: RL queue-policy shadow surface — officer roles enforced + // in-router via requireOfficer (OFFICER_QUEUE_ROLES); the suggestion is + // an advisory annotation over the declaration queue (no auto-reorder) and + // the decision log is caller-attributed, same posture as aeoFastLane. + "queuePolicy", ]); function parseSchema(src) { diff --git a/server/_core/env.ts b/server/_core/env.ts index ac29eda..d592916 100644 --- a/server/_core/env.ts +++ b/server/_core/env.ts @@ -565,6 +565,17 @@ export const ENV = { mswExchangeSender: process.env.MSW_EXCHANGE_SENDER ?? "", mswExchangePeerUrl: process.env.MSW_EXCHANGE_PEER_URL ?? "", mlStackHttpUrl: process.env.ML_STACK_HTTP_URL ?? "", + // Env-only service token for ml-stack calls (Keycloak JWKS audience); + // read live by the shipping-products + queue-policy clients. + mlStackServiceToken: process.env.ML_STACK_SERVICE_TOKEN ?? "", + // Phase 18: shadow RL queue-policy gate (both must be set for the + // queuePolicy suggestion surface to call ml-stack; fail-closed otherwise) + rlQueuePolicyShadowEnabled: process.env.RL_QUEUE_POLICY_SHADOW_ENABLED ?? "", + // PRA-068 sweep registration for the optional CSP extension origins read + // in _core/index.ts (comma-separated origin lists; empty = none). + cspScriptSrcExtra: process.env.CSP_SCRIPT_SRC_EXTRA ?? "", + cspConnectSrcExtra: process.env.CSP_CONNECT_SRC_EXTRA ?? "", + cspImgSrcExtra: process.env.CSP_IMG_SRC_EXTRA ?? "", riskScorerPipeline: process.env.RISK_SCORER_PIPELINE ?? "", schedulerSecret: process.env.SCHEDULER_SECRET ?? "", cvContainerConsumerEnabled: process.env.CV_CONTAINER_CONSUMER_ENABLED ?? "", diff --git a/server/executiveApi.test.ts b/server/executiveApi.test.ts index 4819e8a..e79d33b 100644 --- a/server/executiveApi.test.ts +++ b/server/executiveApi.test.ts @@ -35,6 +35,8 @@ describe("Mission C route registration", () => { "GET /v1/sla/breaches", "GET /v1/customs/summary", "GET /v1/briefings/weekly", + // Phase 18: RL queue-policy shadow status for the ministry portal + "GET /v1/rl/queue-policy/status", ]) { expect(paths, p).toContain(p); } diff --git a/server/phase18.queuePolicy.db.test.ts b/server/phase18.queuePolicy.db.test.ts new file mode 100644 index 0000000..5441a7b --- /dev/null +++ b/server/phase18.queuePolicy.db.test.ts @@ -0,0 +1,212 @@ +/** + * phase18.queuePolicy.db.test.ts — REAL DB-gated integration tests for + * Phase 18 W3: migration 0070 (queue_policy_decisions), the queuePolicy + * shadow router (suggestion against a stubbed ml-stack, officer RBAC, + * untrained honesty) and the append-only decision log. + * + * Skips cleanly with a printed reason when PostgreSQL is unavailable + * (pgTestHarness precedent) — never a fake pass. The ml-stack fetch is + * stubbed: these tests pin OUR shadow-mode handling of its contract. + */ +import { describe, it, expect, afterAll, afterEach, vi } from "vitest"; +import { createTestDatabase } from "./testutils/pgTestHarness"; +import type { TrpcContext } from "./_core/context"; +import { + declarations, + queuePolicyDecisions, + stakeholderProfiles, + users, +} from "../drizzle/schema"; +import { eq } from "drizzle-orm"; + +const tdb = await createTestDatabase("phase18q"); +if (tdb) process.env.DATABASE_URL = tdb.url; +const describeDb = tdb ? describe : describe.skip; + +const { closePool, getDb } = await import("./db"); +const { appRouter } = await import("./routers"); + +afterAll(async () => { + await closePool(); + await tdb?.close(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); + delete process.env.ML_STACK_HTTP_URL; + delete process.env.RL_QUEUE_POLICY_SHADOW_ENABLED; +}); + +let seq = 0; +async function seedUser(role: "user" | "customs_officer" = "user") { + const db = (await getDb())!; + seq += 1; + const [u] = await db + .insert(users) + .values({ openId: `phase18q-${Date.now()}-${seq}`, name: `P18 User ${seq}`, role }) + .returning(); + return u; +} + +async function seedDeclaration(traderId: number, suffix: string) { + const db = (await getDb())!; + const [d] = await db + .insert(declarations) + .values({ + declarationNumber: `EXP-P18-${Date.now()}-${suffix}`, + traderId, + declarationType: "export", + status: "submitted", + }) + .returning(); + return d; +} + +function makeCtx(user: { id: number; openId: string; role: string }): TrpcContext { + return { + user: { + ...user, + email: `u${user.id}@example.com`, + name: `U${user.id}`, + loginMethod: "keycloak", + createdAt: new Date(), + updatedAt: new Date(), + lastSignedIn: new Date(), + } as TrpcContext["user"], + keycloakRoles: [], + req: { protocol: "https", headers: {} } as TrpcContext["req"], + res: { clearCookie: () => {} } as unknown as TrpcContext["res"], + }; +} + +function stubMlStack(body: unknown, status = 200) { + vi.stubGlobal( + "fetch", + vi.fn(async () => + new Response(typeof body === "string" ? body : JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }) + ) + ); +} + +function enableShadow() { + process.env.ML_STACK_HTTP_URL = "http://ml-stack.test:8100"; + process.env.RL_QUEUE_POLICY_SHADOW_ENABLED = "true"; +} + +describeDb("Phase 18 queue-policy shadow router against real PostgreSQL", () => { + it("migration 0070 created queue_policy_decisions with the decision check", async () => { + const db = (await getDb())!; + const officer = await seedUser("customs_officer"); + const decl = await seedDeclaration(officer.id, "M"); + const [row] = await db + .insert(queuePolicyDecisions) + .values({ + officerId: officer.id, + declarationId: decl.id, + policyVersion: "v-test", + suggestedPosition: 1, + authoritativePosition: 1, + decision: "accepted", + }) + .returning(); + expect(row.id).toBeGreaterThan(0); + await expect( + db.insert(queuePolicyDecisions).values({ + officerId: officer.id, + declarationId: decl.id, + policyVersion: "v-test", + suggestedPosition: 1, + authoritativePosition: 1, + decision: "bogus", + }) + ).rejects.toThrow(/queue_policy_decisions_decision_check|Failed query/); + }); + + it("returns the shadow suggestion alongside the authoritative order", async () => { + const officer = await seedUser("customs_officer"); + const trader = await seedUser(); + const a = await seedDeclaration(trader.id, "A"); + const b = await seedDeclaration(trader.id, "B"); + enableShadow(); + stubMlStack({ + mode: "shadow", + policy_version: "queue-policy-v0.1.0", + suggested_order: [b.id, a.id], + ope_score: 0.71, + }); + const caller = appRouter.createCaller(makeCtx(officer)); + const res = await caller.queuePolicy.suggestion({}); + expect(res.mode).toBe("shadow"); + expect(res.policyVersion).toBe("queue-policy-v0.1.0"); + expect(res.opeScore).toBe(0.71); + expect(res.authoritativeOrder).toContain(a.id); + expect(res.authoritativeOrder).toContain(b.id); + expect(res.suggestedOrder).toEqual([b.id, a.id]); + // Authoritative order is unchanged by the suggestion. + expect(res.authoritativeOrder).not.toEqual(res.suggestedOrder); + }); + + it("honestly reports an untrained policy (ml-stack 409)", async () => { + const officer = await seedUser("customs_officer"); + const trader = await seedUser(); + await seedDeclaration(trader.id, "C"); + enableShadow(); + stubMlStack("POLICY_NOT_TRAINED: no promoted queue policy", 409); + const caller = appRouter.createCaller(makeCtx(officer)); + await expect(caller.queuePolicy.suggestion({})).rejects.toThrow(/QUEUE_POLICY_NOT_TRAINED/); + }); + + it("fails closed when the shadow surface is not configured", async () => { + const officer = await seedUser("customs_officer"); + const caller = appRouter.createCaller(makeCtx(officer)); + await expect(caller.queuePolicy.suggestion({})).rejects.toThrow(/QUEUE_POLICY_NOT_CONFIGURED/); + }); + + it("rejects non-officer callers on both procedures", async () => { + const trader = await seedUser(); + const caller = appRouter.createCaller(makeCtx(trader)); + await expect(caller.queuePolicy.suggestion({})).rejects.toThrow(/Officer role required/); + await expect( + caller.queuePolicy.recordDecision({ + declarationId: 1, + policyVersion: "v1", + suggestedPosition: 1, + authoritativePosition: 1, + decision: "accepted", + }) + ).rejects.toThrow(/Officer role required/); + }); + + it("logs accept/override decisions append-only for future reward joins", async () => { + const officer = await seedUser("customs_officer"); + const trader = await seedUser(); + const decl = await seedDeclaration(trader.id, "D"); + const caller = appRouter.createCaller(makeCtx(officer)); + const accepted = await caller.queuePolicy.recordDecision({ + declarationId: decl.id, + policyVersion: "queue-policy-v0.1.0", + suggestedPosition: 2, + authoritativePosition: 3, + decision: "accepted", + }); + const overrode = await caller.queuePolicy.recordDecision({ + declarationId: decl.id, + policyVersion: "queue-policy-v0.1.0", + suggestedPosition: 2, + authoritativePosition: 4, + decision: "overrode", + }); + expect(accepted.recorded).toBe(true); + expect(overrode.id).toBeGreaterThan(accepted.id); + const db = (await getDb())!; + const rows = await db + .select() + .from(queuePolicyDecisions) + .where(eq(queuePolicyDecisions.declarationId, decl.id)); + expect(rows.map((r) => r.decision).sort()).toEqual(["accepted", "overrode"]); + expect(rows.every((r) => r.officerId === officer.id)).toBe(true); + }); +}); diff --git a/server/rl/queuePolicy.test.ts b/server/rl/queuePolicy.test.ts new file mode 100644 index 0000000..d42b7b4 --- /dev/null +++ b/server/rl/queuePolicy.test.ts @@ -0,0 +1,173 @@ +/** + * queuePolicy.test.ts — Phase 18 RL queue-policy client contract tests. + * + * DB-free: the ml-stack fetch is stubbed (mock upstream fine here — these + * tests pin OUR fail-closed handling of its honest answers). Covers: + * config gating, untrained 409/503, shadow-mode enforcement, candidate-set + * validation, OPE score pass-through, and the /health status probe. + */ +import { describe, it, expect, afterEach, vi } from "vitest"; +import { + QueuePolicyConfigError, + QueuePolicyInvalidResponseError, + QueuePolicyUnavailableError, + QueuePolicyUntrainedError, + getQueuePolicyStatus, + requestQueuePolicySuggestion, + type QueuePolicyCandidate, +} from "./queuePolicy"; + +const CANDIDATES: QueuePolicyCandidate[] = [ + { declarationId: 11, features: { aeoTierRank: 3, fastLane: 1, submittedAgeMinutes: 40, riskScore: 12 } }, + { declarationId: 22, features: { aeoTierRank: 0, fastLane: 0, submittedAgeMinutes: 90, riskScore: 55 } }, +]; + +function configure() { + process.env.ML_STACK_HTTP_URL = "http://ml-stack.test:8100"; + process.env.RL_QUEUE_POLICY_SHADOW_ENABLED = "true"; +} + +afterEach(() => { + vi.unstubAllGlobals(); + delete process.env.ML_STACK_HTTP_URL; + delete process.env.RL_QUEUE_POLICY_SHADOW_ENABLED; + delete process.env.ML_STACK_SERVICE_TOKEN; +}); + +describe("queue-policy ml-stack client (Phase 18)", () => { + it("fails closed when ML_STACK_HTTP_URL is not configured", async () => { + process.env.RL_QUEUE_POLICY_SHADOW_ENABLED = "true"; + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyConfigError); + }); + + it("fails closed when the shadow gate is not enabled", async () => { + process.env.ML_STACK_HTTP_URL = "http://ml-stack.test:8100"; + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyConfigError); + }); + + it("returns the shadow suggestion with policy version and OPE score", async () => { + configure(); + const fetchMock = vi.fn(async () => + new Response( + JSON.stringify({ + mode: "shadow", + policy_version: "queue-policy-v0.1.0", + suggested_order: [22, 11], + ope_score: 0.83, + latency_ms: 12, + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ) + ); + vi.stubGlobal("fetch", fetchMock); + const res = await requestQueuePolicySuggestion(CANDIDATES); + expect(res.mode).toBe("shadow"); + expect(res.policyVersion).toBe("queue-policy-v0.1.0"); + expect(res.suggestedOrder).toEqual([22, 11]); + expect(res.opeScore).toBe(0.83); + // Request contract: POST /score/queue-policy with candidate ids. + const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; + expect(url).toBe("http://ml-stack.test:8100/score/queue-policy"); + expect(init.method).toBe("POST"); + const body = JSON.parse(String(init.body)); + expect(body.candidates.map((c: { declaration_id: number }) => c.declaration_id)).toEqual([11, 22]); + }); + + it("sends the env-only service token when configured", async () => { + configure(); + process.env.ML_STACK_SERVICE_TOKEN = "svc-token-1"; + const fetchMock = vi.fn(async () => + new Response( + JSON.stringify({ mode: "shadow", policy_version: "v1", suggested_order: [11, 22] }), + { status: 200 } + ) + ); + vi.stubGlobal("fetch", fetchMock); + await requestQueuePolicySuggestion(CANDIDATES); + const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; + expect((init.headers as Record).Authorization).toBe("Bearer svc-token-1"); + }); + + it.each([409, 503])("maps HTTP %i to a typed untrained error", async (status) => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => new Response("POLICY_NOT_TRAINED", { status }))); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyUntrainedError); + }); + + it("treats a non-OK status body as untrained, never as a suggestion", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => + new Response(JSON.stringify({ status: "SCORING_UNAVAILABLE", detail: "no promoted policy" }), { status: 200 }) + )); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyUntrainedError); + }); + + it("refuses non-shadow modes outright", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => + new Response(JSON.stringify({ mode: "enforce", policy_version: "v9", suggested_order: [11, 22] }), { status: 200 }) + )); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyInvalidResponseError); + }); + + it("refuses suggestions referencing declarations outside the candidate set", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => + new Response(JSON.stringify({ mode: "shadow", policy_version: "v1", suggested_order: [11, 999] }), { status: 200 }) + )); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyInvalidResponseError); + }); + + it("maps transport failure to a typed unavailable error", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("connection refused"); })); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyUnavailableError); + }); + + it("maps 5xx to unavailable", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => new Response("boom", { status: 500 }))); + await expect(requestQueuePolicySuggestion(CANDIDATES)).rejects.toBeInstanceOf(QueuePolicyUnavailableError); + }); + + it("rejects an empty candidate set without calling upstream", async () => { + configure(); + const fetchMock = vi.fn(); + vi.stubGlobal("fetch", fetchMock); + await expect(requestQueuePolicySuggestion([])).rejects.toBeInstanceOf(QueuePolicyUntrainedError); + expect(fetchMock).not.toHaveBeenCalled(); + }); +}); + +describe("queue-policy status probe", () => { + it("reports untrained when the registry has no queue-policy entry", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => + new Response(JSON.stringify({ status: "ok", models: { "declaration-fraud": { version: "0.1.0" } } }), { status: 200 }) + )); + const status = await getQueuePolicyStatus(); + expect(status).toEqual({ trained: false, policyVersion: null, opeScore: null, mode: null }); + }); + + it("reports trained with version and OPE score from the registry entry", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => + new Response( + JSON.stringify({ status: "ok", models: { "queue-policy": { version: "0.2.1", ope_score: 0.77 } } }), + { status: 200 } + ) + )); + const status = await getQueuePolicyStatus(); + expect(status).toEqual({ trained: true, policyVersion: "0.2.1", opeScore: 0.77, mode: "shadow" }); + }); + + it("is unavailable when ml-stack health fails", async () => { + configure(); + vi.stubGlobal("fetch", vi.fn(async () => new Response("bad", { status: 502 }))); + await expect(getQueuePolicyStatus()).rejects.toBeInstanceOf(QueuePolicyUnavailableError); + }); + + it("is config-gated like the scorer", async () => { + await expect(getQueuePolicyStatus()).rejects.toBeInstanceOf(QueuePolicyConfigError); + }); +}); diff --git a/server/rl/queuePolicy.ts b/server/rl/queuePolicy.ts new file mode 100644 index 0000000..eebb70c --- /dev/null +++ b/server/rl/queuePolicy.ts @@ -0,0 +1,263 @@ +/** + * queuePolicy.ts — config-gated client for the ml-stack RL queue-policy + * scorer (Phase 18, POST /score/queue-policy). + * + * Doctrine: + * - SHADOW ONLY. The policy SUGGESTS an order for the officer export + * queue; the FIFO/AEO-prioritized order computed by the aeoFastLane + * router remains authoritative. Nothing here ever reorders the real + * queue automatically. + * - Fail-closed and config-gated: without ML_STACK_HTTP_URL AND + * RL_QUEUE_POLICY_SHADOW_ENABLED=true the module refuses to call + * anything (QueuePolicyConfigError). No suggestion is ever fabricated. + * - Untrained policy is a first-class honest state: ml-stack answers + * 409/503 (or a non-shadow/error body) when no eval-gated policy has + * been promoted; that surfaces as QueuePolicyUntrainedError. + * - Auth: ml-stack verifies Keycloak JWKS; the caller authenticates with + * the env-only service token ML_STACK_SERVICE_TOKEN (same pattern as + * the Phase 16 congestion-forecast client). Secrets are env-only. + * + * Wire contract (Phase 18 plan): + * POST {ML_STACK_HTTP_URL}/score/queue-policy + * body: { entity_id, candidates: [{ declaration_id, features }] } + * 200: { mode: "shadow", policy_version, suggested_order: [declaration_id...], + * ope_score?: number } + * 409/503: policy not trained / scoring unavailable (honest refusal). + */ + +const ML_STACK_TIMEOUT_MS = 5_000; + +/** A candidate queue entry with only real, observable features. */ +export interface QueuePolicyCandidate { + declarationId: number; + features: { + /** 3=gold, 2=silver, 1=standard/uncertified tier rank; 0 = not AEO-certified. */ + aeoTierRank: number; + /** 1 when the exporter is authority-certified AEO (fast-lane flag). */ + fastLane: number; + /** Minutes since submission (real clock math, never fabricated). */ + submittedAgeMinutes: number; + /** Numeric risk score when the pipeline produced one; 0 otherwise. */ + riskScore: number; + }; +} + +export interface QueuePolicySuggestion { + mode: "shadow"; + policyVersion: string; + /** Declaration ids in the policy's suggested processing order. */ + suggestedOrder: number[]; + /** Off-policy evaluation score, only when the serving payload carries it. */ + opeScore: number | null; + latencyMs: number | null; +} + +export class QueuePolicyConfigError extends Error { + constructor(detail: string) { + super(`QUEUE_POLICY_NOT_CONFIGURED: ${detail}`); + this.name = "QueuePolicyConfigError"; + } +} + +export class QueuePolicyUntrainedError extends Error { + readonly status?: number; + constructor(detail: string, status?: number) { + super(`QUEUE_POLICY_NOT_TRAINED: ${detail}`); + this.name = "QueuePolicyUntrainedError"; + this.status = status; + } +} + +export class QueuePolicyUnavailableError extends Error { + constructor(detail: string) { + super(`QUEUE_POLICY_UNAVAILABLE: ${detail}`); + this.name = "QueuePolicyUnavailableError"; + } +} + +export class QueuePolicyInvalidResponseError extends Error { + constructor(detail: string) { + super(`QUEUE_POLICY_INVALID_RESPONSE: ${detail}`); + this.name = "QueuePolicyInvalidResponseError"; + } +} + +/** + * Resolve the configured ml-stack base URL, reading live env (not an + * import-time snapshot) so the gate reflects the current deployment. + */ +export function queuePolicyBaseUrl(): string { + const url = (process.env.ML_STACK_HTTP_URL ?? "").trim(); + if (!url) { + throw new QueuePolicyConfigError( + "ML_STACK_HTTP_URL is not configured — refusing to fabricate a queue suggestion (fail closed)." + ); + } + if ((process.env.RL_QUEUE_POLICY_SHADOW_ENABLED ?? "").trim() !== "true") { + throw new QueuePolicyConfigError( + "RL_QUEUE_POLICY_SHADOW_ENABLED is not 'true' — the shadow queue policy is disabled in this deployment." + ); + } + return url.replace(/\/+$/, ""); +} + +function parseSuggestion(data: unknown, candidateIds: ReadonlySet): QueuePolicySuggestion { + if (typeof data !== "object" || data === null || Array.isArray(data)) { + throw new QueuePolicyInvalidResponseError("response body is not an object"); + } + const body = data as Record; + // Honest-refusal bodies (model undeployed / not promoted) may come back + // with HTTP 200 in some gateway setups — treat any non-shadow payload as + // an untrained/unavailable policy, never as a suggestion. + if (body.status !== undefined && body.status !== "OK") { + throw new QueuePolicyUntrainedError( + typeof body.detail === "string" ? body.detail : `status=${String(body.status)}` + ); + } + if (body.mode !== "shadow") { + throw new QueuePolicyInvalidResponseError( + `mode must be "shadow" (got ${JSON.stringify(body.mode)}) — RL output is advisory only` + ); + } + if (typeof body.policy_version !== "string" || body.policy_version.trim() === "") { + throw new QueuePolicyInvalidResponseError("policy_version must be a non-empty string"); + } + if (!Array.isArray(body.suggested_order)) { + throw new QueuePolicyInvalidResponseError("suggested_order must be an array"); + } + const suggestedOrder = body.suggested_order.map((v, i) => { + if (typeof v !== "number" || !Number.isInteger(v)) { + throw new QueuePolicyInvalidResponseError(`suggested_order[${i}] must be an integer declaration id`); + } + return v; + }); + // Fail closed on suggestions referencing declarations outside the + // candidate set we sent — the policy may only permute what it was shown. + for (const id of suggestedOrder) { + if (!candidateIds.has(id)) { + throw new QueuePolicyInvalidResponseError( + `suggested_order references declaration ${id} which was not a candidate — refusing to display it` + ); + } + } + const opeScore = + typeof body.ope_score === "number" && Number.isFinite(body.ope_score) ? body.ope_score : null; + const latencyMs = + typeof body.latency_ms === "number" && Number.isFinite(body.latency_ms) ? body.latency_ms : null; + return { + mode: "shadow", + policyVersion: body.policy_version, + suggestedOrder, + opeScore, + latencyMs, + }; +} + +/** + * Request a shadow suggestion for the officer export queue. FAIL-CLOSED: + * throws QueuePolicyConfigError / QueuePolicyUntrainedError / + * QueuePolicyUnavailableError / QueuePolicyInvalidResponseError — callers + * must surface these honestly and keep the authoritative order. + */ +export async function requestQueuePolicySuggestion( + candidates: QueuePolicyCandidate[] +): Promise { + const baseUrl = queuePolicyBaseUrl(); + if (candidates.length === 0) { + throw new QueuePolicyUntrainedError("no queue candidates supplied — nothing to score"); + } + + const headers: Record = { "Content-Type": "application/json" }; + const serviceToken = (process.env.ML_STACK_SERVICE_TOKEN ?? "").trim(); + if (serviceToken) headers["Authorization"] = `Bearer ${serviceToken}`; + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), ML_STACK_TIMEOUT_MS); + let res: Response; + try { + res = await fetch(`${baseUrl}/score/queue-policy`, { + method: "POST", + headers, + body: JSON.stringify({ + entity_id: "officer-export-queue", + candidates: candidates.map((c) => ({ + declaration_id: c.declarationId, + features: c.features, + })), + }), + signal: controller.signal, + }); + } catch (err) { + const timedOut = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError"); + throw new QueuePolicyUnavailableError( + `ml-stack ${timedOut ? "timed out" : "unreachable"}: ${err instanceof Error ? err.message : String(err)}` + ); + } finally { + clearTimeout(timer); + } + + if (res.status === 409 || res.status === 503) { + const body = (await res.text().catch(() => "")).slice(0, 300); + throw new QueuePolicyUntrainedError( + `ml-stack honestly refused (HTTP ${res.status}): ${body || "policy not trained / not promoted"}`, + res.status + ); + } + if (res.status >= 400 && res.status < 500) { + const body = (await res.text().catch(() => "")).slice(0, 300); + throw new QueuePolicyUnavailableError(`ml-stack rejected the request (HTTP ${res.status}): ${body || "no detail"}`); + } + if (!res.ok) { + throw new QueuePolicyUnavailableError(`ml-stack upstream error: HTTP ${res.status}`); + } + const data = await res.json().catch(() => null); + if (data === null) { + throw new QueuePolicyInvalidResponseError("response body is not valid JSON"); + } + return parseSuggestion(data, new Set(candidates.map((c) => c.declarationId))); +} + +/** + * Status probe for the ministry RL-insights surface (Phase 18): reads the + * ml-stack registry via /health and reports whether a queue-policy model + * is promoted. Honest states only — untrained is data, not an error. + */ +export interface QueuePolicyStatus { + trained: boolean; + policyVersion: string | null; + opeScore: number | null; + mode: "shadow" | null; +} + +export async function getQueuePolicyStatus(): Promise { + const baseUrl = queuePolicyBaseUrl(); + let res: Response; + try { + res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(ML_STACK_TIMEOUT_MS) }); + } catch (err) { + throw new QueuePolicyUnavailableError( + `ml-stack unreachable: ${err instanceof Error ? err.message : String(err)}` + ); + } + if (!res.ok) { + throw new QueuePolicyUnavailableError(`ml-stack health error: HTTP ${res.status}`); + } + const data = (await res.json().catch(() => null)) as Record | null; + if (!data || data.status !== "ok" || typeof data.models !== "object" || data.models === null) { + throw new QueuePolicyUnavailableError("ml-stack reports not-ok or an unreadable registry"); + } + const entry = (data.models as Record)["queue-policy"]; + if (entry === undefined || entry === null) { + // Honest untrained state: no promoted queue-policy in the registry. + return { trained: false, policyVersion: null, opeScore: null, mode: null }; + } + const rec = typeof entry === "object" ? (entry as Record) : {}; + const version = + typeof rec.version === "string" ? rec.version + : typeof rec.policy_version === "string" ? rec.policy_version + : typeof entry === "string" ? entry + : null; + const opeScore = + typeof rec.ope_score === "number" && Number.isFinite(rec.ope_score) ? rec.ope_score : null; + return { trained: true, policyVersion: version, opeScore, mode: "shadow" }; +} diff --git a/server/routers.ts b/server/routers.ts index b62c1d0..95f36bf 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -125,6 +125,7 @@ import { openDataRouter } from "./routers/openData"; import { ncsNrsRouter } from "./routers/ncsNrs"; import { pcsRouter } from "./routers/pcs"; import { aeoFastLaneRouter } from "./routers/aeoFastLane"; +import { queuePolicyRouter } from "./routers/queuePolicy"; import { transshipmentRouter } from "./routers/transshipment"; import { complianceReportingRouter } from "./routers/complianceReporting"; @@ -403,6 +404,8 @@ export const appRouter = router({ // Phase 16 Wave P1 — AEO export fast-lane + transshipment declaration lane aeoFastLane: aeoFastLaneRouter, transshipment: transshipmentRouter, + // Phase 18 — RL queue-policy shadow suggestion (never auto-reorders) + queuePolicy: queuePolicyRouter, }); export type AppRouter = typeof appRouter; diff --git a/server/routers/aeoFastLane.ts b/server/routers/aeoFastLane.ts index 24dcf6c..bd48b8c 100644 --- a/server/routers/aeoFastLane.ts +++ b/server/routers/aeoFastLane.ts @@ -34,14 +34,14 @@ import { users, } from "../../drizzle/schema"; -const OFFICER_QUEUE_ROLES = [ +export const OFFICER_QUEUE_ROLES = [ "admin", "superadmin", "platform_admin", "customs_commissioner", "customs_officer", "inspector", "finance", ]; const TIER_RANK = sql`case ${stakeholderProfiles.aeoTier} when 'gold' then 3 when 'silver' then 2 else 1 end`; -function requireOfficer(role: string): void { +export function requireOfficer(role: string): void { if (!OFFICER_QUEUE_ROLES.includes(role)) { throw new TRPCError({ code: "FORBIDDEN", message: "Officer role required" }); } @@ -55,6 +55,53 @@ async function requireDb() { return db; } +/** + * Authoritative prioritized export-declaration queue: AEO-certified + * exporters first (gold > silver > standard), then FIFO by submission. + * Shared by the queue.prioritized procedure and the Phase 18 queuePolicy + * shadow router — the RL policy only ever ANNOTATES this order. + */ +export async function loadPrioritizedExportQueue( + db: NonNullable>>, + opts: { status?: string; limit: number } +) { + const conditions = [eq(declarations.declarationType, "export")]; + if (opts.status) conditions.push(eq(declarations.status, opts.status as never)); + const rows = await db + .select({ + id: declarations.id, + declarationNumber: declarations.declarationNumber, + traderId: declarations.traderId, + traderName: users.name, + status: declarations.status, + riskLane: declarations.riskLane, + riskScore: declarations.riskScore, + hsCode: declarations.hsCode, + goodsDescription: declarations.goodsDescription, + countryOfDestination: declarations.countryOfDestination, + submittedAt: declarations.submittedAt, + createdAt: declarations.createdAt, + aeoStatus: stakeholderProfiles.aeoStatus, + aeoTier: stakeholderProfiles.aeoTier, + }) + .from(declarations) + .leftJoin(users, eq(declarations.traderId, users.id)) + .leftJoin(stakeholderProfiles, eq(declarations.traderId, stakeholderProfiles.userId)) + .where(and(...conditions)) + .orderBy( + // Accredited exporters first, then tier rank, then FIFO. + sql`case when ${stakeholderProfiles.aeoStatus} = 'certified' then 0 else 1 end`, + desc(TIER_RANK), + sql`${declarations.submittedAt} asc nulls last`, + desc(declarations.id) + ) + .limit(opts.limit); + return rows.map((r) => ({ + ...r, + fastLane: r.aeoStatus === "certified", + })); +} + /** Accreditation record for a trader, or null when not AEO-certified. */ async function accreditationOf(db: NonNullable>>, userId: number) { const [profile] = await db @@ -83,43 +130,11 @@ export const aeoFastLaneRouter = router({ .query(async ({ ctx, input }) => { requireOfficer(ctx.user.role); const db = await requireDb(); - const conditions = [eq(declarations.declarationType, "export")]; - if (input?.status) conditions.push(eq(declarations.status, input.status as never)); - const rows = await db - .select({ - id: declarations.id, - declarationNumber: declarations.declarationNumber, - traderId: declarations.traderId, - traderName: users.name, - status: declarations.status, - riskLane: declarations.riskLane, - riskScore: declarations.riskScore, - hsCode: declarations.hsCode, - goodsDescription: declarations.goodsDescription, - countryOfDestination: declarations.countryOfDestination, - submittedAt: declarations.submittedAt, - createdAt: declarations.createdAt, - aeoStatus: stakeholderProfiles.aeoStatus, - aeoTier: stakeholderProfiles.aeoTier, - }) - .from(declarations) - .leftJoin(users, eq(declarations.traderId, users.id)) - .leftJoin(stakeholderProfiles, eq(declarations.traderId, stakeholderProfiles.userId)) - .where(and(...conditions)) - .orderBy( - // Accredited exporters first, then tier rank, then FIFO. - sql`case when ${stakeholderProfiles.aeoStatus} = 'certified' then 0 else 1 end`, - desc(TIER_RANK), - sql`${declarations.submittedAt} asc nulls last`, - desc(declarations.id) - ) - .limit(input?.limit ?? 50); - return { - items: rows.map((r) => ({ - ...r, - fastLane: r.aeoStatus === "certified", - })), - }; + const items = await loadPrioritizedExportQueue(db, { + status: input?.status, + limit: input?.limit ?? 50, + }); + return { items }; }), }), diff --git a/server/routers/queuePolicy.ts b/server/routers/queuePolicy.ts new file mode 100644 index 0000000..2d1cf5d --- /dev/null +++ b/server/routers/queuePolicy.ts @@ -0,0 +1,149 @@ +/** + * queuePolicy.ts — Phase 18 RL queue-policy SHADOW surface for officers. + * + * - suggestion — calls the ml-stack POST /score/queue-policy shadow + * scorer (config-gated, fail-closed) and returns the + * policy's suggested order NEXT TO the authoritative + * FIFO/AEO order. The authoritative order is never + * modified; the RL output is an annotation only. + * - recordDecision — append-only log of the officer's accept/override + * decision (queue_policy_decisions, migration 0070) + * for future offline-RL reward joins. + * + * Untrained policy is a first-class honest state: ml-stack 409/503 (or a + * non-shadow payload) maps to PRECONDITION_FAILED with a typed + * QUEUE_POLICY_NOT_TRAINED message; an unconfigured deployment maps to + * PRECONDITION_FAILED QUEUE_POLICY_NOT_CONFIGURED. No suggestion is ever + * fabricated. + */ +import { z } from "zod"; +import { TRPCError } from "@trpc/server"; +import { protectedProcedure, router } from "../_core/trpc"; +import { getDb } from "../db"; +import { queuePolicyDecisions } from "../../drizzle/schema"; +import { + loadPrioritizedExportQueue, + requireOfficer, +} from "./aeoFastLane"; +import { + QueuePolicyConfigError, + QueuePolicyInvalidResponseError, + QueuePolicyUnavailableError, + QueuePolicyUntrainedError, + requestQueuePolicySuggestion, + type QueuePolicyCandidate, +} from "../rl/queuePolicy"; + +function toTrpcError(err: unknown): TRPCError { + if (err instanceof QueuePolicyUntrainedError) { + return new TRPCError({ code: "PRECONDITION_FAILED", message: err.message }); + } + if (err instanceof QueuePolicyConfigError) { + return new TRPCError({ code: "PRECONDITION_FAILED", message: err.message }); + } + if (err instanceof QueuePolicyInvalidResponseError) { + // The upstream answered 200 but the payload violates the shadow + // contract — refuse to display it (fail closed). + return new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: err.message }); + } + if (err instanceof QueuePolicyUnavailableError) { + return new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: err.message }); + } + return err instanceof TRPCError + ? err + : new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "queue-policy suggestion failed" }); +} + +const TIER_RANKS: Record = { gold: 3, silver: 2, standard: 1 }; + +export const queuePolicyRouter = router({ + /** + * Shadow suggestion for the officer export queue. Returns both orders; + * the caller MUST treat authoritativeOrder as the binding sequence. + */ + suggestion: protectedProcedure + .input( + z + .object({ + status: z.string().optional(), + limit: z.number().int().min(1).max(200).default(50), + }) + .optional() + ) + .query(async ({ ctx, input }) => { + requireOfficer(ctx.user.role); + const db = await getDb(); + if (!db) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Database is not available in this environment", + }); + } + const items = await loadPrioritizedExportQueue(db, { + status: input?.status, + limit: input?.limit ?? 50, + }); + const now = Date.now(); + const candidates: QueuePolicyCandidate[] = items.map((r) => ({ + declarationId: r.id, + features: { + aeoTierRank: r.fastLane ? TIER_RANKS[r.aeoTier ?? "standard"] ?? 1 : 0, + fastLane: r.fastLane ? 1 : 0, + submittedAgeMinutes: r.submittedAt + ? Math.max(0, Math.round((now - new Date(r.submittedAt).getTime()) / 60_000)) + : 0, + riskScore: r.riskScore != null ? Number(r.riskScore) : 0, + }, + })); + try { + const suggestion = await requestQueuePolicySuggestion(candidates); + return { + mode: suggestion.mode, + policyVersion: suggestion.policyVersion, + opeScore: suggestion.opeScore, + /** Authoritative FIFO/AEO order — binding, never auto-reordered. */ + authoritativeOrder: items.map((r) => r.id), + suggestedOrder: suggestion.suggestedOrder, + }; + } catch (err) { + throw toTrpcError(err); + } + }), + + /** + * Append-only officer decision log (reward-join substrate). The decision + * is recorded against the policy version that produced the suggestion. + */ + recordDecision: protectedProcedure + .input( + z.object({ + declarationId: z.number().int().positive(), + policyVersion: z.string().min(1).max(64), + suggestedPosition: z.number().int().min(1), + authoritativePosition: z.number().int().min(1), + decision: z.enum(["accepted", "overrode"]), + }) + ) + .mutation(async ({ ctx, input }) => { + requireOfficer(ctx.user.role); + const db = await getDb(); + if (!db) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Database is not available in this environment", + }); + } + const [row] = await db + .insert(queuePolicyDecisions) + .values({ + officerId: ctx.user.id, + declarationId: input.declarationId, + policyVersion: input.policyVersion, + suggestedPosition: input.suggestedPosition, + authoritativePosition: input.authoritativePosition, + decision: input.decision, + }) + .returning(); + return { id: row.id, recorded: true }; + }), +}); diff --git a/server/routes/executiveApi.ts b/server/routes/executiveApi.ts index f7e3d7a..c924d98 100644 --- a/server/routes/executiveApi.ts +++ b/server/routes/executiveApi.ts @@ -33,6 +33,11 @@ import { } from "../executive/portPerformance"; import { buildSignedPortPerformancePdf } from "../executive/portPerformancePdf"; import { callRiskScorer } from "../routers/riskModel"; +import { + QueuePolicyConfigError, + QueuePolicyUnavailableError, + getQueuePolicyStatus, +} from "../rl/queuePolicy"; const PROD_UPSTREAM = { id: "executive-api", sandbox: false } as const; @@ -145,6 +150,42 @@ export function registerExecutiveApiRoutes(app: Express): void { } ); + // ── RL queue-policy shadow status (Phase 18) ──────────────────────────────── + // Honest status of the ml-stack queue-policy for the ministry RL-insights + // card: trained/untrained, policy_version, OPE score when the registry + // carries it. Config-gated and fail-closed — 503 when the shadow policy + // surface is not configured or ml-stack is unreachable; untrained is a + // first-class 200 state, never fabricated. + app.get( + "/v1/rl/queue-policy/status", + requireApiKey("reports:read", PROD_UPSTREAM), + async (_req, res) => { + try { + const status = await getQueuePolicyStatus(); + res.json({ + status: "ok", + surface: "officer-export-queue", + trained: status.trained, + policyVersion: status.policyVersion, + opeScore: status.opeScore, + mode: status.mode, + note: status.trained + ? "Shadow policy promoted — suggestions are advisory only and never auto-applied." + : "Policy not trained — no promoted queue policy in the ml-stack registry.", + }); + } catch (err) { + if (err instanceof QueuePolicyConfigError || err instanceof QueuePolicyUnavailableError) { + res.status(503).json({ + status: "down", + error: err.message, + }); + return; + } + down(res, err); + } + } + ); + // ── Port performance report (Phase 16 Wave P2) ────────────────────────────── // JSON metrics, fail-closed: value:null where a source has no data. app.get(