fix(ids)!: mint the id prefixes the spec documents - #122
Merged
Merged
Conversation
Nine entries in ID_PREFIXES used a prefix the OpenAPI spec (v0.80.0) never
shows in its own `id` examples — abbreviations the emulator invented, such as
`evt_` where the spec documents `event_` and `ra_` where it documents
`role_assignment_`. Response shape conformance cannot see this: a field set
says nothing about what goes in the field, so a wrong prefix passes every
existing spec check while breaking any consumer that string-matches an id.
Prefixes changed, each against the spec example it was read from:
event evt -> event
invitation inv -> invitation
directory_group directory_grp -> directory_group
cors_origin cors -> cors_origin
authorization_resource auth_res -> authz_resource
role_assignment ra -> role_assignment
audit_log_export audit_export -> audit_log_export
authorized_application auth_app -> authorized_connect_app
radar_attempt radar_attempt -> radar_att
The radar attempt is the one with no object-level example: its prefix comes
from the `/radar/attempts/{id}` parameter example and
`RadarStandaloneResponse.attempt_id`, which agree on `radar_att_`.
So this cannot drift back, gen-shapes now extracts the spec's per-object id
prefix into ID_PREFIX_REQUIREMENTS, discovered structurally rather than from a
curated map, and src/workos/id-prefixes.spec.ts asserts ID_PREFIXES matches it.
Objects the spec gives no example for, and prefixes that knowingly differ, live
in ledgers there with a reason each — closing one fails until its entry is
deleted.
BREAKING CHANGE: ids minted for events, invitations, directory groups, CORS
origins, authorization resources, role assignments, audit log exports,
authorized connect applications and radar attempts now carry the prefix the
WorkOS API documents. Tests and fixtures that hardcode or pattern-match the
old prefixes (`evt_`, `inv_`, `directory_grp_`, `cors_`, `auth_res_`, `ra_`,
`audit_export_`, `auth_app_`, `radar_attempt_`) need updating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The id-prefix catalog is discovered structurally from the spec rather than from a curated map, so its keys and values are whatever upstream ships. They were written straight into the generated TypeScript: an object discriminator containing a hyphen would have produced an unparseable property name, and a schema name containing a quote would have ended the string literal early — either way leaving a generator that cannot regenerate. Keys and spec-derived strings now go through JSON.stringify. oxfmt still normalizes the redundant quoting afterwards, so the generated file is byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Radar has no object-level example in the spec, so the prefix suite excludes it; reverting `radar_attempt` to its old prefix left every test green.
`connect_application` and `client_secret` now mint `conn_app_` and `secret_` on main, so the ledger entries tracking them as known divergences are stale and the guard that exists for exactly this case fails.
Collaborator
|
thanks ! |
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.
Mint the id prefixes the spec documents
Nine entries in
ID_PREFIXESused a prefix the upstream OpenAPI contract (@workos/openapi-specv0.80.0) never shows in its ownidexamples — abbreviations the emulator invented. Response-shape conformance cannot catch this: a field set says nothing about what goes in the field, soid: "ra_01…"satisfies every existing spec check while breaking any consumer that string-matches an id.Each prefix below is quoted from the spec example it was read from:
eventevent_01EHZNVPK3SFK441A1RGBFSHRT(EventSchema, in itsallOf[0])evteventinvitationinvitation_01E4ZCR3C56J083X43JQXF3JK5(UserlandUserInvite.id)invinvitationdirectory_groupdirectory_group_01E1JJS84MFPPQ3G655FHTKX6Z(DirectoryGroup.id)directory_grpdirectory_groupcors_origincors_origin_01HXYZ123456789ABCDEFGHIJ(CorsOriginResponse.id)corscors_originauthorization_resourceauthz_resource_01HXYZ123456789ABCDEFGH(AuthorizationResource.id)auth_resauthz_resourcerole_assignmentrole_assignment_01HXYZ123456789ABCDEFGH(UserRoleAssignment.id)rarole_assignmentaudit_log_exportaudit_log_export_01GBZK5MP7TD1YCFQHFR22180V(AuditLogExportJson.id)audit_exportaudit_log_exportauthorized_applicationauthorized_connect_app_01HXYZ123456789ABCDEFGHIJ(AuthorizedConnectApplicationList.data.items.id)auth_appauthorized_connect_appradar_attemptradar_att_01HZBC6N1EB1ZY7KG32X(/radar/attempts/{id}parameter, repeated asRadarStandaloneResponse.attempt_id)radar_attemptradar_attThe radar attempt is the only one without an object-level
idexample; its two independent occurrences agree, and the emulator serves/radar/attempts/:idfor the same resource, so the ids it mints are the ones a caller feeds back to the documented route. It is the one judgement call here and is easy to drop if you would rather not take it.Checked and deliberately not changed:
connected_account: data_installation— the comment said it tracks production rather than the spec, butConnectedAccount.idexamplesdata_installation_01EHZNVPK3SFK441A1RGBFSHRT. The spec agrees; the comment is corrected so it is not re-filed as a divergence later.connection_domain: conn_domain— the spec contradicts itself here.Connection.domains[]examples anorg_domain_…id (the organization-domain prefix), while theconnection.*event payloads exampleconn_domain_…for the sameobject: connection_domain. The emulator follows the events, which is what production emits. Tracked as a divergence rather than silently resolved, with both prefixes recorded.idexample for (identity,refresh_token,role_permission,audit_log_action, …) are left alone and ledgered with a reason each.BREAKING CHANGE
Ids minted for events, invitations, directory groups, CORS origins, authorization resources, role assignments, audit log exports, authorized connect applications and radar attempts now carry the prefix the WorkOS API documents.
Anything downstream that hardcodes or pattern-matches the old prefixes —
evt_,inv_,directory_grp_,cors_,auth_res_,ra_,audit_export_,auth_app_,radar_attempt_— needs updating. Snapshot fixtures, recorded HTTP interactions andid.startsWith(…)//^evt_/-style assertions are what to look for. Nothing about the id format changed otherwise: still<prefix>_<26-char ULID>.Drift guard
Follows the house pattern (generated catalog + spec file with an explicit ledger), reusing the gen-shapes pipeline rather than standing up a parallel one:
gen-shapesnow also emitsID_PREFIX_REQUIREMENTS— 55 objects, each with the prefix, the verbatim example, the source schema, and any conflicting prefixes the spec uses elsewhere. Unlike the response-shape catalogs, nothing here is curated: every object with a prefixedidexample is discovered structurally. That matters —authorized_connect_applicationhas no top-level schema at all, its only example is an inline schema nested inside a list wrapper, which a curated schema-name map would never reach.objectandidare resolved across the wholeallOf/oneOfcomposition, since the spec routinely declares them in different members.src/workos/id-prefixes.spec.tsassertsID_PREFIXESmatches it. Objects with no spec example, and prefixes that knowingly differ, live in two ledgers with a reason each. Closing a divergence fails until its ledger entry is deleted, and a new one fails outright — drift cannot accrue silently. There is also a guard-on-the-guard, so an extractor that silently returns nothing cannot make the suite pass vacuously.Merge-order coupling with #121 — resolved
#121 corrected two further prefixes in the same table (
connect_app_→conn_app_,client_secret_→secret_). This PR ledgered both as known divergences, because they still diverged onmainat the time.#121 has now merged,
mainis merged in here, and those twoTRACKED_DIVERGENCESentries are deleted — the guard reported them as stale the moment the prefixes stopped diverging, which is the ledger behaving as designed rather than a defect. Nothing outstanding.Verification
npm run typecheck,npm run lint,npm run fmt:checkclean.bun test: 1288 pass, 1 fail —redirect-hosts.spec.ts's punycode case, which fails identically on cleanmain(it is a Bun 1.4domainToASCIIbehaviour change; CI pins 1.3.14, where it passes).gen:shapes,gen:eventsandgen:supportedall re-run byte-identical, so there is no codegen drift.Related: #120 (the emulator is pinned to an older spec version) — this PR does not address or close it; it corrects prefixes against the spec version currently pinned.
🤖 Generated with Claude Code