@@ -287,6 +287,42 @@ import { readonlyWhenFkJudgementReadsParent } from './validation/rule-validator.
287287// total over the MASTER's declared fields before it leaves this engine — the
288288// same helper every other server seam materialises with (#1871/#4649/#4953).
289289import { materializeDeclaredFields } from './declared-fields.js';
290+
291+ // ---- [#21571 MEASUREMENT PROBE — temporary, reverted before the fix] ----
292+ // Records every row a driver hands the engine that carries a key the object's
293+ // field map (plus id/created_at/updated_at) does not know. OS_PROBE_21571_OUT
294+ // names the JSONL sink; OS_PROBE_21571_TRIM=1 also deletes those keys.
295+ import { appendFileSync as __probe21571Append } from 'node:fs';
296+ function __probe21571(site: string, object: string, schema: any, rows: unknown): void {
297+ const out = typeof process !== 'undefined' ? process.env.OS_PROBE_21571_OUT : undefined;
298+ if (!out) return;
299+ const fields = schema?.fields;
300+ if (!fields || typeof fields !== 'object' || Array.isArray(fields)) return;
301+ const known = new Set(Object.keys(fields));
302+ if (known.size === 0) return;
303+ known.add('id'); known.add('created_at'); known.add('updated_at');
304+ const list = Array.isArray(rows) ? rows : rows ? [rows] : [];
305+ const extra = new Set<string>();
306+ for (const r of list) {
307+ if (!r || typeof r !== 'object' || Array.isArray(r)) continue;
308+ for (const k of Object.keys(r as object)) if (!known.has(k)) extra.add(k);
309+ }
310+ if (extra.size === 0) return;
311+ const frames = String(new Error().stack ?? '').split('\n').slice(2)
312+ .map((l) => l.trim())
313+ .filter((l) => !l.includes('objectql/src/engine.ts') && !l.includes('objectql/dist/') && !l.includes('node_modules') && !l.includes('node:'))
314+ .slice(0, 6);
315+ try {
316+ __probe21571Append(out, JSON.stringify({ site, object, extra: [...extra], frames }) + '\n');
317+ } catch { /* probe only */ }
318+ if (process.env.OS_PROBE_21571_TRIM === '1') {
319+ for (const r of list) {
320+ if (!r || typeof r !== 'object') continue;
321+ for (const k of extra) delete (r as Record<string, unknown>)[k];
322+ }
323+ }
324+ }
325+ // ---- [/#21571 MEASUREMENT PROBE] ----
290326import { applyInMemoryAggregation } from './in-memory-aggregation.js';
291327import {
292328 resolveEngineDeleteDispatch,
@@ -11910,6 +11946,7 @@ export class ObjectQL implements IObjectQLEngine {
1191011946
1191111947 try {
1191211948 let result = await driver.find(object, hookContext.input.ast as QueryAST, hookContext.input.options as any);
11949+ __probe21571('find', object, _findSchema, result);
1191311950
1191411951 // Post-process: evaluate formula virtual fields against the raw rows.
1191511952 // [#20082] With the caller's permission map when a formula calls
@@ -12186,6 +12223,7 @@ export class ObjectQL implements IObjectQLEngine {
1218612223 hookContext.input.options = this.buildDriverOptions(objectName, opCtx.context, hookContext.input.options as any);
1218712224
1218812225 let result = await driver.findOne(objectName, hookContext.input.ast as QueryAST, hookContext.input.options as any);
12226+ __probe21571('findOne', objectName, _findOneSchema, result);
1218912227
1219012228 // Post-process: evaluate formula virtual fields against the raw row
1219112229 // ([#20082] with the caller's permission map when a formula calls `can`).
@@ -14387,6 +14425,7 @@ export class ObjectQL implements IObjectQLEngine {
1438714425 const priorAst: QueryAST = { object, where: { id }, limit: 1 };
1438814426 const preOpts = this.buildDriverOptions(object, opCtx.context, hookContext.input.options as any);
1438914427 priorRecord = await driver.findOne(object, priorAst, preOpts);
14428+ __probe21571('update-prior', object, this._registry.getObject(object), priorRecord);
1439014429 // ── [#7867] The not-found gate ──────────────────────────────────
1439114430 //
1439214431 // A by-id update whose id names no row was a SILENT NO-OP that
@@ -14510,6 +14549,7 @@ export class ObjectQL implements IObjectQLEngine {
1451014549 if (!priorRowsRead) {
1451114550 priorRowsRead = true;
1451214551 priorRows = (await driver.find(object, ast, preOpts) as Record<string, unknown>[]) ?? [];
14552+ __probe21571('updateMany-prior', object, this._registry.getObject(object), priorRows);
1451314553 }
1451414554 return priorRows;
1451514555 };
@@ -15032,6 +15072,7 @@ export class ObjectQL implements IObjectQLEngine {
1503215072 // call inside a hook to THIS object.
1503315073 try {
1503415074 result = await driver.update(object, hookContext.input.id as string, hookContext.input.data as Record<string, unknown>, hookContext.input.options as any);
15075+ __probe21571('update-result', object, this._registry.getObject(object), result);
1503515076 } catch (driverError) {
1503615077 throw envelopeUniqueViolation(driverError, object);
1503715078 }
@@ -16832,6 +16873,7 @@ export class ObjectQL implements IObjectQLEngine {
1683216873 if (perRowBeforeHooks || perRowAfterHooks) {
1683316874 const preOpts = this.buildDriverOptions(object, opCtx.context, hookContext.input.options as any);
1683416875 const doomed = (await driver.find(object, ast, preOpts) as Record<string, unknown>[]) ?? [];
16876+ __probe21571('deleteMany-prior', object, this._registry.getObject(object), doomed);
1683516877 // [D6] One ceiling, both phases, BEFORE the first per-row dispatch
1683616878 // and before the driver call.
1683716879 this.assertBulkPerRowHookBudget(
0 commit comments