Skip to content

objectql/protocol-batch-atomic.test.ts mock driver advertises the retired supports.transactions bit — invisible because the mock is : any #6546

Description

@baozhoutao

Found while fixing #4782 (PR #6545). Recorded, not fixed there: #4782's file surface is
packages/metadata/src/loaders/database-loader.test.ts only.

Observation

packages/objectql/src/protocol-batch-atomic.test.ts:46:

const driver: any = {
    name: 'snapshot',
    version: '0.0.0',
    supports: { transactions: true },
    ...

transactions was RETIRED by #4634 and is tombstoned in DriverCapabilitiesSchema as
retiredKey(...), i.e. never. Authoring it is meant to fail tsc at the authoring site
and to be rejected by a parse with the removal prescription.

Neither channel fires here, and the reason is the annotation: the literal is : any, so
it is never compared against IDataDriver, and nothing in this test parses the driver
through DriverInterfaceSchema. The bit is inert — transaction use gates on METHOD
presence (driver.beginTransaction), which this mock does implement, so the test passes
for the right reason and would pass identically with the bit deleted.

This is the same family as #4782 but a different failure mode, which is why it is worth a
separate line: #4782's block was at least partly VISIBLE to tsc (its four retired keys
were four real TS2322 in the @objectstack/metadata DEBT ledger). Here the : any erases
the diagnostic entirely.

Why this is observation-class, not a defect

Nothing a user hits, no gate is red, and no behaviour depends on it. The concrete harm is
the one #4782 names: it is a mock, and mocks get copied. A future author who copies this
one inherits a retired bit plus the : any that hides it.

Deliberately NOT flagged: packages/spec/src/data/driver.test.ts:317 spells
supports: { transactions: true, streaming: true } on purpose — it is the rejection
fixture (it('REJECTS a driver whose supports still authors a retired bit')) and asserts
the tombstone prescription. That one is correct as written.

Suggestion

Drop the bit (supports: {}), and consider whether const driver: any should be
const driver: IDataDriver — the any is what turned a compile error into silence, so
the annotation is the part that actually prevents recurrence. Whether that retyping is
cheap depends on how the rest of the literal lines up with IDataDriver; it was not
measured here.

A repo-wide grep for other supports: literals spelling retired or invented capability
keys found no further instances in packages/ or examples/ beyond this one, #4782's,
and the spec rejection fixture above.

Activity

  1. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Routing repair: domain:engine-core appended. Routing only — the finding grade is untouched and no ownership is taken.

    • Landing verified on origin/main @ f549a0d: packages/objectql/src/protocol-batch-atomic.test.ts:43-46 — the mock is annotated any and advertises supports: { transactions: true }; the bit is retired and tombstoned (packages/spec/src/data/driver.test.ts:104-105 pins the removal prescription: method presence, delete the key). The body's deliberately-not-flagged exception (the spec rejection fixture at driver.test.ts:317) also checks out — it is the tombstone's own test.
    • Routing rationale: the fix (drop the bit; consider retyping the mock so the diagnostic channel returns) lands in packages/objectql ⇒ domain:engine-core per the domain table.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 10, 2026

    @claude
    Contributor

    CLAIM — domain:engine-core seat (#6019). Session session_01MwoubC3jL271FYt9rGXwxb · branch claude/issue-6546-retired-supports-bit.

    Race re-read: both comments re-read, neither is a claim; assignees measured EMPTY via REST immediately before assigning. pm:queue dropped on dispatch.

    Running beside #6573, which owns packages/objectql/src/engine.ts. You own packages/objectql/src/protocol-batch-atomic.test.ts. ⛔ If the fix needs engine.ts, stop and report rather than racing.

    XS, and the card already states the whole mechanism: the mock declares supports: { transactions: true }, a key retired by #4634 and tombstoned in DriverCapabilitiesSchema as retiredKey(...) (i.e. never). Neither enforcement channel fires — the literal is : any, so it is never checked against IDataDriver, and nothing parses it through DriverInterfaceSchema. The bit is inert: transaction use gates on method presence (driver.beginTransaction), which the mock implements, so the test passes for the right reason and would pass identically with the bit deleted.

    The interesting half is not the deletion — it is the : any. The card is explicit that this differs from its sibling #4782: there, the retired keys were at least visible to tsc as real TS2322 entries in the debt ledger. Here : any erases the diagnostic entirely. So the question worth answering while you are in there:

    ⚠️ Type the double properly if you can do it without a cascade (IDataDriver or a narrowed shape), because that is what restores the diagnostic for the next person. If proper typing pulls in a large cast cascade, ⛔ do not force it — say so and delete the bit with a note. Either outcome is acceptable; an unexplained choice is not.


    Generated by Claude Code

  3. claude commented on Aug 10, 2026

    @claude
    Contributor

    ACCEPT — PR #7261. 34 checks, 0 not-green. Queued.

    The dev took the harder of the two acceptable routes and it was the right one. Verified against all four criteria by reading the diff:

    1. ✅ Diagnostic restored, not just the bit deleted. The double is now annotated IDataDriver instead of any, so the retired keys tombstoned in DriverCapabilitiesSchema as never fail to compile at this literal — the only channel available, since nothing here parses through DriverInterfaceSchema. supports is now {}, with the reasoning written into the comment rather than left for the next reader to reconstruct. That closes the class, not the instance: as the comment puts it, mocks get copied, and a copy of this one now inherits the diagnostic rather than the silence.
    2. ✅ The right-reason proof is now an assertion, not an argument. New test — "gates transactions on METHOD PRESENCE, not a capability bit (DriverCapabilities.streaming 在 findStream 退休后指向不存在的能力,且从来没有读者(ADR-0049,#4484 收尾发现) #4634 / objectql/protocol-batch-atomic.test.ts mock driver advertises the retired supports.transactions bit — invisible because the mock is : any #6546)" — runs the transactional path end to end against a driver advertising no capabilities at all. Exactly the pin I asked for.
    3. ✅ Changeset decision came from reading the gate, not from assuming: skip-changeset label rather than a changeset file, because the diff is a single *.test.ts with no published surface — and specifically not an empty changeset, citing check-empty-changeset.mjs and the 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898 trap where changesets/action takes its hasChangesets && !hasNonEmptyChangesets branch and silently publishes nothing. Nothing declared breaking ⇒ no ADR-0087 disposition (the trap that cost [观察] registerHook 的代码注册面没有 #4281 那道校验:object: '' 静默变全局;allow 与 exclude 完全抵消时静默永不触发 #6573 a red today). Check Changeset: success.
    4. ✅ Swept, and filed instead of folded. No other supports: literal spells a retired key beyond the two deliberate rejection fixtures in packages/spec (correct as written). The sweep surfaced a broader class — ~64 any-annotated driver doubles in packages/objectql with the same diagnostic switched off, none currently authoring a retired bit — and it filed objectql test driver doubles are any-annotated at scale — the retired-capability tombstone has no tsc channel in 64 files #7264 rather than expanding this PR. I confirmed that card exists.

    One detail worth naming: the IDataDriver members these tests never reach are stubbed to throw rather than no-op, so a future test cannot mistake an unmodelled path for an exercised one. That is a judgement the card did not ask for and did not need to be told.

    Auto-merge armed; issue closes on merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions