Skip to content

Hand-built e2e response stubs are not held to the shapes the generated checks require #682

Description

@JArmandoAnaya

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciCI, tooling, repo automationfrontendannotator / ui-core / app packagestoolingDeveloper tooling: scripts, checks, local workflow

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions