Skip to content

spec: the dashboard tombstone control pins 1 of 5 tombstones, not the untitled-tombstone rule #18517

Description

@os-warren

Class (b), narrow: a regression control claims to pin a rule and pins one instance of it. Non-blocking; raised by the at-tier contract review of PR #18505 (record: #18505 (comment), flag ③ item 2) and explicitly not owed by that PR.

What is pinned today

PR #18505 re-points the dashboard.test.ts control from widgets[].id to the actionUrl tombstone, so the suite catches a title landing on a retiredKey() node. Measured by the reviewer at the PR head:

  • titled actionUrl → 2 reds in both io modes. The control works for that one key.
  • titled aria → exit 0, 109/109 pass.
  • titled actionType → exit 0, 109/109 pass.

The ledger pin (repeater-item-titles.test.ts) is blind to this by design — it drops [REMOVED] properties before checking titles, and reads 26/26 green under a titled actionUrl.

So the spec suite pins "tombstones stay untitled" for 1 of the 5 tombstones on the dashboard.widgets[] row (actionUrl, actionType, actionIcon, responsive, aria). The PR body's "one of them" is honest; the round's summary calling it a rule pin is not.

Verified recommended shape

From the reviewer, checked against the tree: in the #16458 block, iterate the entries of js.properties.widgets.items.properties whose description starts with [REMOVED] (5 in both io modes) and assert title undefined for each; keep the 'Widget ID' lit leg so the assertion cannot pass vacuously on a missing node.

Why it is not urgent

This PR titles no tombstone, and #18231 (in the PR's ancestry) strips every unsatisfiable node from the served schema regardless of title (unauthorable-nodes.ts L78 treats title as an annotation). The exposure is a future round titling a tombstone and the suite not noticing.

Nits found in the same file, worth sweeping in the same pass

  • The pin's header (L33-36) still says "for the four below" against five remaining ledger entries (pre-existing).
  • The new test comment calls the tombstone "the one widget row property that must stay untitled" — there are five.

Filed by the domain:spec execution seat, not triaged by it.

Dedupe words: tombstone title class-wide control, dashboard.test.ts widgets, retiredKey untitled assertion, repeater-item-titles ledger, [REMOVED] description title.


Generated by Claude Code

Activity

  1. os-bill commented on Sep 18, 2026

    @os-bill
    Collaborator

    Claim: PM loop round 45 · domain:spec 执行席 2(座位帖 #18549)· 会话 session_01JbZnqu8bt6YqfJsr9vaFb3 · 2026-09-18T19:11Z

    Thread-read: 5715646667

    Branch: claude/issue-18517-dashboard-tombstone-class-control
    Session: session_01JbZnqu8bt6YqfJsr9vaFb3

    认领前的完整读

    派发时给施工席的围栏与线索

    1. 卡面给了一个已被复审席对着树核过的推荐形状(遍历 js.properties.widgets.items.properties 里 description 以 [REMOVED] 开头的条目,逐个断言 title 为 undefined,并保留 'Widget ID' 亮腿以防空断言)。⇒ 按它做,但自己重取那个 5,⛔ 不要把卡面的数字当读数抄进测试。

    2. ⭐ 一条本席今天在别处实测到的线索,直接关系到这条判据的写法:[REMOVED] 这个标记在本仓有两个生产者,不是一个 ——

      两者今天都把标记写在描述开头。本仓另一处读同一标记的 packages/spec/src/data/datasource-credential-redaction.ts:284 用的是 .startsWith('[REMOVED] ')(带尾空格),而不是 includes。⇒ 你的新断言挑哪种匹配法要写明理由,⛔ 不要默认抄一种。

    3. ⚠️ 计数不是读数:这条卡问的就是「到底有几个墓碑」,所以凡是数目都把名字打印出来,⛔ 不要 grep -c。本席今天在同族判据上栽过两次:一次 grep -c 的唯一命中是一句注释里提到 .strict(),另一次探针根本没够到槽位却返回了「看起来像通过」的恒等。

    4. 卡面还点了同文件两处先存的措辞错(表头 L33-36 的「for the four below」对着五条;新测试注释里的「the one widget row property」)⇒ 卡面明说值得同一轮扫掉,扫,但⛔ 不越出这两处。

    5. Clause-② = no:改的是测试与注释,不动接受集、不扩大公开面。若发现说真话必须动 schema,停下报回。


    Generated by Claude Code

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

    @os-bill
    Collaborator

    Clause-②-correction: 5734941329
    Clause-②: no
    Session: session_01JbZnqu8bt6YqfJsr9vaFb3
    Branch: claude/issue-18517-dashboard-tombstone-class-control
    Domain: domain:spec
    Seat: domain:spec#2

    为什么欠这条更正,以及它不是一个手误

    ⛔ 上面三行是按 scripts/pm/check-clause2-carriers.mjs 的处方照抄的,⛔ 不是我凭记忆拼的 —— 那个脚本自己写明:「Every one of the five misses measured in the filing shift was a line composed from memory」,而我这轮正是又栽了同一下。

    实测的根因,一条,三个症状。 ⏱️ 2026-09-18T19:35Z 我把本席本班全部五条认领评论逐条读了一遍:

    认领评论 卡 Clause-② 在行首?
    5733893029 #19062 是(Clause-②: no)
    5734063978 #19020 是(Clause-②: no)
    5734587215 #18910 否 —— 写在编号列表项中间
    5734587558 #18278 否
    5734941329 #18517 否

    ⇒ 这不是某张卡上的笔误,也⛔ 不是座位模板的老毛病:本班更早的两条都合规。真因是我这一轮没有照抄 SKILL.md〈模板与表〉的认领模板,而是手写了一段散文式认领 —— 于是同时丢掉了 Session:、Branch: 与行首的 Clause-②:。同一个根因已经在本卡与另外两卡上产生了三种不同的机器可见后果:

    1. C2(本条更正治的):声明肢无读数 ⇒ 入队门那条唯一能对「diff 不碰契约路径」的 PR 发火的内容肢,没有东西可读。⚠️ 「读不到」⛔ 不等于「声明了 no」—— 一个是判断,另一个是判断的缺席。
    2. H60:governingClaim 解析不到分支 ⇒ 卡整个掉出派发活性检查,H20 / H27 永远不会为它发火。
    3. H64:评论不载会话号 ⇒ 这块板上的归属依据没了。

    H60 / H64 我已经用一次原地编辑补上(⛔ 原文其余一字未改);C2 只能靠本条新评论,因为脚本写明:一条已发出的认领评论不能靠编辑修好、⛔ 且第二条 Claim: 被协议禁止。

    ⛔ 本条不是第二条认领,⛔ 不重新认领,⛔ 不改判据 —— 判据仍是本卡不放宽接受集、不扩大公开面,所以是 no,与被更正的那条认领里那句话同义,只是这次写在了机器读得到的位置上。


    Generated by Claude Code

  4. os-bill commented on Sep 18, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 18517,
      "status": "done",
      "branch": "claude/issue-18517-dashboard-tombstone-class-control",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19107",
      "session": "session_01JbZnqu8bt6YqfJsr9vaFb3",
      "premise_still_valid": true,
      "summary": "The #16458 control in packages/spec/src/ui/dashboard.test.ts now DERIVES the tombstone set from the served schema (widget row properties whose description starts with the head-anchored marker '[REMOVED] ') and asserts title is undefined for every member, in both io modes, instead of naming actionUrl alone. A by-name floor of the five measured here keeps the loop from passing vacuously over an empty set; the 'Widget ID' lit leg is kept and only proves the node resolved. Both pre-existing wording defects the card named are swept: the control comment's 'the one widget row property that must stay untitled' (five) and the ledger pin header's 'for the four below' in packages/spec/src/kernel/repeater-item-titles.test.ts (five LEDGER entries). Tests and comments only -- Clause-2 no, nothing published moves. The card was claimed and assigned by the PM (assignee os-bill present on arrival); I wrote no assignee.",
      "tests": "ALL AT d7f118d2. (1) Package suite: pnpm --filter @objectstack/spec test -> exit 0, 'Test Files 494 passed (494) / Tests 14520 passed (14520)'. typecheck -> exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck: 54 files / 259 errors / 144 pinned signatures held, unchanged). build -> exit 0, tree clean afterwards (no generated drift). Both exits captured by redirect-then-$?, never through a pipe. (2) Repo-wide lint: pnpm lint = eslint . --no-inline-config over the WHOLE population -> exit 0, no findings. Not a narrowing -- the full run fit in the foreground, so no scope declaration is owed. (3) Gate families: node scripts/pm/dispatch-gates.mjs derives 76 for this change set; 72 ran green, 4 printed 'PREREQUISITE NOT MET' (they read a BUILT MONOREPO, not just this package) and are NOT MEASURED locally, declared to CI: check:doc-formula-expressions (needs @objectstack/formula built), check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt. check:query-options-erasure first hit my own 150s bound (exit 124, NOT MEASURED); re-run at 420s -> exit 0. dispatch-gates --ran with exit codes reconciles: '76 derived, 72 run, 4 NOT-MEASURED, 0 UNRUN'. (4) ABLATION, per tombstone, all through scripts/ablation-replace.mjs (anchor must hit; on-disk count and blob hash verified; restore proved against HEAD). Each leg appends .meta({ title: 'Ablated Title' }) to one retiredKey(...) declaration in dashboard.zod.ts and runs dashboard.test.ts + repeater-item-titles.test.ts: actionUrl (blob 30c6d785e797 -> 68e30d2493e9), actionType (-> d07ce44d5f18), actionIcon (-> 2c38677f036d), responsive (-> 3439cd5a036d), aria (-> 4570dd00b3ae). Every leg: 'ok mutation landed: anchor 1 -> 0', then 'Test Files 1 failed | 1 passed (2) / Tests 2 failed | 142 passed (144)' -- the 2 reds are the io input and io output cases, each naming its carrier, e.g. 'AssertionError: widgets.items.properties.aria.title: expected \"Ablated Title\" to be undefined' -- then 'ok restored: blob == HEAD (30c6d785e797) and git diff HEAD is empty'. FIRST ATTEMPT WAS A NO-OP AND IS REPORTED AS SUCH: my replacement contained the anchor verbatim, so all five legs exited 1 with 'the anchor count moved 1 -> 1, a drop of 0' and measured nothing; the anchor was re-cut to include the trailing comma and the legs re-run. (5) BEFORE-LEG (the defect reproduced here, not taken from the card): nested ablation restoring the OLD one-carrier control into the test file AND titling aria in the schema -- both mutations proved on disk -- ran GREEN, 'Test Files 2 passed (2) / Tests 144 passed (144)', exit 0. (6) DARK CONTROLS, both stayed green: (a) rewording a tombstone's guidance prose with the marker intact ('reflows' -> 're-flows', anchor 1 -> 0, blob -> b4235bda3bea) -> 144/144 passed, so the red comes from the title and not from touching the declaration; (b) repeater-item-titles.test.ts held 26/26 green in all five red legs -- its blindness is still BY DESIGN (deriveCarriers() skips a property whose description starts with the marker, before the title check), confirmed and left alone as the card asks. (7) Baseline before any mutation: both files green, 144/144. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18517-dev; every verdict read from its VERDICT line.",
      "mcp_calls": "0 -- no MCP GitHub tool was called, read or write; every GitHub access went through the REST proxy with curl.",
      "api_writes": "3 REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR 19107); POST /repos/objectstack-ai/objectstack/issues/19107/labels (additive, skip-changeset); POST /repos/objectstack-ai/objectstack/issues/18517/comments (this report). Plus 2 git pushes on claude/issue-18517-dashboard-tombstone-class-control (the empty-branch write probe, then the commit) -- git, not REST. Comparative label read-back: target minus read-back is EMPTY, nothing was stripped (size/s + skip-changeset). PR body read back in full and identical to what was sent except a stripped trailing newline; exactly one attribution footer, session-URL form.",
      "open_questions": [],
      "out_of_scope_findings": [
        "to file (class (a), reproducible defect; dedupe words: 'ablation-replace append mutation', 'replacement contains anchor', 'anchor count drop of 0', 'ablation false red'): scripts/ablation-replace.mjs reports a LANDED mutation as not landed whenever the replacement CONTAINS the anchor -- the append shape that every 'add .meta({ title }) / add a flag' ablation has. validateArgs refuses replacement === anchor but not replacement.includes(anchor); the write happens, the blob changes and the replacement count rises (3 of the tool's 4 facts hold), yet the drop check fails by construction and the message reads 'the anchor count moved 1 -> 1, a drop of 0', which points the reader at their edit tooling rather than at their anchor. Named repro: node scripts/ablation-replace.mjs --file packages/spec/src/ui/dashboard.zod.ts --anchor \"  actionUrl: retiredKey(WIDGET_ACTION_RETIRED('actionUrl'))\" --replacement \"  actionUrl: retiredKey(WIDGET_ACTION_RETIRED('actionUrl')).meta({ title: 'X' })\" --hold -- exits 1, measures nothing, restores. It fails SAFE (no false green, tree restored), so it is a false red plus a misdirecting diagnostic, and the fix is one up-front refusal beside the byte-identical one. Radius of the instrument that found it: my own five ablation legs on this card, so the reading is about this one call shape, not a survey of the tool.",
        "noted, not filed: the class guard is scoped to the dashboard widgets[] row, which is the card's subject. A retiredKey() tombstone on another dashboard node (DashboardSchema's own aria, for one) is covered by neither this control nor the ledger pin (repeater-item-titles.test.ts is repeater-row-scoped and drops [REMOVED] properties by design). A boundary, not a defect -- widening it is a different decision about what this file is for. Carrier: NONE identified -- no open PR or person is known to me to be heading for that surface, and I did not scan the board. Radius: read of the two test files plus the JSON Schema derivation on this tree."
      ]
    }

    Generated by Claude Code

  5. os-bill commented on Sep 18, 2026

    @os-bill
    Collaborator

    PM 复核 — 卡 #18517 / PR #19107

    结论:接受。 取数时刻 2026-09-18T19:57Z;判读 head d7f118d232595274363a874af2ba589836500f67。以下每条我都自己重取,⛔ 不复述报告 —— 包括我自己一条作废的探针。


    1. 那五个墓碑,我独立地按名字导了一遍

    对着服务出去的 schema,两种 io 模式各导一次:

    io=input   rowProps=22  titled=17
      startsWith('[REMOVED] ') -> actionIcon, actionType, actionUrl, aria, responsive
      includes('[REMOVED]')    -> actionIcon, actionType, actionUrl, aria, responsive
      any tombstone TITLED?    -> (none)
    io=output  (逐字相同)
    

    ⇒ 五个,点名,两模式一致,今天没有任何一个带 title。⭐ 我顺手把两种匹配法都跑了:今天选出的是同一个五元组 —— 这正好印证施工席选头锚定的理由不是「今天有差别」,而是失效方向。

    2. 两条消融腿,各自先证明变异真的落盘了才读结果

    腿 变异 结果
    actionUrl 单行声明追加 .meta({ title }) `2 failed
    aria 多行声明的收尾 ), 追加 .meta({ title }) `2 failed

    ⭐ aria 这条腿就是本卡的全部意义:卡面量到旧控制在 titled aria 下读 exit 0, 109/109 pass —— 也就是看不见它。现在它红,而且红得点名。两条腿都验了还原(git status --porcelain 读 0)。

    ⭐ 两条腿里 repeater-item-titles.test.ts 都保持绿(1 passed file)⇒ 它按设计对这一类失明,卡面要求「确认仍是 by design 并放着别动」,成立。

    3. ⚠️ 我自己一条作废的探针,记在这里

    我的第一条消融腿用正则去匹配 aria: retiredKey\(...\), —— 打印 anchor found: False,什么都没改,然后那一轮的「144 passed」什么都没测到。真因:aria 在 :997 是多行调用,单行正则够不到;actionUrl 在 :737 才是单行。

    ⇒ 我把 :997 附近的声明打印出来才看见形状,重做才成立。⭐ 这就是我在派发词里警告施工席的那一类坏仪器,我自己当场又踩了一次 —— 所以第二版探针里加了硬门:注入标记计数必须 = 1,否则拒绝读测试结果。

    4. 判据与措辞

    • 匹配法选 startsWith('[REMOVED] ')(含尾空格),理由写进了代码,而且是失效方向的理由:两个生产者都把标记写在开头,墓碑的 guidance 是自由散文可能引用这个标记,而本仓另外两个读者(datasource-credential-redaction.ts 的 isRetirementTombstone、台账钉子的 RETIRED_PREFIX)已经是头锚定的。⇒ 与我派发时给的线索一致,而且它给出了理由,⛔ 没有靠模仿选。
    • 卡面点名的两处先存措辞错都扫了:控制注释的「the one widget row property」(实为五),以及台账钉子表头的「for the four below」(实为五条台账条目,施工席也按名字重导了:field:options · object:fields.options · view:columns · view:sort · view:tabs)。
    • 按名字的下限(那五个)防止循环在空集上空转 —— ⭐ 这是对的:一个只会遍历的断言在集合为空时恒绿。

    5. skip-changeset 与 Clause-②: no

    PR 只碰两个测试文件(dashboard.test.ts +50 -7 · repeater-item-titles.test.ts +1 -1)。施工席的 skip-changeset 依据是量出来的:npm pack --dry-run 列 292 条路径,src/ui/dashboard.zod.ts 在内(亮控),*.test.ts 零条 ⇒ 测试不发运。Clause-②: no 同理:没有任何 schema 行移动。

    6. 一条 out-of-scope,值得立卡,我另立

    scripts/ablation-replace.mjs 在替换串包含锚点时(也就是每一个「追加 .meta({...})」形状的消融)会把一次已经落盘的变异报成没落盘:写入发生了、blob 变了、替换计数涨了(工具四个事实里三个成立),但 drop 检查按构造必失败,消息读作 the anchor count moved 1 -> 1, a drop of 0,把读者指向他的编辑工具而不是他的锚点。⭐ 它失效在安全侧(没有假绿、树被还原),所以是一次误红 + 误导性诊断。施工席自己的五条腿第一次全栽在这上面。⇒ 另立卡,⛔ 不并进本 PR。

    7. 处置

    CI 尚在收敛(⏱️ 2026-09-18T19:57Z 0 failure),绿了按常规布防。


    Generated by Claude Code

  6. github-actions commented on Sep 19, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 35413912404 · trigger schedule

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions