Repository navigation
feat(ir)!: give parameters, groups and enum members stable IDs - #807
Closed
fuad-daoud wants to merge 6 commits into
Closed
fuad-daoud wants to merge 6 commits into
fuad-daoud wants to merge 6 commits into
Conversation
A Parameter was identified by its display name, so an operation with id in the query and id in the path could not say which one a binding, a pagination cursor or an idempotency token meant. Parameter gains a required ID of the new ParamID class, minted as param/<space>/<operation path>/parameters/<name>/<in>. The operation scopes it because a path-item parameter is copied into every operation on the item; no list index is used, so reordering leaves IDs unchanged. HTTPParamBinding.Param, ParamPath.Param and Idempotency.TokenParam now hold a ParamID, and pass/validate joins bindings to parameters by it, rejecting an ID the binding's own operation does not declare. Breaking: IRVersion 0.6.0 -> 0.7.0; the three references changed from a name string to an ID, and goldens are regenerated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An OperationGroup had no identity beyond its display name, so a declared tag called "default" and the group the compiler synthesizes for untagged operations were indistinguishable, and nothing could reference a group. OperationGroup gains an ID of the new GroupID class (kind prefix g) and a Provenance. A tag-declared group is g/openapi/tags/<escaped name>, the name as one injectively escaped segment with no parent chain, so reordering tags cannot rebind an ID to another tag. A group the compiler synthesizes (untagged fallback, webhooks, path-prefix) lives in g/synth/openapi/<rule>[/<key>], a space no declared ID can reach. The hint of a synthesized group yields to any declared or used spelling. pass.Validate reports a repeated group ID as pass/duplicate-group-id and irverify holds each ID to its space's grammar. GroupKey in the emitter design carries the ID. Breaking: documents gain id and provenance on every group; the 0.7.0 history entry records it, goldens are regenerated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An EnumMember was told apart only by its position, so reordering or inserting members changed what every later one meant to a consumer. EnumMember gains a required ID of the new EnumMemberID class, minted as e/<space>/<enum path>/<key>. The key derives from the member's value (kind-tagged, injective, canonical BigVal decimal for numbers), never from its index or canonical name. A repeated value is kept; later occurrences take a #n suffix and the OpenAPI compiler warns once per repeat (openapi/duplicate-enum-value). pass.Validate rejects a repeated ID within one enum, and irverify holds each ID to its enum's scope. Breaking: extends the 0.7.0 entry (no further bump); EnumMember gains a required id key and goldens are regenerated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Pagination.InputCursor, Pagination.InputLimit and Idempotency.TokenParam were only checked document-wide, so they could name another operation's parameter and validate clean. They are now held to the operation's own Params, with pass/param-binding-mismatch, as HTTP bindings are. An ID no operation declares stays with the dangling-reference walk. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Picks up #804 (Go 1.27.2, golangci-lint v2.14.0, openapi v1.25.5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MQFAkYBVaqgSxGwe9ZJSn
Collaborator
Author
|
Closing in favour of a replacement built on current The replacement keeps what #801 does not cover:
Both new ID kinds will be registered in #801's namespace declaration and held to its verifier rules. The IR version moves to 0.8.0, since 0.7.0 is already on |
7 tasks done
Collaborator
Author
|
Replaced by #811. |
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
Closes #596. Closes #673. Part of #255 (item 1, enum-member identity; items 2–4 have no code yet, and their decisions are recorded on #255).
Three named things in the IR had no stable identity, so anything that needed to point at one could only use its name. Names are presentation (invariant 3), and here they also collide. This gives each of the three a synthetic ID and moves every reference onto it. The three changes ship together because each changes the IR's shape, and together they make one version bump:
0.6.0→0.7.0.Parameters:
ParamID(#596)OpenAPI identifies a parameter by
(name, in), soidin the query andidin the path are both legal on one operation. The IR referenced parameters by name, so their two bindings could not say which one they bound.Parameter.IDisparam/<space>/<operation mount pointer>/parameters/<name>/<in>.$ref'd path item can be mounted at several paths, so the mount pointer is what keeps each copy distinct.HTTPParamBinding.Param,Pagination.InputCursor/InputLimit(ParamPath.Param) andIdempotency.TokenParamnow hold aParamID.pass/validaterejects any of them naming a parameter its own operation does not declare. An ID nobody declares stays with the existing dangling-reference check, so it is reported once.Operation groups:
GroupID(#673)emitter-design§3.4 derivedGroupKeyfrom a group identity that did not exist. A declared tagdefaultand the fallback group for untagged operations both came out asdefault.g/openapi/tags/<escaped tag name>, flat, with no parent chain.#/tags/<i>was rejected. It would silently rebind to a different tag on reorder (ir: an ID derived from an array index names a different entity after the array changes #678), and an undeclared tag has no position at all.~→~0,/→~1, empty→~.ir-design§3.1 states what the ID promises across revisions (docs: several design documents say IDs survive renames, which pointer-derived IDs don't #677).g/synth/openapi/…, so they can never equal a declared group's ID.default_2beside a tagdefault, and the same rule applies to the webhook group.OperationGroupalso gainsProvenance.GroupKeyis defined inemitter-design§3.4 as carrying the group's ID.Enum members:
EnumMemberID(#255, item 1)emitter-design§4.12 resolves naming collisions in a table keyed by IR ID, and the commonest collision, enum members canonicalizing to the same words, had no ID to key it.EnumMember.IDis scoped to its enum and derived from the member's wire value, never its position, so reordering or inserting members changes no existing ID.1and"1"differ.#2suffix, and the compiler warnsopenapi/duplicate-enum-value.EnumMember.IDas the collision-resolver key.Checks
pass/validatereports duplicate group IDs and duplicate member IDs within one enum.irverifyreaches all three classes. Empty and duplicate IDs are found automatically through its reflection-derived declarations. The grammar and scope checks for groups and members are hand-written.Breaking
IRVersionis0.7.0.CompatibleVersionaccepts only an exact match, so a stored0.6.0document is refused.HTTPParamBinding.Param,ParamPath.ParamandIdempotency.TokenParamchange from a parameter name to aParamID.Parameter,OperationGroupandEnumMembergain a requiredid.OperationGroupalso gainsprovenance.testdata/.Test plan
make gatepasses on the branch tip after mergingmain(build: move to Go 1.27.2, golangci-lint v2.14.0, openapi v1.25.5 #804: Go 1.27.2, golangci-lint v2.14.0), including 100% statement coverage, lint, fuzz and bench smoke. The statement count fell from 10000 to 9352 with the merge. build: move to Go 1.27.2, golangci-lint v2.14.0, openapi v1.25.5 #804 changes no Go code; Go 1.27.2 counts fewer statements per coverage block, with the same block set (checked oncompilers/openapi/internal/schema: 1005 blocks under both, 1535 → 1453 statements). Run withGOFLAGS=-p=4, because two wall-clock-budget tests (TestDetectCycles_MergeChainPastBoundStaysFastAndWarns,TestResolverOracle_RefusalMatchesResolverBehavior) overrun under the full parallel race run on a loaded laptop. They do the same onmain; see test: replace the merge-walk timing ratio with a deterministic bound #746.Every new test was checked by planting the defect it guards against and watching it go red, then reverting. For example:
indropped from the param ID;defaulthint;irverifycheck removed.Two-order tests: parameter declaration order, tag and path order, and enum member order each leave every ID unchanged.
Goldens are not a no-op regeneration. Deleting a parameter, a tag, or an enum member from a source spec reddens the suite without
-update.Probes with
morphic compile/validate:$ref'd path item mounted at/a/{id}and/b/{id}withgetanddeletegets four distinct param IDs;g/openapi/tags/defaultandg/synth/openapi/default(hintdefault_2);[a-b, a_b, "A B", "a/b", "a#b", a-b]gives six distinct member IDs and one duplicate warning.All of them validate with exit 0.
🤖 Generated with Claude Code
https://claude.ai/code/session_016MQFAkYBVaqgSxGwe9ZJSn