frontend/ui-core/src/generated/checks.ts carries a runtime check per response shape, and unwrap
refuses a response that fails one. That is deliberate and it works: a field the server promises and
does not send is caught at the boundary rather than as an undefined halfway down a component.
The browser suites do not use the server. They build responses by hand in the spec files, and
nothing checks those hand-built objects against the shape the generated checks require. When a new
required field arrives, every stub that omits it starts failing the runtime check, the app stops
rendering, and the suite reports missing elements and timeouts. The failure names the symptom and
never the field.
This has just happened, at a cost worth recording. Adding a progress state made pre_labeled
required on ProgressCounts, and a batch remembering its most recent pre-labeling run made
pre_label_run required on BatchOut. Fourteen hand-built stubs across annotate.spec.ts,
gallery.spec.ts, navigation.spec.ts and viewport.spec.ts silently stopped satisfying the
checks. The result was 122 failures across four specs, none of which mentioned either field, and a
pull request that stayed red through several pushes because the unit suites, the type-checker and
every contract gate were all green — the only thing that could see it was running chromium.
The type system cannot help as things stand: these objects are built as literals in test files
rather than typed against the generated models, so a missing required field is not a compile error.
Two directions, and the choice is about where the truth should live:
- Type the hand-built stubs against the generated models, so
tsc fails the moment a required
field is added. Cheapest, and it moves the failure from a twenty-minute browser run to the
editor.
- Give the specs a factory that produces a complete default shape, which a test then overrides
only where its scenario differs. This also stops each spec inventing its own idea of what a
batch looks like.
A related instance of the same class, and small enough to fold in: src/visionset/cli/batches.py
computes its SETTLED column as sum(counts) - unannotated. That was already wrong for
review_pending before this, and is wrong for pre_labeled now. A count derived by subtracting one
state goes stale the moment a state is added, which is the same failure as a stub that omits a
field — both look right until the vocabulary grows.
This is adjacent to, but not the same as, the roster gate comparing two transcriptions of
allowed_actions against each other rather than against the kernel (#675). That one is a check
that reads less than it appears to; this one is the absence of any check at all.
frontend/ui-core/src/generated/checks.tscarries a runtime check per response shape, andunwraprefuses a response that fails one. That is deliberate and it works: a field the server promises and
does not send is caught at the boundary rather than as an undefined halfway down a component.
The browser suites do not use the server. They build responses by hand in the spec files, and
nothing checks those hand-built objects against the shape the generated checks require. When a new
required field arrives, every stub that omits it starts failing the runtime check, the app stops
rendering, and the suite reports missing elements and timeouts. The failure names the symptom and
never the field.
This has just happened, at a cost worth recording. Adding a progress state made
pre_labeledrequired on
ProgressCounts, and a batch remembering its most recent pre-labeling run madepre_label_runrequired onBatchOut. Fourteen hand-built stubs acrossannotate.spec.ts,gallery.spec.ts,navigation.spec.tsandviewport.spec.tssilently stopped satisfying thechecks. The result was 122 failures across four specs, none of which mentioned either field, and a
pull request that stayed red through several pushes because the unit suites, the type-checker and
every contract gate were all green — the only thing that could see it was running chromium.
The type system cannot help as things stand: these objects are built as literals in test files
rather than typed against the generated models, so a missing required field is not a compile error.
Two directions, and the choice is about where the truth should live:
tscfails the moment a requiredfield is added. Cheapest, and it moves the failure from a twenty-minute browser run to the
editor.
only where its scenario differs. This also stops each spec inventing its own idea of what a
batch looks like.
A related instance of the same class, and small enough to fold in:
src/visionset/cli/batches.pycomputes its SETTLED column as
sum(counts) - unannotated. That was already wrong forreview_pendingbefore this, and is wrong forpre_labelednow. A count derived by subtracting onestate goes stale the moment a state is added, which is the same failure as a stub that omits a
field — both look right until the vocabulary grows.
This is adjacent to, but not the same as, the roster gate comparing two transcriptions of
allowed_actionsagainst each other rather than against the kernel (#675). That one is a checkthat reads less than it appears to; this one is the absence of any check at all.