Skip to content

fix(spec): 给 dashboard widget 子树补下钻,并让门禁拒绝「未声明的容器继承」(#4956) - #5012

Merged
xuyushun441-sys merged 8 commits into
mainfrom
claude/issue-4956-liveness-widget-drill
Aug 4, 2026
Merged

fix(spec): 给 dashboard widget 子树补下钻,并让门禁拒绝「未声明的容器继承」(#4956)#5012
xuyushun441-sys merged 8 commits into
mainfrom
claude/issue-4956-liveness-widget-drill

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #4956

台账 dashboard.widgets 挂着一条 {"status": "live"},外加一句断言:per-widget props「classified in the DashboardWidgetSchema subtree」。那个 subtree 从来不存在。走查只下钻一层、且只认显式 children,而 widgets 一个也没声明 —— 于是 strict DashboardWidgetSchema 的 22 个可授权键一个都没被分类过,也从不计入 unclassified,而每次运行都照常打印「all governed-type properties are classified」。

这不是「一个键漏了」,是仪器在自己的成功信息里说谎widgets[].responsive 正是靠这个缺口活过了 #3896 那轮清扫 —— 同一轮里它的亲兄弟 widgets[].performance 和字面同名的 view.responsive 都被删了,因为 viewchildren 下钻,list.responsive 被问到了、拿到 dead、出局;widgets 从来没人问。它最后在 #4876 / PR #4995 手工退休,晚了四天。


一、先证红(仪器修复的必要条件)

以下三段全部在当前合并树上重跑,输出为实测粘贴。

① 修复前:门禁全绿,而键在地图之外。widgets 还原成修复前形态(单一 blanket 裁决 + 那句假断言),用 origin/main 的门禁跑:

  dashboard   20 classified (live 18, dead 2)
✓ all governed-type properties are classified, every registered type is governed or
  explicitly pending, no ledger row outlives its property, and all bound high-risk proofs resolve.
EXIT=0

用同一份 --json 做探针,问「这些键在地图的哪一侧」:

gate failed?               false
dashboard byStatus:        {"classified":20,"unclassified":0,"byStatus":{"live":18,"dead":2}}
unclassified (whole tree): 0
  widgets.compareTo      -> in unclassified: false | any coordinate at all: false
  widgets.colorVariant   -> in unclassified: false | any coordinate at all: false
  widgets.aria           -> in unclassified: false | any coordinate at all: false
  widgets.responsive     -> in unclassified: false | any coordinate at all: false

既不在 classified,也不在 unclassified,整份报告里没有任何坐标 —— 而门禁是绿的。这就是 issue 的证据形态。

② 同一份修复前台账,换成本 PR 的门禁 —— 转红,并点名 22 个键:

  dashboard   20 classified (live 18, dead 2)
✗ 1 UNDECLARED container inheritance — a blanket verdict covers keys nothing classified:
    dashboard/widgets — one verdict covers 22 unclassified child key(s): id, title,
    description, type, chartConfig, colorVariant, requiresObject, requiresService,
    actionUrl, actionType, actionIcon, filter, compareTo, dataset, dimensions, values,
    layout, options, filterBindings, suppressWarnings, responsive, aria
EXIT=1

③ 补完分类后 —— 绿,且数字第一次诚实:

  dashboard   41 classified (live 33, dead 8)

container coverage: 58 container entr(ies) carry a blanket verdict over 292 child key(s)
that are classified NOWHERE; 6 more defer 248 key(s) to a coordinate that DOES classify
them (resolved, not asserted). Both recorded in
scripts/liveness/undrilled-containers.baseline.json — shrink-only.

✓ every governed-type property at the walk's one-level granularity is classified, …,
  every container inheritance is declared, and all bound high-risk proofs resolve.
  (not a completeness claim about the 292 child key(s) under the declared blanket verdicts
   above — those are recorded, not classified.)
EXIT=0

20 → 41:22 个 widget 键一次性进入计数(父条目自身不再单独计一条)。


二、结构性修复:三种处置,全部是数据

issue 给了两条路,并倾向「补 children」。补了 —— 但只补数据等于修掉这一处实例,留着这一类缺口。实测全仓库:

65 个容器条目挂着单一裁决,覆盖 562 个从未被下钻的子键widgets 只是其中一个,只是它恰好被人看见了。

刻意没有把 65 个全部下钻:那意味着凭空编造 562 条没有证据的裁决,而伪造一条 live 比诚实的粗粒度更糟。继承本身是合法粒度,不合法的是默默继承。于是容器必须声明三种处置之一,且三种都是数据:

处置 含义
drilled — 条目上的 children 逐键裁决,与既有机制一致
deferred — baseline 里的 { container, to } 子树确实在别处被分类,而这条引用由门禁解析
recorded — baseline containers 列表里的坐标 确实哪里都没分类;被记录、可计数的 shrink-only 债

三者皆无 → ,并打印它正在无声覆盖的子键。baseline 里某行的容器已经下钻了 → 也(与 orphan 行同一种腐烂,方向相反:虚报的债和瞒报的债一样误导人)。

deferred 为什么必须被解析,而不是被相信

这条是被本 PR 自己的 --undrilled 工作清单抓出来的。 baseline 初稿把 64 个容器一律记作「classified NOWHERE」,而其中 6 个那是假的:object.fields[] 就是 FieldSchema,field 台账把它 66 个键全分类了;object.listViews[] 就是 view.list 已经下钻的那张 ListView 表。248 / 540,将近一半。 照初稿发出去,就是在一个专门修「无法兑现的断言」的 PR 里,写下本 PR 自己版本的同一个断言。

区别从来不在那句话,而在谁来核。deferral 把目标写成数据,门禁去解析:目标必须存在(受管类型根,或一个已下钻的 type/prop 坐标),且其已分类键集必须与容器子键集相等。悬空 → 红;漂移 → 红(用相等而非子集:容器多出一个目标从不分类的键,就是 #4956 下移一层);两个列表同时声明 → 也红,而不是让门禁悄悄选一个当真。

实测,把 deferral 指向 issue 里那个不存在的名字:

✗ 1 broken deferral(s) — a "classified elsewhere" claim that does not resolve:
    object/fields defers to 'DashboardWidgetSchema', which does not exist — no governed
    type and no drilled ledger coordinate of that name
EXIT=1

目标存在但键集漂移:

✗ 1 broken deferral(s) — a "classified elsewhere" claim that does not resolve:
    object/listViews defers to 'view/form' but the key sets have drifted — 42 key(s)
    here that 'view/form' does not classify (name, label, filter, sort, …
EXIT=1

这正是 issue 的第二条建议(「让 check-liveness.mts 能解析这种引用,并在引用悬空时报错」),而它也是第一条建议得以诚实的前提。当前 6 条已解析 deferral:

  resolved deferrals (classified, just not here):
    object/fields → field
    object/actions → action
    object/validations → validation
    object/listViews → view/list
    view/listViews → view/list
    view/formViews → view/form

选「baseline 文件」而不是「每条加一个 subtree: inherits 字段」,理由正是让 AI 写的东西难写错:加一行到一个以债务命名的文件里,是评审能看见的动作;在自己正在写的条目旁边多敲一个词,摩擦低到等于没有。逻辑放在纯函数模块 scripts/liveness/drill.mts + 单测,理由与 orphans.mts 一致:落地那一刻全树已自洽,绿灯只能证明规则安静,证明不了它会响


三、22 个键的裁决(证据工作,不是猜)

逐键手工闭合调用图,objectui@91757a7 + 本仓库 HEAD。16 live / 6 dead。本 PR 不退休任何键。

两个方向相反、都值得记住的结果:

  • requiresService 是 live —— 但只在服务端。 在 objectui 里怎么量都是死的;真正的消费者是 packages/rest/src/rest-server.tsfilterDashboardForUser(ADR-0057 D10):未注册的 kernel service 会让 widget 直接从 payload 里被剥掉,:3037/:3476 有调用点,rest.test.ts:3271 有测试。只看渲染器仓库就下 dead,会删掉一个正在生效的门。
  • compareTo 是 live,但只在一条路径上。 内联 object-provider 图表路径三个分支全实现;ADR-0021 dataset 路径(spec 自称「唯一面向作者的分析形态」)上,两个字符串分支被 DatasetWidget 显式丢弃,{ offset } 被转发进一个要求 { kind, dimension } 的契约 → 执行器抛错。照 action.disabled 先例记 live 并把边界写死在 note 里,另开 dashboard widget compareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011

dead 的 5 个(responsive 已由 #4995 退休,不计)

判据
colorVariant 读它的全是编写期界面;渲染器显式拼装组件 schema,只有 options.colorVariant 能到达 MetricWidget。本仓库自带的 system_overview.dashboard.ts 写了 7 次
actionUrl DashboardRenderer 里 14 处 actionUrl 全属于 header.actions[](另一个 schema)。没有任何渲染器画 widget 级按钮
actionType 同上;唯一读者是下面那条 lint
actionIcon 两仓库零引用,连 lint 都不看
aria 两仓库无 widget.aria 消费者;与 17.0.0 删掉的看板级 aria 同一种假合规

二阶成本: validate-dashboard-action-refs.tswidgets[].actionUrl 做引用完整性强校验、解析不到就 ERROR,docblock 还称它为 "the per-widget button" 并声称 mirrors 运行时 dispatch —— 而那个按钮不存在。今天一个作者可能因为一个永远不会渲染的按钮指向不存在的 action 而被卡住构建。

ADR-0049 enforce-or-remove 另开 #5010(未指派),本 PR 只记录裁决


四、闭上作者侧的环

台账新增 5 条 authorWarn,但 dashboard 不在 CLI lint 的 TYPE_COLLECTIONS 里 —— 那条列表自己的注释写着这正是「ledger correct and silent」的形状。所以同时把 dashboard 注册进去;getNested 本就支持数组层扇出,widgets.colorVariant 会检查看板上的每一个 widget(测试里专门有一条:dead 键在第二个 widget 上)。


五、验证(全部在合并后的树上重跑)

结果
check:liveness ✓ EXIT=0(三段红/绿证据见上)
check:generated(8 项) ✓ All 8 generated artifacts are up to date
check:empty-state ✓ all classified (1 closed, 2 open, 4 output, 9 scope)
check:strictness-ledger ✓ 67 file(s) / counts match
@objectstack/spec test 299 files / 7597 passed
@objectstack/lint test 56 files / 1057 passed
typecheck(spec + lint) tsc --noEmit 无输出

新增单测:drill.test.ts 28 条 —— 规则会响(未声明容器 / 陈旧 baseline 行 / 畸形 baseline / 悬空 deferral / 键集漂移(两个方向)/ 双列表声明),以及它必须保持安静的每一种情形;lint-liveness-properties.test.ts +5 条(dashboard 扇出、hint 内容、全 live widget 静默)。

#4995 的共享面

PR #4995 在本单进行中落地。已 git merge origin/main(未 rebase、未 force-push),冲突只在 _note 一处,双边保留并重写:#4876 记录了「responsive 故意不留台账行,否则是 ORPHAN」—— 这在 widgets 未下钻时是对的,而 retiredKey tombstone 会把键留在走查形状里,所以下钻落地后那一行是必须的(不留 → UNCLASSIFIED)。它现在带着 #3896 那轮没来得及记的 dead 裁决在位。合并后 check:liveness 绿,即两处改动可以正确合成的裁决证据。

os-regen 四步已执行(含随后 #5003 / #5006 / #5008 落地后的第二、三次合并):generated artifacts 整体取自 origin/main → 重装重建 → check:generated 对合并后的源全绿 → 逐项断言 #4995 的兄弟条目存活(authorable-surface.json[RETIRED]spec-changes.json ×2、升级指南、references、conversions registry ×2、tombstone)。


不做的事

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

claude added 4 commits August 3, 2026 19:57
…ontainer inheritance (#4956)

The liveness ledger's `dashboard.widgets` entry claimed in prose that its
widget-level props were 'classified in the DashboardWidgetSchema subtree'. No
such subtree ever existed, the walk drills one level only through an explicit
`children`, and `widgets` declared none — so all 22 authorable keys of the
strict DashboardWidgetSchema sat outside the map while the gate printed green.

- classify all 22 widget keys with closed-call-graph evidence (objectui @91757a7
  + this checkout, 2026-08-03): 16 live, 6 dead
- scripts/liveness/drill.mts: a container entry riding on inheritance must be
  DECLARED — drilled, or recorded in the shrink-only
  undrilled-containers.baseline.json. A new one fails; a stale row fails.
- the gate now counts and prints the inheritance population every run, and its
  success line no longer claims a completeness it does not have

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…g loop on dashboards (#4956)

- scripts/liveness/drill.test.ts: the tree is fully reconciled by construction,
  so a green check:liveness proves only that the rule is QUIET. These prove it
  fires — undeclared container, stale baseline row, malformed baseline.
- packages/lint: register `dashboard` in TYPE_COLLECTIONS. Drilling widgets
  produced five authorWarn keys; without the collection the ledger would be
  newly correct and newly silent, which is the shape that lint exists to prevent.
- README: document the third direction + regenerate the dashboard count row
  (18/2 -> 33/8; +21 classified, all 22 widget keys entering the count at once)
- changeset: the five new build-time author warnings, with FROM -> TO per key

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Conflict was `liveness/dashboard.json`'s `_note` only; both sides kept and
reconciled. #4876/#4995 recorded that `widgets[].responsive` deliberately
carries NO ledger row because one would be an ORPHAN — true only while
`widgets` was undrilled. The retiredKey tombstone keeps the key in the walked
shape, so with the drill landed the row is REQUIRED (omitting it reports
UNCLASSIFIED); it is present with the dead verdict the #3896 sweep never got to
record. check:liveness green on the merged tree is the arbiter.

os-regen four-step done: generated artifacts taken wholesale from origin/main,
rebuilt, check:generated clean on the merged source, and every #4995 sibling
entry asserted present (authorable-surface [RETIRED], spec-changes, upgrade
guide, conversions registry, tombstone, changeset).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…4956)

Five dead keys -> #5010 (ADR-0049 enforce-or-remove); compareTo's one-path-only
liveness -> #5011. A dead verdict with no issue behind it is indistinguishable
from never having looked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 12:55am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

)

Caught by this PR's own --undrilled worklist: the first baseline draft recorded
all 64 containers as 'classified NOWHERE', and for six of them that was FALSE —
object.fields[] IS FieldSchema (the `field` ledger classifies all 66 keys),
object.listViews[] is the ListView surface `view.list` already drills, and so
on. 248 of the 540 keys, nearly half. Shipping that note would have been this
PR's own version of the defect it fixes.

So a container now declares one of THREE dispositions, all data:
  - drilled   (`children`)
  - deferred  ({ container, to }) — RESOLVED by the gate, never believed
  - recorded  (the shrink-only `containers` list)

A deferral's target must exist (a governed type root, or a drilled type/prop
coordinate) and classify EXACTLY the container's child keys. Dangling fails;
drifted fails (equality, not subset — a container that grows a key its target
never classifies is #4956 one level down); declared in both lists fails rather
than letting the gate silently pick which is true.

This is the issue's own second option — 'let the checker PARSE such a reference
and error when it dangles' — and it is what lets the first option be honest.
Pointing a deferral at 'DashboardWidgetSchema' now produces a named build
failure, where the same words in a `note` were believed for a release.

Counts are honest for the first time: 58 containers / 292 keys classified
nowhere, plus 6 deferrals covering 248.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
@github-actions github-actions Bot added size/xl and removed size/l labels Aug 4, 2026
…eport (#4956)

A coordinate that is BOTH double-declared and gone was reporting twice — as a
stale row and as a contradiction. The single fix is to delete the row(s), and a
second heading about a coordinate that no longer exists obscures it. Same rule
orphans.mts already follows for its one deliberately-silent case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

liveness: DashboardWidgetSchema 的 ~22 个 widget 级键从未被台账分类,而 dashboard.json 的 _note 声称它们已分类

2 participants