fix(spec): mirror FrameStatusKind/FrameStatusKinds into openapi.yaml #26

Merged
peterswimm merged 1 commit from fix/mirror-frame-status-kinds-into-openapi into main 2026-10-28 02:33:01 +00:00
Owner

Scope

frame_wire 1.5 added FrameStatusKind and FrameStatusKinds to
schemas/frame_wire.json — and only there. For two minor versions the contract's two
documents disagreed about what the contract contains: 40 of 42 $defs were mirrored
in openapi.yaml's components.schemas, and these two were not.

Nothing here could see it. test_the_two_spec_files_agree asks about exactly two
names, FrameStatus and FrameType, because those are what the generator owns. The broad
question — is every $def mirrored — was never asked, so this suite stayed green
throughout (46 passed, 1 xfailed on main today).

It was caught downstream, and late. ritual-engine vendors this contract, and its
frame-wire-contract crate does assert full $defs parity. But it runs against a
vendored snapshot, and that snapshot was two minor versions behind — so it was
asserting parity for 1.4 and passing. The disagreement surfaced only when the snapshot was
refreshed, days after 1.6 merged, and it surfaced as two red tests in a re-vendor PR that
had copied everything correctly.

Each repository held a guard the other needed: this one has the live documents and no
parity test; that one has the parity test and a stale copy of the documents.

What the gap cost. FrameStatusKinds carries is_terminal, is_active_pickup,
is_pre_admission and surfaces_in_queue per status as a generated const, and its own
description says why it exists:

so a client can decide how to present a status — is it finished, is it waiting on a
human, does it belong in a queue — from data rather than from a hardcoded switch that
goes stale the next time a row is added.

A client generated from openapi.yaml could not reach any of it, and would have to
write exactly that switch. That is not hypothetical: ritual-engine had written it eight
times over, in four mutually inconsistent lists, none matching the table — see
ritual-engine #1157, measured the same week.

What this changes

openapi.yaml 0.69.0 → 0.70.0. Additive: two new components, no endpoint, request or
response change.

The mirrored copies were generated from frame_wire.json with $defs refs repointed
at components/schemas, not retyped — so they are identical up to the ref prefix by
construction rather than by proofreading.

Two tests, each mutation-checked:

test what it asks killed by
test_every_wire_def_is_mirrored_in_openapi every $def exists in components.schemas deleting FrameStatusKind from openapi.yaml
test_the_generated_status_metadata_is_identical_in_both_documents the two generated copies match flipping one is_terminal in openapi.yaml's copy

The first has no documented-gap set, deliberately — unlike the ForgeBlock union,
which lags for a stated reason and is pinned as such. A $def on the wire that
openapi.yaml does not define means a client generated from one document cannot parse
what a server generated from the other emits, and there is no version of that worth
tolerating.

The second exists because const is regenerated from core.frame_status_kinds.
Without it, this PR would be a one-time fix that drifts again the next time a row is added
to that table — the same class as the five-versus-nine frame_type disagreement this
module's docstring already records.

Testing

50 passed, 1 xfailed    (main today: 46 passed, 1 xfailed)

Both new tests were confirmed red before the fix — test_every_wire_def_is_mirrored_in_openapi
was written first and failed with exactly schemas/frame_wire.json defines ['FrameStatusKind', 'FrameStatusKinds'], openapi.yaml does not, which is the whole reason
this PR exists. openapi.yaml re-parses and components.schemas goes 167 → 169.

Deliberately NOT added: a broad content-parity test, and the reason is written into
the test file rather than left as an omission. The two documents express the same schemas
differently — OpenAPI wraps a bare $ref in allOf, folded scalars add trailing newlines
— so 36 of the 42 shared defs differ textually while agreeing semantically. I measured
that rather than assuming it, and confirmed whitespace normalisation alone does not close
it. Comparing them properly needs the reducer in ritual-engine's
frame-wire-contract/tests/parity.rs, which collapses those wrappers. Half-porting it
here would be a weak test wearing a strong name, which is the failure mode this contract's
own history keeps producing.

One self-correction worth recording: the first draft of
test_the_status_metadata_const_covers_every_listed_status asserted
kinds == sorted(kinds, key=lambda s: kinds.index(s)) — which always holds. A check that
cannot fail was removed rather than left in; the surviving assertion compares the const's
status set against the FrameStatus enum, which can and does differ.

Impact Statement

No breaking change. Two components added; nothing removed, renamed or re-typed. A
client generated from 0.69.0 keeps working — it simply had no way to name these schemas.

Who should regenerate: any client generated from openapi.yaml that wants the status
metadata. Clients generated from schemas/frame_wire.json already had it.

This unblocks a ritual-engine re-vendor that is currently red on these two tests.
That PR copies this repo verbatim, so it cannot be fixed on its side — editing a vendored
file is what MANIFEST.sha256 exists to catch, and the vendoring README forbids it by
name. The order is: merge this, then re-vendor at the resulting main commit.

What this does not touch: schemas/frame_wire.json and schemas/forge_block.json are
unchanged, so frame_wire stays 1.6 and forge_block stays 1.3. The ForgeBlock union's
documented 15-versus-20 mirror gap is untouched and still pinned by its own test — it lags
for a stated reason, which is a different thing from lagging unnoticed.

## Scope `frame_wire` 1.5 added **`FrameStatusKind`** and **`FrameStatusKinds`** to `schemas/frame_wire.json` — and only there. For two minor versions the contract's two documents disagreed about what the contract contains: **40 of 42 `$defs` were mirrored** in `openapi.yaml`'s `components.schemas`, and these two were not. **Nothing here could see it.** `test_the_two_spec_files_agree` asks about exactly two names, `FrameStatus` and `FrameType`, because those are what the generator owns. The broad question — *is every `$def` mirrored* — was never asked, so this suite stayed green throughout (46 passed, 1 xfailed on `main` today). **It was caught downstream, and late.** `ritual-engine` vendors this contract, and its `frame-wire-contract` crate *does* assert full `$defs` parity. But it runs against a **vendored snapshot**, and that snapshot was two minor versions behind — so it was asserting parity for 1.4 and passing. The disagreement surfaced only when the snapshot was refreshed, days after 1.6 merged, and it surfaced as two red tests in a re-vendor PR that had copied everything correctly. > Each repository held a guard the other needed: this one has the live documents and no > parity test; that one has the parity test and a stale copy of the documents. **What the gap cost.** `FrameStatusKinds` carries `is_terminal`, `is_active_pickup`, `is_pre_admission` and `surfaces_in_queue` per status as a generated `const`, and its own description says why it exists: > so a client can decide how to present a status — is it finished, is it waiting on a > human, does it belong in a queue — from data rather than from a hardcoded switch that > goes stale the next time a row is added. **A client generated from `openapi.yaml` could not reach any of it**, and would have to write exactly that switch. That is not hypothetical: `ritual-engine` had written it eight times over, in four mutually inconsistent lists, none matching the table — see `ritual-engine` #1157, measured the same week. ## What this changes **`openapi.yaml` 0.69.0 → 0.70.0.** Additive: two new components, no endpoint, request or response change. The mirrored copies were **generated from `frame_wire.json`** with `$defs` refs repointed at `components/schemas`, not retyped — so they are identical up to the ref prefix by construction rather than by proofreading. **Two tests, each mutation-checked:** | test | what it asks | killed by | |---|---|---| | `test_every_wire_def_is_mirrored_in_openapi` | every `$def` exists in `components.schemas` | deleting `FrameStatusKind` from `openapi.yaml` | | `test_the_generated_status_metadata_is_identical_in_both_documents` | the two generated copies match | flipping one `is_terminal` in `openapi.yaml`'s copy | The first has **no documented-gap set**, deliberately — unlike the `ForgeBlock` union, which lags for a stated reason and is pinned as such. A `$def` on the wire that `openapi.yaml` does not define means a client generated from one document cannot parse what a server generated from the other emits, and there is no version of that worth tolerating. The second exists because `const` is **regenerated** from `core.frame_status_kinds`. Without it, this PR would be a one-time fix that drifts again the next time a row is added to that table — the same class as the five-versus-nine `frame_type` disagreement this module's docstring already records. ## Testing ``` 50 passed, 1 xfailed (main today: 46 passed, 1 xfailed) ``` Both new tests were confirmed **red before the fix** — `test_every_wire_def_is_mirrored_in_openapi` was written first and failed with exactly `schemas/frame_wire.json defines ['FrameStatusKind', 'FrameStatusKinds'], openapi.yaml does not`, which is the whole reason this PR exists. `openapi.yaml` re-parses and `components.schemas` goes 167 → 169. **Deliberately NOT added: a broad content-parity test**, and the reason is written into the test file rather than left as an omission. The two documents express the same schemas differently — OpenAPI wraps a bare `$ref` in `allOf`, folded scalars add trailing newlines — so **36 of the 42 shared defs differ textually while agreeing semantically**. I measured that rather than assuming it, and confirmed whitespace normalisation alone does not close it. Comparing them properly needs the reducer in `ritual-engine`'s `frame-wire-contract/tests/parity.rs`, which collapses those wrappers. Half-porting it here would be a weak test wearing a strong name, which is the failure mode this contract's own history keeps producing. **One self-correction worth recording:** the first draft of `test_the_status_metadata_const_covers_every_listed_status` asserted `kinds == sorted(kinds, key=lambda s: kinds.index(s))` — which always holds. A check that cannot fail was removed rather than left in; the surviving assertion compares the `const`'s status set against the `FrameStatus` enum, which can and does differ. ## Impact Statement **No breaking change.** Two components added; nothing removed, renamed or re-typed. A client generated from 0.69.0 keeps working — it simply had no way to name these schemas. **Who should regenerate:** any client generated from `openapi.yaml` that wants the status metadata. Clients generated from `schemas/frame_wire.json` already had it. **This unblocks a `ritual-engine` re-vendor** that is currently red on these two tests. That PR copies this repo verbatim, so it cannot be fixed on its side — editing a vendored file is what `MANIFEST.sha256` exists to catch, and the vendoring README forbids it by name. The order is: merge this, then re-vendor at the resulting `main` commit. **What this does not touch:** `schemas/frame_wire.json` and `schemas/forge_block.json` are unchanged, so `frame_wire` stays 1.6 and `forge_block` stays 1.3. The `ForgeBlock` union's documented 15-versus-20 mirror gap is untouched and still pinned by its own test — it lags for a stated reason, which is a different thing from lagging unnoticed.
fix(spec): mirror FrameStatusKind/FrameStatusKinds into openapi.yaml
All checks were successful
PR policy check / policy-gate (pull_request) Successful in 1s
CI / schema-registry (pull_request) Successful in 9s
686969d028
frame_wire 1.5 added `FrameStatusKind` and `FrameStatusKinds` to
schemas/frame_wire.json and only there. For two minor versions the two documents
disagreed about what the contract contains: 40 of 42 `$defs` were mirrored in
openapi.yaml's components.schemas and these two were not.

