Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Tags: oasdiff/oasdiff

Tags

v1.26.0

Toggle v1.26.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Add response header schema checks: type, format, nullability (#1116)

* Add response header schema checks: type, format, nullability (#1094)

Response headers had existence-level checks only (added, removed,
became-optional); nothing walked a response header's schema diff, so a
type or format change on a header was silent in breaking and changelog.
This adds the first slice of the missing checks, walking
responses' HeadersDiff.Modified schema diffs:

- response-header-type-changed / -generalized / -specialized / -compatible
- response-header-became-nullable / -became-not-nullable

Severity mirrors the response body/property rules through the shared
responseTypeChangeId and nullabilityChangeId classifiers. A header value
is text on the wire, so it is treated non-strongly-typed, the same as a
scalar parameter: a bare string to integer swap is backward compatible,
while dropping a format a client parses (Retry-After string/date-time to
integer) is breaking, and widening the returned type is breaking.

Total rule ids 500 -> 506.

* Pass strong-typing as a bool, not via a fake empty media type

The response header check reached non-strongly-typed behavior by passing
mediaType "" (relying on isStronglyTyped("") == false) and then patched
the compatible comment after the fact. Both were kludges.

Make the real axis explicit: requestTypeFormatBreaking / responseTypeFormatBreaking
and requestTypeChangeId / responseTypeChangeId now take stronglyTyped bool, and
the type-change helpers take the loose-swap comment as a parameter. Body and
property callers pass isStronglyTyped(mediaType) + the media-type comment; the
header caller passes false + the header comment directly. No behavior change.

* Note content-serialized headers are out of scope

A header serialized via content (e.g. content: application/json) produces
a ContentDiff, not a SchemaDiff, and would be strongly typed. This slice
walks only the simple schema serialization (text on the wire).

v1.25.1

Toggle v1.25.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
checker: a required request property with a default is breaking (erro…

…r), not warning (#1110)

v1.25.0 moved new-required-request-property-with-default and
request-property-became-required-with-default from info to warning. That still
under-reported. Breaking-ness is a property of the contract, not of a lenient
runtime: a request that omits a required property is invalid under the new
contract whether or not the property has a default. The default is a
server-side fallback, it does not make the omitted property valid. Whether a
particular server applies it and accepts the request anyway is runtime
tolerance, not contract validity (and generated SDKs and strict gateways reject
it regardless).

Both checks are now ERR, same as their no-default siblings, with a comment
explaining why the default does not make them safe. The ids stay distinct so a
team can downgrade exactly these via --severity-levels if the change is safe for
their ecosystem.

Adds a 'How oasdiff decides what is breaking' section to BREAKING-CHANGES.md
documenting the contract-not-runtime principle. Comment message updated in all
four locales.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.25.0

Toggle v1.25.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
docs: validate's duplicate-enum lint; allOf's dropped keywords (#1108)

* docs: validate's duplicate-enum lint; allOf's dropped keywords

VALIDATE.md predates the duplicate-enum-value lint (#1023); it joins the
warning examples. ALLOF.md said some fields are "not merged" but
if/then/else, dependentSchemas, and unevaluatedProperties are dropped
outright, with semantic consequences; state it and link #1097.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: validate also lints ambiguous parameter serialization (#1103)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

v1.24.0

Toggle v1.24.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(review): per-endpoint structural block extraction for large specs (

#1068)

* feat(review): scaffold per-endpoint structural block extraction [WIP]

Render one self-contained card per change instead of the full spec — the
large-spec /review fix (a 250k-line spec currently commits ~1M DOM nodes and
takes minutes to become interactive; cards cut that ~99%).

New reviewblocks package: group changes by their smallest enclosing structural
block and slice each block's source text using kin-openapi origin
end-positions (Origin.Key.EndLine/EndColumn), so a block's full extent is
known, not just its start. Repins getkin/kin-openapi to master HEAD for that
support (pseudo-version) pending the upstream release.

WIP: change grouping, the origin-end line slice, and the component-schema case
are wired and tested. Still to do: operation / path-item / top-level node
resolution (operations don't carry Origin, so the path-item key origin is the
handle), overlap dedup, the "Other changes" fallback, and feeding the blocks
into the encrypted review bundle so the browser renders cards from the
decrypted payload (the server never sees the spec).

* feat(review): slice endpoint (operation + path) blocks

PathItem and Operation both carry Origin (with Key.EndLine), so resolve an
endpoint change to its operation block directly: "<METHOD> <path>" -> the
operation's source span (falling back to the whole path block if the
operation has no recorded origin); a path-level change -> the whole path
block. End-to-end tests parse with IncludeOrigin and assert the POST block
excludes the sibling GET, the other path, and the rest of the document.

Corrects the earlier note that operations don't carry Origin — they do.

* feat(review): key blocks by source line so $ref'd-component changes card correctly

A change inside a $ref'd component is reported by oasdiff under the
referencing operation, but its source line is in the component. Keying by
(operation, path) sliced the operation block, which only shows the $ref, not
the change. Switch to source-line containment: index each document's blocks
(operations, path items, named component schemas) by their Origin span and
map each change to the smallest block containing its BaseSource/RevisionSource
line. This follows the $ref (cards the component) and dedupes the same
component change reported across several endpoints into one card.

(operation, path) plus the rule Area become the fallback when no source line
resolves. New test asserts a property change inside a $ref'd component cards as
components/schemas/User, not POST /users.

* test(review): real-pipeline flatten-allof case keys to the operation

Runs the actual diff + checker with --flatten-allof: the merged schema
produces the readable request-property-became-required change with no
source line, so the change falls back to the operation it names and the
slice is the real operation block (whose origin survives the flatten).
This is the path the review page runs; closes the test TODO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): carry per-change blocks in the encrypted --open bundle

Add a `blocks` field to the review payload, filled by reviewblocks.Extract
from the resolved specs and the raw spec text. The review page renders one
card per block instead of the full spec. Works with and without
--flatten-allof: flattened changes have no source line and fall back to the
operation block, whose line survives the flatten. JSON tags added to Block
so it serializes into the bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): carry per-change fingerprints on each block

The review page joins each change to its card by fingerprint (the stable
key), not the rule id (which repeats). Extract now records each change's
fingerprint alongside its id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(source): emit origin end position (endLine/endColumn)

Source now carries the origin's end position alongside the start, so
-f json/yaml expose endLine/endColumn for each change. A multi-line
element (an operation, an added parameter, an object schema) reports its
full span; a single-line element (an added enum value, a single field)
reports endLine == line (omitted when zero). Populated from
openapi3.Origin.Key end positions and field/sequence locations. Lets a
consumer highlight exactly the changed lines. Updated the operation-level
golden source assertions to include the new end fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(reviewblocks): add package doc.go

Move the package overview into doc.go (the per-package convention), refreshed
to the current state: what Extract returns, source-line block keying with the
$ref-following rationale, origin-end slicing, and the known gaps (top-level
sections, multi-file, schema-shape completeness).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(review): centralize the review bundle in package review

Rename reviewblocks -> review and move the bundle's domain logic out of
internal/open_review.go into it: the Payload wire type, the AES-256-GCM
Encrypt, and the fingerprint Manifest now live alongside Extract, so the
package is the single source of truth for the bundle format. internal/
open_review.go keeps only CLI orchestration (flags, HTTP upload, browser,
changelog rendering). The package comments are product-agnostic (a generic
encrypted review bundle); the oasdiff.com / OASDIFF_URL defaults stay in
internal. Moved the encryption round-trip and manifest unit tests into the
review package; the upload-flow tests stay in internal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* checker: add Source.WithEnd so end-bearing sources keep using NewSource

Adding EndLine/EndColumn to Source meant the golden assertions with a span
couldn't use the 3-arg NewSource and had become raw struct literals. Add a
fluent WithEnd(endLine, endColumn) so they read NewSource(f,l,c).WithEnd(el,ec),
restoring the constructor convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): 'receives' not 'stores' a blob it cannot read

The package produces ciphertext and uploads it; whether the server persists
it is the caller's/server's concern, so 'receives' is the accurate framing
for the library.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): drop 'side-by-side', a renderer-specific layout

The review package is renderer-agnostic; side-by-side is one UI's choice.
Describe the generic behavior (render only what changed; fall back to the
full spec) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): gloss 'rule Area' in the block-selection fallback

Spell out what the Area fallback is (the OpenAPI object a rule concerns,
e.g. security/tags) so the doc is self-explanatory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): index the top-level sections (info/servers/tags/security)

buildIndex now adds a block for each top-level section, spanning from its key
line to just before the next top-level key (from the document origin's field
locations). Key-line based on purpose: it works uniformly even for security,
whose items are bare maps with no origin of their own. changeSources now reads
the sources via the Change interface instead of type-asserting ApiChange, so
SecurityChange / ComponentChange resolve to their block too. Closes the
"top-level sections not indexed" gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): state the real multi-file cause (kin drops cross-file origins)

The limitation isn't a missing text thread: kin resolves an external $ref's
value but discards its origin, so the change is sourced at the referencing
operation, not the external file. Document that, and that the fix is upstream
(carry origins across file resolution) plus a file-aware index here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): correct the multi-file limitation

A $ref into a valid OpenAPI sub-document keeps its origin (external file +
line), so the standard multi-file shape has coordinates; the gap is this
index being single-file (needs file-aware index + per-file texts), not a kin
limitation. Only a bare-fragment ref loses its origin in kin's generic-map
(JSON round-trip) resolution path, which would need an upstream fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(load): capture referenced file texts (NewSpecInfoWithCapture)

For multi-file slicing, the review path needs the text of every file that
contributed to the spec (the root and each $ref'd file). kin reads them all
through ReadFromURIFunc; NewSpecInfoWithCapture installs a recording wrapper
(on both the file and git paths) and returns them in SpecInfo.Sources, keyed
by resolved path -- which matches each element's origin File, so a consumer
can slice by origin file. Off by default; plain NewSpecInfo records nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): slice multi-file specs (file-aware index + per-file texts)

Make block extraction file-aware so a change in a schema $ref'd from another
file cards to that block and slices from the external file, not the
referencing operation in the root.

- span carries the file it lives in; smallestContaining matches (file, line)
  since line numbers aren't unique across files; blockKey keys on the change's
  source File.
- indexExternalSchemas (via WalkSchemas) indexes schemas $ref'd from another
  file by their ref, with the external file's origin span.
- Extract takes per-file text maps (base/revision) and slices each block from
  the file it lives in. open_review passes SpecInfo.Sources (captured on
  --open); normalDiff loads with capture only when --open is set.

Verified end to end: a cross-file role-type change cards to
"other.yaml#/components/schemas/User" and slices from other.yaml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): multi-file is sliced now (only bare fragments remain)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(review): the multi-file gap is narrow (only arbitrary-top-level-key refs)

Tested: per-schema files (./User.yaml) and components-structured files
(./defs.yaml#/components/schemas/User) slice correctly. Only a schema under an
arbitrary top-level key (./schemas.yaml#/User) loses its origin in kin's
generic-map path. Uncommon in idiomatic OpenAPI 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): each block reports the file it was sliced from

Add Block.BaseFile/RevFile (the source file's basename, git <rev>: prefix
stripped) so the review page can label a block with the file it came from,
which for a 'd-from-another-file block differs from the root spec. For a
single-file block it's the spec's own filename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(review): Extract indexes a set of docs (composed groundwork)

buildIndex is variadic and Extract takes []*openapi3.T per side, so a set of
specs (composed mode) share one file-aware index. Single-spec callers pass a
one-element slice. No behavior change yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): support composed mode with --open

A composed diff (-c) can now be reviewed with --open. Previously it was
rejected up front because the review compared exactly two specs; the cards
view removed that constraint (each change is sliced from whichever spec in
the set it lives in), so the rejection is gone.

- composedDiff captures every matched spec's file texts on --open, via a new
  load.NewSpecInfoFromGlobWithCapture (off by default, like the single-spec
  NewSpecInfoWithCapture).
- diffResult carries the per-side spec sets (one for a normal diff, N for
  composed); uploadAndOpen slices blocks from the whole set through one
  file-aware index, and reports each block's own file.
- A composed review has no single spec, so the full-spec fields stay empty
  (the cards carry the diff) and each side is labelled 'composed'.

Verified e2e: a composed changelog of two-file APIs cards a change to the
right operation and slices it from the correct spec file in the set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(review): index component security schemes as blocks

A component security-scheme change (scheme removed, type changed) is sourced
inside components/securitySchemes, which buildIndex didn't cover (only
components/schemas), so those changes got no block. SecurityScheme carries
Origin, so index it the same way. Fixes two changes lacking a block in the
review cards.

* review: drop redundant comment on security-scheme indexing

The securitySchemes loop mirrors the schemas loop above it and is covered by
TestExtract_SecuritySchemeBlock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(review): git-revision slices and composed duplicate-name blocks

Two extraction bugs, both reproduced end to end by decrypting real bundles.

1. Git-revision sources produced blocks with no source slices, and lost the
   source-line-to-block mapping (a component change fell back to its
   operation/Area instead of its own block):
   - the root document is loaded from bytes (LoadFromDataWithPath), so the
     recording ReadFromURIFunc never fired for it; record it directly
   - origins carry a "./" prefix on git "<rev>:<path>" paths (net/url prepends
     it when a relative path's first segment contains a colon) while capture
     keys don't; normalize the capture key and the text lookup
   - checker sources are display paths (the "<rev>:" prefix stripped) while
     span files are raw; fileMatches accepts both forms

2. In composed mode, a component name defined in several specs (a shared
   "Error" schema) sliced whichever file the key map kept last: a change could
   render a diff pane with identical text on both sides, from the wrong file.
   The index now keeps every span per key, the resolved span is carried
   through to slicing instead of re-looked up by key, an ambiguous fallback
   yields no slice rather than a possibly wrong one, and a colliding key is
   qualified by filename ("pets.yaml: components/schemas/Error") so each
   file's block stays a separate card. Top-level sections (info/servers/tags/
   security), which collide across every composed doc, get the same treatment.

Regression tests: composed duplicate component name, rev-prefixed file
matching, "./"-prefixed origin lookup, and git-revision capture of the root
and $ref'd files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): trim comments to rationale

Comments in the review bundle code narrated mechanics and enumerated cases
that the code and tests already show. Trim each to the non-obvious "why":
wire-shape constraints, the file-matching and "./"-prefix normalizations,
why the fallback yields no slice on ambiguity, why top-level sections are
key-line based. Fix the stale Extract signature in the package doc and list
security schemes and composed mode in the indexed block types. Also gofmt
two misformatted files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): never reach production or a real browser from unit tests

Test_OpenWithComposedAllowed stubbed OASDIFF_API_URL (the authenticated
endpoint) but ran the free path, which posts to OASDIFF_URL and defaults to
production: every test run uploaded two real bundles and opened two browser
tabs. Point the free upload at a non-connectable address, and add two
binary-wide guards so the class can't recur: TestMain defaults both upload
URLs to a non-connectable address (stub servers override per-test), and
openBrowser defaults to a no-op for the whole test binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): a component change groups per-endpoint reports, not dedupes them

Each referencing endpoint's report stays on the component's card with its own
operation and fingerprint; nothing is dropped or merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): speak in blocks, not cards

The review package produces blocks in a bundle; what a consumer renders them
as is not its concern. Remove the renderer vocabulary that crept back in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): drop renderer vocabulary from the package docs

The blocks section justified itself by a consumer's rendering costs (DOM
nodes, what a UI shows). State what Extract produces instead: each change
with its enclosing source text, without the full specs. What a consumer
does with the bundle is its own concern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): say change, not report

'Report' names nothing in this package; its objects are changes, blocks,
and fingerprints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): compress the block-selection rationale to one sentence

Keep the why of line keying (it exists for the $ref case); drop the
step-by-step walkthrough, which TestExtract_RefdComponentFollowsSourceLine
already demonstrates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): state when Blocks is empty instead of consumer guidance

Blocks is empty iff the changelog is empty (every change resolves to some
block via the fallback chain). The old sentence was consumer guidance and
wrong for composed bundles, whose full-spec fields are empty too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): describe the manifest by function, not by tier

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: blocks, not cards, in the CLI comments too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(review): mark composed bundles with an explicit field, not a magic filename

The CLI filled the filename fields with "composed", which doubled as display
text and, on the receiving side, as the composed-detection signal - an
undocumented magic-string contract that a spec file literally named
"composed" would trip. Payload.Composed now states the fact; the filename
fields stay empty and presentation is the consumer's choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(review): the filename qualifier is block identity, not title

Qualifying only colliding titles made one block read differently from its
siblings. Key keeps the file qualifier (identity must be unique); Title stays
the plain name; consumers label each block's file from BaseFile/RevFile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: renderChangelogJSON serves any consumer, not a specific page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: describe the payload changelog rendering without naming a private endpoint

The comment referenced a specific server's route; the general fact is the
standard object-wrapped JSON changelog shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: describe uploadAndOpen as it is, not against past designs

'There is no sign-in' documented the absence of a removed feature; the
current facts are that the upload is anonymous and zero-knowledge. Also
composed bundles carry no specs, so 'bundles the two specs' was stale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: from() records the capture on the file and git branches only

The doc claimed every load is recorded; URL and stdin loads ignore the
capture because the review bundle rejects those sources, so their text has
no consumer. State the constraint so a branch that gains a consumer also
gains the recorder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(review): support URL sources with --open

URL sources were rejected at launch because the bundle needs the raw spec
bytes and the loader does not retain them; re-fetching a remote, mutable URL
could disagree with the content the diff was computed from. The capture
dissolved that: the recorder now also wraps the URL branch (keyed by the
full URL, matching the origin File kin records for remote documents), and
readSpecSource serves URL bytes from the captured Sources, byte-identical
to what was diffed, with no second fetch. Blocks slice from the same texts.

