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
不同缺陷:#6386 是比较数位置的 undefined(值),本单与值无关(任何值都丢)。#6386 的 PR 已把这条测量钉在测试里(a non-$ SIBLING of an operator is still dropped — a different defect, not this one),免得被后来人误读成已覆盖——但没有修它。
Verdict: needs-user-decision · Domain: domain:services · ⛔ not queued, not claimed.
Landing site (read, not guessed)
packages/services/service-analytics/src/strategies/filter-normalizer.ts — fieldLeaves, the const opKeys = Object.keys(wrapper).filter((k) => k.startsWith('$')) / if (opKeys.length > 0) { … return out; } arm. Verified on origin/main (be87153): the early return is still there at :502-504, so the non-$ siblings are still never visited. packages/services/** ⇒ domain:services per the SKILL domain table — same lane as the sibling #6386, and the same file the fix must land in.
Stale-premise check
git log origin/main -- …/filter-normalizer.ts ⇒ last touch d8e8d9c; nothing has landed on this file since the body was written, and the quoted code reads byte-identical on today's tip. Premise holds.
objectui / cloud: no shadow (analytics filter compilation is backend-only).
Why this is a decision card and not pm:queue
The defect half is not in doubt — dropping a conjunct widens, in the same function whose own comment forbids exactly that (NEVER drop: a missing predicate does not narrow the query, it WIDENS it … That failure mode is #3650's). What is in doubt is the accept face, and the two candidate remedies do not agree on it:
A (reject) narrows a shape that FilterConditionSchema's z.record(z.string(), z.unknown()) half declares legal today;
B (flatten)widens the authoring surface — it makes “operator keys and nested-relation keys mixed in one field wrapper” a supported way to author a filter, and compiles { amount: { gte: 10 } } (a missing-$ typo) into a predicate on a non-existent member amount.gte.
So the question is not “which error envelope” but “is a mixed $/non-$ field wrapper a legal authoring shape?” — a public-contract call. This is the same class the maintainer took personally on #5240 (zero-operator field constraint: declared-legal by the same z.record half, ruled loud rejection on 2026-08-04), so escalating rather than letting a lane pick is the consistent handling. The author did not self-decide either.
PM recommendation (non-binding, for the inbox)
A, on three converging precedents: the #5240 ruling (“a shape that cannot be read must explode at authoring time”); the sibling door in this very package already fail-closes on this exact predicate (read-scope-sql.tscompileField, keys.some((k) => !k.startsWith('$'))); and #6386's queued fix uses the same INVALID_FILTER / 400 envelope, which is caller-input-facing and already established here (#5352 / #6050). B's cost is concrete: it converts a diagnosable typo into a silently compiled predicate on a member that does not exist.
Ordering
No Blocked-by: line, deliberately. #6444 and #6386/PR #6445 sit in the same seat (domain:services), touching the same function — the services lane sees its own in-flight batch, so serialisation is that lane's step-3 business, not a cross-seat dependency the unlock sweep needs to grep. (Contrast #6438 last round, whose sibling was in another seat.)
Release board
No target:v17. Reachability is honestly recorded as unproven, the direction is not yet decided, and the sibling defect #6386 — strictly more reachable — is not on the board either. If the decision lands as A and a producer is found, that judgment can be revisited at the next freshness pass.
Maintainer ruling — 2026-08-08. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「按照你的建议继续」). Recorded by the PM session; needs-user-decision comes off with this comment.
Decision: Option A — refuse. A field constraint mixing $ operators with non-$ sibling keys is rejected loudly through the module's existing envelope (INVALID_FILTER / 400). Option B (flattening the siblings as a nested path) is rejected: it would compile the likely-real cause — a dropped $ — into a predicate against a non-existent member amount.gte, turning a diagnosable mistake into a harder one.
Rationale: dropping a conjunct widens the query, returning rows the author excluded — the #3650 family, and the same file's own comment forbids it in as many words ("NEVER drop: a missing predicate does not narrow the query, it WIDENS it"). The standing meta-criterion applies directly: one shape, two doors, and the sibling door (read-scope-sql's compileField) already fails closed on exactly this input — converge on the governed side. And per the AI-safety axis, { amount: { gte: 10, $lte: 20 } } is a canonical agent typo; silence there is a wrong answer no one can learn from.
Requirements:
The refusal message names the offending non-$ key(s) and gives both legal rewrites: the operator spelling (gte → $gte) and the nested-relation form, so the message distinguishes the two intents it cannot.
Refusal test asserts code + status (ADR-0112 envelope) — a bare toThrow() does not constitute a refusal test.
Dispatching under the 2026-08-08 02:12Z maintainer ruling (Option A — loud refusal via INVALID_FILTER / 400), all four ruling requirements binding: refusal message names the offending non-$ key(s) and shows both legal rewrites; refusal test asserts code + status; #6386's sibling-drop pin flips to a positive refusal assertion; scope stays this one door with the per-face #6410 statement.
Serialization check done before claiming: same-function sibling #6386's implementation (PR #6445) merged 2026-08-07 20:19Z, so the base already carries the pin to flip and the assertDefinedComparands idiom to match. Work bases on post-#6445origin/main; the card's quoted line numbers have drifted — sites located by content.
Activity
claude commentedon Aug 7, 2026
Triage (triage seat, Routine
trig_01XhwLupWiUBp7GUEigK1RFW, 19:47Z fire)Verdict:
needs-user-decision· Domain:domain:services· ⛔ not queued, not claimed.Landing site (read, not guessed)
packages/services/service-analytics/src/strategies/filter-normalizer.ts—fieldLeaves, theconst opKeys = Object.keys(wrapper).filter((k) => k.startsWith('$'))/if (opKeys.length > 0) { … return out; }arm. Verified onorigin/main(be87153): the early return is still there at :502-504, so the non-$siblings are still never visited.packages/services/**⇒domain:servicesper the SKILL domain table — same lane as the sibling #6386, and the same file the fix must land in.Stale-premise check
git log origin/main -- …/filter-normalizer.ts⇒ last touchd8e8d9c; nothing has landed on this file since the body was written, and the quoted code reads byte-identical on today's tip. Premise holds.Dedup (three repos, issues + PRs)
where门把undefined值的键整个丢掉 —— 单键 where 退化成「无过滤器」,方向是加宽(#6125 五面表漏记的第六、七种读法) #6386 — same function, explicitly a different defect (comparand-positionundefined, value-dependent). Its implementation is in flight as draft PR fix(analytics):where门的undefined比较数改为拒收,不再把整个键丢掉(#6386) #6445 (opened 19:43Z), which pins this card's measurement as a test without fixing it. Not a duplicate; the two are correctly separate.$notallowNull 守卫在 #5298 第二批之后变成冗余的第二层(谓词等价,SQL 多一层括号) #6005 (finding) — same file,$notallowNull guard redundancy. Disjoint.$null/$exists按真值性读比较数 ——{$null: "false"}编成IS NULL,#5347 / #5369 的先例没推到 RLS 编译器 #6387 / PR fix(analytics): read scope 的$null/$exists非布尔比较数改为拒收,不再按真值性编成相反的谓词 (#6387) #6443 — the other door in this package (read-scope-sql.ts). Disjoint by file.Why this is a decision card and not
pm:queueThe defect half is not in doubt — dropping a conjunct widens, in the same function whose own comment forbids exactly that (
NEVER drop: a missing predicate does not narrow the query, it WIDENS it … That failure mode is #3650's). What is in doubt is the accept face, and the two candidate remedies do not agree on it:FilterConditionSchema'sz.record(z.string(), z.unknown())half declares legal today;{ amount: { gte: 10 } }(a missing-$typo) into a predicate on a non-existent memberamount.gte.So the question is not “which error envelope” but “is a mixed
$/non-$field wrapper a legal authoring shape?” — a public-contract call. This is the same class the maintainer took personally on #5240 (zero-operator field constraint: declared-legal by the samez.recordhalf, ruled loud rejection on 2026-08-04), so escalating rather than letting a lane pick is the consistent handling. The author did not self-decide either.PM recommendation (non-binding, for the inbox)
A, on three converging precedents: the #5240 ruling (“a shape that cannot be read must explode at authoring time”); the sibling door in this very package already fail-closes on this exact predicate (
read-scope-sql.tscompileField,keys.some((k) => !k.startsWith('$'))); and #6386's queued fix uses the sameINVALID_FILTER/ 400 envelope, which is caller-input-facing and already established here (#5352 / #6050). B's cost is concrete: it converts a diagnosable typo into a silently compiled predicate on a member that does not exist.Ordering
No
Blocked-by:line, deliberately. #6444 and #6386/PR #6445 sit in the same seat (domain:services), touching the same function — the services lane sees its own in-flight batch, so serialisation is that lane's step-3 business, not a cross-seat dependency the unlock sweep needs to grep. (Contrast #6438 last round, whose sibling was in another seat.)Release board
No
target:v17. Reachability is honestly recorded as unproven, the direction is not yet decided, and the sibling defect #6386 — strictly more reachable — is not on the board either. If the decision lands as A and a producer is found, that judgment can be revisited at the next freshness pass.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
Maintainer ruling — 2026-08-08. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「按照你的建议继续」). Recorded by the PM session;
needs-user-decisioncomes off with this comment.Decision: Option A — refuse. A field constraint mixing
$operators with non-$sibling keys is rejected loudly through the module's existing envelope (INVALID_FILTER/ 400). Option B (flattening the siblings as a nested path) is rejected: it would compile the likely-real cause — a dropped$— into a predicate against a non-existent memberamount.gte, turning a diagnosable mistake into a harder one.Rationale: dropping a conjunct widens the query, returning rows the author excluded — the #3650 family, and the same file's own comment forbids it in as many words ("NEVER drop: a missing predicate does not narrow the query, it WIDENS it"). The standing meta-criterion applies directly: one shape, two doors, and the sibling door (
read-scope-sql'scompileField) already fails closed on exactly this input — converge on the governed side. And per the AI-safety axis,{ amount: { gte: 10, $lte: 20 } }is a canonical agent typo; silence there is a wrong answer no one can learn from.Requirements:
$key(s) and gives both legal rewrites: the operator spelling (gte→$gte) and the nested-relation form, so the message distinguishes the two intents it cannot.code+status(ADR-0112 envelope) — a baretoThrow()does not constitute a refusal test.where门把undefined值的键整个丢掉 —— 单键 where 退化成「无过滤器」,方向是加宽(#6125 五面表漏记的第六、七种读法) #6386 left in place (a non-$ SIBLING of an operator is still dropped — a different defect, not this one) flips to a positive refusal assertion in this change.Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
CLAIM — services-lane PM session
session_01USNUyHEr7uaU6MoEWXitei, branchclaude/issue-6444-mixed-operator-refusal.Dispatching under the 2026-08-08 02:12Z maintainer ruling (Option A — loud refusal via
INVALID_FILTER/ 400), all four ruling requirements binding: refusal message names the offending non-$key(s) and shows both legal rewrites; refusal test assertscode+status; #6386's sibling-drop pin flips to a positive refusal assertion; scope stays this one door with the per-face #6410 statement.Serialization check done before claiming: same-function sibling #6386's implementation (PR #6445) merged 2026-08-07 20:19Z, so the base already carries the pin to flip and the
assertDefinedComparandsidiom to match. Work bases on post-#6445origin/main; the card's quoted line numbers have drifted — sites located by content.Generated by Claude Code
fix(service-analytics)!: the analytics `where` door runs every arm of…
fix(service-analytics)!: the analytics where door runs the shared com…
fix(service-analytics)!: the analytics where door refuses a non-boole…