Skip to content

Filter AST: like is folded to $contains at the wire — wildcards bind as literals and driver-sql's like/ilike arm is unreachable #7536

Closed

Description

@huangyiirene

Symptom

A like predicate carrying SQL wildcards matches nothing, because the % is bound as a literal character:

filter node observed expected
["name","like","%Industries"] 200, 0 rows the rows whose name ENDS WITH Industries
["name","like","Industries"] 200, the 2 matching rows — byte-identical to the $contains control a LIKE 'Industries' (exact) match, not a substring match

The second line is the tell: like and $contains produce the same bytes, so like is not reaching the driver as LIKE at all — it is being lowered onto contains, which wraps the value in %…% and changes what the query means. A caller who writes their own wildcards therefore gets a silently wrong answer in both directions: with wildcards, zero rows; without them, a substring match they did not ask for.

This contradicts canonicalAstOperator's own comment in the same file:

like/ilike share the $contains lowering but are NOT substring matches at the driver: driver-sql passes them to SQL verbatim, so the caller binds the wildcards. Folding them onto contains would silently wrap the value in %…% and change what the query means.

Consequence: driver-sql's like/ilike bind arm has been unreachable from the wire since #5158 — dead code guarding a contract nothing can exercise over HTTP.

Root cause

Located in the report as the like → $contains fold on the wire path. In tree (checked 2026-08-11), the fold is the AST_OPERATOR_MAP entry

'like': '$contains',

in packages/spec/src/data/filter.zod.ts, reached via astOperatorLowering() from the AST comparison converter (parseFilterAST / convertComparison) — the path a wire filter actually takes. canonicalAstOperator(), immediately below it in the same file, exempts like/ilike deliberately (that is the comment quoted above), but that exemption only shapes its own canonical-name output; the lowering the wire uses has no such exemption. Fix lands in packages/spec, with driver-side verification that the like/ilike bind arm is exercised again.

Reproduction

  1. Boot showcase on a fresh isolated file DB (SqlDriver / better-sqlite3); authenticate as admin@objectos.ai.
  2. POST /api/v1/data/showcase_account/query with the filter AST ["name","like","%Industries"] → 200, 0 rows.
  3. Same request with ["name","like","Industries"] → 200, the 2 matching rows.
  4. Control: the same value through $contains → byte-identical response to step 3.

Source

