Skip to content

test(spec): pin the untitled-tombstone rule on the dashboard widget row, not one carrier - #19107

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-18517-dashboard-tombstone-class-control
Sep 18, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-18517-dashboard-tombstone-class-control

Conversation

@os-bill

@os-bill os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18517

Clause-②: no

Tests and comments only. No schema, no accept set, no public surface, nothing that ships:
npm pack --dry-run on packages/spec lists 292 paths, src/ui/dashboard.zod.ts among them
(lit control) and zero *.test.ts — so skip-changeset.

What was pinned, and what is pinned now

The #16458 control in packages/spec/src/ui/dashboard.test.ts asserted that
widgets[].actionUrl carries no title. That is ONE retiredKey() tombstone out of five on
the dashboard.widgets[] row, so the suite pinned one instance of "a tombstone stays
untitled", not the rule — the other four could each be titled with the file green.

The control now DERIVES the set from the served schema — the widget row properties whose
description starts with the [REMOVED] marker — and asserts title is undefined for every
member, in both io modes.

The five, re-derived on this tree (not copied from the card)

z.toJSONSchema(DashboardSchema, { io, unrepresentable: 'any' }), widget row = 22 properties,
of which the tombstones are, by name and identical under both io modes:

actionIcon · actionType · actionUrl · aria · responsive

Seventeen row properties carry a title; the five tombstones carry none. The card's count of
five holds.

The matcher, and why it is stated in the code

Head-anchored startsWith('[REMOVED] '), trailing space included — deliberately not
includes. Both spellings select the same five keys on this row today, so the choice is about
the failure direction rather than today's reading:

  • Both producers of the marker write it at the head — retiredKey()
    (shared/retired-key.ts, "[REMOVED] " + guidance) and the element-grain
    retiredComponentProps (ui/component.zod.ts).
  • A tombstone's guidance is free prose, and a live key's .describe() may quote the marker
    while explaining a retirement. Under includes that key joins a set this test then reports
    as "every tombstone" — and asserting an authorable key is untitled is the opposite of the
    rule.
  • The repo's other reader of this marker is head-anchored already
    (isRetirementTombstone, data/datasource-credential-redaction.ts), as is the ledger pin's
    RETIRED_PREFIX (kernel/repeater-item-titles.test.ts). One marker, one predicate.

The by-name floor beside it is there because the loop is vacuously green over an empty set and
the 'Widget ID' lit leg only proves the node resolved. It is a floor and not an equality: a
sixth tombstone is covered by the loop on the day it lands, which is the reason for deriving
the set instead of listing it.

Evidence

Reverse verification, per tombstone. Each leg runs through scripts/ablation-replace.mjs
(anchor must hit, on-disk count and blob hash verified, restore proved against HEAD):
retiredKey(...) gains .meta({ title: 'Ablated Title' }), suite runs, file is restored.

leg mutation landed (blob) dashboard.test.ts repeater-item-titles.test.ts
title actionUrl 30c6d785e797 to 68e30d2493e9 2 failed / 118 26 passed
title actionType 30c6d785e797 to d07ce44d5f18 2 failed / 118 26 passed
title actionIcon 30c6d785e797 to 2c38677f036d 2 failed / 118 26 passed
title responsive 30c6d785e797 to 3439cd5a036d 2 failed / 118 26 passed
title aria 30c6d785e797 to 4570dd00b3ae 2 failed / 118 26 passed

Two reds per leg = the io: 'input' and io: 'output' cases, each naming its carrier:
AssertionError: widgets.items.properties.aria.title: expected 'Ablated Title' to be undefined.
Every leg ends ok restored: blob == HEAD (30c6d785e797) and git diff HEAD is empty.

Before-leg — the defect reproduced on this tree rather than taken from the card. Nested
ablation: the OLD one-carrier control restored into the test file, aria titled in the schema.
Both mutations landed on disk, and the suite was green: 2 files passed, 144/144, exit 0.
That is the hole this PR closes.

Dark control 1 — prose, not title: rewording a tombstone's guidance text with the marker
intact (reflows to re-flows, anchor 1 to 0, blob 30c6d785e797 to b4235bda3bea) leaves the
suite green, 144/144. The red comes from the title, not from touching the declaration.

Dark control 2 — the ledger pin stayed green in all five red legs.
repeater-item-titles.test.ts is blind to this by design and still is: deriveCarriers()
skips a property whose description starts with [REMOVED] before the title check ("a retired
key is a parse-time refusal, not an authorable column"). Confirmed as designed and left alone.

Swept in the same pass (both pre-existing, both named on the card)

  • The control comment called the tombstone "the one widget row property that must stay
    untitled". There are five.
  • The ledger pin's header in kernel/repeater-item-titles.test.ts said "for the four below"
    against five LEDGER entries, re-derived by name here: field:options,
    object:fields.options, view:columns, view:sort, view:tabs.

Verification

All at d7f118d2.

  • pnpm --filter @objectstack/spec test — 494 files, 14520 tests passed, exit 0.
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc --noEmit, check:scripts-typecheck,
    check:test-typecheck: 54 files / 259 errors / 144 pinned signatures held, unchanged).
  • pnpm --filter @objectstack/spec build — exit 0, and the tree stayed clean (no generated drift).
  • pnpm lint — the whole repo-wide population, eslint . --no-inline-config, exit 0. Not a
    narrowing: the full run fit in the foreground.
  • Gate families: node scripts/pm/dispatch-gates.mjs derives 76 for this change set; 72 ran
    green, 4 report PREREQUISITE NOT MET (they read a built monorepo, not just this package) and
    are NOT MEASURED locally, declared to CI: check:doc-formula-expressions,
    check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt.
    dispatch-gates --ran reconciles 76/76 accounted.

Acceptance notes

  • Dropped one line the rewrite would have made a phantom check: re-asserting
    description matches the [REMOVED] marker inside a loop whose filter already guarantees
    it can never fail. The old control's version of that line was a real assertion because it
    named its carrier; under a derived set it is a tautology.
  • Noted, not filed: the class guard is scoped to the widgets[] row, which is the card's
    subject. A tombstone on another dashboard node (DashboardSchema's own aria, for one) is
    not covered by it, and no ledger pin covers it either — repeater-item-titles.test.ts is
    repeater-row-scoped and drops [REMOVED] properties by design. Not a defect, a boundary:
    the same widening decision belongs with whoever wants the whole-schema version, and it would
    change what this file is about.

Generated by Claude Code

…ow, not one carrier

`dashboard.test.ts`'s #16458 control asserted that `widgets[].actionUrl` — one
`retiredKey()` tombstone out of five on that row — carries no `title`. Titling
any of the other four (`actionType`, `actionIcon`, `responsive`, `aria`) left
the suite green, so the control pinned one instance of the rule rather than the
rule.

Derive the tombstone set from the served schema instead: filter the widget row
properties whose `description` starts with the `[REMOVED] ` marker both
producers write (`retiredKey()` in `shared/retired-key.ts`,
`retiredComponentProps` in `ui/component.zod.ts`) and assert `title` is
undefined for every member, in both io modes. Head-anchored and not `includes`,
for the reason stated at the constant. A by-name floor keeps the loop from
passing vacuously over an empty set — the `'Widget ID'` lit leg only proves the
node resolved — while leaving a sixth tombstone covered by the loop on the day
it lands.

Two pre-existing wording defects swept in the same pass: the control comment
called the tombstone "the one widget row property that must stay untitled"
(there are five), and the ledger pin's header in
`kernel/repeater-item-titles.test.ts` said "for the four below" against five
LEDGER entries.

Tests and comments only: no schema, no accept set, no public surface.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 07c6f822edd9c4c48a7ce34767de420c6096b9c3packageMentionDocs.

@os-bill
os-bill marked this pull request as ready for review September 18, 2026 20:16
@os-bill
os-bill added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 1124c45 Sep 18, 2026
41 checks passed
@os-bill
os-bill deleted the claude/issue-18517-dashboard-tombstone-class-control branch September 18, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

protocol:ui size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants