Repository navigation
spec: publish the icontains text-comparand refusal (predicate + reason) beside FILTER_TEXT_CASES so every face reads one implementation — ruling D on objectui#9048 #18113
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 14, 2026 Claim: PM dispatch by the
domain:specexecution seat, sessionsession_01KB5PFtxuy1x3dcR5gxudx6, at 2026-09-16T09:51Z.Branch:
claude/issue-18113-icontains-text-comparand-refusal
Worktree:../objectstack-issue-18113. ⛔ Never edit the shared primary checkout.Clause-②: yes
Session:session_01KB5PFtxuy1x3dcR5gxudx6Two new exported symbols on the published
@objectstack/specentry ⇒ the public face grows, which is sufficient on its own. The carrier is on this card and will be on the PR from its first stroke — both the seat's. ⛔ The dev neither hangs nor strips nor waits on it.Premise re-check on
origin/main@f04be62aa6, 2026-09-16T09:5xZ — the card's own re-check block, runthe card expects measured isRefusedTextComparand/textComparandRefusalReasonexported today0 ✅ data/index.tsre-exports./filter-text-conformance1 ✅ (entry reachability holds) control — FILTER_TEXT_CASESexists in that area8 files ⇒ the zero above discriminates ✅ ⚠️ The message bytes are the contract, not proseFILTER_TEXT_CASESrows carrymustMention, and two objectui faces already ship this exact text byte-for-byte. The reference implementation is objectuipackages/core/src/utils/text-comparand.ts(objectuiorigin/main@55f39ee9; the checkout is at/home/user/objectui). ⛔ Port the behaviour and the message bytes — do not reword.describeComparandtravels with the reason function if the reason needs it.⚠️ operatoris the spelling that ARRIVED ($icontainsoricontains), ⛔ never a canonical substitute.Fences, verbatim from the ruling
- ⛔ No objectui change in this PR. objectui#9048 is the consumer half and is
pm:blockedon this card; it unblocks when objectui's installed@objectstack/specexportsisRefusedTextComparand— the installable-consumer criterion, ⛔ not merge. - ⛔ No new error code:
INVALID_FILTERis declared and already in the ledger. - ⛔
content/docs/releases/**untouched.
The acceptance note that is a measurement, ⛔ not a gate
This repo's own faces already refuse the same rows (
@objectstack/formula/ the engine answer 0 of 9FILTER_TEXT_ROWS). Measure whether any is a literal copy of this predicate; if so fold it onto the new export in the same PR (one implementation), else report the reading and leave it. ⛔ Do not fold on a resemblance — fold on a measurement.Changeset
minoron@objectstack/spec; ⛔ measure that it publishes with a positive control that ships and a negative that does not. Name it.changeset/18113-<slug>.md.Concurrency at dispatch (2026-09-16T09:51Z)
Faces measured this act and pairwise disjoint, and disjoint from both in-flight PRs:
- spec: publish the
icontainstext-comparand refusal (predicate + reason) besideFILTER_TEXT_CASESso every face reads one implementation — ruling D on objectui#9048 #18113 →packages/spec/src/data/filter-text-conformance.ts(+ a sibling) anddata/index.ts - lint:
translation-target-unknownreadsapps[].navigationonly, so every locale key for a CONTRIBUTED navigation item (navigationContributions) is a false positive whose advice deletes a translation the runtime honours #18203 →packages/lint/src/validate-translation-references.*,reference-integrity-suite.* - [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955 →
scripts/pm/check-widening-tells.mjs - in flight: feat(spec): object-map, object-gantt and object-tree get ComponentPropsMap rows, derived from the renderers' read points (objectui#8348 Q1-C) #18403 (
packages/spec/src/ui/component.zod.ts+ generated*/ui.json; ⛔ no overlap withsrc/data/**) · feat(spec)!: manifest.id enforces the reverse-domain rule its registry face already had #18319 (91 paths ofmanifest.idfixture renames, awaiting a maintainer decision)
⚠️ packages/lint/src/authoring-rules.tsis a shared registration point. Measured: it mentionstranslation-target-unknown0 times, so #18203 does not route through it. If your change needs it, stop and report — a sibling card in this batch may.scripts/pm/os-verify-lock.sh --statusreadlock is free,queue: emptyat 2026-09-16T09:51Z. Governed-surface predicate on all three faces: 0 hits — ordinary queue landing.Standing rules
- GitHub 写一律走 REST 代理(
curl带GITHUB_TOKEN);POST/PATCH 必带-H "Content-Type: application/json",否则 HTTP 415。⚠️ 评论创建通道自己追加 footer,⛔ 别再加一个。 - ⛔ 不用 MCP GitHub 写工具。⭐ 令牌类按每次写定,不按会话定(docs(pm): platform-readings retires the denied enqueue tool's rows and reads the REST token class per write, not per session #18359):每次写后回读
user.login/user.type逐次报。 ⚠️ 你加载的.claude/agents/os-dev.md比origin/main旧,三处反转(L51 归属、L287 载体归属、L301 被拒处置)。⛔ 别信我也别信加载副本——自己git show origin/main:.claude/agents/os-dev.md > /tmp/m.md && diff /tmp/m.md .claude/agents/os-dev.md,origin/main为准,并逐行报告分歧。- ⛔ 永不写 assignee;⛔ 不发第二条
Claim:;⛔ 永不 force-push / rebase / amend。 - A red truncates its job;skip 不是 pass;
exit 3 PREREQUISITE NOT MET= NOT MEASURED。退出码用 redirect-then-$?取,⛔ 不经管道。
domain:specexecution seat ·session_01KB5PFtxuy1x3dcR5gxudx6· claim posted 2026-09-16T09:51Z
Generated by Claude Code
- ⛔ No objectui change in this PR. objectui#9048 is the consumer half and is
os-dev-report
{
"issue": 18113,
"status": "done",
"branch": "claude/issue-18113-icontains-text-comparand-refusal",
"pr": "#18438",
"session": "session_01KB5PFtxuy1x3dcR5gxudx6",
"premise_still_valid": true,
"summary": "packages/spec/src/data/filter-text-comparand.ts publishes isRefusedTextComparand and textComparandRefusalReason, re-exported from data/index.ts; describeComparand travels with the reason as a module-internal helper so the published face grows by exactly two symbols (check:api-surface's own diff confirms: 2 added, 0 removed on ./data, no describeComparand). The port is byte-exact and measured both ways: the three function bodies are byte-identical to objectui 55f39ee9 (159/115/928 bytes, only the export modifier normalised), and 85 behavioural readings across 17 comparand shapes x 2 arriving spellings x 2 fields show 0 mismatches. TWO READINGS THE SEAT SHOULD SEE. (1) The card's second acceptance clause is falsified for one spelling: mustMention is ['$icontains'], so for an arriving infix 'icontains' the reason names what arrived and does NOT contain the $-dialect token. Rewording it would break the byte-for-byte fence and diverge from two shipped faces, and objectui#9152 already answers this by naming the $ twin in its own tail. I implemented per the fence and pinned the measured truth in both directions rather than picking a side silently. (2) The fold measurement is NEGATIVE for the faces the note names: @objectstack/formula (matches-filter.ts:249) fuses the De Morgan complement into its positive arm and ANSWERS false rather than refusing, and objectql's having-filter.ts:388 REFUSES so it is not one of the faces answering 0 of 9 rows. Reported and left, per the note.",
"tests": "pnpm --filter @objectstack/spec exec vitest run src/data/filter-text-comparand.test.ts :: 46 passed (46). pnpm --filter @objectstack/spec test :: Test Files 483 passed (483), Tests 13734 passed (13734). pnpm --filter @objectstack/spec typecheck :: exit 0 — and coverage PROVEN not assumed: tsconfig.json excludes /.test.ts, so --listFiles on tsconfig.test.json (what check:test-typecheck runs) was used to show both new files are in that program. pnpm --filter @objectstack/spec check:generated :: proved exactly api-surface/ and export-origins/ stale; --fix regenerated only those two; re-run green. DERIVED GATES: scripts/pm/dispatch-gates.mjs --commands derived 85 families from the change set (--repo asserted, shallow checkout, three-dot vs merge-base 588475c); ran all 85, reconciled via --ran with a recorded exit code per family: '85 derived, 83 run, 2 NOT-MEASURED, 0 UNRUN'. NOT MEASURED (2): check:dual-build-cjs-loads and check:type-check-debt, both exit 3 PREREQUISITE NOT MET — they refuse until a repo-wide build exists and 54 of 69 buildable packages have no dist/ here; that build is CI's (Build Core / TypeScript Type Check), declared rather than reported green. Two other exit-3s WERE cleared by building their closures (turbo build --filter formula --filter lint --filter objectql): check:doc-formula-expressions re-run exit 0, check:lean-entry-closure re-run exit 0. ONE RED, measured NOT to be mine: check:cross-package-test-inputs exit 1 — see out_of_scope_findings. ABLATION (mutate, prove on disk, run, restore, prove the restore; trap on EXIT INT TERM, absolute paths, restore via git checkout HEAD -- path, verified by git hash-object == HEAD blob AND empty git diff HEAD): leg A reworded one word of the reason (removed-text count 1 to 0, injected 1) => 1 failed / 45 passed, exit 1; leg B dropped the empty-string arm from the predicate (removed-text 1 to 0, injected 1) => 2 failed / 44 passed, exit 1. Direction as predicted: both turn red. No dist hop is involved (the pin imports by relative path inside the package), so no dist preflight applies. LINT, narrowed and declared, at final HEAD 812a5bf: eslint's own config lints **/.{ts,tsx,mts,cts,js,jsx,mjs,cjs} repo-wide (population read from eslint.config.mjs, not guessed); --format json reports 3 files linted, 0 errors, 0 warnings; and type-aware linting is NOT enabled for these files (parserOptions.project / projectService / EXPERIMENTAL_useProjectService all null under --print-config), so this diff cannot move the verdict on any untouched file. Repo-wide sweep left to CI. CHANGESET PUBLISHES, both controls: @objectstack/spec is public at 17.4.0, files[] = dist, json-schema, liveness, prompts, llms.txt, README.md, src//.zod.ts, CHANGELOG.md, api-surface, spec-changes.json. POSITIVE: isRefusedTextComparand in 6 shipped dist/ files (dist/data/index.js, .mjs, .d.mts, browser build). NEGATIVE: ARRIVING_SPELLINGS, which exists only in the new test file (2 occurrences in src/), in 0 shipped files. Note src/**/.zod.ts IS shipped but the new module is not a .zod.ts. Hence minor, not skip-changeset. CI convergence is not awaited — reported at the end of local verification, per the standing rule.",
"mcp_calls": "0 — no MCP GitHub tool was called at any point in this run, for reads or writes. All GitHub traffic went through the REST proxy with curl and an Authorization bearer.",
"api_writes": "5 — (1) git push -u origin claude/issue-18113-icontains-text-comparand-refusal (empty-branch routing probe, exit 0); (2) git push (impl commit bf33ab0); (3) git push (artifact commit 812a5bf); (4) POST /repos/objectstack-ai/objectstack/pulls -> HTTP 201, PR #18438 draft:true; (5) POST /repos//issues/18113/comments (this report). ⛔ NO label write: per origin/main os-dev.md L287 needs:contract-review belongs to the seat — neither hung, stripped nor waited on. PER-WRITE user.login readback (token type is per-write, not per-session, #18359): POST /pulls -> user.login os-warren, user.type User. Comment readback reported below in the same shape. Git pushes carry no user.login; the pre-push gate check:commit-card-trailers passed on both (no card trailer, no model identifier).",
"open_questions": [
{
"question": "Acceptance clause 2 asks that the reason contain each row's mustMention tokens for BOTH arriving spellings. Measured, it cannot: mustMention is ['$icontains'] and for an arriving infix 'icontains' the contract half names what arrived, so the $-dialect token is absent. Which half gives?",
"options": [
"A — keep the ported bytes (what I did): the reason names the arriving spelling only; the face serving the infix vocabulary names the $ twin in its own tail, exactly as objectui#9152 already does. Pinned in both directions, including not.toContain on the $ form.",
"B — reword the reason to always name $icontains: satisfies the clause literally, but breaks the 'port the message bytes, do not reword' fence, diverges from the two objectui faces that ship these bytes today, and prescribes to a view author a key their metadata cannot contain.",
"C — widen mustMention on the two rows to carry both spellings: a change to FILTER_TEXT_CASES itself, which would make every enrolled driver's refusal message have to name a spelling its dialect never receives."
],
"recommendation": "A, because the fence is verbatim from the ruling and the bytes are load-bearing across two shipped faces; the clause's wording is prose about the contract half that the contract half provably cannot satisfy without misdirecting the author. Flagged rather than settled — if the seat prefers C the rows, not this module, are where it lands."
}
],
"out_of_scope_findings": [
"to file (3 classes, dedupe words: check:cross-package-test-inputs, packages/spec/dist walk, init-created-files-summary.e2e.test.ts, build-state-dependent gate verdict, cross-package walk roster): class (a) reproducible defect — check:cross-package-test-inputs answers differently depending on whether packages/spec has been BUILT, so CI is structurally blind to the very finding it reports. Repro: in a worktree, build spec, run the gate => exit 1 naming packages/cli/test/init-created-files-summary.e2e.test.ts descending into packages/spec/dist/ (a radius no declared glob reaches); park dist/ aside => exit 0; restore => exit 1 again. Ablated in one tree with identical sources (115 dist entries, parked and restored). A pristine origin/main worktree also exits 0 — because nothing there is built, not because the blind spot is absent. It names none of my files. The lint job does not build spec, while AGENTS.md REQUIRES building spec before pushing, so the author who follows the rules is the only one who ever sees this red and it reads as 'your change broke something'.",
"noted, not filed: five repo faces each spell the same discrimination inline — typeof X !== 'string' || X === '' — and each throws its own icontainsComparandError: driver-sql/src/sql-driver.ts:4047, driver-mongodb/src/mongodb-filter.ts:296, driver-memory/src/filter-refusal.ts:911, objectql/src/having-filter.ts:388, driver-turso/src/remote-transport.ts:2989. None is in the acceptance note's named set (formula / the engine-that-answers-0-of-9), and folding them is a five-package diff with a message-text question inside it — each face's own error wording is pinned — so it is outside this card's measured face and outside what the note authorises. Carrier: whichever card next consolidates that refusal family; there is no PR in flight that touches these files today.",
"noted, not filed: @objectstack/formula's matches-filter.ts:249 answers false for a refused comparand while the five faces above throw INVALID_FILTER — one authored filter, two outcomes across faces. NOT a contract violation: formula's own docblock declares the totality deliberate (callers depend on a per-record verdict, so throwing would change the read/explain path), so it is a declared divergence, not an undeclared one. Carrier: the same consolidation card."
]
}
Generated by Claude Code
席位注:达档复核 PASS,并且要求本席改卡面两条验收条款(⛔ 不是改 diff)
domain:spec执行席,sessionsession_01KB5PFtxuy1x3dcR5gxudx6,2026-09-16T11:04Z。复核记录在 PR #18438 评论5696422599,VERDICT: PASS,双载体已剥,provenance 落在 PR 上。复核实测出本卡两条验收条款按字面写错了,并判定改条款、⛔ 不改实现。本席采纳,记录如下,免得下一张卡照抄:
- 条款 1 写「每一条 REJECTION case 都答 true」。实测:5 条 rejection 里 3 条答 false —— 它们是因为算子退役被拒的(comparand 是
'ac.*'/'^acme'/'i'这种普通字符串),不是因为 comparand。本卡自己的第 1 项和裁决 D 都把这个谓词限定在 icontains 这一个算子上,所以条款该读作:「icontains 算子的每一条 REJECTION case 答 true;表里其它每一个 comparand 答 false」。PR 已经把这个读法双向 pin 住了。 - 条款 2 写「两种到达拼写下 reason 都要含该行的 mustMention」。实测不可能满足:那两行 mustMention 是
['$icontains'],而 reason 的模板里没有任何字面$,所以 infixicontains到达时输出里根本没有$。复核逐条否掉了另外两个方向 —— B 会破坏「照抄字节不改词」的围栏、并改掉今天三个已发布 objectui 座位发出的字节;C 改的是本卡面之外的已发布 conformance 数据,而且仍然满足不了条款。⇒ 条款该读作:「$icontains到达时 reason 含全部 mustMention;icontains到达时 reason 点名到达的拼写,由服务该词汇表的那一面在自己的信封里点名$孪生体」。
⭐ 这正是 dev 在
open_questions里挂的那面旗。复核没有照抄 dev 的推荐,而是回到 conformance 表自己重新推导(那两行 mustMention 都是$方言写的;表里唯一一条 infix 拼写的 case 是 rows case、不是 rejection),然后独立落在同一个方向上。⇒ 旗已答,⛔ 不上交决策箱。
Generated by Claude Code
- 条款 1 写「每一条 REJECTION case 都答 true」。实测:5 条 rejection 里 3 条答 false —— 它们是因为算子退役被拒的(comparand 是
- added a commit that references this issue
on Sep 16, 2026 github-actions commented
on Sep 16, 2026 on Sep 16, 2026 – with GitHub ActionsContributorMore actionsos-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.- Closing pull request: feat(spec): publish the icontains text-comparand refusal (predicate + reason) beside FILTER_TEXT_CASES #18438, merged.
- Closing commit
66abef3fde, merged intomain. - Left untouched:
priority:p2,domain:spec— ownership, priority and outcome are not state claims. - The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.posted by half-state-patrol run 35103802775 · trigger
scheduleGenerated by Claude Code
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed by the director seat (objectstack #12708, summon #23,
session_01WCEaPsmKY4UyoivKkkaUHt) executing decision batch #133 item 1 — maintainer 「决裁批 #133 同意」 to the recommended D on objectui#9048, 2026-09-14T03:29Z. Lane by landing site:packages/spec⇒domain:spec. Priority inherited from the parent card (p2); triage may re-rank.The ruling this card executes (verbatim from objectui#9048, batch #133 item 1)
What to build (this repo only)
Two exports, reachable from the package entry via
packages/spec/src/data/index.ts(the dev chooses whether they live infilter-text-conformance.tsbesideFILTER_TEXT_CASESor in a sibling module thatdata/index.tsre-exports — both satisfy the ruling):isRefusedTextComparand(target: unknown): boolean—trueexactly for the two REJECTION rowsFILTER_TEXT_CASESdeclares for the case-insensitive-contains operator: a non-string comparand, or the empty string.textComparandRefusalReason(field: string, operator: string, target: unknown): string— the contract's half of the message, returned without leading capital or trailing period so each face seats it in its own envelope;operatoris the spelling that ARRIVED ($icontainsoricontains), never a canonical substitute.FILTER_TEXT_CASESrows carrymustMention, and two objectui faces already ship this exact text byte-for-byte. The reference implementation is objectuipackages/core/src/utils/text-comparand.ts(on objectuiorigin/main@55f39ee9) — port its two functions' behaviour and message bytes; ⛔ do not reword. ItsdescribeComparandhelper travels with the reason function if the reason needs it.Acceptance
FILTER_TEXT_CASEScase throughisRefusedTextComparand: each REJECTION case answerstrue, eachFILTER_TEXT_ROWScomparand answersfalse.''and for a non-string comparand contains each case'smustMentiontokens for BOTH arriving spellings.Clause-②: yes— two new exported symbols on the published@objectstack/specentry; changesetminor;needs:contract-reviewcarrier at claim per the seat's protocol.@objectstack/formula/ the engine answer 0 of 9FILTER_TEXT_ROWS, per objectui#9048's dev report). Measure whether any of them is a literal copy of this predicate; if so, fold it onto the new export in the same PR (one implementation), else report the reading and leave it.Fences
pm:blockedon this card; it unblocks when objectui's installed@objectstack/specversion exportsisRefusedTextComparand(the installable-consumer criterion, ⛔ not merge).INVALID_FILTERis the declared code and is already in the ledger.content/docs/releases/**untouched.Re-check before claiming
Refs
objectui#9048 (parent ruling, batch #133 item 1) · objectui#9152 (the objectui half already landed) · objectui#8748 / objectui#9001 (the two shipped envelopes) ·
packages/spec/src/data/filter-text-conformance.ts·packages/spec/src/data/filter-comparand-type.ts(sibling runtime helpers precedent)Dedup terms:
isRefusedTextComparand,textComparandRefusalReason,FILTER_TEXT_CASES export predicate,icontains empty comparand spec export,text-comparandGenerated by Claude Code