Stdin remains unsupported (single-read stream; no capture consumer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(review): index all named component types

Responses, parameters, request bodies, and headers carry Origin like schemas
and security schemes did, but weren't indexed, so a change inside one (e.g.
a shared Error response) fell back to the referencing operation, whose slice
only shows the $ref. One generic helper indexes every named-component
section. Links, callbacks, and examples are left out: no check sources
changes inside them today.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix ci: yaml tags on Block, check f.Close in the stdin test

The repo requires yaml tags wherever json tags appear, and errcheck flags
the unchecked deferred Close that came in with the both-stdin test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix lint: unchecked Setenv in the TestMain guard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: gate OS-path-specific tests behind //go:build unix (fix Windows build)

TestNewSpecInfoWithCapture_RecordsAllFiles and TestExtract_CrossFileSchemaSlicedFromExternalFile
assert on OS-native $ref path resolution, which uses a different separator on Windows. Move each
into a //go:build unix file so it runs on Linux and macOS but not Windows, instead of comparing
platform-specific paths everywhere.

* review: drop dead base/revision versions from renderChangelogJSON

The encrypted-review path renders the changelog as JSON, and the JSON formatter
does not render versions (only HTML/Markup do, now via constructor injection in
#1085). So plumbing baseVersion/revVersion through renderChangelogJSON, and
computing specSetVersion to feed it, was dead. Remove the params, the FormatterOpts
versions, and the now-orphaned specSetVersion.

* review: drop no-op ColorNever and its comment from renderChangelogJSON

JSON output is never colored (the JSON formatter ignores opts.ColorMode), so
forcing ColorNever, and the comment explaining it, were both irrelevant.

* review: use maps.Copy for the source-text union in specSetDocsAndSources

Replaces the manual key/value copy loop; identical last-write-wins behavior on
duplicate keys (same file path across a composed spec set resolves to the same
text, so overwrite is a no-op).

* review: document why the source-text union is safe

Sources is keyed by resolved file path, so a repeated key across a composed spec
set is the same shared $ref'd file with identical text, making the last-write
overwrite a no-op. Records the invariant the merge relies on.

* load: combine NewSpecInfo and NewSpecInfoWithCapture

They differed only in whether a source recorder is installed. Extract a shared
newSpecInfoFromSource(loader, source, capture bool, options...) and make both
public constructors thin wrappers, mirroring the existing
NewSpecInfoFromGlob / newSpecInfoFromGlob pair. Public API unchanged.

* review: drop unreachable source-type guard in readSpecSource

With stdin and URL handled by the early returns above, only file and
git-revision sources can reach the guard, which is exactly what it permitted, so
it never fired. ReadRaw's own default already rejects any other type.

* diff: name the --open loader choice (loaderForOpen)

normalDiff and composedDiff both branched on --open to pick the capturing loader
variant. Extract loaderForOpen so the name and its doc state the --open-to-capture
relationship once, instead of an unnamed if in each.

* load: split git-revision loading into git.go; add withCapture helper

Move the eight git-revision functions (loadFromGitRevision, readGitRefContent,
hintMissingObject, refBeforeColon, gitFetch, commitExistsLocally, blobHashFromRef,
gitShow) out of load.go into git.go, leaving load.go with just the from dispatcher
and the URL helpers. Also extract withCapture so from's URL and file branches stop
repeating the copy-loader-and-install-recorder dance; behavior unchanged.

* keep comment on one line to fix alignment

* load: fix sourceCapture doc — drop caller naming, ground the origin-key match

The old comment named a single caller (NewSpecInfoWithCapture), which is both
inaccurate (the glob-with-capture path populates it too) and the kind of
caller-listing a type doc shouldn't carry. Restate the install condition
behaviorally (a capture is passed to the load), and say the key matches
origin.Key.File because recordingReader replicates kin's derivation, rather than
asserting the match as if automatic.

* load: move the recorder install-cost note onto recordingReader

The 'installed only when capturing; ordinary loads pay nothing' note belongs on
the recorder, not on the sourceCapture data structure.

* load: key captured sources by location.String() to match origin.Key.File

recordingReader keyed local files by filepath.FromSlash(location.Path); on
Windows that yields backslashes while kin sets origin.Key.File to
location.String() (forward slashes), so review.Extract's lookup by origin File
missed and cross-file blocks came back empty. Key by location.String()
everywhere (the git root's manual record too), matching kin exactly; the '.' /
'./' trim on both the record and the lookup handles git's colon-prefixed paths.

Add cross-platform correspondence tests (local file, URL, git revision) that
assert a $ref'd element's text is findable by its origin File via the same
lookup review.Extract uses. These replace prose with proof and run on Windows,
where the old //go:build unix capture tests could not.

* test: trim capture correspondence header comment

* review: use checker.Fingerprint after the move out of formatters

Adapts review/payload.go and review/blocks.go to #1086 (ComputeFingerprint moved
to checker), switching to checker.Fingerprint(change) and dropping the formatters
import, so the review package no longer depends on the output layer.

* load: simplify blobHashFromRef with strings.Cut (gopls modernize)

* load, review: drop vestigial './' normalization; keys are origin.Key.File verbatim

recordingReader keys captured sources by location.String(), which is exactly
what kin records as origin.Key.File -- including the './' net/url prepends to
colon-first git paths. So record()'s TrimPrefix('./') and textFor()'s './'
fallback were leftovers from the old FromSlash(location.Path) keying: they
cancel out (the strip broke the exact match the fallback then repaired). Remove
both, so a captured file is found by a change's origin File with a direct
lookup. textFor was then a one-line map access, so inline it.

Verified: exact-match-only lookup passes for local-file, URL, and git-revision
loads (the load/ correspondence tests).

* review: test the external-ref './' strip; inline redundant textForOrigin

The './' strip in indexExternalSchemas is load-bearing, not vestigial: kin
preserves the authored './' in sr.Ref, so a ref written 'other.yaml' on the base
side and './other.yaml' on the revision side would key differently without it and
resolve() would emit delete + add instead of pairing the two into one block. No
test covered that asymmetric case (existing fixtures use the same spelling on
both sides), so add TestExtract_CrossFileRefStyleDiffersBetweenSides -- it fails
if the strip is removed.

Also inline textForOrigin: once its './' fallback was dropped (keys now match the
origin File verbatim) it was a bare comma-ok map access.

* test: consolidate capture<->origin correspondence into one source of truth

The capture-key == origin.Key.File invariant (what review.Extract's slice lookup
depends on) was spread across four overlapping partial tests, two keyed on
hardcoded literals rather than the actual origin. Consolidate into a single
assertCapturedByOrigin used by the local-file / URL / git-revision tests: it
checks both the root and the $ref'd file by each element's origin File, with a
direct lookup, plus the exact captured-file count.

Removed as subsumed: TestNewSpecInfoWithCapture_URL (hardcoded URL key),
TestLoadInfo_GitRevisionCapture (hardcoded ./HEAD: keys), and the unix-only
TestNewSpecInfoWithCapture_RecordsAllFiles (the consolidated local-file test now
covers it cross-platform). Kept TestNewSpecInfo_NoCapture (capture-off).

Verified it catches both regressions this work identified: re-adding record()'s
'./' strip fails the git case; reverting the recorder to FromSlash(location.Path)
fails the URL and git cases.

* test: cover the composed (glob) capture path in the correspondence test

Composed --open loads specs via NewSpecInfoFromGlobWithCapture, whose per-spec
capture was untested. Add TestCaptureFoundByOriginFile_ComposedGlob: a two-root
glob sharing a $ref'd file, running the same assertCapturedByOrigin per returned
spec. The capture<->origin invariant is now pinned for the local-file, URL,
git-revision, and composed source types under one assert.

Confirmed per-spec capture is complete: kin re-reads a shared $ref on each
top-level load, so every spec captures it, not just the first.

* repin kin-openapi to v0.141.0

The end-position (getkin#1207) and WalkSchemas (getkin#1206) support this
branch builds on is now in a tagged release; drop the master pseudo-version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review fixes: composed same-basename disambiguation, capture dedup

Composed sets commonly repeat a basename across directories (one
openapi.yaml per service). The block-key qualifier and the cross-side span
lookup disambiguated by basename, so same-named components in same-named
files merged into one block showing one file's slices, and the cross-side
lookup could pick the other file's span. The qualifier now uses a
directory-and-file display ("svc-a/openapi.yaml") and the span lookup
matches the full origin path; regression test with committed fixtures.

Also: a sourced change now upgrades the span-less resolution a
fallback-keyed change stored first for the same key; the two inlined copies
of the capture-wrap in the glob and git load paths use withCapture; a
paragraph wrap in the package doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: pin cross-file source locations; document the arbitrary-key $ref gap

Two external-$ref shapes, exercised through the real diff + checker so the
asserted source is the one users get in -f json and the review bundle:

- Whole-file ref (./user.yaml): passes; pins that a property change inside the
  external file sources at that file and line.
- Arbitrary top-level key ref (./schemas.yaml#/User, the Swagger-2-era
  definitions bag): the released kin-openapi resolves it through a generic map
  that drops the origin, so the change sources at the operation in the ROOT
  file (wrong file, wrong line) and its review block slices from there.
  Asserted at both levels (checker Source, review.Extract), skipped with the
  reason, ready to unskip when the kin fix is released.

Verified both ways: the skipped tests fail against kin v0.141.0 exactly as
described, and pass against the pending kin origin fix via a local replace.

* bump kin-openapi past the arbitrary-key origin fix; unskip its tests

kin now preserves origins for a $ref to a schema under an arbitrary top-level
key, so TestCrossFileSource_ArbitraryTopLevelKeyRef and
TestExtract_ArbitraryTopLevelKeyRefSlicedFromExternalFile run live: the change
sources at the schema's own file and line, and its review block slices from
that file. Master pseudo-version pending the next kin release; it includes the
StringMap removal, so mappingToStringMap takes the plain map type.

* review: doc.go reflects the upstream arbitrary-key origin fix

The definitions-bag shape now slices from its own file, so it moves from the
known-gaps list to the supported external-ref shapes, pointing at its test.

* load: record why glob members bypass from() and capture per member

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: fmt.Appendf instead of []byte(fmt.Sprintf) (gopls modernize)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: document docIndex fields

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: dedup docIndex comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: split the cross-file test into index and extract concerns

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: split the ref-style test into index and extract concerns

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: cross-side file correspondence by longest path suffix; cover blocks.go branches

The strict full-path match in spanFor could never succeed across sides
that live in sibling directories (base/svc-b/openapi.yaml vs
revision/svc-b/openapi.yaml), leaving the other pane empty for one-sided
changes in composed sets. Correspondence is now the longest path-suffix
match in segments, unique or nil: exact paths win outright, a shared
directory segment tells same-named files apart, and a tie stays nil (a
missing slice is safe, the wrong file's slice is not).

Also: remove an unreachable resolution-upgrade branch (a fallback key with
no spans cannot collide with a sourced change's key), and cover the
remaining branches with unit tests: fileBase/fileDisplay forms, the Area
fallback, nil docs in buildIndex, the last top-level section running to
EOF, and the cross-side lookup itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: pin spanFor tie safety

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover the git-prefix strip in suffix correspondence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: build the id->Area map lazily, not at package init

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: explain why schema-area changes skip the Area bucket

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: single-source the Area-to-section correspondence

fallbackKey bucketed a locator-less change by its Area name unless the
area was schema, while addTopLevelSections indexed four section names; the
correspondence between the two lists was implicit, and areas in the gap
(components, paths) produced sliceless buckets with location-shaped titles.
The indexed section names are now one shared list, and fallbackKey buckets
by Area only when the area names one of them, so every Area bucket is a
real block with a slice and everything else goes to "Other".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: anchor topLevelSections to the Area constants

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* checker: add AreaInfo and AreaServers; derive topLevelSections from areas

The area taxonomy now covers every top-level document section a rule could
target. info is annotation-only and servers changes are treated as
deployment metadata, so neither has rules yet; the review package's
top-level section list is now derived entirely from the Area constants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

v1.23.0

Toggle v1.23.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
docs: note the response-header limitation in NULLABILITY.md (#1094) (#…

…1095)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

v1.22.0

Toggle v1.22.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix lint: unchecked deferred Close in the both-stdin test (#1084)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

v1.21.0

Toggle v1.21.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
checker: phrase type generalize/specialize messages as "widened"/"nar…

…rowed" (#1067)

"the type was specialized from number to integer" reads awkwardly. Reword the
nine type generalize/specialize messages to "was widened" / "was narrowed",
plain English that matches the wording the list-of-types messages already use.
Covers request body/property/parameter and response body/property, in all four
locales.

Only the human-facing message text changes; the rule ids and the Action
taxonomy keep the generalize/specialize terms (as do the -description labels,
which mirror the ids). Updates the affected test assertions.


Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

v1.20.1

Toggle v1.20.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
diff: model security requirement alternatives as structured, index-ke…

…yed values (#1044)

* diff: model security requirement alternatives as structured, index-keyed values

Follow-up to #1043. A security list is an unordered set of OR-alternatives
that can't be keyed by a string (several may share a scheme). Model it like
SubschemasDiff/ModifiedSubschemas instead of stringly-typed lists:

- Added/Deleted are now SecurityAlternatives ([]SecurityAlternative), each
  carrying its index plus its schemes and scopes, with a String() method for
  rendering (so the formatting lives on the type, not in the diff loop).
- Modified is now ModifiedSecurityRequirements ([]*ModifiedSecurityRequirement)
  (a slice, like ModifiedSubschemas, to avoid a composite map key), each with
  Base/Revision identity and the per-scheme scope diff.

This makes a removed/added alternative unambiguous when several share a scheme
(the diff carries the scopes structurally, e.g. {index: 2, schemes: {petstore_auth: [admin:pets]}}),
and the changelog renders it via String() as `petstore_auth: [admin:pets]`.

Note: this changes the diff output schema for security added/deleted (string ->
object) and modified (map -> list). Checker and report consumers updated; the
changelog message wording carries the scopes (index kept in the structured
output only, since it is positional).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* diff: clarify the security-alternative identity comment

"Alternatives have no name" was imprecise: a scheme name like petstore_auth
is the name of a scheme inside the alternative. The accurate point is that an
alternative (a map of scheme->scopes) has no identity of its own: scheme names
aren't a unique key (several alternatives may share one), and an alternative may
AND several schemes. Hence index-based identity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* diff: expand the security-requirements doc comment with the OR/AND semantics

Spell out the rules that drive the index-based modeling: the list is an OR,
schemes within an item are AND-ed, scopes within a scheme are AND-ed, so an OR
of scopes for one scheme requires repeating the scheme. Note that a scheme name
is only a reference into components.securitySchemes (an author-chosen label
whose meaning is diffed separately in SecuritySchemesDiff), so it isn't an
identity here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* diff: render an empty security requirement ({}) explicitly in String()

A security alternative can be the empty requirement object `{}` (anonymous /
no-auth alternative), which has no schemes at all, so String() previously
returned "" and the global-security changelog message showed empty backticks.
Render it as "{}" and note this in the doc comment, which previously only
covered a scheme with empty scopes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* diff: unit-test SecurityAlternative.String()

Table test covering each branch: empty requirement ({}) and nil schemes, a
scheme with no scopes (bare name), a scheme with scopes (sorted), and several
schemes AND-ed (sorted by name). The {} and multi-scheme cases were only
exercised indirectly before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* report: label a modified security requirement by scheme name, not full String()

The text report showed "Modified security requirements: petstore_auth: [read:pets]"
right above "Added scopes: [list:pets]", so the base scopes read as noise and
were easy to misread against the delta beneath. Add SecurityAlternative.SchemeNames()
(sorted, AND-joined scheme names) and use it for the modified label, restoring
the pre-remodel "petstore_auth" header. Added/Deleted keep the full String()
(with scopes), where the scopes are the identity and there's no detail below.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* diff: lock the invariant that a modified requirement has a unique scheme set

The report labels a modified security requirement by scheme name only, which is
unambiguous because the scope-modification pass only fires for an unambiguous
scheme set (exactly one unmatched alternative with that set per side). Add a
test proving that two same-scheme alternatives both changing scopes are reported
as deleted + added (carrying full scopes), not as same-scheme modified entries,
so a future matcher change can't silently make the label ambiguous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

v1.20.0

Toggle v1.20.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
refactor(checker): rename SCREAMING_SNAKE_CASE stability constants to…

… MixedCaps (#1032)

STABILITY_DRAFT / ALPHA / BETA / STABLE use a C/Python casing convention,
not Go. Rename them to StabilityDraft / StabilityAlpha / StabilityBeta /
StabilityStable and migrate all internal usages. The values ("draft",
"alpha", "beta", "stable") are unchanged, so no CLI or serialized behavior
changes.

The old names are kept as deprecated aliases, since they are exported and
a library consumer may reference them; they can be dropped in a future
major release.

Closes #1031.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

v1.19.1

Toggle v1.19.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(open): write --open URL and browser notice to stderr, not stdout (#…

…1011)

`oasdiff changelog --format json --open > out.json` appended the human-facing
"Opening <url> (expires ...)" line (and any browser-fallback notice) to stdout,
right after the rendered changelog, producing invalid JSON/YAML. The recent
non-fatal-warning work already routes --open *errors* to stderr; this does the
same for the success path. uploadAndOpen now writes its URL/guidance to a writer
the caller sets to os.Stderr, so stdout carries only the rendered changelog.

Verified: `changelog --format json --open` now emits valid JSON on stdout with
the URL/notice on stderr.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Morty Proxy This is a proxified and sanitized view of the page, visit original site.