Skip to content

spec: a metric-family dashboard widget (metric / kpi / gauge / solid-gauge / bullet) declares exactly one measure — narrow DashboardWidgetSchema.values for the tile family (objectui#8894 ruling D) #17779

Closed

Description

@os-tesla

Filed by the director seat on the maintainer's ruling for objectui#8894 (decision batch #119 item 4, 2026-09-12: 「同意」 to D — judge the protocol wrong: a metric-family widget takes exactly one measure). ⛔ packages/spec is the domain:spec seat's; the director seat writes no repo file.

Governing text

  • Maintainer standing ruling (2026-09-10): 「协议不正确的应该先修改协议。」
  • objectui#8894 / objectui#7293 defect 1 — measured: DashboardWidgetSchema.values is z.array(z.string()).min(1) with no upper bound, so an author may declare three measures on a metric tile; the query runs all three and the tile renders values[0] only. PR objectui#8887 (merged) added the sub-caption; the seat's second half (merged, Part of #8894) makes the tile SAY that the extra measures are not rendered. What remained was whether to invent display semantics for values[1..] or to narrow the protocol.

The defect in the protocol

For the metric family (metric / kpi / gauge / solid-gauge / bullet, and any dimensionless widget the renderer treats as a tile) the declared accept set admits a shape with no rendering semantics. A metric tile answers one number; that is what the family means on every mainstream dashboard product (a single-value card is one measure; several numbers are a different visual). A protocol that admits three measures on a one-number tile is the declared≠delivered shape, kept alive by a runtime warning.

What to do

  1. DashboardWidgetSchema: for the metric family, values is exactly one measure — a superRefine on the widget's type, or a per-type schema arm, whichever the spec seat judges keeps the union diagnostics legible (see view-union-diagnostics.test.ts). The refusal names the widget and says one measure per tile, "make N tiles for N measures".
  2. major changeset with an ADR-0087 semantic migration entry: a stored metric widget carrying N measures → keep values[0] on this tile and emit a structured TODO naming the dropped measures (splitting into N tiles is a layout decision the migration cannot take).
  3. Liveness / conformance: objectui's contract twins gain the refusal pin; the runtime warning objectui landed becomes the door refusal once the spec ships (objectui#8894 is pm:blocked on this card).
  4. Clause-②: no (narrowing).

⛔ Confidence gaps carried

How many stored dashboards declare more than one measure on a metric tile is unmeasured (the seat's P1 premise "multi-measure is rare" was never surveyed); objectstack-ai/duly#109's wish for several numbers on one tile is unmeasured and, under this ruling, is a request for a different visual, not for this one.

Refs

objectui#8894 (5619308792) · objectui#7293 · objectui#8887 · packages/spec/src/ui/dashboard.zod.ts (locate values by symbol)

Filed by the director seat, session_01QsCVSivtpwT6ZXs5Rtvqxe, 2026-09-12T02:5xZ, with Claude Code.


Generated by Claude Code

Activity

  1. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    Claim: PM loop round 9
    Session: session_01JbZnqu8bt6YqfJsr9vaFb3
    Branch: claude/issue-17779-metric-family-single-measure
    Worktree: objectstack-issue-17779
    Domain: domain:spec
    Seat: domain:spec#2(座位贴 #18549;席 1 是 #6017,本认领不碰它)
    File surface: packages/spec/src/ui/dashboard.zod.ts 及其钉子。⚠️ 另开一条:packages/spec/src/migrations/entries/semantic/18.<你定的 id>.ts(新建一个文件)与它欠的重生成一圈(gen:migration-registry,以及 README 点名仍在版本管理里的两个投影)。⚠️ 开放并预先申报:.changeset/*.md 与任何门禁反向要求的派生物。只读:packages/spec/src/ui/view.zod.ts(⚠️ 本席的 PR #18695 正持有它)与 view-union-diagnostics.test.ts(stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier
    Clause-②: yes (narrowing)
    Thread-read: none
    Serial constraints cleared: ⏱️ 本行读数均读于本评论同一动作,2026-09-17T16:02Z。packages/spec/src/ui/dashboard.zod.ts 最近一次触碰是 97233b90ca(#18505,已落地),无在飞持有者。⛔ 不碰 packages/spec/src/ui/view.zod.ts(本席 PR #18695 在队列中持有)· kernel/platform-capabilities.ts(本席 PR #18694)· src/ui/component.zod.ts(席 1 #18305)· shared/expression.zod.ts(os-litant #15811)。

    ⏱️ 声明已改(席位):no → yes (widening)。 --pair 18720 读 exit 4 / C5,点名两条 widening tell,本席逐条验过:

    • T3 是真的:packages/spec/api-surface/ui.json:446 新增一行 "checkDashboardWidgetMetricMeasureArity (function)" —— 已发布入口点的导出清单长了一行。⭐ 而契约复核章程的机械地板就是这一句:「新导出符号或已发布载荷上的新键恒 yes」。⇒ 这不是判断题,是地板。
    • T1 是假的:dashboard.zod.ts:628 是 export function checkDashboardWidgetMetricMeasureArity( 的第二个参数 ctx: z.RefinementCtx,,现场根本没有对象字面量。本席打印了 :624-632 逐行确认。⇒ 已按该脚本自己的处方另立卡(matcher 修复不在本 PR 文件面内)。

    ⇒ needs:contract-review 已同笔挂在卡与 PR 两侧,隔离达档复核已起。changeset 本来就是 minor,所以代码与 changeset 一字不动。⭐ dev 指出这件事而没有自己去改声明(声明住在认领里,那是席位的),处置正确。


    裁定与范围

    维护者对 objectui#8894 取 D(决裁批 #119 item 4,2026-09-12「同意」):判协议错了 —— metric 族部件只取一个 measure。⛔ 方向不可重裁;你做的是执行。

    卡面第 1–4 条就是范围:DashboardWidgetSchema 对 metric 族(metric / kpi / gauge / solid-gauge / bullet)把 values 收成恰一个;major changeset + 一条 ADR-0087 语义迁移条目;拒绝语要点名部件并说「一 tile 一 measure,N 个 measure 就做 N 个 tile」。

    🚨 迁移条目:目录是分片的,但有一条相邻就弹 PR 的硬规则

    packages/spec/src/migrations/entries/ 是一条目一文件(597 个文件,#6957 裁定),README 逐字:「Touch no other file, and never edit inside the markers」。⇒ 新建你自己的条目文件,⛔ 永不手改 registry.ts 的 <os-generated …> 区内(那是 gen:migration-registry 的输出)。

    ⚠️ 而这一条你必须自己查:README 记的 #8344 实测表说,两条在飞的登记 merge clean 且与重生成逐字节相同 —— 除非两个 id 在排序上相邻(中间没有别的条目),或同为某个 major 的第一条;那两种情形队列会把 PR 弹出。

    ⇒ 定下 id 之后、提交之前,列一遍 18.* 的 semantic 目录,确认你的 id 的前后邻居都不是在飞的登记。⚠️ 本席的 PR #18695 此刻正在队列里登记 18.ui-list-view-groupbyfield-padded-refused —— 按字母序它在 18.u…,而你的大概率落在 18.d…,不相邻;但这是本席的预期,⛔ 不是读数,由你当场验。

    本席答不了的三件,写成给 dev 的问题,⛔ 不写成栅栏

    1. superRefine 还是 per-type 分支? 卡面把这个判断明确留给 spec 席(「whichever the spec seat judges keeps the union diagnostics legible」)。⇒ 去读 view-union-diagnostics.test.ts,量两种写法对联合诊断可读性的影响,再定并说明理由。⛔ 本席不替你选。
    2. 🚨 major 撞不撞 check-changeset-no-major? 卡面第 2 条要 major changeset,而本仓库有一个叫 check-changeset-no-major 的门禁。⇒ 先确认这两者怎么共存(是有例外通道?还是该 changeset 其实不该是 major?),⛔ 不要一边写 major 一边等门禁红。⚠️ 这正是本席上一轮栽过的「写出不可满足的约束」——所以本席不替你定,请你测了回报。
    3. 迁移条目的语义:卡面写「保留 values[0] 并发一条结构化 TODO 点名被丢掉的 measure(拆成 N 个 tile 是布局决定,迁移接不了)」。⇒ 这句要落成条目的 replacement / acceptanceCriteria,而验收线要按代码实际拒什么重新推导,⛔ 不是复述卡面。

    验收上必须有的两个控

    • ⭐ LIT:一个合法的单 measure metric tile 在改动前后都照常解析;以及非 metric 族的部件(多 measure 合法的那些)不受影响 —— 给出前后对照。
    • ⭐ DARK:某个必须读 0 的东西 —— 例如 dashboard.zod.ts 里其它带 .min(1) 的数组键不得被这次收窄波及(给出路径与计数,⛔ 不是「应该没事」)。

    ⭐ 本轮章程增量(在飞期间落的,你的工作树可能读不到)

    011121238d(⏱️ 读于 2026-09-17T16:02Z)(PR #18659):PR 正文 dev 只写一次,在开 PR 那一笔,⛔ 不 PATCH;事后要改的在报告里点名改法、本席代写。且正文行首照抄 Clause-②: 行——Check Changeset 读正文不读卡。

    查重

    MCP search_issues 含 closed:本卡是 director 席执行 objectui#8894 裁决 D 的上游 spec 半,objectui#8894 自身 pm:blocked 在它上面 ⇒ ⛔ 无孪生。


    Generated by Claude Code

  2. self-assigned this
    on Sep 17, 2026
  3. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    os-dev-report
    {
    "issue": 17779,
    "status": "done",
    "branch": "claude/issue-17779-metric-family-single-measure",
    "pr": "#18720",
    "session": "session_01JbZnqu8bt6YqfJsr9vaFb3",
    "premise_still_valid": true,
    "summary": "Verified the card's premise on origin/main first: DashboardWidgetSchema.values was z.array(z.string()).min(1) with no upper bound on ANY widget type, and a metric/kpi/gauge/solid-gauge/bullet widget carrying 2-3 measures returned success:true on all five (bogusProp refused by name on the same call as the lit control). Executed ruling D as a new exported object-level check, checkDashboardWidgetMetricMeasureArity, chained onto the same strict door by identifier beside checkDashboardWidgetStageOrder: it refuses more than one measure on the five metric-family types and on a typeless widget (type defaults to 'metric'), with one custom issue at values naming the widget id, the count and the authored type, prescribing one measure per tile and 'make N tiles for N measures'. Shipped with a minor + BREAKING changeset carrying the ADR-0087 marker, one new sharded semantic entry (18.dashboard-widget-metric-family-multi-measure-refused) with its gen:migration-registry lap, the three artifacts check:generated proved stale, and pins in dashboard.test.ts plus the object-refinement-check-exports.test.ts catalogue. Assignee on the card was already set by the dispatch (os-bill); I wrote none.",
    "tests": "ABLATION (red before green, on-disk proof + hash-verified restore): mutated ONLY the '.superRefine(checkDashboardWidgetMetricMeasureArity)' attachment away, leaving the function declared. HEAD blob 30c6d785e7971edbf15cb962639493f352bfde2c == worktree blob before the mutation; anchor grep BEFORE=1 AFTER=0, injected line=1; mutated blob 90548649227c3971f16b7dc85b02e1bab8155f96 (differs, so the edit landed). RED leg: vitest exit=1, 17 failed | 205 passed (222). Restore via 'git checkout HEAD -- PATH' (never a bare checkout): restored blob 30c6d785e7... == HEAD blob AND 'git diff HEAD' on the path empty. GREEN leg: vitest exit=0, 222 passed (222). The script carried trap RESTORE EXIT INT TERM against an absolute git rev-parse --show-toplevel path. | SUITES: pnpm --filter @objectstack/spec test :: exit 0 (486 files / 13933 tests passed, under os-verify-lock.sh, VERDICT command-exit 0, held 142s waited 13s). pnpm --filter @objectstack/spec typecheck :: exit 0 (includes check:scripts-typecheck and check:test-typecheck; the shrink-only test-layer ledger held at 54 files / 259 errors / 144 pinned signatures). | GENERATED: pnpm --filter @objectstack/spec build :: exit 0 (under the lock) then check:generated :: first run exit 1 naming exactly 3 stale (api-surface/, export-origins/, content/docs/references/), regenerated those 3 only, re-run :: exit 0, 'All 15 generated artifacts are up to date.' check:authorable-surface green with NO regeneration => 0 authorable keys move; check:api-surface reported '0 breaking (removed/narrowed), 1 added'. | CHANGESET GATES: check-adr-0087-registration --base origin/main :: exit 0, reading the changeset back as '[BREAKING+bang+clause-2-narrowing] registered dashboard-widget-metric-family-multi-measure-refused'; its --self-test :: exit 0 (384 assertions); check-changeset-no-major --base origin/main :: exit 0; check-empty-changeset --base origin/main :: exit 0. | OTHER GATES, each exit 0: check:nul-bytes (8812 text files, no raw control bytes), check:widget-option-census, check:liveness, check:exported-any, check:dual-source-exports, check:entry-nameability, check:empty-state, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:merge-driver, check:pm-widening-tells (self-test), check-spec-docblock-symbol-anchors, check:dts-closure, check:published-files, check:spec-parsed-alias, check:page-declaration-shape, check:corpus-claim-drift, check:skill-examples, check:docs-transcript-drift, check:doc-formula-expressions, check:variant-docs, check:llms-txt, check:yaml-examples, check:objectui-pin-citations, plus the ten doc gates the regenerated .mdx newly derives (doc-anchors, docs-single-h1, docs-spec-enumerations, quick-reference-counts, check-doc-frontmatter, check-docs-section-name, check-section-landing-index, docs-audit/check-affected-docs, docs-redirects, docs-audit-scope). check:skill-examples and check:docs-transcript-drift FIRST returned PREREQUISITE-NOT-MET (exit 1 / exit 3: client-react and lint not built) — NOT recorded as failures; I built those two closures under the lock and re-ran both to a real exit 0. | LINT: repo-wide, NOT a narrowing — 'node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config --format json' at ea17ab8 (the final commit), 81s, 6822 files linted, 0 errors, 0 warnings, exit 0. | LIT CONTROL: 16 bodies through safeParse before/after — a 1-measure metric tile ACCEPTs identically (values ['amount_sum']) both sides; bar/line/table/pivot/funnel with 3 measures ACCEPT both sides; values: [] keeps its single values:too_small both sides; type:'ziggurat' keeps type:invalid_value ALONE both sides; bogusProp keeps unrecognized_keys both sides. Only the five family types (plus the typeless default) flip ACCEPT -> REFUSE values:custom. | DARK CONTROL (must read 0): OTHER .min(1) ARRAY keys in packages/spec/src/ui/dashboard.zod.ts = 0 — the file has exactly two .min(1) code sites at the branch point, values (line 706) and dashboard.columns (line 1151, z.number().int().min(1).max(24), a number bound, byte-identical after); every other new .min(1) occurrence in the file is inside a docblock. Sibling packages/spec/src/ui/report.zod.ts values (lines 237, 314) is a separate optional() declaration with no .min(1) and no arity check, and its type enum (tabular/summary/matrix/joined) holds 0 metric-family members — untouched. Fleet census over every tracked .ts/.tsx/.json/.mdx/.md/.yaml AT THE BRANCH POINT 72dd95f: 187 brace-local literals carrying a values: [...], 39 of them on a metric-family type (both lit controls), 0 of those with more than one measure. On this branch the same scan reads 205 / 49 / 7, and all 7 are fixtures this PR added. | NOT MEASURED: the 90 derived-but-unrun families dispatch-gates --ran reports (most are whole-tree or checker-health rows; the spec ones are covered by the check:generated aggregate I ran but recorded as the aggregate), and CI convergence on PR #18720 — I did not wait for CI.",
    "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl + GITHUB_TOKEN",
    "api_writes": "3 REST writes + 4 git pushes. REST: POST /repos/objectstack-ai/objectstack/pulls (draft PR #18720); POST /repos//issues/18720/labels (additive: domain:spec, priority:p3, protocol:ui, protocol:breaking — read back and diffed, none stripped, final set is documentation/domain:spec/priority:p3/protocol:breaking/protocol:ui/size/l/tests/tooling); POST /repos//issues/17779/comments (this os-dev-report). git push: the pre-edit empty-branch routing probe (no 403) plus one per commit (3). NO PATCH of the PR body — it was written once at create and read back byte-identical apart from the trailing newline the platform strips (0 line diffs, exactly one session-URL footer under the rule line).",
    "open_questions": [
    {
    "question": "node scripts/pm/check-clause2-carriers.mjs --pair 18720 exits 4 with a C5 row: the CARD's declaration Clause-②: no disagrees with 2 widening tells on the diff. One tell is TRUE and one is FALSE, and the true one is not mine to re-declare — C5 judges the CARD's claim comment ('the declaration limb is judged from the card'), which is the dispatch's own comment 5717438611, and I ⛔ never write a claim. TRUE tell: T3 at packages/spec/api-surface/ui.json:446, '+ "checkDashboardWidgetMetricMeasureArity (function)"' — a new row in a published entry point's export listing, so the public export surface grows (ADR-0059). FALSE tell: T1 at packages/spec/src/ui/dashboard.zod.ts:628 on '+ ctx: z.RefinementCtx,' — that is a FUNCTION PARAMETER annotation in an export function ...( list, not a key on a Zod object schema (see the separate finding). The changeset is already minor, which is what a yes would require, so check-changeset-no-major's level axis is satisfied under EITHER reading and nothing else moves.",
    "options": [
    "A — PM re-declares the card's claim as Clause-②: yes (widening) (T3 is a real ADR-0059 export-surface growth) and, if wanted, has the seat restate the narrowing in prose; the minor changeset already satisfies the level axis, so no code or changeset change is needed.",
    "B — PM keeps Clause-②: no on the ground that clause ② is about a new KEY ON A PUBLISHED PAYLOAD (no authorable key moves here — check:authorable-surface is green with no regeneration) and records the C5 exit 4 as a known, accepted disagreement for this class of PR.",
    "C — treat T3-on-a-new-exported-refinement-check as its own matcher question and file it alongside the T1 repair."
    ],
    "recommendation": "A, because T3 is true by its own declared definition and re-declaring costs nothing here: the changeset is already minor, so the level axis passes either way, and yes is the reading that makes the C5 row go quiet without weakening any gate. ⛔ I did not act on it — the declaration lives in the PM's claim comment and the PR body is a one-shot write I will not PATCH. If PM prefers B, the exit 4 stays and should be named in the landing note so the next reader does not treat it as an unread red."
    }
    ],
    "out_of_scope_findings": [
    "to file (class a, reproducible defect; dedupe words: check-widening-tells, T1, RefinementCtx, false-positive, parameter-annotation): scripts/pm/check-widening-tells.mjs's T1 matcher reads a FUNCTION PARAMETER annotation as 'a new key on a Zod object schema'. Standalone repro on this tree: git diff 72dd95fa5a..HEAD -- packages/spec/src/ui/dashboard.zod.ts > d.patch && node scripts/pm/check-widening-tells.mjs --declaration no --diff d.patch :: exit 4, one row, 'T1 packages/spec/src/ui/dashboard.zod.ts:628 ... + ctx: z.RefinementCtx,' — line 628 is the second parameter of export function checkDashboardWidgetMetricMeasureArity(, no object literal in sight. The shape is generic, not incidental: every exported object-level refinement check in this repo has the same ctx: z.RefinementCtx, parameter, so ANY PR adding one earns a false T1 and a spurious C5. The script's own failure text prescribes exactly this route ('if the tell is FALSE, repair it here in the matcher ... or file that repair as its own card when it is out of this PR's scope'), and scripts/pm/
    is outside this dispatch's file surface. ⚠ Near neighbour, NOT a duplicate: open PR #18700 (#18560) teaches T1 the declaring HELPERS — the false-NEGATIVE direction; this is the false-POSITIVE direction in the same matcher, so the two want one reviewer and probably one --self-test block.",
    "noted, not filed: zod 4.4.3 throws 'Cannot overwrite keys on object schemas containing refinements. Use .safeExtend() instead' on a plain .extend() that redeclares a key of a refined object — measured here while probing the per-type-arm spelling. Not a defect of ours (documented library behaviour) and not an authoring trap, but a live trap for the next author who mirrors or re-arms this door. Successor named: whoever lands objectui#8894's half, which must re-attach this export onto a .shape mirror. Recorded in the new check's docblock and in the migration entry, which is where that author looks.",
    "noted, not filed: an ADR-0087 semantic entry CANNOT name per-document values. applyMigrationChain (packages/spec/src/migrations/chain.ts) maps step.semantic straight onto the result with no interpolation and no filter on whether the stack even carries the shape, and SemanticMigration carries only static strings — so a card instruction of the form 'emit a structured TODO naming the dropped measures' is unsatisfiable as literally written; the parse refusal is the only per-document channel. Not a defect (the type has no such field by design) and not a metadata trap. Successor named: the next card that writes that instruction. Recorded in this entry's acceptanceCriteria, which redirects the author to os build rather than to the migrate output.",
    "noted, not filed: the ADR-0087 migration-entry adjacency check the dispatch asked for came back CLEAR and is recorded here so nobody re-derives it — in-flight ADDED semantic registrations on 2026-09-17 are ui-list-view-groupbyfield-padded-refused (#18695), structured-region-body-pause-and-end-refused (#18688), evaluated-expression-slots-source-required (#18638) and manifest-id-reverse-domain-required (#18319); #18420 only MODIFIES an existing entry. This entry's immediate neighbours in the sorted set are dashboard-header-modal-target-page-only and dashboard-widget-stage-order-non-funnel-refused, both already on main, and it is not the first entry of major 18 — so neither #8344 ejection row applies. The seat's expectation about #18695 held, and was verified rather than assumed."
    ]
    }


    Generated by Claude Code

  4. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    契约复核采纳(第三轮) —— PR #18720 / 卡 #17779。⏱️ 本条每一段的读数都取自同一动作:2026-09-17T18:01Z。

    ⏱️ 下面这块读于 2026-09-17T18:01Z。

    记录  PR #18720 评论 5718903122(本席第一手回读,⛔ 不取子代理叙述)
      Served-tier: 111/111 `CONTRACT_REVIEW_TIER`    ← 常量名,⛔ 无 model id(实测泄漏计数 0)
      Head-sha: ee0f6eaced59b3610d60c2695fbf7774b3f02bc9   ← 与当前 head 一致
      Implemented-by: claude/issue-17779-metric-family-single-measure
      Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3
      尖括号计数 0
      **VERDICT: PASS**
    
    check-clause2-carriers --pair 18720 → exit 0;C6-RECORD 指名该评论、该 head、在档 111/111
      ⚠️ 工具原话:「whether it reads PASS ... **stays human**」⇒ 读作 PASS 的是本席。
    

    ⇒ 逐字采纳。 needs:contract-review 已同笔从卡与 PR 两侧摘除(摘前先回读现集)。

    本卡走了三轮复核,账记在这里

    ⏱️ 下面这块读于 2026-09-17T18:01Z。

    第一轮  head ea17ab8491  →  FAIL
            依据:ADR-0087 语义条目写 `applyMigrationChain`,该符号**全树不存在**;
                  真函数是 `applyMetaMigrations`(chain.ts:68,CLI 调用,根 api-surface 导出)。
            ⭐ 本席逐条复验过它两处引用,**包括**它指的 `registry.ts:6765` —— 且本席第一次
               读该文件得 0、险些据此反驳复核:那是 registry.ts 1.33 MB **超过 Contents API
               内联上限**、`content` 回空串所致。改走 blob API 并断言解码字节数 == `size` 后,
               复核所指的行号**分毫不差**。⇒ 坏的是本席的仪器,⛔ 不是复核。
            修:改条目 + `gen:migration-registry` 重生成(⛔ 未手改 registry.ts)→ 3b15ca1254
    
    第二轮  head 881db1280d  →  PASS,但 ③ 提了一条边界旗标
            旗标:clause-② 的**臂**不对。
    
    第三轮  head ee0f6eaced  →  PASS(即本条采纳的这份)
    

    ⭐ 那条旗标是对的,而本席原来的前提是错的

    ⏱️ 读于 2026-09-17T18:01Z。本席此前判断「收窄接受集 + 扩大公开面」在这套词表里没有正确拼法,于是取了 yes (widening) 并写了一段话解释「它不是字面意思」。那个前提是错的,当场用仓内工具证伪:

    • readClause2Line("Clause-②: yes (narrowing)") → {value:'yes', arm:'narrowing'} —— 合法。
    • check-adr-0087-registration 自测逐字命名这个形状:「the narrowing arm beside a yes value —— a diff that widens AND narrows」。
    • 顺带:no (widening) 读作 malformed ⇒ 臂不是自由的,no 只配 narrowing,yes 两者皆可。

    ⇒ 三个载体(认领 · PR 正文 · changeset)已全部改成 yes (narrowing):值仍由机械地板定(新导出符号 —— api-surface/ui.json 净增 1、移除 0,本席对着 merge-base 实测),臂回到接受集的真实方向。clause-②-narrowing 信号随之回到 ADR-0087(实测 [BREAKING+bang+clause-②-narrowing],exit 0)。

    ⚠️ 这一轮的成本由本席承担:第三轮复核之所以存在,是因为本席推断了词表而没有去跑它。规矩记下:断言某个工具「表达不了」之前,先跑那个工具。

    复核升级的一条,⛔ 本轮不并入

    卡面第 2 条要的「结构化 TODO 点名被丢掉的 measure」按字面不可满足:SemanticMigration 是静态字符串、无逐文档插值(复核实测,本席在 chain.ts:102 上复核过同一事实)。条目已自述该限并改指向 refusal;而 refusal 点的是数量/类型/id,⛔ 不是被丢 measure 的名字。⇒ 条目那句「refusal 会点名它们」比实测的消息松。

    ⚠️ 这是一条另立卡的事(改 refusal 文案属行为改动,会扩本 PR 的面),⛔ 本席不代维护者并入。


    Generated by Claude Code

  5. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    **巡检 H52 的答复 —— dev 那条 open_questions 已答,本条把答案落到卡上。**⏱️ 本条读数取自同一动作:2026-09-17T18:28Z。

    它问的是什么

    dev 报告(评论 5718039204)里那条逐字:--pair 18720 退出 4、C5 行说卡面声明 Clause-②: no 与 diff 上的 2 条 widening tell 不一致。

    答案:两条 tell,一真一假,处置不同

    ⏱️ 下面这块读于 2026-09-17T18:28Z。

    T3  真  api-surface/ui.json 净增一行 `checkDashboardWidgetMetricMeasureArity (function)`
            (对 merge-base 72dd95fa5a 实测:+1 / 移除 0)
            ⇒ 契约复核章程的机械地板逐字:「新导出符号…恒 `yes`」—— 这不是判断题。
    
    T1  假  dashboard.zod.ts:628 是 `export function checkDashboardWidgetMetricMeasureArity(` 的
            **第二个参数** `ctx: z.RefinementCtx,`,现场没有任何对象字面量。
            ⇒ 按该脚本自己的处方另立卡:**#18721**(matcher 修复不在本 PR 文件面内)。
    
    现在的读数:`--pair 18720` → **exit 0**,三个载体(认领 · PR 正文 · changeset)一致。
    

    声明最终落在 yes (narrowing),⛔ 不是 yes (widening)

    本席中途取过 yes (widening),理由是以为「收窄接受集 + 扩大公开面」在词表里没有正确拼法。那个前提是错的,已当场用仓内工具证伪并改正 —— 完整交代在本卡的采纳评论 5718974594。

    ⇒ 本卡不欠 needs-user-decision

    H52 的判据是「一个只有维护者能答的问题,filed、well-formed 而 UNREACHABLE」。这条问题不是只有维护者能答:它是一个可实测的声明问题,席位当场测了、答了、并把发现另立成卡。⇒ ⛔ 不挂 needs-user-decision,⛔ 也不劳维护者。

    ⚠️ 留一笔:H52 是按「报告里有非空 open_questions 而卡上无决策标签」触发的形状判据,⛔ 它读不出该问题是否已在别处答过。本条就是那个「别处」,现在落到卡上了。


    Generated by Claude Code

  6. removed their assignment
    on Sep 17, 2026
  7. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    MERGED 确认 —— PR #18720。⏱️ 本条每一段的读数都取自同一动作:2026-09-17T18:55Z。

    ⏱️ 下面这块读于 2026-09-17T18:55Z。

    git fetch origin main && git log --oneline origin/main | grep -F '(#18720)'
      09e16a5745 feat(spec)!: a metric-family dashboard widget declares exactly ONE measure —
                 narrow `DashboardWidgetSchema.values` for the metric/kpi/gauge/solid-gauge/bullet
                 family (objectui#8894 ruling D) (#18720)
    parents: 1   ← 单亲 squash
    
    主题探针(落地后在 origin/main 上重取,⛔ 不取 PR 自述)
      checkDashboardWidgetMetricMeasureArity 在 api-surface/ui.json      1
                                              在 src/ui/dashboard.zod.ts  3
      ADR-0087 条目 dashboard-widget-metric-family-multi-measure-refused 在 registry.ts   1
      ⭐ LIT  同一把 grep 找已有的姊妹导出检查 checkDashboardWidgetStageOrder   1   ← 仪器活着
      ⭐ DARK 伪造名 checkDashboardWidgetZigguratArity                        0
    

    ⏱️ 下面这块读于 2026-09-17T18:55Z。

    落地后重验生成物(roster 当场读自 `grep os-regen .gitattributes`,命中 3 条)
      PR head ee0f6eaced vs 落地提交 09e16a5745
        content/docs/references/ui/dashboard.mdx   blob 46a0757c6c == 46a0757c6c   一致
        packages/spec/api-surface/ui.json          blob 6da166101d == 6da166101d   一致
        packages/spec/export-origins/ui.json       blob 444f188cc7 == 444f188cc7   一致
      ⭐ LIT 同一把 diff 的总差异路径数  73   ← 非零,所以上面三个「一致」是真读数
      ⇒ 队列自己的合并跑了同一个 driver,一条都没被吞。
    
    ⭐ 第一轮复核判 FAIL 的那条地面,落地后复验
      死标识符 applyMigrationChain 在 packages/spec 上   0 命中(git grep 退出 1)
      ⭐ LIT 真函数名 applyMetaMigrations 在落地后的语义条目里            1
    

    卡由 Fixes 代关(closed / completed),同笔摘 pm:dispatched 并清 assignee(经 label-write 四步:取现集 → 只增删目标 → 写 → 回读,回读为 domain:spec · priority:p3)。⚠️ 定级未动。

    本卡的账,一次交清


    Generated by Claude Code

  8. added a commit that references this issue on Sep 28, 2026
    09e16a5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions