Skip to content

fix(compilers/openapi): scope the key scan, keep the read's error - #441

Open
fuad-daoud wants to merge 2 commits into
stack/6-constraintsfrom
stack/7-review-fixes
Open

fix(compilers/openapi): scope the key scan, keep the read's error#441
fuad-daoud wants to merge 2 commits into
stack/6-constraintsfrom
stack/7-review-fixes

Conversation

@fuad-daoud

Copy link
Copy Markdown
Collaborator

Not breaking. Fixes three defects an adversarial review of the whole stack found — two of them real bugs in stack PR 1, which the per-commit gates could not see.

#420's fix missed JSON — the motivating case

decodeFlowEntries returned (probe, true) whenever the input opened with {, swallowing a mid-stream JSON error. That contract is correct for a cut prefix (the cut always errors) and wrong for a whole document, which the new sniffWhole reuses it for. So a >64 KiB JSON document whose openapi key sat behind a syntax error still reported unrecognized-format — the outcome PR 1 says it replaced. Stripe's spec3.json is flow style.

The key scan was not top-level-scoped

"openapi": matched at any nesting depth, anywhere in the buffer. On main that was bounded to 64 KiB; PR 1 removed the bound without tightening the predicate. So a foreign document nesting an "openapi" key paid a full parse, and one that also failed to parse could be reported as openapi/undecodable-sourcethis compiler claiming bytes that are not its own, which detect.go's own comments forbid.

declaresProbeKey is now declaresBlockKey (bare name at column 0, unchanged) OR declaresFlowKey, the latter requiring the source to open a flow mapping and matching only at depth 1. The 64 KiB bound was not reinstated.

Measured before/after on six >64 KiB inputs, and against a main binary — every case is at least as good as main, and four are better. Both fixes were mutation-checked: swallowing the error again reddens the new flow test; restoring the unscoped scan reddens 7 subtests.

One deliberate loss, for your judgement: a block YAML document quoting its top-level key ("openapi": 3.0.0 at column 0) past the cap is no longer detected. Admitting that would re-open the hole for zero-indent JSON, which json.MarshalIndent(v, "", "") really produces. Against main nothing regresses — that shape was declined there too.

A doc sentence

ir-design.md §12 rule 4 read as if the rule had no live instances after PR 3 gave Parameter its Provenance. Variant and EnumMember still are ones; they are now named.


Stack 7 of 8. Base stack/6-constraints — review and merge bottom-up. Every commit here passed make gate when it landed, and the full gate was re-run on the top of the stack. Run it as GOTOOLCHAIN=go1.26.3 make gate; this machine's Go 1.27 fails it for reasons unrelated to any change (#431).

🤖 Generated with Claude Code

https://claude.ai/code/session_01SYeBgDsskwnyitLgPGCPn1

fuad-daoud and others added 2 commits September 8, 2026 01:13
The whole-source read #420 added got two answers wrong, both by reusing
machinery written for a cut prefix.

The flow decoder reports "this is a flow mapping" for anything opening with
`{`, and drops the error that ended its walk. For a cut prefix that is right:
the cut always breaks the token stream, so the error describes the cut and not
the document. For a whole document it hides the document's own break. A JSON
source past the cap whose `openapi` key sits behind a syntax error came back
with a nil error, so Detect saw no failure to report and declined it as an
unrecognized format — the very answer #420 set out to replace, still standing
for every JSON source, which is the style the motivating spec is written in.
The decoder now returns the error that stopped it and treats stopping on the
mapping's own closing delimiter or on the entry cap as no error at all;
sniffPrefix drops it along with the cut that caused it, and sniffWhole keeps
it.

The key scan was widened to the whole source without being scoped to the top
level. Its block arm reads column 0 and always was top-level, but its quoted
arm matched `"openapi":` at any depth, anywhere in the buffer. Bounded to the
first 64 KiB that cost a needless parse; over a whole source it makes a claim,
and a wrong one — another format's document that nests such a key and does not
parse was reported as an undecodable OpenAPI source. Saying nothing about
bytes that are not this compiler's own is the rule detection is built on.

The quoted spelling is how flow style writes every key, so flow structure is
what scopes it: a depth-tracking scan reads the root mapping's own entries and
nothing under them, and a source that opens no mapping at all declares nothing
here. It is a lexer rather than a parser because the case it exists for is a
document broken before the key that names it, where there is no tree to ask. A
block document that quotes its top-level key is no longer seen and is declined
in silence, which is the direction to be wrong in.

A valid document past the cap that declares its version last still compiles,
which is what #420 was about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYeBgDsskwnyitLgPGCPn1
§12's fourth promotion rule read as if it had none left: `Parameter` was the
instance it named, and the sentence recording that `Parameter` has since gained
a `Provenance` left the rule with nothing to point at. `Variant` (§4.4) and
`EnumMember` (§4.5) each still carry a `Deprecation` with no provenance of
their own, so the rule governs them today. Name them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYeBgDsskwnyitLgPGCPn1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants