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 && (
+ <>
+ logDecision(d.id, idx + 1, "accepted")}
+ >
+ Accept
+
+ logDecision(d.id, idx + 1, "overrode")}
+ >
+ Override
+
+ >
+ )}
+ {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(