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
packages/client/src/index.ts:4089 — if ('where' in options || 'fields' in options || 'orderBy' in options || 'offset' in options). Four sniff keys; limit absent. The second copy at :4766 is byte-identical, confirming the body's warning that a one-site fix forks them.
:185-186 — /** Relations to expand (JOIN / eager-load). */ expand?: Record<string, any> | string[]; declared on QueryOptionsV2, and :172 documents it as replacing legacy populate. No transport mapping on either branch.
Domain — domain:cli, anchored to the file, not the title.packages/client is the REST wire-protocol SDK and sits in the domain:cli package family. The word "client" in the title is not what decides this; packages/client/src/index.ts is.
Release board (target:v17) — two independent criteria, either sufficient:
② public contract declared ≠ enforced: expand is declared, typed, and JSDoc'd on the canonical options type and reaches the wire on neither branch. That is an API that lies about itself.
① a user hits it today: data.find('task', { limit: 20 }) — the most natural canonical spelling of "first 20" — returns the server default page size with HTTP 200 and no warning. Silent wrong answer, not an error.
Priority note the maintainer should have: the hit surface is about to grow, not hold. #6002 / PR #6324 puts the canonical vocabulary (where / fields / orderBy / limit / offset) into content/docs/kernel/runtime-services/data-service.mdx as the recommended column, so readers of that page start writing limit directly. Producer-side fix before the docs page teaches the shape at scale.
Scope guidance carried into the queue (the body's own, endorsed): predicate on "any canonical-only key present" derived from QueryOptionsV2, rather than appending limit to a hand-maintained sniff list — the list has already failed twice (limit, then expand) and a per-key patch guarantees a third. expand separately needs an ADR-0049 call: map it or drop the declaration; a dev should pick "map it" only if the REST list route's expand handling (#4240) covers the shape, otherwise report back rather than inventing a transport spelling.
Dedup: all 463 open issues and PRs paginated. No duplicate. Three neighbours, all distinct surfaces:
Claim: PM loop round 1 (domain:cli seat, 2026-08-08 shift)
Session: session_017uFVNMmTxLpmfQYiuKM1Yx
Branch: claude/issue-6322-client-find-canonical-keys
Worktree: objectstack-issue-6322
Domain: domain:cli
File surface: packages/client/src/index.ts (both find copies, :4081 / :4761), client tests, one .changeset/*.md (stop on breach; explain in the report)
Serial constraints cleared: no in-flight claims in this lane; no open PR touches packages/client/src/index.ts; #6002 / PR #6324 is docs-only (content/docs/**) and file-disjoint.
ACCEPT — PR #6480 (fix(client): derive data.find's canonical-key predicate from QueryOptionsV2, and map expand to the wire).
What shipped. Both defects closed at the producer, in both find copies:
A. The branch predicate is no longer a hand-written key list. It is Exclude<keyof QueryOptionsV2, keyof QueryOptions> held as a Record<…, true>, so TypeScript rejects the object when a canonical-only key is missing and when a non-canonical-only key is present. The next key added to QueryOptionsV2 is a compile error until someone states what it puts on the wire. aggregations / groupBy drop out automatically — both option types declare them, so they cannot discriminate. This is what the queue guidance asked for instead of appending limit; a per-key patch would have been the third round of the same mistake.
B.expand now reaches the wire as expand=<comma-separated relation names>, and the Record form contributes its keys — the same names the server derives from the comma list.
The PM's server-side assumption for expand was verified, not assumed — which is the part of this run worth naming. The dispatch said to check whether a documented wire spelling exists and to report back rather than invent one. Three independent pieces of evidence were produced: HttpFindQueryParamsSchema in packages/spec/src/api/protocol.zod.ts declaring expand on the GET list route as a comma-separated relation-name list, the normalizer in packages/metadata-protocol/src/protocol.ts splitting it on commas, and an existing packages/rest/src/rest.test.ts case driving expand: 'order,product'. Nothing was invented.
Tests satisfy the multi-face clause. One expectation table in packages/client/src/client.test.ts, every row driven through both implementations via a shared driveBoth helper against the same expected string, so a future edit landing on one copy goes red. Expectations are exact full query strings rather than toContain — correctly reasoned, since the defect was a param that never appeared and a substring assertion on the params that did appear stays green through exactly that. The single-key sweep is typed Record<CanonicalOnlyKey, TransportRow> with the key set recomputed in the test from the two exported interfaces, so the anti-rot device exists on the test side too.
Reverse verification with the direction predicted before running: 6 failed / 253 passed against unmodified origin/main, and the six were exactly the predicted set — including { limit: 20 } reporting expected '' to be 'top=20', reproducing the issue's empty-query-string measurement. The rows pinning the already-correct canonical/legacy translation parity were green before and after, confirming the PR does not touch what was already right.
Reviewed and accepted as a deliberate behaviour change: a nested per-relation expand ({ contact: { fields: ['name'] } }) now throws instead of being silently trimmed. It has no GET spelling, so trimming would issue a wider read than the caller asked for and say nothing — the same silent-drop shape this PR exists to close, and in family with #4371's engine-side refusal. Blast radius measured at zero callers workspace-wide, and the form never worked, so nothing functioning today starts failing. The alternative reading (narrow the declared type to string[] under ADR-0049) is a public-type call correctly left unmade. Flagged to the maintainer with a veto window rather than buried.
One residual noted, not blocking. In canonicalExpandParam, an expand value that is neither an array nor an object falls to return undefined — a silent drop, the very shape the function's own doc says it exists to close. It is unreachable for type-correct callers (the declared type is Record<string, any> | string[]) and is not a regression, so it does not hold up a release-board fix; recording it here so the next reader inherits it rather than rediscovering it.
Verification (read from GitHub, not from the report): changed files are exactly 3 — the changeset (patch), client.test.ts, index.ts — matching the claimed file surface, with no content/docs/releases/ edit. CI converged: 24 check runs, 22 success and 2 skipped by path filter, zero failures; ESLint and TypeScript Type Check both read as job conclusions of success, which is where the check:* gate families run.
Out-of-scope finding #6485 filed as required (the { limit: 0 } truthiness drop) — correctly left unfixed, since what limit: 0 means is a contract call rather than a drive-by != null edit.
Activity
claude commentedon Aug 7, 2026
Triage:
pm:queue+domain:cli+target:v17.Both halves re-verified on
origin/main:packages/client/src/index.ts:4089—if ('where' in options || 'fields' in options || 'orderBy' in options || 'offset' in options). Four sniff keys;limitabsent. The second copy at:4766is byte-identical, confirming the body's warning that a one-site fix forks them.:185-186—/** Relations to expand (JOIN / eager-load). */ expand?: Record<string, any> | string[];declared onQueryOptionsV2, and:172documents it as replacing legacypopulate. No transport mapping on either branch.Domain —
domain:cli, anchored to the file, not the title.packages/clientis the REST wire-protocol SDK and sits in thedomain:clipackage family. The word "client" in the title is not what decides this;packages/client/src/index.tsis.Release board (
target:v17) — two independent criteria, either sufficient:declared ≠ enforced:expandis declared, typed, and JSDoc'd on the canonical options type and reaches the wire on neither branch. That is an API that lies about itself.data.find('task', { limit: 20 })— the most natural canonical spelling of "first 20" — returns the server default page size with HTTP 200 and no warning. Silent wrong answer, not an error.Priority note the maintainer should have: the hit surface is about to grow, not hold. #6002 / PR #6324 puts the canonical vocabulary (
where/fields/orderBy/limit/offset) intocontent/docs/kernel/runtime-services/data-service.mdxas the recommended column, so readers of that page start writinglimitdirectly. Producer-side fix before the docs page teaches the shape at scale.Scope guidance carried into the queue (the body's own, endorsed): predicate on "any canonical-only key present" derived from
QueryOptionsV2, rather than appendinglimitto a hand-maintained sniff list — the list has already failed twice (limit, thenexpand) and a per-key patch guarantees a third.expandseparately needs an ADR-0049 call: map it or drop the declaration; a dev should pick "map it" only if the REST list route'sexpandhandling (#4240) covers the shape, otherwise report back rather than inventing a transport spelling.Dedup: all 463 open issues and PRs paginated. No duplicate. Three neighbours, all distinct surfaces:
services.data页的 Parameters 只列 legacy 查询词汇(top/skip/filter),而签名接受的QueryOptionsV2才是 SDK 自称推荐的那套 #6002 / PR docs(kernel): services.data 的 Parameters 补上 canonical QueryOptionsV2 词汇,Example 同批换用 (#6002) #6324 — the docs page whose implementation surfaced this; documentation side, explicitly not the producer fix.services.data页把find当稳定主入口,而 canonical source 给find打了@deprecated指向本页未记载的data.query()#6323 —services.datadocs treatingfindas the stable entry point while the canonical source marks it@deprecated. Docs, different consumer.sort/select/expand指向不存在的字段时被拒绝,而不是静默丢弃 (#4226) #4240 — REST list-route field validation forsort/select/expand; the server-side evidence thatexpandis not rejected upstream.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
Claim: PM loop round 1 (domain:cli seat, 2026-08-08 shift)
Session:
session_017uFVNMmTxLpmfQYiuKM1YxBranch:
claude/issue-6322-client-find-canonical-keysWorktree:
objectstack-issue-6322Domain:
domain:cliFile surface:
packages/client/src/index.ts(bothfindcopies, :4081 / :4761), client tests, one.changeset/*.md(stop on breach; explain in the report)Serial constraints cleared: no in-flight claims in this lane; no open PR touches
packages/client/src/index.ts; #6002 / PR #6324 is docs-only (content/docs/**) and file-disjoint.Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
ACCEPT — PR #6480 (
fix(client): derive data.find's canonical-key predicate from QueryOptionsV2, and map expand to the wire).What shipped. Both defects closed at the producer, in both
findcopies:Exclude<keyof QueryOptionsV2, keyof QueryOptions>held as aRecord<…, true>, so TypeScript rejects the object when a canonical-only key is missing and when a non-canonical-only key is present. The next key added toQueryOptionsV2is a compile error until someone states what it puts on the wire.aggregations/groupBydrop out automatically — both option types declare them, so they cannot discriminate. This is what the queue guidance asked for instead of appendinglimit; a per-key patch would have been the third round of the same mistake.expandnow reaches the wire asexpand=<comma-separated relation names>, and theRecordform contributes its keys — the same names the server derives from the comma list.The PM's server-side assumption for
expandwas verified, not assumed — which is the part of this run worth naming. The dispatch said to check whether a documented wire spelling exists and to report back rather than invent one. Three independent pieces of evidence were produced:HttpFindQueryParamsSchemainpackages/spec/src/api/protocol.zod.tsdeclaringexpandon the GET list route as a comma-separated relation-name list, the normalizer inpackages/metadata-protocol/src/protocol.tssplitting it on commas, and an existingpackages/rest/src/rest.test.tscase drivingexpand: 'order,product'. Nothing was invented.Tests satisfy the multi-face clause. One expectation table in
packages/client/src/client.test.ts, every row driven through both implementations via a shareddriveBothhelper against the same expected string, so a future edit landing on one copy goes red. Expectations are exact full query strings rather thantoContain— correctly reasoned, since the defect was a param that never appeared and a substring assertion on the params that did appear stays green through exactly that. The single-key sweep is typedRecord<CanonicalOnlyKey, TransportRow>with the key set recomputed in the test from the two exported interfaces, so the anti-rot device exists on the test side too.Reverse verification with the direction predicted before running: 6 failed / 253 passed against unmodified
origin/main, and the six were exactly the predicted set — including{ limit: 20 }reportingexpected '' to be 'top=20', reproducing the issue's empty-query-string measurement. The rows pinning the already-correct canonical/legacy translation parity were green before and after, confirming the PR does not touch what was already right.Reviewed and accepted as a deliberate behaviour change: a nested per-relation
expand({ contact: { fields: ['name'] } }) now throws instead of being silently trimmed. It has no GET spelling, so trimming would issue a wider read than the caller asked for and say nothing — the same silent-drop shape this PR exists to close, and in family with #4371's engine-side refusal. Blast radius measured at zero callers workspace-wide, and the form never worked, so nothing functioning today starts failing. The alternative reading (narrow the declared type tostring[]under ADR-0049) is a public-type call correctly left unmade. Flagged to the maintainer with a veto window rather than buried.One residual noted, not blocking. In
canonicalExpandParam, anexpandvalue that is neither an array nor an object falls toreturn undefined— a silent drop, the very shape the function's own doc says it exists to close. It is unreachable for type-correct callers (the declared type isRecord<string, any> | string[]) and is not a regression, so it does not hold up a release-board fix; recording it here so the next reader inherits it rather than rediscovering it.Verification (read from GitHub, not from the report): changed files are exactly 3 — the changeset (patch),
client.test.ts,index.ts— matching the claimed file surface, with nocontent/docs/releases/edit. CI converged: 24 check runs, 22 success and 2 skipped by path filter, zero failures; ESLint and TypeScript Type Check both read as job conclusions ofsuccess, which is where thecheck:*gate families run.Out-of-scope finding #6485 filed as required (the
{ limit: 0 }truthiness drop) — correctly left unfixed, since whatlimit: 0means is a contract call rather than a drive-by!= nulledit.Marking ready and adding to the merge queue.
Generated by Claude Code