Skip to content

test(schemas): freeze a document per schema version and assert it still reads - #174

Merged
LKSNDRTMLKV merged 1 commit into
mainfrom
feat/schema-version-compat-fixtures
Aug 22, 2026
Merged

test(schemas): freeze a document per schema version and assert it still reads#174
LKSNDRTMLKV merged 1 commit into
mainfrom
feat/schema-version-compat-fixtures

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Implements item 3 of #94 — the compatibility test — and it found a live bug on
its first run.

The gate

One minimal sector-data document frozen per (sector, version) the registry
serves: 28 today. Three properties:

  1. Every declared version has a fixture. Adding a schema version fails here
    until one is frozen, which is what makes this a tripwire rather than a suite.
  2. Every fixture validates against the schema it was frozen for. Guards the
    fixtures: one that was never legal would pass (3) while proving nothing.
  3. Every fixture still reads through Passport::from_stored. The tripwire.

Fixtures are written once by just freeze-schema-fixtures and never
regenerated — one regenerated from the current schema would agree with the
current schema by construction and could not catch anything.

Why (3) tests from_stored and not SectorData

My first version deserialised the bare SectorData and reported 14 of 28
versions broken. That was wrong, and worth recording because the number looked
alarming: old documents are meant to arrive through a lens. from_stored is
the path a node actually reads by — direct read, then upcast from the recorded
schemaVersion through the lens registry. A sector whose rename shipped a lens
is not broken, and asserting otherwise would have been a gate that fails for a
contract nobody has.

Through the real path, exactly one sector was genuinely broken.

The bug it found

A textile passport stored under schema v1.0.0 could not be read at all. The
lens chain had no 1.0.0 → 1.1.0 step, so upcast_str_toward refused with
NoPath — while the catalog went on declaring v1.0.0 supported.

The step needs no transformation. v1.0.0 and v1.1.0 declare identical
required lists, v1.1.0 removes no property, and its sixteen additions are all
optional. But a chain cannot cross a gap: a document two versions behind could
not reach current through a step that required no change. An identity lens closes
it, and 1.0.0 → 1.1.0 → 1.2.0 now carries the document through the
countryOfManufacturing rename that was always waiting for it.

Textile is the sector whose rename caused the incident this issue was filed over.

Battery pre-2.5.0 is listed as a deliberate refusal

v2.5.0 made batteryType required because Annex VI Part A point 2 makes the
battery category mandatory public content, and the v2.4.0 → v2.5.0 lens refuses
rather than inventing a category for a record predating the mandate. There is no
correct value to supply, so refusing is the honest outcome.

Listed exactly, and asserted in both directions: a new orphan fails as a
regression, and a listed version that starts reading fails as a stale exemption.
The list cannot quietly become wrong in either direction.

Two tests were pinned to the textile gap

Both toward_still_refuses_a_gap_no_lens_touches_at_all and
from_stored_refuses_a_gap_no_lens_bridges used textile 1.0.0 as their example
of an unbridged version. Neither is about textile — they assert that an
unbridgeable gap is a typed refusal rather than a panic or a silent identity.
Pointing a mechanism test at a real gap means legitimately closing that gap fails
a test that was never about it, which is exactly what happened here. Both now
build their own gap: a synthetic registry, and a schemaVersion this build has
never served.

The gate was verified to fail

Per the standing rule that an untested gate is not a gate. Adding a required
field to TyreData:

"tyre v1.0.0: serialisation error: missing field `deliberatelyAddedRequiredField`"

Reverted immediately; the diff carries no trace of it.

On the generator

minimal_document builds from required only, and is deliberately not a general
JSON Schema instance generator. Pattern-constrained strings come from a small
explicit table (two entries: a check-digit-valid GTIN-14 and an ISO 3166-1
alpha-2 code) that a reviewer can verify by eye; an unknown pattern produces a
value that fails validation, and the freezer then refuses to write the file
and names the version for hand authoring. A fixture that happens to pass while
meaning nothing is worse than one that is missing loudly.

Still open on #94

Items 1 (a written additive-only contract) and 2 (exposing a transform entry
point from this crate) are untouched. Item 2 is partly there already —
LensRegistry exists and from_stored uses it — but there is no
SectorData::upgrade-shaped public entry point for a consumer that is not
reading a whole Passport.

Verification

just check green: 1121 tests, clippy, doctests, ten plugin suites, cargo doc
with -D warnings, cargo audit.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 38 complexity · 0 duplication

Metric Results
Complexity 38
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@LKSNDRTMLKV
LKSNDRTMLKV merged commit f299c8d into main Aug 22, 2026
18 checks passed
@LKSNDRTMLKV
LKSNDRTMLKV deleted the feat/schema-version-compat-fixtures branch August 22, 2026 20:54
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.

1 participant