Uh oh!
There was an error while loading. Please reload this page.
fix(service-analytics): fence $icontains comparands on the analytics where door (#7693) - #7843
Conversation
…s `where` door (#7693) `$icontains` was the one text-pattern operator the #5234 comparand fence never covered on the analytics `where` door. It arrived after the fence: #6520 added it to `MONGO_TO_CUBE_OP` and gave `read-scope-sql.ts`'s arm its `assertRenderableText` call, but not the entry in `comparand-shape.ts`'s `TEXT_PATTERN_OPERATORS` — the set the `where` door's shape gate reads. One operator, two answers inside one package. Measured on origin/main @ b54aaab: `{name: {$icontains: {foo: 1}}}` compiled to the leaf `{operator: 'icontains', values: [{foo: 1}]}` and `NativeSQLStrategy.generateSql` bound `'%[object Object]%'` into a parameterised, syntactically perfect `LIKE` nobody wrote — while the sibling read-scope door refused the same comparand. `$contains` refused it on both. The fix is the entry in `TEXT_PATTERN_OPERATORS`; the gate in `filter-normalizer.ts` is generic over the set. Consumer measurement, quoted exact name across the repo: within this package the set has exactly one reader (`filter-normalizer.ts` :358 import, :577 gate) plus its definition — every other hit is a comment or `driver-sql`'s module-private set of the same name, which has listed `$icontains` since #6520. Tests: #7598's RECORDED GAP pin in `cross-field-reference-refusal.test.ts` is FLIPPED to assert the refusal (`INVALID_FILTER` / 400, LIKE-family wording, byte-identical to what `$contains` says), the shared #5222 corpus is now driven whole there instead of filtering `$icontains` out, and the LIKE-family loops in `comparand-shape-refusal.test.ts` gain their fifth member on both doors. Reverse-verified over the whole package: deleting the entry gives 5 failed / 1553 passed, exactly the `where`-door cells; every read-scope `$icontains` assertion and every narrowness control stays green, so the entry is shown to close a hole rather than retire the operator #6520 added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M7h5X9FCDSAkT531cJZ9D2
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7693
The measured defect
$icontainswas the one text-pattern operator the #5234 comparand fence never covered on the analyticswheredoor. It arrived after the fence: #6520 added it tofilter-normalizer.ts'sMONGO_TO_CUBE_OPand gaveread-scope-sql.ts's arm itsassertRenderableTextcall, but not the entry incomparand-shape.ts'sTEXT_PATTERN_OPERATORS— the set thewheredoor's shape gate reads.Re-measured on
origin/main@b54aaab(the card measured @5823d593d, triage re-verified @a3c0865; the premise holds unchanged on today's tip):wheredoorread-scope-sql{name: {$contains: {foo: 1}}}INVALID_FILTER/ 400)READ_SCOPE_COMPILE_FAILED/ 500){name: {$icontains: {foo: 1}}}{operator: 'icontains', values: [{foo: 1}]}and the leaf reached the statement, verbatim from the reproduction:
That is #5234's defect at the operator its fence was never extended to — a parameterised, syntactically perfect
LIKEpattern nobody wrote, which a row whose text really is[object Object]matches. It is also a split inside one package, the shape #5234 / #5332 / #5567 each spent a round removing. #7158 closed the same gap at the objectqlhavingface; this is the third and last.The change
One entry in
TEXT_PATTERN_OPERATORS. The gate infilter-normalizer.tsis generic over the set, so nothing else in the compiler moves.The consumer measurement the card required (quoted-exact-name grep across the repo, not just the package): inside
service-analyticsthe set has exactly one reader —strategies/filter-normalizer.ts:358 (import) and :577 (the gate inassertCompilableComparand) — plus its own definition. Every remaining hit is either a comment ordriver-sql's module-private set of the same name (sql-driver.ts:1201), a separate declaration in a package this one does not depend on; it has listed$icontainssince #6520, which is why the drivers were already right. So the fix really is one entry, measured rather than assumed.What changes for a caller
A malformed
$icontainscomparand on the/analyticswheredoor — an object, a{$field}reference, or an array — is now refused withINVALID_FILTER/ 400 and the same sentence$containsgets (asserted byte-identical apart from the operator name), instead of compiling into a pattern that matches the wrong rows. Well-formed comparands are untouched: strings, numbers,null, booleans andDates compile exactly as before, ASCII fold and metacharacter escaping included. The read-scope door is unchanged — it already refused these shapes.Tests
cross-field-reference-refusal.test.ts— [spec]service-analytics' read-scope / Cube filter compilers still refuse$field, so a CEL field-to-field RLS rule 400s on those faces #7598's RECORDED GAP pin is flipped: it used to assert thewheredoor returned a leaf, and now asserts the refusal on both doors, the plain{foo: 1}shape, the array shape, and a narrowness control. The shared [spec] SqlDriver 将$field编译为列对列比较(cross-field comparison push-down) #5222 corpus is driven whole there — theCROSS_FIELD_REFUSALS.filter(… !includes('$icontains'))exclusion is gone, so$icontains against a field reference is refusedis now answered on thewheredoor too.comparand-shape-refusal.test.ts— the LIKE-family loops gain their fifth member on both doors (the read-scope arm is the no-regression control; it was already green).Reverse verification, direction predicted before running: delete the entry and re-run the whole package → 5 failed / 1553 passed (with it: 1558 / 0). The five are exactly the
where-door cells — three new ones, the now-unfiltered corpus case, and the fifth loop member. Every read-scope$icontainsassertion and every narrowness control stayed green, which is the other half of the proof: that door's refusal comes from its ownassertRenderableText, and a well-formed comparand compiles identically in both states, so the entry closes a hole rather than retiring the operator #6520 added.Gates run locally
pnpm --filter @objectstack/service-analytics... builddriver-sql)service-analyticspackage suitepnpm check:docs-audit-scopeeslinton the three changed filestsc --noEmit(package)measure-source-field-gate.test.ts,objectql-timedimension-projection.test.ts); zero in the files this PR touches.changeset/icontains-comparand-fence-analytics-where.md(patch — accept/reject behavior change on a served door)No new test fakes were added, so the engine-double dispatch gate does not apply. No
content/docs/releases/edits.Generated by Claude Code