feat: add response-shape conformance codegen and test#9
Merged
Conversation
Extends the "OpenAPI spec is source of truth" principle (already used for the
event catalog) to REST response shapes. Rather than generating the live
formatters — which would leak internal fields and cap the implementation at the
generator's expressiveness — this makes the spec a *checked invariant*: the
hand-written format* helpers stay authoritative, and a test asserts their output
matches the spec.
- scripts/gen-shapes{,-lib}.ts: mirror gen:events — read the @workos/openapi-spec
package (oxfmt-formatted, committed, idempotent) and emit
src/workos/generated/response-shapes.ts (RESPONSE_SHAPE_REQUIREMENTS: per-resource
property + required sets). A curated object->schema map selects the authoritative
schema per object type; extraction fails loudly if a mapped schema lacks a
matching `object` discriminator (rejects request DTOs like OrganizationDto).
- src/workos/response-shapes.spec.ts: per resource, assert (1) every spec-required
field is returned, (2) no field outside the spec is returned, (3) no secret field
ever leaks. Divergences live in exact-match ledgers that ratchet: closing one
forces deleting its entry; a new one fails the build.
Surfaces three real emulator<->spec divergences today (pinned, not hidden):
connection lacks required `status`; role lacks `permissions`/`resource_type_slug`
and carries emulator-only org/priority fields; permission lacks
`resource_type_slug`/`system`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds OpenAPI-backed response shape checks for WorkOS formatter output. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (3): Last reviewed commit: "fix: reject a bare --out flag in gen-sha..." | Re-trigger Greptile |
|
|
||
| const CASES: ReadonlyArray<{ objectType: string; output: Record<string, unknown> }> = [ | ||
| { objectType: 'user', output: formatUser(user) }, | ||
| { objectType: 'organization', output: formatOrganization(organization, ws, { domains: [] }) }, |
There was a problem hiding this comment.
Nested Domain Secrets Bypass Test
When formatOrganization is called without an explicit domains override, it formats stored organization domains through formatDomain; those domain objects can include internal verification fields. This test always passes an empty domain list and only checks top-level keys, so an organization response with a stored domain can leak nested verification data while this conformance test stays green.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/workos/response-shapes.spec.ts
Line: 162
Comment:
**Nested Domain Secrets Bypass Test**
When `formatOrganization` is called without an explicit `domains` override, it formats stored organization domains through `formatDomain`; those domain objects can include internal verification fields. This test always passes an empty domain list and only checks top-level keys, so an organization response with a stored domain can leak nested verification data while this conformance test stays green.
How can I resolve this? If you propose a fix, please make it concise.… catalog Addresses two review findings on the response-shape conformance work: - resolveSchema silently dropped fields when an allOf member resolved to a oneOf/anyOf (it has no top-level `properties` to merge). That could let the generated catalog understate the spec shape, defeating the "fail loudly" guarantee. Now throws, matching the top-level oneOf/anyOf handling. Adds a unit test. - SECRET_FIELDS listed auth-code/token names (`code`, `token`, `secret`, `value`, `key`) that none of the eight catalog resources can produce, which overstated the leak guard's coverage. Trimmed to the internal fields the format* helpers actually strip, with a comment recording why flow-resource formatters (email verification, magic auth, password reset, client secrets, API keys) intentionally surface those values and are out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Without a value (or followed by another flag), `--out` would take the next flag as the output filename — e.g. `--out --dry-run` writes to a file named `--dry-run` and leaves the committed generated file stale. Fail loudly instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gen:events) — from event payloads to REST response shapes.gen:shapescodegen (scripts/gen-shapes{,-lib}.ts), mirroringgen:events: reads the@workos/openapi-specpackage and emits a committed, idempotentsrc/workos/generated/response-shapes.tswith per-resource property + required-field sets. A curatedobject → schemamap selects the authoritative schema per object type; extraction fails loudly if a mapped schema lacks a matchingobjectdiscriminator (rejects request DTOs likeOrganizationDto).src/workos/response-shapes.spec.ts) asserting each hand-writtenformat*helper: (1) returns every spec-required field, (2) returns nothing outside the spec schema, (3) never leaks a secret (e.g.password_hash).connectionlacks requiredstatus;rolelackspermissions/resource_type_slugand carries emulator-onlyorganization_id/is_default_role/priority;permissionlacksresource_type_slug/system.Test plan
npm run typecheck— passesnpm run lint(oxlint) — passesnpm run fmt:check(oxfmt) — passesnpm test— full suite green (45 files, 444 tests)npm run gen:shapesis idempotent (re-run produces no diff)OBJECT_SCHEMA_MAPcuration + divergence ledgers read correctly🤖 Generated with Claude Code