You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
objectql having has no $icontains comparand-shape gate — an empty comparand matches EVERY row (2 of 5 FILTER_TEXT_CASES rejection rows unenrollable) #7158
Found while closing #7047 (the ADR-0112 envelope on having's refusals), by driving the having face against FILTER_TEXT_CASES for the first time. Out of #7047's fence — that card is about the ENVELOPE on refusals that already happen; this is a refusal that does not happen at all.
Measured (2026-08-10, origin/main @ 3e8e669, by execution)
FILTER_TEXT_CASES declares five expectRejection rows. Driven against matchesHaving / applyHaving over FILTER_TEXT_ROWS:
The empty-comparand row is the sharp one. Every string contains the empty substring, so the predicate constrains NOTHING — a filter that compiles to nothing does not narrow a query, it widens it (#3948). The author wrote a constraint and got the unfiltered aggregate back, with no error.
The non-string row is the quieter half: 42 is answered false — "no rows" — for a comparand the declared type (StringOperatorSchema: $icontains: z.string()) does not permit, which is the silent-wrong-answer shape #4706 retired $regex over.
What the other faces do
The driver faces get this from a comparand gate having has never had:
driver-memory — filter-refusal.tsicontainsComparandError(), both rejections in one constructor, INVALID_FILTER / 400.
driver-sql — the twin icontainsComparandError, word for word.
having is the sixth JS evaluation face of the same vocabulary (#6520 gave it the shared ASCII fold) and the only one with no comparand gate.
Closing this means REFUSING filters this face evaluates today — a behaviour change beyond an envelope card, with its own changeset and its own client-visible effect. #7047 kept the same fence #6993 kept before it.
Current state of the coverage
packages/objectql/src/having-filter-text-conformance.test.ts (added by #7047) enrolls the 13 rows cases and the 3 retired-operator rejection rows, and pins these two exclusions as measurements — asserting that the face does not refuse them, and that the empty comparand matches all nine rows. So the gap is red-on-change rather than a silent hole: adding the gate turns those pins red and forces the two rows into the enrolment.
Update that file's UNENROLLED_REJECTION_CASES block when closing this.
Classification: concrete defect with a measured repro and a restore-invariant shape — the empty $icontains comparand widens the filter to every row (the A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 shape) and the non-string comparand silently answers false, while both driver faces already refuse via a comparand gate. Verified on origin/main @ 5d24f4b: driver-memory/src/filter-refusal.ts:670icontainsComparandError (word-for-word twin in driver-sql per its own comment), and having-filter-text-conformance.test.ts:91 pins the two exclusions (UNENROLLED_REJECTION_CASES) so the fix is red-on-change guided. No ruling needed: the refusal semantics are already established on five sibling faces; this face is the only one without the gate.
Not target:v17: triggering requires an author-written degenerate comparand (empty string / non-string literal); silent, but not a today's-published-face defect in the four-class sense.
Claim: PM loop round 3 (spec-surface seat, cross-lane under maintainer directive)
Session: session_016R9de1FqP7NvwKvqXi92Gh
Branch: claude/issue-7158-having-icontains-gate
Worktree: objectstack-issue-7158
Domain: domain:engine-core
File surface: packages/objectql/src/having-filter.ts, packages/objectql/src/having-filter-text-conformance.test.ts (the UNENROLLED_REJECTION_CASES block), .changeset/ (stop on breach; explain in the report)
Container & model: M card (behavior change — new refusals), mode:subagent, model: opus
Serial constraints cleared: #7047's PR #7161 merged ~01:0xZ touching this exact file family (having's refusal envelope) — cleared as merged, carried as same-day churn into the dispatch; the fix builds on the post-#7161 tree. My in-flight #7073 is packages/formula — different package, disjoint. No in-flight claim on packages/objectql.
ACCEPT — PR #7236 (review of record, session session_016R9de1FqP7NvwKvqXi92Gh).
What shipped: having gains the twin icontainsComparandError gate — both rejections in one constructor, message verbatim from the driver twins after the position clause, riding #7161's invalidFilterError helper so this face keeps ONE envelope for one mistake (the #7035 class explicitly avoided, as dispatched). 3 files, +225/−98, changeset patch stating the client-visible effect plainly.
Verified against GitHub: draft targeting main, first line Fixes #7158; CI converged at ce0b476 — 25 checks, zero failures; ESLintsuccess (03:30:14Z) and TypeScript Type Checksuccess (03:37:22Z) personally confirmed.
Review credits:
Both directions as counts: 13/13 evaluation rows verbatim green (accepted surface does not shrink), 5/5 rejection rows enrolled (was 3/5), suite delta exactly +2 — no test lost in the restructure. Rejection assertions are code + status + mustMention through one shared helper, never bare toThrow().
The measurement pins were flipped, not deleted — the same four expressions that pinned the exclusion now assert the refusal. Reverse verification: 5 red, exclusively on the comparand rows, evaluation/retired-operator/guard all green — load-bearing for exactly what it claims.
Two placement judgments argued, not just made: the gate sits above the no-value early exit (a comparand's shape is a property of the filter, not the row — one filter must not get two verdicts decided by data), and the dead typeof limb was hoisted with the column-value guard deliberately kept (row judgment vs filter judgment).
Honest process disclosure: git checkout origin/main -- <file> during reverse verification discarded uncommitted source edits; re-applied and committed before any further measurement, all reported numbers from the restored tree. Lesson recorded — commit before reverse verification. Worth folding into the os-dev definition alongside the stash rule (same trap class, different tool).
Activity
os-zhuang commentedon Aug 10, 2026
Triage:
pm:queue(domain:engine-corestays —packages/objectql/src/having-filter.ts).$icontainscomparand widens the filter to every row (the A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 shape) and the non-string comparand silently answersfalse, while both driver faces already refuse via a comparand gate. Verified onorigin/main@5d24f4b:driver-memory/src/filter-refusal.ts:670icontainsComparandError(word-for-word twin in driver-sql per its own comment), andhaving-filter-text-conformance.test.ts:91pins the two exclusions (UNENROLLED_REJECTION_CASES) so the fix is red-on-change guided. No ruling needed: the refusal semantics are already established on five sibling faces; this face is the only one without the gate.target:v17: triggering requires an author-written degenerate comparand (empty string / non-string literal); silent, but not a today's-published-face defect in the four-class sense.having拒收 retired/unknown 算子时缺 ADR-0112 信封(裸Error,无code/status)—— 五个拒收面中唯一一个 #7047 (envelope on refusals that already happen — explicitly fenced), drivers(memory, mongodb): the$containsfamily still folds case — the last two backends left on the wrong side of #4706 Q2 = A #6682 (case-folding, different defect). Nothing else.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-help commentedon Aug 10, 2026
Claim: PM loop round 3 (spec-surface seat, cross-lane under maintainer directive)
Session:
session_016R9de1FqP7NvwKvqXi92GhBranch:
claude/issue-7158-having-icontains-gateWorktree:
objectstack-issue-7158Domain:
domain:engine-coreFile surface:
packages/objectql/src/having-filter.ts,packages/objectql/src/having-filter-text-conformance.test.ts(theUNENROLLED_REJECTION_CASESblock),.changeset/(stop on breach; explain in the report)Container & model: M card (behavior change — new refusals),
mode:subagent,model: opusSerial constraints cleared: #7047's PR #7161 merged ~01:0xZ touching this exact file family (
having's refusal envelope) — cleared as merged, carried as same-day churn into the dispatch; the fix builds on the post-#7161 tree. My in-flight #7073 ispackages/formula— different package, disjoint. No in-flight claim onpackages/objectql.Instruction provenance (cross-lane dispatch): maintainer, this session's chat, 2026-08-10 ~01:5xZ — 「所有队列卡片都派发,v17 优先派发」.
Generated by Claude Code
os-help commentedon Aug 10, 2026
ACCEPT — PR #7236 (review of record, session
session_016R9de1FqP7NvwKvqXi92Gh).What shipped:
havinggains the twinicontainsComparandErrorgate — both rejections in one constructor, message verbatim from the driver twins after the position clause, riding #7161'sinvalidFilterErrorhelper so this face keeps ONE envelope for one mistake (the #7035 class explicitly avoided, as dispatched). 3 files, +225/−98, changeset patch stating the client-visible effect plainly.Verified against GitHub: draft targeting
main, first lineFixes #7158; CI converged atce0b476— 25 checks, zero failures; ESLintsuccess(03:30:14Z) and TypeScript Type Checksuccess(03:37:22Z) personally confirmed.Review credits:
code+status+mustMentionthrough one shared helper, never baretoThrow().typeoflimb was hoisted with the column-value guard deliberately kept (row judgment vs filter judgment).git checkout origin/main -- <file>during reverse verification discarded uncommitted source edits; re-applied and committed before any further measurement, all reported numbers from the restored tree. Lesson recorded — commit before reverse verification. Worth folding into the os-dev definition alongside the stash rule (same trap class, different tool).Next: ready → merge queue.
Generated by Claude Code
fix(objectql): put `having`'s operator refusals inside the ADR-0112 e…
fix(objectql): gate `having`'s $icontains comparand shape (objectstac…