Skip to content

openapi:roundtrip \xe2\x80\x94 drift gate for openapi \xe2\x86\x92 spec \xe2\x86\x92 openapi #164

@tonydspaniard

Description

@tonydspaniard

Part of #160. Depends on #161 (emitter), #162 (CLI), and #163 (extensions).

Goal

Add bin/altair openapi:roundtrip \xe2\x80\x94 a CI-friendly check that imports an OpenAPI 3.1 document via the emitter, re-emits it via spec:emit-openapi, and fails if the result differs from the input beyond documented normalization. Same idea as the existing spec:emit-sdk --check drift gate, applied to the OpenAPI direction.

Why

Without this gate, the import path silently degrades. Someone refactors the emitter, an extension stops round-tripping, the import-then-edit-then-re-emit workflow starts losing data, and no test catches it because each piece is green in isolation. The gate is what makes the import path safe to depend on.

It also pays a forward dividend: every new x-altair-* extension automatically has a meaningful integration test \xe2\x80\x94 if it doesn't round-trip, the gate fails on the fixture that uses it.

Normalization (documented allowable differences)

The gate is not naive diff. The following differences between input and re-emitted output are expected and don't fail the check:

  • Key order. Output is alphabetical; input is whatever order the author chose.
  • Empty optional arrays. required: [], tags: [], parameters: [] are omitted in output, may be present in input.
  • Tags. The emitter derives tags from path segments; hand-set tags in the input are preserved through x-altair-tags but raw tags arrays are normalized (warn, don't fail).
  • Whitespace + comments. Lost in YAML round-trip; OpenAPI 3.1 doesn't define comment semantics. (Warn, don't fail.)
  • default: null on nullable schemas. Output omits; input may include. (Strict semantic equality.)

Every normalization rule lands in docs/openapi/roundtrip.md with a code example.

A semantic difference (operation lost, schema property dropped, extension stripped) fails the check loudly with a diff localised by JSON pointer.

CLI surface

bin/altair openapi:roundtrip openapi.yaml                 # human report
bin/altair openapi:roundtrip openapi.yaml --check         # exit 1 on drift (CI mode)
bin/altair openapi:roundtrip openapi.yaml --format=json   # structured diff for agents
bin/altair openapi:roundtrip --fixtures=tests/fixtures/openapi/   # batch over a directory

Acceptance criteria

  • Round-trip is clean on every doc in tests/fixtures/openapi/ (Petstore-class + each extension exercised)
  • Diff report (human + JSON) localises drift by JSON pointer with before/after
  • CI gate runs in the framework's own CI on the fixtures and is non-zero on drift
  • Normalization rules documented at docs/openapi/roundtrip.md
  • Test: a deliberately broken emitter produces a non-zero exit + actionable diff (proves the gate works)
  • Test: a x-altair-* extension that's silently dropped on re-emit is caught by the gate
  • JSON receipt is byte-stable for the same input (golden test)

Out of scope

  • Auto-fixing drift. The gate reports; the engineer fixes.
  • Cross-version OpenAPI checks (3.0 \xe2\x86\x94 3.1 conversion). 3.1 only.

Notes

Mirror the receipt shape from spec:emit-sdk --check so agents that already know how to read one drift gate can read this one without new prompting. Consistency across --check commands is itself a usability feature.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions