Skip to content

chore(api): sebuf migration follow-ups (post-#3242)#3287

Merged
SebastienMelki merged 12 commits intomainfrom
feat/sebuf-followups-3242
Apr 24, 2026
Merged

chore(api): sebuf migration follow-ups (post-#3242)#3287
SebastienMelki merged 12 commits intomainfrom
feat/sebuf-followups-3242

Conversation

@SebastienMelki
Copy link
Copy Markdown
Collaborator

Follow-up to #3242 (sebuf-migration-3207). Tackles the next-PR checklist @koala73 left in the merge approval, in priority order. Opening as draft so each commit lands visibly while the queue works through; will mark ready when the high-leverage items are in.

cc @koala73 — kicking off the next-PR checklist now. Will land each item as its own commit and ping you per-commit per the prior PR rhythm. Pushing back on any item that needs a different shape than the checklist suggests.

Plan (priority-ordered, from your merge note)

# Item Status
1 #3279ServiceClientPREMIUM_RPC_PATHS parity lint queued (highest leverage; mechanically prevents the HIGH(new) #1 class)
2 #3277fetchStaleFallback negative cache (NEG_TTL = 30_000 parity) queued
3 #3278enforce-rate-limit-policies.mjs: regex → import() queued
4 alertThreshold: 0 coercion on commit 8 (optional int32 alert_threshold) queued
5 Dead alertThreshold < 0 branch — remove folds into (4)
6 SSRF DNS-rebinding sanity-check on the delivery worker queued
7 api/internal/brief-why-matters.ts manifest band-aid ✅ landed in 74c5fe16

Post-merge: optional smoke on the 5 v1 alias URLs in prod to confirm Vercel file-based routing picks them up (your suggestion).

Out of scope here (stays deferred to its own PR)

Test plan

  • npm run lint:api-contract clean
  • npm run lint:rate-limit-policies clean
  • npm run lint:boundaries clean
  • (new) npm run lint:premium-fetch clean — to be added in checklist item (1)
  • npm run typecheck + typecheck:api clean
  • make generate zero-diff after each proto-touching commit
  • tests/edge-functions.test.mjs + relevant handler tests pass
  • Per-commit verification details posted as PR comments

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Apr 23, 2026 1:31pm

Request Review

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — checklist item 1 (#3279) landed in 5053b0ee. ✅ Highest-leverage one is in.

What

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's path is in 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)

Three latent HIGH(new) #1-class bugs the lint surfaced — all fixed

File Class Premium method called Old fetch Effect on browser pros
src/services/correlation-engine/engine.ts IntelligenceServiceClient deductSituation (no option — globalThis.fetch) LLM-assessment overlay on convergence cards never landed
src/services/economic/index.ts EconomicServiceClient getNationalDebt globalThis.fetch National-debt panel rendered empty
src/services/sanctions-pressure.ts SanctionsServiceClient listSanctionsPressure globalThis.fetch Sanctions-pressure panel rendered empty

All three swap to premiumFetch (single shared client; mirrors supply-chain/index.ts — premiumFetch no-ops safely on public methods so the other methods on EconomicServiceClient keep working unchanged).

Verification

  • lint:premium-fetch clean: 34 ServiceClient classes, 28 premium paths, 466 src/ files
  • Negative test: revert any of the three back to globalThis.fetch → exit 1 with file:line + called-premium-method names
  • typecheck + typecheck:api clean
  • lint:api-contract / lint:rate-limit-policies / lint:boundaries clean
  • tests/sanctions-pressure.test.mjs + tests/premium-fetch.test.mts 16/16 pass
  • Pre-push gauntlet green on push

Caveat (same as before)

I haven't manually e2e-tested the three fixed panels in a signed-in browser session — that needs a Clerk pro session I can't drive from terminal. Adding the same checkbox to your pre-merge test plan as last round.

Next up

#3277fetchStaleFallback negative cache (NEG_TTL = 30_000 parity on list-military-flights). Starting on it now.

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — checklist item 2 (#3277) landed in 59726c0d. ✅

Module-scoped staleNegUntil timestamp set whenever fetchStaleFallback returns null (key missing, parse fail, empty array after staleToProto filter, or thrown error). Checked at the entry of fetchStaleFallback before the Redis getRawJson(REDIS_STALE_KEY) call. Per-isolate state on Vercel Edge — each warm isolate gets its own 30s suppression window. Mirrors the legacy /api/military-flights NEG_TTL = 30_000 behavior.

Test seam: _resetStaleNegativeCacheForTests() exposed for unit tests so they can drive the suppression window without sleeping.

New test in tests/redis-caching.test.mjs pins the three-state contract:

  1. Stale-empty → reads Redis stale key once, arms negative cache.
  2. Within 30s window → does NOT re-read stale key.
  3. After test-only reset → re-reads stale key.

18/18 redis-caching tests pass, typecheck:api clean, lint:premium-fetch clean (still 0 violations).

Next up: #3278enforce-rate-limit-policies.mjs regex → import().

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — checklist item 3 (#3278) landed in 4e79d029. ✅

scripts/enforce-rate-limit-policies.mjs now import()s ENDPOINT_RATE_POLICIES directly from server/_shared/rate-limit.ts (newly exported). Same TS module the gateway uses at runtime → no source-of-truth drift possible.

Runs via tsx (already a dev dep, used by test:data) so the .mjs shebang can resolve a .ts import. npm run lint:rate-limit-policies script flipped to tsx scripts/.... Pre-push wraps it via the npm script so no hook change.

Verified:

  • Clean: 6 policies / 182 gateway routes.
  • Negative test (rename a key back to the original /api/sanctions/v1/lookup-entity typo): exit 1 with the same incident-attributed remedy.
  • Reformat test: split a single-line entry across multiple lines (the failure mode the issue described) → still passes. Object property reads can't be defeated by formatting.

Next up: checklist items 4+5 — alertThreshold: 0 coercion + dead < 0 branch on shipping/v2 webhook proto.

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — checklist items 4+5 landed in 0c384a59. ✅

Proto change

alert_threshold flipped to optional int32 so the handler can distinguish "partner explicitly sent 0 (deliver every disruption)" from "partner omitted the field (apply default 50)". buf.validate int32.gte = 0, int32.lte = 100 retained — applies when the field is set.

Handler change

Coercion req.alertThreshold > 0 ? req.alertThreshold : 50req.alertThreshold ?? 50. Dead < 0 || > 100 runtime branch deleted (proto/wire validation already enforces the range — that branch was unreachable after the previous > 0 coercion anyway).

Wire contract diff (partner-facing)

  • Omit field: same → 50.
  • Send 1..100: same → that value.
  • Send 0: previously 50 (silent intent-drop), now 0 — partner opts in to "every disruption" as documented in the proto comment.

Codegen

Scoped buf generate --path worldmonitor/shipping/v2 to avoid the toolchain-drift @ts-nocheck loss across unrelated files (per Learning #3 from PR #3242). Re-applied @ts-nocheck on the two regenerated files by hand.

Tests

  • alertThreshold 0 coerces to 50 flipped to alertThreshold 0 preserved (deliver every alert).
  • New: alertThreshold omitted (undefined) applies legacy default 50.
  • Dropped the rejects > 100 with ValidationError test — the redundant runtime range check is gone, and proto/wire validation isn't reachable from a direct handler invocation. Wire path covered by the sebuf gateway integration. Comment in the test file documents why.

18/18 shipping-v2-handler tests pass; typecheck + typecheck:api + all 4 custom lints clean.

Next up

Checklist item 6 — SSRF DNS-rebinding sanity-check on the delivery worker. Then 7 (api/internal/brief-why-matters.ts manifest) is already in the seed commit 74c5fe16.

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — checklist item 6 (SSRF DNS-rebinding sanity-check) — ef4abdf5. ✅ + ⚠️

Finding

No webhook delivery worker for shipping/v2 exists in this repo. I grepped the entire tree (excluding generated/dist/public/node_modules):

  • Only readers of webhook:sub:* records: the registration / inspection / rotate-secret handlers themselves.
  • No code reads them and POSTs to the stored callbackUrl.

The webhook-shared.ts comment delegated DNS-rebinding mitigation to "the delivery worker" — that worker either lives in the Railway backend (separate repo, not auditable from here) or hasn't been built yet.

What I did

  1. Refreshed the webhook-shared.ts comment to spell out the four-step contract the delivery worker MUST follow (re-validate URL → dns.lookup → re-check resolved IP → fetch resolved IP + Host header preserved). The previous one-liner ("the delivery worker must re-resolve") was too easy to miss.

  2. Filed shipping/v2: implement webhook delivery worker with DNS-rebinding guard #3288 — full acceptance criteria for the delivery worker including the DNS-rebinding contract. Action moves to wherever it actually lives.

  3. Did NOT touch docs/api-shipping-v2.mdx:170 — that doc claims the delivery worker honors the contract today. If Railway has it and it does → claim is accurate. If not → claim is misleading. I can't verify from here. Your call: confirm Railway side, then either close shipping/v2: implement webhook delivery worker with DNS-rebinding guard #3288 or scope down the doc claim. Flagging so it's not assumed-resolved by silence.

Caveat

I deliberately did NOT build the delivery worker / safeFetchWebhook helper here. That's:

  • Out of scope for the followup PR (would be invented infra, not an audit fix).
  • Wrong runtime — Vercel Edge can't dns.lookup, this is a Node-runtime concern.

When the delivery worker lands (or already exists in Railway), it MUST import from webhook-shared.ts and apply the four steps. The strengthened comment makes the contract impossible to miss for whoever picks this up.

Status of the full checklist

# Item Status Commit
1 #3279 PREMIUM_RPC_PATHS parity lint 5053b0ee
2 #3277 fetchStaleFallback NEG_TTL 59726c0d
3 #3278 enforce-rate-limit-policies regex → import() 4e79d029
4 alertThreshold: 0 coercion 0c384a59
5 dead < 0 branch 0c384a59 (folded into 4)
6 SSRF DNS-rebinding sanity-check ⚠️ Audited — no worker in repo, #3288 filed, comment refreshed ef4abdf5
7 brief-why-matters manifest band-aid 74c5fe16

Plus the optional post-merge prod smoke on the 5 alias URLs that you mentioned — I'll do that after this lands.

Promoting from draft → ready when you get a chance to look. Three real bugs found during the work (PREMIUM_RPC_PATHS lint surfaced two, plus the alertThreshold: 0 silent intent-drop), one tracking issue filed (#3288), one comment-refresh that documents the delivery contract.

@SebastienMelki SebastienMelki marked this pull request as ready for review April 22, 2026 07:31
@SebastienMelki SebastienMelki requested a review from koala73 April 22, 2026 07:32
@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — bonus: prod smoke on the 5 #3242 alias URLs done. ✅

Acceptance from your merge note was "old → 200 via rewrite, not 404." All five alias URLs route in prod — none 404.

URL HTTP
GET /api/scenario/v1/templates (alias) 401 (gateway auth)
GET /api/scenario/v1/list-scenario-templates (canonical) 401 (gateway auth)
POST /api/scenario/v1/run (alias) 401
GET /api/scenario/v1/status?jobId=... (alias) 401
GET /api/supply-chain/v1/country-products?iso2=US (alias) 401
GET /api/supply-chain/v1/multi-sector-cost-shock?iso2=US&... (alias) 401

All 401 with body {"error":"API key required"} — the gateway auth response, which means routing succeeded.

Caveat on the smoke being degenerate: /api/scenario/v1/<bogus> and /api/supply-chain/v1/<bogus> also return 401, because each domain has a [rpc].ts catch-all that gates auth before route validation. So the smoke can't distinguish between "alias file routes" vs "rpc catchall catches it and 401s anyway." Either way the partner-visible behaviour is the same (no 404), which is what the acceptance asked for. The control case — /api/totally-fake-domain/v1/zzz (no domain at all) → 404 — confirms 404 is the live behaviour for genuinely unrouted paths, so the 401s above are real.

For full PRO end-to-end I'd need a signed-in browser session or a real WORLDMONITOR_API_KEY — neither is available from this terminal. Leaving that to your manual pre-merge plan if needed.

PR is now ready for review.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR works through the post-#3242 checklist: it adds the enforce-premium-fetch.mjs lint (AST + dynamic-import based) to catch ServiceClientPREMIUM_RPC_PATHS parity drift, fixes three service clients that were silently 401-ing pro users (CorrelationEngine, EconomicServiceClient, SanctionsServiceClient), migrates enforce-rate-limit-policies.mjs from fragile regex to proper import() + YAML parsing, adds a 30 s negative-cache for the stale Redis fallback in military flights, and fixes the alertThreshold = 0 coercion bug via optional int32 in the proto.

  • The new NEG_TTL test at tests/redis-caching.test.mjs:773 passes the outer-scope request fixture as a second argument rather than the locally constructed ctx URL, so the call site may not exercise the URL path it appears to set up.
  • enforce-premium-fetch.mjs checks fetchText === 'premiumFetch' via raw AST source text — aliased imports (import { premiumFetch as pf }) will produce a false violation; worth documenting or resolving before the lint has wide adoption.

Confidence Score: 4/5

Safe to merge after verifying the NEG_TTL test call site — the rest of the checklist items are solid.

Prior P1/P2 findings from the #3242 review are fully addressed. One new P1 concern: the NEG_TTL test passes the outer-scope request fixture rather than the URL it constructs in ctx, which means the test may not exercise what it claims. The SSRF gap in the delivery worker is a known, tracked deferral (not introduced here). The premiumFetch alias limitation in the lint is P2. Score is 4 rather than 5 because the test call-site issue warrants a quick check before merging.

tests/redis-caching.test.mjs (NEG_TTL test call site — confirm request outer fixture is appropriate), server/worldmonitor/shipping/v2/webhook-shared.ts (delivery-worker SSRF guard deferred to #3288)

Security Review

  • SSRF (partial)server/worldmonitor/shipping/v2/webhook-shared.ts: static registration-time URL blocking is in place (isBlockedCallbackUrl), but no delivery worker exists yet. The four-step DNS-rebinding defence described in the new comment (re-resolve hostname, check private ranges, fetch to resolved IP) is not enforced anywhere in the codebase — tracked in shipping/v2: implement webhook delivery worker with DNS-rebinding guard #3288. Until a delivery worker that imports and applies these steps is landed, a DNS-rebinding attack after registration time remains possible.

Important Files Changed

Filename Overview
scripts/enforce-premium-fetch.mjs New lint script using TypeScript AST + dynamic import to enforce { fetch: premiumFetch } on all ServiceClient instantiations that call premium-gated paths; correctness depends on exact source-text match for premiumFetch identifier which won't recognise aliased imports.
scripts/enforce-rate-limit-policies.mjs Migrated from regex-parse to dynamic import() + proper YAML parsing; fixes the two silent-drift classes called out in the previous review and now runs under tsx for TypeScript transparency.
server/worldmonitor/military/v1/list-military-flights.ts Adds 30 s module-level negative cache (staleNegUntil) for the stale Redis key after empty/failed reads, with a test seam (_resetStaleNegativeCacheForTests); logic is sound, though the new test may not be passing the right request object.
server/worldmonitor/shipping/v2/register-webhook.ts Correct fix for alertThreshold 0/undefined ambiguity — switches from > 0 ? : 50 to ?? 50 after making the proto field optional int32; range guard kept for belt-and-suspenders.
server/worldmonitor/shipping/v2/webhook-shared.ts DNS-rebinding SSRF guard upgraded from a brief note to a four-step procedure, but no delivery worker exists yet — the guard is purely documentation until #3288 lands.
tests/redis-caching.test.mjs New NEG_TTL test verifies stale-key suppression correctly but passes the outer-scope request fixture rather than the locally constructed ctx URL, so the call site may not test the intended URL path.
tests/shipping-v2-handler.test.mjs Added tests for explicit alertThreshold 0 preservation, negative alertThreshold rejection, and omitted alertThreshold defaulting to 50 — all exercising the ?? 50 fix correctly.
.github/workflows/lint-code.yml Added lint:rate-limit-policies and lint:premium-fetch to CI, resolving the prior review concern about pre-push-only coverage.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[enforce-premium-fetch.mjs] -->|dynamic import| B[src/shared/premium-paths.ts\nPREMIUM_RPC_PATHS Set]
    A -->|TypeScript AST walk| C[src/generated/client/\nServiceClient classes]
    A -->|AST scan| D[src/ files\nexcl. generated]
    D -->|new XServiceClient call| E{fetch option\n=== 'premiumFetch'?}
    E -->|Yes| F[✓ Pass]
    E -->|No + premium method called| G[✗ Fail lint]

    H[enforce-rate-limit-policies.mjs] -->|tsx dynamic import| I[server/_shared/rate-limit.ts\nENDPOINT_RATE_POLICIES]
    H -->|yaml.parse| J[docs/api/*.openapi.yaml\npath keys]
    I --> K{Policy key\nin OpenAPI paths?}
    J --> K
    K -->|Yes| L[✓ Pass]
    K -->|No| M[✗ Fail lint]

    N[RegisterWebhook handler] -->|req.alertThreshold ?? 50| O{undefined?}
    O -->|Yes - field omitted| P[default = 50]
    O -->|No - explicit value| Q{0..100 range check}
    Q -->|Valid| R[Store alertThreshold]
    Q -->|Invalid| S[ValidationError]
Loading

Reviews (3): Last reviewed commit: "chore(codegen): regenerate unified OpenA..." | Re-trigger Greptile

Comment thread .github/workflows/lint-code.yml
Comment thread scripts/enforce-premium-fetch.mjs Outdated
Comment on lines +47 to +57
function loadPremiumPaths() {
const src = readFileSync(PREMIUM_PATHS_SRC, 'utf8');
const re = /'(\/api\/[^']+)'/g;
const paths = new Set();
let m;
while ((m = re.exec(src)) !== null) paths.add(m[1]);
if (paths.size === 0) {
throw new Error(`No premium paths parsed from ${PREMIUM_PATHS_SRC}`);
}
return paths;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regex-parsing premium-paths.ts vs. live import

loadPremiumPaths() regex-parses src/shared/premium-paths.ts with /'(\/api\/[^']+)'/g — the exact fragility that motivated refactoring enforce-rate-limit-policies.mjs from regex to import(). If premium-paths.ts is reformatted to use double-quoted strings, array spread, or a const assigned from a helper, paths will be silently dropped from the set and the lint will stop catching violations for those endpoints. The two lint scripts are now inconsistent in their resilience to source formatting changes.

Comment on lines +59 to +80
function loadClientClassMap() {
const map = new Map();
walk(GEN_CLIENT_DIR, (file) => {
if (basename(file) !== 'service_client.ts') return;
const src = readFileSync(file, 'utf8');
const classRe = /export class (\w+ServiceClient)\s*\{/g;
const classMatch = classRe.exec(src);
if (!classMatch) return;
const className = classMatch[1];
const methods = new Map();
const methodRe = /async (\w+)\s*\([^)]*\)\s*:\s*Promise<[^>]+>\s*\{\s*let path = "([^"]+)"/g;
let mm;
while ((mm = methodRe.exec(src)) !== null) {
methods.set(mm[1], mm[2]);
}
map.set(className, methods);
});
if (map.size === 0) {
throw new Error(`No ServiceClient classes parsed from ${GEN_CLIENT_DIR}`);
}
return map;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 loadClientClassMap stops at first class per file

classRe.exec(src) is called once and not looped — so only the first export class ...ServiceClient per file is registered. Any file that defines a second client class will silently miss it. The generated files currently each contain exactly one class, so this doesn't cause a failure today, but it's a latent gap if the codegen template changes.

SebastienMelki added a commit that referenced this pull request Apr 22, 2026
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.
SebastienMelki added a commit that referenced this pull request Apr 22, 2026
…le 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.
@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — greptile P1 addressed in 4c468f04. ✅

lint:rate-limit-policies was in .husky/pre-push but not in .github/workflows/lint-code.yml, so fork PRs and --no-verify pushes skipped the exact drift check #3278 was created to enforce. Added the step right after lint:api-contract so it runs in the same CI context.

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — greptile P2s on enforce-premium-fetch.mjs addressed in 857e1709. ✅

Two fragilities collapsed

(a) Regex → import(). loadPremiumPaths was regex-scraping src/shared/premium-paths.ts with /'(\/api\/[^']+)'/g — same silent-drift class 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 the runtime kept using them. Shebang flipped to #!/usr/bin/env -S npx tsx and PREMIUM_RPC_PATHS is now dynamic-imported directly. Package script flipped nodetsx so npm-script and direct execution share a single path.

(b) classRe.exec once → matchAll. loadClientClassMap called classRe.exec(src) a single time, so any file with more than one export class …ServiceClient would silently drop all but the first. Current codegen is one-class-per-file (hence latent), but a template change would ship un-linted classes. Now collects every class-open with matchAll, slices each body against the next class's start, and scans methods per-body so method-to-class binding stays correct.

Verification

  • lint:premium-fetch clean — 34 classes / 28 premium paths / 466 files, identical to the pre-refactor counts. No coverage regression.
  • Negative test: revert src/services/economic/index.ts new EconomicServiceClient(...) back to globalThis.fetch → exit 1 with src/services/economic/index.ts:64:7, bound var client, premium method getNationalDebt. Restore → clean.
  • lint:rate-limit-policies still clean (6 policies / 182 routes).

All three greptile findings now closed; the two lint scripts are now symmetric in their resilience to source reformatting. Ready for another look.

@koala73
Copy link
Copy Markdown
Owner

koala73 commented Apr 23, 2026

Review — no blockers

Approve once you've decided on the nits. The three ServiceClient fixes (correlation-engine / economic / sanctions-pressure) alone justify the merge — all three were silently 401-ing for signed-in browser pros on premium methods.

Proposed improvements (all optional, none blocking)

1. alertThreshold — add a handler-side range guard (server/worldmonitor/shipping/v2/register-webhook.ts)

Range enforcement now lives entirely at the wire layer via buf.validate. If anything ever calls registerWebhook directly — internal job, test harness, future transport — alertThreshold: 9999 is stored as-is. Costs nothing to preserve the invariant at the handler boundary:

const alertThreshold = req.alertThreshold ?? 50;
if (alertThreshold < 0 || alertThreshold > 100) {
  throw new ValidationError([{ field: 'alertThreshold', description: 'must be 0..100' }]);
}

2. loadClientClassMap method regex is template-fragile (scripts/enforce-premium-fetch.mjs:84)

async (\w+)\s*\([^)]*\)\s*:\s*Promise<[^>]+>\s*\{\s*let path = \"([^\"]+)\" assumes (a) no nested ) in args, (b) no nested > in return, (c) let path = \"...\" is the first statement. All hold today; any template shift silently drops methods and the lint passes clean with missing coverage — same silent-drift class this PR just closed on the premium-paths side. Safer: reuse the TS AST walk checkFile already imports and look for MethodDeclaration whose first VariableStatement is let path = ....

3. OpenAPI parser in enforce-rate-limit-policies.mjs still regex-based (line 40)

/^\s{4}(\\/api\\/[^\\s:]+):/gm hard-codes 4-space YAML indent. You fixed the input side (ENDPOINT_RATE_POLICIES) via import(); the output side (OpenAPI) still silently breaks on indent/formatter changes. A yaml-parser pass closes the loop.

4. webhook-shared.ts breadcrumb doesn't resolve (line ~25)

Tracked separately — see the linked issue in docs/api-shipping-v2.mdx.

The MDX isn't in the diff. The commit message cites #3288 — put the issue number in the comment directly (or update the MDX in this PR) so a future reader can follow it.

5. findCalls is scope-blind within a file (scripts/enforce-premium-fetch.mjs:166)

The walker scans the full AST for <varName>.<method>() without respecting scope. Two constructions in different functions that share a var name merge their called-method sets. No current code does this; worth a comment noting the limitation until scope-aware binding is justified.

6. Shebang #!/usr/bin/env -S npx tsx (minor)

env -S needs macOS (BSD env ≥ 14.4) / coreutils ≥ 8.30. CI + husky both go through npm run (direct tsx), so this only affects direct execution of the .mjs. Noting for completeness.

Pre-merge checks I'd want

  • npm run lint:premium-fetch clean on main after rebase
  • make generate zero-diff after the scoped buf-generate commit (confirm no other proto paths changed)
  • Post-merge smoke: hit national-debt / sanctions-pressure / correlation-engine LLM overlay as a signed-in pro without a WORLDMONITOR_API_KEY — all three should now populate.

@SebastienMelki SebastienMelki force-pushed the feat/sebuf-followups-3242 branch from 857e170 to 1926fc5 Compare April 23, 2026 13:19
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
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.
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
…le 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.
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
… 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>
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
…#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>
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
…#3287)

Input side (ENDPOINT_RATE_POLICIES) was flipped to live `import()` in
4e79d02. 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>
SebastienMelki added a commit that referenced this pull request Apr 23, 2026
Pre-push on main now runs `make generate` and fails the push if there's
drift. This branch rebased on main and the hook surfaced legit codegen
improvements main hasn't regenerated: enum query params default to
`*_UNSPECIFIED` sentinel values (not `""`) and `repeated` query fields
iterate properly with `params.append(...)` per value.

No proto definition changes — just regenerated output from the current
toolchain. No @ts-nocheck loss (verified). Scope covers every service
that has a RPC using an enum query param or a repeated primitive query
field, hence the breadth: aviation, climate, conflict, cyber, economic,
health, infrastructure, intelligence, market, military, positive_events,
resilience, scenario, shipping, supply_chain, trade, unrest.

Isolated from the #3287 nit fixes so the review diff for those stays
narrow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — all 5 addressable nits from your review landed. ✅

# Item Status Commit
1 alertThreshold handler range guard 091e9ce5
2 loadClientClassMap method regex → TS AST 2738cf45 (same commit as 5)
3 enforce-rate-limit-policies OpenAPI regex → yaml parser 32626b2a
4 webhook-shared.ts breadcrumb → inline #3288 2738cf45
5 findCalls scope-blindness comment 2738cf45
6 env -S shebang portability Skipped — CI + husky both go through npm run → direct tsx, so only direct .mjs execution affected. Noted in-code if you'd prefer.

Highlights per nit

1. Handler-side guard restored. alertThreshold ?? 50 then < 0 || > 100 → ValidationError. Two new tests (rejects > 100, rejects < 0). 20/20 shipping-v2-handler tests pass.

2. Method regex → MethodDeclaration AST walk. loadClientClassMap now parses service_client.ts with the same typescript package already in use by checkFile, iterates class members, and reads the first let path = "..." as a StringLiteral. Tolerant to any shift in arg/return-type shape. Same 34 classes / 28 premium paths resolved.

3. OpenAPI regex → yaml parse. /^\s{4}(\/api\/[^\s:]+):/gm hard-coded 4-space indent; flipped to parseYaml(...).paths from the yaml package (already a dep). Route count went 182 → 189 — the yaml parser picked up paths that indentation-regex was silently missing, closing an accidental coverage gap.

4. Inlined #3288 — no more MDX bounce.

5. Scope-blindness comment — documents that the walker errs cautiously (merges called-method sets across functions sharing a var name) and keeps the walker simple until scope-aware binding is actually needed.

Codegen sync (separate commit — 1926fc5a)

Pre-push hook runs make generate and rejected the push: main has stale generated files vs current sebuf toolchain. No proto changes — just regenerated output: enum query params default to *_UNSPECIFIED sentinels (not ""), and repeated query fields iterate with params.append(...). No @ts-nocheck loss (verified). Scope covers every service with an enum-query-param or repeated-primitive-query-field RPC, hence the breadth (17 services).

Three test expectations updated in the same commit: tests/stock-backtest.test.mts, tests/stock-analysis-history.test.mts, tests/market-service-symbol-casing.test.mjs — previously asserted the comma-joined symbols=A,B form; now accept both forms so the tests survive a toolchain downgrade too.

Verification

  • typecheck + typecheck:api clean
  • lint:premium-fetch clean — 34 classes / 28 premium paths / 489 src/ files
  • lint:rate-limit-policies clean — 6 policies / 189 routes (was 182 — yaml parser picks up missed paths)
  • lint:api-contract clean — 90 api/ files / 56 manifest entries
  • lint:boundaries clean
  • npm run test:data6622/6622 pass
  • Negative tests: revert economic premiumFetch → globalThis.fetch → exits 1 with src/services/economic/index.ts:64:7, method getNationalDebt. Rename sanctions policy key → exits 1 with the same remedy.

Ready for another look.

SebastienMelki and others added 12 commits April 23, 2026 16:24
…nal-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>
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>
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>
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>
…ranch (#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>
…inding 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>
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.
…le 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.
… 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>
…#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>
…#3287)

Input side (ENDPOINT_RATE_POLICIES) was flipped to live `import()` in
4e79d02. 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>
… proto change

The shipping/v2 webhook alert_threshold field was flipped from `int32` to
`optional int32` with an expanded doc comment in f333946. 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>
@SebastienMelki SebastienMelki force-pushed the feat/sebuf-followups-3242 branch from 1926fc5 to dbe6e53 Compare April 23, 2026 13:28
@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

@koala73 — rebased on main now that #3341 landed. ✅

The earlier 1926fc5a codegen-sync commit dropped cleanly during rebase (git saw it as empty against the new main — exactly what we expected, since #3341 is a superset of what I was patching). 3 test-file conflicts resolved by taking main's version (narrower assertions, matches the pinned v0.11.1 toolchain).

One new commit (dbe6e53f): regenerated the unified docs/api/worldmonitor.openapi.yaml bundle so the alert_threshold proto doc comment (from commit f3339464, where I flipped to optional int32) surfaces in the bundle. 6-line documentation-only drift — no behaviour change.

Final branch state

11 commits — every one scoped to the PR intent, no toolchain noise:

Commit Intent
3440db11 api-manifest: brief-why-matters reason rewrite
c02f3833 lint: premium-fetch parity (closes #3279)
ab713819 fix: fetchStaleFallback NEG_TTL parity (closes #3277)
0071c125 refactor: rate-limit-policies regex → import() (closes #3278)
f3339464 fix: alertThreshold: 0 preserved
6cca3a69 docs: webhook DNS-rebinding contract + #3288
0b0861e8 ci: lint:rate-limit-policies (greptile P1)
411255e2 refactor: premium-fetch regex → import() (greptile P2)
1a97f323 fix: alertThreshold handler range guard (nit 1)
2bbcf3ff refactor: premium-fetch method-regex → TS AST (nits 2+4+5)
d3e27f98 refactor: rate-limit OpenAPI regex → yaml parser (nit 3)
dbe6e53f chore: regen bundle for alert_threshold doc comment

Verification (post-rebase)

  • typecheck + typecheck:api clean
  • All 4 custom lints clean (premium-fetch 34/28/489, rate-limit 6/189, api-contract 90/56, boundaries)
  • npm run test:data: 6622/6622
  • make generate zero-diff with v0.11.1 plugins

Ready.

@SebastienMelki
Copy link
Copy Markdown
Collaborator Author

Hi @greptile-apps please review again

@koala73
Copy link
Copy Markdown
Owner

koala73 commented Apr 24, 2026

@greptileai

@SebastienMelki SebastienMelki merged commit e68a714 into main Apr 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants