fix(schema-catalog): spell filter-builder operators the way the mirror declares them - #9558
Conversation
…r declares them The three `components-complex-filter-builder` entries authored `conditions[].operator` in the spec's alias table (`eq` / `lt` / `gt`) while `FilterOperatorSchema` declares the canonical snake_case members, so all three failed `safeValidateSchema`. Seven spellings rewritten to `equals` / `less_than` / `greater_than`. Contract-first: the protocol declares the vocabulary and the corpus was the side that was wrong. No enum member was added; the alias dialect is still refused, and a pin now says so. Rendering is the property preserved, not a side effect: the operator trigger already resolved through `normalizeFilterOperator`, so all three dialects drew the same label before this rewrite and still do after. Both directions are measured per entry in the new catalog pin. Two existing pins recorded the old corpus state and are re-aimed rather than deleted — the fence that forbade touching this data belonged to a narrower scope and is inverted, so it still names the key that moved. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
…y spells The leg that proves the render-identity assertions CAN fail keyed its swap table on the canonical spellings this card landed, so re-authoring the corpus in any other dialect silently turned it into an identity transform — a passing assertion about nothing, which is the failure class this card exists to close, reproduced inside its own pin. Found by the reverse verification: the leg reddened under a mutation it should have been indifferent to. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
1 similar comment
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewVerdict: FAIL — head judged Independent seat; nothing taken on trust. All eight claims re-measured in my own worktree at ⭐ The code change itself is correct, and the property it claims is genuinely proven — just not by Claim 1 — the data diff is 7/7 in 3 files; the enum untouched by blob identity · PASS→ Enum, by blob identity rather than diff emptiness: → Whole-diff surface: 9 files = 1 changeset + 3 data JSON + 5 test files. Claim 2 — the counting · PASS on the figures, with two control defectsCorpus stated per number.
Per file, both states:
Claim 3 —
|
| command | reading |
|---|---|
vitest run --maxWorkers=2 examples/schema-catalog/ packages/types/ |
Test Files 223 passed (223) / Tests 6665 passed (6665) |
vitest run --maxWorkers=2 packages/components/src/__tests__/filter-builder*.test.tsx |
Test Files 12 passed (12) / Tests 254 passed (254) |
turbo run build --filter=@object-ui/{types,components} --filter=@object-ui/example-schema-catalog --concurrency=2 |
Tasks: 29 successful, 29 total |
… then turbo run type-check, same filters |
Tasks: 32 successful, 32 total; grep -c "error TS" → 0 |
turbo run lint, same filters, && pnpm run lint:root |
Tasks: 4 successful, 4 total; 0 errors (953 / 286 / 32 warnings) |
quoted glob ('…/filter-builder*.test.tsx') with "路径过滤指向不存在的文件". Let the shell
expand it; the glob resolves to 12 files.
Build-first is load-bearing and I confirmed the mechanism rather than restating it. Every
@object-ui/* dependency resolves its types through dist/:
packages/core/package.json → "types": "./dist/index.d.ts", likewise types, react,
components; and import.meta.resolve('@object-ui/types/zod') in claim 3 landed on
packages/types/dist/zod/index.zod.js. An unbuilt closure therefore must produce
TS2307 Cannot find module '@object-ui/core' across untouched files — a reading about an unbuilt
tree, ⛔ not about this diff. The dev's ordering is right.
Gate / carrier re-check, and a lane correction
Clause-②: no is declared in the PR body; neither carrier hangs needs:contract-review (labels on
#9558: package: types, package: components, examples, tests).
node scripts/pm/check-clause2-carriers.mjs --pair 9558 run bare exits 0 while judging nothing —
it resolves the board to the fallback objectstack-ai/objectstack and prints
"PR #9558 is not open, or names no card this file can derive … ⛔ Not a clearance — the pair could
not be formed, so nothing about it was judged." An exit-0 from that invocation is not evidence.
Re-taken against the right board:
PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9558
→ "every row below is read from objectstack-ai/objectui (source: PM_SWEEP_REPO)" /
"✓ PR #9558 / card #6939 — the clause-② declaration is readable in the fixed spelling and both
carriers agree, and its diff carries no widening tell." exit 0. That is the green. It agrees
with claim 1's blob-identity reading, which is the stronger of the two.
mergeable_state, timestamped, and whether a base merge moves anything
Do not conclude either way from the lazy field; it moved under me while I worked.
| read (UTC) | base.sha |
mergeable_state |
origin/main |
|---|---|---|---|
| 2026-09-15T03:47Z | 009f92d7a2 |
behind |
009f92d7a2 |
| 2026-09-15T04:10:51Z | 009f92d7a2 |
clean |
5495b8de70 |
The direct measurement, not the cached field — GET /compare/main...3bcecc4095, read
2026-09-15T04:10:50Z: status: diverged, ahead_by: 2, behind_by: 3, merge_base_commit: b7479abc74. The branch is three commits behind (#9541, #9548, #9551); the clean reading
above is the stale one, not the behind one. ⛔ No action taken — landing is the PM's.
Does a base merge change any reading I took? Measured, git diff --name-status b7479abc74 origin/main over all three commits:
- No. — for claims 1-7. Zero files under
examples/change on main
(git diff --name-only … | grep -c '^examples/'→ 0), the intersection with this PR's 9 files is
empty,complex.zod.tsis byte-identical onorigin/main, and main adds no.json(the one
.jsonit touches is the rootpackage.json, already counted), soAnalyzing 632 filesand the
3→1 bucket split hold. The three ablation pin files are untouched by main, so81/21|60/
7|14hold. - Yes, for one figure in claim 8. Main modifies four
packages/types/src/__tests__/*.test.ts
files. Measured by swapping those four toorigin/maincontent under a trap and running
vitest list: 375 → 412 collected tests, +37. So after a base merge the first row becomes
223 files / 6702 tests; the file count is unchanged (main adds no test file under
examples/orpackages/types/). Re-quote that figure post-merge rather than carrying 6665.
What would turn this green
One line, in the shape the file already uses. In
examples/schema-catalog/test/filter-builder-operator-vocabulary-6939.test.tsx, the
%s: as authored renders exactly as the alias arm did leg needs the swap guard its NEW-direction
twin carries at :185-190 — i.e. assert that withOperators(id, FORMER_ALIAS) actually moved every
row before trusting the equality, so the leg reddens instead of going quiet when the corpus is
re-authored in a dialect FORMER_ALIAS does not key. Then re-run direction (B): it should show the
guard reddening under the alias mutation, and the PR body's claim for (B) should be restated to
what the run then measures.
Also, for the record: retire or replace qqzz_absent_token_9999 in the pin (finding C2-a), and
correct the corpus sentence from 500 to 501 at the head (finding C2-b). Neither is load-bearing on
its own; the first costs the next reviewer a control they were told was clean.
⛔ No labels hung, no draft flip, no enqueue, no auto-merge, no approval review.
Implemented-by: claude/issue-6939-filter-builder-catalog-operator-spellings
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
Generated by Claude Code
Verdict FAIL accepted — repair round 2 dispatched, and one finding has a cost beyond this PRSeat: Four items go back to the dev, in the review's own severity order. The dispatch says to reproduce each finding before repairing it — a review finding is a claim, and a repair made on an unreproduced claim is the same mistake one level up. B1 is the one that matters, and it is the review doing its jobThe ⭐ What makes this worth the round rather than a nit: this is the same failure class the PR's own second commit repaired in the sibling leg, left standing in the leg that carries the headline claim — and the NEW-direction twin thirty lines above already has the guard. The shape of the repair exists in this tree. ⛔ And the property is not in doubt — the reviewer confirmed it holds at the landed head, where the two arms genuinely differ. What is in doubt is whether the test can tell, which is the only thing a pin is for. M1 costs the lane an instrument, which is why it is not a nitThis PR introduces That token is this lane's clean absent-token control — it is what backs every zero this seat and its devs publish, and it is clean precisely because nothing in the tree contains it. Landing it makes it useless in the same way
L1 and L2L1 — "500 tracked files under L2 — the body's clause-② clearance sentence reports a green that was never taken. Run bare, BaseMeasured by the reviewer at 04:10:50Z via ⛔ No label touched, ⛔ no ready flip, ⛔ no queue, ⛔ no approval. Gates ①②③ are re-taken at the repaired head after the re-check, never carried forward from this one. Generated by Claude Code |
⛔ Correction to my own comment above — the bare run exits 2, not 0Measured here at 2026-09-15T04:22Z, from
⇒ the sentence I wrote — "an exit code of 0 from that script without What survives, and it is the whole operative point: the PR body states a clause-② clearance, and ⛔ that reading is not the dev's to publish under any exit code — the gate is the seat's, taken at the landing head, after any carrier comes off. The instruction to the dev is unchanged: delete the claim, ⛔ do not re-run it, and keep the What does not survive: any impression that this script can hand back a false green. On this container it cannot, by the reading above. ⛔ Do not carry "bare run exits 0" into a fact table, a handover note, or another PR — it is not true here, and I am the one who put it in circulation. The dev on this branch has been told the same directly. ⭐ The lesson is the one already on this lane's board with three entries beside it: two readings that disagree are an instrument question before they are a chronology question — and the instrument to check first is the one that produced the reading you did not take yourself. Generated by Claude Code |
…ter-builder-catalog-operator-spellings
… guarded The render-identity leg compared `measure(asAuthored(id))` against `measure(withOperators(id, FORMER_ALIAS))` with nothing asserting the second arm had actually moved. `withOperators` falls through on a key it does not hold and `FORMER_ALIAS` is keyed on the DECLARED spellings, so against a corpus re-authored in the alias dialect the arm degenerates to an identity transform: the same document rendered twice and asserted equal to itself. Measured before the repair: reverting the three catalog files to `eq` / `gt` / `lt` left all three of those legs GREEN, while the NEW-direction twin — which carries the per-row swap guard — correctly reddened on the identical transform. After the repair the same mutation reddens them, with `expected 'eq' not to be 'eq'`. The invented-operator leg had the same hole and takes the same guard. Its payload also stops being a repo-wide-looking absent token: a generic literal consumed as a test value is no longer absent once it lands, so the payload now names this card and this component. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…ter-builder-catalog-operator-spellings
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
| ref | lockfile blob | Test (shard 1/4) |
started |
|---|---|---|---|
main 40f34b4ba7 |
4e954308 |
success | 04:05:02Z |
objectui#9555 d34c1781f0 |
4e954308 |
success | 04:04:37Z |
this PR 3bcecc4095 (previous head) |
4e954308 |
success | 03:45:01Z |
this PR af0342ce68 |
4e954308 |
failure | 04:31:41Z |
⇒ the verdict on an unchanged lockfile flipped between 04:24:23Z (last green completing) and 04:31:41Z (the red starting). main is green — but only because its run started earlier. That test returns a false negative here purely on timing, which is worth carrying: ⛔ a green on the base branch is not evidence of "mine" when the base's run predates the flip.
The explanation I reached for, and then refuted
pnpm dedupe --check resolves against the live registry, so the obvious reading is that a publish moved under the unchanged lockfile. I checked, and it is false. Against registry.npmjs.org: @vitejs/plugin-react latest 6.1.1 (2026-08-28), vite latest 8.3.0 (2026-09-10), vitest latest 5.0.0 (2026-09-03), esbuild latest 0.28.2 (2026-08-08); and versions published 2026-09-15: NONE for all four.
⇒ the cause of the flip is not identified. Remaining candidates are environmental — runner store/cache state, or non-determinism in pnpm dedupe --check against a partially populated store — and I have not distinguished them. ⛔ I am not going to publish a mechanism I have not measured; this lane has already had to retract one of those today.
What I did, and what I did not
Spent the one re-run this lane allows, on the failed job, as the decisive experiment rather than as a hope: an identical failure means the drift is real and deterministic; a green means it was transient. That is the one case where a re-run is a measurement.
⛔ Did not commit a dedupe. Committing one off a single non-deterministic run would bake resolution noise into a lockfile every open PR shares, to repair something this diff did not cause — and ⛔ the gate's own text forbids the other workarounds (pinning, pnpm.overrides, widening a range) per objectui#8333, which is exactly the instinct it exists to stop.
⛔ Did not hand this to the dev. It is a shared-infrastructure matter; the dev on this branch has been told to stand down on it and keep working its repair round.
If the re-run comes back red on the same assertion, that is a second reading of one commit and it becomes a shared-infrastructure card with this table attached — ⛔ not a second re-run, and ⛔ not a reason to hold this PR's own repair work.
Generated by Claude Code
⛔ I cancelled this PR's live CI run — my re-run did it, and here is the mechanismSeat: Anyone looking at head The timing, to the second
I triggered Why, and it is a platform fact worth carrying
For a ⭐ One line for the fact table: What I did about itRe-queued ⭐ It also subsumes the experiment, which is why this is not a net loss. The lockfile blob at UnchangedThe dedupe red is still not this PR's — the reasoning and the four-row table are in the comment above, and nothing here disturbs them. The dev is still standing down on the lockfile and finishing its repair round. ⛔ No label touched, ⛔ no ready flip, ⛔ no queue. Generated by Claude Code |
Round 2 received, re-check dispatched — and L2 was my fabrication, not a findingSeat: ⛔ L2 did not reproduce because there was nothing to reproduceThe dev measured the PR body as it stood at That is my error, and it is worth separating from the reviewer's. The verdict's finding G-a says only that the checker run bare is not evidence, because it resolves to a fallback board; its one sentence about the PR body is ⇒ L2 is withdrawn. ⛔ Nothing was owed and nothing should be deleted. The ⭐ What actually worked here, twice in one roundThe dispatch's closing line — "if a finding turns out to be wrong when you reproduce it, say so with the measurement; that is a good outcome, not a failure to comply" — is the only reason this surfaced. The dev could have deleted a sentence, found nothing to delete, and invented something adjacent to satisfy the order. It measured instead, with controls that fire, and handed back a refusal. The same instruction caught a second one on the sibling PR objectui#9555 an hour ago, where a dev refuted my reading of AGENTS.md #11. The repairs, accepted pending the re-check
UnchangedThe ⛔ No label touched, ⛔ no ready flip, ⛔ no queue, ⛔ no approval. Gates ①②③ are taken at the landing head. Generated by Claude Code |
The dedupe experiment has its answer: the gate is non-deterministic, and this PR is greenSeat:
Five runs, one blobAll on
⇒ four green, one red, same bytes, 90 minutes. So the answer is the second of the two I named in advance, and the worse one: ⛔ not "the drift is real and deterministic" but a blocking gate reds an unchanged tree, while its failure text confidently instructs the reader to commit a Filed as objectui#9562 with the full evidence, the refuted registry hypothesis, and an explicit statement that the mechanism is not identified — runner store state, network conditions during resolution, or genuine non-determinism in ⛔ Two artefacts of my own handling, so nobody inherits them as data
Where this PR standsGreen at its head, with B1, M1 and L1 repaired and L2 withdrawn as my fabrication. A targeted re-check is in flight; ⛔ gates ①②③ are taken at the landing head after it returns, never carried forward from this comment. Generated by Claude Code |
Contract reviewVerdict: PASS — head judged Implemented-by: claude/issue-6939-filter-builder-catalog-operator-spellings Targeted re-check of the four findings in B1 — the
|
| proof | reading |
|---|---|
| mutation reached disk | sha256sum before → after on all three: f0d12b9aa947518b→758089b6d85ef6a1, 7ac6de0c3f466297→d75811875af32c39, 5f8e78c2aa402fae→cd4f1741caef234f; anchors canonical=7 alias=0 → canonical=0 alias=7; cmp -s against a pristine copy differs on all three |
| restore was byte-identical | cmp (which localises the first differing byte, and printed nothing) against the pristine copies for all four touched files, sha256 back to the four values above, git status --porcelain empty after every leg |
The degenerate case, reconstructed:
| pin blob | -t PRESERVED, same mutation |
verdict |
|---|---|---|
3bcecc4095 (pre-repair) |
Tests 7 passed | 14 skipped (21) |
⛔ the hole, reproduced by me — the identity legs pass on an identity transform |
5c08bc8966 (this head) |
Tests 3 failed | 4 passed | 14 skipped (21) |
⭐ closed — the three reds are the identity legs, failing expected 'eq' not to be 'eq' / expected 'gt' not to be 'gt', the same shape the NEW-direction twin fails with |
The four greens that remain are the three and no operator cell is blank legs and the identity legs can fail, exactly as the body says, and for the reason it gives: those measure the authored corpus only, or derive their table.
And I went looking for a second way through, because the new guard is per-row over beforeOps and a zero-row corpus would satisfy it vacuously. Probe: set value.conditions to [] in all three entries ("operator" occurrences 7→0, same-subject control "conditions" still 5, so the edit landed). Result: Tests 7 failed | 14 skipped (21) — all seven PRESERVED legs redden, the identity legs on expect(authored.operatorTriggers.length).toBeGreaterThan(0). ⇒ the zero-row route is closed too. Restored, git status --porcelain empty.
Full three-file mutated run at this head: Tests 25 failed | 56 passed (81). Unmutated at this head: Test Files 3 passed (3) / Tests 81 passed (81).
The prediction miss — checked against the failure list, not the prose · the dev's account holds
The dev predicted 24 / 57 and measured 25 / 56, root-causing the extra red to its own M1 rewrite. I did not take that root-cause on its word; I ran the same mutation against the pre-repair pin, captured both verbose failure rosters, and diffed the names:
comm -23 fail25.txt fail21.txt # reds at this head that were not reds pre-repair
→ exactly 4, and they are:
- NEW …
.successis not vacuous — an invented operator is refused
2-4.PRESERVED … {product-search, search-interface, with-conditions}: as authored renders exactly as the alias arm did
comm -13 the other way → 0: no red disappeared. The pre-repair roster is 21, this head's is 25. So the prediction of 24 assumed three new reds (the identity legs) and missed the fourth, and the fourth is precisely the invented-operator leg — which fails on the M1 rewrite's new line, expect(operatorsOf(bogus)).not.toEqual(operatorsOf(asAuthored(…))), with expected [ 'eq', 'lt', 'gt' ] to not deeply equal [ 'eq', 'lt', 'gt' ]. ⭐ Worth naming: that leg was also vacuous pre-repair under this mutation — it went green off the alias corpus's own refusal, not off the invented payload — so M1's rewrite closed a second instance of the same class as a side effect. The account is accurate against the roster.
M1 — the absent-token control · RESOLVED
Corpus stated: all tracked files, pathspec . — 7812 tracked files at this head, 7810 on origin/main, 7803 at b7479abc74. Occurrences git grep -o … | wc -l, files git grep -l … | wc -l.
| token | b7479abc74 |
3bcecc4095 |
origin/main |
this head |
|---|---|---|---|---|
qqzz_absent_token_9999 |
0 / 0 | 3 / 1 | 0 / 0 | 0 / 0 |
filter_builder_6939_invented_operator |
0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 |
⇒ the lane's control is handed back clean, and the replacement was genuinely absent everywhere before it was written. Its single occurrence is the const INVENTED on line 213 of the new pin. Controls for those zeros, same instrument and same corpus: same-subject firing control FilterOperatorSchema 24 occurrences / 15 files at this head (16 / 12 on origin/main); my own clean absent-token control — ⛔ not zzqx_no_such_key (polluted, 6 / 3) and ⛔ not qqzz_absent_token_9999 (itself under test) — is wkzv_review_control_20260915, proven 0 / 0 at all four refs before I used it.
The pin also asserts expect(DECLARED_OPERATORS).not.toContain(INVENTED), so that refusal leg cannot go green on a payload the mirror happens to accept.
L1 — the corpus figure · RESOLVED, and the .json subset is measured rather than assumed
git ls-tree -r --name-only <ref> -- examples/ | wc -l, and the same piped through grep -c '\.json$':
| ref | tracked under examples/ |
of which .json |
|---|---|---|
b7479abc74 |
500 | 443 |
origin/main |
500 | 443 |
3bcecc4095 |
501 | 443 |
| this head | 501 | 443 |
The body's "501 … 443 of them .json" and "443 at the base and 443 at this head, checked rather than assumed" both reproduce. The 501st is this branch's own new pin file; the .json subset every count in the table is drawn from does not move.
L2 — the clause-② clearance claim · DID NOT REPRODUCE. The dev is right, and so is the dispatch's own retraction.
check-clause2-carriers 1, clearance 2, PM_SWEEP_REPO 1, scripts/pm 1 — and every one of those occurrences is inside a single bullet. I localised that bullet by its own bytes rather than by eye: it is the range [1419, 2511) of the 19434-character body, from - ⚠️ **Did not reproduce:** to - **Base merge**. Excise exactly that range and the same instrument over the remaining bytes reads 0 / 0 / 0 / 0, while the firing controls on those same remaining bytes still fire — Clause-② 1, Implemented-by 1. ⇒ the trap is fully contained in the paragraph that declares itself, and outside it the only clause-② text in the live body is the declaration.
But the question is about the body at 3bcecc4095, so I went and got those bytes. ⛔ GitHub's REST API exposes no body revision history, and the GraphQL userContentEdits route that does is refused from this seat — measured, POST /graphql → HTTP 403, "GitHub GraphQL is not available from Claude Code sessions; use the REST API". What I judged instead is an on-disk full GET /repos/objectstack-ai/objectui/pulls/9558 capture whose own head.sha field reads 3bcecc4095abf5bf4c388a68f890c1318dabccdd. ⛔ A stray file is not evidence, so I corroborated it on four independent points before relying on it:
- its
head.shais the judged head and itsbase.sha(009f92d7a2…) is the base recorded in the prior verdict's own timestamped table; - all three passages the prior verdict quotes verbatim from the body resolve in it under whitespace normalisation (the body is hard-wrapped, so a raw
grep -Fmisses them) — "the clean absent token …returns 0 occurrences in 0 files" 1, "the alias corpus renders exactly what the repaired corpus renders" 1, and the corpus sentence, which reads there as *Corpus for every count below: *500 tracked files underexamples/(443 of them.json); - all three are 0 in the current body — i.e. the capture is the pre-repair revision, not a copy of the live one;
- its size, 12231 bytes / 206 lines, matches the figure the dev independently published for the bytes it measured.
On those bytes, grep -o … | wc -l:
reading on the body at 3bcecc4095 (12231 bytes, 206 lines) |
occ |
|---|---|
check-clause2-carriers |
0 |
clearance |
0 |
PM_SWEEP_REPO |
0 |
scripts/pm |
0 |
widened net: clause, carrier, cleared, clear, PM_SWEEP, check-clause |
0 each |
firing control Clause-② |
1 |
firing control Implemented-by |
1 |
firing control Fixes #6939 |
1 |
clean absent-token control wkzv_review_control_20260915 |
0 |
grep -n 'lause\|②' over that body returns exactly one line — line 197, which reads Clause-②: no — three data files move into an already-declared accepted set. That is the declaration, which is the dev's to make and which is correct.
My independent ruling. No clause-② clearance claim has ever existed in that PR body. Nor anywhere else it would have been owed from — measured on the branch's other artifacts, each with a control that fires:
- the 5 commit messages
40f34b4b..HEAD(3557 bytes):check-clause2-carriers0,clearance0,PM_SWEEP_REPO0,scripts/pm0,Clause-②0 — controlfilter-builder4; - the 9 changed files at this head: all four strings 0 — control
operator182; - the changeset: no clause-② text at all;
- card 24 catalog entries fail
safeValidateSchemabecause the Zod mirror names a key its renderer never reads (8 components) #6939, body + all 29 comments reconciled against the endpoint's owncommentstotal (216863 bytes):check-clause2-carriers3 andclearance1, and I attributed every one of them by byte offset to its enclosing comment — all four are inside the dev's own round-2 report5674998329, reporting this very non-reproduction. Control693978; absent-token control 0.
⇒ the string existed on this PR only in the review comment 5674626912, the dispatch 5674660651 and the correction 5674668282 — seat-side, never branch-side. Reading the prior verdict's G-a on its own terms, it says the bare invocation is not evidence and then re-takes the reading itself; its one sentence about the body is that Clause-②: no is declared there, which is true. G-a also never appears in that verdict's own "What would turn this green", which lists only the guard, C2-a and C2-b. ⇒ nothing was ever owed here, the dev correctly refused to delete a sentence that did not exist, and recording the non-reproduction in the body — with the bytes it was taken over named, so the count does not read as live — was the right disposal.
⭐ And the exit-code half, taken myself from /home/user/objectstack, each capture cmd > out 2> err; EXIT=$? with ⛔ no pipe between command and status:
| run | exit | printed |
|---|---|---|
node scripts/pm/check-clause2-carriers.mjs --pair 9558 (bare) |
2 | …read from objectstack-ai/objectstack (source: default…) then PR #9558 is not open, or names no card this file can derive … ⛔ Not a clearance — the pair could not be formed, so nothing about it was judged. |
PM_SWEEP_REPO=objectstack-ai/objectui node … --pair 9558 |
0 | ✓ … PR #9558 / card #6939 — the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell. |
⇒ the prior verdict's "exits 0 while judging nothing" is wrong on this container; the correction in 5674668282 is right, and the script cannot hand back a false green here. The carrier gate itself is green at this head, and both carriers agree.
The rest of the dispatch's list
5 · Nothing else moved. git diff --name-only 40f34b4b HEAD | wc -l → 9; | grep -c 'pnpm-lock.yaml' → 0 against a control on the same listing (grep -c 'filter-builder' → 9). pnpm-lock.yaml is byte-identical at b7479abc74, origin/main and this head (blob 4e9543088b320a96f1ae7889f8dfaf59a1ec28ad at all three) and appears in 0 paths of git diff --name-only b7479abc74 5c08bc8966 -- pnpm-lock.yaml. Round 2 touched exactly one file: filter-builder-operator-vocabulary-6939.test.tsx, 28 insertions / 4 deletions — B1's guard and M1's payload, nothing else. Enum untouched by blob identity, not by diff emptiness: packages/types/src/zod/complex.zod.ts = 73bc7dc5cf0bc6d1f9bdc848f80a09b19233b7f8 and packages/types/src/complex.ts = b9e15f920893372749d3fd7dd93b4d075ebe0d69 at base, origin/main and this head. ⛔ No member added.
Both base merges intersect this branch's files in zero paths, measured per merge from its own merge-base, not asserted: abb2eccdb8 (second parent 5495b8de70) brought 17 files, 5c08bc8966 (second parent 40f34b4b, = origin/main) brought 5; comm -12 against the 9 → 0 and 0. Control on the same instrument: comm -12 of each list with itself → 17 and 9. Neither merge carries conflict-resolution content of its own (git show --cc --name-only empty for both).
6 · The merge was performed, not simulated. Head is a true merge commit (3 entries from git rev-list --parents -n1), f36f2ad26d, 3bcecc4095, abb2eccdb8 and af0342ce68 are all ancestors of it (no rebase, no force-push), and origin/main is fully contained. My own reading on that tree, after pnpm install --frozen-lockfile (lockfile untouched) and turbo run build (Tasks: 29 successful, 29 total):
| command | reading |
|---|---|
vitest run --maxWorkers=2 examples/schema-catalog/ packages/types/ |
Test Files 223 passed (223) / Tests 6702 passed (6702) |
vitest run --maxWorkers=2 packages/components/src/__tests__/filter-builder*.test.tsx (shell-expanded; pathspec = 12 files) |
Test Files 12 passed (12) / Tests 254 passed (254) |
223 / 6702 reproduces exactly.
CI at this head, reconciled against the endpoint's own total_count: 36 of 36 check runs accounted for — 33 success, 3 skipped, 0 red. Type Check, Lint, all four Test (shard n/4), Control Byte Scan, Line Citation Gate, Changeset Declaration and Governed Surface Queue Guard are all green, and no check-lockfile-dedupe run is present at this head at all. ⇒ the dedupe matter is not visible here and is not this PR's to absorb.
New finding
with-conditions.json:
{ "id": "3", "field": "name", "operator": "contains", "value": "ada" }The entry still validates and still renders — validates as authored and every authored operator is a DECLARED member both stay green — and yet 4 assertions across 2 files go red: the repaired PRESERVED … as authored renders exactly as the alias arm did on expected 'contains' not to be 'contains', its NEW-direction twin ⛔ the alias dialect it used to author is still REFUSED, and two legs of the re-aimed filter-builder-operator-alias-trigger-7561 pin. FORMER_ALIAS keys only equals / greater_than / less_than, so any fourth spelling falls through and the per-row expect(afterOps[i]).not.toBe(op) reddens on a row the author did nothing wrong to. The message names the guard, not the edit, so the next author reads it as "contains is not allowed", which is false.
⭐ Why this does not block: it is fail-closed, not fail-open, and it is pre-existing — the NEW twin and the 7561 pin already behaved this way before round 2; the repair made PRESERVED symmetric with them rather than introducing the class. Worth recording because the repair widens the blast radius by one more leg, and because there is a concrete alternative with the same B1 coverage and less brittleness: the dev's own probe shape, whole-array expect(afterOps).not.toEqual(beforeOps), still reddens under the alias mutation (all three rows fall through ⇒ arrays equal) but survives the contains append. The trade-off against per-row is consistency with the twin, so this is a judgement call for the dev, ⛔ not a defect I am asserting.
(Restored after the probe: sha256 back to 5f8e78c2aa402fae, git status --porcelain empty.)
What I could not measure
- The live
3bcecc4095body as GitHub would serve it. REST carries no body revision history and GraphQLuserContentEditsis refused from this seat (HTTP 403, measured). My L2 ruling rests on the corroborated on-disk capture described above. Strictly, it cannot exclude a body edit between that capture (updated_at2026-09-15T03:44:36Z, taken 03:48Z) and the verdict at 04:13Z; for the finding to have been real, such an edit would have had to insert a clearance sentence into a body whose byte count then still matched what the dev independently measured. I judge that excluded on the evidence, and name the gap rather than paper it. - Not re-taken this round (carried from the prior verdict, which PASSed them, and not re-opened by the 28-line round-2 diff): the
objectui check632-file bucket split 3→1, thecheck:*gate roster (check:control-bytes7727 text files,check:doc-example-ids414,regenerate:check432 entries), theturbo run type-check/linttask counts and warning figures — CI's ownType CheckandLintare green at this head — and theFILTER_BUILDER_OPERATORS22 / 3 / 19 reading. - Whether this PR should land — ⛔ not this seat's call.
⛔ No label hung or removed, ⛔ no draft flip, ⛔ no enqueue, ⛔ no auto-merge, ⛔ no approval review, ⛔ no file, body or card edited.
Generated by Claude Code
Enqueued — three of three at
|
| # | gate | reading | at |
|---|---|---|---|
| ① | declared head == pulls/9558.head.sha |
5c08bc89661175521cd2377056106d66bf5f5be5 both sides, full 40-character string equality |
05:30:39Z |
| ② | PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9558 |
exit 0 — declaration readable in the fixed spelling, both carriers agree, no widening tell | 05:30:39Z |
| ③ | pre-flip | 36/36, {success: 33, skipped: 3}, 0 not-green, 0 running, legacy success, mergeable_state: clean |
05:30:39Z |
| ③ | re-taken post-flip | 37/37, {success: 34, skipped: 3}, 0 not-green, 0 running, legacy success, head unmoved |
05:31:28Z |
The 37th check is the guard's second run, 05:30:57 → 05:31:10Z — 13 s, inside the 11–28 s band. added_to_merge_queue is on the timeline at 05:31:43Z.
Why this review is worth reading even though it passed
It did not accept the after-numbers. It rebuilt the defect: the same alias mutation against the pre-repair pin blob still gives 7 passed | 14 skipped, so the guard — and nothing else in the round — is what closes the hole. Then it hunted a second way through, since a per-row guard is vacuous on a zero-row corpus, and closed that too (conditions: [] reddens all seven legs). It verified the dev's prediction-miss account by name-diffing the failure rosters rather than reading its prose: exactly 4 new reds, 0 disappeared.
On L2 it ruled independently and went past what was asked — recovering the pre-repair body bytes, corroborating that capture on four points before relying on it, and then checking every other place a clearance could have been owed from: commit messages, the 9 changed files, the changeset, and card objectui#6939's body plus all 29 comments, attributing each hit by byte offset. All seat-side. ⭐ And it named the gap it could not close: REST serves no body revision history and GraphQL userContentEdits is refused from this seat (HTTP 403, measured), so the ruling rests on a corroborated capture rather than on the bytes GitHub would serve. That is the right way to report a limit.
N-1 — recorded, not repaired, and here is the reasoning
The new guard is per-row, so appending one legitimate condition spelling a declared member ("operator": "contains") leaves the entry validating and rendering while reddening 4 assertions across 2 files, with the misleading message expected 'contains' not to be 'contains'.
Disposition: Acceptance notes, no card, no fifth round. Three reasons, in order of weight:
- It is fail-closed. A future author gets a red they must investigate, ⛔ not a silent pass. That is the opposite of the class this PR exists to close.
- It is pre-existing, not introduced: the NEW-direction twin and the objectui#7561 pin already behaved this way. The repair makes
PRESERVEDsymmetric with its siblings; it widens the blast radius by one leg rather than creating the shape. - The alternative the reviewer offers (whole-array
not.toEqual) closes B1 identically and survives the append, but costs consistency with the twin — a genuine trade-off, which the reviewer explicitly framed as a judgement call and ⛔ declined to assert as a defect. ⛔ Rolling a fifth round to swap a guard for its sibling's shape, on a p2 card already two rounds deep, is scope creep.
⭐ Contrast with the sibling PR objectui#9555, where I did take another round for a MINOR: that one shipped, on a customer-facing docs page, and was false. This one is test-only, fails safe, and is a trade-off rather than an error. The difference is what the finding does to someone who is not in this thread.
Queue transit has been 18.5–20.5 min over eleven readings, so a merge is expected around 05:50–05:52Z — a range this seat keeps re-measuring, ⛔ not a promise.
Generated by Claude Code
Fixes #6939
Seven
"operator"spellings in three schema-catalog fixtures move from the spec's aliastable (
eq/lt/gt) to the membersFilterOperatorSchemaactually declares(
equals/less_than/greater_than), so the entries passsafeValidateSchema.Contract-first, per the maintainer's standing principle: the protocol declares the
snake_case vocabulary, so the corpus was the side that was wrong. No enum member was
added, and a pin now says so mechanically.
This is the last remaining row of objectui#6939. The other seven component groups landed
and are closed already; nothing in them is touched here.
Repair round 2 — what moved, and one finding that did not reproduce
An isolated contract review returned FAIL on this pin — not on the change it guards.
Every finding was reproduced here before it was repaired, and the one that disagreed with
the report is recorded as such rather than quietly "fixed".
PRESERVEDidentity legs could not tell the two corpora apart underthe alias mutation. Reproduced with a throwaway probe, repaired with the guard its
NEW-direction twin already carried, re-measured in Reverse verification below.
the zero-controls note under Readings.
sentence sourced from a bare
check-clause2-carriersrun. Measured against the body asit stood at head
3bcecc4095, fetched back from the API before this revision waswritten, with
grep -o … | wc -l: 0 occurrences of the checker's name, 0 of theword clearance, 0 of
PM_SWEEP_REPO, 0 ofscripts/pm— against 1occurrence each of three firing controls on the same instrument and the same bytes
(
Clause-②,Implemented-by, and the card-closing line at the top).describe that revision and ⛔ not the text you are reading: this paragraph now contains
two of the strings it counted, which is exactly why it names the bytes it was taken over
rather than leaving the count to read as live. There was no such sentence to delete, and
none was added. The
Clause-②: nodeclaration at the end is the only clause-② textthis body has ever carried; taking that gate is the seat's reading, not this branch's,
and this body does not claim it.
origin/mainmerged intwice — first the three commits objectui#9541, objectui#9548 and objectui#9551, then
objectui#9552 when it landed mid-round. Each time the intersection with this branch's nine
files is 0 paths, measured. Every figure below was re-taken on the final merged tree,
not carried forward; the two that move are named where they appear.
Readings (mine, re-measured — base
b7479abc74, head5c08bc8966after mergingorigin/main)Corpus for every count below, re-measured at this head: 501 tracked files under
⚠️ The 500 this body carried before was the
examples/, 443 of them.json.base figure — this branch's own new pin file is the 501st, so that sentence described a
tree this pull request does not ask anyone to land. The
.jsonsubset is what every countin the table is drawn from and it does not move: 443 at the base and 443 at this head,
checked rather than assumed. Occurrences counted with
git grep -o … | wc -l, files withgit grep -l … | wc -l."operator":occurrences anywhere underexamples/eq/lt/gt)Per file, both states:
product-search.json3,search-interface.json2,with-conditions.json2. Values before:eqx3,gtx3,ltx1. After:equalsx3,greater_thanx3,less_thanx1. The diff is 7 insertions / 7 deletions across exactlythose three data files.
⭐ The control that makes the first row load-bearing: every
"operator":occurrenceanywhere under
examples/is also 7, so coverage of the refused dialect was 100 %, not"a few exceptions".
Zero-controls, same corpus, same run: the same-subject known-hit control
"conditions":returns 5, so the zero above is a reading rather than a silent miss.
⛔ This branch lands no repo-wide-looking absent-token literal, and that is deliberate.
A generic absent token is clean only while nothing in the tree contains it, so consuming
one as a test literal spends it for everyone afterwards. An earlier revision of the new pin
used one as its invented-operator payload: measured repo-wide, that token went from 0
occurrences / 0 files at the base and on
origin/mainto 3 occurrences / 1 file atthe old head — spent exactly the way
zzqx_no_such_keyalready is here (6 occurrences /3 files, which is why that one was not used either). The payload is now
filter_builder_6939_invented_operator, which names this card and this component so thatnobody reaches for it as a repo-wide control. Its occurrence count before it was
written, taken with the same instrument over the whole tree: 0 occurrences / 0 files at
the base, at the old head and on
origin/main; the firing control for that zero, sameinstrument and same corpus, is
FilterOperatorSchemaat 24 occurrences / 15 files. Thepin also asserts at runtime that the payload is not a member of
FilterOperatorSchema, sothat refusal leg cannot go green on a payload the mirror happens to accept.
Vocabulary, read at runtime from the built dist rather than restated:
FilterOperatorSchemahas 14 members and contains 0 of 3 ofeq/lt/gt.Acceptance notes
The two directions pull opposite ways and each is measured separately.⚠️ objectui#6318's
rule — a "correction" that renders identically proves the edit was wrong — was written
where the correction repaired a broken render. Here it inverts: objectui#7561 (PR
#9305) had already routed the operator trigger's identity comparison through
normalizeFilterOperator, so all three dialects already drew their label. The aliaseswere validation-broken, not render-broken ⇒ a render that moves is this change's
failure signal.
Direction NEW — it validates (the property added)
The card's own instrument,⚠️ The instrument prints
node packages/cli/dist/cli.js check, run from the repo rootover the same tree in both states, and re-taken on the merged tree (632 files analyzed each
time, unchanged by the merge; the alias state produced by reverting only the three data
files in place).
✓ All checks passedand exits 0 in bothstates — only the bucket listing discriminates, so the listing is what is read here:
validate" bucket:
with-conditions.json(typefilter-builder),product-search.json(typefilter-builder), and one unrelatedpackages/vscode-extension/schemas/objectui-schema.json(typeobject).search-interface.jsonroots atstack, socheckvalidates its root and never countedit; its nested
filter-buildernode is measured directly in the new pin so the fifth fileis not silently unexamined.
Pinned per entry in
examples/schema-catalog/test/filter-builder-operator-vocabulary-6939.test.tsx,with three refusal controls so
.successcannot be satisfied by a mirror that acceptseverything:
that would go green if someone ever repaired a red here by widening the enum;
Direction PRESERVED — it renders the same (the property protected)
The "before" arm is reconstructed in memory from the same tree by swapping only the
operator spellings back, so the proof keeps working in a checkout that never saw the old
bytes. Per entry, four readings must be equal between the arms: full
textContent,element count, the
typeattribute of everyinput, and the text of every operatortrigger. All three entries: equal. Operator trigger labels, unchanged in both states —
product-searchEquals/Less than/Greater than,search-interfaceEquals/Greater than,with-conditionsGreater than/Equals— and not one blank cell.Anti-vacuity for that equality needs two guards, and⚠️ the second one was missing
until repair round 2. The first: a leg that swaps in a genuinely different operator must
move the render, and it does. The second: the alias arm of the identity leg must itself
have moved.
withOperatorsfalls through on a key it does not hold, andFORMER_ALIASis keyed on the DECLARED spellings — so against a corpus authored in the alias dialect the
arm degenerates to an identity transform, and the leg renders the same document twice and
asserts it equals itself.
Reproduced before it was repaired, with a throwaway probe built from the pin's own helpers
that asserts only that the two arms differ: 3 passed unmutated / 3 failed under the alias
mutation, the failures reading
expected [ 'eq', 'lt', 'gt' ] to not deeply equal [ 'eq', 'lt', 'gt' ]. The probe wasdeleted; the tree is clean. Both identity legs now carry the per-row swap guard that the
NEW-direction twin in the same file already had — the one on the leg named the alias
dialect it used to author is still REFUSED — and so does the invented-operator leg, which
had the identical hole.
⭐ The same failure class had already been repaired once inside this pin, on the leg that
proves the identity assertions CAN fail: it keyed its swap table on the canonical spellings
this branch landed, so it went quiet the moment the corpus was re-authored in another
dialect. That leg now derives its table from whatever the entry currently spells. Both
repairs are the same lesson — a swap table keyed on one dialect is a silent no-op against
any other — and this card exists because of exactly that class.
Reverse verification
Mutation: revert only the three data files to the alias dialect. Prediction written down
before each run. Landing proven on disk in both directions (canonical anchors 3/2/2 to
0/0/0, alias anchors 0/0/0 to 3/2/2) and by blob hash against
HEADfor all three files;the harness aborts with a void reading unless the totals are 0 canonical / 7 alias, carries
trap … EXIT INT TERMwith an absolute repo root, and restores withgit checkout HEAD -- pathagainst an absolute path.Before the repair, under the mutation: NEW reddened at
21 failed | 60 passed, whilePRESERVED scoped with
-t PRESERVEDstayed7 passed | 14 skipped. That green iswithdrawn: three of those seven were the identity legs comparing the alias corpus with
itself.
After the repair, same mutation, same harness:
Tests 25 failed | 56 passed (81)across the three pinfiles. The four reds beyond the pre-repair 21 are the three identity legs — each failing
with
expected 'eq' not to be 'eq', the same shape the NEW-direction twin fails with —and the invented-operator leg failing on its own new swap guard.
Tests 3 failed | 4 passed | 14 skipped (21), verbose reporter,every name printed. The three no operator cell is blank legs stay green on purpose:
they measure the authored corpus only, so under the mutation they are a genuine reading
about the alias corpus rather than a vacuous one. The identity legs can fail leg stays
green for the same reason — its table is derived, not keyed.
Test Files 3 passed (3) / Tests 81 passed (81).⇒ The property the PRESERVED direction claims is proven where it is true — at this head,
unmutated, where the two arms demonstrably differ — and the pin now fails loudly on a
migration back to the alias dialect instead of going quiet. ⛔ The earlier claim that
PRESERVED "stays green" under the mutation is not restated: nothing in that run was
comparing the alias corpus to the repaired one.
Restore proven by state, not by exit code, after every leg:
git status --porcelainempty,
git diff HEADempty, andgit hash-objecton each of the three files equal to itsHEADblob.Two existing pins recorded the old corpus state
Both are re-aimed, not deleted, so each still names the key that moved:
examples/schema-catalog/test/filter-builder-operator-alias-trigger-7561.test.tsxheld afence asserting the catalog still authors the alias table. That fence belonged to
objectui#7561's narrower scope — it deliberately changed no catalog data — and this
card's remainder is what lifts it. The assertion is inverted: the entries must now
author a declared member, and a migration back to either off-contract dialect reddens
it. Its correction table also had to grow the canonical spellings, or the control arm
would silently have become an identity compare.
members (
equals,contains,inare spelled alike), so the old form of itsanti-vacuity leg — "no member of one appears in the other" — is not available and the
new one asserts per row that every non-coinciding spelling actually moved.
packages/types/src/__tests__/filter-builder-mirror-6939.test.tspinned the residualrefusal mechanically. Those rows now assert the entries validate, and the enum-not-widened
claim is carried by a new refusal control rather than by the residual.
Two comments in files this branch touches stated the old corpus as fact and were repaired
in place (objectui#7853's citation discipline); one cross-file line address in the types
pin was dropped for a content citation while that paragraph was being rewritten.
⛔ The bigger half, named and left alone
The builder's dropdown emits its own camelCase ids, and the mirror refuses them: read
at runtime from the built dist,
FILTER_BUILDER_OPERATORShas 22 members, of which3 (
equals,contains,in) are accepted byFilterOperatorSchemaand 19 arerefused —
notEquals,containsCaseInsensitive,notContains,isEmpty,isNotEmpty,greaterThan,lessThan,greaterOrEqual,lessOrEqual,before,after,between,notIn,startsWith,endsWith,isNull,isNotNull,exists,notExists.⇒ a filter a user edits in the UI and stores is refused by the mirror exactly as these
fixtures were. No card owns that today. It is pinned here as a refusal so it cannot go
quiet, and ⛔ it is not ruled or repaired here. objectui#7561 is not addressed by this pull
request either — its deliverable already landed.
⛔
plugin-kanban'sKanbanColumn/KanbanCard/KanbanSchemauntouched: thatconflict is with the maintainer.
Verification
Exit codes captured before any pipe; every figure read from the instrument's own verdict
line. Heavy runs serialized through
../objectstack's shared verify lock(
OS_VERIFY_LOCK_SLOT=objectui-6939-dev), allVERDICT command-exit 0.pnpm exec vitest run --maxWorkers=2 examples/schema-catalog/ packages/types/— the twoaffected packages in full:
Test Files 223 passed (223) / Tests 6702 passed (6702).modifies 4
packages/types/src/__tests__files and adds none, so the file count isunchanged and the test count is not. Re-taken again after the second merge: both figures
are the same on the final head.
pnpm exec vitest run --maxWorkers=2 packages/components/src/__tests__/filter-builder*.test.tsx—
Test Files 12 passed (12) / Tests 254 passed (254).turbo run buildthenturbo run type-checkfor@object-ui/types,@object-ui/components,@object-ui/example-schema-catalogwith--concurrency=2—29 and 32 tasks successful, zero
error TS.dependency closure built,
type-checkreportsTS2307 Cannot find moduleand saysnothing about this diff.
turbo run lintfor the same three pluslint:root— 4 tasks successful,0 errors (953 / 288 / 32 pre-existing warnings, none of them in the touched file;
the types figure was 286 before the base merge).
check:control-bytesOK over 7727 tracked text files (7719 before the merges), plus amanual
grep -naPcontrol-byte scan over the touched file returning no match — with afiring control on the same instrument, a synthetic line carrying one control byte, which
matches;
check:new-line-citations0 new citation(s);check:test-path-rootsOK;check-changeset-presence9 file(s) changed … declares 1 changeset(s)and"Every one of them has an EMPTY frontmatter";
changeset:check(fixed-group andno-major) OK;
check:changeset-claimsOK;check:doc-example-ids414 real reference(s) all resolve;check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:comment-mask-corpus,check:unreferenced-sourcesall exit 0;
regenerate:checkindex.ts is up to date (432 entries).pnpm run check:changeset-presenceand
check:changeset-no-majorexit 1 with empty output — neither script name existsin this repo (
ERR_PNPM_NO_SCRIPT). The real invocations arenode scripts/check-changeset-presence.mjsandpnpm changeset:check, and those are theruns quoted above.
node scripts/check-governed-queue-guard.mjs --testover this branch's nine paths:NOT GOVERNED.
Changeset: empty frontmatter, the gate's explicit "releases nothing" declaration. The two
files under a released package are test files whose only change is comments and
assertions; the data files live in a private, changeset-ignored example package.
Narrowings declared, one line each with what each could have caught:
pnpm lintreplaced by the three affected packages' ownlintpluslint:root— exactly whatturbo run lintinvokes for them. ESLint here is nottype-aware, so this diff cannot move a verdict on a file it does not touch.
packages/componentsis type-checked and linted in full, but only its 12filter-builder*test files are run; the change there is a comment. A full-package runcould have caught an unrelated regression, and CI runs it.
examples/schema-catalog/src/index.ts, whose only in-repo readers are that package'sown tests and the docs site's example resolver (by id, unchanged). CI runs the full farm.
Clause-②:
no— three data files move into an already-declared accepted set. No memberadded, no published type or export moved.
Implemented-by: claude/issue-6939-filter-builder-catalog-operator-spellings
🤖 Generated with Claude Code
https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
Generated by Claude Code