Nothing here could see it. `test_the_two_spec_files_agree` asks about exactly
two names, FrameStatus and FrameType, because those are what the generator owns.
The broad question -- is every `$def` mirrored -- was never asked, so this suite
stayed green the whole time.

It was caught downstream, and late. `ritual-engine` vendors this contract and its
`frame-wire-contract` crate DOES assert full `$defs` parity -- but it runs against
a vendored snapshot, and that snapshot was two minor versions behind, so it was
asserting parity for 1.4 and passing. The disagreement surfaced only when the
snapshot was refreshed, days after 1.6 merged. Each repository held a guard the
other needed: this one has the live documents and no parity test, that one has
the parity test and a stale copy of the documents.

What the gap cost: `FrameStatusKinds` carries `is_terminal`, `is_active_pickup`,
`is_pre_admission` and `surfaces_in_queue` per status as a generated `const`,
and its own description says it exists "so a client can decide how to present a
status ... from data rather than from a hardcoded switch that goes stale the next
time a row is added." A client generated from openapi.yaml could not reach any of
it, and would have to write exactly that switch.

openapi.yaml 0.69.0 -> 0.70.0. Additive: two new components, no endpoint,
request or response changes. The mirrored copies were generated FROM
frame_wire.json with `$defs` refs repointed at components/schemas, not retyped.

Two tests, each mutation-checked:

  test_every_wire_def_is_mirrored_in_openapi asks the broad question and has NO
  documented-gap set, unlike the ForgeBlock union which lags for a stated reason.
  A `$def` on the wire that openapi.yaml does not define means a client generated
  from one document cannot parse what a server generated from the other emits.
  Killed by deleting FrameStatusKind from openapi.yaml.

  test_the_generated_status_metadata_is_identical_in_both_documents pins the two
  generated copies to each other. `const` is regenerated from
  core.frame_status_kinds, so without this, mirroring would be a one-time fix
  that drifts again on the next row -- the same class as the five-vs-nine
  frame_type disagreement this module's docstring already records. Killed by
  flipping one `is_terminal` in openapi's copy.

Deliberately NOT added: a broad content-parity test. The two documents express
the same schemas differently -- OpenAPI wraps a bare `$ref` in `allOf`, folded
scalars add trailing newlines -- so 36 of the 42 shared defs differ textually
while agreeing semantically. Comparing them properly needs the reducer in
ritual-engine's frame-wire-contract/tests/parity.rs; half-porting it here would
be a weak test wearing a strong name. The reason is written into the test file
rather than left as an omission.

50 passed, 1 xfailed (was 46 passed, 1 xfailed).
peterswimm deleted branch fix/mirror-frame-status-kinds-into-openapi 2026-10-28 02:33:01 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Toilville/rituals-api-spec!26
No description provided.