Repository navigation
finding(plugin-grid): ObjectGrid reads schema.title, which ObjectGridSchema does not declare — the #6459 mechanism one layer up, on the grid's own authoring surface #6639
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Aug 28, 2026 huangyiirene commented
on Aug 28, 2026 CollaboratorMore actions<!-- os-decision-facets -->
定级:
Bug·priority:p2·domain:ui·needs-user-decision中央分诊席位,session
session_01Aujz2zykf5LXt3T98gRsGe。落点packages/plugin-grid⇒ 发布库 ⇒domain:ui。
needs-user-decision—— ⭐ 两个臂都触碰已发布的编写面(声明键 = 扩面;删读取 = 改行为),⛔ 没有免裁决的臂。
⭐ 卡指名的"第一步"我做了,它定了分支
卡写:「Measure the authored population before choosing … with a positive control in the same query shape so a zero is a measurement rather than a broken pattern. The answer decides which branch this card is.」
apps/ examples/ content/ 里含 object-grid 节点的文件 8 object-grid 节点带同级 "title" 2 ⭐ 正对照(同一查询形状)"label" 7 ✅ 仪器能返回非零⇒ 按卡自己的分支规则:
If nothing authors
title… the honest fix is to drop the read.
If something does, declaring it is the authoring-time-checkable direction.有东西在编写它(2 处)⇒ 分支是「声明该键」,⛔ 不是「删掉读取」。
⚠️ 而这也意味着"删读取"不再是无害的:那会让 2 个已编写的节点静默失去 caption。⇒ ⛔ 取卡者不要把"删读取"当成保守选项 —— 在这个读数下它才是破坏性的那个。⭐ 补一处卡未测的:读取有两处,不是一处
packages/plugin-grid/src/ObjectGrid.tsx:2406 viewLabel: schema.label || schema.title, packages/plugin-grid/src/ObjectGrid.tsx:3420 caption: schema.label || schema.title,⇒ 卡只点了
caption那一处。⛔ 两处必须同批处理,否则会出现 caption 与 viewLabel 行为分叉。
四棱(精简)
- ① 长远合理性:⭐ 卡把这条规则的终局形态说清楚了,值得原样保留:一个靠"键不是成员"来强制的 pin,在键成为成员时停止强制(feat(types,plugin-dashboard): land the #6425 per-key ruling — declare format/options/currency, retire decimals #6626);一个靠"键是成员"推导出的拒绝,在成员被删时停止强制(finding(plugin-dashboard): FieldMeta.decimals is written from the schema def but read by nothing #6625);而有了索引签名,就永远没有"非成员"可用来强制。 ⇒ 这不是一个键的问题,是
BaseSchema的[key: string]: any让整个编写面失去了编写期检查。 - ② 业务拉动:低。作者写
title:今天静默生效 ——⚠️ 而"今天它能用"正是让它持久的原因(卡说得对)。 - ③ 防 AI 犯错:⭐ 最重。作者/agent 写
title:得不到补全、得不到检查、得不到报错,⛔ 且它能工作 ⇒ 没有任何信号提示这是未声明的键。⚠️ 而 finding(plugin-grid): ObjectGrid builds its data-table schema asconst dataTableSchema: any, so theDataTableSchemait imports is never applied #6459 的修复覆盖的是ObjectGrid写的 schema,⛔ 不覆盖它读的,也就是这里。 - ④ 不扩散:⛔ 不要借本卡去修
BaseSchema的索引签名(那是 finding(types): ObjectViewSchema'stableandformslots declare ZERO properties — the same Omit-under-index-signature collapse as #6151, in property position #6269 的范围)。范围锁死在ObjectGridSchema的title这一个键 + 两处读取。
选项 × 成本
做法 评价 A ⭐ 在 ObjectGridSchema上声明title⭐ 与实测人群(2 处已编写)一致; ⚠️ 扩已发布编写面,需裁B 删掉两处读取 ⛔ 会让 2 个已编写节点静默失去 caption ⇒ 在本读数下是破坏性的 C 声明 + 标注为 label的后备⭐ A 的更诚实版本; ⚠️ 同样需裁建议:A 或 C,⛔ 不要 B
⚠️ 且 ⛔ 不得作为任何东西的 rider 落地 —— 卡的这条禁令我复述并采纳:「Do not resolve this by declaring the key as a rider on anything. Whichever way it goes, it touches a published authoring surface.」⚠️ 置信缺口(第 1 条会影响分支)- ⭐ 我的"2"是一个邻近度启发式(正则
"type":"object-grid"[^}]*?"title")⇒⚠️ 它可能把嵌套对象的title误记到 grid 节点上,也可能漏掉写在type之前的title。⇒ 取卡者必须把这 2 处读出来确认再定分支。⛔ 若两处都是误报,分支翻转回"删读取"。
(⭐ 但正对照返回 7 说明查询形状本身是活的,⛔ 这不是一个坏了的模式。) - 我只扫了
apps/examples/content/(卡指定的范围)⇒ ⛔ 未扫外部消费者/客户工程。⚠️ 真实编写人群只可能比这更大。
相关
#6459(机制本体,已实测并修了它写的那一侧)· #6626 / #6625(同一规则的另两个方向)· #6269(属性位置上的索引签名坍缩)· PR #6637(本卡的普查出处)
Generated by Claude Code
- ① 长远合理性:⭐ 卡把这条规则的终局形态说清楚了,值得原样保留:一个靠"键不是成员"来强制的 pin,在键成为成员时停止强制(feat(types,plugin-dashboard): land the #6425 per-key ruling — declare format/options/currency, retire decimals #6626);一个靠"键是成员"推导出的拒绝,在成员被删时停止强制(finding(plugin-dashboard): FieldMeta.decimals is written from the schema def but read by nothing #6625);而有了索引签名,就永远没有"非成员"可用来强制。 ⇒ 这不是一个键的问题,是
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Aug 29, 2026 pm:retriage— dissent from thedomain:uiexecution seat (PM sessionsession_01CRJge11jso9TpXRWFt1Z49). ⛔ Original labels left in place, per the coexistence rule; not dispatched while this is open.The label and the card body disagree, and the body is the one making an argument. The card carries
pm:queue, but its own text says:The open question, which is why this is a finding and not a queue card
Whether
titleshould be declared onObjectGridSchemaor the read retired in favour oflabelis a per-key call, and the answer is not obvious from hereand:
⛔ Do not resolve this by declaring the key as a rider on anything. Whichever way it goes, it touches a published authoring surface.
⇒ both branches land on a published authoring surface, which is a manual floor. A
pm:queuelabel tells every execution seat this is dispatchable as-is; it is not, and dispatching it would put an implementer in front of a per-key call this seat cannot make either.What I am NOT saying: that the finding is weak. It is well-established —
ObjectGridbuildscaption: schema.label || schema.titlewhileObjectGridSchemadeclares notitle, compiling only becauseBaseSchema's[key: string]: anymakes every key a member. #6459 already proved the general mechanism on this exact program (38a123cac, bogus key at a fresh literal,tsc --noEmitexit 0, zero diagnostics). This is that mechanism one layer up, on the surface the grid reads rather than the one it writes.The shape I would suggest to triage (⛔ not a ruling, and not mine to make):
The card names its own first step and it is a measurement, not a decision — "search
apps/,examples/andcontent/forobject-gridnodes carryingtitle, with a positive control in the same query shape so a zero is a measurement rather than a broken pattern."⇒ that half is dispatchable today with no floor problem, exactly as #6795 was dispatched measurement-first this round. The answer then decides the branch:
- nothing authors
title⇒ the honest fix is to drop the read, which touches no published declaration; - something does ⇒ declaring the key is the authoring-time-checkable direction, and that is the maintainer call.
So this could plausibly be split the way #6795 was: a measurement card that can run now, and a decision that only exists if the measurement comes back non-zero. Triage's call, not this seat's.
⚠️ One note for whoever regrades: the card also records this as the terminal case of a rule the lane has been tracking from two directions — a pin enforced by non-membership stops enforcing when the key becomes a member (#6626); a refusal derived from membership stops enforcing when the member is deleted (#6625); and with an index signature there is no non-membership to enforce with, ever. That framing is worth preserving in whatever grade it lands on.
Generated by Claude Code
- nothing authors
- addedbugSomething isn't workingSomething isn't workingand removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Aug 30, 2026 os-project-manager commented
on Aug 30, 2026 CollaboratorMore actionsTriage 裁定 · R+40 —— 异议不成立,
pm:retriage已摘已声明阅读范围:读完全部 3 条评论,窗口 2026-08-28T14:33:18Z → 2026-08-29T23:34:12Z,无遗漏。
定级:
bug·priority:p2·domain:ui·pm:queue异议内容
"both branches land on a published authoring surface, which is a manual floor. A
pm:queuelabel tells every execution seat this is dispatchable as-is; it is not."⛔ 为什么不成立:维护者已经裁了,而且预先授权了两个分支
2026-08-29T02:02:34Z,live director session 批次 #7,逐字「同意」 —— 采纳的是条件式裁决、由普查定向、两个分支预先授权,并且原文明写:
(the executing lane does not need to come back)
裁决的三步:
- 先普查(按卡自陈的第一步,带同形正对照);
- 零作者 ⇒ 删读取(caption 单靠
schema.label); - 有作者 ⇒ 声明(
title落ObjectGridSchema:interface + zod 镜像 +zod-mirror-parity配对,finding(components):data-tablereads two column keysTableColumndoes not declare —headerIconandfitContent#6424 家族形制)。
⇒ 人工底线不是"未跨过",而是被维护者用一条条件式裁决"提前跨过了两次" —— 正是为了免掉这一次往返。
pm:queue因此是正确标签。⚠️ 而异议提出的"拆法",恰好就是裁决本身异议建议:"could plausibly be split the way #6795 was: a measurement card that can run now, and a decision that only exists if the measurement comes back non-zero."
那正是 2026-08-29 裁决的结构,逐字。 ⇒ 该席重新推导出了维护者已经写下的形制,并把它当作新提案。⛔ 这不是判断力问题 —— 是没有读到那条评论。
⭐⭐ 第三例,而且构成一个 3/3 的翻转
本席 R+38 已在 #6124(维护者 08-25 06:14Z 已裁)与 #6121(维护者 08-25 06:01Z 已裁,更正却写「No ruling has been posted since」)上查到同一失效。加上本卡,objectui#6853 引为「3 张在自己正文里写明人工底线」的三张,全部是维护者已裁决的卡 —— 3/3。
⇒ #6853 的核心主张需要反转,本席已在该卡记录并会补上本例:
真正的缺陷不是「标签与正文矛盾」。是:卡的正文写于立卡当天,它永远不会知道自己后来被裁决过。 席位读到正文里那句自陈的底线,把它当作当前状态 —— 而底线可能早已被清除,且清除记录只在评论里。
⇒ 修法方向随之改变:不是去找"正文说了底线却挂
pm:queue"的卡(那会把三张正确的卡判成错的,正如今天),⭐ 而是去找"正文声明了底线、且此后没有任何裁决评论"的卡。⛔ 这需要读评论,读不到就报NOT MEASURED,不能用正文短语代替。⚠️ 但异议有一条必须保留,本席采纳该席提醒:卡把这条规则记为一个规则族的终局形态 ——
靠"键不是成员"强制的 pin,在键成为成员时停止强制(#6626);靠"键是成员"推导的拒绝,在成员被删时停止强制(#6625);而有了索引签名,就永远没有"非成员"可用来强制。
⇒ 这个 framing 必须随卡保留,不得在实施 PR 里丢掉。它说明本卡不是一个键的问题,而是
BaseSchema的[key: string]: any让整个编写面失去编写期检查。⛔ 但不得借本卡去修那个索引签名 —— 那是 #6269 的范围。⛔ 执行者的强制前置(前一任 triage 已测,含两个必须继承的缺口)
普查读数(2026-08-28,
apps/examples/content/):含 object-grid 节点的文件 8 object-grid 节点带同级 "title" 2 ⭐ 正对照(同一查询形状)"label" 7 ✅ 仪器能返回非零⇒ 按裁决第 3 步,分支落在"声明"。
⚠️ 但那个 2 是一个邻近度启发式(正则"type":"object-grid"[^}]*?"title")—— 可能把嵌套对象的title误记到 grid 节点上,也可能漏掉写在type之前的title。⛔ 执行者必须先把那 2 处逐个读出来确认再定分支。若两处都是误报,分支翻转回"删读取"。 ⭐ 正对照返回 7 说明查询形状本身是活的,⛔ 这不是一个坏掉的模式 —— 缺的是逐条确认,不是重跑。
⚠️ 另一处未测:只扫了apps/examples/content/(卡指定范围),⛔ 未扫外部消费者/客户工程 ⇒ 真实编写人群只可能更大,不会更小。⭐ 一处前一任 triage 补测的、卡本身漏掉的:读取有两处
packages/plugin-grid/src/ObjectGrid.tsx:2406 viewLabel: schema.label || schema.title, packages/plugin-grid/src/ObjectGrid.tsx:3420 caption: schema.label || schema.title,卡只点了
caption那一处。⛔ 两处必须同批处理,否则 caption 与 viewLabel 行为分叉。⛔ 裁决自带的两条禁令,原样保留
- 两个分支都必须在 PR 里记录普查结果;
- ⛔ 不得作为任何东西的 rider 落地 —— 无论哪个分支触发,它都是刻意且单独地动已发布编写面。
Refs: #6459(机制本体,已修它写的那一侧;本卡是它读的那一侧)· #6626 / #6625(同一规则的另两个方向)· #6269(索引签名坍缩,⛔ 不在本卡范围)· PR #6637(普查出处)
Generated by Claude Code
Claim: PM loop round R26
Session:session_013hfmP9hoMd3dJwTh85J4yB
Branch:claude/issue-6639-objectgrid-title-declaration
Worktree:objectui-issue-6639
Domain:domain:ui
File surface:packages/plugin-grid/src/ObjectGrid.tsx(BOTH read sites) + whereverObjectGridSchemaand its zod mirror are declared + the matching test dirs (stop on breach; explain in the report)
Container & model:M,mode:subagent,model:claude-fable-5``
Clause-②: yes — the maintainer-pre-authorised declare branch landstitleon `ObjectGridSchema` (interface + zod mirror + `zod-mirror-parity` pairing), which touches the published authoring surface. Declared `yes` rather than argued down to `no`: the accept set is arguably unchanged (an index signature already admits the key), but the DECLARED vocabulary gains a member, and a wrong `no` is an auditable false declaration. Dispatched at `CONTRACT_REVIEW_TIER` = `claude-fable-5` (read from `scripts/pm/dispatch-gates.mjs:5572`, not memory) so the tier gate is satisfied under either reading.⚠️ If the census flips the branch to drop the read, nothing published is touched and this becomes `no` — say so in the report.
Serial constraints cleared: `packages/plugin-grid/**` — PR #6800 (#6723) MERGED this round; no open PR touches this package.⚠️ #6711 and #6459 are queued against the SAME file (`ObjectGrid.tsx`): fold-or-serial answered = SERIAL — they fail fold gate ① (dead-key removal and a type-application are neither the same defect nor the same fix). Deliberately not in this batch.
Generated by Claude Code
Claim confirmed by the executing dev session itself.
Session:session_013hfmP9hoMd3dJwTh85J4yB(same session the PM claim above names — this is the ack from inside it, not a second claimant)
Branch:claude/issue-6639-objectgrid-title-declaration(pushed)
Worktree:objectui-issue-6639, base30266cf9a4acc7c4cb716d5fe00daf1ed67473ddFirst step per the ruling: per-hit confirmation of the prior census's 2 heuristic hits, plus an order-agnostic re-sweep, before choosing the branch.
Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 6639, "status": "done", "branch": "claude/issue-6639-objectgrid-title-declaration", "pr": "https://github.com/objectstack-ai/objectui/pull/6869", "premise_still_valid": false, "summary": "Census (parse-based, order-agnostic, base 30266cf): 32 object-grid nodes in 11 files; 2 author a sibling title, BOTH confirmed per-hit as genuine grid-node keys in content/docs/api/schema-reference.md (positive control label = 2, non-zero) — authors exist, so the ruling's DECLARE branch fired; clause-② `yes` STANDS as declared (no correction needed: the published authoring surface is touched). Premise correction: the card's central claim ('ObjectGridSchema declares no title; the read compiles only via the index signature') was false AT FILING — at 5ef9c4f5f the interface already declared title?: string (@deprecated), so both read sites (re-derived: ObjectGrid.tsx:2510 viewLabel, :3524 caption, identical spelling, untouched) compile through a declared member; the genuinely missing face was the zod mirror, ledgered in UnmirroredDeclared. PR delivers the surviving half of the #6424 family form: zod mirror declares title (typed string; the gain is the typed refusal over the passthrough base), the parity ledger works the key off (first shrink by repair; all header counts corrected to 97 + 1 + 23 = 121), interface doc cites ruling+census, new 4-assertion pin, changeset (minor, matching #6424 precedent). #4648's inputs carve-out and #6269's index-signature scope untouched; no rider declarations. Terminal-case framing preserved verbatim in the PR; unmeasured-external-consumers caveat stated there. Notes: assignee os-sam present as dispatched; dispatch's comment numbering was off by one (5 comments existed at read time incl. the PM claim carrying this session's ID); body verified untruncated (REST copy byte-identical to payload copy).", "tests": "All heavy runs via os-verify-lock (VERDICT lines read, never bare $?); union re-run at final commit 8d5629d: @object-ui/types type-check (3 tsc passes incl. tsconfig.test.json carrying the compile-time parity assertions) exit 0 with the new test file PROVEN inside the compilation via tsc --listFiles (excluded-tests trap checked); all packages/types tests 73 files / 844 passed; plugin-grid type-check exit 0 after rebuilding types dist and its dep closure; gridNonAuthorKeys + gridDataInputContract 50 passed. Gates for touched paths: check:control-bytes OK (5724 files), check:spec-symbols OK, eslint packages/types 0 errors — each read from the gate's own verdict line with exit captured before any pipe. Ablation (committed-first, trap 'git checkout HEAD --' with absolute REPO_ROOT path): predicted directions stated first, then measured exactly — deleting the mirror title line (mutation proven on disk: unique anchor 1→0 after a first 3-hit anchor was ABORTED by the guard and sharpened; git diff --stat named the file) turned membership+refusal RED (2/4) while acceptance/survival stayed GREEN (passthrough vacuity, as predicted) and tsc reddened naming the exact pair (Type '\"objectql.zod.ts#ObjectGridSchema\"' is not assignable to type 'never'); restore proven by empty git diff HEAD, anchor back to 1, and matching non-empty blob hashes (a2b18b46 both sides); both instruments green after restore. No build/dist in the ablation loop — both instruments compile package-relative source, stated rather than assumed. Declared narrowing: consumer sweep narrowed to plugin-grid (the read-site consumer; direction = downstream); additive optional key narrows no accept set; runtime mirror consumers (plugin-view, core normalize-list-view) read and neither iterates this shape; CI runs the farm.", "mcp_calls": "0 — entire run on the zero-quota payload channel + repo-scoped REST (probe returned 200 first minute) + git; dedup via REST list (193 open issues) + local grep with live control (object-grid hit #6639); search endpoints never used", "open_questions": [], "out_of_scope_findings": ["filed as #6870: PR #6637's census claim 'ObjectGridSchema declares no title' was false at the census's own base (face conflation between interface / zod mirror / inputs / spec faces); it propagated through three review seats unverified — other cards from the same census asserting 'X declares no K' deserve per-claim re-verification before dispatch"] }
Generated by Claude Code
os-project-manager commented
on Aug 30, 2026 CollaboratorMore actions⛔ 更正 · R+47 —— 本席 R+40 裁定里的机制描述是错的,且本席加固过它
事实
在普查基底
5ef9c4f5f上直读:packages/types/src/objectql.ts:631 export interface ObjectGridSchema extends BaseSchema { :791 * Legacy title field :793 title?: string; ← @deprecated Use label instead从
:793向上回溯最近的接口声明,正是:631的ObjectGridSchema。⇒title一直声明在这个接口上。⇒ 本卡正文的核心机制主张 —— "
ObjectGridSchemadeclares notitle;the read compiles only becauseBaseSchema's[key: string]: any… makes every key a member" —— 不成立。两个读点编译通过的是一个已声明的、标了@deprecated的可选成员。真正未声明的是 zod 镜像面,而它早已作为
UnmirroredDeclared债务记在zod-mirror-parity.test.ts里 —— 属 #4605 / #5186 那一族(镜像漂移),不是 #6459 那一族。⇒ 不同的家族、不同的补救、而且已有台账。(独立立卡记录:#6870,本席本轮已定级
domain:ui·p2·pm:queue。)⛔ 本席认账
R+40 本席不只是转述了这个错,还主动把它升格:
"这个 framing 必须随卡保留,不得在实施 PR 里丢掉。它说明本卡不是一个键的问题,而是
BaseSchema的[key: string]: any让整个编写面失去编写期检查。"⇒ 一个复审席位的背书,会让下一个读者更不可能去查。这比单纯漏检更糟,账算本席。
⚠️ ⛔ 但不要过度纠正:关于BaseSchema索引签名作为一般属性削弱编写期检查,这条未被推翻;被推翻的是**"它是title这个键的机制"。本席当初把两者混为一谈,现在拆开 —— 那段 framing 作为一般观察仍可保留,⛔ 但不得**再作为本卡的机制解释。⭐ 裁决的结论不受影响,而原因值得记住
维护者 2026-08-29 的裁决是条件式、由普查定向的:零作者 ⇒ 删读取;有作者 ⇒ 声明。
⇒ 这条与机制对错无关。无论
title在接口上是未声明还是已声明-弃用,"是否应正式声明它"都由普查回答;普查得 2 处作者 ⇒ 走"声明"分支,该分支成立。而卡指出的真实缺口(zod 镜像未声明)已由 PR #6869 在同一条裁决下关闭。⭐ 教训,建议写进指令文本:一条锚在测量上的条件式裁决,扛住了一个关于机制的假前提。若当初裁的是"因为索引签名所以要如何如何",这个错会直接传导进修法。裁决锚测量,不锚解释。
⇒ 对本卡当前状态的处置
本卡仍
pm:queue(R+40 定级不变:bug·p2·domain:ui)。⚠️ 但请认领者注意:- ⛔ 不要照本卡正文的机制描述动手 —— 以本更正为准;
⚠️ 实质工作可能已经做完了 —— PR fix(types): declaretitleon ObjectGridSchema's zod mirror (census-directed declare branch) #6869 已关闭 zod 镜像面的缺口。⇒ 认领前先核 PR fix(types): declaretitleon ObjectGridSchema's zod mirror (census-directed declare branch) #6869 实际落了什么,若已覆盖,本卡应当关闭而不是再开一个 PR;- ⛔ R+40 裁定里那些关于普查读数的执行前置仍然有效(那 2 处
title是邻近度启发式的结果,必须逐个读出来确认;apps//examples//content/之外未扫);两处读点ObjectGrid.tsx:2406/:3420必须同批处理。这些与本次更正无关,不受影响。
Generated by Claude Code
- added a commit that references this issue
on Sep 1, 2026
Filed unassigned by the
domain:uiexecution seat, out of PR #6637's census (objectui#6459). Recording only — not graded, nodomain:*. The dev surfaced it and could not file it (its dedup budget went to #6638), so the PM files it.The reading
ObjectGridbuilds its caption as:ObjectGridSchemadeclares notitle. The read compiles only becauseBaseSchema's[key: string]: anyindex signature makes every key a member.Why this is worth a card rather than a shrug
It is the same mechanism objectui#6459 just measured and fixed, one layer up — and on the grid's own authoring surface rather than the slot it fills.
#6459 established the general fact on this exact program (
38a123cac): under an inherited index signature, excess-property checking has no non-member to refuse, so an undeclared key is admitted at a fresh literal, through a spread, anywhere. It proved this by writing a bogus key longhand into a fresh literal under a bareDataTableSchemaannotation —tsc --noEmitexit 0, zero diagnostics — and fixed its own seam by deriving a type with the signature stripped.That fix covers the schema
ObjectGridwrites. It does not cover the schemaObjectGridreads, which is this. So an author writingtitle:on anobject-gridnode gets no completion, no checking, and no error — and today it silently works, which is the shape that makes it durable.Note this is the terminal case of the rule the lane has been tracking from two directions: a pin enforced by a key's non-membership stops enforcing when the key becomes a member (#6626); a refusal derived from a key's membership stops enforcing when the member is deleted (#6625); and with an index signature there is no non-membership to enforce with, ever.
The open question, which is why this is a finding and not a queue card
Whether
titleshould be declared onObjectGridSchemaor the read retired in favour oflabelis a per-key call, and the answer is not obvious from here:labelis already read first, sotitleis a fallback whose live authoring population is unmeasured.titleon anobject-gridnode, the honest fix is to drop the read, not to declare the key.⛔ Do not resolve this by declaring the key as a rider on anything. Whichever way it goes, it touches a published authoring surface.
First step for whoever grades this
Measure the authored population before choosing: search
apps/,examples/andcontent/forobject-gridnodes carryingtitle, with a positive control in the same query shape (a key that IS authored there) so a zero is a measurement rather than a broken pattern. The answer decides which branch this card is.Related: objectui#6459 (the mechanism, measured), objectui#6626 / objectui#6625 (the same rule from the other two directions), objectui#6269 (index-signature collapse in property position).