mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* chore(api-manifest): rewrite brief-why-matters reason as proper internal-helper justification Carried in from #3248 merge as a band-aid (called out in #3242 review followup checklist item 7). The endpoint genuinely belongs in internal-helper — RELAY_SHARED_SECRET-bearer auth, cron-only caller, never reached by dashboards or partners. Same shape constraint as api/notify.ts. Replaces the apologetic "filed here to keep the lint green" framing with a proper structural justification: modeling it as a generated service would publish internal cron plumbing as user-facing API surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(lint): premium-fetch parity check for ServiceClients (closes #3279) Adds scripts/enforce-premium-fetch.mjs — AST-walks src/, finds every `new <ServiceClient>(...)` (variable decl OR `this.foo =` assignment), tracks which methods each instance actually calls, and fails if any called method targets a path in src/shared/premium-paths.ts PREMIUM_RPC_PATHS without `{ fetch: premiumFetch }` on the constructor. Per-call-site analysis (not class-level) keeps the trade/index.ts pattern clean — publicClient with globalThis.fetch + premiumClient with premiumFetch on the same TradeServiceClient class — since publicClient never calls a premium method. Wired into: - npm run lint:premium-fetch - .husky/pre-push (right after lint:rate-limit-policies) - .github/workflows/lint-code.yml (right after lint:api-contract) Found and fixed three latent instances of the HIGH(new) #1 class from #3242 review (silent 401 → empty fallback for signed-in browser pros): - src/services/correlation-engine/engine.ts — IntelligenceServiceClient built with no fetch option called deductSituation. LLM-assessment overlay on convergence cards never landed for browser pros without a WM key. - src/services/economic/index.ts — EconomicServiceClient with globalThis.fetch called getNationalDebt. National-debt panel rendered empty for browser pros. - src/services/sanctions-pressure.ts — SanctionsServiceClient with globalThis.fetch called listSanctionsPressure. Sanctions-pressure panel rendered empty for browser pros. All three swap to premiumFetch (single shared client, mirrors the supply-chain/index.ts justification — premiumFetch no-ops safely on public methods, so the public methods on those clients keep working). Verification: - lint:premium-fetch clean (34 ServiceClient classes, 28 premium paths, 466 src/ files analyzed) - Negative test: revert any of the three to globalThis.fetch → exit 1 with file:line and called-premium-method names - typecheck + typecheck:api clean - lint:api-contract / lint:rate-limit-policies / lint:boundaries clean - tests/sanctions-pressure.test.mjs + premium-fetch.test.mts: 16/16 pass Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(military): fetchStaleFallback NEG_TTL=30s parity (closes #3277) The legacy /api/military-flights handler had NEG_TTL = 30_000ms — a short suppression window after a failed live + stale read so we don't Redis-hammer the stale key during sustained relay+seed outages. Carried into the sebuf list-military-flights handler: - Module-scoped `staleNegUntil` timestamp (per-isolate on Vercel Edge, which is fine — each warm isolate gets its own 30s suppression window). - Set whenever fetchStaleFallback returns null (key missing, parse fail, empty array after staleToProto filter, or thrown error). - Checked at the entry of fetchStaleFallback before doing the Redis read. - Test seam `_resetStaleNegativeCacheForTests()` exposed for unit tests. Test pinned in tests/redis-caching.test.mjs: drives a stale-empty cycle three times — first read hits Redis, second within window doesn't, after test-only reset it does again. Verified: 18/18 redis-caching tests pass, typecheck:api clean, lint:premium-fetch clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lint): rate-limit-policies regex → import() (closes #3278) The previous lint regex-parsed ENDPOINT_RATE_POLICIES from the source file. That worked because the literal happens to fit a single line per key today, but a future reformat (multi-line key wrap, formatter swap, etc.) would silently break the lint without breaking the build — exactly the failure mode that's worse than no lint at all. Fix: - Export ENDPOINT_RATE_POLICIES from server/_shared/rate-limit.ts. - Convert scripts/enforce-rate-limit-policies.mjs to async + dynamic import() of the policy object directly. Same TS module that the gateway uses at runtime → no source-of-truth drift possible. - Run via tsx (already a dev dep, used by test:data) so the .mjs shebang can resolve a .ts import. - npm script swapped to `tsx scripts/...`. .husky/pre-push uses `npm run lint:rate-limit-policies` so no hook change needed. Verified: - Clean: 6 policies / 182 gateway routes. - Negative test (rename a key to the original sanctions typo /api/sanctions/v1/lookup-entity): exit 1 with the same incident- attributed remedy message as before. - Reformat test (split a single-line entry across multiple lines): still passes — the property is what's read, not the source layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(shipping/v2): alertThreshold: 0 preserved; drop dead validation branch (#3242 followup) Before: alert_threshold was a plain int32. proto3 scalar default is 0, so the handler couldn't distinguish "partner explicitly sent 0 (deliver every disruption)" from "partner omitted the field (apply legacy default 50)" — both arrived as 0 and got coerced to 50 by `> 0 ? : 50`. Silent intent-drop for any partner who wanted every alert. The subsequent `alertThreshold < 0` branch was also unreachable after that coercion. After: - Proto field is `optional int32 alert_threshold` — TS type becomes `alertThreshold?: number`, so omitted = undefined and explicit 0 stays 0. - Handler uses `req.alertThreshold ?? 50` — undefined → 50, any number passes through unchanged. - Dead `< 0 || > 100` runtime check removed; buf.validate `int32.gte = 0, int32.lte = 100` already enforces the range at the wire layer. Partner wire contract: identical for the omit-field and 1..100 cases. Only behavioural change is explicit 0 — previously impossible to request, now honored per proto3 optional semantics. Scoped `buf generate --path worldmonitor/shipping/v2` to avoid the full- regen `@ts-nocheck` drift Seb documented in the #3242 PR comments. Re-applied `@ts-nocheck` on the two regenerated files manually. Tests: - `alertThreshold 0 coerces to 50` flipped to `alertThreshold 0 preserved`. - New test: `alertThreshold omitted (undefined) applies legacy default 50`. - `rejects > 100` test removed — proto/wire validation handles it; direct handler calls intentionally bypass wire and the handler no longer carries a redundant runtime range check. Verified: 18/18 shipping-v2-handler tests pass, typecheck + typecheck:api clean, all 4 custom lints clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(shipping/v2): document missing webhook delivery worker + DNS-rebinding contract (#3242 followup) #3242 followup checklist item 6 from @koala73 — sanity-check that the delivery worker honors the re-resolve-and-re-check contract that isBlockedCallbackUrl explicitly delegates to it. Audit finding: no delivery worker for shipping/v2 webhooks exists in this repo. Grep across the entire tree (excluding generated/dist) shows the only readers of webhook:sub:* records are the registration / inspection / rotate-secret handlers themselves. No code reads them and POSTs to the stored callbackUrl. The delivery worker is presumed to live in Railway (separate repo) or hasn't been built yet — neither is auditable from this repo. Refreshes the comment block at the top of webhook-shared.ts to: - explicitly state DNS rebinding is NOT mitigated at registration - spell out the four-step contract the delivery worker MUST follow (re-validate URL, dns.lookup, re-check resolved IP against patterns, fetch with resolved IP + Host header preserved) - flag the in-repo gap so anyone landing delivery code can't miss it Tracking the gap as #3288 — acceptance there is "delivery worker imports the patterns + helpers from webhook-shared.ts and applies the four steps before each send." Action moves to wherever the delivery worker actually lives (Railway likely). No code change. Tests + lints unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(lint): add rate-limit-policies step (greptile P1 #3287) Pre-push hook ran lint:rate-limit-policies but the CI workflow did not, so fork PRs and --no-verify pushes bypassed the exact drift check the lint was added to enforce (closes #3278). Adding it right after lint:api-contract so it runs in the same context the lint was designed for. * refactor(lint): premium-fetch regex → import() + loop classRe (greptile P2 #3287) Two fragilities greptile flagged on enforce-premium-fetch.mjs: 1. loadPremiumPaths regex-parsed src/shared/premium-paths.ts with /'(\/api\/[^']+)'/g — same class of silent drift we just removed from enforce-rate-limit-policies in #3278. Reformatting the source Set (double quotes, spread, helper-computed entries) would drop paths from the lint while leaving the runtime untouched. Fix: flip the shebang to `#!/usr/bin/env -S npx tsx` and dynamic-import PREMIUM_RPC_PATHS directly, mirroring the rate-limit pattern. package.json lint:premium-fetch now invokes via tsx too so the npm-script path matches direct execution. 2. loadClientClassMap ran classRe.exec once, silently dropping every ServiceClient after the first if a file ever contained more than one. Current codegen emits one class per file so this was latent, but a template change would ship un-linted classes. Fix: collect every class-open match with matchAll, slice each class body with the next class's start as the boundary, and scan methods per-body so method-to-class binding stays correct even with multiple classes per file. Verification: - lint:premium-fetch clean (34 classes / 28 premium paths / 466 files — identical counts to pre-refactor, so no coverage regression). - Negative test: revert src/services/economic/index.ts to globalThis.fetch → exit 1 with file:line, bound var name, and premium method list (getNationalDebt). Restore → clean. - lint:rate-limit-policies still clean. * fix(shipping/v2): re-add alertThreshold handler range guard (greptile nit 1 #3287) Wire-layer buf.validate enforces 0..100, but direct handler invocation (internal jobs, test harnesses, future transports) bypasses it. Cheap invariant-at-the-boundary — rejects < 0 or > 100 with ValidationError before the record is stored. Tests: restored the rejects-out-of-range cases that were dropped when the branch was (correctly) deleted as dead code on the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lint): premium-fetch method-regex → TS AST (greptile nits 2+5 #3287) loadClientClassMap: The method regex `async (\w+)\s*\([^)]*\)\s*:\s*Promise<[^>]+>\s*\{\s*let path = "..."` assumed (a) no nested `)` in arg types, (b) no nested `>` in the return type, (c) `let path = "..."` as the literal first statement. Any codegen template shift would silently drop methods with the lint still passing clean — the same silent-drift class #3287 just closed on the premium-paths side. Now walks the service_client.ts AST, matches `export class *ServiceClient`, iterates `MethodDeclaration` members, and reads the first `let path: string = '...'` variable statement as a StringLiteral. Tolerant to any reformatting of arg/return types or method shape. findCalls scope-blindness: Added limitation comment — the walker matches `<varName>.<method>()` anywhere in the file without respecting scope. Two constructions in different function scopes sharing a var name merge their called-method sets. No current src/ file hits this; the lint errs cautiously (flags both instances). Keeping the walker simple until scope-aware binding is needed. webhook-shared.ts: Inlined issue reference (#3288) so the breadcrumb resolves without bouncing through an MDX that isn't in the diff. Verification: - lint:premium-fetch clean — 34 classes / 28 premium paths / 489 files. Pre-refactor: 34 / 28 / 466. Class + path counts identical; file bump is from the main-branch rebase, not the refactor. - Negative test: revert src/services/economic/index.ts premiumFetch → globalThis.fetch. Lint exits 1 at `src/services/economic/index.ts:64:7` with `premium method(s) called: getNationalDebt`. Restore → clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lint): rate-limit OpenAPI regex → yaml parser (greptile nit 3 #3287) Input side (ENDPOINT_RATE_POLICIES) was flipped to live `import()` in4e79d029. Output side (OpenAPI routes) still regex-scraped top-level `paths:` keys with `/^\s{4}(\/api\/[^\s:]+):/gm` — hard-coded 4-space indent. Any YAML formatter change (2-space indent, flow style, line folding) would silently drop routes and let policy-drift slip through — same silent-drift class the input-side fix closed. Now uses the `yaml` package (already a dep) to parse each .openapi.yaml and reads `doc.paths` directly. Verification: - Clean: 6 policies / 189 routes (was 182 — yaml parser picks up a handful the regex missed, closing a silent coverage gap). - Negative test: rename policy key back to /api/sanctions/v1/lookup-entity → exits 1 with the same incident-attributed remedy. Restore → clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(codegen): regenerate unified OpenAPI bundle for alert_threshold proto change The shipping/v2 webhook alert_threshold field was flipped from `int32` to `optional int32` with an expanded doc comment inf3339464. That comment now surfaces in the unified docs/api/worldmonitor.openapi.yaml bundle (introduced by #3341). Regenerated with sebuf v0.11.1 to pick it up. No behaviour change — bundle-only documentation drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
413 lines
17 KiB
JSON
413 lines
17 KiB
JSON
{
|
|
"$comment": "Single source of truth for non-proto /api/ endpoints. All new JSON data APIs MUST use sebuf (proto → buf generate → handler). This manifest is the only escape hatch, and every entry is reviewed by @SebastienMelki (see .github/CODEOWNERS). Categories: external-protocol (MCP / OAuth — shape dictated by external spec), non-json (binary/HTML/image responses), upstream-proxy (raw pass-through of an external feed), ops-admin (health/cron/version — operator plumbing, not a product API), internal-helper (dashboard-internal bundle, not user-facing), deferred (should migrate eventually — must have a removal_issue), migration-pending (actively being migrated in an open PR — removed as its commit lands). See docs/adding-endpoints.mdx.",
|
|
"schema_version": 1,
|
|
"exceptions": [
|
|
{
|
|
"path": "api/mcp.ts",
|
|
"category": "external-protocol",
|
|
"reason": "MCP streamable-HTTP transport — JSON-RPC 2.0 envelope dictated by the Model Context Protocol spec, not something we can or should redefine as proto.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/mcp-proxy.js",
|
|
"category": "external-protocol",
|
|
"reason": "Proxy for the MCP transport — same shape constraint as api/mcp.ts.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/oauth/authorize.js",
|
|
"category": "external-protocol",
|
|
"reason": "OAuth 2.0 authorization endpoint. Response is an HTML consent page and 302 redirect; request/response shape is dictated by RFC 6749.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/oauth/register.js",
|
|
"category": "external-protocol",
|
|
"reason": "OAuth 2.0 dynamic client registration (RFC 7591). Shape fixed by the spec.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/oauth/token.js",
|
|
"category": "external-protocol",
|
|
"reason": "OAuth 2.0 token endpoint (RFC 6749). application/x-www-form-urlencoded request body; shape fixed by the spec.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/oauth-protected-resource.ts",
|
|
"category": "external-protocol",
|
|
"reason": "RFC 9728 OAuth Protected Resource metadata. Dynamic per-host to satisfy scanner origin-match rules — shape dictated by RFC 9728.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/discord/oauth/callback.ts",
|
|
"category": "external-protocol",
|
|
"reason": "Discord OAuth redirect target — response is an HTML popup-closer page, query-param shape fixed by Discord.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/discord/oauth/start.ts",
|
|
"category": "external-protocol",
|
|
"reason": "Discord OAuth initiator — issues 302 to Discord's authorize URL. Not a JSON API.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/slack/oauth/callback.ts",
|
|
"category": "external-protocol",
|
|
"reason": "Slack OAuth redirect target — HTML response with postMessage + window.close, query-param shape fixed by Slack.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/slack/oauth/start.ts",
|
|
"category": "external-protocol",
|
|
"reason": "Slack OAuth initiator — 302 to Slack's authorize URL. Not a JSON API.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
|
|
{
|
|
"path": "api/download.js",
|
|
"category": "non-json",
|
|
"reason": "Binary file download (zip/csv/xlsx). Content-Type is not application/json.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/og-story.js",
|
|
"category": "non-json",
|
|
"reason": "Open Graph preview image (PNG via @vercel/og). Binary response.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/story.js",
|
|
"category": "non-json",
|
|
"reason": "Rendered HTML story page for social embeds — response is text/html, not JSON.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/youtube/embed.js",
|
|
"category": "non-json",
|
|
"reason": "YouTube oEmbed passthrough — shape dictated by YouTube's oEmbed response, served as-is.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/youtube/live.js",
|
|
"category": "non-json",
|
|
"reason": "Streams YouTube live metadata — chunked text response, not a typed JSON payload.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/brief/carousel/[userId]/[issueDate]/[page].ts",
|
|
"category": "non-json",
|
|
"reason": "Rendered carousel page image for brief social posts. Binary image response, dynamic path segments.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
|
|
{
|
|
"path": "api/opensky.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Transparent proxy to OpenSky Network API. Shape is OpenSky's; we don't remodel it.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/polymarket.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Transparent proxy to Polymarket gamma API. Shape is Polymarket's.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/gpsjam.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Transparent proxy to gpsjam.org tile/feed. Shape is upstream's.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/oref-alerts.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Transparent proxy to Pikud HaOref (IDF Home Front Command) alert feed. Shape is upstream's.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/rss-proxy.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Generic RSS/Atom XML proxy for CORS-blocked feeds. Response is XML, not JSON.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/telegram-feed.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Telegram channel feed proxy — passes upstream MTProto-derived shape through.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/supply-chain/hormuz-tracker.js",
|
|
"category": "upstream-proxy",
|
|
"reason": "Transparent proxy to Hormuz strait AIS tracker feed. Shape is upstream's.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
|
|
{
|
|
"path": "api/health.js",
|
|
"category": "ops-admin",
|
|
"reason": "Liveness probe hit by uptime monitor and load balancer. Not a product API; plain-text OK response.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/seed-health.js",
|
|
"category": "ops-admin",
|
|
"reason": "Cron-triggered data-freshness check for feed seeds. Operator tool, not a user-facing API.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/version.js",
|
|
"category": "ops-admin",
|
|
"reason": "Build-version probe for the desktop auto-updater. Tiny plain-JSON operator plumbing.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/cache-purge.js",
|
|
"category": "ops-admin",
|
|
"reason": "Admin-gated cache invalidation endpoint. Internal operator action, not a product API.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/seed-contract-probe.ts",
|
|
"category": "ops-admin",
|
|
"reason": "Cron probe that verifies seed contract shapes against upstreams. Operator telemetry.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/invalidate-user-api-key-cache.ts",
|
|
"category": "ops-admin",
|
|
"reason": "Admin-gated cache bust for user API key lookups. Internal operator action.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/fwdstart.js",
|
|
"category": "ops-admin",
|
|
"reason": "Tauri desktop updater bootstrap — starts the sidecar forwarding flow. Operator plumbing, not JSON.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/notify.ts",
|
|
"category": "ops-admin",
|
|
"reason": "Outbound notification dispatch (Slack/Discord/email) driven by cron. Internal, not a typed user API.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
|
|
{
|
|
"path": "api/bootstrap.js",
|
|
"category": "internal-helper",
|
|
"reason": "Dashboard-internal config bundle assembled at request time. Exposing as a user-facing API would implicitly commit us to its shape; keep it deliberately unversioned and out of the API surface.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/geo.js",
|
|
"category": "internal-helper",
|
|
"reason": "Lightweight IP-to-geo lookup wrapping Vercel's request.geo. Dashboard-internal helper; not worth a service of its own.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/reverse-geocode.js",
|
|
"category": "internal-helper",
|
|
"reason": "Reverse-geocode helper used only by the map layer for label rendering. Wraps an upstream provider; shape tracks upstream, not a versioned product contract.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/internal/brief-why-matters.ts",
|
|
"category": "internal-helper",
|
|
"reason": "LLM-enrichment helper for the brief pipeline. Cron-triggered from Railway under RELAY_SHARED_SECRET bearer auth — never reached by dashboards, desktop, or partners. Request/response shape is an implementation detail of the brief renderer; modeling it as a generated service would publish internal cron plumbing as user-facing API surface. Same shape constraint as api/notify.ts (also cron-only).",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/data/city-coords.ts",
|
|
"category": "internal-helper",
|
|
"reason": "Static city-coordinates payload served from the deploy artifact. Returns a fixed reference table, not a queryable service.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
{
|
|
"path": "api/me/entitlement.ts",
|
|
"category": "internal-helper",
|
|
"reason": "Thin wrapper over the canonical server/_shared/premium-check.ts isCallerPremium helper — returns { isPro: boolean } for the /pro marketing bundle so it can swap upgrade CTAs for a dashboard link when the visitor is already a paying Pro user. Not a product data API: the authoritative gates live in panel-gating.ts (frontend), isCallerPremium (per-handler), and gateway.ts PREMIUM_RPC_PATHS (Bearer gate). This endpoint exists purely to let the separate pro-test bundle (no Convex client, no gateway client) ask the same question without reimplementing the two-signal check. Shape is unversioned by design — flip a boolean and ship.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": null
|
|
},
|
|
|
|
{
|
|
"path": "api/latest-brief.ts",
|
|
"category": "deferred",
|
|
"reason": "Returns the current user's latest brief. Auth-gated and Clerk-coupled; migrating requires modeling Brief in proto and auth context in handler. Deferred to brief/v1 service.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/brief/share-url.ts",
|
|
"category": "deferred",
|
|
"reason": "Creates a shareable public URL for a brief. Part of the brief/v1 service work.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/brief/public/[hash].ts",
|
|
"category": "deferred",
|
|
"reason": "Resolves a share hash to public-safe brief JSON. Part of the brief/v1 service work; dynamic path segment needs proto path-param modeling.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/brief/[userId]/[issueDate].ts",
|
|
"category": "deferred",
|
|
"reason": "Fetches a specific brief by user + issue date. Part of the brief/v1 service work.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/notification-channels.ts",
|
|
"category": "deferred",
|
|
"reason": "Lists / configures user notification channels. Auth-gated; migrating requires user/v1 or notifications/v1 service. Deferred until Clerk migration settles.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/user-prefs.ts",
|
|
"category": "deferred",
|
|
"reason": "Reads / writes user dashboard preferences. Auth-gated; part of user/v1 service work pending Clerk migration.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/create-checkout.ts",
|
|
"category": "deferred",
|
|
"reason": "Creates a Dodo Payments checkout session. Payments domain is still stabilizing (Clerk + Dodo integration); migrate once shape is frozen.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/customer-portal.ts",
|
|
"category": "deferred",
|
|
"reason": "Issues a Dodo customer-portal redirect URL. Paired with create-checkout.ts; migrate together.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/product-catalog.js",
|
|
"category": "deferred",
|
|
"reason": "Returns Dodo product catalog (pricing tiers). Migrate alongside the rest of the payments surface.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
{
|
|
"path": "api/referral/me.ts",
|
|
"category": "deferred",
|
|
"reason": "Returns the signed-in user's referral state. Auth-gated; part of user/v1 service.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "TBD"
|
|
},
|
|
|
|
{
|
|
"path": "api/scenario/v1/run.ts",
|
|
"category": "deferred",
|
|
"reason": "URL-compat alias for POST /api/scenario/v1/run-scenario. Thin gateway wrapper that rewrites the documented pre-#3207 v1 URL to the canonical sebuf RPC path. Not a new endpoint — preserves the partner-documented wire contract. Retires at the next v1→v2 break.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3282"
|
|
},
|
|
{
|
|
"path": "api/scenario/v1/status.ts",
|
|
"category": "deferred",
|
|
"reason": "URL-compat alias for GET /api/scenario/v1/get-scenario-status. See api/scenario/v1/run.ts for the same rationale.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3282"
|
|
},
|
|
{
|
|
"path": "api/scenario/v1/templates.ts",
|
|
"category": "deferred",
|
|
"reason": "URL-compat alias for GET /api/scenario/v1/list-scenario-templates. See api/scenario/v1/run.ts for the same rationale.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3282"
|
|
},
|
|
{
|
|
"path": "api/supply-chain/v1/country-products.ts",
|
|
"category": "deferred",
|
|
"reason": "URL-compat alias for GET /api/supply-chain/v1/get-country-products. See api/scenario/v1/run.ts for the same rationale.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3282"
|
|
},
|
|
{
|
|
"path": "api/supply-chain/v1/multi-sector-cost-shock.ts",
|
|
"category": "deferred",
|
|
"reason": "URL-compat alias for GET /api/supply-chain/v1/get-multi-sector-cost-shock. See api/scenario/v1/run.ts for the same rationale.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3282"
|
|
},
|
|
|
|
{
|
|
"path": "api/v2/shipping/webhooks/[subscriberId].ts",
|
|
"category": "migration-pending",
|
|
"reason": "Partner-facing path-parameter endpoint (GET status by subscriber id). Cannot migrate to sebuf yet — no path-param support in the annotation layer. Paired with the typed ShippingV2Service on the same base URL; tracked for eventual migration once sebuf path params are available.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3207"
|
|
},
|
|
{
|
|
"path": "api/v2/shipping/webhooks/[subscriberId]/[action].ts",
|
|
"category": "migration-pending",
|
|
"reason": "Partner-facing path-parameter endpoints (POST rotate-secret, POST reactivate). Cannot migrate to sebuf yet — no path-param support. Paired with the typed ShippingV2Service; tracked for eventual migration once sebuf path params are available.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3207"
|
|
},
|
|
{
|
|
"path": "api/chat-analyst.ts",
|
|
"category": "migration-pending",
|
|
"reason": "SSE streaming endpoint. Migrating to analyst/v1.ChatAnalyst (streaming RPC) in commit 9 of #3207. Blocked on sebuf#150 (TS-server SSE codegen).",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3207"
|
|
},
|
|
{
|
|
"path": "api/widget-agent.ts",
|
|
"category": "migration-pending",
|
|
"reason": "Migrating to analyst/v1.WidgetComplete in commit 9 of #3207. Blocked on sebuf#150.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3207"
|
|
},
|
|
{
|
|
"path": "api/skills/fetch-agentskills.ts",
|
|
"category": "migration-pending",
|
|
"reason": "Migrating to analyst/v1.ListAgentSkills in commit 9 of #3207. Blocked on sebuf#150.",
|
|
"owner": "@SebastienMelki",
|
|
"removal_issue": "#3207"
|
|
}
|
|
]
|
|
}
|