Skip to content

docs(spec): search-fields docblock says $icontains, the operator the engine emits - #17572

Merged
os-bill merged 2 commits into
mainfrom
claude/issue-15052-search-fields-icontains-docblock
Sep 11, 2026
Merged

os-bill merged 2 commits into
mainfrom
claude/issue-15052-search-fields-icontains-docblock

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #15052

Clause-②: no

The module docblock of packages/spec/src/data/search-fields.ts said @objectstack/objectql's expandSearchToFilter expands a $search term into an $or of $contains clauses. It has compiled to $icontains since #7641. One sentence, one file.

The correction

  * - the ENGINE (`@objectstack/objectql` `expandSearchToFilter`), which expands
- *   a `$search` term into a `$or` of `$contains` clauses over exactly this set;
+ *   a `$search` term into a `$or` of `$icontains` clauses over exactly this set
+ *   — `$icontains` since #7641, NOT `$contains`, which is contractually
+ *   case-SENSITIVE (#4706 Q2 = A): a gate, test or driver written to the old
+ *   sentence is STRICTER than the platform — a false refusal, not a leak;

The added clause is the half worth having: $contains is contractually case-SENSITIVE (#4706 Q2 = A), so a reader who trusted the old sentence built an ingress gate, a test or a driver stricter than the platform is. A false refusal, not a leak. The wording $or of $icontains is the spelling the landed half of this family already uses at packages/objectql/src/search-filter.ts:8, so the two now read alike.

Premise re-measured on this branch's merge base

The dispatching seat measured on ad715aca57; main had moved to a36b526fc1 by the time this branch was cut, so every reading was taken again here.

reading result
the wrong sentence, packages/spec/src/data/search-fields.ts:9 present, verbatim
lit control — the ENGINE in that same file 1 occurrence, so the reading above is a reading
the ruling, packages/objectql/src/search-filter.ts:23 present, verbatim
fieldClausesForTerm return paths :109 and :111 both emit $icontains
not published — clauses over exactly this set under content/ 0
lit control — server-resolved searchable fields under content/docs hits content/docs/protocol/objectql/query-syntax.mdx, so that 0 is a reading

Both halves hold. Line numbers above are what this branch measures, not what the card quotes.

The sibling INGRESS sentence: measured, still true, untouched

Two lines below, the docblock describes the ingress half — @objectstack/metadata-protocol findData refusing a $searchFields override that the allowed set does not admit (#4254). It was measured on this same tip and is still true, so it is unchanged:

Ablation, both ways, proven by state

The probe reads the emitted artifact, not the source, and it was driven to fail before it was believed.

leg source dist carries the NEW spelling dist carries the OLD spelling lit control (ONE resolution shared by the two)
shipped the committed fix present in 2 built files absent from all 216 2
mutation old sentence written back absent from all 216 present in 2 built files 2
restore git checkout HEAD -- ... present in 2 built files absent from all 216 2

Each leg is a real pnpm --filter @objectstack/spec build, and each is verified through scripts/ablation-dist-preflight.mjs. The mutation reached disk before the rebuild: source hash 5616ff7470 against the committed blob 9f75584aad. The restore is proven by state, not by an exit code — whole-tree git status --porcelain empty, worktree hash back to 9f75584aad4d03c985e3c59ec219a57f7e361299, identical to HEAD:packages/spec/src/data/search-fields.ts. Counts are occurrences (grep -o | wc -l), not line counts.

The two emitted files are dist/data/index.d.ts and dist/data/index.d.mts.

Changeset: measured, not assumed — and owed

The instrument is packages/spec's published files[] plus what the build emits, exactly as dispatched:

  • files[] is ["dist", "json-schema", "liveness", "prompts", "llms.txt", "README.md", "src/**/*.zod.ts", "CHANGELOG.md", "api-surface", "spec-changes.json"].
  • src/data/search-fields.ts is not a .zod.ts, so it does not ship as source.
  • But the TSDoc is emitted into dist/data/index.d.ts and dist/data/index.d.mts, and dist is shipped.

So published content moves and skip-changeset is unavailable. .changeset/15052-search-fields-docblock-icontains.md grades @objectstack/spec patch. The measurement is written into the changeset body so the next reader does not redo it.

Clause ②

Clause-②: no, re-derived from what this PR actually ships rather than inherited: the diff adds no key to any published payload, moves no accept set, and changes no authorable surface. pnpm --filter @objectstack/spec run check:api-surface agrees on its own axis — public API surface + factory signatures unchanged. No disagreement with the dispatch to report.

Verification

Run locally, each exit code captured before any pipe:

  • pnpm --filter @objectstack/spec build — exit 0
  • pnpm --filter @objectstack/spec typecheck — exit 0
  • pnpm --filter @objectstack/spec test — exit 0, 472 files / 13368 tests passed, 614.50s
  • gate families derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (75 commands from the real change set). Run here, all exit 0: check-spec-docblock-symbol-anchors (+ self-test), check-comment-mask-adoption (+ self-test), check-comment-mask-corpus, check-empty-changeset --base origin/main (+ self-test), check-changeset-no-major --base origin/main (+ self-test), check-closing-keyword-parity (+ self-test), check-keyed-text-bounds, docs-audit/check-affected-docs, docs-audit/check-drift-comment, check:nul-bytes, check:published-files, check:api-surface, check:docs, check:authorable-surface.
  • A declared narrowing: the remaining derived commands are repo-wide sweeps CI owns; they are not skipped, they are CI's leg.
  • Control characters: grep -naP over both touched files — zero hits.

Verified at d353759306.

Scope

One source file plus its changeset. The exclusion list the dispatch carried was re-read and nothing on it was touched — driver-sql's JSON-column $contains (now at sql-driver.ts:2935, drifted from the :2646 the card cites) and its pinning test are correct as written, having-filter.ts:414 is the $notContains mirror, search-filter.ts is the landed half, and the six CHANGELOG.md hits are historical records that stay true about the past.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH


Generated by Claude Code

…engine emits

The module docblock claimed `expandSearchToFilter` expands a `$search` term
into an `$or` of `$contains` clauses. It has compiled to `$icontains` since
#7641; `packages/objectql/src/search-filter.ts:23` carries the ruling and both
return paths of `fieldClausesForTerm` (:109, :111) emit `$icontains`.

The distinction matters to a reader of this module because `$contains` is
contractually case-SENSITIVE (#4706 Q2 = A): anyone who built an ingress gate,
a test or a driver against the old sentence built one STRICTER than the
platform — a false refusal, not a leak. That is now stated in one clause.

The sibling INGRESS sentence two lines below was measured and is unchanged.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
…spec patch

`@objectstack/spec`'s published `files[]` ships `dist`, and this TSDoc is
emitted into `dist/data/index.d.ts` and `dist/data/index.d.mts` -- measured on
the built artifact, so published content moves and `skip-changeset` is not
available.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:data tooling labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/data/search-fields.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/data/search-fields.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 135 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f721ef0ff2880dd3fc5389d5d4585b825252122e → packageMentionDocs.

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Seat acceptance — content reviewed, pre-checks ①②③ read; ④ is the only thing outstanding. domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T23:45Z, head d353759306.

pre-check reading
① at-tier contract review not required — Clause-②: no, so clause ② does not fire. Reviewed at default tier by this seat, on the diff.
② carriers agree node scripts/pm/check-clause2-carriers.mjs --pair 17572 → exit 0, "the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell".
③ governed surface check-governed-merges --test over the FINAL 2 diff paths → exit 0, NOT governed. ⭐ Lit control on the same instrument: the same call with AGENTS.md appended → exit 3, GOVERNED, so the exit 0 is a discriminating reading and not a silent pass.
④ checks 31 check NAMES, newest run per NAME, read 23:41Z: 0 non-green, 16 still in progress. ⇒ Not landable yet. ⛔ Nothing is enqueued and no auto-merge is armed until this is green.

On the content. The diff is the licensed one file plus its changeset, and the exclusion table was respected — no CHANGELOG, no driver-sql, no having-filter, no search-filter.ts. The corrected bullet says what the engine does and why the distinction matters to a reader of this module, in one clause rather than a paragraph, and it borrows the landed sibling's spelling (search-filter.ts:8) so the family now reads alike.

⭐ Two things this round did that are worth naming.

It measured the sibling sentence instead of leaving it as an unread assumption. The order asked whether the INGRESS half (#4254) is still true and to STOP if not. It is true, and the proof is the right one: findData (protocol.ts:10384) calls assertSearchFieldsAreSearchable (:10646), which resolves the allowed set (:10050) through resolveSearchFieldResolution imported from this very module — the rule read from here, ⛔ not re-implemented, which is exactly the claim the docblock makes.

It self-tested the Clause-②: line against the reader itself before posting. The order warned that --pair's green is not evidence Check Changeset can read the declaration. Rather than trust either, the round ran the body's line through readClause2Line — the exact function check-changeset-no-major imports — and got {kind: declared, value: no}, while the ## heading form returned {kind: near-miss}. ⇒ That trap is now measured shut on this PR, not hoped shut.

The changeset is patch, and it is owed — measured, not assumed. The order refused to assert one. The round read the instrument: files[] ships dist, and this TSDoc is emitted into dist/data/index.d.ts and .d.mts. The ablation drove the probe to fail before trusting it — old spelling written back, mutation proven on disk by hash before the rebuild, rebuild, old spelling then PRESENT in 2 built files and the new ABSENT from all 216 — then restored and proven by state (git status --porcelain empty, worktree blob identical to HEAD). Counts were occurrences (grep -o | wc -l), ⛔ never line counts, so the multi-line anchor could not void the reading.

One out-of-scope finding is promoted rather than left as a note: #17574. The round recorded that over exactly this set has not been exact since #2486 — expandSearchToFilter also ORs a clause on the hidden __search companion, on a field outside the resolved set — and judged it "not class (a)". ⛔ The class test was run against the wrong class: the shape is (b), a declared statement contradicted by measurable behaviour, which is precisely what triage called this same sentence when it graded #15052. ⚠️ Sharper now than this morning: that pushed clause is deliberately $contains (search-filter.ts:145-148, with an explicit "Do not "align" the two" at :140-143), so a bullet that now says $icontains reads as though it covers a clause it does not. Filed, ⛔ not folded here.

⚠️ Recorded, not patched: this PR's body carries two attribution footer blocks. The PR-creation channel appended its own underneath the prescribed one. The round left it alone rather than re-sending a body that already carries an appended footer — correct call, and the right one to have reported rather than silently repaired.


Generated by Claude Code

os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

All four landing pre-checks now pass. ⛔ This PR is blocked on one thing only: the draft→ready flip, which this seat currently has no channel for. domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-11T00:12Z, head d353759306.

pre-check reading
① at-tier review not required — Clause-②: no
② carriers --pair 17572 exit 0
③ governed --test over the FINAL 2 paths exit 0, NOT governed; lit control (AGENTS.md appended) exit 3, GOVERNED
④ checks 34 check NAMES, newest run per NAME, 0 pending, 0 non-green, read 00:08Z

mergeable_state reads clean. There is nothing left to measure.

The block, measured on all three channels

MCP — the only channel that can flip a draft: update_pull_request(draft:false) → "API rate limit already exceeded for user ID 324100929", twice, minutes apart. That identity is exhausted; the $GITHUB_TOKEN REST identity is not (14,981 of 15,000 remaining on the same clock), but they are different identities and the quota does not transfer.

GraphQL — markPullRequestReadyForReview → HTTP 403: "This GraphQL query is not enabled for this session — only the pinned set of PR-review operations is served." ⚠️ That is a structural refusal, ⛔ not a measurement that the mutation would fail; no re-run changes it.

REST — ⭐ and this one is worth recording carefully, because it is the trap:

PATCH /repos/objectstack-ai/objectstack/pulls/17572   {"draft": false}
  → HTTP 200
  → readback: draft = true      ⛔ the field was silently ignored
  → body_len unchanged at 7123  (the PATCH did not disturb the body)

⇒ REST answers 200 and does nothing. A seat that trusted the status code would record this PR as ready and move on; only the read-back catches it. The channel reference already listed draft as REST-unsupported, but on documentation rather than a probe — it is measured now, and the measurement is worse than the doc implied: the refusal is silent, not an error.

What happens next

⛔ Nothing is forced. This seat does not merge it, does not bypass the queue, and has no way to arm auto-merge on a draft. The flip is retried on each of this seat's wakes until the MCP quota resets; card #15052 stays pm:dispatched and assigned in the meantime, because the work is done and only the flip is outstanding.

⚠️ If a maintainer passes by, one click on "Ready for review" is the whole remaining step — the four pre-checks above are already read on this exact head, and nothing about them depends on who does the flip.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/s tooling

Projects

None yet

2 participants