Repository navigation
A list inside a nested-relation condition in a dataset or measure filter ({ account: { region: ['a'] } }) passes the save-time schema door and is refused only when the chart runs #20080
Description
Activity
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions分诊首次定级:
priority:p3·bug·domain:spec·pm:queue—— 数据集或度量的筛选里,嵌套关联下的列表({ account: { region: ['a'] } })在保存时通过校验,到图表运行时才被拒绝Path: 分析的两个载体
DatasetSchema.filter/DatasetMeasureSchema.filter(packages/spec)· 对照packages/spec/src/data/filter.zod.ts(第 1676 行起,只在depth === 0时判列表)与packages/services/service-analytics/src/strategies/filter-normalizer.ts(第 1569 行forEachWhereFieldEntry会下探嵌套关联,第 1655 行assertNoListInEqualitySlot拒绝列表)Triage: lands in
packages/spec(the analytics carriers) ⇒domain:spec,bug,priority:p3,pm:queue; rationale: the save door judges an equality-slot list only at depth 0 (deliberately, mirroring the shared face and ruling A5805248669, because the engine accepts a deep-equality comparand there) while the analytics door descends a nested relation and refuses the list, so a dataset saves clean and every chart on it fails withINVALID_FILTER/ 400 (measured by the #19889 dev); 0 shipped instances, loud at chart time, hence p3; triage's routing is remedy A — a refinement on the two analytics carriers that refuses at save what the analytics door refuses at run — which moves the refusal earlier without changing any ruled meaning; remedy B (the analytics door stops descending) changes what a nested list means and would need a ruling, so it is not taken.分诊席 #6015,2026-09-25T02:26Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectstack
origin/main7f1de2eb66上核对。#20081 是本卡的一字不差的重复(晚 50 秒建成),本席已把它交给维护者,建议作为重复关闭。本席核对
filter.zod.ts第 1676 行:if (depth === 0) {才判列表。filter-normalizer.ts第 1569 行是forEachWhereFieldEntry,第 1655 行是assertNoListInEqualitySlot。- 两份待发更新说明都还在
.changeset/:19889-filter-schema-door-array-equality.md第 24 行写明了这个例外(「A field spec with no$key, such as the nested-relation condition …, is not judged … The analyticswheredoor does refuse that shape when a dataset or measure filter is charted」);另一份是19888-analytics-implicit-array.md。 - PR fix(spec)!: refuse an array in the equality slot at the schema door, in the comparand face's own words (#19889) #20047 已合并为
6aa3188a39。
为什么选 A,而且不找维护者
- A:只在两个分析载体上加校验,保存时就拒绝分析那一面本来就会拒绝的东西。已有的含义都不变,只是把拒绝提前了。
- B:让分析那一面不再下探,这会改变嵌套列表的含义,需要裁决。
- 所以本席按 A 路由。如果执行席位认为 B 更好,再交维护者。
执行要点
- 在两个分析载体上加校验:像分析那一面一样下探嵌套关联,对等值位置上的列表用同一句话拒绝。共用的
FilterConditionSchema保持现状,不动。 ⚠️ 待发更新说明:如果本卡在下一次changeset version之前落地,上面两份待发更新说明里写「例外」的句子就会变成假话,必须在同一个 PR 里一起改掉(待发更新说明里的假话属于 p1 一类)。- 收窄的登记:按 ADR-0087 登记,更新说明写明旧数据集的改法。
- 钉子:
DatasetSchema.safeParse对嵌套列表返回失败,报错与分析那一面一致;顶层列表照旧拒绝;嵌套关联下的非列表值照常通过。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingand removed
on Sep 25, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01QcAS3qiYYZNezaxZxaUdMV
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-20080-dataset-filter-nested-list-refused
Worktree:objectstack-issue-20080
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface:packages/spec/src/ui/dataset.zod.ts(a refinement on the two analytics carriersDatasetSchema.filter/DatasetMeasureSchema.filteronly) and its tests; the exception sentences of the two PENDING changesets.changeset/19889-filter-schema-door-array-equality.mdand.changeset/19888-analytics-implicit-array.md(triage item 2: they turn false when this lands; a deliberate correction, confirmed by the at-tier record);packages/spec/src/migrations/(one ADR-0087 entry plus its regenerated region);.changeset/. ⛔ Notpackages/spec/src/data/filter.zod.tsorfilter-comparand-shape.ts(held by #19886 stage 2b), import-only reuse. ⛔ Notservice-analytics(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier(dispatch-gates.mjs --tierat2bbebf5: 「no path-derived mandate … floor · default · ceiling」; a door refinement mirroring another face)
Clause-②: no
Thread-read: 5825670610
Serial constraints cleared: read at 2026-09-27T06:52Z —Open-PR census (10 open PRs besides the release PR): none touches packages/spec/src/ui/dataset.zod.ts or the two pending changesets; PR #20180 (#20003) edits a service-analytics test only. In-flight claims: #19886 stage 2b (os-zhuang) names filter.zod.ts and filter-comparand-shape.ts, off this surface (import-only). Sibling #20116 (same class, the shared checkFilterConditionComparands in filter.zod.ts) is NOT folded: fold gate ① fails (different fix sites, shared door vs carrier refinement) ⇒ it stays queued, serial behind #19886.
Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 Remedy A only, as triage routed it (
5825670610); ⛔ remedy B (the analytics door stops descending) needs a ruling and is not this card.- added a commit that references this issue
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20080,
"status": "done",
"branch": "claude/issue-20080-dataset-filter-nested-list-refused",
"pr": "#20207",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM session id)",
"premise_still_valid": true,
"summary": "Remedy A as triage routed it (record 5825670610). DatasetSchema.filter and DatasetMeasureSchema.filter now refuse on save an array in the equality slot of a field inside a nested-relation condition (implicit form and $eq, empty included, any relation depth, under $and / $or / $not). The walk mirrors the analytics door (its mapWhereFieldEntries traversal and isNestedRelationSpec test), reports only inside a nested relation, and prints the face's sentence from the shared builder arrayEqualityComparandMessage; the shared FilterConditionSchema is untouched (ruling A). The premise re-measured at base 9e7824a: DatasetSchema.safeParse answered success:true for both shapes, and the analytics door refuses both (INVALID_FILTER/400). The narrowing is registered as ADR-0087 entry dataset-filter-nested-relation-equality-array-refused-at-save; the exception sentences of the two pending changesets (19888, 19889) and one sentence each in two unreleased sibling major-18 entries are corrected; the 11 new dropped-refinement sites are declared.",
"tests": "All at head 3ed391a unless named; exit codes captured before any pipe; heavy runs through os-verify-lock.sh. New pins packages/spec/src/ui/dataset-filter-nested-relation-list.test.ts: 68 passed (§1 12 nested shapes refused at the list path on DatasetSchema.filter, measure filter via DatasetSchema, DatasetMeasureSchema alone, plus one-document-all-refusals; §2 carrier message EQUALS the analytics door refusal rebuilt from its one-entry hand-over to the face, minus the location clause, envelope INVALID_FILTER/400 asserted; §3 top-level lists reported exactly once; §4 13 controls accepted and kept on both carriers, and FilterConditionSchema still accepts both nested shapes). Ablation 1 (scripts/ablation-replace.mjs, anchor 1 to 0, blob 781a5b6df01f to 5411f8763031): refinement removed -> 50 failed | 18 passed (§1+§2 red, §3+§4 green); restore proven blob == HEAD 781a5b6df01f, git diff HEAD empty. Ablation 2: outside-relation guard removed -> exactly the 4 §3 rows red (4 failed | 64 passed); restore proven. Subject resolves by relative import to src, so no dist leg applies. Cross-door conformance (one-off, not committed): 3000 seeded random filters through the real analytics door (normalizeAnalyticsFilterTree, service-analytics source) and the rebuilt DatasetSchema: door refused 2240 with the equality sentence, 1304 of them where the shared schema alone misses; carrier agreed on 3000/3000, 0 mismatches. pnpm --filter @objectstack/spec test at 43df140: 1 failed | 15902 passed | 2 todo (the ledger header total, fixed in 3ed391a); scripts/dropped-refinements.test.ts + new pins rerun at 3ed391a: 95 passed. pnpm --filter @objectstack/spec typecheck: exit 0 (check:test-typecheck OK, 53 files / 255 errors held in ledger); tsc -p tsconfig.test.json --listFiles lists the new test once; 0 of the 255 errors in any touched file. pnpm --filter @objectstack/spec build && check:generated at 43df140: all 15 artifacts up to date; gen:schema rerun at 3ed391a: exit 0, 585 sites across 207 schemas all declared, tree clean. Base vs head ui/Dataset.json and ui/DatasetMeasure.json bodies byte-identical with x-dropped-refinements removed (compare worktree at 9e7824a). Consumers (upstream-prefix filter @objectstack/service-analytics^... built its 14-package closure): service-analytics test 128 files / 3017 passed (all 128 tracked test files, incl. the read-only control where-equality-slot-list-refusal.test.ts); metadata typescript-serializer-annotation.test.ts 7 passed; platform-objects report-form-echo-decisions.test.ts 35 passed; rest (3 files) and cli (1 file) declared to CI (closures 25 / 57 packages) with a static sweep: none of the 80 consumer test files referencing DatasetSchema passes a nested-relation list through it. Lint (declared narrowing): eslint --no-inline-config --format json on the 6 touched TS files -> 6 files reported, 0 errors, 0 warnings, none ignored; eslint.config.mjs never enables type-aware linting (its note near line 326) and no touched module export changed, so no untouched file verdict can move. Repo-wide pnpm lint is CI.",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at 3ed391a (identical to the derivation at 43df140): 87 families. --ran reconciliation with recorded exit codes: 87 derived, 85 run, 2 NOT-MEASURED, 0 UNRUN. 84 exit 0. check-empty-changeset --base origin/main exit 1 BY DESIGN (DELIBERATE CORRECTION of the two pending changesets, declared under its own PR heading, no skip-changeset). NOT MEASURED: check:type-check-debt and check:dual-build-cjs-loads, both exit 3 PREREQUISITE NOT MET (whole-repo build, CI). check:doc-formula-expressions first exit 3 (lint/formula unbuilt), rerun exit 0 after the closure build. CI not awaited (in_progress at report time).",
"line_budget": "n/a (no skills/** or governed ledger touched)",
"files_changed": [
"packages/spec/src/ui/dataset.zod.ts",
"packages/spec/src/ui/dataset-filter-nested-relation-list.test.ts (new)",
"packages/spec/src/migrations/entries/semantic/18.dataset-filter-nested-relation-equality-array-refused-at-save.ts (new)",
"packages/spec/src/migrations/entries/semantic/18.filter-equality-array-comparand-refused-at-save.ts",
"packages/spec/src/migrations/entries/semantic/18.filter-equality-array-comparand-refused.ts",
"packages/spec/src/migrations/registry.ts (regenerated region)",
"packages/spec/dropped-refinements.baseline.json",
".changeset/20080-dataset-filter-nested-relation-list.md (new)",
".changeset/19888-analytics-implicit-array.md",
".changeset/19889-filter-schema-door-array-equality.md"
],
"deviations": [
"packages/spec/dropped-refinements.baseline.json is outside the claim File surface: build-schemas.ts refuses a new refinement until its sites are declared (11 sites across 6 existing entries, exactly as printed; header droppedRefinementSites 574 -> 585).",
"Two unreleased sibling ADR-0087 entries were edited (one sentence each plus one acceptance-criteria sentence): inside the claimed packages/spec/src/migrations/ directory but beyond its parenthetical (one entry plus its region). Each sentence said the nested-relation list refuses only at chart time, which this change makes false; same principle as triage item 2.",
"H1 refined: the shared sentence lives in packages/spec/src/data/filter-comparand-refusal-text.ts (the builder both doors import), imported read-only; filter.zod.ts and filter-comparand-shape.ts are not edited (the test imports assertListComparandShapes only to rebuild the door refusal).",
"H1 design note: a first cut refined FilterConditionSchema directly, which cloned the recursive schema and changed the published ui/Dataset.json (a second inlined copy beside the $ref); replaced by a check on each carrier optional wrapper, measured byte-identical.",
"PR body carries the claim line Clause-②: no verbatim; the PR changeset carries Clause-②: no (narrowing), the spelling check:adr-0087-registration reads.",
"A foreground wait via pgrep -f matched the asking shell (as AGENTS.md warns) and timed out harmlessly; every later wait was on a PID recorded at launch."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, each one repository_dispatch to POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (PR 20207, draft forced), run 36308599466; (2) label-write --assign os-project-manager -> POST /repos//issues/20207/assignees, run 36308637463, read back MATCHES; (3) this os-dev-report comment -> POST /repos//issues/20080/comments. git push (not REST): 6 pushes of the branch. Reads (GET) not counted.",
"open_questions": [
{
"question": "Confirm the deliberate correction of the two pending changesets (.changeset/19888-analytics-implicit-array.md, .changeset/19889-filter-schema-door-array-equality.md); check-empty-changeset stays red until a person confirms it.",
"options": [
"A: confirm on PR 20207 as written",
"B: request different wording for either note"
],
"recommendation": "A: each edit replaces only the exception sentence this change makes false, and restoring either from base republishes a false sentence (the gate text names this class DELIBERATE CORRECTION)."
}
],
"out_of_scope_findings": [
"class: a · reach: DatasetSchema.safeParse, the save door behind defineStack / os validate / the metadata protocol save, answers success:true where the shared face and the analytics door answer INVALID_FILTER/400 · evidence: probe at 3ed391a with a control dataset that parses: { amount: { $gt: null } }, { stage: { $in: 'won' } }, { acct: { stage: { $in: ['won', null] } } }, { amount: { $between: [null, 5] } } and their { acct: ... } nested forms all accepted; the face refuses each; same save-door family as #19889 / #20116 -> fold into that family collector card, not filed singly · dedupe words: dataset filter $gt null accepted at save · FilterConditionSchema other comparand arms not judged at save · $in non-list dataset filter saves clean",
"carrier: 承接者:无 · noted, not filed: whether a stored dashboard widget filter or report runtimeFilter reaches the analytics door carrying a nested-relation list is decided on the objectui side, NOT MEASURED; a caller where and a dataset selection runtimeFilter do reach it and are refused at request time (loud)",
"carrier: 承接者:无 · noted, not filed: dropped-refinements.baseline.json measured.refinementSitesThatDidProject reads 369 while build-schemas.ts prints 409 at base 9e7824a and at head; no test reads the field"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsACCEPT — PR #20207 at head
3ed391abf9f717f1be723d9064d8bb19ba9c1c74·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T09:16ZThis is the seat's review, read against GitHub rather than the report.
- Shape: draft, base
main, first lineFixes #20080. It is the only closing keyword.Clause-②: nois line-leading, and it is right for a narrowing. The PR assignee isos-project-manager. - Remedy A (triage
5825670610):DatasetSchema.filterandDatasetMeasureSchema.filterrefuse an equality-slot list inside a nested relation, at any depth and under$and/$or/$not.- The sentence comes from the shared builder in
filter-comparand-refusal-text.ts, imported read-only. - ⛔ The shared
FilterConditionSchemais untouched (ruling A5805248669). The publishedui/Dataset.json/ui/DatasetMeasure.jsonbodies are measured byte-identical apart from the declared dropped-refinement sites. - Evidence: two ablations with proven restores, and 3000/3000 carrier-versus-door agreement.
- File-surface amendment to claim
5853543833(recorded here, ⛔ not a second claim):packages/spec/dropped-refinements.baseline.jsongains 11 sites, becausebuild-schemas.tsrefuses an undeclared refinement.- One sentence each in the two unreleased major-18 entries
18.filter-equality-array-comparand-refused-at-saveand18.filter-equality-array-comparand-refused. Both said a nested-relation list is refused only at chart time, which this change makes false; that is a must-fix under the dev contract.
Check Changesetred: this ischeck-empty-changeset's DELIBERATE CORRECTION class, triage item 2 (the exception sentences of pending19888and19889). The dev's open question, answered: A, keep both corrections. Underlanding-operations.mdthe confirmation is a same-head at-tier PASS record that names each corrected note and judges each rewritten sentence. ⛔ Noskip-changeset.- Clause-② gate: the path limb fires (
packages/spec/src/**).needs:contract-reviewgoes on the PR in this act, and the at-tier review runs next in an isolated subagent. - CI on this head at review time: 12 success, 3 skipped, 16 in progress, 1 red (
Check Changeset, above).
Out-of-scope findings (the dev's three):
DatasetSchema.safeParseaccepts{ amount: { $gt: null } },{ stage: { $in: 'won' } },{ amount: { $between: [null, 5] } }and their nested forms, all of which the face and the analytics door refuse. Class a, reach measured at the save door. → Folded into spec: FilterConditionSchema (the save-time door) admits a non-boolean $null / $exists flag, so a stored dataset or widget filter carrying one saves clean and is refused 400 on every query face #20116 as this save-door family's collector (the second occurrence), noted there in this act.- Whether a stored widget or report runtime filter reaches the analytics door with a nested-relation list is decided on the objectui side and not measured here. → Acceptance notes.
dropped-refinements.baseline.jsonmeasured.refinementSitesThatDidProjectreads 369 where the build prints 409, and no test reads the field. → Acceptance notes (an unread informational count, not a gate).
- Shape: draft, base
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20080,
"status": "done",
"branch": "claude/issue-20080-dataset-filter-nested-list-refused",
"pr": "#20207",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM session id)",
"premise_still_valid": true,
"summary": "Merge-only round after the queue dequeued PR 20207 with MERGE_CONFLICT. Re-fetched origin/main (af32cf9, which contains e7344f0 #20204) and merged it once with scripts/pm/os-regen-merge.sh, in its order: merge, commit, rerun, regenerate. The only conflict (driver-free bare-clone probe: exit 1, one path) was packages/spec/dropped-refinements.baseline.json. The merge commit e70d591 takes main's side, byte-equal to main's blob. The regeneration commit a053bfc re-derives this branch's 11 sites from the schema build's printed corrected entries, with none removed, and sets the header to the build's own measurement: 588 sites across 207 schemas, which is main's 577 plus 11. registry.ts auto-merged and is exactly what gen:migration-registry writes. Pushed as a fast-forward: 3ed391a to a053bfc. No change to the PR's own content, no rebase, amend or force-push, no ready, auto-merge or label action.",
"tests": "At new head a053bfc, heavy runs through os-verify-lock.sh, exit codes captured before any pipe. pnpm --filter @objectstack/spec build: exit 0 (gen:schema: 588 refinement sites across 207 schemas, all declared; 1536 schemas generated; tree clean afterwards). pnpm --filter @objectstack/spec check:generated: exit 0, all 15 generated artifacts up to date (check:migration-registry, check:authorable-surface, check:api-surface, check:spec-changes, check:upgrade-guide, check:docs among them). vitest scripts/dropped-refinements.test.ts + src/ui/dataset-filter-nested-relation-list.test.ts: 2 files, 95 passed (27 ledger tests including the header-totals test, plus the 68 pins). Standalone: check:migration-registry exit 0 (256 semantic, 210 retired-key, 196 retired-def), and this PR's entry id appears exactly once as an id. check:authorable-surface exit 0. node scripts/check-adr-0087-registration.mjs --base af32cf9 (the merged main): exit 0, 1 declared-breaking changeset carrying registered dataset-filter-nested-relation-equality-array-refused-at-save. check-empty-changeset --base af32cf9: exit 1 on exactly the two DELIBERATE CORRECTION notes (19888, 19889). Unchanged and by design. Old-head to new-head split (3ed391a to a053bfc, 152 paths, +9374/-2660): 150 byte-equal to main's blob. Regenerated: 2. dropped-refinements.baseline.json is main's side plus the build-printed 11 sites plus the measured header. registry.ts: gen:migration-registry rewrites it byte-identical (blob db141c3889), and its prose outside the 6 generated regions equals main's (control: the old head's prose equals base's, so the branch never touched prose). Anything else: 0. Survival (script step 4.3): all 255 of main's semantic ids are present at head, and the only extra id is this PR's. The entries directory differs from main only by this PR's entry file. #20204's packages/spec/src/data/filter.zod.ts is byte-equal to main. GitHub after the push: PR 20207 head a053bfc, mergeable true, mergeable_state blocked (checks or review pending), base af32cf9. origin/main was still af32cf9 at that read, so it did not move and no second merge arose.",
"gates": "Ran the gates the dispatch named for this round (above). No full re-derivation this round: the diff only brings in main's files and regenerates two generated files. check-empty-changeset stays red by design (DELIBERATE CORRECTION, unchanged). CI on a053bfc was not awaited.",
"line_budget": "n/a",
"files_changed": [
"merge commit e70d591: origin/main af32cf9 brought in; packages/spec/dropped-refinements.baseline.json resolved to main's side",
"a053bfcc2a: packages/spec/dropped-refinements.baseline.json (+11 build-printed sites, droppedRefinementSites 577 -> 588)"
],
"deviations": [
"Dispatch premise corrected by measurement: dropped-refinements.baseline.json is NOT written by build-schemas.ts or gen:schema. It is a hand-edited ledger (its own description), git check-attr merge answers unspecified, and it is not in NOT_DRIVER_MANAGED. build-schemas.ts only verifies it and PRINTS the corrected entries. So "regenerate" was done as: take main's side in the merge commit, then apply the build's printed corrected entries mechanically (a script that refused any removal or any site outside the 11 expected), then set the header from the build's measured total. No count was hand-merged.",
"os-regen-merge.sh labels this path class 1 ("resolve by hand"). The dispatch's route (main's side, then re-derive after the merge commit) was followed instead; both land on the same bytes, and this one keeps the merge commit free of hand-merged content."
],
"mcp_calls": "0",
"api_writes": "1 REST write: this os-dev-report comment, POST /repos//issues/20080/comments, sent through the fleet-write relay as objectstack-fleet[bot]. git push (not REST): 1 fast-forward push, 3ed391a to a053bfc. Reads (GET) not counted.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. #20204 (merged in this round) now refuses a list under $ne at the shared face and at FieldOperatorsSchema.$ne. Measured at a053bfc against a control dataset that parses: DatasetSchema.safeParse accepts { stage: { $ne: ['a'] } } and { acct: { stage: { $ne: ['a'] } } }, while the face on the analytics hand-over refuses both with INVALID_FILTER/400. That is the save-door family already reported last round (the other arms, #19889 / #20116). One consequence for this PR's own text: the dataset.zod.ts docblock phrase "$ne carrying a list (no ruling decides it)" and the §4 test label "$ne carrying a list — no ruling decides it" are now stale. Behaviour and pins are unaffected (the carrier does not judge $ne, and the pins still pass). Not edited, because this round forbids content changes. The fix is a one-line wording change for whoever picks up the $ne save-door gap."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20080,
"status": "done",
"branch": "claude/issue-20080-dataset-filter-nested-list-refused",
"pr": "#20207",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM session id)",
"premise_still_valid": true,
"summary": "Patch round: the two phrases this PR carried that became false once #20204 landed now state what is true. Commit 9425cae is a fast-forward from a053bfc, wording only, 2 files, +6/-2. The dataset.zod.ts docblock now says a $ne list is refused at the shared comparand face and at FieldOperatorsSchema.$ne under ruling A on #19886 (record 5805254639), and on the analytics door's hand-over of a nested entry, but not yet at this save door or FilterConditionSchema's. That stored-filter arm is #20116's: release record 5854888976 on #19886 moved it there, so it is no longer #19886's remainder. The §4 test label says the same in short. No behaviour change: the carrier still does not judge $ne.",
"tests": "Behaviour measured from source at a053bfc before wording, with a control dataset that parses. $ne: [a], top level and nested under account: DatasetSchema.filter, the measure filter and FilterConditionSchema all answer success true. The shared face (assertListComparandShapes): refuses the top-level form with INVALID_FILTER/400 ("Operator \"$ne\" on field \"stage\" requires a single comparable value"). It also refuses the analytics door's one-entry hand-over of the nested entry (holder where.account), and accepts the whole nested condition, which it does not descend. FieldOperatorsSchema.$ne [a]: refused at path $ne. Control $ne scalar: accepted. At head 9425cae: vitest src/ui/dataset-filter-nested-relation-list.test.ts gave 1 file, 68 passed, including the relabelled §4 row (through os-verify-lock.sh, command-exit 0). git grep "no ruling decides" over the files this PR changes against main: 0 hits. Diff 9425cae vs a053bfc: exactly 2 hunks, in dataset.zod.ts (docblock) and the test file (one label). No other file. Gates exit 0 on the edited text: node scripts/check-spec-docblock-symbol-anchors.mjs, pnpm check:issue-citations, pnpm check:doc-authoring, pnpm check:nul-bytes. The edited docblock sits on the module-private refuseNestedRelationEqualityLists, so no exported TSDoc or generated artifact reads it.",
"gates": "Ran the proofs the order named, plus the four text gates above. No re-derivation: the diff is two comment/label hunks. check-empty-changeset stays red by design (DELIBERATE CORRECTION, unchanged). CI on 9425cae was not awaited.",
"line_budget": "n/a",
"files_changed": [
"packages/spec/src/ui/dataset.zod.ts (docblock only, the $ne clause)",
"packages/spec/src/ui/dataset-filter-nested-relation-list.test.ts (one §4 test label)"
],
"deviations": [
"The order suggested citing "the carrier-save arm that #19886 records as its remainder". Measured on #19886: release record 5854888976 moved that stored-filter arm to #20116 and says it is no longer #19886's remainder. So the text cites #20116 for the arm, and names that record."
],
"mcp_calls": "0",
"api_writes": "1 REST write: this os-dev-report comment, POST /repos//issues/20080/comments, sent through the fleet-write relay as objectstack-fleet[bot]. git push (not REST): 1 fast-forward push, a053bfc to 9425cae. Reads (GET) not counted.",
"merge_state": "origin/main moved to 7e7fab7 (#20211, #20190, #20208) after the last merge. The PR is NOT dirty: a driver-free bare-clone merge-tree of 9425cae against 7e7fab7 exits 0 with no conflicted path, and GitHub reads mergeable true, mergeable_state blocked (checks and review pending), draft false, auto_merge null. No merge was made this round.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #20116 · noted, not filed, edited nothing: after #20204, more unreleased text still calls a $ne list unjudged (the list in record 5854888976 on #19886, measured here). Two of them sit in files this PR also touches. (1) .changeset/19888-analytics-implicit-array.md line 30: "$newith a list is not part of the ruling and is not judged here". That note is one of the two this PR deliberately corrects, and the sentence is now false: the analytics door's shape pass hands each entry to the face, and the face refuses a $ne list (measured: INVALID_FILTER/400 on the door's hand-over). The same-head review of the DELIBERATE CORRECTION may want it in that correction. (2) Entry 18.filter-equality-array-comparand-refused.ts lines 68-69: "$ne carrying an array ... is deliberately left to its own ruling", which is stale now that ruling A has landed; this PR edited a different sentence of that entry. The others are changesets 19757, 19974, 19975, 20010, 20035 and 20099. All were left untouched, because this round is limited to the two phrases."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsLanding record — PR #20207 merged through the merge queue at 2026-09-27T11:24Z as
e8fcf5554e·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T11:25Z- Verified by content on
origin/main:e8fcf5554eis an ancestor.refuseNestedRelationEqualityListsis inpackages/spec/src/ui/dataset.zod.ts.- The ADR-0087 entry
18.dataset-filter-nested-relation-equality-array-refused-at-save.tsis present underpackages/spec/src/migrations/entries/semantic/, and it is registered inregistry.ts. - The pin
dataset-filter-nested-relation-list.test.tsis on main.
- Card: closed
completedbyFixes #20080.pm:dispatchedwas stripped in this act;bug,domain:specandpriority:p3stay. - Review chain:
- at-tier PASS 108/108 at
3ed391abf9, which confirmed the DELIBERATE CORRECTION of the two pending notes for [finding] service-analytics' filter normalizer reads an implicit-equality ARRAY asIN, while ruling 乙 refuses the same shape at the shared face "for every driver at once" #19888 and [finding]FilterConditionSchemastill PARSES{ field: [...] }and{ field: { $eq: [...] } }, so a stored dataset or widget filter publishes clean and is refused at query time on every backend #19889; - a kick-out for a
dropped-refinements.baseline.jsonconflict, followed by a merge round; - a wording patch for the
$nesentence that fix(spec)!: refuse an array under $ne at the shared comparand-shape face and at FieldOperatorsSchema.$ne #20204 had made false; - at-tier PASS 130/130 at
9425caec39, which re-confirmed the correction on that head. It rode the queue withcheck-empty-changesetred by design, recorded on the PR.
- at-tier PASS 108/108 at
- Closing-keyword audit: since 11:15Z the repo closed this card, docs: error-handling.mdx says
QUOTA_EXCEEDEDis "not emitted today … do not write a client branch against it", but cloud's AI agent route emits it and objectui branches on it #19958 (PR docs(error-handling): QUOTA_EXCEEDED has one producer, the ObjectOS agent chat route #20220) and spec(ui)+metadata save: judge a flattened view overlay's top-leveloptions.KINDwith the strict per-kind schema and persist the PARSED body — the door half of objectui#10380 #20051's PR fix(spec)!: judge a flattened list view overlay's legacyoptionsbag at the view write door (door half of #20051) #20183 (Part of, which closed nothing). PR fix(spec)!: a dataset or measure filter with a list inside a nested relation is refused on save (#20080) #20207 names no other issue. - Unblocked: none. No open
pm:blockedcard carriesBlocked-by: #20080.
- Verified by content on
- added 3 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a product defect with a named landing site (the publish-clean, fail-later class). It was measured by the #19889 dev (os-dev-report
5823840715,out_of_scope_findings[0], class c). The at-tier review5824029954on PR #20047 confirmed it real. Thedomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d) re-read it atorigin/main7f1de2eb66, after PR #20047 landed as6aa3188a39. Filed unassigned and unlabelled: routing and grading are triage's. ⛔ Not a claim. ⛔ Not a ruling: the remedy needs one (below).Hand that acts: the lane triage routes this to.
Dedupe (including closed): the semantic query
nested relation list dataset filter publishes but refused at chart time analytics equality arrayreturns 5 hits. The nearest are #20035 and #20010 (the object-form analyticswhereskipping other arms of the shared comparand face). Neither covers a list under a nested relation. No match onDatasetSchema filter nested region array accepted assertNoListInEqualitySlot.The defect (at
origin/main7f1de2eb66)FilterConditionSchemarefuse an array in the equality slot, but only atdepth === 0of each pass (packages/spec/src/data/filter.zod.ts:1676,:1698). A no-$spec, i.e. a nested-relation condition, recurses atdepth + 1(:1690) and is not judged. That is deliberate: the shared compile faceassertListComparandShapesdoes not descend a no-$spec either. The engine accepts a deep-equality comparand there, and ruling A (5805248669) mirrors the face.packages/services/service-analytics/src/strategies/filter-normalizer.tsforEachWhereFieldEntry(:1569) recurses into a no-$spec (:1588-1590) and flattens the nested relation to a dotted member.assertNoListInEqualitySlot(:1655) then hands each equality-slot list to the shared face, which refuses it withINVALID_FILTER/ 400.46e1c81727:DatasetSchema.safeParse({ …, filter: { account: { region: ['a'] } } })returnssuccess: true, while the analytics door answersINVALID_FILTER/ 400 ("The implicit-equality comparand on field "region" …"). The dataset saves clean, and every chart built on it fails..changeset/19889-filter-schema-door-array-equality.mdand the corrected.changeset/19888-analytics-implicit-array.mdstate the exception: a list inside a nested relation still publishes and is refused when it is charted.Producer: a stored dataset or measure filter. In-repo census (the dev's): 0 shipped instances. Deployed metadata: NOT MEASURED.
Remedy shape (needs a decision; not ruled here)
DatasetSchema.filter,DatasetMeasureSchema.filter) get a refinement that descends a nested relation the way the analytics door does and refuses the list at save. The sharedFilterConditionSchemastays as ruled, because the engine path accepts a deep-equality comparand.$spec and treats it as the engine does (a deep-equality comparand). The two doors then agree and nothing is refused late, but a nested list's meaning changes.Seam: spec
DatasetSchema.filter/DatasetMeasureSchema.filter→ runtimeservice-analyticsfilter-normalizer.tsassertNoListInEqualitySlot.Dedupe words:
nested relation list dataset filter publishes·analytics where nested relation equality array·DatasetSchema filter nested region array acceptedGenerated by Claude Code