fix(docs): derive closed-value OpenAPI examples#4844
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Greptile SummaryThis PR fixes OpenAPI documentation by replacing placeholder example strings with real, validated values for closed-value API fields across IntelligenceService, ConsumerPricesService, AviationService, NewsService, and ScenarioService. The fix is generator-level:
Confidence Score: 4/5Safe to merge for documentation purposes; no runtime handler behavior changes. The generator logic has a few edge-case gaps but the test suite acts as a meaningful safety net at generation time. The change is entirely documentation-layer: generated OpenAPI spec files and the injector script that produces them. The three issues found are all in the generator logic — a latent scripts/openapi-inject-examples.mjs — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[openapi-inject-examples.mjs] --> B{overrideStringExample}
B -->|matched key| C[Curated override\ne.g. GDELT topic, region, basket, range]
B -->|no match| D{shouldUseDescriptionClosedValue?\nexampleSurface = parameter or request}
D -->|yes| E[descriptionClosedValueExample\nparse prose description]
E --> F{closed-value match?}
F -->|yes| G[firstClosedValue\nprefer default-annotated candidate]
F -->|no| H[heuristic fallback\nformat / key pattern / generic]
G --> I[constrainedString → example value]
H --> I
D -->|no| H
C --> I
I --> J[Per-service openapi.json / .yaml\nupdated examples]
J --> K[worldmonitor.openapi.yaml bundle]
K --> L[openapi-examples-contract.test.mjs\nvalidates all examples]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[openapi-inject-examples.mjs] --> B{overrideStringExample}
B -->|matched key| C[Curated override\ne.g. GDELT topic, region, basket, range]
B -->|no match| D{shouldUseDescriptionClosedValue?\nexampleSurface = parameter or request}
D -->|yes| E[descriptionClosedValueExample\nparse prose description]
E --> F{closed-value match?}
F -->|yes| G[firstClosedValue\nprefer default-annotated candidate]
F -->|no| H[heuristic fallback\nformat / key pattern / generic]
G --> I[constrainedString → example value]
H --> I
D -->|no| H
C --> I
I --> J[Per-service openapi.json / .yaml\nupdated examples]
J --> K[worldmonitor.openapi.yaml bundle]
K --> L[openapi-examples-contract.test.mjs\nvalidates all examples]
Reviews (1): Last reviewed commit: "fix(docs): derive closed-value OpenAPI e..." | Re-trigger Greptile |
| function firstClosedValue(candidates) { | ||
| for (const raw of candidates) { | ||
| const value = normalizeDescriptionValue(raw); | ||
| if (/\bdefault\b/i.test(String(raw)) && candidateLooksLikeClosedValue(value)) return value; | ||
| } | ||
| for (const raw of candidates) { | ||
| const value = normalizeDescriptionValue(raw); | ||
| if (!value || value.toLowerCase() === 'default') continue; | ||
| if (candidateLooksLikeClosedValue(value)) return value; | ||
| } | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
firstClosedValue first pass may return the literal "default" as an example
The first loop checks \bdefault\b in the raw candidate and returns the normalized value. When raw === "default", normalizing it gives "default" and candidateLooksLikeClosedValue("default") is true, so the function returns "default" as the example string — a value that no API handler accepts. The second pass correctly guards against this with value.toLowerCase() === 'default', but it only runs when the first pass doesn't short-circuit. Any description parsed as ["economy", "default"] would produce "default" as the chosen example instead of "economy".
The fix is to add the same guard in the first pass: if (/\bdefault\b/i.test(String(raw)) && value.toLowerCase() !== 'default' && candidateLooksLikeClosedValue(value)) return value;. The assertClosedValueParamExamples test would catch this at generation time, but the root cause is still worth addressing.
| const SCENARIO_RESULT_TEMPLATE_NAME = | ||
| SCENARIO_RESULT_AFFECTED_CHOKEPOINT_IDS.length > 0 | ||
| ? SCENARIO_RESULT_AFFECTED_CHOKEPOINT_IDS.join('+') | ||
| : 'tariff_shock'; |
There was a problem hiding this comment.
'tariff_shock' fallback is permanently unreachable dead code
SCENARIO_RESULT_AFFECTED_CHOKEPOINT_IDS is guaranteed to be non-empty: its own IIFE returns ids.length > 0 ? ids : [CHOKEPOINT_EXAMPLE_ID] (line 93), so it always contains at least one element. Therefore the condition SCENARIO_RESULT_AFFECTED_CHOKEPOINT_IDS.length > 0 is always true, and SCENARIO_RESULT_TEMPLATE_NAME is never 'tariff_shock'. The mirror in the test (assertScenarioStatusPollExample, line 444) has the identical dead branch. If the chosen scenario template ever has no affectedChokepointIds in source, the fallback would silently produce the chokepoint example ID (e.g., 'suez') as the template name rather than the intended 'tariff_shock'.
| const CONSUMER_PRICE_BASKET_EXAMPLE_ID = (() => { | ||
| const src = readRepoText('src/services/consumer-prices/index.ts') || readRepoText('scripts/seed-consumer-prices.mjs'); | ||
| const match = src.match(/\b(?:DEFAULT_BASKET|BASKET)\s*=\s*['"`]([^'"`]+)['"`]/); | ||
| return match?.[1] ?? 'essentials-ae'; | ||
| })(); |
There was a problem hiding this comment.
Injector and test read basket ID from different source files with different regex patterns
The injector reads src/services/consumer-prices/index.ts (fallback: scripts/seed-consumer-prices.mjs) using the pattern (?:DEFAULT_BASKET|BASKET)\s*=, while the test builds CURATED.consumerBaskets from server/worldmonitor/consumer-prices/v1/get-consumer-price-basket-series.ts with the narrower DEFAULT_BASKET\s*= pattern. If the constant in the injector's primary source file is named BASKET (not DEFAULT_BASKET) or is set to a different value than the handler constant, the injector will emit a basket slug that the test's accepted-set doesn't contain — the test would fail without revealing the source-of-truth mismatch. Both lookups should use the same source file and same pattern.
4dc497d to
3db2580
Compare
…son — back under the ~1MB scanner cap (#4852) (#4853) Today's per-op doc injections (#4843 429 blocks, #4841 idempotency, #4844 examples) grew the minified public/openapi.json 752KB -> 1.04MB, crossing the ~1MB body cap orank's function-calling validator imposes: the Access check function-calling-compat flipped PASS -> 'API spec found but couldn't validate function calling compatibility' (score 91 -> 90). elevenlabs (1.8MB) and openrouter (1.5MB) hit the identical error path; sub-800KB specs get computed verdicts. Hoist the byte-identical non-2xx response objects (429 x193, default x193, 401 x186, 400, 403, 409, 422) into components.responses $refs when emitting the JSON artifact only - semantically identical OpenAPI 3.1, validated with @seriousme/openapi-schema-validator. 2xx responses stay inline (orank credits only the inline responses['200'] schema). YAML sources keep their inline copies for Mintlify and the contract tests. Result: 812,276 bytes (10 shared components, 975 refs), deterministic rebuild, lossless roundtrip proven by test. A 950KB budget guard test stops the next injector from silently re-crossing the cap. Claude-Session: https://claude.ai/code/session_013AWjvnpnJTE6G2PaYk9iT1
Summary
OpenAPI examples now use real documented values for closed-value API fields instead of placeholder strings that clients could not copy successfully. The examples injector now derives curated values at generation time for the confirmed clusters in #4827, including GDELT topics, regional intelligence IDs, consumer-price ranges and baskets, Google Flights filters, summarize-article mode, scenario polling, and request/response echo fields that must agree.
This is a generator-level fix in
scripts/openapi-inject-examples.mjs, so regenerated per-service specs and the unified OpenAPI bundle stay consistent instead of patching YAML/JSON by hand. The async scenario status poll example now represents a plausible completed job using the worker template key and lifecycle-compatible result shape.Closes #4827.
Validation
node scripts/openapi-inject-examples.mjsnpm run build:openapinode --test tests/openapi-examples-contract.test.mjsnode --test tests/openapi-async-jobs-contract.test.mjsnode scripts/openapi-inject-examples.mjs --checkgit diff --checknpm run typecheck:apitypecheck, APItypecheck:api, Convex type check, CJS syntax, Unicode safety, boundary/safe-html/rate-limit/premium-fetch guards, edge bundle check, changed OpenAPI examples test, proto freshness, version syncPost-Deploy Monitoring & Validation
No additional operational monitoring required. This changes generated API documentation examples and the docs injector/test coverage only; no runtime handler behavior changes.