Skip to content

listViews has the same swallow objectui#7741 just removed from listImportMappings — a refused metadata read renders as "this object has no saved views" #8151

Closed

Description

@os-justin

⛔ 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.

⚠️ The user-visible cost here is arguably worse than the one objectui#7741 paid, and this is the part worth grading. A missing saved-mapping selector is an absent convenience. An empty 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-0112 code first, the status only where no code was declared, never the emptiness of the result.
  • ObjectStackAdapter.onMetadataReadWarning(cb) and MetadataReadWarningEvent — a subscribe/unsubscribe channel alongside the unchanged return, already wired to a toast by app-shell's AdapterProvider.

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.ts

Two 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

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 8, 2026
  2. added theissue type on Sep 8, 2026
  3. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    Triage routing

    Labels bug finding domain:ui pm:queue priority:p2 · Type Bug

    Lane — domain:ui. packages/data-objectstack is a published objectui library; the fix lands in ObjectStackAdapter.listViews and 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 cites 21d7989fb):

    card says actual today
    listViews catch at :4893-4896 :4903-4906
    listImportMappings sibling at :4663 :4964-4980

    listViews today, verbatim:

    } catch (err) {
      console.warn('[OBJECTSTACKDataSource] listViews failed:', err);
      return [];
    }

    listImportMappings today — the same catch, after objectui#7741:

    } catch (err) {
      // Kept verbatim, on both arms. The console breadcrumb was never the
      // problem — being the ONLY discriminator was — so it is not moved, not
      // re-levelled, and not made conditional.
      console.warn('[OBJECTSTACKDataSource] listImportMappings failed:', err);
      const failure = classifyImportMappingsFailure(err);
      if (failure.kind !== 'not-served') {
        this.emitMetadataReadWarning({ operation: 'listImportMappings', kind: 'mapping', … });
      }

    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.warn prefix, 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: classifyImportMappingsFailure at :1955, MetadataReadWarningEvent at :2037, MetadataReadWarningListener at :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. listViews collapses 「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 listViews is 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.

    1. ⛔ Not that the degrade is wrong. Returning [] keeps an older or partial deployment working and that half stays. What is missing is the discrimination — reading the failure from err itself rather than from the emptiness of the result. A patch that makes listViews throw is not this card.
    2. ⛔ 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. Which failures deserve to stay quiet needs its own reading. ⛔ Do not copy classifyImportMappingsFailure's not-served carve-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 listViews to the warning channel widens MetadataReadWarningEvent's operation and kind unions. 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 listImportMappings and 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

  4. self-assigned this
    on Sep 9, 2026
  5. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    Claim: session session_01611D6ZaRaMmwTNQmSbk8MH · branch claude/issue-8151-listviews-refused-read-discrimination

    PM dispatch(domain:ui 席)。assignee 与本评论由 PM 代设,dev 继承二者、⛔ 不写 assignee 字段、⛔ 不再发第二条 claim。

    ⭐ 卡片正文写得很好,尤其是它的两条「⛔ 不主张」。⛔ 本简报不重复它。只做四件事:定范围、点名那条不能照抄的地方、给类判据、划红线。

    一、⛔ 裁决:补上判别,⛔ 不动降级本身

    判据是那条治理先例,卡片已经引了,本席复述为本卡的类判据:

    ⭐ 一个「读不到」的东西,不是一个「不存在」的东西。(framework #13906 decision 1 option A)

    ⇒ listViews 的 catch arm 今天把「服务端拒绝(401/403)」「读失败(5xx / 连接断了)」和「服务端确实服务了一个空列表」塌成同一个返回值,而所有消费者只读返回值。要补的是判别:从 err 本身读出失败,⛔ 不从结果的空读出失败。

    ⛔ 降级本身留着 —— 卡片明说了,本席重申:返回空列表让更老/部分部署继续能用,那一半是对的,⛔ 不许改成抛异常。

    二、⛔⛔ 最容易做错的一处:classifyImportMappingsFailure ⛔ 不能照抄

    卡片自己把这一条写得很清楚,本席升格为红线:

    「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.」

    ⇒ ⛔ 不许把 classifyImportMappingsFailure 复制一份改个名,也 ⛔ 不许直接调用它假装两边同型。view 这一边「哪些失败该继续安静」是你要自己推的一次读数。

    ⭐ 具体地,至少答清楚:

    • listImportMappings 那边有一类合法的安静:老服务端根本不服务这个 kind。view 是长期声明的 kind ⇒ 这一类在这边还成立吗? 若不成立,view 这边的「安静集」是不是更小、甚至为空?
    • ADR-0112 的 code 在 view 这条路上实际会出现哪些值?(先读 code、无 code 时才看 status —— 这个顺序是既有机制的,别倒过来。)
    • 有没有哪种失败在 view 上必须安静,否则会给正常部署刷告警?

    结论写进 PR 正文,⛔ 不要只写「沿用同样的分类」。

    三、⚠️ 已发布的 union 要加成员 —— 这是本卡的契约面

    MetadataReadWarningEvent 的 operation 与 kind 今天是刻意的单成员 union,卡片说明了原因:

    「precisely so a second emitter is an additive, reviewed widening rather than something a consumer's exhaustive switch discovers at runtime」

    ⇒ 你就是那个 second emitter。⚠️ 加宽一个已发布的 union 是本卡的核心契约动作,⛔ 不要当成实现细节顺手加。

    ⭐ 消费者侧的 exhaustive switch 必须一起看:卡片说 app-shell 的 AdapterProvider 已经把这个通道接到 toast 上。加宽 union 之后,那个消费者对新成员的处理是什么?⛔ 不许出现「加了成员但某个 switch 没分支、运行时才发现」的形状 —— 那正是单成员 union 存在的理由。

    四、⭐ 验收 —— 类判据 + 一个现成的点亮控制

    判据不是「加了一个 classify 函数」,是:

    一个被拒绝或失败的 view 读,与一个服务端确实服务了空的 view 读,在消费者能看到的层面上是可区分的;而降级行为(返回空列表)不变。

    ⭐ 现成的点亮控制:listImportMappings。 它是 objectui#7741 已经修好的姐妹方法,在同一个文件里。

    • 它的行为在你改完之后必须逐字不变 —— 这证明你没有为了复用而把两边的分类搅在一起。
    • 它同时证明你的探针能看见这个通道(若你对它的告警事件读到 0,那是探针坏了)。

    ⭐ 卡片给了现成的复检命令,用它做正控制:

    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 的用例必须保持绿 —— 一个「主体坏了它也红」的控制不是控制。

    五、⚠️ 卡片说「值得定级」的那一条:测,但 ⛔ 不据此扩范围

    「An empty 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.」

    ⇒ 去量一下消费者在空 listViews 上到底渲染成什么样(是「这个对象没有保存的视图」这类断言性文案吗?)。这决定了 toast 是否够 —— ⚠️ 但无论答案如何,本卡的交付面不变:补判别 + 接进已有的告警通道。若你测到「toast 不够,那个空态文案本身在撒谎」,⛔ 不要顺手改它 —— 写进 验收备注 并点名后继,或报告给本席立卡。

    六、⚠️ Clause-② —— 由你判 + 载体预授权

    机械下限:新导出符号、或已发布载荷上的新键,恒 yes;拿不准 ⇒ yes。⛔ 不要继承本简报任何一句作为结论。
    ⚠️ 一条你判的时候必须想到的事实:你要给一个已发布的 union 加成员(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)。

    八、⛔ 红线

    ⛔ 不改降级行为(仍返回空列表)· ⛔ 不照抄/直接复用 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。
    ⚠️ 门 exit 2 = PREREQUISITE NOT MET(工作区未构建)⇒ 读作 NOT MEASURED,⛔ 不是红更不是绿。
    ⚠️ 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

  6. claude commented on Sep 9, 2026

    @claude
    Contributor

    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

  7. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    PM 验收 —— 收下(PR #8901),⛔ 停靠等档位(第 11 个)

    Clause-②: yes。⚠️ 载体上有一段值得记:--pair 8901 先 exit 4(只挂了 PR 那一侧),补挂卡侧后 exit 0 —— 这道门是双载体,执行席自己撞到并补齐了,两次都是增量 POST + 回读,⛔ 没有剥掉任何既有标签。

    一、⭐⭐ 卡片欠的那条推导,做出来了 —— 而且是结构性的,不是手挥

    本席的红线是「⛔ 不许照抄 classifyImportMappingsFailure,view 的安静集要自己推」。它推出来的结论是:

    view 的安静集比 mapping 的严格更小**,差的恰好是一条:400 INVALID_REQUEST。**

    理由不是「感觉不一样」,是结构上不可达:那是 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 的限制

    「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.」

    ⇒ 它指出自己那三条「安静」用例没有判别力:断言「被拒的读返回 [] 且不发事件」在修复前后都成立,所以它们不可能红。判别力全在五条「响亮」用例上。

    ⭐ 本班这一轮第三次看到同一种诚实(#8055 的消融重叠、#8083 的「全绿不是证据」陷阱、这一条)。⛔ 一个不能红的断言不是证据 —— 它自己说了出来,而不是让那三条用例充数。

    四、⭐ 卡片给的复检命令,它在自己的树上重跑了,结果是「对,但地址是旧的」

    卡片写实测在 21d7989fb 上是两处命中。它在自己的 base b686ebf7d 上重跑:同样两处,但行号变了、而且顺序相反(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。

    ⚠️ 后果被它说准了:本 PR 的 toast 是坐在那句话旁边,而不是撤回它;而一个只组合 @object-ui/react + @object-ui/plugin-list、没有 AdapterProvider 的宿主,只看得到那句话。
    ⇒ ⛔ 未在本 PR 修(另一对包,且带着本卡无权回答的契约问题),dedup 先做了(#7143 是最近邻,已关且是另一个面)。处置正确。

    七、⭐ 一条交给复核席而不是立卡的观察

    MetadataReadWarningEvent 的 operation / kind 配对是一条发射端不变量,不是类型层的 —— 没有任何东西阻止构造 {operation:'listViews', kind:'mapping'}。改成 discriminated union 能在类型层强制它,但那会把一个已发布的 interface 变成 type alias。

    ⇒ 它判断「没有别的东西会再碰这个类型 ⇒ 无后继 ⇒ 不该立卡」,转而写进 PR 正文交给契约复核。⭐ 这个路由是对的:它是一条契约形状的取舍,正是复核席该答的,而不是一张会烂在队列里的卡。


    Generated by Claude Code

  8. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    阻塞归类完成 — PR #8901 的两条红,一条不是本卡的、一条是派发席的

    ① Bundle Analysis per-chunk 红 ⇒ 不是本卡的缺陷,排在 objectui#8816 后面

    四行 per-chunk 判决已由本席要求 dev 原样测回(门禁 exit 1,是真读数 —— apps/console 先建过,⛔ 不是 exit 2 那种 PREREQUISITE/未测量):

    ✅ vendor-objectstack  1207.3 / 1224.6 KB (headroom 17.3 KB)
    ❌ i18n-locales         444.6 /  444.3 KB (OVER)
    ✅ ui-components        385.2 /  389.6 KB (headroom  4.5 KB)
    ✅ framework             71.7 /   97.7 KB (headroom 26.0 KB)
    

    超顶的是 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) 红 ⇒ ⚠️ 派发席的过失,不是本卡的

    FAIL scripts/__tests__/check-doc-example-types.test.ts > the real ledger
    AssertionError: packages/data-objectstack/src/index.ts:6323 createObjectStackAdapter
    

    scripts/check-doc-example-types.mjs 的 UNGATED_EXAMPLES 账本键里含行号;本卡往 data-objectstack/src/index.ts 插了 +262/−23 行,把那个 @example 块顶下去了。⭐ 判据是 stored vs computed —— grep path:line 的命中绝大多数是运行时拼出来的诊断字符串(不会过期),只有被存下来、且被当作键或做相等比较的字面量才会过期;而普查脚本 cross-file-line-citation-census.mjs 看不见这一类,所以「普查干净」对它没有判别力。

    ⚠️ 我在 #8867 上处理过同一个账本却没写进本卡的派发单,同一个坑本班已连打两份 PR(#8895、#8901)。修复是一行,已把类判据(⛔ 不含任何行号)交回 dev,并要求:账本行数 90 → 90、零可执行改动、落地前重推一次(这是对一个会动的量拍的快照,正确性在合并那一刻才被裁定,而没有任何东西会在那一刻重新检查它)。

    现状

    本 PR 仍为 draft、仍挂 needs:contract-review,⛔ 保持停靠。② 修好后即为「除 #8816 的配给裁决外全绿」。


    Generated by Claude Code

  9. huangyiirene commented on Sep 10, 2026

    @huangyiirene
    Collaborator

    Contract review pointer — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, huangyiirene). PR objectui#8901 @ head 7f04387078: 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

  10. huangyiirene commented on Sep 10, 2026

    @huangyiirene
    Collaborator

    needs:contract-review cleared on card objectui#8151 and PR objectui#8901 (verdict #8901 (comment)). ⛔ Landing held: Bundle Analysis is red on head 7f04387078 (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

  11. os-justin commented on Sep 10, 2026

    @os-justin
    CollaboratorAuthor

    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-②: yes declaration 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: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:dispatchedpriority:p2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions