member fails typecheck. Two guards then existed and neither covered application.
`completeRequest.guard.test.ts` proves the TYPE's semantics and would stay green
with every annotation in the SPA deleted. `test_optional_request_members.py`
derives which schemas can silently drop a member and files each under a
disposition — one of which, COVERED, is worded "the builder is annotated
`Complete<T>`".
That wording was a claim about another language's source that nothing checked. A
COVERED row and a SPA with the annotation deleted were indistinguishable to the
whole suite, and §4b's opposite rule — do NOT annotate a schema whose optional
members are computed server-side — was prose with no executable form.
`completeAnnotations.guard.test.ts` closes both. The droppable-schema population
is read every run off the generated `v1.d.ts` with the compiler API; the
annotation population is read off the SPA AST, intersected with the git index.
Neither is a list. Set equality against a reviewed disposition table means a new
optional member fails until someone rules on it.
Why the compiler API and not a regex: `Complete<Foo>` also appears in doc
comments, string literals and the `import type` specifier. Those are three
different AST node kinds, so they are excluded structurally rather than by a
string predicate — the class this repo has a withdrawn guard (six rounds, then
deleted) recording the cost of.
It found a real defect on landing. `playouts.ts` declared
ReplacePlayoutTemplatesRequest and ReplacePlayoutAlternateSchedulesRequest as
hand-written object literals that SHADOWED generated schemas of the same name,
so `Complete<>` on those wrappers was checking a local mirror rather than the
contract — the #754 mechanism wearing the annotation meant to prevent it. They
narrowed `null | Array<T>` to `T[]` and dropped no field, which is why nothing
else saw them. Now aliased to the generated schemas; the stated reason for the
hand-written form (satisfying the `Record<string, unknown>` RequestBody bound)
never required re-declaring the shape, since an alias is already a `type`.
Four mutations witnessed, each reddening one assertion and only that one:
stripping `Complete<>` from a covered site (Done-when's witnessed red),
annotating the forbidden schema, reverting a wrapper to a hand-written mirror,
and deleting a disposition row. The scanner's own fixture suite is mutation-
proven separately, over synthetic sources, so its correctness does not depend on
what the repo happens to contain today.
A deep `Complete<T>` was probed and NOT taken. It works — measured against the
real generated types, it catches a missing NESTED member, preserves the `null`
arm of `null | Array<X>`, keeps explicit `undefined` legal and still rejects
phantoms — and flipping it repo-wide left one real gap, a test fixture omitting
`weight`. It is not shipped because it cannot be applied blanket-wise
(`UpdateChannelRequest.logo` reaches ArtworkContentTypeModel, where annotating
is a bug by §4b) and because it closes only the missing direction: the phantom
direction needs a fresh literal in a contextually typed position, which a
generic `.map` callback's return is not. Recorded so it is not re-proposed on
plausibility.
The guard passed in isolation and timed out at vitest's 5s default under
full-suite load, because the population was walked twice per call across four
tests. Memoised (module-level, immutable inputs, fresh process per run, so it
cannot mask a mutation) and given explicit per-test timeouts per
ci.web-test-per-test-timeouts. Whole-suite time went 60s to 27s.
Residuals stated rather than implied closed: this is per-SCHEMA, not
per-construction-site, so the phantom direction still rests on the §4b site rule
and review (sites-in-code is #777); and the two disposition tables overlap
rather than coincide (13 rows vs 9 — this side also sees response models), so
re-dispositioning one does not redden the other. What neither can miss is a
schema unnoticed by BOTH, since both derive from the same `required` arrays.
Verified: web 1297 tests green, scripts/tests 1228 green, typecheck + eslint +
ruff clean, decisions-validate OK.
fixes#820
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UYNbVwgVszv6Pum7ZuGd75