Repository navigation
spec: InboxListResult.unreadCount 的 JSDoc 仍写「over the returned window」—— 与 #6363 落地后的实现和同族 .describe() 相反 #6438
Description
Activity
Triage —
pm:blocked·domain:spec-surface·Blocked-by: #6363Routing by the accept-face criterion, not by the package. The landing site is
packages/spec/src/contracts/notification-service.ts:102— verified onorigin/main(26b72e0), the line still reads/** Unread count over the returned window. */aboveunreadCount: number;at :103. Insidepackages/specthe seat split is decided by whether the set of legal metadata moves, not by which directory the line is in: a JSDoc rewrite leaves every input that validated before validating byte-for-byte after ⇒domain:spec-surface, even though the file lives undercontracts/**. JSDoc is named explicitly in that seat's scope. No.zod.tschange is implied, so the red line towarddomain:spec(#6245 / #6235) is not crossed.Blocked — and this one is genuinely cross-lane. The proposed text asserts behaviour that does not exist yet:
#6363isdomain:services, claimed, and its implementation has not merged, somessaging-service.tsstill counts over the window. Landing this JSDoc first would not fix a lie, it would invert it — the interface would promise a total while the service still delivers a window count, and an SDK consumer reading it in their editor would be misled in the opposite direction for the duration. Unlike #6435 and #6436 (which sit in the same lane as their siblings and can be sequenced inside it), the two halves here belong to different seats — spec-surface cannot see the services lane's in-flight batch — so the ordering has to be machine-readable rather than social.Blocked-by: #6363has been added to the body on its own line, which is what the unlock sweep greps; when #6363 closes, this returns to the queue.Verified in the same read, so the eventual fix has its anchors: the wire declaration at
packages/spec/src/api/protocol.zod.ts:924already says.describe('Total number of unread notifications')— i.e. after #6363 lands, this JSDoc is the last remaining statement of the old semantics in the package, and the two prose sources in one package will finally agree with the implementation.Release board. Deliberately not boarded (
target:*withheld). It is a text-face card whose user-visible half is #6363 itself, and #6363 is not on the board either; boarding the follow-on comment while its subject is off-board would misreport what actually gates the RC. If the maintainer boards #6363, this should follow it on.Duplicate search across all three repos (
unreadCount,InboxListResult, returned-window wording): #6363 (the implementation half, claimed) and #6361 (request-sidecursor, same package, same family) — related, neither a duplicate. Worth noting for whoever takes it: #6361 and this card touch the same package and are both text-adjacent, so the spec-surface seat may prefer to sweep them together once both unblock (that seat's default mode is sweep-first) — thenotifications"thelimit-bounded window" sentence suggested in the body belongs in the same pass.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commented
on Aug 7, 2026 CollaboratorMore actionsUnlock + claim (paired with the body/label edit just made in the same pen):
pm:blockedremoved,pm:queuerestored, and theBlocked-by: #6363body line deleted together — #6363 closedcompletedat 2026-08-07T19:05:47Z (its implementation PR #6439 merged to main as17d095413, verified onorigin/main, not from memory). The service now counts the whole matching inbox, so rewriting this JSDoc no longer inverts a lie — it retires the last statement of the old semantics in the package (triage's own anchor).Triage's "sweep together with #6361" suggestion was evaluated and does not bind: #6361 is
domain:spec(protocol seat — it changes request-parsing behavior, an acceptance surface), open and unclaimed. Its work cannot ride in this lane; this card goes alone.Claim: PM loop round 4 (
domain:spec-surfaceseat #6298)
Session:session_018ffcE95NaMJcL9XJ9VDYgk(GitHubos-project-manager)
Branch:claude/issue-6438-unreadcount-jsdoc
Worktree:objectstack-issue-6438
Domain:domain:spec-surface
File surface:packages/spec/src/contracts/notification-service.ts(theunreadCountJSDoc line + the companionnotificationswindow sentence the body suggests),.changeset/*.md(stop on breach; explain in the report)
Serial constraints cleared: no in-lane work in flight (lane cleared at 22:18Z). #6439 (#6363) touched the siblingservice-messagingpackage and MERGED — carried as same-day churn (branch from freshorigin/main). #6361 (protocol seat) is open but NOT in flight — no same-file claim exists; its surfaces (request-sidecursor/limit, response-sidecursorremoval) are explicitly out of this card's scope.
Container weight: S (two JSDoc sentences + changeset),mode:subagentshared container.
Generated by Claude Code
os-project-manager commented
on Aug 7, 2026 CollaboratorMore actionsACCEPT — PR #6463 (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk; early-review path — reviewed from the PR's diff, body and gate narrative at headf66e367; the dev's JSON report will be reconciled on arrival).Verified:
- 2 files, matching the claim's declared surface exactly:
packages/spec/src/contracts/notification-service.ts(theInboxListResultdoc block only) + one substantive English changeset (@objectstack/spec: patch, justified against the docs(spec): flipOWNING_BUSINESS_UNIT_IDto INJECTED and sync thesystemFieldsinjection list (ADR-0117 D1, #5767) #6364 precedent which the dev verified rather than assumed). - Fix substance matches the ruling:
unreadCountnow states the total-across-whole-matching-inbox semantics, anchored to the wire.describe()text, with the do-not-re-derive / do-not-clamp consequence and the measured notification 响应侧:unreadCount声明「总未读数」实测只数 limit 窗口内;响应cursor从无 producer 发出 #6363 symptom retained; the companionnotificationssentence documents thelimit-bounded window; the interface-level line records that the two bounds differ on purpose. - One clause beyond the two scoped sentences, accepted with its reasoning: "
InboxQuery.readdoes not zero it" states an existing fact both in-repo implementations already agree on (quoted frommessaging-service.ts+ the contract-test fake counting over the unfiltered set) — a truth statement, not a new obligation. The dev explicitly declined to promote the type-filter interaction, which WOULD have been a new contract clause. Correct scope judgment on both sides of the line. - Forbidden surfaces untouched: nothing request-side (GET /api/v1/notifications 从不解析它声明的请求 schema ——
cursor被静默丢弃(SDK 分页永远第一页),limit默认 20 声明 vs 50 实现 #6361's territory), no response-cursorremoval, zero.zod.tsbytes,content/docs/releases/clean; repo-wide grep for the retired phrase now hits only notification 响应侧:unreadCount声明「总未读数」实测只数 limit 窗口内;响应cursor从无 producer 发出 #6363's own changeset (historical usage, correct). - Honest negative results accepted as such: no pin (interface JSDoc has no runtime surface; the semantics are pinned where executable — notification 响应侧:
unreadCount声明「总未读数」实测只数 limit 窗口内;响应cursor从无 producer 发出 #6363's tests + the.describe()), and reverse verification inapplicable (no gate reads TS doc comments) with the discriminating check supplied instead. Both match the dispatch's stated assumptions. - Premise re-verified against fresh
origin/main(three anchors held); generated artifacts confirmed untouched (git status= exactly the two files; anchor byte-identical).
Ready-flip + auto-merge arm happens as soon as both gate-family jobs (ESLint / TypeScript Type Check) report
conclusion: success— checkpoint armed.
Generated by Claude Code
- 2 files, matching the claim's declared surface exactly:
- added a commit that references this issue
on Aug 8, 2026
发现于 #6363 的实施。故意不在该 PR 修:#6363 的派单红线是「不改
packages/spec」(Option A 是让声明成真,不是改声明),而这一行落在 spec 座位的文件里,与 #6361 的cursor移除同属一个包。单独立单,好让 spec 座位一次处理。事实
packages/spec/src/contracts/notification-service.ts:/** Unread count over the returned window. */记录的是 #6363 修好之前的实现语义。#6363 落地后:service-messaging/src/messaging-service.ts)数的是整个匹配信箱的总未读,窗口只约束notifications[];packages/spec/src/api/protocol.zod.ts:924)一直写的是'Total number of unread notifications'。于是同一个字段在同一个包里有两句相反的散文,而实现站在
.describe()那边。为什么值得单独修,而不是「只是个注释」
这行不是内部注释:它是
INotificationService这个对外契约接口的 JSDoc,随.d.ts发布,是 TS SDK 使用者在编辑器里读到的那句话。一个把「窗口内计数」讲给消费者听的提示,正好会诱导消费者写出 #6363 要消灭的那种适配(自己再去数一遍、或按窗口大小截断显示)。防 AI 轴上也是同一件事:AI 写的消费端就是照这句 JSDoc 生成的。修法(一行)
把
/** Unread count over the returned window. */改成与.describe()同义的表述,例如:顺带可考虑给
notifications补一句「thelimit-bounded window」,因为这两个界现在是故意不同的,而当前接口里没有任何一句写下这个区别。关联
unreadCount声明「总未读数」实测只数 limit 窗口内;响应cursor从无 producer 发出 #6363(unreadCount数总未读,维护者 2026-08-07 裁 Option A)cursor被静默丢弃(SDK 分页永远第一页),limit默认 20 声明 vs 50 实现 #6361(请求侧cursor;响应侧cursor的移除随其同向处理)