Measured while implementing #5222 (cross-field push-down). Filing unassigned — recording, not claiming.
The fact
#5222 made driver-sql and driver-sqlite-wasm compile { $field: 'col' } into a same-table column-to-column comparison for the six scalar operators. It did not reach service-analytics, whose two filter compilers are independent emitters:
read-scope-sql.ts — the RLS read-scope lowering
strategies/filter-normalizer.ts — the Cube face
Both gate comparands through comparand-shape.ts's isBindableComparand, a deliberate hand-mirror of driver-sql's module-private predicate (the file says so, and __tests__/comparand-shape-refusal.test.ts pins {$field: …} as refused "converging with driver-sql"). A { $field } object is not bindable, so it is still refused there.
Why it matters
This is the same defect #5041 named, at a different face. compileCelToFilter emits { $field: path } for a field-to-field comparison in a CEL permission/RLS rule. After #5222 that rule executes on the SQL drivers and in memory — but a read scope lowered through read-scope-sql still answers INVALID_FILTER / 400.
So the capability is now asymmetric across SQL-lowering faces: one rule, two outcomes, chosen by which compiler the request went through. That asymmetry is narrower than the one #5222 closed, but it is the same shape.
Note the convergence comment those tests carry is now stale in one direction: it says refusing $field converges with driver-sql, and as of #5222 it diverges from it for the six scalar operators.
What the work looks like
The #5222 rulings port directly — same-table columns only, declared-only enumeration, tenant-isolation column forbidden on both sides, same comparison class, and the same total-across-NULLs predicate shape. What each face needs its own answer for is where the declared-field set comes from: the driver reads its own initObjects capture, and these compilers do not have one.
The conformance corpus is reusable as-is — it is exported from @objectstack/driver-sql as CROSS_FIELD_CASES / CROSS_FIELD_ROWS / CROSS_FIELD_REFUSALS precisely so a second face can be held to the same table.
Scope note: driver-mongodb and driver-turso REMOTE are also unconverted; they were out of #5222's ruling scope too, and are the reason its corpus was deliberately not promoted into packages/spec/src/data (doing so would enrol them as DEBT rows in the driver-conformance matrix).
Refs
Measured while implementing #5222 (cross-field push-down). Filing unassigned — recording, not claiming.
The fact
#5222 made
driver-sqlanddriver-sqlite-wasmcompile{ $field: 'col' }into a same-table column-to-column comparison for the six scalar operators. It did not reachservice-analytics, whose two filter compilers are independent emitters:read-scope-sql.ts— the RLS read-scope loweringstrategies/filter-normalizer.ts— the Cube faceBoth gate comparands through
comparand-shape.ts'sisBindableComparand, a deliberate hand-mirror ofdriver-sql's module-private predicate (the file says so, and__tests__/comparand-shape-refusal.test.tspins{$field: …}as refused "converging withdriver-sql"). A{ $field }object is not bindable, so it is still refused there.Why it matters
This is the same defect #5041 named, at a different face.
compileCelToFilteremits{ $field: path }for a field-to-field comparison in a CEL permission/RLS rule. After #5222 that rule executes on the SQL drivers and in memory — but a read scope lowered throughread-scope-sqlstill answersINVALID_FILTER/ 400.So the capability is now asymmetric across SQL-lowering faces: one rule, two outcomes, chosen by which compiler the request went through. That asymmetry is narrower than the one #5222 closed, but it is the same shape.
Note the convergence comment those tests carry is now stale in one direction: it says refusing
$fieldconverges withdriver-sql, and as of #5222 it diverges from it for the six scalar operators.What the work looks like
The #5222 rulings port directly — same-table columns only, declared-only enumeration, tenant-isolation column forbidden on both sides, same comparison class, and the same total-across-NULLs predicate shape. What each face needs its own answer for is where the declared-field set comes from: the driver reads its own
initObjectscapture, and these compilers do not have one.The conformance corpus is reusable as-is — it is exported from
@objectstack/driver-sqlasCROSS_FIELD_CASES/CROSS_FIELD_ROWS/CROSS_FIELD_REFUSALSprecisely so a second face can be held to the same table.Scope note:
driver-mongodbanddriver-tursoREMOTE are also unconverted; they were out of #5222's ruling scope too, and are the reason its corpus was deliberately not promoted intopackages/spec/src/data(doing so would enrol them as DEBT rows in the driver-conformance matrix).Refs
$field编译为列对列比较(cross-field comparison push-down) #5222 (the driver-side implementation and its rulings)$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041 (the original enforce-or-remove adjudication)