Skip to content

fix(service-analytics): fence $icontains comparands on the analytics where door (#7693) - #7843

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-7693-icontains-comparand-fence
Aug 11, 2026
Merged

fix(service-analytics): fence $icontains comparands on the analytics where door (#7693)#7843
huangyiirene merged 1 commit into
mainfrom
claude/issue-7693-icontains-comparand-fence

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#7693

The measured defect

$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 filter-normalizer.ts's 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.

Re-measured on origin/main @ b54aaab (the card measured @ 5823d593d, triage re-verified @ a3c0865; the premise holds unchanged on today's tip):

filteranalytics where doorread-scope-sql
{name: {$contains: {foo: 1}}}REFUSED (INVALID_FILTER / 400)REFUSED (READ_SCOPE_COMPILE_FAILED / 500)
{name: {$icontains: {foo: 1}}}compiled — leaf {operator: 'icontains', values: [{foo: 1}]}REFUSED

and the leaf reached the statement, verbatim from the reproduction:

sql = … WHERE translate(name, 'ABC…', 'abc…') LIKE translate($1, 'ABC…', 'abc…') ESCAPE $2 …
params = ["%[object Object]%","\\"]

That is #5234's defect at the operator its fence was never extended to — a parameterised, syntactically perfect LIKE pattern 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 objectql having face; this is the third and last.

The change

One entry in TEXT_PATTERN_OPERATORS. The gate in filter-normalizer.ts is 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-analytics the set has exactly one readerstrategies/filter-normalizer.ts :358 (import) and :577 (the gate in assertCompilableComparand) — plus its own definition. Every remaining hit is either a comment or driver-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 $icontains since #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 $icontains comparand on the /analyticswhere door — an object, a {$field} reference, or an array — is now refused with INVALID_FILTER / 400 and the same sentence $contains gets (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 and Dates compile exactly as before, ASCII fold and metacharacter escaping included. The read-scope door is unchanged — it already refused these shapes.

Tests

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 $icontains assertion and every narrowness control stayed green, which is the other half of the proof: that door's refusal comes from its own assertRenderableText, 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

gateresult
pnpm --filter @objectstack/service-analytics... build✅ pass (closure, incl. driver-sql)
service-analytics package suite✅ 72 files / 1558 tests pass
reverse verification (entry removed)✅ 5 failed / 1553 passed, exactly the predicted cells
pnpm check:docs-audit-scope✅ pass
eslint on the three changed files✅ clean
tsc --noEmit (package)⚠️ 10 pre-existing errors, all in untouched test files (measure-source-field-gate.test.ts, objectql-timedimension-projection.test.ts); zero in the files this PR touches
changeset.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

…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
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 11, 2026 8:26pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics.

5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/data-api.mdx(via @objectstack/service-analytics)
  • content/docs/api/index.mdx(via @objectstack/service-analytics)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/service-analytics)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/service-analytics)
  • content/docs/plugins/packages.mdx(via @objectstack/service-analytics)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/service-analytics)
  • content/docs/releases/v17.mdx(via @objectstack/service-analytics)
  • content/docs/releases/v9.mdx(via @objectstack/service-analytics)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 11, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 11, 2026 20:58
@huangyiirene
huangyiirene added this pull request to the merge queueAug 11, 2026
Merged via the queue into main with commit 1a19e9dAug 11, 2026
26 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7693-icontains-comparand-fence branch August 11, 2026 21:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@huangyiirene@claude