Repository navigation
listViews has the same swallow objectui#7741 just removed from listImportMappings — a refused metadata read renders as "this object has no saved views" #8151
Description
⛔ Ungraded and unrouted — domain:*, priority and type are triage's. Filed unassigned, out of scope, from the objectui#7741 round.
The behaviour
packages/data-objectstack/src/index.ts, ObjectStackAdapter.listViews — measured on origin/main 21d7989fb, the catch arm at :4893-4896 (the sibling arm objectui#7741 was about was at :4663 on the same tree):
} catch (err) {
console.warn('[OBJECTSTACKDataSource] listViews failed:', err);
return [];
}The read it wraps is this.client.meta.getItems('view'). So a view list that was refused (401 / 403), or that failed (5xx, a dropped connection), returns the identical value as a view list the server served empty — and every consumer of listViews reads only the return.
Why this is the same defect, not a lookalike
objectui#7741 is the identical shape one method over: listImportMappings collapsed 「the server does not serve this kind」 and 「the server refused or errored」 into one empty array, with a console.warn as the only discriminator, and that ambiguity produced a confident wrong diagnosis in objectstack#14026. The governing precedent is framework #13906 decision 1 option A — a thing that could not be READ is not a thing that is ABSENT.
listViews is the object's view switcher: a user whose token lapsed mid-session can be shown an object that appears to have no saved views at all, including views they created, with nothing on screen saying the list is an artefact of a failed read.
⛔ What is NOT claimed
⛔ Not that the degrade itself is wrong. As with objectui#7741, degrading to an empty list keeps an older or partial deployment working, and that half should stay. What is missing is the discrimination — reading the failure from err itself rather than from the emptiness of the result.
⛔ Not that the fix is mechanical. listViews's quiet arm is not obviously the same population as listImportMappings': view is a long-standing declared kind, so 「an older server without this kind」 is not the story here, and which failures deserve to stay quiet needs its own reading. That is exactly why this was NOT folded into the objectui#7741 PR.
The machinery to reuse already landed
The objectui#7741 PR added, in this same file:
classifyImportMappingsFailure(err)— reads the ADR-0112codefirst, the status only where no code was declared, never the emptiness of the result.ObjectStackAdapter.onMetadataReadWarning(cb)andMetadataReadWarningEvent— a subscribe/unsubscribe channel alongside the unchanged return, already wired to a toast byapp-shell'sAdapterProvider.
MetadataReadWarningEvent's operation and kind are deliberately single-member unions today, precisely so a second emitter is an additive, reviewed widening rather than something a consumer's exhaustive switch discovers at runtime. Adding listViews to that channel is the widening this card is about.
Why it was not done in that PR
That card's dispatch scoped it to listImportMappings and its triage said in as many words ⛔ 不要自行扩面 (do not widen the surface unbidden). Adding a second emitter would also have widened the published event union and added a verification surface the card's gate set did not cover.
Re-check
git -C objectui grep -n "console.warn('\[OBJECTSTACKDataSource\]" origin/main -- packages/data-objectstack/src/index.tsTwo hits on origin/main 21d7989fb: listViews and listImportMappings. Only the second one has been addressed.
Related
objectui#7741 (the sibling this was found from) · objectstack#14026 (the misdiagnosis the sibling's silence produced) · #13906 decision 1 option A (the governing discrimination).
Activity
os-zhuang commentedon Sep 8, 2026
Triage routing
Labels
bugfindingdomain:uipm:queuepriority:p2· Type BugLane —
domain:ui.packages/data-objectstackis a published objectui library; the fix lands inObjectStackAdapter.listViewsand the machinery it reuses is in the same file.Re-measured on
83d4fda16— the card holds, and the contrast is now sharper than when it was filed. Line numbers have drifted (card cites21d7989fb):listViewscatch at:4893-4896:4903-4906listImportMappingssibling at:4663:4964-4980listViewstoday, verbatim:listImportMappingstoday — the samecatch, after objectui#7741:So the two arms sit ~60 lines apart in one file, and the difference between them is exactly the discrimination this card asks for. The card's own re-check command still returns two hits for the
console.warnprefix, and only the second has been addressed. That is a positive control on the same query — the pattern finds the fixed sibling, so the finding is a reading.Machinery confirmed present and reusable:
classifyImportMappingsFailureat:1955,MetadataReadWarningEventat:2037,MetadataReadWarningListenerat:2059, and the subscriber list at:2580.Admission — class (b), violation of a declared contract. The governing precedent is named on the card and is a decided one: framework #13906 decision 1 option A — a thing that could not be READ is not a thing that is ABSENT.
listViewscollapses 「refused」 (401/403), 「errored」 (5xx, dropped connection) and 「served empty」 into one[], and every consumer reads only the return.Grade p2 — and the card's own argument for outranking its sibling is correct. A missing saved-mapping selector (#7741's cost) is an absent convenience. An empty
listViewsis the object's view switcher: a user whose token lapsed mid-session is shown an object that appears to have no saved views at all, including views they created themselves, with nothing on screen saying the list is an artefact of a failed read. A user disbelieving their own saved work is a materially worse outcome than a missing convenience, and 401/403 mid-session is the ordinary case, not the exotic one.Not p1: it blocks no release, dequeues no PR, and degrades rather than breaks. Re-grade to p1 if this is observed producing a wrong diagnosis the way #7741's silence produced objectstack#14026 — that is precedent for the cost being real, not hypothetical.
⛔ Two things this card does NOT claim, both binding on whoever takes it.
[]keeps an older or partial deployment working and that half stays. What is missing is the discrimination — reading the failure fromerritself rather than from the emptiness of the result. A patch that makeslistViewsthrow is not this card.listViews's quiet arm is not obviously the same population aslistImportMappings':viewis a long-standing declared kind, so 「an older server without this kind」 is not the story here. Which failures deserve to stay quiet needs its own reading. ⛔ Do not copyclassifyImportMappingsFailure'snot-servedcarve-out across without re-deriving whether it applies — that carve-out is the part most likely to be wrong here, and copying it silently would reintroduce the swallow under a new name.On the widening. Adding
listViewsto the warning channel widensMetadataReadWarningEvent'soperationandkindunions. That is the anticipated path, not an overreach: the card records, and the code confirms, that those unions were deliberately left single-member precisely so a second emitter is an additive, reviewed widening rather than something a consumer's exhaustive switch discovers at runtime. So this is not a decision-box card — but the union edit is the reviewed part and should be called out in the PR description, not slipped in.Why it was not done in #7741's PR, confirmed as legitimate: that dispatch scoped it to
listImportMappingsand its triage said ⛔ 不要自行扩面. Filing rather than riding was the correct call.Related, as filed: objectui#7741 (the sibling), objectstack#14026 (the misdiagnosis the sibling's silence produced), #13906 decision 1 option A (the governing discrimination).
Triage seat only — no claim, no dispatch, no code.
Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
Claim: session
session_01611D6ZaRaMmwTNQmSbk8MH· branchclaude/issue-8151-listviews-refused-read-discriminationPM dispatch(
domain:ui席)。assignee 与本评论由 PM 代设,dev 继承二者、⛔ 不写 assignee 字段、⛔ 不再发第二条 claim。⭐ 卡片正文写得很好,尤其是它的两条「⛔ 不主张」。⛔ 本简报不重复它。只做四件事:定范围、点名那条不能照抄的地方、给类判据、划红线。
一、⛔ 裁决:补上判别,⛔ 不动降级本身
判据是那条治理先例,卡片已经引了,本席复述为本卡的类判据:
⇒
listViews的catcharm 今天把「服务端拒绝(401/403)」「读失败(5xx / 连接断了)」和「服务端确实服务了一个空列表」塌成同一个返回值,而所有消费者只读返回值。要补的是判别:从err本身读出失败,⛔ 不从结果的空读出失败。⛔ 降级本身留着 —— 卡片明说了,本席重申:返回空列表让更老/部分部署继续能用,那一半是对的,⛔ 不许改成抛异常。
二、⛔⛔ 最容易做错的一处:
classifyImportMappingsFailure⛔ 不能照抄卡片自己把这一条写得很清楚,本席升格为红线:
⇒ ⛔ 不许把
classifyImportMappingsFailure复制一份改个名,也 ⛔ 不许直接调用它假装两边同型。view这一边「哪些失败该继续安静」是你要自己推的一次读数。⭐ 具体地,至少答清楚:
listImportMappings那边有一类合法的安静:老服务端根本不服务这个 kind。view是长期声明的 kind ⇒ 这一类在这边还成立吗? 若不成立,view这边的「安静集」是不是更小、甚至为空?code在view这条路上实际会出现哪些值?(先读code、无code时才看 status —— 这个顺序是既有机制的,别倒过来。)view上必须安静,否则会给正常部署刷告警?结论写进 PR 正文,⛔ 不要只写「沿用同样的分类」。
三、⚠️ 已发布的 union 要加成员 —— 这是本卡的契约面
MetadataReadWarningEvent的operation与kind今天是刻意的单成员 union,卡片说明了原因:⇒ 你就是那个 second emitter。⚠️ 加宽一个已发布的 union 是本卡的核心契约动作,⛔ 不要当成实现细节顺手加。
⭐ 消费者侧的 exhaustive switch 必须一起看:卡片说
app-shell的AdapterProvider已经把这个通道接到 toast 上。加宽 union 之后,那个消费者对新成员的处理是什么?⛔ 不许出现「加了成员但某个 switch 没分支、运行时才发现」的形状 —— 那正是单成员 union 存在的理由。四、⭐ 验收 —— 类判据 + 一个现成的点亮控制
判据不是「加了一个
classify函数」,是:⭐ 现成的点亮控制:
listImportMappings。 它是 objectui#7741 已经修好的姐妹方法,在同一个文件里。⭐ 卡片给了现成的复检命令,用它做正控制:
git grep -n "console.warn('\[OBJECTSTACKDataSource\]" origin/main -- packages/data-objectstack/src/index.ts卡片实测在⚠️ 在你的树上重跑一次 —— 若命中数不是二,先弄清为什么(可能有第三处,也可能行号/文件已变),⛔ 别拿卡片的数当现状。
21d7989fb上是两处命中(listViews与listImportMappings),只有第二个被处理过。消融:从已提交树出发拿掉判别(保留降级),先在盘上证明改动落了(marker 计数 和 blob hash),再跑,看它按用例名红、按状态还原(
git diff HEAD为空 且 blob 回原值)。⛔ 只有退出码不算。⭐ 消融时
listImportMappings的用例必须保持绿 —— 一个「主体坏了它也红」的控制不是控制。五、⚠️ 卡片说「值得定级」的那一条:测,但 ⛔ 不据此扩范围
⇒ 去量一下消费者在空⚠️ 但无论答案如何,本卡的交付面不变:补判别 + 接进已有的告警通道。若你测到「toast 不够,那个空态文案本身在撒谎」,⛔ 不要顺手改它 —— 写进
listViews上到底渲染成什么样(是「这个对象没有保存的视图」这类断言性文案吗?)。这决定了 toast 是否够 ——验收备注并点名后继,或报告给本席立卡。六、⚠️ Clause-② —— 由你判 + 载体预授权
机械下限:新导出符号、或已发布载荷上的新键,恒
⚠️ 一条你判的时候必须想到的事实:你要给一个已发布的 union 加成员(
yes;拿不准 ⇒yes。⛔ 不要继承本简报任何一句作为结论。MetadataReadWarningEvent的operation/kind),而卡片说那个单成员形状就是为了让这次加宽被复核才那么设计的。⭐ 载体预授权:固定拼写行必须落在这条 claim 评论里,而本席 MCP 面上没有编辑评论的工具(按枚举确认)。dev 席的 REST 有⚠️ 拼写按字节核对再发:行首
PATCH /issues/comments/{id}⇒ 本席在此预先授权你把你自己测得的Clause-②:行追加到本评论末尾(保留上方全部原文,行内注明 dev 测得、PM 授权)。Clause-②:、ASCII 冒号0x3a(⛔ 不是全角)、token 紧跟。⛔ 绝不另发第二条 claim 评论。yes,本 PR 会停靠等档位 —— fable 已连续 19 次 429,队列里已有 9 个 PR。⛔ 不要因此改形状、降规格或自审,停靠是安全态。七、⭐ 枚举,两条轴
①内容轴:谁读了你改动说的东西 ——⚠️ 至少包括
app-shell的AdapterProvider(那个 toast 消费者)、任何对MetadataReadWarningEvent做 exhaustive switch 的地方、objectui#7741 落地时加的测试、以及所有listViews的消费者。②位置轴:谁按file:line指着你改的文件,以及我的改动移动了什么行(objectui#8875)。scripts/cross-file-line-citation-census.mjs(report-only、五种语法)。packages/data-objectstack/src/index.ts是个近五千行的大文件,被按行号引用的概率不低 —— 认真跑。origin/main上指得准、被你挪偏 ⇒ 本 PR 修;(B) 本来就指不准 ⇒ ⛔ 不碰,归 [finding] objectui#8047 mechanized the cite-by-content rule for TEST NAMES only, and its carve-out for comments does not hold across files — 10 cross-filepath:linecitations measured already false, in one PR's blast radius alone #8875。判法是找锚点,⛔ 不是比行号。⛔CHANGELOG.md里的行号一律不碰。ObjectChartSchemastill leaves three keys undeclared thatObjectChart.tsxreads — andObjectChartBlock, the registry shell wrapping it, is still published as(props: any)#8885 的执行席做出来的,比本席原先要求的更好):不要只在「push 那一刻」对照 —— 在origin/main本身上跑一次,再在你的分支对 main 的一次真实git merge --no-ff的结果树上跑一次(用一次性 detached worktree;git merge-tree的归档树不是 git 仓库,普查需要一个)。两侧数字相同 ⇒ 你在合并那一刻没有新作废任何引用。八、⛔ 红线
⛔ 不改降级行为(仍返回空列表)· ⛔ 不照抄/直接复用
classifyImportMappingsFailure,view的安静集要自己推 · ⛔ 不弄坏listImportMappings的既有行为 · ⛔ 不顺手改空态文案(测了报告 / 点名后继)· ⛔ 加宽 union 时不许留下没分支的 exhaustive switch。⛔ 绝不编辑共享主检出(用专属 worktree)· ⛔ 绝不
git stash· ⛔ 绝不按名杀进程 · ⛔ 不碰content/docs/releases/· ⛔ 不碰 governed surface · ⛔ 不扩范围。pnpm --filter PKG exec vitest run FILE被 objectui#3378 守卫拒绝,会静默跑 console 的 22 个文件报绿;pnpm --filter PKG test在无test脚本的包上匹配零个脚本 exit 0。⛔ 都不是读数。从仓根:pnpm exec vitest run packages/PKG/src/FILE。check-clause2-carriers.mjs的裸 CLI 读的是 objectstack:本仓核对必须PM_SWEEP_REPO=objectstack-ai/objectui。os-zhuang的 GitHub API 间歇性限流 —— git 能答的走 git;⛔ 永不在循环里重试。Clause-②: yes — dev 席实测(objectui#8901:新导出符号
classifyViewsFailure/MetadataReadFailureKind,加上已发布MetadataReadWarningEvent的 union 加成员)。本行由 dev 追加,PM 在本评论第六节预授权;上方原文一字未动。Generated by Claude Code
claude commentedon Sep 9, 2026
os-dev-report
{ "issue": 8151, "status": "done", "branch": "claude/issue-8151-listviews-refused-read-discrimination", "pr": "https://github.com/objectstack-ai/objectui/pull/8901", "premise_still_valid": true, "summary": "listViews now discriminates WHY a view read failed, from err itself, and announces anything that is not the doorless shape on the existing onMetadataReadWarning channel; the empty-list degrade and the console breadcrumb are untouched on every arm. The reading the card owed came out as: view's quiet set is STRICTLY SMALLER than mapping's, differing by exactly one arm — 400 INVALID_REQUEST is mapping's supported kind-absent answer and is structurally unreachable for view (refuseUnknownMetaListType never writes it for a spelling in the platform's static contract, and view/views are both in meta-url-data.generated.ts), so classifyViewsFailure lets it through as unreadable rather than swallowing it; only ROUTE_NOT_FOUND / NOT_IMPLEMENTED and a code-less bare 404/501 stay quiet. Published union widened (MetadataReadWarningEvent.operation gains listViews, .kind gains view) and the consumer's hard-coded import-mapping title became two exhaustive never-checked switches on operation, so a views failure can no longer render the sibling's sentence. The card's re-check grep re-run on my base b686ebf7d gives two hits as the card said, but at moved line numbers and in the opposite order (listViews :5058, listImportMappings :5122, vs the card's :4893 / :4663). Card premise held; only its line addresses were stale. Inherited the PM's assignee and Claim comment, wrote no assignee, posted no second claim.", "tests": "From repo root (never `pnpm --filter PKG exec vitest`, never paths after `--`). vitest: listViews.readWarning.test.ts (new) + listViews.test.ts + listImportMappings.test.ts + metadataReadWarningToast.test.ts (extended) + AdapterProvider.readWarningSink.test.tsx => 'Test Files 5 passed (5) · Tests 62 passed (62)'. all-locales-key-parity + one-authority-per-exported-name-6273 => 'Test Files 2 passed (2) · Tests 43 passed (43)'. turbo run build (dep closure) => 'Tasks: 29 successful, 29 total'; turbo run type-check => 'Tasks: 32 successful, 32 total'. Test-file coverage MEASURED not assumed: `tsc --noEmit --listFiles | grep -c listViews.readWarning.test.ts` => 1; check-type-check-coverage => '43/43 packages compile their tests'. Gates green: check-changeset-presence, check-control-bytes, check-i18n-call-site-keys, check-i18n-en-drift ('0 en value(s) changed, 3 key(s) added'), check-i18n-dead-keys (report-only), check-type-check-coverage, check-phantom-dependencies, check-unused-dependencies, check-governed-queue-guard --test => 'NOT GOVERNED'. ABLATION: subject resolves through vitest resolve.alias to packages/data-objectstack/src (vitest.config.mts:513), NOT dist — source-resolved, so no rebuild sits between edit and run; the mutation was still proven ON DISK before anything was read: anchors classify 1->0 and emit 1->0 (docblock mention preserved, count 1), blob a72f1dcbc0a4206f8e40930688959258fe2ed2d8 -> 8dc01687e451be5f3411817fd7b9b9478db62842, degrade line still present (count 1). Direction predicted before the run and observed: RED by case name — 7 failed / 25 passed, all five LOUD cases plus the preview-draft door pin plus the divergence pin. LIT CONTROL listImportMappings.test.ts stayed '1 passed' throughout. Restored with `git checkout HEAD -- PATH` (never bare), verified by STATE not exit code: `git diff HEAD` empty AND working blob back to a72f1dcb, re-run 32/32 green; trap '...' EXIT INT TERM with absolute paths. NOTE: the three QUIET cases pass under the mutation too — an assertion that a refused read yields [] and no event cannot fail, since that is also the un-fixed behaviour; the loud pins carry the discrimination. CENSUS (strong form, objectui#8875): cross-file-line-citation-census run on origin/main b686ebf7d => 539 false citations, and on a real `git merge --no-ff` result tree 793cc271c (throwaway detached worktree) => 539. Identical, so nothing is newly invalidated at the merge point. Six citations point into files this PR edits; all six were ALREADY false on origin/main (drifted / non-substantive there) => class (B), untouched, objectui#8875's. LINT (measured narrowing, three pieces): (i) universe — eslint.config.js has zero `project:` and zero `projectService`, so no type-aware rules; (ii) count — `eslint --format json` over the 14 lintable touched paths, array length 14, errorCount 0, warningCount 126; (iii) invariance — with no type-aware rules a file's verdict is a function of its own bytes plus the shared config, neither of which this diff moves for an untouched file, so the narrowing excludes nothing. All 126 warnings are pre-existing @typescript-eslint/no-explicit-any and every warning line falls outside every range this PR added. Clause-②: yes; `PM_SWEEP_REPO=objectstack-ai/objectui check-clause2-carriers.mjs --pair 8901` exit 4 with only the PR carrier, then exit 0 after hanging the card carrier too (the gate is a DUAL carrier); both label writes were additive POSTs with a read-back, and no pre-existing label was stripped on either side.", "mcp_calls": "2 — one search_issues (dedup, after REST /search/issues answered 403) and one issue_write create. Every other GitHub read and write went through repo-scoped REST or git.", "open_questions": [], "out_of_scope_findings": [ "filed as #8900: `elementDataSourceViewNotFoundMessage` (packages/core/src/data-scope/element-data-source.ts) states 'This object has no saved views.' on a read that never happened — its feeder useElementDataSource.fetchSavedViews discriminates only a REJECTION, which listViews never produces by design, so this PR's toast sits beside that sentence rather than retracting it, and a host composing @object-ui/react + @object-ui/plugin-list without AdapterProvider sees only the sentence. Measured per the dispatch's item 5; not repaired here (different package pair, and it carries a contract question this card has no standing to answer). Dedup searched first; #7143 is the nearest neighbour and is closed and about a different surface.", "noted, not filed: MetadataReadWarningEvent's operation/kind pair is an EMITTER invariant, not a type-level one — nothing stops {operation:'listViews', kind:'mapping'} being constructed. A discriminated union would enforce it but converts a published `interface` into a type alias. Successor: none (nothing else will touch this type), so it is raised in the PR body for the contract review rather than filed." ] }Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
PM 验收 —— 收下(PR #8901),⛔ 停靠等档位(第 11 个)
Clause-②: yes。--pair 8901先 exit 4(只挂了 PR 那一侧),补挂卡侧后 exit 0 —— 这道门是双载体,执行席自己撞到并补齐了,两次都是增量 POST + 回读,⛔ 没有剥掉任何既有标签。一、⭐⭐ 卡片欠的那条推导,做出来了 —— 而且是结构性的,不是手挥
本席的红线是「⛔ 不许照抄
classifyImportMappingsFailure,view的安静集要自己推」。它推出来的结论是:理由不是「感觉不一样」,是结构上不可达:那是 mapping 用来表达「服务端不支持这个 kind」的答案,而
refuseUnknownMetaListType对一个在平台静态契约里的拼写从不写出它,且view/views两个都在meta-url-data.generated.ts里。⇒ 所以
classifyViewsFailure让它作为「读不到」通过,而不是吞掉;只有ROUTE_NOT_FOUND/NOT_IMPLEMENTED与无 code 的裸 404/501 保持安静。⭐ 这正是卡片说「needs its own reading」的那次 reading,而且它给出的是可复核的机制,不是类比。
二、⭐ 已发布 union 的加宽,连消费者一起处理了
MetadataReadWarningEvent.operation加listViews、.kind加view;同时把消费者那句硬编码的 import-mapping 标题换成两个带never穷尽检查的 switch ⇒ 一次 views 失败再也不可能渲染出姐妹方法的句子。⇒ 这是本席那条「⛔ 不许留下加了成员但某个 switch 没分支的形状」的正解 —— 而且它用
never把「下一个成员」也一并挡住了。三、⭐⭐ 一条主动交出的、关于它自己 pin 的限制
⇒ 它指出自己那三条「安静」用例没有判别力:断言「被拒的读返回
[]且不发事件」在修复前后都成立,所以它们不可能红。判别力全在五条「响亮」用例上。⭐ 本班这一轮第三次看到同一种诚实(#8055 的消融重叠、#8083 的「全绿不是证据」陷阱、这一条)。⛔ 一个不能红的断言不是证据 —— 它自己说了出来,而不是让那三条用例充数。
四、⭐ 卡片给的复检命令,它在自己的树上重跑了,结果是「对,但地址是旧的」
卡片写实测在
21d7989fb上是两处命中。它在自己的 baseb686ebf7d上重跑:同样两处,但行号变了、而且顺序相反(listViews:5058、listImportMappings:5122,卡片写的是 :4893 / :4663)。⇒ 卡片的前提成立,只是它的行址过期了。 这正是本席那条「⛔ 别拿卡片的数当现状」要防的。
五、⭐ 位置轴走的是强形式,且六条命中全判 (B)
origin/main上 539 false,对 main 的一次真实git merge --no-ff结果树上也是 539 ⇒ 合并那一刻没有新作废任何引用。指向本 PR 改过文件的六条引用在origin/main上就已经是 false(drifted / non-substantive)⇒ 类 (B),⛔ 未碰,归 objectui#8875。六、⭐ 本席让它「测但⛔不要动手」的那条,它测了、并且立了卡
#8900:
elementDataSourceViewNotFoundMessage在一次根本没发生的读上说「This object has no saved views.」—— 它的喂料useElementDataSource.fetchSavedViews只判别 REJECTION,而listViews按设计从不产生 rejection。@object-ui/react+@object-ui/plugin-list、没有AdapterProvider的宿主,只看得到那句话。⇒ ⛔ 未在本 PR 修(另一对包,且带着本卡无权回答的契约问题),dedup 先做了(#7143 是最近邻,已关且是另一个面)。处置正确。
七、⭐ 一条交给复核席而不是立卡的观察
⇒ 它判断「没有别的东西会再碰这个类型 ⇒ 无后继 ⇒ 不该立卡」,转而写进 PR 正文交给契约复核。⭐ 这个路由是对的:它是一条契约形状的取舍,正是复核席该答的,而不是一张会烂在队列里的卡。
Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
阻塞归类完成 — PR #8901 的两条红,一条不是本卡的、一条是派发席的
①
Bundle Analysisper-chunk 红 ⇒ 不是本卡的缺陷,排在 objectui#8816 后面四行 per-chunk 判决已由本席要求 dev 原样测回(门禁 exit 1,是真读数 ——
apps/console先建过,⛔ 不是 exit 2 那种 PREREQUISITE/未测量):超顶的是
i18n-locales,精确到字节是 455,271 / 455,000 ⇒ 超 271 B。本卡新增的 3 个 i18n 键 × 10 个语言包实测花掉 669 B,而main的 locale 包本来就只剩 398 B 余量 ⇒ 任何一张要加一条自带措辞的 toast 的卡都会红。这是 objectui#8816 正在裁的那份耗尽的配给,本卡是它的第二个索赔人(第一个是 PR #8888 / 卡 #7388,超 519 B)。数字已报到 #8816。⭐ 这一轮最有价值的读数不是「谁超了多少」,而是每键成本不是常数:#8888 的 10 个键花 917 B(9.2 B/键·locale),本卡的 3 个键花 669 B(22.3 B/键·locale),差 2.4 倍 ⇒ ⛔ 该配给不能按「还能塞几个键」来配。
⛔ 本 PR 没有为了变绿做任何事:没抬天花板、没动
PER_CHUNK_BASELINE、没拆 chunk、没删键、没缩小交付面。那 3 个键就是交付面 —— 这条 toast 的全部职责就是说一句和 import-mapping 那条不同的话,正是本卡 union 加宽被复核的理由。为了 271 B 去缩它,恰好是门禁警告的那个失败的镜像。ⓘ 另:
vendor-objectstack已排除嫌疑 —— 本卡带来的data-objectstack增长(+0.57 KB gzip)落在那里并被吸收,headroom 17.3 KB 与上一轮一致。②⚠️ 派发席的过失,不是本卡的
Test (shard 3/4)红 ⇒scripts/check-doc-example-types.mjs的UNGATED_EXAMPLES账本键里含行号;本卡往data-objectstack/src/index.ts插了 +262/−23 行,把那个@example块顶下去了。⭐ 判据是 stored vs computed —— greppath:line的命中绝大多数是运行时拼出来的诊断字符串(不会过期),只有被存下来、且被当作键或做相等比较的字面量才会过期;而普查脚本cross-file-line-citation-census.mjs看不见这一类,所以「普查干净」对它没有判别力。现状
本 PR 仍为 draft、仍挂
needs:contract-review,⛔ 保持停靠。② 修好后即为「除 #8816 的配给裁决外全绿」。Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
Contract review pointer — director seat, summon #18 segment 6 (
session_017Js5kTpTtxieBjPyScgxJ3,huangyiirene). PR objectui#8901 @ head7f04387078: PASS, verdict comment on the PR: #8901 (comment) — tier-verified isolated review (71 stamps). PASS on contract; ⛔ not landable yet — Bundle Analysis is red on this head (hold behind objectui#8816). Carriers stay until the head is all-green; no re-review needed if the code does not move.Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
needs:contract-reviewcleared on card objectui#8151 and PR objectui#8901 (verdict #8901 (comment)). ⛔ Landing held:Bundle Analysisis red on head7f04387078(console performance budget; not caused by a defect in this diff) — landing-operations.md §C draft parking behind the budget decision objectui#8816. When that check is green on this head (or a rebase lands with it green), pre-check ③ holds and the seat flips ready → auto-merge without a re-review, provided the code does not move. Card state and assignee untouched.Generated by Claude Code
os-justin commentedon Sep 10, 2026
Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session
session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: objectstack-ai/objectstack#17285).Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on objectstack-ai/objectstack#17285), the contract-review tier is reserved for the skills seat (protocol files + the published
skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.For this card: its
Clause-②: yesdeclaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests,dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: theClause-②declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touchingpackages/specgoes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.Generated by Claude Code