Repository navigation
ValueDataSource is the only text-operator face not enrolled in @objectstack/spec's FILTER_TEXT_CASES — all five platform drivers import the table, this adapter hand-mirrors it #8600
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanedomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 8, 2026 Claim:
domain:spec@objectuiPM seat — dispatching, R7Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-8600-valuedatasource-filter-text-cases· 2026-09-08T23:54Z.⭐ This is a PM dispatch: this seat set the assignee and posts this claim on the dev's behalf. The dev inherits both, checks that the newest
Claim:names its branch, ⛔ posts no second claim and ⛔ never writes the assignee field.Why this card, and why now — the order is measured, not assumed
The lane's full order puts #8221 first (p2, type
Bug, older). It is not being taken, and the reason has changed since the last seat wrote one:- Its deferral note (
5573616429, 2026-09-07T16:49Z) parks it behind PR fix(types,core,plugin-list,plugin-view,app-shell): derive ViewType from @objectstack/spec instead of re-declaring it #8372. ⛔ That reason is stale: fix(types,core,plugin-list,plugin-view,app-shell): derive ViewType from @objectstack/spec instead of re-declaring it #8372 merged at 2026-09-07T23:04:10Z (read back from GitHub 23:50Z). - The live blocker is a different one, measured just now on
origin/mainda5e4f69: option B narrowssortto the array arm, and the string arm is declared atpackages/types/src/zod/objectql.zod.ts:194,:826,:1021— a file the finding(types): 57ZodDefaultnodes still reachable from the published@object-ui/types/zodbarrel after #7735 — batch #69's principle stops at the files it named, and the rest are imported by reference from@objectstack/spec#8317 dev has modified in its worktree right now. ⇒ file-granularity collision, ⛔ not a precautionary serialisation.
I am recording that on #8221 itself rather than only here, because a card whose deferral note names a merged PR is a card the next seat re-derives from scratch.
Every other p2 in this lane is held too: #8318 on
#7735de-defaulting (#8317 haszod-mirror-authors-no-defaults-7735.test.tsopen), #8499 and #7759 onsrc/zod/**andzod-mirror-parity.test.ts(PR #8553), #8347 ⛔ barred on its stale figure, #8345/#8327 are this seat's own remainders. ⇒ #8600 is the highest-priority card on a free file surface —packages/core/src/adapters/**, which no live PR holds.Carrier hung at dispatch, ⛔ deliberately, before the PR exists
needs:contract-reviewis on this card now. The card names the risk itself:like/ilikeare refused by name in this adapter (#7383), the table may declare them, and PR #8599's type gate «must not quietly re-open». Enrolling a face in a published conformance table can move its accept set ⇒ 强制条款②.⛔ I am not pre-judging that it moves. A verified "the accept set did not move" is exactly what clears this carrier — but an unreviewed accept-set change is a contract breach, so the carrier goes on first and comes off on evidence. It will be hung on the PR the moment one exists (双载体).
What is NOT settled by this dispatch
⛔ Whether enrolment is even the right step. The card's own instruction stands: read rule 2 in
packages/spec/src/data/filter-text-conformance.ts(the local../objectstackcheckout — the file, ⛔ not a ruling comment) and determine whether "the gap" is shut. If it is not, the deliverable is a precise statement of what remains open, ⛔ not an enrolment forced through.Notes carried into the brief
⭐ Both hand-mirrored fixtures stay as lit controls; a table-driven suite that passes because the table is empty or the import resolved to something inert is the failure mode this repo has measured repeatedly.
⚠️ And ablation legs are classified per test from vitest's JSON reporter — a signature list plusgrep -cover the text reporter has been measured twice to read a fully dead run as a clean one (#8593, #8452).⛔ Dedup is declared, not claimed — the filing seat could not run one, and this seat inherits that limit rather than papering over it.
Generated by Claude Code
- Its deferral note (
os-dev-report
{ "issue": 8600, "status": "needs_decision", "branch": "claude/issue-8600-valuedatasource-filter-text-cases", "pr": null, "premise_still_valid": false, "summary": "Step 1 measured: the gap is NOT shut, so no enrolment was forced and there is nothing to land. Run against the PUBLISHED @objectstack/spec@17.3.0 table, ValueDataSource answers 14 of 19 cases exactly and fails all 5 rejection cases. Three independent blockers: (A) the published table carries 19 cases and rows keyed [id, name] — the five #14079 score rows and the score column exist only in objectstack main, so the very rows #8452/PR #8599 deferred for are not importable from objectui yet, and ValueDataSource.nonStringStoredValue.test.ts is currently the only thing pinning that behaviour here; (B) the face refuses by excluding the row and logging, never by throwing an ADR-0112 envelope, so no FilterTextRejectionCase can be satisfied as declared, and $regex+$options additionally fails mustMention because the dollar-dialect loop returns on the first refused operator and never names $options; (C) two rejection rows are live wrong answers — $icontains with an empty comparand returns ALL NINE rows and $icontains with 42 is coerced to '42' and evaluated — but refusing them moves the accept set and a live in-repo producer emits the empty shape, so that refusal is a contract ruling I am not entitled to make. Forward measurement: the face already answers all 5 unpublished #14079 rows correctly, so the only open family is the rejection family. Branch pushed empty as the claim's landing marker and write-routing probe; zero commits, zero files changed.", "tests": "All exit codes captured before any pipe. pnpm --filter '@object-ui/core^...' build = 0 (prerequisite; the first type-check attempt returned 2 with TS6305 'Output file has not been built', a PREREQUISITE NOT MET, not a red gate). pnpm --filter @object-ui/core type-check = 0. pnpm exec vitest run (the two card fixtures, from the REPO ROOT — a package-dir vitest run is refused by objectui#3378's guard) = 0, Test Files 2 passed (2), Tests 31 passed (31) — population floor, not a zero-match false green. pnpm --filter @object-ui/core lint = 0 (0 errors, 532 warnings). node scripts/check-changeset-presence.mjs = 0, its own line: '0 file(s) changed ... no changeset is owed'. pnpm check:control-bytes = 0 (6881 tracked text files). pnpm check = 0 after pnpm --filter @object-ui/cli... build = 0 (first attempt exit 1 MODULE_NOT_FOUND — PREREQUISITE NOT MET). Ablation: the dispatch's on-disk table mutation was REFUSED for a measured reason and substituted — see the ablation section below. Restoration proved by hash: all three subject files match their HEAD blobs, git status --porcelain empty, git diff HEAD empty, at head da5e4f69.", "mcp_calls": "0 — REST, git and local reads only; no MCP GitHub call in the whole run", "open_questions": [ { "question": "Can a face whose declared refusal mechanism is exclude-the-row-and-log satisfy FilterTextRejectionCase, which demands code INVALID_FILTER? All five open rows hang on this one answer, including the two live wrong answers, because WHICH inputs get refused and IN WHAT ENVELOPE cannot be fixed in opposite orders without redoing one of them.", "options": [ "A — the adapter throws an ADR-0112 INVALID_FILTER envelope for the rejection family. Reverses objectui#7349's deliberate exclude-and-log ruling for this matcher, whose docblock contrasts it on purpose with the wire-side sibling data-objectstack that DOES throw MalformedFilterError. Widest blast radius: every consumer of a provider value list gains a throw where a filtered list used to render.", "B — the enrolled suite translates expectRejection into this face's envelope: row set empty AND a console refusal naming every mustMention string. Declares the code INVALID_FILTER field explicitly inapplicable-with-reason rather than skipping it. Still requires fixing the two live wrong answers and the $options short-circuit first.", "C — partial enrolment: import the table, drive the 14 evaluated cases, declare the 5 rejection rows inapplicable. REJECTED by measurement and stated here so it is not re-proposed: they are not inapplicable, the face is asked them and answers three of them correctly by verdict and two of them WRONG, so declaring them inapplicable would be a false declaration, not a contract.", "D — defer enrolment entirely until the objectstack release carrying the #14079 rows lands, then rule on A or B in the same PR." ], "recommendation": "The dispatch carried no four-axis decision frame, so no per-axis analysis is offered and none is invented — requesting the frame. Subject to that: B, and sequenced behind blocker A. B keeps objectui#7349's measured refusal mechanism intact, turns the envelope difference from a silent skip into a written contract, and is the only option that lets the face conform without a platform-wide throw. It cannot be executed today because the published table still lacks the #14079 rows." }, { "question": "Must ValueDataSource refuse $icontains with an empty comparand, given a live in-repo producer emits exactly that shape? The table says refuse; this adapter's own $exists arm says refusing what a live producer emits turns 'every row' into 'no rows', which it calls the one outcome worse than the bug.", "options": [ "A — refuse it. Accept set moves FROM 'returns all rows' TO 'returns no rows' for the empty comparand, in BOTH dialects. Concretely: FilterConditionField's containsCaseInsensitive row with the value box left empty renders an empty list instead of a full one.", "B — fix it at the producer instead: FilterConditionField drops a text-operator condition whose value is empty, and the adapter keeps evaluating. Contract-first: the defect is then upstream of the matcher.", "C — refuse it AND fix the producer in the same change, so no user-visible list flips to empty." ], "recommendation": "Requesting the decision frame before recommending. The measurement that should drive it: FilterConditionField.tsx line 166 emits { field: { $icontains: value } } verbatim, condToMongo drops a condition only when the FIELD is missing, and filterGroupToMongo drops only null fragments — there is no empty-value guard anywhere on that path. The search-box path is NOT affected and was checked: ListView gates on searchTerm being truthy and emits $search, which the adapter answers in its own arm." } ], "out_of_scope_findings": [ "handed to PM, not filed: $icontains with an empty comparand returns ALL NINE rows on both the dollar and AST dialects — a predicate that constrains nothing, the same widening class as objectui#7349 and objectui#8447. NOT filed as a separate card on purpose: whether this is a defect at all is precisely the ruling being escalated on this card, so a card asserting it is wrong would presuppose the answer. No dedupe search was run for it, for that same reason. File it if you want it tracked apart.", "handed to PM, not filed: $icontains with the non-string comparand 42 is coerced by String(target) to '42' and EVALUATED, returning an empty row set indistinguishable from 'evaluated, matched nothing'. Same disposition and same reason as above.", "noted, not filed: the dollar-dialect loop in matchesFilter returns false on the FIRST refused operator, so { name: { $regex: ..., $options: 'i' } } records only the $regex refusal and never names $options. The table's own note says why that matters — an author who fixes only $regex trips the dangling-$options refusal on the next attempt. Carrier: whichever PR answers open question 1.", "noted, not filed: the published table declares NO $like / $ilike rows — its header states those two are pinned on the faces that answer them, not here. So objectui#7383's by-name refusal of $like / $ilike is NOT contradicted by enrolment, in either direction. Recorded because the dispatch flagged it as a risk; measured as a non-risk. Carrier: whichever PR answers open question 1." ] }
Step 1 verdict: the gap is NOT shut. No enrolment was forced; nothing was landed.
Branch
claude/issue-8600-valuedatasource-filter-text-casespushed atda5e4f69(identical toorigin/main, zero commits) as the claim's landing marker and the write-routing probe. No PR, per the dispatch's own instruction for this branch of step 1.The measurement that decided it
Every case of the published table driven through a real
ValueDataSourceoverFILTER_TEXT_ROWS:count evaluated cases ( expectedis a row list)14 of 14 PASS rejection cases ( expectRejection: true)0 of 5 pass — 5 FAIL Discrimination evidence, so this is not an inert run: population floor
cases=19 rows=9, the named member"$contains is case-SENSITIVE — a lower-case comparand misses the upper-case row"present, and the run produced 14 agreements and 5 disagreements from one comparison function. An empty or inert table yields zero lines; a broken comparator yields all-pass or all-fail. Neither happened.Blocker A — the published table does not contain the rows the deferral was about
packages/core/package.jsondeclares"@objectstack/spec": "^17.2.0"; installed and resolved: 17.3.0.import { FILTER_TEXT_CASES, FILTER_TEXT_ROWS } from '@objectstack/spec/data'yields 19 cases, andObject.keys(FILTER_TEXT_ROWS[0])is["id","name"].- objectstack
main,packages/spec/src/data/filter-text-conformance.ts: 24 cases, and the row interface carriesscore: number. - The five #14079 rows are on objectstack main and not in the published artifact objectui consumes.
Consequence:
ValueDataSource.nonStringStoredValue.test.ts(#8452 / PR #8599) is today the only thing in objectui pinning the non-string-stored-value behaviour. Enrolling now and retiring it would silently drop that coverage — the exact drift this card exists to prevent, inverted.Forward reading, so the PM knows how much of the gap is actually the face's: the five unpublished #14079 rows, transcribed verbatim from objectstack main and driven through the same adapter, are 5 of 5 PASS, with two live controls (
{ name: { $contains: 'acme' } }returns["2"],{ score: { $eq: 5 } }returns["1"]) proving the fixture is not inert. PR #8599's half of the gap is shut on the face; it is only unreachable from the table.Blocker B and C — the per-row executability table
The five open rows are all in the rejection family.
envelopemeans: did it throw an ADR-0112 error carryingcode: 'INVALID_FILTER'.# row executable here? measured answer 1–14 every evaluated case, $icontainsfolding + ASCII boundary + literal%_.+ the case-sensitive$containsfamily + the infixicontainsdoor +$notContainsyes exact row-set match, 14 of 14 15 $regexis REFUSED, the refusal names$icontainsverdict yes, envelope no rows [], one console refusal quotingRETIRED_FILTER_OPERATORS;mustMentionsatisfied by the log text; no throw, no code16 $regexwith$optionsREFUSED as one mistakeverdict yes, envelope no, and mustMention FAILS rows [], one refusal naming$regexonly.$optionsis never mentioned — the dollar-dialect loop returns on the first refused operator17 a dangling $optionswith no$regexis REFUSEDverdict yes, envelope no rows [], refusal names$optionsand$icontains; no throw, no code18 an empty $icontainscomparand is REFUSEDNO — not refused at all returns ALL NINE rows, zero console lines. A predicate that constrains nothing 19 a non-string $icontainscomparand is REFUSEDNO — not refused at all String(42)becomes'42', evaluated, returns[], zero console lines — indistinguishable from "evaluated, matched nothing"Rows 15–17 are a mechanism mismatch: the adapter refuses by excluding the row and logging once per distinct refusal per
find(), deliberately, documented at objectui#7349 and contrasted in its own docblock with@object-ui/data-objectstack, which throwsMalformedFilterError. Rows 18–19 are wrong answers, not a mechanism difference.A skipped row would be a hole, so neither of these is proposed as a skip; and rows 15–19 cannot honestly be declared inapplicable either, because the face is asked them and does answer — three correctly by verdict, two wrongly. That is why option C above is rejected by measurement rather than left on the table.
Accept-set finding: it did NOT move
Zero code changed, so nothing a caller could pass changed. Three independent readings of that:
git status --porcelainempty,git diff HEADempty, andcheck-changeset-presencereporting on its own line0 file(s) changed.What enrolment would move, measured as FROM readings, each paired with a well-formed control that still succeeds — a face that refuses everything would otherwise pass for conformance:
probe FROM (today) control control answer { name: { $icontains: '' } }all nine rows { name: { $icontains: 'acme' } }["1","2"]— still succeeds['name','icontains',''](AST door)all nine rows ['name','icontains','acme']["1","2"]— still succeeds{ name: { $icontains: 42 } }[], evaluated not refused{ name: { $icontains: '42' } }[]— the well-formed string comparand gives the SAME answer, which is exactly why the coercion is invisible{ name: { $regex: 'ac.*' } }[]+ 1 console refusal, no throw{ name: { $contains: 'acme' } }["2"]— still succeeds{ name: { $options: 'i' } }[]+ 1 console refusal, no throwsame as above ["2"]{ name: { $like: 'ac%' } }/$ilike[]+ refusal "not implemented by the in-memory matcher"same as above ["2"]The producer risk that makes row 18 a ruling rather than a bug fix:
packages/fields/src/widgets/FilterConditionField.tsxline 166 emits{ [field]: { $icontains: value } }verbatim for thecontainsCaseInsensitiveoperator.condToMongoreturns null only when the FIELD is missing;filterGroupToMongodrops only null fragments. There is no empty-value guard on that path. So a builder row with the operator chosen and the value box still empty produces exactly the shape the table says must be refused — and refusing it flips that list from "every row" to "no rows", which this adapter's own$existsarm names as the one outcome worse than the bug. The search-box path was checked separately and is not affected:ListViewgates onsearchTermbeing truthy and emits$search, which the adapter answers in its own arm.Ablation — a declared deviation, with the reason measured
The dispatch asked for an on-disk mutation of one table row "in your worktree only". That is not achievable here, and the attempt would have corrupted two other agents' runs. The table ships inside
node_modules, and pnpm hardlinks it:node_modules/.pnpm/@objectstack+spec@17.3.0_.../dist/data/index.mjs— inode16023626, link count 4.find -inum 16023626names the other holders:/home/user/objectui-issue-8317and/home/user/objectui-review-8646, both live agent worktrees, plus the store copy.
Substituted an in-memory ablation over the same comparison function, and reported the real run's own discrimination as the primary evidence:
- baseline: row
$contains is case-SENSITIVE — a lower-case comparand...wants["2"], got["2"]— GREEN - ablated: same row with
expectedmutated to["1","2"]— RED, naming that row - restored: original object untouched,
expectedstill["2"]— GREEN
There is no enrolled suite to redden, because none was landed; that is stated as NOT MEASURED below rather than dressed up.
Restoration proved by hash — the two probe files written during measurement were removed and the tree returned to
HEAD:file worktree sha256 (first 16) HEAD sha256 (first 16) match packages/core/src/adapters/ValueDataSource.tsdf29ade98a7e5227df29ade98a7e5227YES .../__tests__/ValueDataSource.textOperatorCase.test.ts16a4b7235c81c75216a4b7235c81c752YES .../__tests__/ValueDataSource.nonStringStoredValue.test.ts716aa03cc753ae95716aa03cc753ae95YES plus
git status --porcelainempty andgit diff HEADempty atda5e4f69.Gates — every one with its exit code, captured before any pipe
gate exit reading pnpm --filter '@object-ui/core^...' build0 prerequisite for type-check pnpm --filter @object-ui/core type-check0 first attempt was 2 with TS6305 "Output file has not been built from source file" — a PREREQUISITE NOT MET, reported as such, not as a red gate; green after the closure build the two card fixtures, via pnpm exec vitest runfrom the repo root0 Test Files 2 passed (2),Tests 31 passed (31)— a real population, not a zero-match false green. A package-dirvitestrun is refused outright by objectui#3378's guardpnpm --filter @object-ui/core lint0 0 errors, 532 warnings node scripts/check-changeset-presence.mjs0 "0 file(s) changed ... no changeset is owed" pnpm check:control-bytes0 6881 tracked text files scanned, 85 binary skipped pnpm --filter @object-ui/cli... build0 prerequisite for pnpm checkpnpm check0 first attempt exit 1, MODULE_NOT_FOUND— PREREQUISITE NOT MET, the CLI dist was absent; green after the buildHeavy runs went through the container's shared verify lock (
OS_VERIFY_LOCK_SLOT=os-dev-issue-8600); the first turn waited 353s behind the issue-8317 agent, and the VERDICT line was read rather than a bare exit status.Changeset: none, and none is owed. Rule applied:
check-changeset-presence's own criterion — a changeset is owed when published source or a published contract of a released package moves in the range. Zero files changed, so nothing moved. This is the measured branch of AGENTS.md's changeset rule, not askip-changesetclaim.NOT MEASURED — explicitly
check:published-dist— measured here or nowhere, and not measured.published-dist-gate.ymlstates in its own header that it is deliberately NOT a pull-request job: the only false-red-free criterion is over the built artifact, so it must build all 39 published packages, and the gate sits on the release path. It does not fit the foreground budget on a contended shared box, and against a zero-file diff it could only have produced a baseline reading ofmain.check:node-esm-load— not measured, same reasoning: zero diff, baseline only.Build Docs— not measured.- CI as a whole — there is no PR, so none of these gates will run in CI for this work at all. Every reading above is measured here or nowhere in the operative sense.
- The enrolled suite's ablation — there is no enrolled suite, so there is nothing to mutate and nothing to redden. The in-memory substitute above is reported as what it is.
- Whether rows 18 and 19 reproduce against a real driver — out of scope; this is the in-memory face only.
- Dedupe search for the two live wrong answers — deliberately not run, because they are not being filed as separate cards; the reason is in
out_of_scope_findings.
Carriers
needs:contract-reviewremains on this card, untouched, exactly as the PM hung it at dispatch. The second carrier does not exist because there is no PR — that is the one part of the 双载体 instruction that cannot be satisfied, and it is reported rather than worked around. Nothing was enqueued, no auto-merge armed, nothing marked ready.
Generated by Claude Code
ACCEPT the report — the premise is FALSIFIED and there is nothing to land. ⇒ decision box, with the four-axis frame the dev correctly refused to invent
PM seat
domain:spec@objectui, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, 2026-09-09T00:17Z. Verified against GitHub and the tree, ⛔ not against the report's self-description: branchclaude/issue-8600-valuedatasource-filter-text-casesis present on origin with zero commits and zero files changed, and no PR exists.⭐ This is a legitimate terminal report, ⛔ not a failed dispatch. The protocol says so in as many words — 「前提被证伪且无 PR 是合法终报,当再分诊输入复核,⛔ 永不当失败派发」. The dispatch's step 1 said: read rule 2, and if the gap is not shut, ⛔ do not force an enrolment through. The dev measured that the gap is not shut and stopped. That is the instruction executed, not missed.
What was measured
Against the published
@objectstack/spec@17.3.0table,ValueDataSourceanswers 14 of 19 cases exactly and fails all 5 rejection cases. Three independent blockers:blocker why it stops enrolment A The published table carries 19 cases keyed [id, name]; the five #14079 score rows and the score column exist only in objectstackmain⛔ The very rows #8452 / PR #8599 deferred for are not importable from objectui yet, and ValueDataSource.nonStringStoredValue.test.tsis currently the only thing pinning that behaviour hereB The face refuses by excluding the row and logging, never by throwing an ADR-0112 envelope ⛔ No FilterTextRejectionCasecan be satisfied as declared.$regex+$optionsadditionally failsmustMentionbecause the dollar-dialect loop returns on the first refused operator and never names$optionsC Two rejection rows are live wrong answers $icontainswith an empty comparand returns ALL NINE rows;$icontainswith42is coerced to'42'and evaluated. ⛔ Refusing them moves the accept set, and a live in-repo producer emits the empty shape ⇒ a contract ruling the dev is ⛔ not entitled to make⭐ Forward measurement worth keeping: the face already answers all 5 unpublished #14079 rows correctly. ⇒ the only genuinely open family is the rejection family.
⭐ And the dev refused a shortcut on the record. Its option C — "import the table, drive the 14, declare the 5 rejection rows inapplicable" — it rejected by measurement and wrote down why: the face is asked those rows, answers three correctly and two wrong, so calling them inapplicable would be a false declaration wearing a contract's clothes. That is the failure mode this whole family is about, caught by the person who would have benefited from it.
⛔ Gates — green, and reported as what they are
type-check0 · the two card fixtures from the repo root 0 (2 files, 31 tests, a population floor, ⛔ not a zero-match false green) ·lint0 (0 errors) ·check-changeset-presence0 ("0 file(s) changed … no changeset is owed") ·check:control-bytes0 (6881 files) ·pnpm check0.⚠️ Two exit codes were correctly classified as PRECONDITION NOT MET rather than red:TS6305 Output file has not been builtbefore the dependency build, and aMODULE_NOT_FOUNDonpnpm checkbefore the cli build. ⭐ "Could not run" is not "failed" — reported that way, not laundered into a green.⚠️ ⭐ A platform fact this run discovered, and it is the most reusable thing hereThe dispatch told the dev to mutate the installed spec table "in your worktree only". It refused, for a measured reason: the installed
@objectstack/specdist file has link count 4 at inode16023626, andfind -inumnames/home/user/objectui-issue-8317and/home/user/objectui-review-8646— two other live agents' worktrees.⇒ ⛔ pnpm hardlinks from the store, so "my worktree only" is FALSE for any file under
node_modules. Mutating an installed dependency to run an ablation silently corrupts every other agent's checkout, and the corruption reports success in your own. It substituted an in-memory ablation (baseline GREEN → expected mutated to['1','2']RED naming the row → restored GREEN) and used the real run's own 14-agree / 5-disagree split as the primary discrimination evidence.Restoration hash-proven on all three files (
df29ade98a7e5227,16a4b7235c81c752,716aa03cc753ae95, each equal to its HEAD blob),git status --porcelainempty,git diff HEADempty atda5e4f69.⛔ Carrier removed, and this is the reason
needs:contract-reviewwas hung on this card at dispatch, deliberately, before any PR. Nothing landed and no head exists, so a carrier meaning "review this head" has nothing to point at. It comes off — ⛔ not because the accept-set risk went away, but because it moved: the ruling below will move the accept set, and the carrier belongs on the implementation card and its PR when they exist. Recorded here so this removal cannot later be mistaken for a clearance on evidence.⚠️ needs-user-decisionandpm:*⛔ do not coexist, sopm:dispatchedcomes off in the same write and the assignee is cleared.
The two rulings — four-axis, as every decision card owes
⭐ The dev flagged that its dispatch carried no four-axis frame and declined to invent one. That was right: the frame is the PM's to supply, and a recommendation assembled without it is a preference wearing a method's clothes. Here it is.
Q1 · Can an exclude-and-log face satisfy
FilterTextRejectionCase, which demandscode INVALID_FILTER?All five open rows hang on this one answer — ⛔ which inputs are refused and in what envelope cannot be settled in opposite orders without redoing one of them.
axis A: throw ADR-0112 B: the suite translates D: defer entirely 业务需求 widest blast radius — every consumer of a provider value list gains a throw where a filtered list used to render no user-visible change; conformance becomes checkable nothing moves; the two wrong answers stay live and unpinned 长远合理性 ⭐ ⛔ reverses #7349's deliberate ruling, whose docblock contrasts this matcher on purpose with the wire-side data-objectstackthat does throw. Two faces, one envelope, by fiat⭐ keeps the measured mechanism and turns the envelope difference from a silent skip into a written contract. The table stays the authority; the face's difference is declared, not hidden ⛔ the debt survives with no carrier once its two fixtures are the only pin 防 AI 写错 an agent that reads "the table says refuse" and adds a throw breaks rendering repo-wide ⭐ inapplicable-with-reasonis machine-readable; a future agent sees why, not a gap⛔ the worst — an undeclared gap invites exactly the false "inapplicable" the dev refused 创业阶段聚焦 large, and it is a platform-wide behaviour change for a conformance chore small and local zero cost now, larger later ⇒ Recommend B, and ⛔ it cannot execute today: blocker A means the published table still lacks the #14079 rows. So B is sequenced behind the objectstack release that carries them — which is D's timing with B's answer, ⛔ not D's open ending. It also requires fixing the two live wrong answers and the
$optionsshort-circuit first, which is Q2.⭐ Long-term weight is where this is decided: B is the only option that lets the face conform without contradicting a standing measured ruling.
Q2 · Must
ValueDataSourcerefuse$icontainswith an empty comparand, when a live producer emits exactly that shape?Measured:
FilterConditionField.tsx:166emits{ field: { $icontains: value } }verbatim;condToMongodrops a condition only when the field is missing;filterGroupToMongodrops only null fragments. ⇒ there is no empty-value guard anywhere on that path. ⭐ The search-box path was checked and is not affected —ListViewgates onsearchTermbeing truthy and emits$search, answered in its own arm.axis A: refuse only B: fix producer only C: both, one change 业务需求 ⛔ a user who leaves the value box empty sees an empty list where a full one rendered — a visible regression list keeps rendering; the matcher still accepts a predicate that constrains nothing ⭐ contract holds and no user-visible list flips to empty 长远合理性 ⭐ conforms to the table but ships a regression the adapter's own $existsdocblock calls "the one outcome worse than the bug"⛔ contract-first is right, but leaves the same widening class as #7349 and #8447 live in the matcher ⭐ defect fixed at the producer (where contract-first says it belongs) and the matcher stops accepting a no-op predicate 防 AI 写错 an agent later "fixes" the empty list by re-widening the matcher an agent reads the matcher as permissive and copies it ⭐ producer and matcher agree; neither reads as licence 创业阶段聚焦 smallest diff, largest user cost small, incomplete two small edits, one PR — ⛔ not a programme ⇒ Recommend C. The adapter's own
$existsarm already states the principle: refusing what a live producer emits turns "every row" into "no rows". C is the only option that honours both that and the table.⚠️ Named, falsifiable premise on both recommendationsP. The published
@objectstack/specdoes not yet carry the #14079 score rows, so neither ruling can be executed until an objectstack release ships them.
Re-check: readFILTER_TEXT_CASESfrom the installed@objectstack/specdist and count the rows — 19 today; the #14079 rows arriving raises it.
⛔ If P is falsified (a release lands sooner), the sequencing collapses and B+C can go in one card.Handed to the PM, ⛔ deliberately not filed as separate cards
The two live wrong answers (
$icontainsempty → all nine rows;$icontains 42→ coerced and evaluated) are ⛔ not filed apart, and the dev's reason is correct: whether they are defects at all is precisely the ruling being escalated here, so a card asserting they are wrong would presuppose the answer. They live in Q2.⚠️ No dedupe search was run for them, for that same reason — declared, not claimed.Also noted with named carriers: the
$optionsshort-circuit (whichever PR answers Q1) and — ⭐ measured as a NON-risk, contrary to what the dispatch flagged — the published table declares no$like/$ilikerows at all, so #7383's by-name refusal is ⛔ not contradicted by enrolment in either direction.⇒
pm:dispatched→needs-user-decision, assignee cleared, carrier removed, in one write.
Generated by Claude Code
Decision-box duty — the card-face facet block and the maintainer digest this card owes on entry (director seat, summon #18,
session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-09T00:4xZ). ⛔ Not a ruling: the four-axis analysis at 5593781306 (PM seatdomain:spec@objectui) stands as the analysis; this comment serialises it into the fixed card-face shape and adds the digest. The card is presented to the maintainer in this summon's batch #104.- ① 项目长远合理性:问题 1 的 B 让这张面按同一张已发布一致性表受检,而把「不抛错、只剔行并记日志」的既定拒绝机制(objectui#7349)以「不适用并注明原因」写进契约,缩小特例;A 把一个本地适配器的拒绝形状改成平台级抛错,反向扩大爆炸半径。问题 2 的 C 在生产者(筛选条件表单)与匹配器两侧同时收口,不留一个「什么都不筛」的谓词。
- ② 实际业务拉动:今天撞上的是两条实测错答——筛选值留空时列表返回全部行、非字符串比较值被静默转成字符串求值——以及五条拒绝用例在这张面上一条都过不了;协议侧的 #14079 五行尚未随版本发布,B 今天不可执行,只能排到那次发版之后。
- ③ 防 AI 犯错:A 把「表里写着 refuse」直译成抛错,会让所有值列表消费者从「渲染一个过滤后的列表」变成「抛错」;B 把差异写成机器可读的「不适用 + 原因」,后来者看见的是理由不是空洞。问题 2 若只改匹配器(A),用户看到的是列表突然变空;只改生产者(B),匹配器仍接受一个不约束任何行的谓词;C 让两侧一致,谁都读不出「许可」。
- ④ 创业阶段不扩散:B 与 C 都是同一 PR 内的小改动,不加能力、不加配置;D(整体推迟)零成本,但把债务留在两份手抄夹具里、无载体。
推荐:问题 1 = B(排在携带 #14079 五行的 objectstack 发版之后执行;裁后本卡转pm:on-hold,Restart-when:判据 = 已安装@objectstack/spec的FILTER_TEXT_CASES行数 ≥ 24),问题 2 = C;回退 D。
置信缺口:两条错答只在内存面上实测,未在真实驱动上复测;问题 2 的 C 改变已发布适配器的接受集(Clause-②: yes,changesetminor),未做消费者普查——筛选条件表单之外是否还有别的生产者会发出空比较值,本分析看不见。
维护者速读
事情:前端的内存值列表适配器(
ValueDataSource)是唯一没有接入协议侧文本操作符一致性表的那张面,它的行为只靠两份手抄测试钉着。开发实测:已发布的表 19 条用例里,14 条求值用例全过,5 条「应拒绝」用例全不过——其中 3 条只是拒绝方式不同(它剔行记日志而不抛错),2 条是真错答(筛选值留空返回全部行;数字比较值被静默当字符串)。协议仓 main 上新增的 5 行还没发版,所以现在接不进去。问题 1(拒绝的信封):A 改成抛错(推翻 #7349 的既定做法,所有值列表消费者从渲染过滤结果变成抛错);B(推荐) 测试套件把「应拒绝」翻译成这张面自己的形状(空结果 + 日志点名),把不适用的
code字段连同原因写明;D 整体推迟到发版后再裁。问题 2(空比较值):A 只在匹配器拒绝(用户会看到列表突然为空);B 只修生产者(筛选条件表单不再发空值);C(推荐) 两侧同一次改。
你要做的:回两个字母——问题 1 A/B/D,问题 2 A/B/C。
Generated by Claude Code
Ruling recorded — closed, not planned; reopen when the work is actually reached (director seat, summon #18, decision batch #104, 2026-09-09)
Maintainer's reply, verbatim (live director-seat chat, 2026-09-09, answering the batch #104 presentation of this card — recommendation B + C, held behind
Restart-when: 已安装 @objectstack/spec 的 FILTER_TEXT_CASES 行数 ≥ 24):8600 这种是不是太折腾了,直接关闭不好吗?以后遇到再开。
⇒ Ruling: close as not planned. Neither question is ruled on the merits: Q1 (A / B / D — how an exclude-and-log face satisfies
FilterTextRejectionCase) and Q2 (A / B / C — whether an empty$icontainscomparand is refused) stay open questions, ⛔ not answered by this closure. Thepm:on-hold+Restart-when:route was offered and declined; nothing scans for the wake condition now — reopening is a human act, by the maintainer's choice.What closes with the card, so it is not lost: the dev's measurement at 5593740951 — 14 of 19 published cases exact, 5 rejection rows failing (3 by envelope, 2 by answer: empty
$icontainsreturns every row;$icontains: 42is coerced and evaluated), the five unpublished #14079 rows already answered correctly by the face, the$optionsshort-circuit, and the measured non-risk that the table declares no$like/$ilikerows. The two live wrong answers were deliberately not filed apart (they hinge on Q2) and close here with it. The two hand-mirrored fixtures (ValueDataSource.textOperatorCase.test.ts,ValueDataSource.nonStringStoredValue.test.ts) remain the only pins, with their docblocks recording the rule-2 deferral.Reopen when: an objectstack release carries the #14079 rows (installed
@objectstack/specFILTER_TEXT_CASES≥ 24 cases) and someone touchesValueDataSource's text operators or theFilterConditionFieldproducer — start from 5593740951 and the PM frame at 5593781306, ⛔ do not re-measure from scratch.Labels, one write:
needs-user-decisionremoved (closed cards carry no decision label); stalefindingremoved (graded p2 at filing — 定级即离标);priority:p2,domain:spec,domain:uikept for the record. No assignee. The spec@objectui seat's empty landing branchclaude/issue-8600-valuedatasource-filter-text-cases(zero commits) can be deleted by that seat; ⛔ not touched here.needs:contract-reviewwas already removed at 5593781306.Provenance chain for the ledger: filing 8600 (domain:ui seat), dispatch claim 5593559223, dev report 5593740951, PM frame 5593781306, card-face facet block 5593984631, this ruling.
Generated by Claude Code
Filed by the
domain:uiPM seat (session_01YBWFb5YgMU5dw8p2VKj16S) on behalf of the objectui#8452 dev, which measured it while landing PR #8599 and could not file it:search_issuesrefused on the rate limit, container REST is 403 for this repo, and the zero-quota payload channel does not cover issue search — so the dedupe was genuinely unavailable rather than skipped. ⛔ Not claimed. Observation class.What is there
@objectstack/specpublishesFILTER_TEXT_CASES, the conformance table for text-operator behaviour. All five platform drivers import the whole table.packages/core/src/adapters/ValueDataSource.tsdoes not.Its answers — ASCII-only folding, comparand literalness for
%,_and., the$regex/$options/ empty-comparand refusals, and now the non-string rows PR #8599 just fixed — are pinned instead by two hand-mirrored fixtures:packages/core/src/adapters/__tests__/ValueDataSource.textOperatorCase.test.ts(objectui#7379)packages/core/src/adapters/__tests__/ValueDataSource.nonStringStoredValue.test.ts(objectui#8452 / PR fix(core): a non-string stored value satisfiesnot_containsinstead of failing the operator and its negation both #8599)A hand mirror of a published table drifts silently: the table can gain a row, or change one, and both fixtures stay green while this face stops conforming. That is the same shape as
zod-mirror-parity.test.ts's ledger figures rotting while every pin stayed green (objectui#8252, measured again on objectui#8517), and as thedata-objectstackREADME table that carried three wrong lowerings for five weeks (objectui#8558 / PR #8565).⭐ The entry condition is already written down
Both fixtures carry a docblock recording that enrolment was deferred because the table's own rule 2 is that a face's rows join it in the PR that shuts the gap. So the debt is recorded in code, has an agreed trigger, and has no card — which is what this card is.
⇒ Whoever takes it should read rule 2 first and decide whether "the gap" is now shut. If it is, enrolment is the remaining step; if it is not, this card names what is still open rather than being a cleanup.
The five drivers run against a backend; this adapter is in-memory and answers some rows by refusing rather than by matching. Check before assuming the table's rows are all executable here:
like/ilikeare refused by name in this adapter (objectui#7383) — the table may declare themnot_containsinstead of failing the operator and its negation both #8599 established that the positive text family deliberately keeps a type gate while the negation family is its exact complement (objectstack#14079 option A). Enrolment must not quietly re-open that.packages/spec/src/data/filter-text-conformance.tsin the local../objectstackcheckout. That is free, authoritative, and it is how PR #8599's dev got the ruling after the GitHub comment proved unreachable behind the payload channel's ~15-item timeline cap. The table is the contract; a comment is a report about it.⭐ If enrolment lands, the two fixtures become the ablation
Do not delete them. A table-driven suite that passes because the table is empty, or because the import resolved to something inert, is exactly the failure mode this repo has measured six times this week. Keep at least one hand-written case as a lit control that fires, and prove the enrolled suite reddens when a row is mutated.
grep -cover the text reporter has now been measured twice to read a fully dead run as a clean one (objectui#8593: React'sElement type is invalid; objectui#8452:No test suite found in file).Related
objectui#8452 / PR #8599 (where it was measured; the non-string rows and the objectstack#14079 ruling) · objectui#7379 (the other hand-mirrored fixture) · objectui#7383 (
like/ilikerefused by name in this adapter) · objectui#8252, objectui#8517 (the same drift shape in the zod-mirror ledger) · objectui#8558 / PR #8565 (a hand-maintained mirror of a code constant, wrong for five weeks)Dedup
total_count: 0forValueDataSourcewhile objectui#8447's own title carries that word. No dedup claim is made. Suggested query for whoever picks it up: "ValueDataSource not enrolled in FILTER_TEXT_CASES conformance table, text operator answers pinned only by a hand-mirrored fixture".