test(scripts): census the $-dialect lowercase aliases before objectui#8568 is ruled - #8977
Merged
Conversation
…sks about objectui#8568 records two acceptance sets for one `$`-dialect and names three directions without ruling any of them. It says the choice needs one number first: "option 1's cost is entirely a function of how many authors are relying on the tolerance, and nobody has measured that." This adds that measurement and nothing else. No acceptance set moves, no `operatorMap` row changes, and the census is report-only under the tree's existing `census:*` convention rather than a `check:*` gate, so nothing here makes an undecided question blocking. The alias set is DERIVED — `operatorMap` read from the converter's source (the technique `readme-filter-operator-table.test.ts` already uses, because the map is not exported) minus `@objectstack/spec`'s `FILTER_OPERATORS`. Hardcoding the four spellings would have rebuilt the card's own defect one level up, and would have left the census hunting a dialect the code no longer speaks on a tree where the aliases had been retired. Four controls, because a zero with no lit positive control is not a reading: two positive (the camelCase twins must be visible to the same scanner; a canonical `$`-operator payload must be found in a NON-TEST authored file) and two negative (an impossible spelling reads zero; the `$`-free sibling dialect in `FILTER_OPERATOR_ALIASES` is never counted). The last one is the conflation the census exists to avoid: that table is tolerant by design and case-folding by construction, and reading its rows as `$`-dialect reliance would price the breaking option against filters it does not touch. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
…bait its negative control declines Two defects in the instrument, both found by running it rather than by reading it, and both of the same family: a control that reports PASS while witnessing nothing. SELF-REFERENCE. The census and its suite carry every alias spelling as fixture text, including the impossible spelling the negative control is built on. The moment those two files became TRACKED, the census started scanning them and the `impossible` control fired on its own declaration — caught by the suite on the first run after the first commit, which is the only moment the defect is visible. They are carved out now, and the number of occurrences carved out is printed rather than hidden. BAIT. `sibling-dialect` asserted only that zero `$`-free rows had been counted. "The census counted zero sibling rows" and "there are no sibling rows left to count" are the same reading unless the bait is measured, so the control now counts the `$`-free rows in `FILTER_OPERATOR_ALIASES` with a deliberately `$`-less probe and fails when it finds NONE. Ablation, on the committed tree: loosening the `$`-guard at both layers (the literal prefilter and the regexes it guards) turns `sibling-dialect` red with 10 conflated hits and the census exits 1, "this run is NOT a reading"; restoring returns it to PASS with 4 bait rows and 0 counted. Ablating the REGEX ALONE leaves the control GREEN, because the prefilter has already excluded the file — so the runtime control witnesses the COMPOSED scanner, and the `$`-strictness of the regexes is pinned directly on `scanText` in the suite instead. That asymmetry is now written down where the control is defined rather than left for the next reader to rediscover. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
Both sides added a scripts entry immediately after `census:cross-file-line-citations`; kept both. No other file conflicted, and this branch touches nothing under packages/. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
os-steve
marked this pull request as ready for review
September 10, 2026 14:08
This was referenced Sep 10, 2026
os-steve
pushed a commit
that referenced
this pull request
Sep 10, 2026
…empty (objectui#8568) PR #8977's live-tree case asserted `aliases.length > 0`, `rows.length > 0` and `selfCarved > 0` unconditionally. Retiring the four aliases makes all three false at once, by construction: the census derives its subject set as operatorMap-minus-spec, and that set is now empty. The census script already models the empty tree and a synthetic unit case already pins it; only the live-tree case had the pre-ruling shape baked in. The blindness guard those three carried is real, so it is re-expressed rather than deleted. On an empty tree the instrument's reach is proved by the `authored-reach` control, which counts canonical `$`-operator payloads in non-test authored files and cannot be satisfied by a scanner that never opened the authored corpus; the empty set is then held to being DERIVED rather than the residue of a failed read on either side. The pre-retirement branch is kept intact, so the case still holds on any tree that carries an alias again. The subset check is recomputed in the test rather than by calling `deriveAliases` again: measured under ablation, a stubbed `deriveAliases` returning `[]` satisfies its own output, so reusing it would have made this branch pass while witnessing nothing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Sep 10, 2026
…rtFiltersToAST` (objectui#8568) (#8988) * fix(core)!: retire the four lowercase $-dialect aliases (objectui#8568) `convertOperatorToAST`'s `operatorMap` carried `$notin`, `$notcontains`, `$startswith` and `$endswith` "for tolerance" while `ValueDataSource` refused the same four by design (objectui#8447: alias arms there "would fossilise a second dialect"). One authored filter therefore had two fates depending on which data source was behind the view. The maintainer ruled the tolerant side out: the `$` dialect follows `@objectstack/spec`'s spellings, with no deprecation window, and the documentation follows the implementation. The four are refused BY NAME and the refusal names the canonical spelling, so the repair is a key rename rather than a diff of two operator lists. A generic unknown operator still gets the generic message; that asymmetry is pinned. `packages/data-objectstack/README.md`'s tables and PR #8565's reconciliation pin follow the code: the pin reddened on exactly the four rows that had to change, which is the signal it was built to give. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB * test(core): make the named-refusal assertion discriminate (objectui#8568) Ablating the named arm left `names the canonical spelling` GREEN: the generic unknown-operator message's own "Supported operators:" list already spells `$nin`, `$notContains`, `$startsWith` and `$endsWith`, so `toContain(canonical)` was a case that passed while witnessing nothing. The prescription phrase is what only the named arm can produce, so that is what is asserted now, with the measurement recorded beside it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB * test(scripts): keep the alias census a reading once the alias set is empty (objectui#8568) PR #8977's live-tree case asserted `aliases.length > 0`, `rows.length > 0` and `selfCarved > 0` unconditionally. Retiring the four aliases makes all three false at once, by construction: the census derives its subject set as operatorMap-minus-spec, and that set is now empty. The census script already models the empty tree and a synthetic unit case already pins it; only the live-tree case had the pre-ruling shape baked in. The blindness guard those three carried is real, so it is re-expressed rather than deleted. On an empty tree the instrument's reach is proved by the `authored-reach` control, which counts canonical `$`-operator payloads in non-test authored files and cannot be satisfied by a scanner that never opened the authored corpus; the empty set is then held to being DERIVED rather than the residue of a failed read on either side. The pre-retirement branch is kept intact, so the case still holds on any tree that carries an alias again. The subset check is recomputed in the test rather than by calling `deriveAliases` again: measured under ablation, a stubbed `deriveAliases` returning `[]` satisfies its own output, so reusing it would have made this branch pass while witnessing nothing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #8568
⛔ This PR rules NOTHING. objectui#8568 names three directions — retire the converter's aliases, teach
ValueDataSourcethe aliases, keep both and document the split — and this change takes no position on any of them. The card stays OPEN.It delivers only the prerequisite the card names in its own words:
Zero behaviour change. No acceptance set moves.
operatorMap,ValueDataSourceand every$-dialect arm are byte-identical tomain. Against the merge-base, the change set is exactly 3 files, 958 insertions, 0 deletions: two new files underscripts/plus onescriptsentry in the rootpackage.json.The number
pnpm census:dollar-dialect-aliases, HEAD738cc8bdc:Every one of the 24 occurrences is either the map that defines the aliases, or something pointing at that map. Nothing in
examples/,apps/,content/,docs/,e2e/orskills/authors one. The four "payloads" outside the map are the converter's own unit tests exercising the alias arms.So: in the corpus this repository can reach, option 1's blast radius is zero authored filters and the work is the ledger — the map's four rows, the converter's alias assertions, and the README rows PR #8565's pin will name.
Why
examples/is zero for a structural reason, not a lucky oneexamples/authors filters exclusively in the object-form dialect ({ "operator": "eq" }), not the$-dialect. Measured: 7 rows across 3 files undercomponents-complex-filter-builder/, and no$-operator in key position anywhere in that tree. That dialect has its own table (FILTER_OPERATOR_ALIASES), which is tolerant by design and case-folding by construction (normalizeFilterOperatorlowercases before the lookup) and carries barestartswith/endswith/notcontains/notinas first-class members. Option 1 does not touch it.Conflating the two dialects is the single largest way to misread this number, and it is easy: a grep for
startswithcannot tell them apart. The census requires the$and requires operator-key position, and ships a control that fails if a sibling row is ever counted.The authored-corpus zero is consumer-local, not seam-wide (objectui#6839). This repo is a renderer; the filters it lowers are authored as metadata, and the metadata store is not in this tree. Invisible from here, and therefore not in any count above:
FilterConditionFieldwritescriteria_jsoninto exactly such a store, and nothing here can enumerate what is in one;objectstack-ai/objectstackor any other producer repo;@object-ui/*consumers;{ [op]: value }), which no static scan resolves.A zero here means "this repo does not rely on the tolerance". It does not mean "no author relies on the tolerance", and it must not be quoted as if it did.
One measured fact that narrows the unseen population
kvToCondition— the reader that loads stored$-criteria back into the filter builder — has arms for$contains,$icontains,$notContains,$ncontains,$gt,$lt,$gte,$lte,$startsWith,$endsWith,$null,$exists,$in,$nin,$ne. It has an arm for none of the four aliases (verified: 0 matches forcase '$notin'|'$startswith'|'$endswith'|'$notcontains'), so they fall todefault: return null— "criteria can't be represented", which drops the admin into the raw-JSON editor.⇒ On the one authoring surface this repo ships, a stored lowercase alias already fails to round-trip today. It executes on the ObjectStack adapter and cannot be reopened in the builder. That is a real, pre-existing degradation for exactly the population option 1 would break, and it lowers option 1's marginal cost.
Verification of the card's own claims
PR #8565's reconciliation pin does redden when the aliases are removed — CONFIRMED.
readme-filter-operator-table.test.tsreadsoperatorMapout offilter-converter.ts's source (operatorMapFromSource, anchored onconst operatorMap) and asserts, among others, that every spelling in the README's first column lowers — so a README row whose spelling no longer lowers is reported asis refused by convertFiltersToAST, so it is not a supported operator, and that row's worked example throws. Removing an alias reddens on two independent assertions.Anchors re-derived, not trusted. The four spellings are exactly the four named. Their positions are not: measured today they are at
filter-converter.tsL75 / L79 / L81 / L83 with the comment at L65 — a uniform +6 from the line numbers in the dispatch. The set held; the addresses did not.What is delivered, and why a committed script rather than a one-off
A one-off number would have been enough for a stable count. This one is not stable and is not simple:
operatorMapread from source (the technique the docs(data-objectstack): re-derive the filter-operator table from convertFiltersToAST and pin it #8565 pin already uses, because the map is not exported) minus@objectstack/spec'sFILTER_OPERATORS. Hardcoding the four would have rebuilt this card's own defect one level up.Report-only, under the tree's existing
census:*convention (census:body-dialect,census:tsconfig-test-parity,census:cross-file-line-citations) — the spelling that already means "runnable, reported, not blocking". Not wired into CI: objectui#8568 reserves that decision.The four controls, because a zero with no lit positive control is not a reading
canonical-twin$notContains=42,$startsWith=36,$endsWith=29authored-reach$-operator payloads in NON-TEST authored files (AuditLogPage.tsx:122$gte,recordReadability.ts:164$in, …)impossible$startswithzzz: 0sibling-dialect$-free bait rows present inFILTER_OPERATOR_ALIASES; 0 counted as$-dialectauthored-reachexcludes tests deliberately: reaching only the suites is not reaching the metadata.sibling-dialectmeasures its own bait, because "counted zero sibling rows" and "there are no sibling rows left to count" are otherwise the same reading.Ablation — the controls can actually fail
Run on the committed tree, mutation proven on disk by anchor counts and blob hash before the result was read, restored by
git checkout HEAD -- pathand proven restored by blob equality plus an emptygit diff HEAD:sibling-dialect$-guard loosened at BOTH layers (prefilter + regexes)scanTexthad already excluded the file before the regex ran. So the runtime control witnesses the composed scanner; a one-layer regression is caught by the suite, which pins$-strictness and case-sensitivity directly onscanText. That asymmetry is now written where the control is defined instead of being left for the next reader.The suite also caught two real defects in the instrument, both of the "PASS while witnessing nothing" family: the census scanning itself (its fixture text includes the impossible spelling, so the negative control fired on its own declaration the moment the files became tracked), and the bait gap above. Both are fixed in the second commit.
Gates
Re-run on the merged tree at HEAD
738cc8bdc, afterorigin/mainadvanced toefead6c60mid-flight. Every exit code captured to a file before it was read, never through a pipe.pnpm run type-check:scriptsscripts/**/*.tsis a glob include, so the new test is covered automaticallypnpm exec turbo run lint --concurrency=2pnpm lint; 47/47 tasks, 0 errors repo-widepnpm exec vitest run scripts/pnpm run check:new-line-citationspnpm run check:control-bytesgrep -naPover the diff, no hitsnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --test(3 paths)pnpm run check:doc-typespnpm run check:doc-snippetspnpm run check:doc-examplespnpm run check:entry-guardnode scripts/check-unreferenced-sources.mjsnode scripts/check-pre-install-import-graph.mjsnode scripts/check-lint-coverage.mjsnode scripts/check-type-check-coverage.mjsLint population, stated so the reading is checkable. An auxiliary whole-tree
pnpm exec eslint . --no-inline-config --format json(not CI's command — CI runsturbo run lintper package) judged 4720 files by eslint's own config. The two new files carry 0 errors and 0 warnings. The files it reports with errors are all underapps/,packages/ande2e/; this diff touches none of them. Invariance:eslint.config.jsdeclares no type-aware linting (0 matches forprojectService,parserOptions,project:,tsconfigRootDir, and 0 fortsconfig), so nothing in this diff can move the verdict on any untouched file.Heavy runs went through the container's shared verify lock; each printed
VERDICT command-exit 0. Those wall-clock figures are shared-box seconds, not idle-box seconds.Merge with
main.origin/mainadvanced from3ff3fc63ftoefead6c60during this task. Merged in and resolved: the only conflict was the rootpackage.json, where #8974 and this branch each added ascriptsentry immediately aftercensus:cross-file-line-citations. Both entries kept.origin/mainis an ancestor of this head, and the change set against the new merge-base is unchanged at 3 files / 958 insertions.Serial adjacency, re-derived here rather than cited. All open PRs enumerated and their file lists read directly. At the first reading none touched the root
package.json; by the time #8974 merged, one did — which is the conflict resolved above, and a demonstration of why the dispatch said not to trust any adjacency snapshot, including this one. Both new files are new, so no open PR can conflict on them.Out of scope, filed separately
objectui#8976 — the same seam has a third acceptance-set disagreement, in the direction opposite to this card's:
$icontainsis a canonical member of the spec'sFILTER_OPERATORS,ValueDataSourceexecutes it, this repo's ownFilterConditionFieldemits it for thecontainsCaseInsensitivebuilder operator, andconvertFiltersToASTrefuses it withINVALID_FILTER/ 400. Measured both legs in one process with a$containscontrol that passes on both sides. PR #8565's pin is structurally blind to it: it holds the README tables againstoperatorMap's keys and the unknown-operator error's list, and$icontainsis in neither.Noted, not filed:
kvToCondition's missing alias arms (written up above as census evidence — it reads as consistent with the contract-first direction rather than as a defect, and whoever rules objectui#8568 is the reader who needs it).For whoever rules the three options
criteria_jsonis the population that matters and it is not in this tree. The census says so in its own output rather than leaving the zero to be quoted flat.pnpm census:dollar-dialect-aliasesat the moment a direction lands. It derives its own alias set, so it stays correct on a tree where the aliases are gone.🤖 Generated with Claude Code
https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB