Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

owner widget 拿不到表单下发的 dataSource,而 plugin-grid 认为它需要 —— 且 owner 根本不在 spec 的 FieldType 里(enforce-or-remove 待判) #4814

Closed

Description

@yinlianghui

发现于 #4790 的实施(表单 data-source 允许表收敛)。测量副产物,不在该卡范围内,单独立此单。

事实(实读 origin/main @ f2dc8fa)

  1. packages/fields/src/field-type-alias.ts:105 把字段类型 owner 映射到 field:owner;packages/fields/src/index.tsx:2435 把 field:owner 注册到 UserField(与 user 同一个 widget);index.tsx:2190 另有 registerFieldRenderer('owner', UserCellRenderer) 管单元格展示。即 owner 在本仓是一个活的 widget 键。

  2. 但 owner 不在 @objectstack/spec 的 FieldType 枚举里(17.0.0-rc.6,dist/percent-scale-DQMCB4dC.d.ts 的 declare const FieldType:… lookup / master_detail / tree / user / image … 全表 48 个成员,无 owner)。所以没有任何对象 schema 能声明 type: 'owner' —— 它只能经 widget: 'field:owner' 这条手写 SDUI 路径到达。

  3. 两个消费面对它的判断相反:

  4. 同一 probe 还读到 field:grid 三项皆 false。而 packages/fields/src/widgets/types.ts:166 的 dataSource TSDoc 写的是「DataSource for widgets that query records (lookup / user / object-ref / recipient-picker / grid). Injected by the form renderer for the field types that need it」—— grid 从来不在任何 data-source 表里,GridField.tsx 也 grep 不到 dataSource。这句契约文档的 grid 是零消费者的声明。(user 那半已由 DATA_SOURCE_FIELD_TYPES(form 层)与 core 的 EXPANDABLE_FIELD_TYPES 已知不同集且零 gate —— 同 #4770 的第二张允许表待收敛 #4790 / PR 补齐。)

影响

  • owner:手写 widget: 'field:owner' 的字段落到 UserField → LookupField。dataSource / dependentValues 各有 SchemaRendererContext 兜底,所以有 provider 时能瘸着跑;dependsOnLabels 没有任何兜底,于是带 depends_on 的 owner 选择器把裸 API 名插进「先选 … 」提示句 —— objectstack#5407 给 lookup 关掉、这里仍开着的同一个洞。
  • grid:纯文档失真,今天没有用户碰得到。

需要裁决的是「接线还是退役」,不是补一个 string

owner 既然不是 spec 声明得出来的类型,把它加进表单的 DATA_SOURCE_ONLY_WIDGET_TYPES 等于新造一条 declared-but-unexercised 条目 —— 正是 ADR-0049 enforce-or-remove 要消灭的东西,也正是 #4790 这一族在关的失效模式。所以 #4790 的实施故意没有顺手加,留给分诊:

  • A. 退役 owner 别名:删 field-type-alias.ts 的 owner 映射、index.tsx 的 field:owner 注册与 plugin-grid 的 USER_WIDGET_TYPES 成员,cell renderer 另议。前提是确认没有产品在用 widget: 'field:owner'。
  • B. 承认它是一个 widget-hint-only 名(与 object-ref / filter-condition / recipient-picker同类),补进表单规则并在 paramValueShape.ts 之类的地方登记 widgetHintOnly: true,让「声明即强制」。
  • grid 那句 TSDoc 无论 A/B 都应改掉或补消费者。

参考

Activity

  1. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-16, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the evening decision-inbox report's recommendations in full, verbatim: 「8931 我已经处理了,其他接受你的所有建议。」 For this card:

    Ruled: Option A — retire the owner alias, preconditioned on a zero-consumer measurement.

    • Precondition (first step of the dispatch): search all four repos + examples/catalog for field:owner / 'owner' widget usage. Zero hits ⇒ proceed with A (drop the field-type-alias.ts mapping, the field:owner registration, and the USER_WIDget_TYPES membership in plugin-grid; cell renderer disposition per measurement). Any real consumer ⇒ fork report back to the decision box — do not migrate silently.
    • Rationale on record: owner is not a spec FieldType, adds nothing over user (same widget), and is reachable only via hand-written SDUI with broken plumbing (dependsOnLabels has no fallback — the objectstack#5407 hole still open here).
    • Either way: the grid claim in packages/fields/src/widgets/types.ts dataSource TSDoc (zero consumers) is corrected in the same batch.

    First-touch grading completes: label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  2. self-assigned this
    on Aug 16, 2026
  3. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    ContributorAuthor

    【认领】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)批次 12 派发,os-dev 实施。分支:claude/issue-4814-retire-owner-alias。

    按 2026-08-16 维护者裁定(评论 5307574997)执行:方案 A 退役 owner 别名,前置零消费者测量 —— 第一步扫可达仓 + examples/catalog 的 field:owner / 'owner' widget 用法(零命中须带邻近词反查;任何真消费者 ⇒ 停手回报决策箱,⛔ 静默迁移);零消费者 ⇒ 删 field-type-alias.ts 映射、index.tsx 的 field:owner 注册、plugin-grid USER_WIDGET_TYPES 成员(cell renderer 按测量定夺)。types.ts dataSource TSDoc 的 grid 零消费者声明同批更正。

    文件面:packages/fields/src/field-type-alias.ts、packages/fields/src/index.tsx、packages/plugin-grid/src/components/bulkParamToField.ts、packages/fields/src/widgets/types.ts + 测试。与在飞 #4788(⛔ widgets 零改动)、#4824(TextAreaField/RichTextField)无文件交集。


    Generated by Claude Code

  4. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    ContributorAuthor

    测量结论:前置零消费者条件 不成立 —— 停手,未迁移,无 PR

    按裁定评论 5307574997 的前置条件执行第一步扫描。结果:owner 不是零消费者。命中 3 处代码 + 4 处文档/示例消费面,其中一处是裁定与本单正文都未列出的第三个代码消费者。按「Any real consumer ⇒ fork report back to the decision box — do not migrate silently」,本次不实施退役、不开 PR,把命中清单交回决策箱。

    扫描面(诚实记录)

    仓 版本 状态
    objectstack-ai/objectui origin/main @ 1ef236e18 全仓已扫(含 examples/schema-catalog、content/docs、apps、scripts)
    objectstack-ai/objectstack origin/main @ d491625c1 全仓已扫(含 examples/app-showcase、content/docs、packages/spec)
    objectstack-ai/cloud — 未扫描 —— 本会话凭据无访问权(add_repo 返回 "you don't have access to objectstack-ai/cloud")。裁定要求的「四仓」我只够到两仓,这一面是空白,不是绿。

    扫描器反查对照(证明零命中处确实是零、而不是扫描器没够到):field:user 在 objectui 命中 3 文件;type: 'user' 在 objectstack 命中 8 处以上(含 examples/app-showcase/src/automation/flows/*.flow.ts)—— 即扫描确实到达了两仓的 examples / docs / metadata 面。

    真消费者命中(widget 键 / 字段类型命名空间)

    代码 3 处,全部在活路径上:

    1. packages/app-shell/src/utils/paramToField.ts:94 —— if (LOOKUP_WIDGET_TYPES.has(type) || type === 'user' || type === 'owner')。本单正文与裁定都没列这一处;它是 plugin-grid bulkParamToField 的孪生面(后者注释自陈 "mirrors app-shell's paramToField")。只删裁定点名的三处,会把这一支留成悬空分支。
    2. packages/plugin-grid/src/components/bulkParamToField.ts:43 —— USER_WIDGET_TYPES = new Set(['user', 'owner'])(裁定已点名)。
    3. packages/fields/src/index.tsx:2190 —— registerFieldRenderer('owner', UserCellRenderer)。它被现有门钉住:packages/fields/src/field-type-coverage.test.ts:48 的 CELL_RENDERER_TYPES 显式含 'owner',断言「resolves "owner" to a dedicated cell renderer (not TextCellRenderer)」。删它 = 现有测试直接变红。

    示例 / 文档 4 处,用户可见,其中一条是被真实渲染的活例子:

    1. examples/schema-catalog/src/schemas/fields-user/record-owner-read-only.json:9 —— "type": "owner"。它不是死文件:在目录索引里注册(examples/schema-catalog/src/index.ts:379 的 import、:3730 的条目),并被文档站渲染 —— content/docs/fields/user.mdx:18 是 < SchemaExample id="fields-user/record-owner-read-only" / >(此处 < 后特意加了空格,避开 GitHub 正文的 HTML 清洗;原文无空格)。它还被 examples/schema-catalog/test/fields-form-hosted.test.tsx 经真 SchemaRenderer + 真表单渲染器实跑。
    2. content/docs/fields/user.mdx:24 —— 公开类型联合 type: 'user' | 'owner';;:93-94 的 "Permission Patterns" 可复制片段 { type: 'owner', name: 'owner', ... };正文「How it works」把 user / owner 并列描述为 sys_user 的特化 lookup。
    3. content/docs/components/complex/filter-builder.mdx:27 —— FilterField.type 联合含 | 'user' | 'owner'。
    4. content/docs/core/report-schema.mdx:99 —— 报表列 type 联合含 | 'user' | 'owner'。

    顺带一提:fields-form-hosted.test.tsx 的文件头自陈这批 fields-* 例子是「the docs site's field demos and a few-shot retrieval source for AI authors, so what they show is what gets copied」—— 即第 4、5 条不只是文档,还是 AI 作者的抄写样本。

    关键新事实:方案 A 按字面执行是静默降级,现有门只红一半

    这一条决定了它不是「删三行」:

    • 表单侧无门。mapFieldTypeToFormType 的未知类型兜底是 packages/fields/src/field-type-alias.ts:155 的 return typeMap[fieldType] || 'field:text';,resolveFormWidgetType 的兜底是 packages/fields/src/index.tsx:2486 的 return fieldWidgetMap[mapped] ? mapped : 'text'; —— 两者都是静默的,不告警不报错。而 field-type-coverage.test.ts 的 FORM_WIDGET_TYPES 名单里没有 owner(也没有 user),所以删表单别名不会让它红。
    • 于是第 4 条那个目录例子会从人员选择器静默退化成一个普通文本框,而 fields-form-hosted.test.tsx 依然绿 —— 它只断言 form 元素存在、[data-field="record_owner"] 存在、label 文本 "Owner" 落在该字段的 chrome 里,一个 TextField 全部满足。文档站会展示一个错的示例,而没有任何门会报。
    • 单元格侧有门且会红(上面第 3 条)。即现有测试对「删 cell renderer」变红、对「删表单别名」不变红 —— 两半方向相反。

    这也顺带回答了派发里「cell renderer 按测量定夺」:它不但被 field-type-coverage.test.ts:48 钉住,还有文档(第 7 条)把 owner 当作报表/表格列类型宣传,即数据侧 owner 列确实会流到单元格路径。读数支持保留 cell renderer。

    objectstack 的 4 处 type: 'owner' 不是消费者(不同命名空间)

    packages/spec/src/conversions/registry.ts:354,365、packages/spec/src/security/sharing.test.ts:200、content/docs/permissions/permissions-matrix.mdx:184、content/docs/protocol/objectql/security.mdx:387 —— 全部是 sharing rule 的 type 判别式(sharingRules[].type),与字段 widget 类型无关。附带一提,那个 sharing 的 owner 规则形状自己也已经退役(sharing.test.ts:194 "should reject the retired owner-based rule shape")。同理,objectui 里 role === 'owner'(组织成员角色)与 "owner" 作字段名(dashboard / grid / report 示例)也都不算消费者,已逐条排除。

    交回决策箱的问题

    方案 A 的前提已被证伪,「A 还是 B」需重裁,且无论走哪条都多出一个迁移决定:

    1. 目录例子 + 三处文档类型联合怎么处置? 若仍走 A,record-owner-read-only.json 必须同批迁到 type: 'user' —— 但那样它就和同目录的 single-user-selection 重复、失去存在理由,可能该连同 user.mdx 的 "Owner Field" 小节整条删除;三处公开类型联合也要去掉 'owner'。这是用户可见的文档契约收缩,不该由实施 agent 自行定夺。
    2. 静默兜底要不要一并堵? 即便退役,field:text 静默兜底意味着任何存量手写 type: 'owner' 都会无声退化成文本框。要不要同批给「已退役 widget 名」加显式告警或拒绝(参照 field:capability-multiselect 的 TOMBSTONE 约定)?否则退役对线上手写 SDUI 是无声破坏 —— 而无声正是 ADR-0049 要消灭的那类失效。
    3. paramToField.ts:94 这个第三消费者要不要纳入同一张单?它与 plugin-grid 那处必须同进同退,否则两个 param 面再次漂移 —— 正是 bulkParamToField 的注释想防的。

    grid TSDoc 那一条(裁定说「无论 A/B 都改」)

    已独立复核,零消费者属实:packages/fields/src/widgets/types.ts:166-167 的 dataSource TSDoc 写着 "(lookup / user / object-ref / recipient-picker / grid)",而 packages/fields/src/widgets/GridField.tsx 里 grep 不到 dataSource(零命中)。这一条与 owner 的 A/B 完全解耦,且只是注释。因为本单主体已停手,我没有单独为它开 PR(免得一个只改注释的 PR 挂在一张处置未定的单上),建议决策箱要么并入重裁后的批次,要么单独派一张小单 —— 请指示。


    Generated by Claude Code

  5. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    ContributorAuthor

    【PM 处置 · 测量证伪前提,转回决策箱】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt):pm:queue → needs-user-decision。dev 按 2026-08-16 裁定(评论 5307574997)的停手条款执行正确 —— 零消费者前置不成立,零代码零 PR 零 worktree,完整命中清单见 dev 评论 5308145625。

    测量要点:①代码消费者 3 处,含裁定与正文都未列出的第三面 packages/app-shell/src/utils/paramToField.ts:94(plugin-grid bulkParamToField 的孪生,注释自陈互为镜像);②文档/示例消费者 4 处,其中 record-owner-read-only.json 被 content/docs/fields/user.mdx 真实渲染、被 hosted 测试实跑,且该测试文件自陈是 AI 作者的 few-shot 检索源;③方案 A 按字面执行是静默降级:表单别名走 field:text 兜底,删后无任何门变红、catalog 测试空绿(dev 已做红绿不对称预判:cell renderer 有钉、表单别名无钉);④objectstack 仓 owner-as-widget 真零(带 field:user 邻近词反查);⑤ cloud 仓未扫(凭据无权限),该面是空白不是绿。

    重裁一问(dev 建议 B,PM 附议):

    • A′ 完整退役:删 3 处代码消费者 + 迁移/删除 catalog 例子 + 收缩三处公开文档类型联合 + 必须为已退役键加显式拒绝/告警(否则 AI 作者从文档抄 type: 'owner' 无声得到文本框 —— A 不带显式失败等于亲手新增一类肉眼才能发现的元数据错误);
    • B 承认 widget-hint-only:补进表单 data-source 规则 + 登记 widgetHintOnly: true,同批修掉 dependsOnLabels 无兜底(objectstack#5407 同源洞)。enforce-or-remove 的 enforce 侧同样合规,且此处 enforce 明显更便宜 —— owner 不是「无人用的顺手别名」,是 4 处公开契约在宣传的词汇。

    附属两件随主裁:paramToField.ts:94 无论 A′/B 都与 plugin-grid 同单同进退(拆单必产生两面判断相反的时间窗);types.ts grid TSDoc 更正(零消费者已独立复核属实)并入重裁后批次。关联:#4815(Blocked-by 本单,继续持有)、#4855(dev 顺手立的 FORM_WIDGET_TYPES 守卫缺口 finding,排重裁后)。


    Generated by Claude Code

  6. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-17, recorded by PM session session_01DbRmJD3iPhXjKr6vhd4Qkv). The maintainer reviewed the re-framed options after the fork report (comment 5308145625) and the four-card analysis presented in PM chat, and approved its recommendations, verbatim: 「同意」.

    Ruled: Option A′ — complete retirement of the owner widget alias, with a loud tombstone.

    This supersedes the 2026-08-16 Option A ruling (whose zero-consumer premise the fork report falsified) and decides against the dev/PM preference for B recorded above. Deciding rationale on record: the spec's closed FieldType (48 members) has no owner; the word carries zero behavioral delta over user (same widget); and this card's own measurement history — three code faces already drifted three ways on this one word — is the standing evidence that every extra synonym is another drift channel. A smaller closed vocabulary with loud refusal beats teaching AI authors two spellings for one thing; the same idiom survives verbatim as { type: 'user', name: 'owner' }.

    Scope, all one batch:

    1. All three code consumers retire together: the field-type-alias.ts mapping, the index.tsx field:owner registration, plugin-grid's USER_WIDGET_TYPES membership — plus the third face packages/app-shell/src/utils/paramToField.ts:94, same batch (the two param faces move together, never split).
    2. Loud tombstone: the retired spelling gets an explicit refusal/warning per the field:capability-multiselect TOMBSTONE convention — ⛔ the silent field:text fallback must not be the retirement's failure mode. This is also the answer to the unscanned cloud surface: any unknown consumer fails loudly instead of silently degrading to a text box.
    3. Docs/catalog contract shrink: record-owner-read-only.json migrates to type: 'user' or is removed together with user.mdx's "Owner Field" section (implementer judges the dedup against single-user-selection and states the choice in the PR); the three public doc type unions (user.mdx, filter-builder.mdx, report-schema.mdx) drop 'owner'.
    4. Cell renderer: disposed consistently with the retirement — field:owner/owner registrations removed and field-type-coverage.test.ts expectations updated in the same PR.

    The grid TSDoc zero-consumer correction (packages/fields/src/widgets/types.ts:166) rides the same batch, per the standing half of the prior ruling. Downstream: #4815 stays Blocked-by this card until it closes; #4855's premise gets re-read after this lands.

    Also clearing the stale assignee left from the pre-fork claim (that claim ended with the fork report; comment 5308167330 flipped the label but left the assignee — half-state repair). Label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  7. self-assigned this
    on Aug 17, 2026
  8. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Claim: PM loop round 2
    Session: session_01DbRmJD3iPhXjKr6vhd4Qkv
    Branch: claude/issue-4814-retire-owner-tombstone
    Worktree: objectui-issue-4814
    Domain: repo:objectui
    File surface: packages/fields/src/field-type-alias.ts + packages/fields/src/index.tsx + packages/fields/src/field-type-coverage.test.ts + packages/fields/src/widgets/types.ts (grid TSDoc line) + packages/plugin-grid/src/components/bulkParamToField.ts + packages/app-shell/src/utils/paramToField.ts + examples/schema-catalog (the fields-user/record-owner-read-only example + its index entries) + content/docs/fields/user.mdx / content/docs/components/complex/filter-builder.mdx / content/docs/core/report-schema.mdx + tests (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Serial constraints cleared: the #4648 same-package serial (plugin-grid registration surface) is resolved — PR #4901 MERGED 03:05Z, branch from post-merge main. In-flight checked this round: #4636 (lockfile + package.json ranges only), #4886 (sibling seat, app-shell/views/metadata-admin/ResourceEditPage.tsx), #4003 / PR #4892 (examples/schema-catalog — its 100-file list grepped for fields-user / record-owner-read-only: zero overlap), #4884 / PR #4888 (plugin-gantt) — all disjoint from this surface. Prior claim on this card (branch claude/issue-4814-retire-owner-alias, session session_01GTRjn8xBqp75dk7kFupVRt) ended with the fork report at comment 5308145625 — zero code, zero PR, zero worktree per the PM disposition; this is a fresh dispatch under the superseding A′ ruling, new branch name to avoid any stale-ref ambiguity.


    Generated by Claude Code

  9. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator
    {
      "issue": 4814,
      "status": "done",
      "branch": "claude/issue-4814-retire-owner-tombstone",
      "pr": "https://github.com/objectstack-ai/objectui/pull/4915",
      "premise_still_valid": true,
      "summary": "Implemented ruling A' in full: the `owner` field-type alias, its `field:owner` widget-map entry, its cell-renderer registrations, and both param faces (plugin-grid `bulkParamToField` + app-shell `paramToField`) are retired together; the catalog example migrates to `type: 'user'` and the three public doc type unions drop `'owner'`; the zero-consumer `grid` claim in the `dataSource` TSDoc is corrected (independently re-verified: zero `dataSource` occurrences in GridField.tsx). The tombstone is LOUD as ruled: because `form.tsx`'s `renderFieldComponent` resolves both `type: 'owner'` and `widget: 'field:owner'` through the same `ComponentRegistry.get('field:owner')` lookup, one registered tombstone widget catches both spellings with a visible refusal, plus a console prescription deduped once per spelling. Two caveats the PM should weigh. (1) The A' premise stands and is strengthened, but the ruling's SCOPE enumeration is materially incomplete: re-sweeping for `owner` in the field-type namespace (not the field NAME) finds ~13 further faces across 8 packages, including a published runtime Zod enum in `@object-ui/types` (`reports.zod.ts:49`) that still ACCEPTS `type: 'owner'`, and two published TS unions that are the code twins of the very doc unions this PR shrinks — so docs and code now disagree in the opposite direction. All were present at the fork report's own base commit `1ef236e18`, i.e. pre-existing faces the earlier sweep did not reach, NOT new consumers (checked with `git show 1ef236e18:<path>`), so this is not the dispatch's new-consumer stop-and-report trigger. Filed as #4914 rather than silently widening the batch into 8 unclaimed packages. (2) One face beyond the ruled list IS included because it turned red here and its own drift guard prescribed the fix: `app-shell/src/utils/paramValueShape.ts` declared a value shape for `owner`, and its guard 'no stale contract entries pointing at removed widget types' failed the moment `owner` left FORM_FIELD_TYPES. Also correcting the fork report on one point: it concluded the form side has NO gate that reddens on the alias removal. Removing the `fieldWidgetMap` ENTRY does redden two — `widget-aria-invalid-registry-e2e` and `widget-dom-leak-e2e`, both of which pin a local widget inventory against FORM_FIELD_TYPES. Only the alias-table half was ungated. File surface stayed within the claim (all extra files are tests, covered by the claim's '+ tests'), except paramValueShape.ts as described.",
      "tests": "Union run at HEAD=59b30a100 (= final commit = pushed remote head; PR head sha confirmed 59b30a100 after a second push). Repo-root path-filter vitest only; heavy runs under `flock -E 99 -w 540 /tmp/os-heavy-verify.lock`. Dependency closures built BEFORE judging — the first typecheck pass failed only on missing dist/*.d.ts (`Cannot find module '@object-ui/fields'`) and went green after `--filter '@object-ui/plugin-grid^...' --filter '@object-ui/app-shell^...' build`. RESULTS: packages/fields/ -> 'Test Files 100 passed (100) / Tests 1689 passed (1689)'; examples/schema-catalog/ -> 'Test Files 9 passed (9) / Tests 1589 passed (1589)' (fields-form-hosted green under the migrated example); app-shell/src/utils + plugin-grid param tests -> 'Test Files 23 passed (23) / Tests 377 passed (377)'. type-check (hyphenated script name, echoed in output so no zero-match trap) for @object-ui/fields, @object-ui/plugin-grid, @object-ui/app-shell -> all 'Done'. Gates: check:doc-types (143 mdx, 632 code blocks, 558 type literals) OK; check:control-bytes (4389 tracked text files) OK; docs:check-links (13 scan roots) OK; changeset:check OK. Also grepped for references to the renamed 'Owner Field' heading anchor: zero. REVERSE VERIFICATION, direction predicted first — predicted the ablation (removing the retired-type branch + tombstone registration, i.e. the naive 'just delete the alias' execution) would redden the new pin ONLY on the loudness assertions while pre-existing gates stayed green. Observed exactly that: 5 failures, all loudness — \"expected 'field:text' not to be 'field:text'\", \"expected 'text' not to be 'text'\", \"expected '' to contain '`owner`'\", the dedupe-count assertion, and \"expected undefined to be [Function RetiredFieldTombstone]\" — while field-type-coverage.test.ts stayed GREEN in the same run ('Test Files 1 failed | 1 passed (2)'), reproducing the fork report's central claim that no existing gate catches a silent retirement. Ablation restored byte-identically from the commit via `git checkout <branch> -- <paths>` (empty `git diff HEAD`). No dist-ablation concern: these suites import from source, not dist.",
      "open_questions": [
        {
          "question": "The A' ruling scoped 'all three code consumers' + the fourth param face, but the measured field-type consumer surface is ~15 faces across 8 packages. The three published contract twins in particular (`@object-ui/types` reports.zod.ts runtime enum, reports.ts, field-types.ts) now bless a spelling the renderer refuses. Should those ride a fast follow-up, or be re-adjudicated together with the drift-channel argument that decided A'?",
          "options": [
            "A. Fast-follow #4914 items 1-3 only (the published TS/Zod twins) as a small dedicated card — closes the docs/code contradiction this PR introduces, leaves the 12 dead predicate branches for later cleanup",
            "B. Fast-follow all of #4914 in one PR — mechanically similar work, one review, but 8 packages of collision surface at once",
            "C. Leave as filed and let normal triage schedule it — the tombstone already makes any surviving `owner` fail loudly, so nothing is silently broken meanwhile"
          ],
          "recommendation": "A, because it is the only half that is an active contradiction rather than dead code: `reports.zod.ts` is a PUBLISHED runtime validator that accepts `type: 'owner'` while `@object-ui/fields` now refuses it, and `field-types.ts` still offers `owner` in published .d.ts autocomplete — so an author is told 'legal' by the types package and 'retired' by the renderer, which is the declared-but-unenforced shape ADR-0049 targets and exactly the drift the A' ruling was chartered to end. Items 4-15 are inert by comparison (branches that can no longer match) and can wait for ordinary triage. Note the types/spec question overlaps #2231, so A may want to be scoped as a sub-issue of that rather than standalone."
        }
      ],
      "out_of_scope_findings": [
        "filed as #4914: `owner` survives as a live field-type in ~13 more code faces across 8 packages after this retirement — including a published runtime Zod enum (`packages/types/src/zod/reports.zod.ts:49`) that still accepts it and two published TS unions (`reports.ts:105`, `field-types.ts:584`) that are the code twins of the doc unions this PR shrinks; plus 8 dead predicate branches (filter-builder, InlineFieldInput, buildDefaultPageSchema, view-config-utils, ObjectDataTable, recordFields, UserFilters, FieldEditWidget) and 4 test fixtures. All verified present at the fork report's base commit 1ef236e18, so pre-existing rather than new."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  10. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    ACCEPT — PR #4915 (reviewer of record: PM session session_01DbRmJD3iPhXjKr6vhd4Qkv; readings taken on GitHub at head 59b30a10, not from the report).

    Ready-flip + auto-merge after the six gate jobs report success at this head — a pointed check is armed.


    Generated by Claude Code

  11. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Patch round on PR #4915 — CI Test shard 4/4 fixed. New head abbc8173e.

    Same claim, same branch, no restart. The failure was real and caused by this PR — a fifth stale-inventory face, same class as the four already fixed, in a package I never ran.

    What it was, and why the snapshot edit is not "forcing"

    packages/plugin-detail/src/__tests__/inlineEditTypeCoverage.test.tsx — "the four buckets, as landed".

    Read before touching, as asked. The snapshot is derived from ALL_TYPES, defined at the top of that file as FORM_FIELD_TYPES ∪ spec FieldType.options. owner left both halves: this PR removed it from FORM_FIELD_TYPES, and it was never a member of the spec enum — which is this card's own founding premise. So owner does not move between buckets, it leaves the universe the guard quantifies over. - "owner" from routed is the complete delta, and the edit records a derived reality rather than overriding one.

    Two spots, not the one reported:

    1. :234 (now :243) — the routed array in the snapshot.
    2. :77 — the universe rationale comment, which still named owner among "the form-only keys the spec enum misses". That sentence became false in this PR and would have misinformed the next reader about why the union exists. Refreshed with the same one-place-per-fact mirror style used in field-type-coverage.test.ts and the two e2e inventories, pointing at owner-retired.test.tsx and objectui#4814.

    The thing you asked me to say rather than force

    INLINE_ROUTED_FIELD_TYPES (packages/plugin-detail/src/InlineFieldInput.tsx:107) still contains 'owner'. The snapshot does not derive from it, so I left it alone and said so in a comment at the snapshot instead.

    But sharpening what I filed earlier: this member is not merely dead code. I had classified InlineFieldInput under "8 dead predicate branches" in #4914. That classification is too generous. Because the set is consulted at runtime against whatever type a stored field actually carries, a record whose field is still typed owner would get inline-edit routing to the user picker, while the record form now shows the tombstone refusal. That is a live inconsistency between two edit surfaces for the same field, not an unreachable branch. Same for InlineFieldInput.tsx:290's editType === 'user' || editType === 'owner'.

    It is still out of this card's ruled scope and outside the claimed file surface, so I have not changed it — but #4914 item 5 should be read as more urgent than the "inert" framing in my main report, and it strengthens the case for option A in that report's open_questions.

    Readings at abbc8173e

    packages/plugin-detail/src/__tests__/inlineEditTypeCoverage.test.tsx
        Test Files  1 passed (1)   Tests  66 passed (66)
    
    packages/plugin-detail/  (full package, as instructed)
        Test Files  81 passed (81)  Tests  787 passed (787)
    
    check:control-bytes   4389 tracked text files   OK
    

    The targeted file passing is itself the proof it resolved the new FORM_FIELD_TYPES: had it read a stale build still containing owner, ALL_TYPES would have re-included it and the edited snapshot would have failed in the opposite direction.

    No changeset impact — tests-only delta on a PR whose changeset is already declared. Prior union at 59b30a100 is unaffected by a test-file edit in a package outside it; the scanning gate was re-run at the new head as shown.


    Generated by Claude Code


    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

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions