Skip to content

objectql test driver doubles are any-annotated at scale — the retired-capability tombstone has no tsc channel in 64 files #7264

Description

@os-zhuang

Found while fixing #6546 (PR #7261). Recorded, not fixed there#6546's file surface is
packages/objectql/src/protocol-batch-atomic.test.ts only, and folding this in would have turned an
XS card into a 64-file sweep.

Observation

#6546 was one driver double authoring a capability key retired by #4634. The deletion was trivial;
the interesting half was why nobody noticed — the literal was annotated const driver: any, so it
was never compared against IDataDriver, and nothing in that file parses it through
DriverInterfaceSchema. With both channels off, the retiredKey(...) tombstone in
DriverCapabilitiesSchema had nothing to fire through.

That annotation is not unusual in this package. Measured on origin/main @ 0caf122:

files
packages/objectql/src/**/*.test.ts with a driver-ish double annotated : any = 64
…of which also spell supports: {} as any 17
test files that reference IDataDriver at all 14

What this is NOT

No retired bit is authored in any of them today, and no gate is red. A repo-wide sweep for
supports: literals spelling retired or invented keys (run as part of #6546) found nothing beyond
#6546's own instance, #4782's, and the two deliberate rejection fixtures
(packages/spec/src/data/driver.test.ts:317, packages/spec/src/data/datasource.test.ts:60), which
are correct as written. Every other literal is either {} or a live bit
(autonumber, batchSchemaSync, queryDateGranularity).

So this is observation-class, exactly like #6546: nothing a user hits, nothing broken.

Why it is still worth a line

The tombstone's stated design is that a retired key is authored-unwritable — "tsc rejects it at
the authoring site" is the first of the two channels driver.zod.ts names for it. Across these 64
files that channel is switched off, so the tombstone's protection currently rests entirely on the
second channel (a parse), which test doubles by definition rarely go through.

The concrete harm is the one #4782 and #6546 both name: mocks get copied. #6546's mock had
carried its retired bit since #4634 without anyone noticing, and it was found by reading, not by a
gate. The cost of the class is that the next such copy is equally invisible.

Measurement from #6546, for sizing

Retyping one double anyIDataDriver cost three stubs for interface members the tests never
reach (upsert, syncSchema, dropTable), and moved @objectstack/objectql's measured
TEST_DEBT count by zero (340 → 340, ceiling 355). One data point, not a trend — that double
was already close to interface-shaped, and the as any in 17 of the files above suggests some are
further away. Anyone picking this up should re-measure per file rather than extrapolate from one.

Note also that a naive sweep will trip TS2790 wherever a test does delete driver.<requiredMember>
to prove a method-presence gate; Reflect.deleteProperty(...) is the cast-free equivalent (#6546
used it).

Suggestion

Not obviously a single sweep. Plausible shapes, in increasing cost:

  1. Leave it, and retype opportunistically whenever a file is touched for another reason.
  2. Retype only the doubles that author a non-empty supports — the ones where a capability key
    could actually be spelled wrong — which is a much smaller set than 64.
  3. A gate that fails a driver double annotated any, ratcheted against a measured baseline in the
    style of check-engine-double-contract.

Filing without a recommendation; the sizing above is what I measured, not a plan.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions