Use oasdiff to compare a committed Loom OpenAPI contract with a regenerated
revision. This workflow reports compatibility changes before they reach API
consumers.
Loom does not include a diff engine. The consuming repository owns its oasdiff version, policy, severity overrides, and ignores.
Loom shares automatic request-body, parameter and header definitions only when their contents, including examples, agree. Response components compare the HTTP status and complete response shape, except for the top-level description. Operations can share a data schema while retaining different examples in their own definitions. Changing an example can therefore remove an automatic component and replace its references with inline definitions. These automatic references are not stable identifiers. Explicitly authored component names and schema names retain their existing contracts.
Generation rejects duplicate operationId values and duplicate method/path
slots, including collisions with file servers. Different parameter names do
not distinguish path templates, even across methods: /items/{id}.json and
/items/{slug}.json collide. Use one spelling for a shared template; different
methods on that same path remain valid.
For OpenAPI 3.2, an explicit openapi:component:mediaType name can be shared
only when the complete projected media definitions agree, including schemas,
examples, encodings and extensions. Conflicting definitions produce a
generation error instead of overwriting one another. Regenerate after
upgrading, and resolve any identity diagnostics before publishing the contract.
For both OpenAPI 3.1 and 3.2, operation-specific response descriptions appear
beside $ref. The shared component has a neutral status description:
responses:
"400":
$ref: '#/components/responses/BadRequestError'
description: |-
invalid_account: Account is invalid.
invalid_metadata: Metadata is invalid.Changing only that description does not create another component or change its automatic name. Different schemas, headers, media types, links, summaries, extensions or examples still prevent sharing. Explicit component names remain separate. An explicitly omitted 3.2 description retains its omission policy and 3.1 fallback text. Unnamed single-use responses can remain inline.
Regenerate OpenAPI after upgrading. Tools that read response descriptions must
apply Reference Object description overrides, including an explicit empty string,
instead of reading only components.responses. This is standard
OpenAPI 3.1 Reference Object behavior,
also supported by OpenAPI 3.2. Existing automatic component names and hash
suffixes can change during this migration; there is no legacy allocation mode.
Review both the OpenAPI diff and regenerated SDK and validator output when updating Loom. Moving an equivalent definition inline preserves its contract, but a client generator may produce different declarations or exports. Schema equivalence alone does not prove compatibility of the generated client API.
Authored defaults and enums remain available in generated contracts for text, raw and custom codec locations, including cookies and mapped SSE data. Loom checks the declared value and any known builtin constraints; a retained annotation does not verify the output of a custom codec. Regenerate after upgrading if a valid design previously failed annotation preparation.
For both targets, Loom projects MinLength and MaxLength according to the
value on the wire: string bounds use minLength and maxLength, array bounds
use minItems and maxItems, and map entry counts use minProperties and
maxProperties. Named and aliased maps keep their effective bounds. Byte
lengths use the separate binary or base64 representation rules.
Regenerate contracts after upgrading the map projection. Earlier versions emitted string-length keywords for maps, which JSON Schema validators ignore for object values. Review validator behavior at the declared map boundaries.
Loom uses the registered unsigned integer formats:
UInt32 emits uint32, while UInt64 and native UInt emit uint64.
Signed integer formats and authored numeric bounds retain their behavior.
Regenerate contracts and review generated clients after upgrading from the
earlier signed annotations. Formats are annotations with optional validator
support; this correction does not change the JSON numeric representation or
replace explicit numeric assertions.
Loom emits OpenAPI 3.2 by default. oasdiff v1.33.0 reads 3.2 documents, but
its documentation says that 3.2 coverage remains under development.
Use Loom's 3.1 compatibility target for this conservative baseline:
var _ = API("MyAPI", func() {
Meta("openapi:version", "3.1")
Meta("openapi:output", "json")
})OpenAPI 3.1 support is generally available in oasdiff. Review the oasdiff OpenAPI support guide before changing this target.
This guide uses these tested versions:
- oasdiff CLI
v1.33.0 - oasdiff action
v0.1.18, commitb9325c9e0a27ab65b0da3b766522cedec6be81dc
Install the pinned CLI without adding it to the service module:
go install github.com/oasdiff/oasdiff@v1.33.0Pin the action to its full commit SHA in CI. Keep the release tag in a comment so reviewers can identify the version.
For an upgrade:
- Read the oasdiff CLI and action release notes.
- Update both pins in one change.
- Review OpenAPI version support and changed checks.
- Run the normal comparison and the known-breaking canary below.
- Review every policy or ignore-file change separately.
Replace the design package if your module uses another path.
DESIGN_PACKAGE=example.com/myservice/design
OPENAPI_SPEC=gen/http/openapi.json
go tool loom gen "$DESIGN_PACKAGE"
git diff --exit-code -- gen/http/openapi.json gen/http/openapi.yaml
oasdiff breaking \
--fail-on ERR \
--format githubactions \
--allow-external-refs=false \
-- \
"origin/main:${OPENAPI_SPEC}" \
"$OPENAPI_SPEC"Run the command from the repository that contains the generated specification.
Fetch origin/main first when the local Git object is missing.
--fail-on ERR blocks definite breaking changes. oasdiff still reports WARN
findings for review. Use --fail-on WARN when project policy blocks both
levels.
Create a non-blocking Markdown changelog for review:
oasdiff changelog \
--format markdown \
--allow-external-refs=false \
-- \
"origin/main:${OPENAPI_SPEC}" \
"$OPENAPI_SPEC" \
> openapi-changelog.mdThe changelog command does not set a blocking threshold in this recipe.
Run this canary when adopting or upgrading oasdiff:
- Create a temporary branch.
- Remove one published success response from the Loom design.
- Regenerate the OpenAPI contract.
- Run the local
oasdiff breakingcommand above. - Make sure the command exits with status
1and reports anERRfinding. - Revert the temporary design change and regenerate.
This canary proves that the selected version and project policy block a known breaking change.
Copy the pinned GitHub Actions example to
.github/workflows/oasdiff.yml. Replace DESIGN_PACKAGE when required.
The example performs these tasks in order:
- Fetch the pull request's base branch.
- Regenerate the Loom contract.
- Fail when committed JSON or YAML output is stale.
- Add a non-blocking Markdown changelog to the job summary.
- Report
WARNfindings and blockERRfindings.
The revision input reads the regenerated working-tree file. The base input uses the committed file from the pull request's base Git revision.
Store project policy in the consuming repository. Common files include:
.oasdiff.yaml- a severity override file
- a narrow
ERRignore file - a narrow
WARNignore file
Loom does not generate or overwrite these files. Give each ignore an owner, reason, and removal condition. Prefer a severity override when project policy classifies a check differently.
Start with ERR as the blocking threshold and review every WARN. A project
can later block WARN after it has removed noisy or ambiguous findings.
The CLI resolves external $ref values by default. Keep
--allow-external-refs=false for untrusted pull requests.
The oasdiff action defaults this setting to false. The example sets it
explicitly. Enable external references only after defining safe file and
network loading rules.
The action also enables encrypted hosted review by default. It uploads an encrypted comparison to oasdiff.com and can add a review link to the pull request.
Set review: false when no specification data can leave CI. The example uses
this setting. For the CLI, omit --open to keep review data local.
| Contract area | Required coverage |
|---|---|
| Published standard OpenAPI fields | oasdiff breaking gate and changelog |
| Generated transport responses | Loom response-contract scaffold |
x-loom-* extension behavior |
Loom-owned focused tests |
| Business behavior and fixtures | Application-owned tests |
oasdiff can report text changes inside extensions. It cannot prove the runtime
meaning of Loom-specific extensions such as x-loom-async.
Use both layers. oasdiff protects the published standard contract, while Loom scenarios prove that the implementation produces each declared response.