Uh oh!
There was an error while loading. Please reload this page.
fix(compilers/openapi): stop dropping writeOnly beside readOnly - #322
Merged
Conversation
Uh oh!
There was an error while loading. Please reload this page.
OmarAlJarrah added a commit
that referenced
this pull request
Aug 9, 2026
Nine PRs landed on main after this branch was opened, and two of them added tests to files this branch rewrites. Git reconciled the two edits without a textual conflict, so the merged tree compiled nowhere: the new tests call the package-local helpers this branch deletes. Resolved by pointing those call sites at the package that now owns them — 26 in operations_test.go and schema_test.go across requireNoErrorDiags, findOp, hasDiagCodeAt, pathsSpec, componentSpec and propsByWire. The one real conflict, conformance_test.go's import block, keeps both: the branch added openapitest and #322 added diag, and the file needs each. No test was lost in the merge — the name list gains the new package's 22 and loses nothing against main.
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 freeto 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
A property whose schema declared both
readOnlyandwriteOnlylowered exactly as one declaringreadOnlyalone:annotation.EffectiveVisibilityguarded the two flags in order and returned onthe first match, so
writeOnlycontributed nothing, appeared nowhere in the emitted document, andraised no diagnostic in either channel. The same contradiction spread over two
allOfbranchesalready intersected to
Visibility{None: true}and warned, so one document could get two differentanswers depending on how it spelled the same thing.
The compiler now reads both flags and answers the intersection: readOnly means the value is not
writable, writeOnly that it is not readable, so a position in both is admitted by no lifecycle at
all —
Visibility{None: true}, which is whatmerge.mergeVisibilityhas answered for the branchspelling since #34. The two spellings now agree, in the IR and under the same diagnostic code.
Three decisions worth stating:
beside the other, so the document is contradictory but legal. It gets a warning, not an error.
Unmodeled. That carve-out is for constructs the IR cannot express, and theIR expresses this one exactly:
Visibility{None: true}is the shape it already has for "visiblenowhere". Preserving the raw keywords beside a field that models them would restate what the
carrier holds.
readOnlysettlesreadOnlyfor that position and says nothing aboutwriteOnly, which still resolves from the$reftarget — the uniform use-site-over-referent merge of ir-design §14 that every otherannotation here follows. So
{$ref: X, readOnly: true}against awriteOnlyX now yieldsNonewhere it used to yield plainreadOnly;TestEffectiveVisibility_MapsTheFlagsToLifecyclespinned that as "the site wins over the referent", and the site still does win — for the flag it
writes. Treating the pair as one annotation that a node wins outright would have kept the old
answer at the cost of discarding the target's flag silently, which is the defect being fixed.
The reversed spelling (
writeOnlyat the site over areadOnlytarget) answeredreadOnlybefore this change, contradicting that same precedence claim; it is symmetric now.
EffectiveVisibilityreturns the contradiction as a bool rather than a diagnostic: it has noprovenance of its own, and provenance is built in exactly one place (
lowering.Ctx). The callerthat has one —
FillPropertyDetail, which serves both model properties and response/part headers —reports it, mirroring how
merge.reconcilePropertyreports its own disjoint intersection fromoutside
mergeVisibility.The
openapi/disjoint-visibilitycode is reused rather than joined by a second one. The finding isthe same for a consumer — this field ends up carried by no request or response — and giving the two
spellings different codes would reintroduce, in the diagnostic channel, the divergence this change
removes from the IR. Its doc comment now covers both.
Swept for the same mechanism (an ordered guard collapsing co-declared contradictory keywords
into the first, silently):
EffectiveDeprecatedreads a single flag and has no pair to collapse;pickFlagis the intended site-over-referent primitive;annotation.reconcileBoundkeeps thetighter of two co-declared bounds and already reports doing so;
schema.dispatchOfelects amongconst/enum/allOfin a fixed order but records every loser underUnmodeledwith adiagnostic. One genuine sibling turned up outside this change's subsystem — a parameter or header
declaring both
schemaandcontentdrops one in silence, and the two positions disagree aboutwhich — filed as #320 rather than folded in here.
Test plan
TestModel_ReadOnlyAndWriteOnlyTogetherAreVisibleNowhere(new,internal/schema) compiles theissue's own reproduction and asserts the property is visible in none, that it no longer lowers as
a readOnly-only sibling does, and that exactly one
openapi/disjoint-visibilitywarning lands atthe property's pointer naming
writeOnly. Reverting the reader to its ordered guard turns allthree red.
TestEffectiveVisibility_MapsTheFlagsToLifecyclesgrows four cases — both flags on one schema,both on the referent, and each cross-node direction — and keeps the same-flag precedence cases.
readonly-writeonlyconformance fixture gains the pairing in both spellings (one schema, anda
$refsite carrying the opposite flag). Its golden picks up the twonone: trueproperties andthe two warnings, and nothing else. Deleting the new property from the fixture reddens the suite,
so the regeneration is not a no-op.
unwitnessed.golden.txtlosesVisibility.None: no committed spec drove that field to anon-zero value until now.
go vet,golangci-lint,go build, 100% statement coverage.Closes#276