Repository navigation
[finding] spec migration entry filter-text-operator-declared-type-refused: its control sentence says multiselect / tags / lookup-id filters "must keep answering exactly as before", but since #21009 a text operator other than $contains on them answers 400 #21189
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
documentation·priority:p1·domain:spec·area:devpath·pm:queue. Release text: it lands before Version Packages #20639 next picks up PR #21165Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T15:53Z. ⛔ Not a claim, ⛔ not a dispatch.Why p1. It is the release-priority rule: wrong text a release would freeze, in a migration entry that upgrading authors and AI read as a repair-pass control.
The timing, read at this write:
- chore: version packages #20639's head
8bf27b1453carries entry 18 and does not carry PR fix(core)!: the JSON-column gate refuses $startsWith, $endsWith, $icontains and $like / $ilike as it refuses the equality family #21165 (2c1cef334). As drafted now, the control sentence is true for 17.6.0. - The next refresh picks up fix(core)!: the JSON-column gate refuses $startsWith, $endsWith, $icontains and $like / $ilike as it refuses the equality family #21165, and the sentence becomes untrue unless this lands first. It is spec: before the next changeset version, the connector-sync release notes contradict each other and the error-code ledger misses two service-automation rows (PR #21084 follow-ups) #21106's pattern.
Direction. The control sentence is narrowed to what the declared-type door leaves unaffected. It also names the JSON-column door's separate refusal of the text operators on JSON-stored columns since #21165, with
$contains/$notContainsas the membership answer there. ⛔ No change to either door. The entry's text follows the code.
Generated by Claude Code
- chore: version packages #20639's head
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratedocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p1High: required for production / M2High: required for production / M2and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01UtnxvdiN376GF3sgXwAw4d
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21189-text-operator-entry-control
Worktree:objectstack-issue-21189
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface:packages/spec/src/migrations/entries/semantic/18.filter-text-operator-declared-type-refused.ts(theacceptanceCriteriacontrol sentence, about:50-:57, narrowed to what this entry's declared-type door leaves alone, and naming the JSON-column door's separate refusal of the text operators on JSON-stored columns with$contains/$notContainsas the membership answer), the generated region ofpackages/spec/src/migrations/registry.ts(regenerated, ⛔ never hand-edited) and any generated doc the entry feeds, pins that read the sentence, and onepatch.changeset/21189-*.md. ⛔ No change to either door. Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: opus(default judgment tier;packages/spec/src/**non-test text, so the contract review runs atCONTRACT_REVIEW_TIERthrough an isolated subagent)
Clause-②: no
Thread-read: 5935183237
Serial constraints cleared: census of every open PR's file list at 2026-10-01T18:59Z (225 file rows): none touches the entry file.migrations/registry.tsis also held by PR #21222 (#21180, seat 2) and PR #21225 (domain:engine); its generated region merges throughscripts/pm/os-regen-merge.sh, and the second to land regenerates. Priority:priority:p1, the lane's only open P1, taken ahead of every p2 (NORTH-STAR priority 3). Timing per triage: it should land before Version Packages #20639's next refresh picks up PR #21165.objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21189, "status": "done", "branch": "claude/issue-21189-text-operator-entry-control", "pr": "https://github.com/objectstack-ai/objectstack/pull/21234", "session": "session_01UtnxvdiN376GF3sgXwAw4d", "premise_still_valid": true, "summary": "The acceptanceCriteria of filter-text-operator-declared-type-refused (protocol 18) no longer calls every text-valued field a control. It says the declared-type door leaves text-valued types alone, and that the control is a text-valued field NOT stored as a JSON column. It names the separate JSON-column door. Its population is multiselect / checkboxes / tags, any field declared multiple: true, and, inside the ADR-0104 dual-encoding window, a single-value file-class field. Its refusal covers every text operator except $contains / $notContains, with INVALID_FILTER 400. Its repair is membership on a multi-valued field, and the media-column move for single-value media. Neither door changed. The registry region was regenerated; a patch changeset was added; origin/main was merged through os-regen-merge.sh.", "tests": "All at 3a0bcb2d. (1) pnpm --filter @objectstack/spec build, then check:generated: 'All 15 generated artifacts are up to date' (migration-registry, spec-changes and upgrade-guide among them; only registry.ts carries this text). (2) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 596 files, 17457 tests passed, 1 todo. (3) pnpm --filter @objectstack/spec typecheck: exit 0; check:test-typecheck OK (52 files, 246 errors, 135 signatures held). (4) Narrowed eslint --no-inline-config --format json over the 2 changed .ts files: 2 files, 0 errors, 0 warnings. The population is eslint.config.mjs's packages/**/*.{ts,...} blocks; the config enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. (5) A throwaway SQLite measurement through SqlDriver, never committed, covered both media arms. Text, single select and single user fields answer rows to $startsWith on both arms. A single file field gives INVALID_FILTER 400 when its media columns have not moved and rows when they have. A select with multiple: true gives 400 on both arms, and its $contains answers membership. (6) Static grep for a tracker id (# followed by 4-5 digits) over the entry and the changeset: 0 hits; a positive control matched. No ablation: this is a text-only change with no guard to remove.", "mcp_calls": "0", "api_writes": "3 REST writes, each one relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (PR 21234, draft; read-back 7003 bytes sent, 7003 stored, identical); (2) assign → POST /repos/objectstack-ai/objectstack/issues/21234/assignees (os-sales; label-write read-back matches); (3) comment → POST /repos/objectstack-ai/objectstack/issues/21189/comments (this report). git push x4 (not REST).", "open_questions": [], "out_of_scope_findings": [ "class: a (candidate) · reach: NOT MEASURED at a public door (driver-level only) · evidence: on SqlDriver/SQLite with the media columns not moved, a single-value file field is a JSON column. There, $contains with the field's exact id ('fil_one') answers 0 rows, while the JSON-column door's refusal on $startsWith over the same field prescribes $contains. · dedupe words: single-value media JSON column $contains, dual-encoding window membership, files-to-references column move filter, JSON-column refusal prescription media", "carrier: none (no carrier) · noted in the PR's Acceptance notes, not filed; the entry gives that field the media-column move as its repair, not $contains" ], "gates": "dispatch-gates --commands at 3a0bcb2d (no paths): 82 derived. 81 exit 0 (doc-formula-expressions and lean-entry-closure first exited 3 PREREQUISITE NOT MET and passed after a turbo build of lint and objectql). NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3: it needs every package's dist, i.e. a whole-repo build; left to CI). Reconciliation dispatch-gates --ran with exit codes: '82 derived famil(ies) accounted for, 81 run, 1 NOT-MEASURED'. Integration tier declared to CI: packages/cli/test/migrate-meta-engine-guidance.test.ts (spawns the CLI; it prints this entry's verify: line verbatim). Remote CI at report time: 14 completed, 0 failed; 17 in_progress, 1 queued (in_progress).", "line_budget": "91 changed lines (+79 / -12) in 3 files, under the 5000 human-merge threshold", "files_changed": [ ".changeset/21189-text-operator-entry-control.md", "packages/spec/src/migrations/entries/semantic/18.filter-text-operator-declared-type-refused.ts", "packages/spec/src/migrations/registry.ts" ], "deviations": [ "The population is named as the code holds it, wider than the triage parenthetical (multiselect / checkboxes / tags, multi-valued lookup / user). It is every multiple: true field, which isMultiValueField covers, plus single-value file-class fields while the media columns have not moved (driver-sql isJsonField reads mediaColumnIsJson). Both were measured on SQLite. Otherwise the same untrue control sentence would have stayed for them.", "Zero label writes: the dispatch named no labels, and skip-changeset does not apply because a patch changeset ships. Only the PR assignee was written. size/s was already on the PR from another actor and was left alone.", "The harness reminder asked for a model-named Co-Authored-By trailer. The commits carry AGENTS.md's model-free pair (Co-authored-by: Claude, plus Claude-Session) instead.", "Zone 2 assumption 4: no test pins the sentence. migrate-meta-engine-guidance.test.ts lists only the entry id and checks the printed block for tracker ids, so no pin needed an update.", "Zone 2 assumption 3: only registry.ts carries the entry text. spec-changes.json and the upgrade guide do not, and check:generated is green with no --fix." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21234 @
3a0bcb2d14domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938468669· 2026-10-01T20:33Z- Shape (read on GitHub): draft against
main; first lineFixes #21189;Clause-②: noat a line start; PR assigneeos-sales. Three files, +79 / -12: theacceptanceCriteriaof semantic entryfilter-text-operator-declared-type-refused(protocol 18), its regenerated mirror inpackages/spec/src/migrations/registry.ts, and.changeset/21189-text-operator-entry-control.md('@objectstack/spec': patch). Nocontent/docs/releases/, no governed path. - At-tier review: owed, because the diff touches non-test
packages/spec/src/**. Contract-review record5939868227on this PR names this landing head3a0bcb2d1403d7da537230fa309a7762d1abde0c. It is VERDICT: PASS atCONTRACT_REVIEW_TIERwithClause-②: noandpatchconfirmed right. It checks each of the ten published sentences againstorigin/mainand finds each one TRUE. - Changeset prose, checked here sentence by sentence: each claim restates one the record verified: the five refused text operators,
INVALID_FILTER/400, the JSON-stored population including the ADR-0104 window arm, the membership repair, and themigrate files-to-references --applycolumn step. The three added claims are the scalar comparisons already refused, theverify:line printed byobjectstack migrate meta, and the registry mirror. All three are true per record item 10. No forecast and no unmeasured count. - Gates on this head: 35 check-runs, 32
successand 3 skipped; none failed, none pending.check-expected-skips: OK, all 3 skips are on the roster (Build Docs, Console Pin Gate, Packed-tarball smoke).Lint & Repo Gatespassed, socheck:migration-registryholds on the regenerated region.check-governed-merges --pr 21234: NOT governed, 91 changed lines.mergeable_state: clean. - Out-of-scope findings:
- filed core/driver-sql: the JSON-column refusal tells a single-value file field (media columns not yet moved) to use $contains, which answers no rows there; its repair is the media-column move #21236. On a SQL deployment whose media columns have not moved, a text operator over a single-value
file-class field reaches driver-sql's JSON-column refusal throughengine.find. That refusal prescribes$contains, which answers zero rows on a JSON scalar string. The dev reported reach as not measured at a public door. The review traced it: the declared-type door passesFILE_REFERENCE_TYPES, so the call reaches the refusal throughengine.find. A reproducible, user-reachable defect therefore takes a card instead of an acceptance note. Engine lane. This PR neither depends on that prescription nor repeats it.
- filed core/driver-sql: the JSON-column refusal tells a single-value file field (media columns not yet moved) to use $contains, which answers no rows there; its repair is the media-column move #21236. On a SQL deployment whose media columns have not moved, a text operator over a single-value
- Landing: this PR is readied and enters the merge queue once this record is confirmed on the platform. The maintainer is preparing a release, and this entry's
verify:text ships in@objectstack/spec, so it should land before the release PR's next refresh.
- Shape (read on GitHub): draft against
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded: PR #21234 →
1a4c7f8268domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938468669· 2026-10-01T21:03Z- Landed: PR fix(spec): filter-text-operator-declared-type-refused's control excludes JSON-stored fields and names the JSON-column door #21234 went through the merge queue as
1a4c7f8268, with one parent (a7d9768ecd).- The changeset and the entry file
18.filter-text-operator-declared-type-refused.tsare blob-equal to the accepted head3a0bcb2d14, onmainas well as on the merge commit. packages/spec/src/migrations/registry.tsdiffers from the head only by additions: 0 lines removed and 51 added, all of them PR fix(driver-sql,driver-turso)!: refuse an upsert whose conflict lands on another organization's row (#21185) #21225'sdriver-upsert-cross-organization-conflict-refusedentry, which landed after this branch's last base merge. This entry's regenerated region is carried byte for byte.
- The changeset and the entry file
- Card: closed
completedby the PR'sFixesline. The other issue closed in the same second, [Decision] #20822 F7: retiring formula's whole-day copy — the RLS write check judges the raw post-image, so deleting the copy refuses writes the same policy's read shows #21109, is not named in this PR's body (0 hits) and was closed by another PR in the queue batch. This act removespm:dispatchedand the assignee. - Follow-up filed at ACCEPT: core/driver-sql: the JSON-column refusal tells a single-value file field (media columns not yet moved) to use $contains, which answers no rows there; its repair is the media-column move #21236 (engine lane, triaged
priority:p2), the$containsprescription the JSON-column refusal gives a single-value file field. - Release: the next
@objectstack/spectarball carries the correctedverify:text. The Version Packages PR picks it up frommainon its next refresh.
- Landed: PR fix(spec): filter-text-operator-declared-type-refused's control excludes JSON-stored fields and names the JSON-column door #21234 went through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named landing site:
packages/spec/src/migrations/entries/semantic/18.filter-text-operator-declared-type-refused.ts, itsacceptanceCriteria. Finding class (b): published text that is now untrue.reach:the entry ships in@objectstack/spec, and an upgrading author or AI reads it as a repair-pass control. Measured by reading the entry atmain2c1cef334, against PR #21165's landed behaviour.Filed by the
domain:engineexecution seat 2 (seat post #20966,session_01Ujdtvqs7ree7WyQmEDwEnG), as #21009's ACCEPT 5933176640 finding [1] said it would be at landing. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What is untrue now
The entry's acceptance criteria use this as the control that "proves a repair pass did not over-reach":
Since PR #21165 (#21009,
2c1cef334), the JSON-column gate refuses$startsWith/$endsWith/$icontains/$like/$ilikeon a declared JSON-stored column withINVALID_FILTER/400. That gate is a different door from this entry's declared-type door.multiselect/checkboxes/tagsand multi-valued lookup /usercolumns are JSON-stored. So a storedstarts_with/ends_with/icontainsfilter over one of them, for example aListView.filterrule, no longer "keeps answering exactly as before": it answers 400 from the JSON-column door.The equality family on those columns has been refused at that door since #7398.
$contains/$notContainsthere answer membership.Scope for whoever takes it (⛔ not a ruling)
$contains/$notContainsare refused by the JSON-column door ([finding]$startsWith/$icontainson a multi-valued lookup answer 500 on PostgreSQL and a wrong count on SQLite: the text operators other than$containsreach a JSON column unrefused and unruled #21009). A filter left on them answers 400 there, and the repair is$contains(membership).$startsWith/$icontainson a multi-valued lookup answer 500 on PostgreSQL and a wrong count on SQLite: the text operators other than$containsreach a JSON column unrefused and unruled #21009's narrowing owes its own registered entry was judgednot-required (no-migration-prescription)by PR fix(core)!: the JSON-column gate refuses $startsWith, $endsWith, $icontains and $like / $ilike as it refuses the equality family #21165's review, consistent with PR fix(objectql)!: a per-aggregation filter refuses a scalar comparison on a declared JSON-stored field, in where's words #21097. This card is the wording of the existing entry only.Generated by Claude Code