Found while implementing #7853 (the analytics pipeline dump's lost RegExp operand). Deliberately outside that card's scope and not fixed in #7869 — this is an execution defect, not a dump one. #7853's surface is AnalyticsResult.sql, a debug string; the crash below happens inside mingo during $match, before that string is ever built, so no change to the dump reaches it. Filing rather than folding in, and filing rather than dropping: the card decides nothing, it preserves a measurement.
Measured on origin/main @ 55635fc, executed (mingo@7.2.2)
A BigInt comparand escapes as an unhandled third-party TypeError with no rows and no refusal envelope:
TypeError: Do not know how to serialize a BigInt
at JSON.stringify (<anonymous>)
at Query.compile (node_modules/.pnpm/mingo@7.2.2/node_modules/mingo/cjs/query.js:49:49)
at new Query (node_modules/.pnpm/mingo@7.2.2/node_modules/mingo/cjs/query.js:44:10)
at $match (node_modules/.pnpm/mingo@7.2.2/node_modules/mingo/cjs/operators/pipeline/match.js:25:13)
at node_modules/.pnpm/mingo@7.2.2/node_modules/mingo/cjs/aggregator.js:73:36
at Array.reduce (<anonymous>)
Reproducing filter, on a number field over a one-row table:
where: {qty: {$eq: BigInt(100)}}Both faces, identically — this is not a face divergence. MemoryAnalyticsService.query() and InMemoryDriver.find() each throw the same TypeError; mingo serializes the query object to build its cache key, and JSON.stringify refuses a BigInt outright.
The wider shape: it is comparand TYPES that have no policy, not just BigInt
The single-type reading would be too narrow. Every comparand type below, {qty: {$eq: <value>}}, measured on both faces:
| comparand | query() | find() |
|---|
BigInt(100) | TypeError, unhandled | TypeError, unhandled |
() => 1 | OK — 0 rows | OK — 0 rows |
Symbol('x') | OK — 0 rows | OK — 0 rows |
new Map() | OK — 0 rows | OK — 0 rows |
| class instance | OK — 0 rows | OK — 0 rows |
undefined | OK — 0 rows | OK — 0 rows |
So one unsupported type crashes and five are answered with a silent empty result — a row count the author never asked for, which is the shape #5240 ruled on for {field: {}} ("both silent readings answer it with a row count the author never asked for").
Why it reads as a defect: the face refuses filter SHAPE cleanly and comparand TYPE not at all
The contrast is what makes this more than a curiosity. Measured on the same face, filter vocabulary and shape problems already get clean, descriptive refusals:
{ qty: {} } -> Error: Field constraint at where.qty carries zero operators ({ "qty": {} }). A field constraint must name at least on…
{ qty: {$wat} } -> Error: Unsupported filter operator "$wat" on field "qty". Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $…
filter-refusal.ts is built for exactly this and its own docblocks already reason about these types — the isFilterNode header names "a Date, a RegExp, a Map or a class instance" as values that enumerate to nothing, and isEmptyFieldConstraint distinguishes them as COMPARANDS "left to the paths that already handle it". The measurement above is that no path in fact handles them: the refusal vocabulary covers the operator and the constraint shape, and stops at the comparand's type.
The routing question, stated rather than answered
Whether the fix belongs in this package's filter-refusal.ts vocabulary, or is a packages/spec door question — a comparand type policy enforced once at parseFilterAST() for every driver rather than re-derived per driver — is a cross-seat call that needs the PM's declaration, not a dev's. Both readings are defensible from the measurement:
filter-refusal.ts: the crash is driver-local (mingo's cache-key serialization), the refusal envelope already lives here, and driver-sql's twin makes the same prototype check for its own reasons.- spec door: the six types above are not JSON, so no driver can receive them through a REST/JSON write path — they only arrive from direct JS callers, and a policy that every driver copies is the kind of hand-copy the repo has been consolidating (
like-pattern.ts's header refuses a third copy of its escape on exactly that ground). Whether the sibling drivers answer these six the same way is not measured here and would decide the routing.
Not measured / open for triage
- Whether the other four drivers (
driver-sql, driver-mongodb, driver-sqlite-wasm, driver-turso) crash, refuse, or silently empty on the same six comparands. This is the measurement that most cheaply settles the routing question above. - Whether ADR-0112 already rules on comparand types anywhere, which would make this enforcement of an existing decision rather than a new one.
- Grade. It is a raw unhandled third-party
TypeError reaching the caller, but only from direct JS callers on an input no JSON path can express — plausibly S rather than higher, and left to triage.
Unassigned and unclaimed — a finding for triage to grade, not a claimed card.
Found while implementing #7853 (the analytics pipeline dump's lost
RegExpoperand). Deliberately outside that card's scope and not fixed in #7869 — this is an execution defect, not a dump one. #7853's surface isAnalyticsResult.sql, a debug string; the crash below happens inside mingo during$match, before that string is ever built, so no change to the dump reaches it. Filing rather than folding in, and filing rather than dropping: the card decides nothing, it preserves a measurement.Measured on
origin/main@55635fc, executed (mingo@7.2.2)A
BigIntcomparand escapes as an unhandled third-partyTypeErrorwith no rows and no refusal envelope:Reproducing filter, on a
numberfield over a one-row table:Both faces, identically — this is not a face divergence.
MemoryAnalyticsService.query()andInMemoryDriver.find()each throw the sameTypeError; mingo serializes the query object to build its cache key, andJSON.stringifyrefuses aBigIntoutright.The wider shape: it is comparand TYPES that have no policy, not just
BigIntThe single-type reading would be too narrow. Every comparand type below,
{qty: {$eq: <value>}}, measured on both faces:query()find()BigInt(100)TypeError, unhandledTypeError, unhandled() => 1Symbol('x')new Map()undefinedSo one unsupported type crashes and five are answered with a silent empty result — a row count the author never asked for, which is the shape #5240 ruled on for
{field: {}}("both silent readings answer it with a row count the author never asked for").Why it reads as a defect: the face refuses filter SHAPE cleanly and comparand TYPE not at all
The contrast is what makes this more than a curiosity. Measured on the same face, filter vocabulary and shape problems already get clean, descriptive refusals:
filter-refusal.tsis built for exactly this and its own docblocks already reason about these types — theisFilterNodeheader names "aDate, aRegExp, aMapor a class instance" as values that enumerate to nothing, andisEmptyFieldConstraintdistinguishes them as COMPARANDS "left to the paths that already handle it". The measurement above is that no path in fact handles them: the refusal vocabulary covers the operator and the constraint shape, and stops at the comparand's type.The routing question, stated rather than answered
Whether the fix belongs in this package's
filter-refusal.tsvocabulary, or is apackages/specdoor question — a comparand type policy enforced once atparseFilterAST()for every driver rather than re-derived per driver — is a cross-seat call that needs the PM's declaration, not a dev's. Both readings are defensible from the measurement:filter-refusal.ts: the crash is driver-local (mingo's cache-key serialization), the refusal envelope already lives here, anddriver-sql's twin makes the same prototype check for its own reasons.like-pattern.ts's header refuses a third copy of its escape on exactly that ground). Whether the sibling drivers answer these six the same way is not measured here and would decide the routing.Not measured / open for triage
driver-sql,driver-mongodb,driver-sqlite-wasm,driver-turso) crash, refuse, or silently empty on the same six comparands. This is the measurement that most cheaply settles the routing question above.TypeErrorreaching the caller, but only from direct JS callers on an input no JSON path can express — plausibly S rather than higher, and left to triage.Unassigned and unclaimed — a finding for triage to grade, not a claimed card.