fix(agent-readiness): $ref-dedupe error responses in /openapi.json — back under orank's ~1MB function-calling cap#4853
Conversation
…son — back under the ~1MB scanner cap (#4852) 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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR deduplicates repeated non-2xx response bodies in
Confidence Score: 4/5The dedup logic and build-script wiring are correct; the only issue is in the test file, which imports an undeclared dependency that is absent from node_modules. The core dedup module and the build-script integration are well-written and safe. The test suite covers the important invariants (lossless roundtrip, 2xx-inline, size budget, wiring). The one concrete defect is in the test file: it imports tests/openapi-json-dedup.test.mjs — undeclared js-yaml import needs to be replaced with yaml Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[worldmonitor.openapi.yaml] -->|parseYaml| B[spec object]
B --> C{dedupeErrorResponses}
C -->|Pass 1| D[Collect non-2xx inline responses\nCompute canonical key per body\nCount occurrences per key]
D -->|Pass 2| E{count >= 2?}
E -->|Yes| F[Hoist to components.responses\nAssign deterministic name\nCollision-guard vs pre-existing]
E -->|No| G[Leave inline]
F --> H[Replace all matching sites\nwith dollar-ref]
G --> H
H --> I[JSON.stringify spec]
I -->|~812 KB| J[public/openapi.json]
J --> K{orank scanner}
K -->|size under ~1 MB cap| L[function-calling-compat: 2/2]
%%{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[worldmonitor.openapi.yaml] -->|parseYaml| B[spec object]
B --> C{dedupeErrorResponses}
C -->|Pass 1| D[Collect non-2xx inline responses\nCompute canonical key per body\nCount occurrences per key]
D -->|Pass 2| E{count >= 2?}
E -->|Yes| F[Hoist to components.responses\nAssign deterministic name\nCollision-guard vs pre-existing]
E -->|No| G[Leave inline]
F --> H[Replace all matching sites\nwith dollar-ref]
G --> H
H --> I[JSON.stringify spec]
I -->|~812 KB| J[public/openapi.json]
J --> K{orank scanner}
K -->|size under ~1 MB cap| L[function-calling-compat: 2/2]
Reviews (1): Last reviewed commit: "fix(agent-readiness): $ref-dedupe error ..." | Re-trigger Greptile |
| import { readFileSync } from 'node:fs'; | ||
| import { dirname, resolve } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { load as loadYaml } from 'js-yaml'; |
There was a problem hiding this comment.
js-yaml is not declared in the root package.json and is absent from node_modules. The test will fail with ERR_MODULE_NOT_FOUND in any clean install or CI environment where it is not accidentally hoisted. The yaml package is already declared and is the same parser used by the build script — switching to it also makes the size-budget measurement consistent with what build-openapi-json.mjs actually produces.
| import { load as loadYaml } from 'js-yaml'; | |
| import { parse as loadYaml } from 'yaml'; |
…support discovery + status advertisement fixes (#4867) Five external agent-journey runs (signup, pricing, developer-auth, support, integration intents) showed a clean split: intents answerable via the Link-header → .well-known → auth.md chain scored 90-100%, while pricing, signup, and support — discoverable only via llms.txt or nowhere — forced agents into slug-guessing, UA 403s, and misleading-200 SPA shells. Closes #4850 (signup journey): - llms.txt/llms-full.txt + auth.md: document the descriptive User-Agent policy (edge WAF challenges curl/*, python-requests/*, short UAs — and masks 404s as 403s for those UAs) - docs/accounts.mdx (+ nav): agent-facing account-creation guide — free tier needs no account; Clerk modal flow; MCP OAuth path for agents - vercel.json: 301 /blog/posts → /blog (posts live at /blog/posts/<slug>/, the parent had no index) Closes #4854 (pricing journey): - .well-known/api-catalog: RFC 9727 service-meta advertising pricing.md, the live product-catalog JSON, and support.md on the api context object - middleware.ts: /api/product-catalog joins PUBLIC_API_PATHS — the keyless read-only pricing catalog was UA-403'd for the exact agents it serves; robots.txt gets the matching Allow carve-out (llms.txt precedent) - docs/pricing.mdx (+ nav) with a drift guard test extracting prices from convex/config/productCatalog.ts source text (no generator import) - docs/openapi/CommerceService.openapi.yaml wired into the api-reference nav — placed OUTSIDE docs/api/ (that directory is generated: proto-check regen diff + injector scripts would clobber a hand-authored spec); kept out of the root openapi.json to respect the #4853 ~1MB size budget - docs.json redirects for the guessed slugs: plans/tiers → pricing, rate-limit(s) → usage-rate-limits, mcp-api-key → usage-auth - pricing.md: Limits & Overage section (hard 429 + Retry-After, no silent overage billing) + live JSON catalog pointer + UA note Closes #4856 (status advertisement, stale since #4715): - api-catalog status href + 3x vercel.json Link rel="status" now point at the keyless /api/health?compact=1 form - /api/health 401 carries WWW-Authenticate with RFC 9728 resource_metadata (RFC 7235 §3.1 made it mandatory; it was absent) and a body hint naming the public compact form - deploy-config guards: every advertised status URL must be the keyless compact form; service-meta entries pinned Closes #4857 (support journey): - public/support.md (root, sitemap-listed) + docs/support.mdx (+ nav): consolidated channels (support@/enterprise@, GitHub issues, Discord, status page, Turnstile form marked human-only) + explicit no-SLA statement per tier - vercel.json: /contact, /support, /help 301 → /docs/support (they served the misleading-200 dashboard SPA shell via the catch-all rewrite) - server-card.json documentation block: support repointed to /docs/support (was getting-started), pricing + productCatalog entries added Out of scope, noted: the Cloudflare-side WAF UA rule (dashboard config, not repo) and the root openapi info.description (generated + size-budgeted). Claude-Session: https://claude.ai/code/session_01N3xTXNUrCiy1xqy3VbkMRx
… 401 and product-catalog, pin advertised markdown, cover the API annual plan (#4874) Review of merged #4867 confirmed 5 of 6 findings (the sixth was half-right: the Commerce spec IS fetchable via the Mintlify proxy — it just wasn't linked from any descriptor). - product-catalog partial-Dodo branch stamped X-Product-Catalog-Source: dodo while the body said priceSource: "partial" — header now carries the same source as the body. Spec: header enum gains partial, body priceSource documented, tier price nullable (Enterprise returns price: null). - /api/health 401 advertised a Bearer/OAuth challenge, but validateApiKey only reads X-WorldMonitor-Key / X-Api-Key headers and tester cookies — agents were pointed at a flow that cannot succeed. Challenge is now ApiKey realm="worldmonitor-health", header="X-WorldMonitor-Key"; test asserts Bearer is absent. - api-catalog service-meta links the Commerce OpenAPI spec URL (it lives outside the root bundle for the #4853 size budget; without the link no advertised descriptor reached it). - pricing.md + support.md pinned like auth.md: SPA catch-all exclusions (both regex copies), explicit text/markdown + CORS header rules, and deploy-config guards incl. file-existence (deleting the static file must 404, not fall through to the dashboard-HTML misleading-200). - pricing.md prose + JSON summary and docs/pricing.mdx gain the active API annual plan ($999/year, currentForCheckout in productCatalog.ts, live in /api/product-catalog — previously omitted everywhere). - drift test hardened: numeric comparison of pricing.md's machine-readable JSON block (stale JSON now fails even when prose was updated), comma-tolerant prose matching, api_starter_annual covered. Claude-Session: https://claude.ai/code/session_01N3xTXNUrCiy1xqy3VbkMRx
Fixes #4852. Restores the orank Access
function-calling-compatcheck to 2/2 (score regression 91→90 on the 2026-07-05T12:12Z scan).What happened
Today's per-op OpenAPI doc injections — #4843 (429 rate-limit blocks), #4841 (idempotency), #4844 (closed-value examples) — grew the minified
public/openapi.jsonfrom ~752KB to 1,040,576 bytes. orank's function-calling validator flipped from PASS ("Compatible: 192/192 ops with IDs, 192/192 with typed schemas") to WARN ("API spec found but couldn't validate function calling compatibility").Bracketed against orank's own cached scans: elevenlabs (1.8MB JSON) and openrouter (1,543,657 bytes) hit the identical "couldn't validate" error path, while sub-800KB specs (deepgram 49 ops, resend 83 ops, ora.ai 16 ops) get computed verdicts — elevenlabs' spec parses fine in orank's other checks, so the cap is specific to this validator's fetch. We passed at 752KB and fail at 1.04MB → cap ∈ (752KB, 1040KB].
scripts/build-openapi-json.mjsalready documented "~1 MB body caps such fetchers sometimes impose" when the JSON emit was introduced.Byte attribution (minified, 193 ops)
~227KB of byte-identical repetition.
Fix
scripts/openapi-dedup-responses.mjshoists repeated non-2xx response objects intocomponents.responses$refs, applied bybuild-openapi-json.mjsonly when emitting the served JSON artifact:$refs; validated with@seriousme/openapi-schema-validator→valid: true).responses['200']schema (napkin-proven 2026-07-05).TooManyRequests,BadRequest/BadRequest2, …); collision-guarded against pre-existing keys.docs/api/*.yamlsources untouched → Mintlify rendering, per-service specs, and all existing contract tests unchanged.Result: 812,276 bytes (10 shared components, 975 refs), deterministic rebuild (identical md5 on re-run).
Guards
tests/openapi-json-dedup.test.mjs(8 tests):$refsdeep-equals the original bundle;Verification
npm run docs:statszero drift.POST https://ora.ai/api/scan {"url":"worldmonitor.app"}after the Vercel deploy and confirmfunction-calling-compatback to 2/2 (verify live/openapi.jsonbyte size first — deploy lags merge).https://claude.ai/code/session_013AWjvnpnJTE6G2PaYk9iT1