Problem
The served aggregate spec docs/api/worldmonitor.openapi.yaml (copied to public/openapi.yaml at build) is missing four auth-contract elements the 34 per-service *Service.openapi.json specs already carry. Measured on main @ 4cbcf16f9:
| Element |
Per-service |
Bundle |
Per-op 401 |
185 |
0 |
UnauthorizedError schema |
present |
absent |
BearerAuth scheme |
on bearer specs |
absent |
Root security schemes |
up to 3 |
2 |
Public-op security: [] |
7 |
0 |
Entitlement 403 / examples / enums / required / degradation |
present |
present (already correct) |
Root cause: PR #4602 (auth) deliberately deferred per-op detail in the 21k-line bundle YAML (a js-yaml re-dump reformats ~100% of it), doing only top-level insertion. Sibling injectors #4625/#4621/#4626 later added their own per-op bundle surgery but none backfilled the deferred auth detail; #4626's refactor also made BearerAuth conditional, which the bundle path never implemented.
Impact: a consumer reading the served aggregate spec sees a key is required (root security) + the 403 gates, but not the 401 shape, the bearer option, or which endpoints are public. Last open thread of #4599 root cause #1.
Fix (bundle-only — per-service specs stay byte-unchanged)
Extend the bundle path of scripts/openapi-inject-security.mjs to full parity, reusing the per-op YAML-surgery toolkit already in that same file for entitlement 403s (findYamlOperationRange, findYamlResponseRange, ensureYamlForbiddenResponse/Schema → mirror as ensureYamlUnauthorizedResponse/Schema + an injectYamlAuthContract orchestrator). Consume the same BEARER_AUTH_PATHS (ENDPOINT_ENTITLEMENTS + PREMIUM_RPC_PATHS) and PUBLIC_NO_AUTH_RPC_PATHS constants the per-service path uses, so bundle and per-service can't drift.
- Per-op
401 → UnauthorizedError on the 185 authenticated ops; UnauthorizedError schema defined once.
BearerAuth in bundle securitySchemes + 3-item root security; stamped per-op on the ~19 entitlement/premium ops.
security: [] (no 401) on the 7 public no-auth RPCs.
- Formatting-preserving surgical insertion (no js-yaml re-dump); idempotent — full Makefile injector chain must stay zero-diff.
Acceptance criteria
- Bundle: per-op
401 = 185, BearerAuth present, UnauthorizedError present, public security:[] = 7, 403 still 19, paths = 191.
tests/openapi-security-contract.test.mjs extended with a bundle-parity block (fails on current main, passes after) + full contract suite green.
- Injector-chain drift check: zero diff after re-running all four injectors.
- Only 3 files changed:
scripts/openapi-inject-security.mjs, tests/openapi-security-contract.test.mjs, docs/api/worldmonitor.openapi.yaml.
Plan
Implementation-ready plan (3 units, test-first): docs/plans/2026-07-02-001-fix-openapi-bundle-auth-parity-plan.md
Umbrella #4599 (root cause #1, auth) · follow-up to merged PR #4602.
Problem
The served aggregate spec
docs/api/worldmonitor.openapi.yaml(copied topublic/openapi.yamlat build) is missing four auth-contract elements the 34 per-service*Service.openapi.jsonspecs already carry. Measured onmain@4cbcf16f9:401UnauthorizedErrorschemaBearerAuthschemesecurityschemessecurity: []403/ examples / enums / required / degradationRoot cause: PR #4602 (auth) deliberately deferred per-op detail in the 21k-line bundle YAML (a js-yaml re-dump reformats ~100% of it), doing only top-level insertion. Sibling injectors #4625/#4621/#4626 later added their own per-op bundle surgery but none backfilled the deferred auth detail; #4626's refactor also made
BearerAuthconditional, which the bundle path never implemented.Impact: a consumer reading the served aggregate spec sees a key is required (root security) + the 403 gates, but not the 401 shape, the bearer option, or which endpoints are public. Last open thread of #4599 root cause #1.
Fix (bundle-only — per-service specs stay byte-unchanged)
Extend the bundle path of
scripts/openapi-inject-security.mjsto full parity, reusing the per-op YAML-surgery toolkit already in that same file for entitlement 403s (findYamlOperationRange,findYamlResponseRange,ensureYamlForbiddenResponse/Schema→ mirror asensureYamlUnauthorizedResponse/Schema+ aninjectYamlAuthContractorchestrator). Consume the sameBEARER_AUTH_PATHS(ENDPOINT_ENTITLEMENTS+PREMIUM_RPC_PATHS) andPUBLIC_NO_AUTH_RPC_PATHSconstants the per-service path uses, so bundle and per-service can't drift.401 → UnauthorizedErroron the 185 authenticated ops;UnauthorizedErrorschema defined once.BearerAuthin bundle securitySchemes + 3-item root security; stamped per-op on the ~19 entitlement/premium ops.security: [](no 401) on the 7 public no-auth RPCs.Acceptance criteria
401= 185,BearerAuthpresent,UnauthorizedErrorpresent, publicsecurity:[]= 7,403still 19,paths= 191.tests/openapi-security-contract.test.mjsextended with a bundle-parity block (fails on current main, passes after) + full contract suite green.scripts/openapi-inject-security.mjs,tests/openapi-security-contract.test.mjs,docs/api/worldmonitor.openapi.yaml.Plan
Implementation-ready plan (3 units, test-first):
docs/plans/2026-07-02-001-fix-openapi-bundle-auth-parity-plan.mdUmbrella #4599 (root cause #1, auth) · follow-up to merged PR #4602.