@@ -243,3 +243,175 @@ export function captureExpectedReadRefusals(
243243 } ,
244244 } ;
245245}
246+
247+ /**
248+ * ═══════════════════════════════════════════════════════════════════════════
249+ * [#10983] A SECOND, independent predicate: cross-field `{ $field }` refusal
250+ * engine noise (#7929) — the sibling {@link captureExpectedReadRefusals}
251+ * cannot recognise, because it has no table to key on
252+ * ═══════════════════════════════════════════════════════════════════════════
253+ *
254+ * ## Why this cannot reuse the table-keyed predicate above
255+ *
256+ * `captureExpectedReadRefusals` withholds an engine frame only when it sits
257+ * directly above a driver refusal ITS OWN `captureDriver` sink already
258+ * recognised ({@link pending} in that closure) — the engine frame alone never
259+ * carries enough identity to withhold safely, so the driver channel supplies
260+ * it via the caller's declared TABLE plus that table's `no such table` reason.
261+ *
262+ * A cross-field `{ $field }` refusal (#7929, `uncompilableFieldReferenceError`
263+ * in `driver-sql/src/sql-driver.ts`) never goes through that path at all: it is
264+ * a validation refusal raised while COMPILING the filter, not a backend
265+ * statement fault, so it never reaches `SqlDriver.backendStatementFault` and
266+ * there is no driver-channel line — and therefore no `pending` entry — for an
267+ * engine frame to sit above. There is no table involved either: the refusal is
268+ * about which FIELDS a filter compared, not which table was queried.
269+ *
270+ * ## What replaces the table: the engine frame's own message, measured
271+ *
272+ * Unlike the generic "no such table" case, THIS engine frame's `error.message`
273+ * already carries the refusal's own identity — so the message itself is the
274+ * correlation, and no driver-channel line is needed to supply it. Measured
275+ * directly off a real run of `cross-field-refusal-operand-withhold.test.ts`
276+ * (`--reporter=default`, the reporter this feature bypasses regardless — see
277+ * the file's own header): its 6 ERROR frames carry not one message shape but
278+ * TWO, both produced by `uncompilableFieldReferenceError` —
279+ *
280+ * * the WITHHELD generic wording (4 of the 6): `"A cross-field comparison
281+ * ({ \"$field\": … }) in this filter cannot be compiled here. …"`;
282+ * * the [#8220] author-DISCLOSED wording (2 of the 6, from the one case in
283+ * that file where the predicate is positively marked `'author'` and the
284+ * driver restores the full diagnostic): `"Operator \"$gt\" on field
285+ * \"amount\" compares against another field ({ \"$field\":
286+ * \"secret_policy_column\" }), which cannot be compiled here: …"`.
287+ *
288+ * A predicate keyed on the withheld wording alone would silently miss the
289+ * disclosed half — 4 of 6 withheld and 2 loud is not "expected-noise
290+ * withholding", it is a partial mute with a passing assertion sitting on top
291+ * of it. Both strings share exactly two substrings and no third candidate
292+ * does: **`cannot be compiled here`** (verified by source grep to appear
293+ * NOWHERE else in `driver-sql/src/sql-driver.ts` — it is
294+ * `uncompilableFieldReferenceError`'s own wording and no other builder's) and
295+ * **`$field`** (present on every `{ $field }`-shaped refusal, cross-field
296+ * comparison or not). Requiring BOTH — mirroring the sibling predicate's
297+ * "table AND reason" pairing above — is what keeps a sibling `{ $field }`
298+ * family (`bareFieldReferenceError`'s "bare field reference … with no
299+ * operator", which contains `$field` but never "cannot be compiled here")
300+ * OUTSIDE the match: same operand syntax, different refusal, stays loud.
301+ *
302+ * ## ⛔ Still not a mute: the counting dimension moves, the discipline does not
303+ *
304+ * There is no driver-channel table to count against, so what is counted
305+ * changes from "per table, across two correlated channels" to "per OBJECT,
306+ * on the one channel that exists" — the engine frame's own `object` meta,
307+ * scoped to the caller's declared list exactly as the table list scopes the
308+ * sibling predicate. What does NOT change: a frame is withheld only on an
309+ * exact identity match (declared object AND both message markers), the
310+ * withholding is COUNTED per object, and the caller MUST assert those counts
311+ * ({@link ExpectedCrossFieldRefusalCapture.silentChannels}) — a capture
312+ * nobody asserts is a mute, whichever dimension it counts by.
313+ *
314+ * ⛔ Test-only, same reason as above: not exported from `src/index.ts`.
315+ */
316+
317+ export interface ExpectedCrossFieldRefusalCapture {
318+ /** Per-object count of engine `Find operation failed` frames withheld. */
319+ readonly frames : WithheldByTable ;
320+ /** Total engine frames withheld, across every declared object. */
321+ totalFrames ( ) : number ;
322+ /** The declared objects that were seen at least once. */
323+ objectsSeen ( ) : string [ ] ;
324+ /**
325+ * The declared objects whose expected cross-field frame never fired — the
326+ * assertion surface, mirroring
327+ * {@link ExpectedReadRefusalCapture.silentChannels}. ⛔ Repairing a failure
328+ * here means finding out why the refusal stopped firing (or reproducing on
329+ * a new message shape this predicate has not been taught) — NEVER loosening
330+ * the match in {@link captureExpectedCrossFieldRefusalNoise} to make it pass.
331+ *
332+ * @param required the subset that must have fired. Defaults to every
333+ * declared object.
334+ */
335+ silentChannels ( required ?: readonly string [ ] ) : string [ ] ;
336+ /**
337+ * Install the engine sink. Same access pattern and same call-before-reads
338+ * discipline as {@link ExpectedReadRefusalCapture.captureEngine} — the
339+ * engine's logger is a private field with no setter.
340+ */
341+ captureEngine ( engine : unknown ) : void ;
342+ }
343+
344+ /**
345+ * Build a capture for the cross-field `{ $field }` refusal family (#7929) on
346+ * a fixture's declared objects.
347+ *
348+ * @param objects the object names whose cross-field `{ $field }` refusals are
349+ * EXPECTED here. Derive them by measurement (as the file header does)
350+ * rather than assumption, so an object this predicate was not told about
351+ * stays loud rather than silently swallowed.
352+ */
353+ export function captureExpectedCrossFieldRefusalNoise (
354+ objects : readonly string [ ] ,
355+ ) : ExpectedCrossFieldRefusalCapture {
356+ const frames = new Map < string , number > ( ) ;
357+
358+ const bump = ( object : string ) : void => {
359+ frames . set ( object , ( frames . get ( object ) ?? 0 ) + 1 ) ;
360+ } ;
361+
362+ const sum = ( ) : number => {
363+ let n = 0 ;
364+ for ( const v of frames . values ( ) ) n += v ;
365+ return n ;
366+ } ;
367+
368+ /**
369+ * Both markers must be present — see the docblock above for why each one
370+ * alone is either not unique enough (`$field` alone also matches
371+ * `bareFieldReferenceError`) or, on its own, unverified against a message
372+ * shape this predicate has never seen.
373+ */
374+ const isExpectedCrossFieldRefusal = ( detail : string ) : boolean =>
375+ detail . includes ( 'cannot be compiled here' ) && detail . includes ( '$field' ) ;
376+
377+ return {
378+ frames,
379+ totalFrames : ( ) => sum ( ) ,
380+ objectsSeen : ( ) => [ ...frames . keys ( ) ] . sort ( ) ,
381+
382+ silentChannels ( required : readonly string [ ] = objects ) : string [ ] {
383+ const out : string [ ] = [ ] ;
384+ for ( const o of required ) {
385+ if ( ( frames . get ( o ) ?? 0 ) === 0 ) {
386+ out . push (
387+ `the engine's 'Find operation failed' frame for the cross-field refusal on '${ o } ' was never emitted` ,
388+ ) ;
389+ }
390+ }
391+ return out ;
392+ } ,
393+
394+ captureEngine ( engine : unknown ) : void {
395+ const base = ( engine as { logger : Record < string , any > } ) . logger ;
396+ ( engine as { logger : unknown } ) . logger = new Proxy ( base , {
397+ get : ( target : Record < string , any > , key : string ) =>
398+ key === 'error'
399+ ? ( msg : string , err ?: unknown , meta ?: unknown ) => {
400+ const object = ( meta as { object ?: string } | undefined ) ?. object ;
401+ const detail = String ( ( err as { message ?: string } | undefined ) ?. message ?? '' ) ;
402+ if (
403+ msg === 'Find operation failed' &&
404+ object !== undefined &&
405+ objects . includes ( object ) &&
406+ isExpectedCrossFieldRefusal ( detail )
407+ ) {
408+ bump ( object ) ;
409+ return ;
410+ }
411+ target . error ( msg , err , meta ) ;
412+ }
413+ : target [ key ] ,
414+ } ) ;
415+ } ,
416+ } ;
417+ }
0 commit comments