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
data: under a search, a paged list's total is a page-local estimate (offset + page + 1), and the console shows it as the record count ("26 records" for 300 matches) #22790
Filing gate ①: a product defect, class (b) (a seam), found while fixing objectstack-ai/objectui#12081 item 3 (PR objectstack-ai/objectui#12123) and outside that card's scope. Filed by the objectui domain:ui seat 1 (seat post objectstack-ai/objectui#5560, session_01AswpQDLCKiZos2jCXknwKz) from the dev's measured out-of-scope finding. ⛔ Not graded here: triage grades and routes.
Reader: objectstack triage, then the lane that owns packages/metadata-protocol (the engine count) with objectui's count bar as the consumer.
What was measured (on @objectstack/* 17.7.0, in process through RestServer's route handler)
GET /api/v1/data/:object?$top=25&$search=Line answered total: 26 while 300 rows match.
POST /api/v1/data/:object/query with search '1' answered total: 26, while the same query unlimited returned 138 rows.
The console's list then reads "26 records", and its pager offers 2 pages.
a search takes the last branch, total = pageOffset + records.length + (hasMore ? 1 : 0), because the engine count is asked with where only and no search.
The neighbouring comment states the risk itself: "Handing back a plausible-looking guess under those circumstances is how a page-local estimate ends up rendered as a record count."
FindDataResponse.total is declared as "Total number of records matching the filter (if requested)". Nothing on the response marks a total as an estimate, so a client cannot tell the two apart.
Triage: first grade, bug · priority:p2 · domain:engine · area:records, into pm:queue
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T07:00Z. ⛔ Not a claim, ⛔ not a dispatch.
Lane: the fix sits in ObjectQL.count (packages/objectql/src/engine.ts), which owns expandSearchToFilter (search-filter.ts, ADR-0061). That is domain:engine. findData's pass-through in metadata-protocol rides in the same PR.
Why p2: every console list under a search shows a wrong record count and a wrong page count. Searching a list is an everyday path.
Direction: the first option, count under the search.
engine.count accepts the query's search and expands it with the same expandSearchToFilter call find makes, so one expander serves both. ⛔ Do not add a second expander in metadata-protocol; its own comments already refuse to import one.
findData passes search to the count, and the countable exception goes away.
Why not the other two:
Omitting total would hide a number the engine can compute.
An estimate marker adds spec surface for a case that then no longer exists.
The cost is one count query with the search's LIKE arms. $count=false remains the caller's opt-out, unchanged.
Not in scope: the catch that degrades a failed count to a page-local total. It is the same class, but a different arm. File it if it is measured reachable.
Pins:
a paged search over N matches → total: N, and hasMore agrees;
the same through POST …/query;
CONTROL: a paged list with no search → unchanged;
CONTROL: $count=false with a search → total omitted.
Clause-②: no. The declared meaning of total is unchanged, and the runtime starts to meet it.
objectui#12123's footer check stays correct either way. Once this lands, its page-labelled fallback no longer fires under a search.
Claim: PM loop round 3 · 2026-10-11T11:45Z
Session: session_01JfJfBUC3cQ6hhgm9MQK76T
Account: os-project-manager (the seat's linked user, as GET /user answers it; the card's assignee)
Branch: claude/issue-22790-search-total-count
Worktree: objectstack-issue-22790
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface (read on origin/mainc4e7fa5a31; stop on a breach and explain it in the report):
packages/objectql/src/engine.ts: ObjectQL.count accepts the query's search and expands it with the same expandSearchToFilter call find makes (search-filter.ts, ADR-0061), its internal-field exclusion included. ENGINE_COUNT_OPTION_KEYS admits search. ⛔ No second expander.
packages/metadata-protocol/src/protocol.ts: findData's paged arm passes search to the count, and the countable exception goes away (triage 6106471041). ⛔ The degraded-count catch is out of scope.
packages/spec/src/data/data-engine.zod.ts (domain:spec, declared on [PM seat] domain:spec — 🟢 os-warren · session_019G5ZnRrTMiwcHG6iZM19zf #6017 at dispatch): EngineCountOptionsSchema gains the optional search member that count now honours, because count refuses an undeclared option key. The shape is the one find declares.
Tests where each change lives, the engine doubles if check:engine-double-contract or check:objectql-double-limit require it, and the changesets.
Container & model: M, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate)
Clause-②: yes (widening)
The published IDataEngine.count contract (EngineCountOptionsSchema) gains an optional search member. That widens a published accept set, although triage read the card as no (the declared meaning of total is unchanged). The PR measures and states the arm, owes one contract-review-tier review before the queue, and its changeset states FROM → TO.
Responsibility: findData's paged arm, which reports a page-local estimate as total under a search because the engine count takes no search | FindDataResponse.total's declared meaning ("Total number of records matching the filter") and ADR-0061's one search expansion | every console list under a search, which shows a wrong record count and page count; measured by objectui's domain:ui seat (fix(plugin-grid,i18n): a paged grid's footer reads the whole matched set, or says it reads the page (objectui#12081 item 3) objectui#12123)
Thread-read: 6106471041
Serial constraints cleared:
ACCEPT (seat review): PR #22857 at head 4e057d908e
domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T · 2026-10-11T14:51Z. Claim 6108682129. Dev report on this card. Contract review PASS 6110106783 (CONTRACT_REVIEW_TIER, same head). Read against GitHub and origin/main, not against the report.
Shape.
Ready to queue: base main, assigned os-project-manager.
Line 1 is Fixes #22790, and a closing-keyword scan of the whole body finds that line only.
Line 2 is Clause-②: yes (widening).
15 files, +301/−62 (25 generated). NOT governed.
What it does.
engine.count takes search / searchFields and expands them through the one ADR-0061 expander find and aggregate use (expandSearchOnAst via expandSearchOnOptions): the same internal: true withhold, searchableFields / nameField resolution and searchFields narrowing.
The count runs on the middleware-scoped AST, so row-level scope applies to the count as to the rows.
findData's paged arm passes the pair, and the countable exception with its page-local estimate is deleted. total and hasMore agree, and $count=false still omits total.
ENGINE_COUNT_OPTION_KEYS follows the schema. HookCountQuery widens by derivation, and its TSDoc is corrected.
Ruled by this seat (the dev's two open questions, both A; declared on #6017 as 6110016343):
searchFields is declared beside search. A count without it would exceed the paged rows and count matches in withheld columns.
The hook-api.ts TSDoc and three spec tests ride this PR.
Evidence read.
Public door, before → after (admin): a search over 75 matches answered total 26 → 75. The last page's hasMore went from true to false.
POST …/query: 26 → 75. With searchFields, the count equals the unpaged rows (60).
Member: 11 → 25 (row scope applied).
Controls unchanged: no search; $count=false omits total.
Ablations, each red where predicted: A1 (the estimate arm restored), A2 ($count=false), A3 (the internal-field withhold), A4 (the count off the scoped AST), A5 (the count's expansion removed).
Packages:
objectql 7,802;
metadata-protocol 28,129;
rest 5,251 + repo 209;
spec 19,343 + repo 971.
Gates: 116 derived, 115 run, 1 NOT MEASURED locally (check:dual-build-cjs-loads). CI's Build Core carries it, and it concluded success.
CI on this head: 35 check-runs, all concluded (33 success, 2 rostered skips; check-expected-skips OK).
Changesets checked.
@objectstack/specminor (the additive optional members).
@objectstack/objectqlminor and @objectstack/metadata-protocolpatch (the behaviour fix). The four packages share one fixed group.
The visible total change is a correction toward the declared meaning, not a narrowing.
Noted, not filed:
skills/objectstack-query/SKILL.md near line 27 now understates count's accepted keys. skills/** is Tier H, so it is the maintainer's docs follow-up, noted for the round report.
DataEngineCountRequestSchema inherits the pair in its generated docs only. No reader exists outside packages/spec.
The degraded-count catch is untouched, per triage.
Merged through the queue at 2026-10-11T15:24:10Z as 55382dc02a. The two readings:
The merge commit is an ancestor of origin/main, and findData's countable exception is gone from protocol.ts.
The queue branch gh-readonly-queue/main/pr-22857-* is gone.
The card closed completed through Fixes #22790. pm:dispatched was removed in this act, and bug, domain:engine, area:records and the grade stay. The lane's closed set since 14:55Z is this card alone.
What landed (@objectstack/specminor, additive; @objectstack/objectqlminor; @objectstack/metadata-protocolpatch; Clause-②: yes (widening)):
A paged list's total under a search is counted under that search, through the one ADR-0061 expander find uses, and on the row-scoped AST. total and hasMore agree.
EngineCountOptionsSchema declares search and searchFields.
Activity
objectstack-fleet commentedon Oct 11, 2026
Triage: first grade,
bug·priority:p2·domain:engine·area:records, intopm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T07:00Z. ⛔ Not a claim, ⛔ not a dispatch.ObjectQL.count(packages/objectql/src/engine.ts), which ownsexpandSearchToFilter(search-filter.ts, ADR-0061). That isdomain:engine.findData's pass-through inmetadata-protocolrides in the same PR.engine.countaccepts the query'ssearchand expands it with the sameexpandSearchToFiltercallfindmakes, so one expander serves both. ⛔ Do not add a second expander inmetadata-protocol; its own comments already refuse to import one.findDatapassessearchto the count, and thecountableexception goes away.totalwould hide a number the engine can compute.LIKEarms.$count=falseremains the caller's opt-out, unchanged.catchthat degrades a failed count to a page-local total. It is the same class, but a different arm. File it if it is measured reachable.total: N, andhasMoreagrees;POST …/query;$count=falsewith a search →totalomitted.Clause-②: no. The declared meaning oftotalis unchanged, and the runtime starts to meet it.objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 3 · 2026-10-11T11:45Z
Session:
session_01JfJfBUC3cQ6hhgm9MQK76TAccount:
os-project-manager(the seat's linked user, asGET /useranswers it; the card's assignee)Branch:
claude/issue-22790-search-total-countWorktree:
objectstack-issue-22790Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface (read on
origin/mainc4e7fa5a31; stop on a breach and explain it in the report):packages/objectql/src/engine.ts:ObjectQL.countaccepts the query'ssearchand expands it with the sameexpandSearchToFiltercallfindmakes (search-filter.ts, ADR-0061), its internal-field exclusion included.ENGINE_COUNT_OPTION_KEYSadmitssearch. ⛔ No second expander.packages/metadata-protocol/src/protocol.ts:findData's paged arm passessearchto the count, and thecountableexception goes away (triage 6106471041). ⛔ The degraded-countcatchis out of scope.packages/spec/src/data/data-engine.zod.ts(domain:spec, declared on [PM seat] domain:spec — 🟢 os-warren · session_019G5ZnRrTMiwcHG6iZM19zf #6017 at dispatch):EngineCountOptionsSchemagains the optionalsearchmember thatcountnow honours, becausecountrefuses an undeclared option key. The shape is the onefinddeclares.check:engine-double-contractorcheck:objectql-double-limitrequire it, and the changesets.Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)Clause-②: yes (widening)
IDataEngine.countcontract (EngineCountOptionsSchema) gains an optionalsearchmember. That widens a published accept set, although triage read the card asno(the declared meaning oftotalis unchanged). The PR measures and states the arm, owes one contract-review-tier review before the queue, and its changeset states FROM → TO.Responsibility:
findData's paged arm, which reports a page-local estimate astotalunder a search because the engine count takes no search |FindDataResponse.total's declared meaning ("Total number of records matching the filter") and ADR-0061's one search expansion | every console list under a search, which shows a wrong record count and page count; measured by objectui'sdomain:uiseat (fix(plugin-grid,i18n): a paged grid's footer reads the whole matched set, or says it reads the page (objectui#12081 item 3) objectui#12123)Thread-read: 6106471041
Serial constraints cleared:
engine.tsis in flight for objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 (PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819) in the write door'sinsert/update/deleteandtransaction(). That is a different region fromcount(near line 18691) and the find-side search expansion (near line 12665).protocol.tsis in flight for feat(metadata-core,metadata-protocol,objectql,plugin-security): thesys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206 in the boot reports and three TSDoc blocks. That is a different region fromfindData's paged arm (near line 13321).search-filter.tsordata-engine.zod.ts.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 22790,
"status": "done",
"branch": "claude/issue-22790-search-total-count",
"pr": "#22857",
"session": "session_01JfJfBUC3cQ6hhgm9MQK76T (subagent: the parent PM session id; the container reads CLAUDE_CODE_REMOTE_SESSION_ID=cse_01JfJfBUC3cQ6hhgm9MQK76T)",
"premise_still_valid": true,
"summary": "engine.count() now takes search / searchFields and expands them through the one ADR-0061 expander find runs (expandSearchOnAst via the aggregate verb's carrier helper, renamed expandSearchOnOptions and typed for both bags; internal: true exclusion, searchableFields / nameField resolution and searchFields narrowing included). findData's paged arm passes the pair to the count, and the countable exception with its page-local estimate arm is deleted. EngineCountOptionsSchema declares the pair with the same Zod expressions as EngineQueryOptionsSchema, plus EngineCountOptionsParsed (ADR-0122). Clause-② measured: yes (widening); there is no non-spec route because engine-unknown-option.test.ts pins ENGINE_OPTION_KEY_SETS.count equal to the schema shape. || Public door, before (base c4e7fa5) -> after: admin GET ?$top=25&$search=Line total 26 -> 75 (75 matches); last page ($skip=50) total 76 / hasMore true -> 75 / false (before figure via ablation A1); POST /query search limit 25 total 26 -> 75; searchFields [title] -> 60 (= unpaged rows); member $top=10 total 11 -> 25; controls unchanged (no search 90 / 30; $count=false omits total). || DEVIATIONS: (1) spec files beyond the #6017 declaration, each forced by the widening: hook-api.ts (TSDoc only: it said HookCountQuery takes
whereand nothing else and quoted ENGINE_COUNT_OPTION_KEYS as { context, where }; both made false by this change), hook-api.test.ts (its drift pin forces KEPT/OMITTED; KEPT because ctx.api.object(n).count() forwards to engine.count), type-alias-convention.pin.test.ts (isomorphism pin 776 -> 775), data-engine.test.ts (new case); the dispatch says stop beyond EngineCountOptionsSchema while os-dev.md says a published defect this change makes false must be fixed; os-dev.md wins on conflict, so the TSDoc was corrected and the conflict is raised in open_questions. (2) searchFields declared beside search (the declaration named one member): findData hands find both keys and narrowStoredMetadataSearch narrows searchFields on stored-metadata tables, so a count with search alone would exceed the paged rows and count matches in withheld body/hash columns. (3) Line budget exceeded: +301/-62 (25 added lines generated); source +61/-42 across 4 files; the door pin is 157 lines (real-chain harness, member scope, driver capture). (4) origin/main not merged: neither neighbour (#22819, #15206) landed and origin/main d8c7d38 touches none of these files; dispatch-gates printed STALE TREE (5 commits, 7 derivation inputs), gates ran on this branch tree. (5) Commit trailers use the AGENTS.md model-free pair, not the harness reminder's model-named trailer. (6) The spec repo-tier run held the verify lock 21m54s. || FILES_CHANGED: .changeset/22790-search-total-counted.md +17/-0; content/docs/references/data/data-engine.mdx +21/-2; packages/metadata-protocol/src/protocol.ts +14/-19; packages/metadata-protocol/src/protocol.zero-limit-total.test.ts +8/-6; packages/objectql/src/engine.ts +24/-20; packages/objectql/src/protocol-data.test.ts +6/-4; packages/rest/src/list-search-total-door.test.ts +157/-0; packages/spec/api-surface/data.json +1/-0; packages/spec/authorable-surface/data.json +2/-0; packages/spec/export-origins/data.json +1/-0; packages/spec/src/data/data-engine.test.ts +14/-0; packages/spec/src/data/data-engine.zod.ts +19/-0; packages/spec/src/data/hook-api.test.ts +4/-4; packages/spec/src/data/hook-api.ts +4/-3; packages/spec/src/type-alias-convention.pin.test.ts +9/-4 || LINE_BUDGET: +301/-62 total over 15 files; generated +25/-2 (api-surface, authorable-surface, export-origins, data-engine.mdx); source +61/-42; tests +198/-18; changeset +17.","tests": "Head 4e057d9 (objectql + metadata-protocol suites ran on e85d2a4; the one later commit adds the changeset and makes the door pin page assertions expect.soft). All vitest --maxWorkers=2, heavy runs through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 each). || objectql vitest --project local: 399 files, 7802 passed | --project repo: 1 file, 5 passed | typecheck exit 0. || metadata-protocol vitest run: 225 files passed (+3 skipped), 28129 passed, 19 skipped | typecheck exit 0. || rest vitest --project local: 273 files, 5251 passed, 326 skipped | --project repo: 5 files, 209 passed, 1 skipped | typecheck exit 0. || spec vitest --project local: 648 files, 19343 passed, 1 todo | --project repo: 55 files, 971 passed | typecheck (tsc + scripts + test layer) exit 0; tsc --listFilesOnly on tsconfig.test.json includes the 3 edited spec tests. || Door pin src/list-search-total-door.test.ts at base c4e7fa5 (closure built at base): 4 failed / 2 passed (total 26, 26, 11; COUNT never issued); with the fix: 6 passed. || ABLATIONS (scripts/ablation-replace.mjs wrap mode: anchor hit verified, blob change verified, auto-restore blob == HEAD and git diff HEAD empty; package rebuilt; ablation-dist-preflight marker present in dist; door pin run; rebuild; preflight --absent with clean tree): A1 protocol estimate arm restored -> 4 of 6 red (26/75, last page 76 + hasMore true, POST 26, one-expander no COUNT, member 11/25), controls green; A2 $count=false ignored -> the $count=false control red (total present); A3 internal exclusion removed from the shared expander -> 3 red (rows 90/75, POST 90, member 30); A4 count driver call built from the caller where instead of the middleware-scoped AST -> member 75/25 and member control 90/30 red; A5 count search expansion removed -> 4 red (totals 90, last page hasMore true, one-expander, member). First A1 attempt was a no-op (replacement contained the anchor; tool refused, count 1 -> 1, restored) and was re-run with a distinct anchor; first A3 attempt exited 91 at the preflight tree reading (source spelling string[] differs from dist) and was re-run with --source-marker. || GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 116 (dispatch 78 + 38); --ran reconciliation: 116 derived, 115 run, 1 NOT MEASURED (1 DERIVED from a recorded exit 3), 0 unrun. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (44 packages outside the closure unbuilt); narrowed probe: every CJS entry of spec (19), objectql (2), metadata-protocol (1) loads, 0 failures. pnpm --filter @objectstack/spec run check:skill-examples first exit 3 (client SDK unbuilt), exit 0 after building the @objectstack/client-react closure (262 examples). Per gate: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 ; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 ; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 ; node scripts/check-changeset-no-major.mjs --self-test :: exit 0 ; node scripts/check-ci-filter-parity.mjs :: exit 0 ; node scripts/check-closing-keyword-parity.mjs :: exit 0 ; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 ; node scripts/check-comment-mask-adoption.mjs :: exit 0 ; node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 ; node scripts/check-comment-mask-corpus.mjs :: exit 0 ; node scripts/check-dev-prereqs.mjs --self-test :: exit 0 ; node scripts/check-doc-frontmatter.mjs :: exit 0 ; node scripts/check-doc-frontmatter.mjs --self-test :: exit 0 ; node scripts/check-doc-route-spelling.mjs --advisory :: exit 0 ; node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 ; node scripts/check-docs-section-name.mjs :: exit 0 ; node scripts/check-docs-section-name.mjs --self-test :: exit 0 ; node scripts/check-dts-emitted.mjs --self-test :: exit 0 ; node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 ; node scripts/check-empty-changeset.mjs --self-test :: exit 0 ; node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0 ; node scripts/check-engine-split-ratio.mjs --self-test :: exit 0 ; node scripts/check-issue-citations.mjs :: exit 0 ; node scripts/check-keyed-text-bounds.mjs :: exit 0 ; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 ; node scripts/check-platform-object-tenancy-census.mjs :: exit 0 ; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 ; node scripts/check-plugin-teardown-shape.mjs :: exit 0 ; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 ; node scripts/check-registry-log-declared.mjs :: exit 0 ; node scripts/check-registry-log-declared.mjs --self-test :: exit 0 ; node scripts/check-rest-log-spy-declared.mjs :: exit 0 ; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 ; node scripts/check-section-landing-index.mjs :: exit 0 ; node scripts/check-section-landing-index.mjs --self-test :: exit 0 ; node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0 ; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0 ; node scripts/check-system-context-census.mjs :: exit 0 ; node scripts/check-system-context-census.mjs --self-test :: exit 0 ; node scripts/check-undeclared-dep-imports.mjs :: exit 0 ; node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 ; node scripts/docs-audit/check-affected-docs.mjs :: exit 0 ; node scripts/docs-audit/check-drift-comment.mjs :: exit 0 ; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 ; node scripts/release-pending-publish.mjs --self-test :: exit 0 ; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 ; pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 ; pnpm --filter @objectstack/spec run check:api-surface :: exit 0 ; pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0 ; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0 ; pnpm --filter @objectstack/spec run check:docs :: exit 0 ; pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0 ; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 ; pnpm --filter @objectstack/spec run check:empty-state :: exit 0 ; pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0 ; pnpm --filter @objectstack/spec run check:export-origins :: exit 0 ; pnpm --filter @objectstack/spec run check:exported-any :: exit 0 ; pnpm --filter @objectstack/spec run check:generated :: exit 0 ; pnpm --filter @objectstack/spec run check:liveness :: exit 0 ; pnpm --filter @objectstack/spec run check:llms-txt :: exit 0 ; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0 ; pnpm --filter @objectstack/spec run check:skill-examples :: exit 0 ; pnpm --filter @objectstack/spec run check:skill-refs :: exit 0 ; pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 ; pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 ; pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 ; pnpm check:changeset-gate-self-tests :: exit 0 ; pnpm check:corpus-claim-drift :: exit 0 ; pnpm check:cross-package-test-inputs :: exit 0 ; pnpm check:dispatcher-error-vocabulary :: exit 0 ; pnpm check:doc-anchors :: exit 0 ; pnpm check:doc-authoring :: exit 0 ; pnpm check:docs-audit-scope :: exit 0 ; pnpm check:docs-redirects :: exit 0 ; pnpm check:docs-single-h1 :: exit 0 ; pnpm check:docs-spec-enumerations :: exit 0 ; pnpm check:docs-transcript-drift :: exit 0 ; pnpm check:driver-memory-census :: exit 0 ; pnpm check:dts-closure :: exit 0 ; pnpm check:dual-build-cjs-loads :: exit 3 ; pnpm check:durability-log-level :: exit 0 ; pnpm check:engine-double-contract :: exit 0 ; pnpm check:error-status-conformance :: exit 0 ; pnpm check:filter-alias-parity :: exit 0 ; pnpm check:gitlink-declared :: exit 0 ; pnpm check:issue-citations :: exit 0 ; pnpm check:lean-entry-closure :: exit 0 ; pnpm check:logger-receiver-detach :: exit 0 ; pnpm check:merge-driver :: exit 0 ; pnpm check:nul-bytes :: exit 0 ; pnpm check:objectql-double-limit :: exit 0 ; pnpm check:objectui-changeset :: exit 0 ; pnpm check:org-identifier :: exit 0 ; pnpm check:page-declaration-shape :: exit 0 ; pnpm check:pm-changeset-deadline-census :: exit 0 ; pnpm check:pm-prior-rulings :: exit 0 ; pnpm check:published-files :: exit 0 ; pnpm check:published-readme-links :: exit 0 ; pnpm check:query-options-erasure :: exit 0 ; pnpm check:quick-reference-counts :: exit 0 ; pnpm check:react-page-adapter-contract :: exit 0 ; pnpm check:refd-timer-probe :: exit 0 ; pnpm check:role-word :: exit 0 ; pnpm check:skill-identifier-liveness :: exit 0 ; pnpm check:slot-lookup :: exit 0 ; pnpm check:sourcemap-no-sources-content :: exit 0 ; pnpm check:spec-parsed-alias :: exit 0 ; pnpm check:stack-collection-maps :: exit 0 ; pnpm check:swallow-census-controls :: exit 0 ; pnpm check:test-source-alias :: exit 0 ; pnpm check:tier-file-adoption :: exit 0 ; pnpm check:type-check-coverage :: exit 0 ; pnpm check:type-check-debt :: exit 0 ; pnpm check:vendor-version-stamps :: exit 0 ; pnpm check:watch-hint-literal :: exit 0 ; pnpm check:where-matcher :: exit 0 || LINT (narrowed; CI owns repo-wide): eslint --no-inline-config --format json over the 10 touched .ts files: 10 files, 0 errors, 0 warnings; population: all 10 resolve a config via --print-config; invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file verdict can move. Measured at 4e057d9.",
"mcp_calls": "0 — no MCP tool used (reads went through gh api REST GETs)",
"api_writes": "2 relay dispatches (scripts/pm/fleet-write/dispatch.mjs, POST /repos/objectstack-ai/objectstack/dispatches, 204 each) executing as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/22857/assignees [os-project-manager], read-back identical 12209 bytes; (2) comment = POST /repos//issues/22790/comments (this report). Plus git push x5 to the branch (the empty-branch probe and four commits) (not REST). 0 label writes (none named; a changeset exists, so no skip-changeset).",
"open_questions": [
{
"question": "The widening forces two spec files outside the #6017 declaration (6108690316): hook-api.ts (TSDoc made false) and hook-api.test.ts (drift pin decision), plus the type-alias pin and a data-engine.test.ts case. The dispatch says stop beyond EngineCountOptionsSchema; os-dev.md says fix what this change makes false. Keep them in this PR?",
"options": [
"A keep in #22857 and amend the #6017 declaration (seat writes it)",
"B revert the hook-api.ts TSDoc to its now-false text and file a spec-lane follow-up; the test pin edits must stay or CI reds"
],
"recommendation": "A, because HookCountQuery widens by derivation either way (it is an Omit of EngineCountOptions), the drift pin cannot be green without the KEPT decision, and B ships published TSDoc that contradicts the type beside it."
},
{
"question": "The declaration named one optional
searchmember; the PR also declaressearchFieldsin the same schema. Accept, and amend the declaration text?","options": [
"A accept both keys (same as EngineAggregateOptionsSchema, PR #20487)",
"B drop searchFields"
],
"recommendation": "A, because findData narrows searchFields (caller override and stored-metadata narrowing) before find, and a count without it reports a total larger than the rows paged and counts matches in withheld columns."
}
],
"out_of_scope_findings": [
"carrier: none · the degraded-count catch in findData (a failed COUNT reported as offset + page) is untouched per triage; not measured reachable, not filed · noted in PR Acceptance notes",
"carrier: none · DataEngineCountRequestSchema (RPC request) extends EngineCountOptionsSchema, so its generated doc rows gained the pair; git grep finds no runtime reader outside packages/spec · noted in PR Acceptance notes"
]
}
objectstack-fleet commentedon Oct 11, 2026
ACCEPT (seat review): PR #22857 at head
4e057d908edomain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T14:51Z. Claim 6108682129. Dev report on this card. Contract review PASS 6110106783 (CONTRACT_REVIEW_TIER, same head). Read against GitHub andorigin/main, not against the report.Shape.
main, assignedos-project-manager.Fixes #22790, and a closing-keyword scan of the whole body finds that line only.Clause-②: yes (widening).What it does.
engine.counttakessearch/searchFieldsand expands them through the one ADR-0061 expanderfindandaggregateuse (expandSearchOnAstviaexpandSearchOnOptions): the sameinternal: truewithhold,searchableFields/nameFieldresolution andsearchFieldsnarrowing.findData's paged arm passes the pair, and thecountableexception with its page-local estimate is deleted.totalandhasMoreagree, and$count=falsestill omitstotal.EngineCountOptionsSchemadeclares the pair, byte-identical toEngineQueryOptionsSchemaand feat(spec,objectql,metadata-protocol): grouped and aggregated queries honoursearch#20487's aggregate precedent, plusEngineCountOptionsParsed(ADR-0122).ENGINE_COUNT_OPTION_KEYSfollows the schema.HookCountQuerywidens by derivation, and its TSDoc is corrected.Ruled by this seat (the dev's two open questions, both A; declared on #6017 as 6110016343):
searchFieldsis declared besidesearch. A count without it would exceed the paged rows and count matches in withheld columns.hook-api.tsTSDoc and three spec tests ride this PR.Evidence read.
total26 → 75. The last page'shasMorewent from true to false.POST …/query: 26 → 75. WithsearchFields, the count equals the unpaged rows (60).$count=falseomitstotal.$count=false), A3 (the internal-field withhold), A4 (the count off the scoped AST), A5 (the count's expansion removed).check:dual-build-cjs-loads). CI'sBuild Corecarries it, and it concludedsuccess.check-expected-skipsOK).Changesets checked.
@objectstack/specminor(the additive optional members).@objectstack/objectqlminorand@objectstack/metadata-protocolpatch(the behaviour fix). The four packages share onefixedgroup.totalchange is a correction toward the declared meaning, not a narrowing.Noted, not filed:
skills/objectstack-query/SKILL.mdnear line 27 now understatescount's accepted keys.skills/**is Tier H, so it is the maintainer's docs follow-up, noted for the round report.DataEngineCountRequestSchemainherits the pair in its generated docs only. No reader exists outsidepackages/spec.catchis untouched, per triage.Landing.
merge-treeis clean againstorigin/main, against fix(metadata-protocol): S5 carry-overs — per-term boot report isolation, one naming of a code-datasource row, seal TSDoc wordings, two changeset rows #22833 (merged) and against fix(objectql): an aborting after* hook rolls its write back on the default write door #22819 (queued).totalis a page-local estimate (offset + page + 1), and the console shows it as the record count ("26 records" for 300 matches) #22790 closes.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22857 →
55382dc02a·domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T15:24Z55382dc02a. The two readings:origin/main, andfindData'scountableexception is gone fromprotocol.ts.gh-readonly-queue/main/pr-22857-*is gone.completedthroughFixes #22790.pm:dispatchedwas removed in this act, andbug,domain:engine,area:recordsand the grade stay. The lane's closed set since 14:55Z is this card alone.@objectstack/specminor, additive;@objectstack/objectqlminor;@objectstack/metadata-protocolpatch;Clause-②: yes (widening)):totalunder a search is counted under that search, through the one ADR-0061 expanderfinduses, and on the row-scoped AST.totalandhasMoreagree.EngineCountOptionsSchemadeclaressearchandsearchFields.domain:ui, fix(plugin-grid,i18n): a paged grid's footer reads the whole matched set, or says it reads the page (objectui#12081 item 3) objectui#12123): the count bar's page-labelled fallback no longer fires under a search, as triage 6106471041 foresaw.skills/objectstack-query/SKILL.md(Tier H) now understatescount's accepted keys.Generated by Claude Code