You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security(search): a field-narrowed search still matches through the name field's pinyin companion #21880
Found by the independent code review of PR #21879, the fix for #21836. That PR narrows each swept object's search to the fields the caller may query. One search clause the engine adds is not governed by that narrowing.
Class: authorization (field-level disclosure through search matching). Under RUNNER rule 2, the mechanism and the reproduction are withheld pending maintainer. They are held in the PM session (Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6).
Scope:
It applies only when the optional pinyin search companion is enabled.
Direction: the engine drops the companion clause whenever the field it mirrors is outside the effective search-field set. Alternatively, the companion's eligibility follows the same field-level read rules the predicate guard applies. Either fix lands in the engine search expansion, not in searchAll.
Path: business objects and records — searchable, with consistent permissions | search.field-scoped-narrowing | P2
Triage: first grade — bug · security · priority:p3 · domain:engine · area:access · pm:queue (finding removed). The companion clause follows the effective search-field set
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T13:51Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only. The withheld detail stays withheld (RUNNER rule 2); this seat does not hold it.
Triage: lands in the engine's search expansion (not searchAll) ⇒ domain:engine; rationale: the narrowing to queryable fields is applied before the expansion adds the companion clause, so the clause escapes it.
Direction (no boundary moves, so not a decision card). The engine drops the companion clause whenever the field it mirrors is outside the effective search-field set. That is the card's first option: one gate, the set the narrowing already computed. ⛔ No second eligibility rule for the companion.
Why p3. The reviewer graded it low. It applies only with the optional companion enabled, and it predates 17.6.0.
Also owed (public tests), as the card lists: a row-scoped object where the member sees only their own matching row, and a term present only in a field hidden from the member, which yields no hit.
Claim: PM loop round 38 · 2026-10-05T23:54Z
Session: session_017ErfyP2Rx7XWHJA27QjyUi
Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-21880-search-companion-scope
Worktree: objectstack-issue-21880
Domain: domain:engine
Seat: domain:engine#1
Provenance: the seat asked the maintainer in this seat's session (2026-10-05) whether to take this card, which had stayed in pm:queue with no claim since triage. The maintainer answered, verbatim: 「并发3」. This lane runs three concurrent claims, and this card is the only eligible pm:queue card. The withheld detail (RUNNER rule 2, held in session_018zT8d8NpiQ1ExhuNd5TxY6) is not requested and not needed: triage's direction (5995863103) is public and names the fix. The PR describes the fix only in the terms already public on this card.
File surface (at origin/maindcb11c2ec9), per triage's grade and direction 5995863103:
packages/objectql/src/search-filter.ts, expandSearchToFilter's companion clause (about :180–:185). The engine drops the companion clause whenever the field it mirrors is outside the effective search-field set. That is one gate: the set resolveSearchFields already computed. The mapping from companion to source fields comes from search-companion.ts (resolveSearchCompanionSources), touched only if the mapping must be read there.
⛔ No second eligibility rule for the companion.
⛔ Not in searchAll.
Pins, as triage lists them:
a unit pin in objectql: a field-narrowed search does not match through the companion of a field outside the set;
os-dev-report
{
"issue": 21880,
"status": "done",
"branch": "claude/issue-21880-search-companion-scope",
"pr": "#21930",
"session": "session_017ErfyP2Rx7XWHJA27QjyUi — the PM's session; this dev ran as its subagent (mode:subagent), identity = the branch",
"premise_still_valid": true,
"summary": "The search expansion (expandSearchToFilter) adds the pinyin companion clause only when every field the companion mirrors is inside searchFields, the effective set resolveSearchFields already computed. The mirrored fields come from resolveSearchCompanionSources, which the registry provisions the companion from and plugin-pinyin-search fills it from. There is one gate, no second eligibility rule, searchAll is not touched, and no spec file changed. With that, a field-narrowed search no longer matches through the companion of a field outside the search-field set. A search with no narrowing keeps the clause whenever the display/name field is in the object's searchable set, and a CJK term still skips it. Delivered: 10 unit pins in packages/objectql (a/b/c), the two public dogfood cases plus controls, and an @objectstack/objectql patch changeset with Clause-②: no. Draft PR #21930 is assigned to os-project-manager.",
"h2_companion_form_and_gate": "Measured at dcb11c2 and unchanged at 40618fa: ONE __search column per object. It holds the normalized form of ONE source field, the resolved display/name field. resolveSearchCompanionSources returns [display] or []; provisionSearchCompanion declares no column on []; the populate hook and the backfill read the same function. Gate: keep the clause iff every entry of resolveSearchCompanionSources({ nameField: opts.displayField, fields: opts.fields }) is in searchFields. The engine passes displayField = nameField ?? displayNameField, the same pointer resolveDisplayField reads, so this is the companion's real source. The test is every-not-some because one shared column matches through all its sources at once; today that reduces to 'the display/name field is in searchFields'. An empty list passes vacuously, which is reachable only for an author-declared __search the platform does not fill. H1 confirmed: at dcb11c2 the clause (search-filter.ts:180-185) keyed only on the column's presence. H3 held: one gate, no searchAll change, and an un-narrowed search keeps the clause (unit (b)).",
"tests": "All at final head 40618fa (origin/main faf8dce merged), under os-verify-lock. (1) pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: Test Files 375 passed (375), Tests 7479 passed (7479), OBJECTQL_TEST_EXIT=0. (2) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=1 test/search-companion-field-scope.dogfood.test.ts test/search-skip-unreadable.dogfood.test.ts: Test Files 2 passed, Tests 11 passed. (3) pnpm --filter @objectstack/objectql run typecheck: exit 0, including check:test-typecheck over tsconfig.test.json. pnpm --filter @objectstack/dogfood run typecheck: exit 0, after a full turbo build. --listFilesOnly counts 1 for each new test file in its program. (4) Reverse verification on committed HEAD, done twice (e7b2a3c, then 40618fa) with identical readings. node scripts/ablation-replace.mjs replaced the gate with a constant-true guard carrying marker __ABLATED_21880: anchor 1 to 0, blob 5a2afee37090 to ce8eb93139c8, wrap mode with a restore trap. Then pnpm --filter @objectstack/objectql build. ablation-dist-preflight: marker present in 4 built files. Unit src/search-companion.test.ts: 5 failed / 34 passed; the 5 are exactly the (a) cases. Dogfood file: 2 failed / 5 passed; the 2 are exactly the two hidden-field no-hit cases, and the row-scoped case and all controls stayed green. Direction: red, as expected. Restore: blob == HEAD 5a2afee37090, git diff HEAD empty. objectql rebuilt; ablation-dist-preflight --absent: marker absent from all 14 built files, working tree clean. (5) Lint narrowed to the 4 changed .ts files: eslint --no-inline-config --format json gives 4 files, 0 errors, 0 warnings. eslint --print-config resolves a config for each (in population, none ignored). eslint.config.mjs has no parserOptions.project (type-aware linting off), so the verdict on untouched files cannot move. Whole-tree pnpm lint is CI's run.",
"gates": "At 40618fa. node scripts/pm/dispatch-gates.mjs --commands (no paths) derived 78 commands. All ran, and --ran reconciled: '78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED', all exit 0. check:dual-build-cjs-loads measured after a full turbo build; the earlier sweep gave PREREQUISITE NOT MET. 10 families were added versus the dispatch-time list and all exited 0: check-changeset-fixed, check-osv-exemptions (+--self-test), spec check:llms-txt, check:manifest-repository-directory, check:merge-driver, check:override-consistency, check:turbo-task-graph, check:vendor-export-contract-resolve, check:workspace-manifest-cycles. Artifact-roster block, 53 commands: 50 exit 0 in the sweep. The other 3 were NOT WIRED (exit 2) without PR context, then each ran with it, all exit 0: check-partof-closing-keyword with PR_BODY = the PR body; check-closing-target-claim and check-single-claim-paths with PR_NUMBER=21930. Symbol-anchor sweeps: check:adr-symbol-anchors, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors, check:adr-anchors, all exit 0. check:test-source-alias went red once in an earlier sweep (new unaliased import @objectstack/plugin-pinyin-search in dogfood); the anchored source alias it prescribed fixed it, and it is green at the final head. NOT MEASURED locally, declared to CI as dispatch-gates prints them: 6 path-scheduled CI jobs, 4 type-check lanes, 6 workflow-valued families, 11 wide-population families. CI on #21930: not read; convergence is the PM's.",
"line_budget": "n/a",
"files_changed": [
".changeset/21880-search-companion-field-scope.md",
"packages/objectql/src/search-filter.ts",
"packages/objectql/src/search-companion.test.ts",
"packages/qa/dogfood/test/search-companion-field-scope.dogfood.test.ts",
"packages/qa/dogfood/package.json",
"packages/qa/dogfood/vitest.config.ts",
"pnpm-lock.yaml"
],
"deviations": [
"Branch probe git push -u origin claude/issue-21880-search-companion-scope: attempts 1-3 answered '! [remote rejected] claude/issue-21880-search-companion-scope -> claude/issue-21880-search-companion-scope (Internal Server Error)'. That is a server error, not a 403. I retried with backoff, and attempt 4 succeeded before any edit.",
"Refused by this session's permission checks, verbatim: 'Permission for this action was denied by the Claude Code auto mode classifier. Reason: [Exfil Scouting].' The command was curl -sS "$HTTPS_PROXY/__agentproxy/status" plus reading /root/.ccr/README.md, to diagnose the push 500. I did not retry it or route around it.",
"File surface beyond the claim's list. packages/qa/dogfood/package.json and pnpm-lock.yaml add the workspace dependency @objectstack/plugin-pinyin-search; the dogfood boot mounts the real PinyinSearchPlugin. packages/qa/dogfood/vitest.config.ts gains one anchored source alias, which check:test-source-alias demands (it went red without it). search-companion.ts is untouched, and the unit pins live in the existing search-companion.test.ts.",
"One PR body line is now stale: 'The other two are declared to CI'. Both later ran with PR_NUMBER=21930 and exited 0. Per the write-once rule I did not PATCH the body. Seat edit if wanted: replace that sentence with 'check-closing-target-claim and check-single-claim-paths ran after the PR opened, with PR_NUMBER=21930: both exit 0.'",
"Conflict noted, not resolved silently. The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md wins: commits carry the model-free pair (Claude-Session + Co-authored-by: Claude), and the PR body ends in the session-URL footer.",
"origin/main was merged twice (3dbd084, then faf8dce) before opening the PR. Every test, typecheck, gate, roster and anchor reading above was re-run on the final head 40618fa, and the reverse verification was repeated there.",
"Cleanup is done. rm -rf the worktree's node_modules, then git worktree remove /home/user/objectstack-issue-21880: exit 0, without --force, after confirming the tree was clean and the remote head equalled 40618fa. No server or background process is left running."
],
"mcp_calls": "0 — no MCP GitHub tool was called. Reads went through gh api REST GETs: issue, comments, PR read-back.",
"api_writes": "3 relay strokes, each one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches, 204) executed as objectstack-fleet[bot]. (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, opening draft #21930; read-back 7777 bytes sent, 7777 stored, identical. (2) label-write --assign os-project-manager, POST /repos//issues/21930/assignees; read-back MATCHES, and no label was written. (3) This os-dev-report comment, POST /repos//issues/21880/comments. Plus git push: 9 attempts, 3 rejected with 500, 6 landed. Pushes are not REST writes.",
"open_questions": [
{
"question": "Non-blocking boundary. An author-declared __search column whose source resolveSearchCompanionSources cannot name passes the gate vacuously, so its clause is kept even under narrowing. Should that case instead fail closed?",
"options": [
"A: keep the vacuous pass, as shipped. That is the literal ruling (drop when a mirrored field is outside the set), and an un-narrowed search keeps the clause everywhere",
"B: require a non-empty source list. That fails closed for a source-less author column, but it also drops the clause on that object's un-narrowed searches"
],
"recommendation": "A. The platform never provisions a companion without a source. An author-declared __search is an ordinary field under its own field-level rules, and B would change recall for un-narrowed searches with no measured user."
}
],
"out_of_scope_findings": [
"carrier: PR #21930 Acceptance notes · noted, not filed — an un-narrowed search on an object whose effective search set omits its display/name field no longer gets the clause. Examples: a declared searchableFields without it, or an html/richtext display field. Measured over examples/ at dcb11c2: 0 objects change (showcase_account's declared set includes name; todo_task's nameField subject is text). Derived html/richtext display fields: NOT MEASURED."
]
}
Landed: PR #21930 → 0728cbf192 on main. It merged through the merge queue at 2026-10-06T04:31Z; it entered the queue at 2026-10-06T03:54Z. Verified at 2026-10-06T04:31Z. domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi.
The squash. It is on origin/main as a single-parent commit. Its diffstat is the reviewed one at 211df5615d: 7 files, +449/-3.
What is on main.objectql's search-filter.ts adds the pinyin companion clause only when every field it mirrors is inside the effective search-field set (companionWithinSearchFields). An unnarrowed search keeps its companion matches. Public dogfood cases pin both directions.
The card.Fixes #21880 closed this card as completed. pm:dispatched is removed in this act. No other card was closed by the body.
From this release (@objectstack/objectql patch, Clause-②: no): a search narrowed by searchFields no longer matches a record through the pinyin companion of a field outside that set.
Activity
objectstack-fleet commentedon Oct 5, 2026
Path: business objects and records — searchable, with consistent permissions | search.field-scoped-narrowing | P2
Triage: first grade —
bug·security·priority:p3·domain:engine·area:access·pm:queue(findingremoved). The companion clause follows the effective search-field setTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T13:51Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only. The withheld detail stays withheld (RUNNER rule 2); this seat does not hold it.Triage: lands in the engine's search expansion (not
searchAll) ⇒domain:engine; rationale: the narrowing to queryable fields is applied before the expansion adds the companion clause, so the clause escapes it.session_018zT8d8NpiQ1ExhuNd5TxY6on the maintainer's word.Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Claim: PM loop round 38 · 2026-10-05T23:54Z
Session:
session_017ErfyP2Rx7XWHJA27QjyUiAccount:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-21880-search-companion-scopeWorktree:
objectstack-issue-21880Domain:
domain:engineSeat:
domain:engine#1Provenance: the seat asked the maintainer in this seat's session (2026-10-05) whether to take this card, which had stayed in
pm:queuewith no claim since triage. The maintainer answered, verbatim: 「并发3」. This lane runs three concurrent claims, and this card is the only eligiblepm:queuecard. The withheld detail (RUNNER rule 2, held insession_018zT8d8NpiQ1ExhuNd5TxY6) is not requested and not needed: triage's direction (5995863103) is public and names the fix. The PR describes the fix only in the terms already public on this card.File surface (at
origin/maindcb11c2ec9), per triage's grade and direction 5995863103:packages/objectql/src/search-filter.ts,expandSearchToFilter's companion clause (about:180–:185). The engine drops the companion clause whenever the field it mirrors is outside the effective search-field set. That is one gate: the setresolveSearchFieldsalready computed. The mapping from companion to source fields comes fromsearch-companion.ts(resolveSearchCompanionSources), touched only if the mapping must be read there.searchAll.objectql: a field-narrowed search does not match through the companion of a field outside the set;.changeset/21880-*.md(@objectstack/objectqlpatch).Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate).Clause-②: no
Clause-②: noatpatch.Thread-read: 5995863103
Serial constraints cleared: at 2026-10-05T23:54Z:
87712ab823.filename, paged: none touchessearch-filter.ts,search-companion.ts,plugin-pinyin-searchsource or a search dogfood test. This lane's own PR fix(objectql): the cascade skips a federated object's injected tenant anchor #21917 touchesengine.ts's cascade scan only. The release PR chore: version packages #21352 touchesplugin-pinyin-search's CHANGELOG and version line only.engine.ts) and [finding] The metadata door saves an edit to a code-defined datasource (PUT /api/v1/meta/datasource/:nameanswers 200) and the metadata read then serves it, while the datasource admin door refuses the same edit as read-only #21899 (metadata-protocol's datasource write path).objectstack-fleet commentedon Oct 6, 2026
os-dev-report
{
"issue": 21880,
"status": "done",
"branch": "claude/issue-21880-search-companion-scope",
"pr": "#21930",
"session": "session_017ErfyP2Rx7XWHJA27QjyUi — the PM's session; this dev ran as its subagent (mode:subagent), identity = the branch",
"premise_still_valid": true,
"summary": "The search expansion (expandSearchToFilter) adds the pinyin companion clause only when every field the companion mirrors is inside searchFields, the effective set resolveSearchFields already computed. The mirrored fields come from resolveSearchCompanionSources, which the registry provisions the companion from and plugin-pinyin-search fills it from. There is one gate, no second eligibility rule, searchAll is not touched, and no spec file changed. With that, a field-narrowed search no longer matches through the companion of a field outside the search-field set. A search with no narrowing keeps the clause whenever the display/name field is in the object's searchable set, and a CJK term still skips it. Delivered: 10 unit pins in packages/objectql (a/b/c), the two public dogfood cases plus controls, and an @objectstack/objectql patch changeset with Clause-②: no. Draft PR #21930 is assigned to os-project-manager.",
"h2_companion_form_and_gate": "Measured at dcb11c2 and unchanged at 40618fa: ONE __search column per object. It holds the normalized form of ONE source field, the resolved display/name field. resolveSearchCompanionSources returns [display] or []; provisionSearchCompanion declares no column on []; the populate hook and the backfill read the same function. Gate: keep the clause iff every entry of resolveSearchCompanionSources({ nameField: opts.displayField, fields: opts.fields }) is in searchFields. The engine passes displayField = nameField ?? displayNameField, the same pointer resolveDisplayField reads, so this is the companion's real source. The test is every-not-some because one shared column matches through all its sources at once; today that reduces to 'the display/name field is in searchFields'. An empty list passes vacuously, which is reachable only for an author-declared __search the platform does not fill. H1 confirmed: at dcb11c2 the clause (search-filter.ts:180-185) keyed only on the column's presence. H3 held: one gate, no searchAll change, and an un-narrowed search keeps the clause (unit (b)).",
"tests": "All at final head 40618fa (origin/main faf8dce merged), under os-verify-lock. (1) pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: Test Files 375 passed (375), Tests 7479 passed (7479), OBJECTQL_TEST_EXIT=0. (2) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=1 test/search-companion-field-scope.dogfood.test.ts test/search-skip-unreadable.dogfood.test.ts: Test Files 2 passed, Tests 11 passed. (3) pnpm --filter @objectstack/objectql run typecheck: exit 0, including check:test-typecheck over tsconfig.test.json. pnpm --filter @objectstack/dogfood run typecheck: exit 0, after a full turbo build. --listFilesOnly counts 1 for each new test file in its program. (4) Reverse verification on committed HEAD, done twice (e7b2a3c, then 40618fa) with identical readings. node scripts/ablation-replace.mjs replaced the gate with a constant-true guard carrying marker __ABLATED_21880: anchor 1 to 0, blob 5a2afee37090 to ce8eb93139c8, wrap mode with a restore trap. Then pnpm --filter @objectstack/objectql build. ablation-dist-preflight: marker present in 4 built files. Unit src/search-companion.test.ts: 5 failed / 34 passed; the 5 are exactly the (a) cases. Dogfood file: 2 failed / 5 passed; the 2 are exactly the two hidden-field no-hit cases, and the row-scoped case and all controls stayed green. Direction: red, as expected. Restore: blob == HEAD 5a2afee37090, git diff HEAD empty. objectql rebuilt; ablation-dist-preflight --absent: marker absent from all 14 built files, working tree clean. (5) Lint narrowed to the 4 changed .ts files: eslint --no-inline-config --format json gives 4 files, 0 errors, 0 warnings. eslint --print-config resolves a config for each (in population, none ignored). eslint.config.mjs has no parserOptions.project (type-aware linting off), so the verdict on untouched files cannot move. Whole-tree pnpm lint is CI's run.",
"gates": "At 40618fa. node scripts/pm/dispatch-gates.mjs --commands (no paths) derived 78 commands. All ran, and --ran reconciled: '78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED', all exit 0. check:dual-build-cjs-loads measured after a full turbo build; the earlier sweep gave PREREQUISITE NOT MET. 10 families were added versus the dispatch-time list and all exited 0: check-changeset-fixed, check-osv-exemptions (+--self-test), spec check:llms-txt, check:manifest-repository-directory, check:merge-driver, check:override-consistency, check:turbo-task-graph, check:vendor-export-contract-resolve, check:workspace-manifest-cycles. Artifact-roster block, 53 commands: 50 exit 0 in the sweep. The other 3 were NOT WIRED (exit 2) without PR context, then each ran with it, all exit 0: check-partof-closing-keyword with PR_BODY = the PR body; check-closing-target-claim and check-single-claim-paths with PR_NUMBER=21930. Symbol-anchor sweeps: check:adr-symbol-anchors, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors, check:adr-anchors, all exit 0. check:test-source-alias went red once in an earlier sweep (new unaliased import @objectstack/plugin-pinyin-search in dogfood); the anchored source alias it prescribed fixed it, and it is green at the final head. NOT MEASURED locally, declared to CI as dispatch-gates prints them: 6 path-scheduled CI jobs, 4 type-check lanes, 6 workflow-valued families, 11 wide-population families. CI on #21930: not read; convergence is the PM's.",
"line_budget": "n/a",
"files_changed": [
".changeset/21880-search-companion-field-scope.md",
"packages/objectql/src/search-filter.ts",
"packages/objectql/src/search-companion.test.ts",
"packages/qa/dogfood/test/search-companion-field-scope.dogfood.test.ts",
"packages/qa/dogfood/package.json",
"packages/qa/dogfood/vitest.config.ts",
"pnpm-lock.yaml"
],
"deviations": [
"Branch probe git push -u origin claude/issue-21880-search-companion-scope: attempts 1-3 answered '! [remote rejected] claude/issue-21880-search-companion-scope -> claude/issue-21880-search-companion-scope (Internal Server Error)'. That is a server error, not a 403. I retried with backoff, and attempt 4 succeeded before any edit.",
"Refused by this session's permission checks, verbatim: 'Permission for this action was denied by the Claude Code auto mode classifier. Reason: [Exfil Scouting].' The command was curl -sS "$HTTPS_PROXY/__agentproxy/status" plus reading /root/.ccr/README.md, to diagnose the push 500. I did not retry it or route around it.",
"File surface beyond the claim's list. packages/qa/dogfood/package.json and pnpm-lock.yaml add the workspace dependency @objectstack/plugin-pinyin-search; the dogfood boot mounts the real PinyinSearchPlugin. packages/qa/dogfood/vitest.config.ts gains one anchored source alias, which check:test-source-alias demands (it went red without it). search-companion.ts is untouched, and the unit pins live in the existing search-companion.test.ts.",
"One PR body line is now stale: 'The other two are declared to CI'. Both later ran with PR_NUMBER=21930 and exited 0. Per the write-once rule I did not PATCH the body. Seat edit if wanted: replace that sentence with 'check-closing-target-claim and check-single-claim-paths ran after the PR opened, with PR_NUMBER=21930: both exit 0.'",
"Conflict noted, not resolved silently. The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md wins: commits carry the model-free pair (Claude-Session + Co-authored-by: Claude), and the PR body ends in the session-URL footer.",
"origin/main was merged twice (3dbd084, then faf8dce) before opening the PR. Every test, typecheck, gate, roster and anchor reading above was re-run on the final head 40618fa, and the reverse verification was repeated there.",
"Cleanup is done. rm -rf the worktree's node_modules, then git worktree remove /home/user/objectstack-issue-21880: exit 0, without --force, after confirming the tree was clean and the remote head equalled 40618fa. No server or background process is left running."
],
"mcp_calls": "0 — no MCP GitHub tool was called. Reads went through gh api REST GETs: issue, comments, PR read-back.",
"api_writes": "3 relay strokes, each one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches, 204) executed as objectstack-fleet[bot]. (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, opening draft #21930; read-back 7777 bytes sent, 7777 stored, identical. (2) label-write --assign os-project-manager, POST /repos//issues/21930/assignees; read-back MATCHES, and no label was written. (3) This os-dev-report comment, POST /repos//issues/21880/comments. Plus git push: 9 attempts, 3 rejected with 500, 6 landed. Pushes are not REST writes.",
"open_questions": [
{
"question": "Non-blocking boundary. An author-declared __search column whose source resolveSearchCompanionSources cannot name passes the gate vacuously, so its clause is kept even under narrowing. Should that case instead fail closed?",
"options": [
"A: keep the vacuous pass, as shipped. That is the literal ruling (drop when a mirrored field is outside the set), and an un-narrowed search keeps the clause everywhere",
"B: require a non-empty source list. That fails closed for a source-less author column, but it also drops the clause on that object's un-narrowed searches"
],
"recommendation": "A. The platform never provisions a companion without a source. An author-declared __search is an ordinary field under its own field-level rules, and B would change recall for un-narrowed searches with no measured user."
}
],
"out_of_scope_findings": [
"carrier: PR #21930 Acceptance notes · noted, not filed — an un-narrowed search on an object whose effective search set omits its display/name field no longer gets the clause. Examples: a declared searchableFields without it, or an html/richtext display field. Measured over examples/ at dcb11c2: 0 objects change (showcase_account's declared set includes name; todo_task's nameField subject is text). Derived html/richtext display fields: NOT MEASURED."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
Landed: PR #21930 →
0728cbf192onmain. It merged through the merge queue at 2026-10-06T04:31Z; it entered the queue at 2026-10-06T03:54Z. Verified at 2026-10-06T04:31Z.domain:engine#1·session_017ErfyP2Rx7XWHJA27QjyUi.origin/mainas a single-parent commit. Its diffstat is the reviewed one at211df5615d: 7 files, +449/-3.main.objectql'ssearch-filter.tsadds the pinyin companion clause only when every field it mirrors is inside the effective search-field set (companionWithinSearchFields). An unnarrowed search keeps its companion matches. Public dogfood cases pin both directions.Fixes #21880closed this card ascompleted.pm:dispatchedis removed in this act. No other card was closed by the body.@objectstack/objectqlpatch,Clause-②: no): a search narrowed bysearchFieldsno longer matches a record through the pinyin companion of a field outside that set.mainafter it landed. The non-required OSV red the PR inherited is filed as [finding] main's lockfile matches four advisories (proxy-addr 2.0.7 critical, source-map-js 1.2.1 high, sprintf-js 1.1.3 no fix, katex 0.16.47): the scheduled OSV scan is red, and Validate Package Dependencies goes red on every PR touching a package.json #21945.Generated by Claude Code
docs(qa): checklist items for the 17.7 pre-release security fixes (ob…
fix(objectql): a field-narrowed search no longer matches through the …