Skip to content

IDataDriver declares no aggregate, yet the engine dispatches to driver.aggregate through as any — the one engine-reached driver verb with no signature to match #14345

Description

@os-musk

Found while typing the recording driver double in packages/objectql/src/engine-filter-array-lowering.test.ts (#14117 / PR #14344). ⛔ Recording only — no severity asserted, routing is triage's. Not fixed there: out of that card's fence, and it moves a public contract.

Measured at 909a4417

packages/objectql/src/engine.ts reaches the driver's aggregation path by duck-typing, through a cast that erases the call:

const drv = driver as any;
…
if (typeof drv.aggregate === 'function' && allStructuredSupported && !tzRequiresInMemory && !hasAggregationFilter) {
    const aggregated = await drv.aggregate(object, ast, this.buildDriverOptions(object, opCtx.context));
    return applyHaving(aggregated, ast.having);
}

packages/spec/src/contracts/data-driver.ts declares no aggregate member on IDataDriver — required or optional. Grep for aggregate in that file returns nothing.

Why this is not just the capability convention

The duck-typing itself is deliberate and documented: packages/spec/src/data/driver.zod.ts states the rule for DriverCapabilities — "aggregation on typeof driver.aggregate === 'function' … Do not add a boolean here for behaviour that a method's presence already decides". That rule is about capability bits, and it is right. It says nothing about whether the method should be declared.

Every other engine-reached optional verb IS declared: updateMany?, deleteMany?, explain?, syncSchemasBatch?, registerExternalObject?, registerObjectMetadata?, getSchemaSyncStats?, introspectSchema?, reclaimSpace?, temporalFilterValue?, temporalFilterColumnSql?. aggregate is the only one reached by the engine with nothing to match against.

introspectSchema's own TSDoc in that file argues exactly this case for itself, and the argument transfers verbatim: a driver author implementing the interface "had no signature to mis-match against, so a column flag spelled isPrimary … compiled clean and surfaced only as a federated table whose records silently could not be located". Today an author can write aggregate(query, object) with the arguments swapped, or return the wrong row shape, and tsc has nothing to say — the engine's as any absorbs it and the result reaches applyHaving.

Consequence, stated narrowly

  • A custom driver's aggregate is unchecked in both directions: its parameters and its return.
  • A test double for a driver cannot be annotated against the real contract without adding a local extension for this verb, which is what PR test(objectql): type the #13357 negative pin's witness seams against the driver contract #14344 had to do. Any future double faces the same choice, and the cheap one is any — the shape the tsc-program work is trying to retire.
  • No drift is claimed to exist today. In-repo drivers are fine. The claim is only that a drift here would not be caught.

⛔ What is NOT claimed

  • ⛔ Not claimed the duck-typed dispatch is wrong. Presence-testing an optional member is the repo's stated convention and works.
  • ⛔ Not claimed the capability schema needs a bit for this. driver.zod.ts rules that out explicitly and correctly.
  • ⛔ Not claimed the engine's as any at that site must go. Declaring the member is the smaller move; whether the cast then narrows is a separate judgement.
  • ⛔ No opinion offered on whether the declaration should be optional (matching the presence test) or required (matching the fact that four in-repo drivers implement it). That is the decision this card hands to triage.

Dedup — the channel fired, so this is a reading

One targeted semantic search returned 28 on-topic results. Nearest neighbours, all closed:

card state why it is not this
#6212 closed same verb, but about the object-name written twice at the driver boundary, not about the member being undeclared
#4406 closed query.object at the driver boundary — the redundancy DriverQuery then removed
#12248 closed the same defect shape on the engine contract (IDataEngine gaining declared optional members). Precedent for the class, on the other interface
#8112 closed an as any hiding a wrong aggregation key in test fixtures — the consequence class, not this seam

No open card covers IDataDriver's missing aggregate declaration.

Related

#14117 / PR #14344 (where this was found) · #11493 / #11381 (the ruling that declared introspectSchema for this exact reason) · #12248 (same shape, engine side)

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions