Skip to content

spec: retire ChartConfigSchema.aria — no renderer ever applied it, and its two alias spellings become refusals - #18300

Merged
os-warren merged 7 commits into
mainfrom
claude/issue-17751-chart-config-aria-retired
Sep 15, 2026
Merged

os-warren merged 7 commits into
mainfrom
claude/issue-17751-chart-config-aria-retired

Conversation

@os-warren

@os-warren os-warren commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17751

Clause-②: yes

Retires ChartConfigSchema.aria under ADR-0049 enforce-or-remove, executing the maintainer's decision batch #118 item 2 (2026-09-12) — 「其他同意」 to the director seat's recommendation C, judge the protocol wrong for this one key. The standing principle the card cites, verbatim and untranslated:

我们的项目以objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。

The premise, re-measured first-hand before writing anything

Every row below was taken in this worktree, not read off the card.

claim how it was measured reading
the key is still declared grep -n on packages/spec/src/ui/chart.zod.ts :630 aria: AriaPropsSchema.optional() — control showLegend 2 hits in the same file
no reader at the pinned objectui git show 53ded82bf7a4:packages/core/src/utils/chart-presentation.ts aria appears only in prose, incl. its own docblock calling it "the one declared key with no reader at all" — control showLegend 3 hits in the same file
no reader at objectui HEAD git grep '\.aria\b' HEAD over the chart paths every hit is a negative pin ("ignores chartConfig.aria", "an authored chartConfig.aria reaches no attribute on this surface") — control: plugin-view/src/ObjectView.tsx still has 1 real .aria read, the VIEW's
the precedent is a live tombstone family grep -n 'retiredKey(' on app.zod.ts / dashboard.zod.ts app.zod.ts:1571, dashboard.zod.ts:829 and :1190 — and src/ui/aria-carrier-tombstones.test.ts already pins the class
the sibling channel really works liveness/dashboard.json, chartConfigPresentation docblock description is lowered onto the chart graphic as role="img" + aria-label, pinned in objectui's DOM tests

Scope: why this registers TWO retired keys, and why that is not a widening

The card is titled for ChartConfigSchema.aria and that is exactly what this retires — one property, on one schema. ui/ReportChart:aria appears beside it as arithmetic, not as a second decision: ReportChartSchema is ChartConfigSchema.extend({ xAxis, yAxis }), so the extension copies the retired property into its own walked shape and authorable-surface/ marks the copy [RETIRED] separately. There is no second declaration to leave alone — the extension has no aria of its own to keep — so the two keys are not separable.

The build says so before any reader does. Tombstoning the one property failed build-schemas.ts check (b) on the first run, naming both keys unprompted, and stayed red until both were registered:

2 key(s) were tombstoned with no registered retirement:
     - ui/ChartConfig:aria
     - ui/ReportChart:aria

This is the shared/FieldMapping:transform shape, where one tombstone produced three keys; RETIRED_KEYS_BY_MAJOR's own docblock states the rule — registered per key, as the gate reads them; nothing radiates from the base. The fact is written into src/ui/aria-carrier-tombstones.test.ts beside the test that exercises it, so a later reader meets it where the behaviour is.

Two things the card did not spell out, and what was decided

1. The replacement vocabulary is NOT what the card's shorthand says, and the tombstone must not repeat it. The card describes the live channel as "BaseSchema.ariaLabel / ariaDescribedBy / role". Measured: BaseSchema does not exist in packages/spec — it is objectui's own node interface in packages/types, and SchemaRenderer.resolveAriaProps (objectui packages/react/src/SchemaRenderer.tsx:122) is what reads it. A metadata author cannot write BaseSchema.ariaLabel anywhere in this protocol, so shipping that sentence in a tombstone would have been plausible and false. What the prescription names instead is measured: the same three words live in the shared AriaProps block, still carried by page.aria, page.components[].aria and the list view aria; and on a chart the accessible name that is actually applied is the sibling description.

2. The two alias spellings. chart.zod.ts mapped accessibility: 'aria' and ariaProps: 'aria'. Both moved from aliases to guidance, carrying the retirement instead of a rename. This was not a judgement call in the end — the alias map's own rules decide it, and the decision was ablated in both directions:

  • Leaving them is mechanically refused. Restoring the two alias entries turns src/shared/alias-integrity.test.ts red, naming both by name: `accessibility` -> `aria` — `aria` is a tombstone; it accepts nothing and the same for ariaProps. An alias runs before the edit-distance fallback and bypasses the tombstone filter, so it would send an author to the one key the shape is now guaranteed to reject.
  • Deleting them with no replacement loses the migration path. In the same ablation the author who writes accessibility gets a bare Unrecognized key(s) on this chart config… with no retirement text in it — because knownKeys excludes tombstones from the distance candidates, so nothing points anywhere.

The retirement kit

Route: retiredKey() tombstone, not a bare deletion — even though ChartConfigSchema is a strictObject. A bare delete stays loud but only as a generic unrecognized-key report that cannot carry the prescription; that is the exact distinction aria-carrier-tombstones.test.ts already asserts by name for the widget twin. Consequences, all of them followed:

  • Two registered keys from one tombstone. ReportChartSchema is a ChartConfigSchema.extend(...), so the retired property copies into its own walked shape. Registered per key, nothing radiating from the base: ui/ChartConfig:aria and ui/ReportChart:aria, each its own file under src/migrations/entries/retired-keys/, under major 18 (launch-window convention — the tombstone ships on the 17.x line, the prescription lives at the major boundary where migrate meta users look).
  • D2 conversion chart-config-aria-removed (toMajor: 18, retiredFromLoadPath: true) strips all three authored coordinates — dashboards[].widgets[].chartConfig, reports[].chart, reports[].blocks[].chart — with a fixture asserting 3 notices. A separate entry rather than more keys on dashboard-widget-action-aria-removed, for the reason that entry itself gives.
  • D3 chain step: the id is wired into MIGRATIONS_BY_MAJOR[18].conversionIds and the step's rationale is extended.
  • Liveness ledger: the dashboard/widgets.chartConfig.aria row stays (the tombstone keeps the key in the walked shape — the rls.priority precedent), regraded with a REMOVED note and a fresh verifiedAt. Its undrilled-containers.baseline.json row is deleted, which the gate itself asked for: "the container now drills (or its property is gone) — delete it."
  • Baselines: authorable-surface/ui.json now carries both rows as [RETIRED]; the generated chart / report / dashboard references regenerate with the prescription in place of the old nested-shape table.
  • No form input, no locale bundle. Unlike its siblings this key never reached a *.form.ts, so there is no false-compliant UI half to remove. pnpm check:i18n confirms: 9 packages, all bundles in sync.
  • Changeset: minor, with the FROM → TO table, the one-line fix and the ADR-0087 disposition. ⛔ Not major: scripts/check-changeset-no-major.mjs refuses it during the launch window and breaking ships as minor until GA.

The ablation — the pin proven red before, green after

Both legs mutated on disk, proved on disk, and restored by blob identity under a trap.

Leg 1 — the tombstone. Mutation put aria: AriaPropsSchema.optional() back:

BEFORE: aria-tombstone-lines=1 prescription-lines=1
AFTER : aria-tombstone-lines=0 prescription-lines=0 live-declaration-lines=1
HEAD_BLOB=cbd0147784ead1076f2148671af25b9809172e34
MUTATED_BLOB=2f422b6394e5a27f24ed18db3f2d3fd7c8d53bc2
ABLATED_PIN_EXIT=1   ->   Tests  2 failed | 5 passed (7)
  FAIL … > the chart-config tombstone fires and prescribes only live carriers
  FAIL … > the tombstone rides the `.extend()` onto ReportChart
  AssertionError: the tombstone must still REJECT — a passing parse means the key came back

Leg 2 — the alias decision. Mutation restored the two alias entries and deleted the two guidance entries:

BEFORE: alias-entry-lines=0 guidance-entry-lines=1
AFTER : alias-entry-lines=1 guidance-entry-lines=0
MUTATED_BLOB=cce7ea995b60582cf0f58dc765d1fcd5a1bfa716
ABLATED_EXIT=1   ->   Tests  2 failed | 24 passed (26)
  FAIL src/shared/alias-integrity.test.ts > every alias target is a key the schema really accepts
    + "ui/chart.zod.ts:540 — \"this chart config\": `accessibility` -> `aria` — `aria` is a tombstone; it accepts nothing",
    + "ui/chart.zod.ts:540 — \"this chart config\": `ariaProps` -> `aria` — `aria` is a tombstone; it accepts nothing",
  FAIL src/ui/aria-carrier-tombstones.test.ts > the two former alias spellings refuse instead of renaming onto the tombstone
    AssertionError: `accessibility` must carry the retirement: expected 'Unrecognized key(s) on this chart con…' to contain 'was removed in @objectstack/spec 17'

Restore proven both times by blob identity, not by an exit code: RESTORED_BLOB=cbd0147784ead1076f2148671af25b9809172e34 equals HEAD:packages/spec/src/ui/chart.zod.ts, with git diff HEAD empty and git status --porcelain clean. Green after: 7/7 in the family pin, 84/84 across chart.test.ts + aria-carrier-tombstones.test.ts.

One fixture was inverted, not respelled

src/ui/chart.test.ts had Chart ARIA Integration > should accept chart with ARIA attributes, which pinned exactly the acceptance this change removes. There is no spelling of aria this shape accepts any more, so the block is inverted in place — it now asserts the refusal and carries the prescription, with a positive control asserting description still parses so the negative cannot pass on a shape that lost its accessibility story altogether.

Verification

All exit codes captured by redirect-then-$?, never through a pipe.

run exit
pnpm --filter @objectstack/spec build (under scripts/pm/os-verify-lock.sh) VERDICT command-exit 0
pnpm --filter @objectstack/spec typecheck && … test 1 → one fixture red (inverted above) → 0 on re-run: Test Files 481 passed (481), Tests 13698 passed (13698)
pnpm --filter @objectstack/spec check:generated 0 (15 artifacts; 2 regenerated with --fix, reviewed)
the 14 source audits check:generated names as not run all 0, run as a group
pnpm check:i18n 0 — 9 packages, all bundles in sync (prerequisite closure built first; it had refused with exit 3)
pnpm lint (repo-wide eslint . --no-inline-config) 0 — no narrowing claimed, none owed
node scripts/pm/dispatch-gates.mjs --ran 0 — 113 derived, 112 run, 1 NOT-MEASURED, 0 UNRUN

The one NOT-MEASURED is pnpm check:dual-build-cjs-loads, recorded exit 3 (PREREQUISITE NOT MET — it reads built output for studio, the connectors and several plugins, none of which this diff touches). That is a whole-monorepo build, which is CI's run; it is declared here rather than counted as green.

Acceptance notes

  • Docs Drift Check: no hand-written page is owed an edit, measured rather than assumed. The advisory anchored one page, content/docs/ui/dashboards.mdx, on a fixture string literal (revenue_by_region) and not on the retired key. Re-measured independently on origin/main 827cacbf4b (not on the drift run's merge tree): word-boundary aria in that page is 0 hits — grep -nE '(^|[^a-zA-Z])aria([^a-zA-Z]|$)', exit 1 — against a lit control of chartConfig at 1 hit, which is the single table row | chartConfig | object | optional | Advanced chart configuration |. That row does not enumerate the keys, so retiring aria falsifies nothing on it. The boundary form matters: a naive grep -i aria returns two false positives in this same file, colorVariant and variable. ⚠️ The same run reported 4 changed files yielding no anchor — one of them this PR's 18.ui__ChartConfig__aria.ts — and warned that its own checkout carried uncommitted changes, so pages documenting the retirement are NOT MEASURED by it, never cleared by it.
  • The pinned sibling is unaffected, checked before opening this. No objectui production source authors chartConfig.aria at .objectui-sha 53ded82bf7a4; the two places that do are objectui's own negative-pin test files, and Console Pin Gate builds the Console SPA rather than running objectui's tests. The consumer half — objectui's contract twins gaining the tombstone pin — is objectui#4044's, which is pm:blocked on this card by design.
  • Noted, not filed: the card body's item 4 asks for a major changeset and Clause-②: no. Both are stale against what the tree enforces — scripts/check-changeset-no-major.mjs refuses major during the launch window, and clause ② covers accept-set movement in both directions (packages/client/CHANGELOG.md records an "Accept-set narrowing on a published SDK (clause-②)"). This PR follows the dispatch order and the gates: minor, Clause-②: yes. No card edited — the delivering seat does not rewrite the card it was dispatched from.
  • Noted, not filed: .claude/skills/spec-property-retirement/SKILL.md §4 still instructs @objectstack/spec retirements to take a major changeset, which check:changeset-no-major refuses during the launch window. Governed surface, and not this card's to edit. Successor: the next retirement card through that playbook.
  • Noted, not filed: the same playbook's §2 fork table reads ".strict() ⇒ delete the key + guidance map", while every aria-family precedent on this tree is a retiredKey() tombstone inside a strictObject — which shared/strict-object.ts documents as "deliberate and strictly stronger than a guidance entry". The table is a default, not a rule, but it points a reader the wrong way on exactly this class. Same successor as above.

Delivered by the domain:spec dev seat with Claude Code, session session_01KB5PFtxuy1x3dcR5gxudx6; head 16a8de837a.


Generated by Claude Code

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 15, 2026
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/dashboard.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ChartConfig__aria.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ui/dashboards.mdx (via revenue_by_region (literal, a string literal in fixture))
What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/dashboard.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ChartConfig__aria.ts, …) — pages documenting those are invisible to this run
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 136 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 827cacbf4b79088926f98bba25cccfb0f369ebbcpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 4bac193b4280556ee84172d567dff8025a615769 — the merge of head 16a8de837aec5791ba8976e80272f066bd818118 into base 827cacbf4b79088926f98bba25cccfb0f369ebbc, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4bac193b4280556ee84172d567dff8025a615769 && git checkout 4bac193b4280556ee84172d567dff8025a615769
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 827cacbf4b79088926f98bba25cccfb0f369ebbc 16a8de837aec5791ba8976e80272f066bd818118 && git checkout -B drift-repro 827cacbf4b79088926f98bba25cccfb0f369ebbc && git merge --no-ff 16a8de837aec5791ba8976e80272f066bd818118

node scripts/docs-audit/affected-docs.mjs --json 827cacbf4b79088926f98bba25cccfb0f369ebbc

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 827cacbf4b79088926f98bba25cccfb0f369ebbc → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 16a8de837aec5791ba8976e80272f066bd818118

Reviewed against merge-base 827cacbf4b (the PR's base sha) with origin/main at 53dd5aaaaf. Every reading below was taken on this head fetched into a ref of my own, or on objectui at the pinned 53ded82bf7a4. The implementer's report was an input; its load-bearing claims were re-measured.

Fence checks first. packages/spec/src/shared/retired-key.ts is blob 17e82fa3c9 at the merge-base, at this head and at origin/main — untouched. content/docs/releases/ and every CHANGELOG.md: no such path among the 14 files. Both hold.

① Derived judgments

  1. Tombstone on ChartConfigSchema.aria — right. chart.zod.ts:671 now reads aria: retiredKey(…) where the merge-base had aria: AriaPropsSchema.optional(). The accept set narrows (an aria object that parsed at 827cacbf4b is refused at this head with the prescription as the issue message) and the ChartConfig input type's aria becomes never. Both channels are declared: the changeset opens with the BREAKING banner and carries the FROM → TO table and the one-line fix. The prescription closes with the house os migrate meta --from 17 sentence pinned class-wide by retired-key-migrate-sentence.test.ts, and "removed in @objectstack/spec 17" is the tree's dominant spelling (80 tombstones say exactly that, the 18-registered dashboard.refreshInterval among them). AriaPropsSchema itself is untouched: 24 live aria: AriaPropsSchema declaration sites remain (action, component ×20, page ×2, the list view). A key retirement, not a def retirement — right.

  2. Two registered retired keys — arithmetic, not a widening. report.zod.ts:31 is ChartConfigSchema.extend({ xAxis, yAxis }), so the tombstone is copied into ReportChartSchema's walked shape and authorable-surface/ui.json marks both rows [RETIRED]. build-schemas.ts:1009-1013 (check b) filters newlyRetired per key against the registered set, so registering one of the two cannot build; the per-key rule is the documented precedent in RETIRED_KEYS_BY_MAJOR's own docblock (shared/FieldMapping:transform, ui/ObjectListView:bordered, kernel/TenantRuntimeContext:previewMode). Exactly one property on one schema was retired; the second key is the gate's arithmetic. Right, and stated as such in the entry file, the registry comment and the family pin.

  3. The two alias entries moving from aliases to guidance — right, and it moves nothing in the accept set. strict-object.ts is explicit that an alias "runs only from the unrecognized_keys path, so a declared key can never reach it" — aliases[aliasProbe(key)] ?? findClosestMatches(…) inside the error map. accessibility: and ariaProps: were refused at the merge-base and are refused at this head; only the rejection text changed. Leaving them was not available: alias-integrity.test.ts:725/760 ("every alias target is a key the schema really accepts") flags an alias whose target acceptsNothing. Deleting them outright would have dropped both words onto the distance fallback, whose candidate list excludes tombstones (strict-object.ts:336). guidance is the documented slot for exactly this. Right.

  4. D2 conversion chart-config-aria-removed and its three coordinates — right and complete. The only authored embeddings of the shape at this head are dashboard.zod.ts:550 (widgets[].chartConfig), report.zod.ts:350 (ReportSchema.chart) and report.zod.ts:224 (JoinedReportBlockSchema.chart, the blocks[] member at :363); react-blocks.ts:330 binds the schema to the ObjectChart react block and is not an authored metadata coordinate. dashboard and report are registered kinds (metadata-plugin.zod.ts:845/851), so sys_metadata rows can carry the key and D2 is the right disposition. stripKeys emits one notice per key per site, so expectedNotices: 3 is exact for one key at three sites. toMajor: 18 equals the step it is wired into (step18.conversionIds), retiredFromLoadPath: true matches the sibling dashboard-widget-action-aria-removed (which is toMajor: 17, so "a different major" is true), and the changeset's stored-row sentence is what conversions/types.ts and stored.ts say the flag does (rehydration replays retired entries). Right.

  5. The inverted fixture in chart.test.ts — right. "Chart ARIA Integration / should accept chart with ARIA attributes" pinned the branch this retirement deletes; it is inverted in place to assert refusal with the prescription reachable in the message, plus a positive control that description still parses. The family pin in aria-carrier-tombstones.test.ts gains three cases (the tombstone fires; it rides the .extend(); the two alias spellings refuse without "did you mean"), sharing the file's existing ground-truth cases that parse page.aria, page.components[].aria and the list view aria as accepted. Ledger at this head: page.json props.aria live, view.json props.list.children.aria live, props.form.children.aria dead — the prescription names only live carriers. Right.

  6. The withdrawn prose — the correction was warranted, and what shipped is true. BaseSchema with a word boundary: 0 hits in packages/spec/src at this head, against lit controls FormFieldBaseSchema 13, ObjectPermissionBaseSchema 5, MetadataTypeRegistryEntryBaseSchema 2. resolveAriaProps: 0 in this repo; it is objectui packages/react/src/SchemaRenderer.tsx:122 at the pin, and BaseSchema is objectui packages/types/src/base.ts:70. The card's "BaseSchema.ariaLabel via SchemaRenderer.resolveAriaProps" would have been false in a packages/spec prescription. What shipped instead measures true: at the pin chart-presentation.ts mentions aria 4 times, all prose, its docblock calling it "the one declared key with no reader at all" (control showLegend 3); description is read at :315-316 and lowered as role="img" plus aria-label (DOM pin DatasetWidget.chartConfig.dom.test.tsx:107); production .aria readers on the chart paths: 0, against a real .aria read one surface over (plugin-view/src/ObjectView.tsx:1834); the ObjectChart block's dataProps lists 13 names against 14 ui/ChartConfig: keys and the missing one is aria. Same-repo zero-hit: no aria: author in any chart context across examples/, non-spec packages/ and hand-written content/docs/ (control chartConfig: 29 hits in examples/ alone); content/docs/ui/dashboards.mdx at the merge-base has 0 word-boundary aria against 1 chartConfig row that enumerates no keys. The tombstone guidance, the two guidance entries, the conversion summary, the step-18 rationale, the two entry-file comments, the changeset body and the three regenerated reference pages were each read against the tree; one prose imprecision is noted under ③.

  7. Generated artifacts and ledger — right. authorable-surface/ui.json gains the two [RETIRED] markers; the three reference pages regenerate with the prescription in place of the nested-shape table; the liveness/dashboard.json row stays dead with the house REMOVED note and a fresh verifiedAt (the tombstone keeps the key in the walked shape, as the playbook's own ledger table requires); the undrilled-containers.baseline.json row is deleted, which is a shrink.

② Semver level

minor is correct under this tree's rules, and the changeset text is complete. scripts/check-changeset-no-major.mjs (same blob on origin/main) refuses a major changeset during the launch window and states its end condition (GA); its level axis requires a Clause-② PR to grade at least one moved package minor or above, and AGENTS.md's Post-Task Checklist says the same. The changeset grades @objectstack/spec minor, opens with the BREAKING banner, states the retirement and the re-declared shape (the tombstone, the two registered keys, the alias re-homing, the stored-row behaviour), carries the FROM → TO table and the one-line fix, and carries exactly one ADR-0087 marker in the registered form naming chart-config-aria-removed — an id this diff adds to CONVERSIONS_BY_MAJOR[18], which is the honest category per the gate's own header. The card's item 4 (major, Clause-②: no) is stale against the tree: major is refused, and clause ② is already applied to narrowings here (packages/client/CHANGELOG.md:809, "Accept-set narrowing on a published SDK (clause-②)"). CI context at this head as of this review: 42 checks, 38 success, 4 skipped, 0 failing; landing is not this record's question.

③ Boundary flags

open_questions: [] — nothing I measured should have been one. The four out_of_scope_findings:

  1. Card item 4 stale (major, Clause-②: no) — agree; answered in ② above. The delivering seat rightly did not edit the card; the divergence is recorded in the PR body and here.
  2. Playbook §4 asks for major — agree, verbatim at .claude/skills/spec-property-retirement/SKILL.md:291 ("@objectstack/specmajor"), which check-changeset-no-major.mjs refuses until GA. Governed surface; the next retirement card walks into this unless the line is amended to "minor during the launch window, major at GA". Escalate as a rules-layer edit for the maintainer's batch — not this PR's.
  3. Playbook §2 fork table — agree, verbatim at :87: the .strict() row says "删键 + guidance map", while every aria tombstone on this tree (app, form, dashboard, dashboard.widgets[], ElementFilterProps, ElementFormProps, now ChartConfig) is retiredKey() inside a strictObject, and strict-object.ts documents that declaration as "deliberate and strictly stronger than a guidance entry". The table reads as a rule and points a reader away from the precedent on exactly this class. Same escalation.
  4. objectui negative-pin tests author chartConfig.aria at the pin — agree: DatasetWidget.chartConfig.test.tsx:218-221 and DatasetWidget.chartConfig.dom.test.tsx:136-139, both test files, no production author; Console Pin Gate builds and does not run those tests. objectui#4044's half, already carried there.

Two evidence-quality findings of my own, neither verdict-affecting:

  • The ablation's HEAD_BLOB names the previous head, not this one. cbd0147784ea is chart.zod.ts at commits a73affe through 4ea3b4ef; at the reviewed head the blob is 2384130e9216. The whole delta is one comment line — backticks around the pin sha, from 1b6cbe2ab4 — so both legs' red/green transfer and the restore-by-blob-identity claim was true of the tree it was taken on; but a blob cited without its commit is the reading AGENTS.md says is not a reading. The pins the report names (ChartConfig.aria, description, the three live carriers, the exact-spelling refusals, the alias cases) exist at this head as described.
  • "The third and last member of the aria family" (changeset, both entry files, step-18 rationale) is true of the dashboardwidgets[]chartConfig lineage and false as a count: seven aria: retiredKey( sites exist at this head. Prose only; no FROM → TO row depends on it.

Implemented-by: claude/issue-17751-chart-config-aria-retired
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: PASS


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 15, 2026 15:38
@os-warren
os-warren added this pull request to the merge queue Sep 15, 2026
Merged via the queue into main with commit 2bf6ef1 Sep 15, 2026
47 checks passed
@os-warren
os-warren deleted the claude/issue-17751-chart-config-aria-retired branch September 15, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/l tests tooling

Projects

None yet

2 participants