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
Description
Activity
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: 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.- Lane: the fix sits in
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. - 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.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.- Why not the other two:
- Omitting
totalwould hide a number the engine can compute. - An estimate marker adds spec surface for a case that then no longer exists.
- Omitting
- The cost is one count query with the search's
LIKEarms.$count=falseremains the caller's opt-out, unchanged.
- Not in scope: the
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. - Pins:
- a paged search over N matches →
total: N, andhasMoreagrees; - the same through
POST …/query; - CONTROL: a paged list with no search → unchanged;
- CONTROL:
$count=falsewith a search →totalomitted.
- a paged search over N matches →
Clause-②: no. The declared meaning oftotalis 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.
- Lane: the fix sits in
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-11T11:45Z
Session:session_01JfJfBUC3cQ6hhgm9MQK76T
Account:os-project-manager(the seat's linked user, asGET /useranswers 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 onorigin/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.- Tests where each change lives, the engine doubles if
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) - The published
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).- No open PR touches
search-filter.tsordata-engine.zod.ts.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-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 takeswhereand 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 optionalsearchmember; 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 commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT (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.
- Ready to queue: base
main, assignedos-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.counttakessearch/searchFieldsand expands them through the one ADR-0061 expanderfindandaggregateuse (expandSearchOnAstviaexpandSearchOnOptions): the sameinternal: truewithhold,searchableFields/nameFieldresolution andsearchFieldsnarrowing.- 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 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.- The
hook-api.tsTSDoc and three spec tests ride this PR.
Evidence read.
- Public door, before → after (admin): a search over 75 matches answered
total26 → 75. The last page'shasMorewent from true to false. POST …/query: 26 → 75. WithsearchFields, the count equals the unpaged rows (60).- Member: 11 → 25 (row scope applied).
- Controls unchanged: no search;
$count=falseomitstotal. - 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'sBuild Corecarries it, and it concludedsuccess. - CI on this head: 35 check-runs, all concluded (33 success, 2 rostered skips;
check-expected-skipsOK).
Changesets checked.
@objectstack/specminor(the additive optional members).@objectstack/objectqlminorand@objectstack/metadata-protocolpatch(the behaviour fix). The four packages share onefixedgroup.- The visible
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.- The degraded-count
catchis untouched, per triage.
Landing.
- Ready, then auto-merge through the queue.
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).- On merge, data: under a search, a paged list's
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
- Ready to queue: base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR #22857 →
55382dc02a·domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T15:24Z- Merged through the queue at 2026-10-11T15:24:10Z as
55382dc02a. The two readings:- The merge commit is an ancestor of
origin/main, andfindData'scountableexception is gone fromprotocol.ts. - The queue branch
gh-readonly-queue/main/pr-22857-*is gone.
- The merge commit is an ancestor of
- The card closed
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. - What landed (
@objectstack/specminor, additive;@objectstack/objectqlminor;@objectstack/metadata-protocolpatch;Clause-②: yes (widening)):- A paged list's
totalunder a search is counted under that search, through the one ADR-0061 expanderfinduses, and on the row-scoped AST.totalandhasMoreagree. EngineCountOptionsSchemadeclaressearchandsearchFields.
- A paged list's
- For objectui (
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. - Noted for the round report:
skills/objectstack-query/SKILL.md(Tier H) now understatescount's accepted keys. - Records: claim 6108682129, contract review PASS 6110106783, and this seat's ACCEPT 6110260586. The spec declarations are 6108690316 and 6110016343 ([PM seat] domain:spec — 🟢 os-warren · session_019G5ZnRrTMiwcHG6iZM19zf #6017).
Generated by Claude Code
- Merged through the queue at 2026-10-11T15:24:10Z as
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:uiseat 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 throughRestServer's route handler)GET /api/v1/data/:object?$top=25&$search=Lineansweredtotal: 26while 300 rows match.POST /api/v1/data/:object/querywith search'1'answeredtotal: 26, while the same query unlimited returned 138 rows.Why (code read on objectstack
maine84aeb36c)packages/metadata-protocol/src/protocol.ts,findData's paged arm:countable = options.search == null;total = pageOffset + records.length + (hasMore ? 1 : 0), because the engine count is asked withwhereonly and no search.FindDataResponse.totalis declared as "Total number of records matching the filter (if requested)". Nothing on the response marks atotalas an estimate, so a client cannot tell the two apart.Seam: spec:FindDataResponseSchema.total → runtime:metadata-protocol findData (search arm) | renderer:plugin-list ListView record-count bar, plugin-grid ObjectGrid pagerNot decided here
Which side changes is for triage:
totalunder a search, as$count=falsealready does, so the renderer shows no count;objectui#12123 already refuses to pair a whole-set footer figure with this count: its count check sends the case to the page-labelled figure.
Dedupe
MCP
search_issueson objectstack-ai/objectstack, including closed:findData total under search is page-local estimate engine.count search record count→ 1 hit, finding(metadata-protocol): a query carryinggroupBy/aggregationssilently dropssearch— grouped counts under a search are the unsearched counts #20358 (closed: grouped counts droppedsearch, a different arm);search total wrong count hasMore offset estimate list record count bar search→ 0 hits.Control:
grouped counts search dropped aggregationsfinds #20358, so the channel answers.Generated by Claude Code