Extracted from the QA run #7463 (framework a86db17).

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 11, 2026
  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 11, 2026
  3. self-assigned this
    on Aug 11, 2026
  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    CLAIM — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-7536-like-wire-lowering. Dispatching a cloud dev session (model: Fable — the root cause is one map entry, but removing the fold means every driver now receives like/ilike from the wire for the first time since #5158; the blast radius across memory/mongodb/turso evaluators and wildcard-binding semantics is the real work).

    Direction bounds recorded at dispatch:

    • Fix in packages/spec/src/data/filter.zod.ts per the file's own contract comment (canonicalAstOperator's deliberate exemption): the wire lowering stops folding like/ilike onto $contains; they reach drivers verbatim with caller-bound wildcards.
    • Driver-side verification that driver-sql's like/ilike bind arm is exercised again — the card's repro table becomes pins (wildcard match works; non-wildcard like is exact, NOT substring).
    • Sweep the other execution paths that now see like/ilike (memory matchesFilter, driver-memory, driver-mongodb, driver-turso): each either implements the same semantics (conformance-proven) or refuses loudly in the ADR-0112 envelope — no silent divergence between paths, that's the failure mode this card exists to kill.
    • Wire-compat check: stored filters / existing callers that relied on the buggy fold (a like without wildcards behaving as substring) — measure whether any shipped artifact in-repo authored like expecting $contains behavior; report findings rather than silently changing them.
    • Changeset judged and justified (lean: @objectstack/spec patch — restoring the documented contract is a bug fix; argue if you conclude otherwise).
    • Serial adjacency: [spec] SqlDriver 将 $field 编译为列对列比较(cross-field comparison push-down) #5222 (cross-field push-down, same window) also touches spec filter semantics and driver-sql tests — later-lander merges main, never rebases.

    Generated by Claude Code

  5. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    DEV CLAIM

    Starting work: fix the like/ilike → $contains fold in the wire lowering (packages/spec/src/data/filter.zod.ts), pin the repro table as tests, run the blast-radius sweep across all execution paths, and report back here with the OS-DEV-REPORT.


    Generated by Claude Code

  6. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Correction to the claim comment above — the dispatched model. The claim records the seat's judgment as Fable; the cloud session actually provisioned and serving this card is Opus (session_01PhHptz16p1kRmbmuzEZkgd, last_served_model: claude-opus-5). The model parameter on the dispatch did not take effect, and the session title carries the same stale label.

    Recording it rather than letting the audit trail state a model that was never used — the work bounds are unchanged. Flagged for the next shift as a dispatch-mechanics defect to chase.


    Generated by Claude Code

  7. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Step-7 review — ACCEPT. PR #7593 undrafted, auto-merge enabled (spec-lane PM seat #6017, session_01PiRUoQkTSBBmpyXBY3cVn2). CI verified green by this seat: 26 checks, 25 success + 1 path-skipped.

    The dispatch asked for a per-path sweep with implement-or-refuse and no silent divergence, and that is what came back — one row per execution path, each with its verdict and its evidence file, including the three that refuse loudly and the one (service-analytics read-scope-sql) that refuses in a different envelope, recorded rather than quietly normalised.

    What the review turns on:

    Both reverse-verification misses are recorded honestly, and the second is worth more than the probe it came from: parseFilterAST's lenient $${op} fallback mints $ilike with no map entry while isFilterAST (the protocol door) would refuse it — two functions, two answers, which is #3948's subject restated. Worth a finding card if it is not already covered there.

    Close-verify and label cleanup follow the merge notice. Note the serial: PR #7582 (#5222) is in the queue ahead of this one and also touches driver-sql; if the queue ejects this PR, merge origin/main (never rebase) and re-push.


    Generated by Claude Code

  8. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    OS-DEV-REPORT

    {
      "issue": 7536,
      "outcome": "implemented",
      "branch": "claude/issue-7536-like-wire-lowering",
      "pr_number": 7593,
      "premise_check": {
        "card_premise_confirmed": true,
        "detail": "The `'like': '$contains'` entry was present in AST_OPERATOR_MAP and reached via astOperatorLowering() from convertComparison, exactly as the card located it. canonicalAstOperator's exemption and its comment were present and did document the contract being violated.",
        "card_premise_corrected_in_one_place": "The card says driver-sql's `like`/`ilike` BIND ARM has been unreachable since #5158. Measured: there is no arm to reach. The array-format emitter that once handled infix `like` was deleted with the array dialect in #5158, leaving only the two infix spellings in SCALAR_COMPARAND_OPERATORS - a comparand GATE for an operator no emitter could produce. A lowering-only fix would therefore have converted a silent wrong answer into a 400 and the card's repro table still would not have passed. driver-sql needed a new emitter arm, which this PR adds."
      },
      "changes": {
        "packages/spec": [
          "AST_OPERATOR_MAP: 'like' -> '$like'; NEW 'ilike' -> '$ilike' (ilike had no entry at all, so isFilterAST refused it)",
          "CANONICAL_INFIX: '$like' -> 'like', '$ilike' -> 'ilike'",
          "canonicalAstOperator: the hand-written like/ilike exemption RETIRED - the generic round-trip now answers both by construction",
          "StringOperatorSchema + FieldOperatorsSchema: new $like / $ilike declared with full describe() text",
          "FILTER_OPERATORS: $like/$ilike deliberately NOT added; the staging and the per-face implement-or-refuse table recorded on its docblock",
          "NEW shared pattern language, one definition for every face: hasDanglingLikeEscape, likePatternToRegexSource, matchesLikePattern, likePatternToGlobPattern"
        ],
        "packages/drivers/driver-sql": [
          "NEW likePatternPredicate() - LIKE on postgres/mysql/unknown, GLOB on sqlite (case-exactness per #6518), pattern TRANSLATED not escaped",
          "NEW applyLikePattern() + case '$like' / case '$ilike' emitter arms",
          "SCALAR_COMPARAND_OPERATORS += $like/$ilike; NEW LIKE_PATTERN_OPERATORS set",
          "reduceFilterKey gate: non-string pattern and dangling trailing escape refused on the shape walk (so a boolean identity cannot skip it)",
          "NEW likePatternComparandError / danglingLikeEscapeError; unknown-operator message lists the two new operators"
        ],
        "packages/drivers/driver-turso": [
          "SUPPORTED_FILTER_OPERATORS and MISPLACED_FIELD_OPERATORS += $like/$ilike",
          "NEW pushLikePattern() + compile arms, sharing the spec's LIKE->GLOB translation with the local twin",
          "NEW likePatternComparand / danglingLikeEscape refusals"
        ],
        "packages/drivers/driver-memory": [
          "SUPPORTED_FIELD_OPERATORS widened BY HAND with $like/$ilike (the precedent driver-turso's remote transport set for $icontains in #5702) - arms landed in the same commit as the widening",
          "query path (normalizeFieldOperators) and reference matcher (memory-matcher) both answer, via the spec's shared translation",
          "convertConditionToMongo: the stale `case 'contains': case 'like': case 'ilike':` shared arm SPLIT - like/ilike no longer regex-escape the comparand and substring-match (the memory-face twin of the wire defect)",
          "NEW likePatternComparandError / danglingLikeEscapeError on the shape walk"
        ],
        "packages/formula": [
          "case '$like' / '$ilike' in evalOp via matchesLikePattern; malformed pattern answers false (this evaluator is total by contract)",
          "module header's 'every declared operator has an arm' claim maintained rather than silently falsified"
        ],
        "packages/client": [
          "contains()/startsWith()/endsWith() STOP gluing wildcards into a `like` tuple - they now emit contains/starts_with/ends_with, whose comparand is text",
          "NEW ilike(); like() docblock states the pattern contract"
        ],
        "docs": [
          "content/docs/protocol/objectql/query-syntax.mdx - $like/$ilike in the operator table + a section on why $like is not a spelling of $contains + per-backend coverage",
          "content/docs/deployment/troubleshooting.mdx - the `$like -> $contains` 'correction' row was misinformation once $like became real; replaced, $regex->$icontains added",
          "content/docs/api/client-sdk.mdx - the FilterBuilder example"
        ],
        "generated": [
          "packages/spec/authorable-surface/data.json (+2 declared operators)",
          "packages/spec/api-surface/data.json, packages/spec/export-origins/data.json (+4 exported functions)",
          "content/docs/references/data/filter.mdx"
        ],
        "tests_added": [
          "packages/spec/src/data/filter-like-wire-lowering.test.ts (26) - the card's repro table at the parseFilterAST/convertComparison level, the pattern language, the LIKE->GLOB translation",
          "packages/drivers/driver-sql/src/sql-driver-like-pattern.test.ts (19) - the repro table EXECUTED against real SQLite, incl. the wire path via parseFilterAST",
          "packages/drivers/driver-turso/src/turso-local-remote-like-parity.test.ts (15) - every case asserts the two transports agree before asserting the answer",
          "packages/drivers/driver-memory/src/memory-like-pattern.test.ts (15) - every case asserted on BOTH faces (query path + reference matcher)",
          "packages/formula/src/matches-filter-like.test.ts (8)"
        ]
      },
      "path_sweep": [
        { "path": "driver-sql", "verdict": "implements", "evidence": "sql-driver-like-pattern.test.ts, 19 cases on better-sqlite3: wildcard %Industries -> ['1','3']; wildcard-free 'Industries' -> ['3'] (exact, not substring); $contains control unchanged at ['1','2','3']; escapes, GLOB-metacharacter literality, $and/$or/$not, and 4 refusal cases." },
        { "path": "driver-sqlite-wasm", "verdict": "implements (inherited)", "evidence": "SqliteWasmDriver extends SqlDriver and carries no filter emitter of its own; full package suite green (23 files / 308 tests) on the changed compiler." },
        { "path": "driver-turso (local + remote)", "verdict": "implements on both transports", "evidence": "turso-local-remote-like-parity.test.ts, 15 cases; each asserts remote == local BEFORE asserting the expected rows, and the 3 refusal cases assert INVALID_FILTER/400 on both faces." },
        { "path": "driver-memory (query path + reference matcher)", "verdict": "implements", "evidence": "memory-like-pattern.test.ts, 15 cases, each asserted on both faces with the face-vs-face comparison reported first. Also keeps memory-filter-ast-vocabulary.test.ts (the #3948 VALID_AST_OPERATORS expressibility invariant) green - see deviations." },
        { "path": "formula matchesFilterCondition", "verdict": "implements", "evidence": "matches-filter-like.test.ts, 8 cases incl. the ASCII-fold boundary and the totality guarantees (non-string value and malformed pattern answer false, never throw)." },
        { "path": "driver-mongodb", "verdict": "refuses loudly", "evidence": "translateFieldOperators' default: arm -> unsupportedFilterError -> INVALID_FILTER/400. Hand-written switch, so the spec change cannot silently admit the operator. Package suite green (14 files / 302 tests)." },
        { "path": "objectql having", "verdict": "refuses loudly", "evidence": "CONDITION_OPERATORS has no $like; unknownOperator() -> invalidFilterError -> INVALID_FILTER/400. (#7047 already replaced the bare Error here.)" },
        { "path": "service-analytics filter-normalizer", "verdict": "refuses loudly", "evidence": "MONGO_TO_CUBE_OP miss -> invalidFilterError -> INVALID_FILTER/400, explicitly never dropped." },
        { "path": "service-analytics read-scope-sql", "verdict": "refuses, DIFFERENT envelope", "evidence": "switch default: -> readScopeCompileError, code READ_SCOPE_COMPILE_FAILED / status 500. Deliberate pre-existing posture (a broken RLS read scope is an internal fault, not a caller mistake). Recorded, not changed here - see open_questions." },
        { "path": "service-analytics native-sql-strategy / objectql-strategy", "verdict": "unreachable for $like", "evidence": "Both consume CUBE operator names produced by filter-normalizer, which refuses first. native-sql-strategy's `return null` (= matches everything) would be a silent widen if $like were ever added to MONGO_TO_CUBE_OP without an opMap row - noted for the follow-up, not a live defect." },
        { "path": "metadata-protocol wire door (protocol.ts findData)", "verdict": "lowers, does not validate", "evidence": "isFilterAST -> parseFilterAST at protocol.ts:6167. The OBJECT spelling is passed through with no $-vocabulary check, so the refusal is the driver's job by design. This door is what now mints $like/$ilike from the wire; ilike additionally stops being refused at the door (it previously failed isFilterAST)." }
      ],
      "wire_compat_findings": {
        "shipped_metadata_seeds_docs_examples": "NONE. No *.json/*.yml/*.ts under packages/apps, examples, packages/create-objectstack or content authors a like/ilike filter predicate. VIEW_FILTER_OPERATORS does not admit the spelling at all (ViewFilterRuleSchema would reject it at parse time), so no stored view metadata can carry one.",
        "queryast_comparison_producers": "NONE in-repo. The only `type: 'comparison'` literals are driver-memory's own doc comment, its consumer branch and its own tests (which use >, =, notcontains). QuerySchema.where cannot express the shape, so it is reachable only via a cast.",
        "real_producer_found_and_adjudicated": "packages/client/src/query-builder.ts. `.contains()`, `.startsWith()` and `.endsWith()` built a `like` tuple by gluing wildcards onto the caller's value. This was broken in BOTH regimes: under the fold, $contains LIKE-escaped the glued %, so `.contains('name','Corp')` searched for the literal text '%Corp%' and matched only rows containing percent signs; after the fix, the glue would instead let a caller's own % or _ become a wildcard. Adjudicated: repointed at contains/starts_with/ends_with (comparand is TEXT), which is what the method names mean. Their docblock also claimed 'case-insensitive', which contradicts the #4706 Q2 = A ruling; corrected, with .ilike() added as the case-insensitive pattern door. `.like()` itself was left exactly as it was - it was always correct and simply did not work.",
        "test_and_doc_sites_using_like_as_an_UNKNOWN_OPERATOR_exemplar": "3, all retargeted at $sounds_like with the reason recorded in place: driver-turso/src/remote-transport-not-operator.test.ts (this one FAILED, correctly - it asserted a refusal that must no longer happen), spec/src/contracts/data-driver.test.ts, spec/src/contracts/data-driver.ts (prose). The historical account of cloud#1030 was left intact rather than rewritten.",
        "nothing_silently_rewritten": "No third-party-shaped fixture was touched. The two non-client grep hits - lint/src/data-model-rules.ts:69 (a reserved object-NAME word list) and sql-driver.ts:3929 (a direct knex .where(field,'like',...) on the autonumber re-seed scan, not a Filter Protocol predicate) - were inspected and correctly left alone."
      },
      "gates": {
        "spec_check_generated": "PASS - all 13 generated artifacts up to date (re-run after the origin/main merge)",
        "check_driver_conformance": "PASS - OK, 36 covered cells, 4 DEBT, ledger UNCHANGED by this PR",
        "local_suites": "spec 9832, driver-sql 1315, driver-turso 941, driver-memory 576, driver-sqlite-wasm 308, driver-mongodb 302, formula 631, client 265, objectql - all passing",
        "ci": "GREEN - all 26 checks complete and successful on f05d9f3 (Test Core 3 shards, Dogfood Regression 3 shards, Dogfood Verify CLI, Temporal Conformance live PG+MySQL, Build Core, Build Docs, TypeScript Type Check, ESLint, Check Changeset, Spec property liveness, ADR maintainer approval, PR size, docs links, console pin, duplicate-claim gate). PR mergeable_state: clean.",
        "known_unrelated_local_failures": "In THIS container only, some suites fail to LOAD on unbuilt workspace deps ('Failed to resolve entry for package @objectstack/sdui-parser / @objectstack/verify / @objectstack/metadata / @objectstack/plugin-hono-server'): packages/lint (12 suites), driver-turso/date-bucket-parity, and 4 client suites. Each was checked and none references the code this PR touches; all are green in CI, which builds the full workspace."
      },
      "reverse_verification": {
        "method": "Predictions written to a file BEFORE any probe ran; each probe applied to a checkpoint-committed tree and reverted with `git checkout -- <path>` against that commit (never git stash, never a checkout that could reach uncommitted work).",
        "probes": [
          { "probe": "restore 'like': '$contains' (and drop the ilike entry)", "predicted": "the wildcard pin and the exact-match pin both red; object-spelling driver cases stay green", "measured": "MATCHED - 13 red in spec (11 in the new file + 2 in filter.test.ts) and 3 red in driver-sql, all object-spelling cases green. Includes the case I most wanted: canonicalAstOperator('like') goes red, which proves the retired exemption and the map entry were always one fact." },
          { "probe": "break the $contains wrap (wrapTextMatchShape returns the bare comparand)", "predicted": "the control pin red, AND the like != contains inequality red", "measured": "MATCHED - exactly 2 red, those two. Every $like case stayed green, which is the structural claim: likePatternPredicate never calls wrapTextMatchShape." },
          { "probe": "delete driver-sql's case '$like' / case '$ilike' arms", "predicted": "every $like case red with INVALID_FILTER rather than wrong rows", "measured": "MATCHED - 14 red, all 14 carrying 'Unsupported filter operator'." },
          { "probe": "emit LIKE instead of GLOB on the sqlite arm", "predicted": "1 red (the case-sensitivity case)", "measured": "MISSED - 5 red. Direction right, count wrong: the fixture's 'ACME INDUSTRIES' row is reachable by several patterns once LIKE folds ASCII, so the fold shows up in more row sets than the case-labelled one. Recorded rather than retrofitted." },
          { "probe": "drop the hasDanglingLikeEscape gate in reduceFilterKey", "predicted": "the refusal loses its ADR-0112 envelope", "measured": "MATCHED - 1 red, 'expected undefined to be INVALID_FILTER'. Confirms the gate supplies the envelope and the shared translator only throws a bare Error as a backstop." }
        ],
        "second_prediction_miss": "I predicted the wire `ilike` driver case would go red under probe 1; it stayed green. parseFilterAST's lenient `$${op}` fallback mints $ilike even with no map entry - it is isFilterAST (the protocol door) that would have refused it. Two spec functions, two answers about one operator: the #3948 split, observed live.",
        "what_generalises": "Encoded as assertions rather than prose: the like-vs-contains INEQUALITY (catches a future change that moves both spellings, and is load-bearing in both directions as probe 2 showed); the per-face and per-transport AGREEMENT assertions reported before the expected-value assertion, so a fork reads as a fork."
      },
      "changeset": {
        "file": ".changeset/like-wire-lowering.md",
        "bump": "@objectstack/spec minor, @objectstack/driver-sql minor, @objectstack/driver-turso minor, @objectstack/driver-memory minor, @objectstack/formula minor, @objectstack/client patch",
        "justification": "The PM leaned `@objectstack/spec` patch on the reasoning that this restores a contract the file itself declares. That is true of the LOWERING but does not describe the change that shipped, so I argued it up to minor on precedent. This PR ADDS two operators to the public authorable surface ($like/$ilike on StringOperatorSchema and FieldOperatorsSchema, visible in authorable-surface/data.json) and four new exported functions (api-surface/data.json), and it changes user-visible query semantics on every backend. The in-repo precedent is exact: #5701 (85d95e7), which added $icontains to the same two schemas, took MINOR on @objectstack/spec - verified by locating that entry under a `### Minor Changes` heading in packages/spec/CHANGELOG.md. The driver and formula packages take minor for the same reason (new executable operator), not patch. @objectstack/client is patch: no new public surface beyond .ilike(), and its three changed methods are bug fixes to what they always claimed to do."
      },
      "deviations": [
        {
          "deviation": "driver-memory IMPLEMENTS $like/$ilike instead of refusing them, which is broader than the card's 'implements or refuses' minimum implied for the non-SQL faces.",
          "why": "Two reasons discovered while sweeping. (1) memory-filter-ast-vocabulary.test.ts encodes a #3948 invariant - every operator in VALID_AST_OPERATORS must be EXPRESSIBLE by this driver, never dropped - and `like` is in that set with `ilike` joining it here, so a refusal FAILED that invariant (measured: 2 red). (2) driver-memory is the in-memory double; an app whose tests run there and whose production runs SQL would have met a 400 in test for a filter that works in production, which is the same one-filter-two-answers divergence #6520 closed, wearing a refusal instead of a wrong row set. Implemented via a BY-HAND widening of the driver's own SUPPORTED_FIELD_OPERATORS rather than by adding the names to FILTER_OPERATORS - the precedent driver-turso's remote transport set for $icontains in #5702 - so mongodb/having/analytics keep refusing and the #6520 'word list must not land ahead of the evaluators' rule is respected."
        },
        {
          "deviation": "formula got arms rather than being left on its documented silent-false default.",
          "why": "Its module header claims every operator an author may WRITE has an arm, and that a silent false is reachable only for an undeclared or retired spelling. Declaring $like while leaving it on the default would have falsified that claim and reproduced the exact defect the #6993 census measured for $icontains: a write-side RLS `check` written with $like would DENY every write while the read scope's SQL matched rows."
        },
        {
          "deviation": "Docs edited beyond the strict scope of the fix (query-syntax.mdx, troubleshooting.mdx, client-sdk.mdx).",
          "why": "troubleshooting.mdx carried a `| $like | $contains |` row under 'Common mistakes / Wrong -> Correct', which became active misinformation the moment $like was a real operator with different semantics. The other two document the operator vocabulary and the FilterBuilder methods this PR changes. No content/docs/releases/ and no docs/adr/** were touched."
        },
        {
          "deviation": "$like/$ilike are NOT enrolled in the shared FILTER_TEXT_CASES conformance case-set.",
          "why": "That table's axes are case folding and comparand literalness; $like's axes are anchoring and wildcard binding, and its rows do not exist there. Enrolling this PR's suites would also flip the conformance gate's FILTER_TEXT cell for a driver on the strength of cases the table does not contain, and driver-memory/driver-mongodb hold measured DEBT rows on it. The shared-standard question for the pattern operators is real and belongs to the follow-up; the gate's ledger is unchanged by this PR."
        }
      ],
      "findings_filed": [
        {
          "finding": "Pre-existing $contains face divergence inside driver-memory, re-measured incidentally.",
          "detail": "`{ name: { $contains: 'Industries' } }` answers ['1','2','3','4'] on the QUERY path (a RegExp carrying the `i` flag) and ['1','2','3'] on the reference matcher (String.includes). This is the open #6682 DEBT row the driver-conformance ledger already carries for exactly this pair of faces; it predates #7536 and is out of scope here. Not fixed and not pinned - my control case was deliberately steered onto a case-unambiguous comparand, with the measurement recorded in a comment at the site so the next reader does not rediscover it as a mystery.",
          "action": "recorded in-code and here; no new issue filed (already tracked as #6682)"
        },
        {
          "finding": "service-analytics read-scope-sql answers an unsupported operator with READ_SCOPE_COMPILE_FAILED / 500, not INVALID_FILTER / 400.",
          "detail": "Deliberate and pre-existing (a broken RLS read scope is treated as an internal fault). It means an RLS policy author who writes $like gets a 500 rather than a 400. Out of scope for this card and not changed; flagged so the PM can decide whether it wants an issue.",
          "action": "reported here; no code change"
        },
        {
          "finding": "Latent silent-widen risk in service-analytics native-sql-strategy for the clearing PR.",
          "detail": "Its `if (!sqlOp || ...) return null` treats an unmapped operator as 'constrains nothing' (TRUE). It is unreachable today because filter-normalizer refuses first, but if the follow-up adds $like to MONGO_TO_CUBE_OP without a matching opMap/likeShape row, the result is a silently widened query - the #3650 failure mode.",
          "action": "reported here as a precondition for the follow-up"
        }
      ],
      "docs_adr_touched": {
        "content_docs_releases": "NOT touched",
        "docs_adr": "NOT touched",
        "other_docs": ["content/docs/protocol/objectql/query-syntax.mdx", "content/docs/deployment/troubleshooting.mdx", "content/docs/api/client-sdk.mdx", "content/docs/references/data/filter.mdx (generated)"]
      },
      "serial_adjacency": {
        "issue_5222": "Not landed on main as of this PR's last merge; not implemented here. origin/main was merged (never rebased) at 90336e6 after #7574 landed, and the incoming diff is disjoint from every file this PR touches. Spec moved on both sides, so the spec build + check:generated were re-run after the merge per AGENTS.md section 10 - clean."
      },
      "open_questions": [
        "Clearing the staging: $like/$ilike enter FILTER_OPERATORS only when driver-mongodb, objectql `having` and the three service-analytics compilers all have arms, in ONE PR (the #6520 rule). Worth a follow-up issue? driver-mongodb and objectql having are the substantive work; note the native-sql-strategy silent-widen precondition in findings_filed.",
        "Should the pattern operators get their own shared conformance case-set (a FILTER_PATTERN_CASES sibling to FILTER_TEXT_CASES) so the per-driver suites written here become one standard? That is the honest way to hold five backends to one pattern language, but it is a case-set design question rather than this card's fix.",
        "RLS: `rls.zod.ts` documents LIKE/ILIKE as OUTSIDE the CEL subset that lowers, so an RLS policy still cannot use them. Now that they are first-class filter operators, the CEL compiler growing a matching form is a coherent ask - deliberately not done here."
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seat

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions