Repository navigation
[spec] ADR-0049 enforce-or-remove: retire ObjectGridProps.defaultSort (the producer half of objectui#5861) #11805
Description
Activity
Triage:
domain:spec+needs-user-decision(入决策箱,不入队)。方向本身已裁 —— 2026-08-22 维护者对 objectui#4869 的裁决(「接受所有」,含 C 半:table.defaultSort走 ADR-0049 enforce-or-remove 独立卡);实施中测得执行面在本仓 spec strictObject(objectui 侧零拒收能力,ui 席已裁 ESCALATE 并把 ui#5861 转pm:blocked指向本卡)。未被原裁决覆盖的新事实:退役 changeset 为 major ——@objectstack/spec17.2.0 → 18.0.0 的发版规划时点是维护者的裁决面,故按「实施中测出对向事实 ⇒ 冲突立卡」入箱,⛔ 不代裁。四维分析(标准卡面块):
- 实际业务需求:两仓实测零 authored 占用(带阳性对照的测量,非裸零命中);唯一活命中即 spec 声明本身。退役不伤及任何真实用户;不退役则契约持续声明一个无人写、UI 亦不再发布的键。
- 项目长远合理性:contract-first —— declared=enforced 只能在生产者(strictObject)恢复;[spec] Retire three zero-reader UI keys per maintainer ruling: PageHeaderProps.icon, PageCardProps.actions, RecordDetailsProps.layout (objectui#3829 / objectui#3818) #6946+ui#3974 已在姊妹键
form.defaultSort上端到端验证过同一机器。消费端替代路线(B 静默丢弃 / C 渲染器自造拒收)均已被 ui 席按四维实测否决。 - 防 AI 写代码犯错:strict 拒收在 authoring 时点响亮报错并带迁移指引,是对 AI 元数据作者最硬的防线;静默丢弃(B)恰是 AI 批量犯错被掩盖的温床 —— 模型读回该键会以为生效。
- 创业阶段不扩散需求:本卡是删面不是加面;major bump 是 enforce-or-remove 裁决的诚实价格,非范围扩张。
- 推荐:接受 —— 走 strict 路线退役,major changeset 随卡落地(发版本身照旧人工,changeset≠发版;若维护者更愿把 18.0.0 攒批,可裁「批准实施、changeset 落地、发版时点另定」,卡即可转队)。
裁决后动作(预写,任何座位可继承):批准 ⇒ 本卡换
pm:queue,派发档位按条款②(改变接受/拒绝行为,CONTRACT_REVIEW_TIER),按spec-property-retirementplaybook strict 路线;ui#5861 在 pin bump 后机械收尾(flip-list 已在该卡 PM 裁决评论里)。暂缓 ⇒ 本卡pm:on-hold+Restart-when: 下一次 spec major 窗口开启(#11805 由维护者点名或 RETIRED_KEYS_BY_MAJOR 出现 v18 条目),ui#5861 随之 hold。
Generated by Claude Code
os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsMaintainer ruling — retirement APPROVED; the changeset is NOT major
Source: maintainer, 2026-08-25, live PM chat (decision-inbox batch 4 review, session
session_01KWRU3s15AJz7PGW7a7wdCh), verbatim: 「#11805 退役 defaultSort,不需要major」.Two parts, recorded precisely:
- Retire
ObjectGridProps.defaultSort— the strict route per thespec-property-retirementplaybook, exactly as this card scoped it (delete from the shape,*_RETIRED_KEY_GUIDANCEentry, ADR-0087 conversion,RETIRED_KEYS_BY_MAJORexact-key entry, D3 chain step, liveness ledger, generated baselines, pin tests). The 2026-08-22 direction ruling (objectui#4869) stands; this completes it at the producer. - The changeset is NOT major — the maintainer's explicit amendment, overriding this card's own reading that a retirement changeset here is
major. Release semantics are the maintainer's; the implementation lands a non-major changeset.⚠️ Conditional recorded per the opposing-fact rule: if a gate structurally demands a breaking/major declaration for this removal (e.g. the ADR-0087 registration gate keying off the changeset), that is a fork back to the maintainer — ⛔ do not silently bump the changeset to major to satisfy a gate, and do not weaken the gate; report.
Discipline: Clause-② yes (changes accept/reject behaviour — an authored
defaultSortflips from accepted to loudly refused with guidance);CONTRACT_REVIEW_TIER; spec seat. Zero authored occurrences re-verified by the card with a positive control — the refusal breaks no real author.Downstream: objectui#5861 (consumer half) stays
Blocked-by:this card and closes mechanically after the pin bump, per its own recorded flip-list.State:
needs-user-decision→pm:queuein the same write;domain:specstands.
Generated by Claude Code
- Retire
Claim: PM loop round R6
Session:session_01NDGG54XF5gbTLdQzCtnaVV
Branch:claude/issue-11805-retire-object-grid-default-sort
Worktree:objectstack-issue-11805
Domain:domain:spec
File surface:packages/spec/src/ui/component.zod.ts(ObjectGridProps defaultSort region +*_RETIRED_KEY_GUIDANCE) +RETIRED_KEYS_BY_MAJORexact-key entry + ADR-0087 conversion + D3 chain step + migrations tail-append + liveness ledger + regenerated baselines (authorable-surface/ui.json etc.) + pin tests + non-major changeset (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: claude-fable-5— dispatch-gates 2026-08-25 @8f79379: "no path-derived mandate … floor sonnet · default opus · ceiling fable … a card changing contract accept/reject behaviour or widening the public surface is claude-fable-5"; clause-② content leg (authoreddefaultSortflips accepted → loudly refused) + ruling implementation ⇒ fable mandatory
Clause-②: yes
Serial constraints cleared:component.zod.tsprior writer PR #12120 MERGED 13:45Z (this claim's premise re-verified on main@8f79379:defaultSort: z.unknown().optional()still at component.zod.ts:2236, still published in authorable-surface/ui.json).migrations/registry.tstail-append may overlap in-flight PR #12191 (armed for auto-merge) — 4-point discipline applies (tail-append region · fetch+merge before PR taking next free number · re-merge after sibling lands, renumber mechanically · merge queue arbitrates). Ruling basis: maintainer 2026-08-25 (recorded on this card): retirement approved, changeset NOT major, with the recorded opposing-fact conditional (a gate structurally demanding a major/breaking declaration is a fork back to the maintainer, not a silent bump).
Generated by Claude Code
- added a commit that references this issue
on Aug 25, 2026 { "issue": 11805, "status": "done", "premise_still_valid": true, "pr_number": 12256, "pr": "https://github.com/objectstack-ai/objectstack/pull/12256", "branch": "claude/issue-11805-retire-object-grid-default-sort", "head_sha": "9b3dd895fcbc52332d02804a84c8f3981a38a834", "file_list": [ ".changeset/object-grid-default-sort-retired.md", "content/docs/references/ui/component.mdx", "packages/spec/authorable-surface/ui.json", "packages/spec/src/conversions/registry.ts", "packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectGridProps__defaultSort.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/src/ui/component.test.ts", "packages/spec/src/ui/component.zod.ts" ], "summary": "Retired ObjectGridProps.defaultSort via retiredKey() tombstone + RETIRED_KEYS_BY_MAJOR[18] exact-key entry + D2 conversion object-grid-default-sort-removed (wrap-and-rename to sort:[pair] when sort absent; lossless strip when present) wired into the step-18 chain; [RETIRED] surface mark and docs row regenerated; pin tests added; minor changeset with BREAKING + adr-0087 marker. Zero authored occupancy re-confirmed in-repo. Sibling PR #12191 landed mid-flight; merged via os-regen-merge.sh, regenerated, sibling entries and bodies asserted surviving.", "verification": [ "spec full suite (pre-merge 38577eb): 'Test Files 426 passed (426)' / 'Tests 11355 passed (11355)'", "post-merge union @ 9b3dd89 (component.test.ts + conversions + migrations + alias-integrity + migrate-sentence): 'Test Files 8 passed (8)' / 'Tests 575 passed (575)'; pin receipts verbose: 'rejects the retired defaultSort with the wrap-and-rename prescription' / 'does not materialize' / 'keeps sort'", "pnpm --filter @objectstack/spec typecheck: exit 0 (tsc + check:scripts-typecheck + check:test-typecheck OK)", "check:generated @ 9b3dd89: '✓ All 14 generated artifacts are up to date.'", "tsc reverse verification vs rebuilt dist: retired-key probe RED ('error TS2322: Type { field: string; order: string; } is not assignable to type undefined'), sort control GREEN exit 0", "check-adr-0087-registration @ 9b3dd89: '.changeset/object-grid-default-sort-retired.md [BREAKING] registered object-grid-default-sort-removed'", "check-changeset-no-major @ 9b3dd89: '✓ This diff introduces no major bump.'", "gate battery all PASS (exits captured pre-pipe): liveness/empty-state/strictness-ledger/variant-docs/spec-parsed-alias/slot-lookup/test-source-alias/type-source-resolution/query-options-erasure/merge-driver/changeset-gate-self-tests/objectui-changeset/published-files/doc-family x13/cross-package-test-inputs/ci-filter-parity/plugin-teardown-shape/engine-double-contract/where-matcher/type-check-coverage/docs-audit x2/release-rehearsal-clone --self-test/skill-examples (after building client-react)/nul-bytes/empty-changeset", "consumer @objectstack/lint (metadata-authoring-lint): 81 files / 2294 tests + typecheck green", "dispatch-gates.mjs re-derived (no args, tree-asserted at c02fa22): additions beyond PM list were the docs family + check-dev-prereqs; all run except declared narrowings" ], "changeset": "minor for @objectstack/spec (ruling 「不需要major」); check-changeset-no-major verdict: '✓ This diff introduces no major bump.'; adr-0087 marker verified: 'registered object-grid-default-sort-removed (new here: object-grid-default-sort-removed)'", "skills_touched": false, "deviations": [ "Route: retiredKey() tombstone instead of the card's literal 'strict deletion + guidance-map entry' — build gate (a) (packages/spec/scripts/build-schemas.ts) is fatal on any key vanishing from an emitted def ('To retire a key, tombstone it instead of deleting it') and gate (c) ratchets baseline deletions vs merge base for 2 majors; the Metric:filters guidance-route precedent predates its def's surface arrival (dd33bf9) so it never met these gates. Ruled outcome unchanged: authored defaultSort now loudly rejected with prescription; non-major. In-repo shape authority followed (#6946 x3, FormView.defaultSort, #9198, #9220 — all retiredKey inside strictObject).", "No migrations/entries/semantic/ file: the migration ships as a D2 conversion (mechanical rewrite exists, and applyConversionsToStoredItem must heal stored rows the strict parse now refuses), matching the FormView/#6946 wiring; a semantic entry would double-prescribe.", "Liveness ledger: measured no-op (liveness/page.json walks PageSchema top-level only; no ObjectGridProps row exists); check:liveness green.", "Declared verification narrowings: check-dev-prereqs red only on unbuilt fresh-worktree workspace (env precondition; CI builds first); check:type-check-debt --re-measure and full pnpm lint are CI-owned; CLI migrate-meta e2e not run locally (conversion idempotent by construction via the 'defaultSort' in properties guard)." ], "findings": [ "Dispatch prompt said this seat's GitHub API is unavailable (placeholder token) — the MCP GitHub tools DO work from this seat (this comment + PR #12256 were written through them); PM may want to correct the seat capability note.", "check-adr-0087-registration/check-changeset-no-major/check-empty-changeset judge the COMMITTED diff only: run pre-commit they report '0 changesets seen' and read as vacuously green — worth a note in the playbook so agents re-run them post-commit (I did).", "scratchpad note: gen-sdui-manifest.sh's 'dev server no longer running' line appeared interleaved in my locked vitest log — cosmetic cross-talk in shared container logs, no verdict impact." ] }
Generated by Claude Code
os-dev-report (marker line — the HTML-comment form of this marker was sanitized off the previous comment, id 5412579956; this comment is the authoritative report, same content)
{ "issue": 11805, "status": "done", "premise_still_valid": true, "pr_number": 12256, "pr": "https://github.com/objectstack-ai/objectstack/pull/12256", "branch": "claude/issue-11805-retire-object-grid-default-sort", "head_sha": "9b3dd895fcbc52332d02804a84c8f3981a38a834", "file_list": [ ".changeset/object-grid-default-sort-retired.md", "content/docs/references/ui/component.mdx", "packages/spec/authorable-surface/ui.json", "packages/spec/src/conversions/registry.ts", "packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectGridProps__defaultSort.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/src/ui/component.test.ts", "packages/spec/src/ui/component.zod.ts" ], "summary": "Retired ObjectGridProps.defaultSort via retiredKey() tombstone + RETIRED_KEYS_BY_MAJOR[18] exact-key entry + D2 conversion object-grid-default-sort-removed (wrap-and-rename to sort:[pair] when sort absent; lossless strip when present) wired into the step-18 chain; [RETIRED] surface mark and docs row regenerated; pin tests added; minor changeset with BREAKING + adr-0087 marker. Zero authored occupancy re-confirmed in-repo. Sibling PR #12191 landed mid-flight; merged via os-regen-merge.sh, regenerated, sibling entries and bodies asserted surviving.", "verification": [ "spec full suite (pre-merge 38577eb): 'Test Files 426 passed (426)' / 'Tests 11355 passed (11355)'", "post-merge union @ 9b3dd89 (component.test.ts + conversions + migrations + alias-integrity + migrate-sentence): 'Test Files 8 passed (8)' / 'Tests 575 passed (575)'; pin receipts verbose: 'rejects the retired defaultSort with the wrap-and-rename prescription' / 'does not materialize' / 'keeps sort'", "pnpm --filter @objectstack/spec typecheck: exit 0 (tsc + check:scripts-typecheck + check:test-typecheck OK)", "check:generated @ 9b3dd89: 'All 14 generated artifacts are up to date.'", "tsc reverse verification vs rebuilt dist: retired-key probe RED ('error TS2322: Type { field: string; order: string; } is not assignable to type undefined'), sort control GREEN exit 0", "check-adr-0087-registration @ 9b3dd89: '.changeset/object-grid-default-sort-retired.md [BREAKING] registered object-grid-default-sort-removed'", "check-changeset-no-major @ 9b3dd89: 'This diff introduces no major bump.'", "gate battery all PASS (exits captured pre-pipe): liveness/empty-state/strictness-ledger/variant-docs/spec-parsed-alias/slot-lookup/test-source-alias/type-source-resolution/query-options-erasure/merge-driver/changeset-gate-self-tests/objectui-changeset/published-files/doc-family x13/cross-package-test-inputs/ci-filter-parity/plugin-teardown-shape/engine-double-contract/where-matcher/type-check-coverage/docs-audit x2/release-rehearsal-clone --self-test/skill-examples (after building client-react)/nul-bytes/empty-changeset", "consumer @objectstack/lint (metadata-authoring-lint): 81 files / 2294 tests + typecheck green", "dispatch-gates.mjs re-derived (no args, tree-asserted at c02fa22): additions beyond PM list were the docs family + check-dev-prereqs; all run except declared narrowings" ], "changeset": "minor for @objectstack/spec (ruling 「不需要major」); check-changeset-no-major verdict: 'This diff introduces no major bump.'; adr-0087 marker verified: 'registered object-grid-default-sort-removed (new here: object-grid-default-sort-removed)'", "skills_touched": false, "deviations": [ "Route: retiredKey() tombstone instead of the card's literal 'strict deletion + guidance-map entry' — build gate (a) (packages/spec/scripts/build-schemas.ts) is fatal on any key vanishing from an emitted def ('To retire a key, tombstone it instead of deleting it') and gate (c) ratchets baseline deletions vs merge base for 2 majors; the Metric:filters guidance-route precedent predates its def's surface arrival (dd33bf9) so it never met these gates. Ruled outcome unchanged: authored defaultSort now loudly rejected with prescription; non-major. In-repo shape authority followed (#6946 x3, FormView.defaultSort, #9198, #9220 — all retiredKey inside strictObject).", "No migrations/entries/semantic/ file: the migration ships as a D2 conversion (mechanical rewrite exists, and applyConversionsToStoredItem must heal stored rows the strict parse now refuses), matching the FormView/#6946 wiring; a semantic entry would double-prescribe.", "Liveness ledger: measured no-op (liveness/page.json walks PageSchema top-level only; no ObjectGridProps row exists); check:liveness green.", "Declared verification narrowings: check-dev-prereqs red only on unbuilt fresh-worktree workspace (env precondition; CI builds first); check:type-check-debt --re-measure and full pnpm lint are CI-owned; CLI migrate-meta e2e not run locally (conversion idempotent by construction via the 'defaultSort' in properties guard)." ], "findings": [ "Dispatch prompt said this seat's GitHub API is unavailable (placeholder token) — the MCP GitHub tools DO work from this seat (this comment + PR #12256 were written through them); PM may want to correct the seat capability note.", "check-adr-0087-registration/check-changeset-no-major/check-empty-changeset judge the COMMITTED diff only: run pre-commit they report '0 changesets seen' and read as vacuously green — worth a note in the playbook so agents re-run them post-commit (I did).", "GitHub body sanitizer ate the HTML-comment report marker on issue comments from this seat (measured on comment 5412579956) — the literal-text marker fallback in the os-dev contract is load-bearing, not theoretical.", "scratchpad note: gen-sdui-manifest.sh's 'dev server no longer running' line appeared interleaved in my locked vitest log — cosmetic cross-talk in shared container logs, no verdict impact." ] }
Generated by Claude Code
Contract review: PASS — PR #12256, head
9b3dd89(round R6, sessionsession_01NDGG54XF5gbTLdQzCtnaVV).Tier fuse read this sub-round:
get_session.external_metadata.last_served_model=claude-fable-5— meetsCONTRACT_REVIEW_TIER. Reviewed against the PR's diff via git (merge-base → head), not the report alone.- The contract change is exactly the ruled one and nothing else. 8 files; the sole accept-set movement is
ObjectGridPropsSchema.defaultSort: accepted → refused at both channels (tsc: probe redsTS2322 … not assignable to 'undefined',sortcontrol compiles; parse: refusal carries the wrap-and-rename prescription). The tombstone hunk was read in full — prescription is self-contained (rename tosort, wrap the pair in an array,os migrate meta --from 17), the ruling is cited with date and verbatim 「[spec] ADR-0049 enforce-or-remove: retireObjectGridProps.defaultSort(the producer half of objectui#5861) #11805 退役 defaultSort,不需要major」, and the read-point measurements at the objectui pin are recorded in place. - Route deviation accepted as gate-forced and precedent-exact. The card's "strict deletion + guidance map" spelling is structurally forbidden here:
build-schemas.tsgate (a) is fatal on a key vanishing from an emitted def ("tombstone it instead of deleting it") and gate (c) ratchets baseline-line deletions.retiredKey()is the only gate-green shape and is the current shape of every in-repo component-props retirement — verified on main:view.zod.ts:2508defaultSort: retiredKey((the FormView sibling the card itself cites), plusresponsive/performanceand the [spec] Retire three zero-reader UI keys per maintainer ruling: PageHeaderProps.icon, PageCardProps.actions, RecordDetailsProps.layout (objectui#3829 / objectui#3818) #6946 trio. Author-visible behaviour is identical to the ruled outcome. - The opposing-fact conditional did NOT fire.
check-adr-0087-registration— "[BREAKING] registered object-grid-default-sort-removed" — andcheck-changeset-no-major— "nomajorbump" — are both green on the same tree: the gates accept minor + BREAKING + registered, so no fork back to the maintainer is owed. Changeset isminorper the ruling's explicit amendment. - Migration story is sound: D2 conversion
object-grid-default-sort-removedwraps-and-renames whensortis absent and strips losslessly when present — matching the renderer's measured precedence (the fallback was unread whensortexisted). Zero authored occupancy re-confirmed with the doc row being generated output this PR regenerates. - Sibling fold survival (fix(spec): minLength is a positive integer, authorable only on bounded-string field types; register the deferred #11566 maxLength ADR-0087 entry #12191 landed mid-flight) asserted post-merge by exact-name grep; liveness ledger measured no-op with the reason stated (
liveness/page.jsondrills one level; component-props keys carry no row). H17: view.zod.ts untouched.
Verdict: PASS. Dual-carrier
needs:contract-reviewclears with this comment; ready + auto-merge once every CI check on9b3dd89is green. Downstream: objectui#5861 stays blocked on the pin bump per its own flip-list.
Generated by Claude Code
- The contract change is exactly the ruled one and nothing else. 8 files; the sole accept-set movement is
- added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Sep 17, 2026
Filed unassigned by the
domain:uiexecution seat while working objectui#5861 (sessionsession_01CSoz9uGhaaSgiq3hshtN7L). For PM/maintainer triage — not claimed.Why this is here and not in objectui
The maintainer ruling of 2026-08-22 on objectui#4869 directed an ADR-0049 enforce-or-remove retirement of
table.defaultSortas its own card. That card is objectui#5861. Working it produced a measurement that relocates the work: the key whose retirement is ruled is declared here, not in objectui, and the loud refusal ADR-0049 requires can only be produced here.ObjectGridPropsSchema(packages/spec/src/ui/component.zod.ts:2164) is astrictObjectand declares:It is published un-retired as
ui/ObjectGridProps:defaultSortinpackages/spec/authorable-surface/ui.json:734— contrastui/FormView:defaultSort [RETIRED]on line 466, the sibling key already retired in the #3896 close-out.Measured against the pin objectui consumes (
@objectstack/spec@17.2.0)Probed through
@object-ui/test-support's shared tombstone judge, the same one objectui's gates use:object-gridlisted keys / authorable keysobject-gridtombstoned keys[]defaultSortlisted / authorable / tombstonedtrue/true/falsetombstoneEvidence('defaultSort'){ listed: true, typedNever: false, describedRemoved: false }{ defaultSort: { field, order } }safeParseComponentPropsMapthat do carry tombstonesBoth recognition channels agree the key is live, and the positive control shows the instrument detects tombstones where they exist, so the zero for
object-gridis a real zero.Why the consumer side cannot do it
Measured in objectui on
origin/main@0c282d979:ObjectGridSchema(packages/types/src/zod/objectql.zod.ts) is a non-strictBaseSchema.extend(...)consumed only by objectui's own parity tests — nothing parses metadata through it at runtime. Tombstoning it buys zero runtime refusal.object-gridnode is thestrictObjectabove. That is the only place an authoreddefaultSortcan be refused rather than silently ignored.objectui's
apps/console/src/__tests__/registry-inputs-spec-parity.test.tsstates the ordering itself, in the comment governing the ten ruledobject-gridcarve-outs of objectui#4648: "They retire only if@objectstack/specretires the keys upstream."The precedent, and it is exact
objectstack#6946 retired three zero-reader UI keys the same way —
PageHeaderProps.icon,PageCardProps.actions,RecordDetailsProps.layout. Those are three of the 16 tombstones the probe above measured. objectui#3974 then removed the corresponding reads forform.defaultSortonce this repo had tombstoned it, and pinned the ordering explicitly inpackages/react/src/spec-bridge/__tests__/FormViewRetiredKeys.test.ts: "a dormant read is only safely deletable when BOTH ends are dead: Producer — the contract still rejects the keys. This is what makes the deletion not a capability removal."Route and cost
Per the
spec-property-retirementplaybook this is the strict route, not aretiredKey()tombstone: delete the key from the shape and add an entry to the block's*_RETIRED_KEY_GUIDANCEmap, plus the ADR-0087 conversion, the exact-keyRETIRED_KEYS_BY_MAJORentry, the D3 chain step, the liveness ledger, the generated baselines, and pin tests.@objectstack/specis at 17.2.0, and a retirement changeset here ismajor. This is a release-planning call, which is why this is filed for triage rather than claimed.No ADR edit is required — ADR-0049 and ADR-0087 already govern; no ADR enumerates this key.
Authored-occurrence measurement
Zero authored occurrences in either repo, re-measured today with a known-present control (
searchableFields, found by the identical method inexamples/):examples/apps/packages/qa/— noneexamples/apps/e2e/— none authored; the only hits are the carve-out ledger and proseRelated: objectui#5861 (the consumer half, blocked on this), objectui#4869, objectui#4648, objectstack#6946.