Skip to content

fix(agent-readiness): $ref-dedupe error responses in /openapi.json — back under orank's ~1MB function-calling cap#4853

Merged
koala73 merged 1 commit into
mainfrom
fix/openapi-json-dedup-error-responses
Jul 5, 2026
Merged

fix(agent-readiness): $ref-dedupe error responses in /openapi.json — back under orank's ~1MB function-calling cap#4853
koala73 merged 1 commit into
mainfrom
fix/openapi-json-dedup-error-responses

Conversation

@koala73

@koala73 koala73 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Fixes #4852. Restores the orank Access function-calling-compat check 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.json from ~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.mjs already documented "~1 MB body caps such fetchers sometimes impose" when the JSON emit was introduced.

Byte attribution (minified, 193 ops)

Response bytes distinct bodies
429 128,924 1 (×193)
400 39,240 2
default 36,477 1 (×193)
403 35,848 5
401 25,482 1 (×186)
409 + 422 ~10K 1 + 1

~227KB of byte-identical repetition.

Fix

scripts/openapi-dedup-responses.mjs hoists repeated non-2xx response objects into components.responses $refs, applied by build-openapi-json.mjs only when emitting the served JSON artifact:

  • Semantically identical OpenAPI 3.1 (document-local $refs; validated with @seriousme/openapi-schema-validatorvalid: true).
  • 2xx responses never hoisted — orank's response checks credit only the inline responses['200'] schema (napkin-proven 2026-07-05).
  • Unique bodies stay inline; deterministic component names (TooManyRequests, BadRequest/BadRequest2, …); collision-guarded against pre-existing keys.
  • docs/api/*.yaml sources 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):

  • lossless roundtrip: resolving the $refs deep-equals the original bundle;
  • every 2xx stays inline;
  • dedup actually engages on the injected 429s (≥500 refs);
  • 950KB size budget on the emitted JSON with an actionable failure message — the next per-op injector fails in CI instead of in orank;
  • build-script wiring guard.

Verification

  • 8/8 new tests pass; 185/185 adjacent OpenAPI contract tests pass (rate-limit-errors, examples, idempotency, webhooks, batch, async-jobs).
  • biome clean; npm run docs:stats zero drift.
  • Post-merge: rescan via POST https://ora.ai/api/scan {"url":"worldmonitor.app"} after the Vercel deploy and confirm function-calling-compat back to 2/2 (verify live /openapi.json byte size first — deploy lags merge).

https://claude.ai/code/session_013AWjvnpnJTE6G2PaYk9iT1

…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
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 5, 2026 12:31pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR deduplicates repeated non-2xx response bodies in public/openapi.json by hoisting them into components.responses $refs, cutting the served artifact from ~1.04 MB back to ~812 KB and restoring orank's function-calling compatibility check.

  • scripts/openapi-dedup-responses.mjs implements a two-pass dedup: it canonicalises every non-2xx inline response body (with sorted-key hashing), counts occurrences, then installs shared bodies as named components and replaces repeated sites with $refs — 2xx responses are intentionally excluded to preserve scanner credits.
  • scripts/build-openapi-json.mjs calls dedupeErrorResponses immediately before JSON.stringify, so the YAML sources and all contract tests remain unaffected.
  • tests/openapi-json-dedup.test.mjs covers lossless roundtrip, 2xx-inline invariant, collision-avoidance, a 950 KB size budget, and a build-wiring guard — but the test imports js-yaml, which is not declared in the root package.json and is missing from node_modules; it should use the already-declared yaml package instead.

Confidence Score: 4/5

The 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 js-yaml, which is not listed in the root package.json and is not present in node_modules, so the tests would fail with a module-not-found error in any clean CI environment. Swapping the import to yaml (already declared) is a one-line fix that also aligns the parser with what the build script uses.

tests/openapi-json-dedup.test.mjs — undeclared js-yaml import needs to be replaced with yaml

Important Files Changed

Filename Overview
scripts/openapi-dedup-responses.mjs New module that hoists repeated non-2xx response objects into components.responses $refs. Logic is correct: two-pass approach, canonical key sorting, collision-guarded names, and 2xx responses correctly excluded.
scripts/build-openapi-json.mjs Wires dedupeErrorResponses into the JSON emit pipeline correctly; dedup runs before JSON.stringify and the log message accurately reports hoisted count and ref count.
tests/openapi-json-dedup.test.mjs Imports js-yaml which is not declared in root package.json and is not present in node_modules; should use the already-declared yaml package (same one used by the build script) to avoid a fragile transitive-dependency reliance and parser inconsistency.

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]
Loading
%%{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]
Loading

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';

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.

P1 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.

Suggested change
import { load as loadYaml } from 'js-yaml';
import { parse as loadYaml } from 'yaml';

koala73 added a commit that referenced this pull request Jul 5, 2026
…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
koala73 added a commit that referenced this pull request Jul 5, 2026
… 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
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.

fix(agent-readiness): /openapi.json crossed the ~1MB scanner body cap — orank function-calling-compat regressed 2/2→1/2 (score 91→90)

1 participant