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
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)
D — lift the predicate to the contract's owner.
@objectstack/specalready publishes the refusal as conformance cases (FILTER_TEXT_CASES) and already exports runtime comparand helpers from the sibling module (isAcceptedFilterComparand,normalizeFilterComparandTypesinfilter-comparand-type.ts, both re-exported bypackages/spec/src/data/index.ts). D exports the text-comparand refusal predicate (+ its reason text) from that same place;@object-ui/core's internaltext-comparand.tsbecomes a call into spec, and@object-ui/data-objectstackreads the same door. One implementation, three envelopes, and it lives with the producer of the rule.
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 in filter-text-conformance.ts beside FILTER_TEXT_CASES or in a sibling module that data/index.ts re-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_CASES rows carry mustMention, and two objectui faces already ship this exact text byte-for-byte. The reference implementation is objectui packages/core/src/utils/text-comparand.ts (on objectui origin/main @ 55f39ee9) — port its two functions' behaviour and message bytes; ⛔ do not reword. Its describeComparand helper travels with the reason function if the reason needs it.
Acceptance
- A pin that drives every
FILTER_TEXT_CASEScase throughisRefusedTextComparand: each REJECTION case answerstrue, eachFILTER_TEXT_ROWScomparand answersfalse. - A pin that the reason text for
''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.- Acceptance note (measurement, ⛔ not a gate): this repo's own faces already refuse the same rows (
@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
- ⛔ 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/specversion exportsisRefusedTextComparand(the installable-consumer criterion, ⛔ not merge). - ⛔ No new error code:
INVALID_FILTERis the declared code and is already in the ledger. - ⛔
content/docs/releases/**untouched.
Re-check before claiming
git fetch origin main
git grep -n -E 'export (const|function) (isRefusedTextComparand|textComparandRefusalReason)' origin/main -- packages/spec/src # expected 0 today
git grep -n -E "export \* from './filter-text-conformance'" origin/main -- packages/spec/src/data/index.ts # expected 1 (entry reachability)
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-comparand
Generated by Claude Code
Activity
os-warren commentedon Sep 16, 2026
Claim: PM dispatch by the
domain:specexecution seat, sessionsession_01KB5PFtxuy1x3dcR5gxudx6, at 2026-09-16T09:51Z.Branch:
claude/issue-18113-icontains-text-comparand-refusalWorktree:
../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, runisRefusedTextComparand/textComparandRefusalReasonexported todaydata/index.tsre-exports./filter-text-conformanceFILTER_TEXT_CASESexists in that areaFILTER_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
pm:blockedon this card; it unblocks when objectui's installed@objectstack/specexportsisRefusedTextComparand— the installable-consumer criterion, ⛔ not merge.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:
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.tstranslation-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.*scripts/pm/check-widening-tells.mjspackages/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
curl带GITHUB_TOKEN);POST/PATCH 必带-H "Content-Type: application/json",否则 HTTP 415。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为准,并逐行报告分歧。Claim:;⛔ 永不 force-push / rebase / amend。exit 3 PREREQUISITE NOT MET= NOT MEASURED。退出码用 redirect-then-$?取,⛔ 不经管道。domain:specexecution seat ·session_01KB5PFtxuy1x3dcR5gxudx6· claim posted 2026-09-16T09:51ZGenerated by Claude Code
os-warren commentedon Sep 16, 2026
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
os-warren commentedon Sep 16, 2026
席位注:达档复核 PASS,并且要求本席改卡面两条验收条款(⛔ 不是改 diff)
domain:spec执行席,sessionsession_01KB5PFtxuy1x3dcR5gxudx6,2026-09-16T11:04Z。复核记录在 PR #18438 评论5696422599,VERDICT: PASS,双载体已剥,provenance 落在 PR 上。复核实测出本卡两条验收条款按字面写错了,并判定改条款、⛔ 不改实现。本席采纳,记录如下,免得下一张卡照抄:
'ac.*'/'^acme'/'i'这种普通字符串),不是因为 comparand。本卡自己的第 1 项和裁决 D 都把这个谓词限定在 icontains 这一个算子上,所以条款该读作:「icontains 算子的每一条 REJECTION case 答 true;表里其它每一个 comparand 答 false」。PR 已经把这个读法双向 pin 住了。['$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
feat(spec): publish the icontains text-comparand refusal (predicate +…
github-actions commentedon Sep 16, 2026
os-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.66abef3fde, merged intomain.priority:p2,domain:spec— ownership, priority and outcome are not state claims.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
fix(service-analytics)!: both analytics doors refuse the two $icontai…
test(spec): the third data/ file group's test titles state each cited…