Skip to content

docs(adr): ADR-0145 — ui ?: never tombstones retire on the spec's one-major window - #27251

Merged
os-zhuang merged 4 commits into
mainfrom
claude/project-thread-dhlsh7
Oct 28, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/project-thread-dhlsh7

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Fixes #25912

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:新增一篇 ADR-0145(根 docs/adr/)并重生成 ADR 索引:ui 授权面上的 ?: never 墓碑按 spec 自己的窗口退场,退役后再保留一个 major,到 R+2 major 切出时到期,绝不更长;只立规则,不删任何墓碑、不改代码。
  • 为什么改:执行本卡的维护者字母 A(裁决 6407810703);ui 授权面只增不减,每个墓碑都成了永久义务。
  • 风险与回滚:只有文档,无发布面;风险在规则措辞本身(容器豁免、R 的读法),回滚即 revert 本 PR。
  • 先看:ADR 的 D1(规则)、D2(R 怎么读,含早于 ADR-0087 与只属 ui 的退役)、D3(删墓碑不能把拒绝变成静默接收)。
  • 两分钟验证:pnpm check:adr-index && pnpm check:adr-anchors && node scripts/check-adr-links.mjs,应全部 exit 0。
  • 需要你做的:Tier H,等契约复核 PASS 与席位 ACCEPT 后由您批准合并;在那之前本 PR 保持草稿。

What changes

  • New record docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md (Proposed; becomes Accepted by the maintainer's merge). It records letter A and states:
    • D1, the rule. A ?: never tombstone leaves the ui authoring face in the first release after the spec major in which its ADR-0087 conversion expires: carried through protocol major R + 1, due from the cut of R + 2, the same cut at which ADR-0087's 2026-10-26 addendum removes the spec's kit. One clock (the protocol major, never the npm major). The window binds both ways: a ui tombstone neither leaves before nor outlives the spec kit for the same key.
    • D2, reading R. Spec-named retirements read R from the spec (RETIRED_KEYS_BY_MAJOR, else the release-tag clock the spec's deletion gate uses). A retirement that predates ADR-0087 is counted as if its conversion had run the one-major window from the major it retired in, so no such tombstone gets a fresh window. A ui-only retirement runs the same window from the first @object-ui/types release that carried it. Undated means not due, never guessed.
    • D3, the container carve-out. A deletion must leave a refusal: the TS member stays while its container is open (an index signature); the zod twin stays where its container is .passthrough() or strips, which is ADR-0087's own strip-container rule and the posture agedOutSpecKeyRefusal already takes. Derived faces move together.
    • D4, the changeset. minor (never major), a Breaking note naming each Type.key and its remedy, plus the declaration lines and ADR-0087 disposition marker the gates require, not restated.
    • Applying the rule. How the first batch enumerates, dates (git log -S on a full clone; pre-merge ui history is in objectstack-ai/objectui, not here), judges and re-pins.
  • Regenerated index docs/adr/README.md (pnpm adr:index).

Why

Ruling 6407810703 (maintainer letter A, 2026-10-25T01:46Z) over seat options 6407810621. Not taken: B (remove when the spec retires the key) and C (no rule).

Tests

None. This PR declares a rule and changes no code, so there is no behaviour to pin or ablate.

Acceptance notes

Verification

At d3e67407 (the head after the contract-review rework; 01db9123 was tree-identical to 11b3e687), branch point origin/main 8d79bbd7.

Gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 11b3e687: 21 commands, each exit captured before any pipe:
    • node scripts/check-adr-links.mjs :: exit 0
    • node scripts/check-adr-links.mjs --self-test :: exit 0
    • node scripts/check-closing-keyword-parity.mjs :: exit 0
    • node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
    • node scripts/check-comment-mask-corpus.mjs :: exit 0
    • node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
    • pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
    • pnpm check:adr-anchors :: exit 0
    • pnpm check:adr-index :: exit 0
    • pnpm check:adr-symbol-anchors :: exit 0
    • pnpm check:ci-filter-parity :: exit 0
    • pnpm check:closed-ceremony-text :: exit 0
    • pnpm check:cross-package-test-inputs :: exit 0
    • pnpm check:doc-authoring :: exit 0
    • pnpm check:driver-memory-census :: exit 0
    • pnpm check:gitlink-declared :: exit 0
    • pnpm check:nul-bytes :: exit 0
    • pnpm check:pm-governed-merges :: exit 0
    • pnpm check:pm-prior-rulings :: exit 0
    • pnpm check:refd-timer-probe :: exit 0
    • pnpm check:watch-hint-literal :: exit 0
  • check:doc-formula-expressions first exited 3 (prerequisite not met: nothing measured) and exited 0 after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint (under the verification lock, exit 0).
  • --ran: "21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)".
  • pnpm --dir ui run check:new-line-citations: "0 new citation(s)", exit 0.
  • Not run locally, CI's: the type-check lanes and the Plan Test Core shards step --commands lists as NOT MEASURED (no TypeScript changes here).

Generated by Claude Code

…ne-major window

Records the maintainer's letter A on #25912 (ruling 6407810703): a `?: never`
tombstone on the ui authoring face leaves in the first release after the
spec major in which its ADR-0087 conversion expires, one major after the
retirement it names and never longer. States how R is read (spec-named,
pre-ADR-0087 and ui-only retirements), the container carve-out that keeps a
deletion from turning a refusal into a silent accept, the minor + Breaking
changeset, and how the first application batch finds due tombstones.
Declares only; removes nothing. Regenerates the ADR index.

Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk
Co-authored-by: Claude <noreply@anthropic.com>
The pull request opens at a head whose net diff touches no governed path, so
the relay's pr_create requests no Tier H approver before the contract review
(#26617). The next commit re-applies 11b3e68 unchanged.

Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk
Co-authored-by: Claude <noreply@anthropic.com>
Restores 11b3e68's record and regenerated index unchanged (tree equal to
11b3e68), now that the draft pull request is open with no approver
requested; the seat asks for approvers after the contract review PASS and
its ACCEPT (#26617).

Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 28, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · contract review owed · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-28T12:58Z

This PR is Tier H: it adds one record under the root docs/adr/** (ADR-0145) and regenerates docs/adr/README.md. A Tier H PR goes in front of approvers only after a contract review PASS and the seat's ACCEPT (#26617). The seat runs at TIER_DEFAULT (the ceiling model has been unavailable to the fleet since 2026-10-27) and hands the review to triage. The PR stays draft on purpose, because relay pr_ready on a Tier H diff requests the approvers.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-28T12:58Z) · head 01db912331a529a791ee6091e74fa268fedae959

Inputs for the reviewer:

No reviewer is requested and the PR stays draft until a PASS is on file and the seat's Tier H record is posted (#26617).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head 01db912331a529a791ee6091e74fa268fedae959 · 2026-10-28T13:06Z

Triage seat, taking the Contract-review-handoff: triage … line that names this head. The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 01db912331a529a791ee6091e74fa268fedae959
Local-runs: none
Scope: docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md, docs/adr/README.md + docs/adr/0087-metadata-protocol-upgrade-contract.md, docs/adr/0049-no-unenforced-security-properties.md, AGENTS.md, ui/AGENTS.md, ui/packages/types/src/zod/tombstone.zod.ts, ui/packages/types/src/zod/base.zod.ts, ui/packages/types/src/base.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/tests/zod-mirror-parity.test.ts, packages/spec/scripts/build-schemas.ts, packages/spec/src/kernel/protocol-version.ts, packages/spec/src/shared/retired-key.ts, packages/spec/src/migrations/registry.ts.template, scripts/check-changeset-no-major.mjs, scripts/pm/check-governed-merges.mjs, scripts/adr-index.mjs, .changeset/config.json, .changeset/25767-retire-16x-retirement-kits.md

Inputs read: card #25912 body and all 8 comments (ruling 6407810703 over options 6407810621, transfer note 6407817058, order 6468844696, claim 6468920996, os-dev report 6469572550); PR #27251 body, file list (2 files), net diff vs main at the merge-base 8d79bbd7; the 46 check-runs on the head, all completed (none pending): every non-skipped run success, including Lint & Repo Gates, Check Documentation Links, Governed Surface Queue Guard, PR-body guards; Check Changeset skipped under skip-changeset. Live tree read at origin/main 4d3a077f66 (git show/git grep only); origin/main has moved past the branch point by one docs/adr/ commit (9c1214006e, a note inside ADR-0066, no status line), so the regenerated index is still current against today's main.

① Derived judgments

  • Accept set / public surface: none. Two markdown files under docs/adr/; no package source, no schema, no .d.ts, no generated baseline moves. The net diff is exactly +153 (new record) and +4/−3 (index). Right.
  • Governed surface: docs/adr/** is register row adr (scripts/pm/check-governed-merges.mjs:1201, GOVERNED_TIER_H). Tier H; the PR is draft, no approver requested; the seat hands approval to the maintainer after this record. Right.
  • ADR number 0145. On origin/main the registry ends at 0143; of the 21 open PRs, only docs(adr): ADR-0144 multi-currency — record currency, dated rates, write-time home projection; dated note on ADR-0104 #27239 adds 0144-… and only this PR adds 0145-… (REST file lists read). Right.
  • Index regeneration (docs/adr/README.md). Emulated scripts/adr-index.mjs on the head's record (status-line regex, plainText, cell(title,110), statusCell with cell(detail,64), ISO date off the status value): the produced row is byte-identical to the committed row. Summary 145 records (140 numbers), Proposed 4→5, Total 144→145 match the head tree (145 record files, 140 distinct numbers). check:adr-index runs --report in CI (warn-only, exit 0 always), so this was verified by reading, not trusted to the green. Right.
  • Status line. **Status**: Proposed (2026-10-28). … is the one spelling the generator reads; the keyword is Proposed (earliest in the window). The line wraps onto a second physical line mid-sentence; the generator reads the first line only, which is what the index shows. Cosmetic, right.

② Semver level

skip-changeset label present; Clause-②: no, Metadata-protocol: none on the PR body. The diff publishes nothing from any released package (two files under docs/adr/), which is exactly the case AGENTS.md Post-Task Checklist step 3 reserves skip-changeset for. Check Changeset skipped accordingly. The declaration lines match the diff. Right.

③ Boundary flags

  • Dev deviation — pnpm install --frozen-lockfile --ignore-scripts (better-sqlite3 headers download aborted). Docs-only diff; the 21 dispatch-gates --commands families all recorded exit 0 and the head's check-runs are the gate verdicts here. Answered, no effect.
  • Dev deviation — PR template "Human Minimum" checklist not carried. The body carries ## 维护者速读, Fixes #25912, both declaration lines; PR-body guards is green. Answered.
  • Dev deviation — commit trailers. The three commits carry Claude-Session: + Co-authored-by: Claude (the repo's model-free pair), not the harness's model-named line. Repo rule wins. Answered.
  • Tier. dispatch-gates --tier mandates the ceiling tier for docs/adr/**; the claim records the measured quota exemption (ceiling model out for the fleet since 2026-10-27) and hands the contract review to triage. This record is that review, rendered at CONTRACT_REVIEW_TIER. Answered.
  • PR-open sequence (fleet-write: relay pr_create asks the governed approvers to review a Tier H draft before any contract review exists, so an approver can approve and merge it unreviewed (#26566, #26570) #26617). 11b3e687 adds, 98dcc57e reverts, 01db9123 re-applies; git diff 11b3e687 01db9123 is empty (tree-equal), requested_reviewers is empty. Answered.
  • open_questions[0] — does the review accept D2's third bullet (a ui-only retirement runs the same window from its first @object-ui/types release)? A, accepted. Measured on origin/main: the ui TS face carries 432 RETIRED (objectui#…) members and 0 RETIRED (objectstack#…); the ruling's own premise exemplar (objectql.ts:524, ConditionalFormattingRule.field, objectui#11533, cited in options 6407810621) is a ui-only retirement, and ruling item 4 puts "the 640 tombstones" under the batch's dating. Option B would empty the rule of the population the maintainer ruled over. The ruling's clause "the spec major in which the retirement it names has its ADR-0087 conversion expire" has no literal referent for a ui-only key (objectui keeps no conversion registry, tombstone.zod.ts:309), so D2's bullet is the narrowest reading that gives the ruling its population and keeps "never a longer one"; the @object-ui/* packages are in the one fixed group with @objectstack/* (.changeset/config.json:119; @object-ui/types@18.2.0 = @objectstack/spec@18.2.0), so the tag's major is the protocol major. Not escalated.
  • Registry choice (root docs/adr/ vs the ruling's "objectui docs/adr/**"). The ruling predates the transfer (objectui#13467 → docs/adr: a sunset rule for @deprecated tombstones — 574 tags (504 in packages/types, 640 'never' members) with no rule for when a tombstone leaves the authoring face #25912, 2026-10-26). On main, ui/docs/adr/ is the frozen import ([Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A: indexed under ui-, files untouched) and check-adr-anchors.mjs resolves only the root registry. Root is the right reading of the ruling after the merge; the citation the ADR gives for it is imprecise (see ④ should-fix 3). Not escalated.

④ Design judgment

docs/adr/0145-….md — against the ruling, ADR-0087/0049, the spec's tombstone rules on main, and its first consumer (the application batch).

What holds, read in the live tree:

  • The window. ADR-0087 D2 as amended 2026-10-26 (:147–151): "the kit retired in N is in the tree through N+1 and leaves when N+2 is cut"; the addendum (:1390–1395): at major 18 the kits retired at or below 16.x leave. D1's "carried through R+1, due from the cut of R+2" is the same clock; retired-key.ts (header, "lives through the protocol major AFTER the one its key was removed in and leaves when the next major is cut") and agedOutSpecKeyRefusal's docblock ("tombstone through N+1.x and leaves the spec's shape at the next major's cut") say the same. PROTOCOL_VERSION = '18.0.0', so "R ≤ 16 is already due" is right. The ruling's "one major after the spec retirement, never longer" is recorded verbatim in substance; B and C are named as not taken. Right.
  • Reading R (D2). RETIRED_KEYS_BY_MAJOR exists (registry.ts.template:3631, rows 17 and 18); the deletion gate's second clock is TOMBSTONE_DATING_TAG = @objectstack/spec@(CURRENT_MAJOR − 1).0.0 with TOMBSTONE_AGE_MAJORS = 1 (build-schemas.ts:769–797), so "already [RETIRED] at M.0.0 ⇒ retired below M" is the gate's own reading. The pre-launch section ("Ratified: the pre-launch launch-window exemption (majors 12–15)", :481) exists and records the one-step breaks. The "undated ⇒ not due" leg is the right failure direction (a wrong date is option B by accident). Right.
  • The changeset (D4). The spec's own kit removal, .changeset/25767-retire-16x-retirement-kits.md, is '@objectstack/spec': minor with **BREAKING**, Clause-②: no (narrowing), Metadata-protocol: narrowed and an ADR-0087 disposition marker; check-changeset-no-major.mjs is the launch-window guard against major. D4 (minor, never major, Breaking note per Type.key, gate vocabulary not restated) matches that precedent and ruling item 2. Right.
  • Cited facts. BaseSchema declares no index signature (base.ts:65); BaseSchemaCore ends .passthrough() (base.zod.ts:591, BaseSchema = BaseSchemaCore); the five helpers named exist in zod/tombstone.zod.ts; agedOutSpecKeyRefusal exists for exactly the stated reason (18 call sites across app/complex/objectql/public-blocks.zod.ts); the @object-ui/*/@objectstack/* fixed group is stated in ui/AGENTS.md:181; ui history before the merge is not in this repository (3 commits on ui/packages/types/src/objectql.ts, shallow clone). ADR-0049 is what the ui helper file names as its convention (tombstone.zod.ts:10, 21) and ADR-0049's amendment records that the repo cites it for spec-property retirement generally. All true today.

What breaks:

  • MUST-FIX 1 — D3 lets the two faces of a hand-written mirror pair diverge, which the live parity ratchet refuses and the ruling's facet ① excludes. D3 judges the TS member and the zod twin by their own containers, and the execution plan carries that through: step 3 "Due ⇔ … D3 does not hold the key", step 4 "Delete the member and its JSDoc; delete the zod twin where D3 allows", with "move together" reserved to z.input/z.infer-derived TS types. On main, 172 ui mirrors are BaseSchema(Core).extend(…) and 125 end .passthrough() against 39 strict containers, while the TS BaseSchema is closed — so for the bulk of the 451 retirementTombstone() sites the rule as written deletes the ?: never member and keeps the zod twin. That outcome is the fourth direction of ui/packages/types/src/__tests__/zod-mirror-parity.test.ts (MirroredUndeclaredKeys, objectui#9711, :1143, reconciled per registered pair at :2268): a key mirrored on the zod side and undeclared on the TS side is a ledgered drift whose "remedy is NOT automatic … each key is a separate question", judged by tsc -p tsconfig.test.json in the Type Check UI lane. The first batch following D3 therefore either reds that lane or grows that ledger by hundreds of entries, and the ADR neither names the ratchet nor decides the question it poses. It also contradicts the ruling's recorded facet ① ("zod 面与 .d.ts 面同步退场", options 6407810621 and ruling 6407810703), which the ADR itself cites as "two faces retire together". Fix: D3 must state that for a registered mirror pair the two faces leave together or neither does — a zod container that would keep the key in silence holds the TS member too (the key is held, not the face) — and name zod-mirror-parity's fourth direction as the pin that makes the divergence red; step 4 then drops "where D3 allows" on the twin alone. (The exemplar ConditionalFormattingRuleSchema extends the spec's strict rule schema, so it happens to pass; the population does not.)

  • Should-fix 2 — D1's clock sentence contradicts its own instrument. D1 says "major is the protocol major (PROTOCOL_VERSION, the CURRENT_MAJOR the spec's deletion gate reads), ⛔ never the npm major". The deletion gate reads CURRENT_MAJOR off packages/spec/package.json version (build-schemas.ts:302, 769), not PROTOCOL_VERSION, and check-changeset-no-major.mjs (header §3) records the window in which the two differ by one; D2's third bullet then reads R off @object-ui/types release tags, which are npm versions. What ADR-0087's 2026-09-13 amendment keeps apart is the changeset level axis, not the fixed group's major, which is lockstep with the protocol major at every cut. Say "the fixed group's major, lockstep with PROTOCOL_VERSION; a changeset's level is a different axis" and drop "never the npm major".

  • Should-fix 3 — the registry citation. "Prime Directive [WIP] Add Chinese version of the documentation #13: one registry per repository" is the index's CONVENTIONS gloss (scripts/adr-index.mjs), not PD13's text, which says "An ADR lives in the repository whose code it governs … Files never move between registries". The decision that keeps ui/docs/adr/ frozen is [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A (ui- prefix, files untouched); cite that and the anchor checker's root-only resolution for why a new ui decision lands here, since the ruling's own words were "objectui docs/adr/**".

  • Should-fix 4 — D1's "never outlives it" is absolute; D3 then holds past it. The 18 agedOutSpecKeyRefusal twins exist precisely because the spec's kit for those keys already left at 18.2 (list-view-aged-out-spec-keys-25767.test.ts), and D3 keeps them on .passthrough() containers. Write "except as D3 holds" into D1's third bullet. In the same place, warn the batch that a ui guidance's retiredIn: '17.0.0' names the first refusing release, so R for that key is 16 by the gate's clock (already [RETIRED] at 17.0.0), not 17 — a reader who takes the JSDoc's number runs a longer window than the spec, which D1 forbids.

docs/adr/README.md — generated output of scripts/adr-index.mjs over the head tree; row and summary verified by emulation (①); current against origin/main 4d3a077f66. Holds.

Implemented-by: claude/project-thread-dhlsh7
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: FAIL


Generated by Claude Code

…; registry cited

Contract review on PR 27251 (comment 6470170569):
- D3: a registered mirror pair (or a z.input/z.infer-derived TS member) leaves
  together or not at all; zod-mirror-parity's MirroredUndeclaredKeys is the pin.
  Applying step 4 deletes member, JSDoc and twin together.
- D1: "major" is the fixed group's major, lockstep with PROTOCOL_VERSION; the
  deletion gate reads CURRENT_MAJOR off the spec package version; a changeset's
  level is a separate axis. "Never outlives the kit" yields to D3's holds.
- D2: one tag clock for every R (first refused/carried at a .0.0 cut counts in
  the major before it); a guidance's retiredIn names the first refusing release.
- Registry: cite #26080 ruling A and the anchor checker's root-only resolution,
  and Prime Directive 13's own wording.

Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · contract re-review owed · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-28T13:37Z

Triage's contract review 6470170569 (FAIL on 01db9123) is answered by the rework at d3e67407 (ADR only, +29/−15; the index is unchanged):

  • Must-fix 1: D3 now says a registered mirror pair, or a z.input/z.infer-derived member, leaves together or not at all ("the key is held, not one face"); it names zod-mirror-parity's MirroredUndeclaredKeys as the pin and forbids growing its ledger. Applying step 4 deletes the member, its JSDoc and its twin together.
  • Should-fix 2: D1's "major" is the fixed group's major, in lockstep with PROTOCOL_VERSION, with CURRENT_MAJOR read off the spec package version; the changeset level is a separate axis ("never the npm major" is dropped).
  • Should-fix 3: the registry is cited by [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A, the anchor checker's root-only resolution, and PD13's own wording. The PR body's Registry note now says the same.
  • Should-fix 4: D1's "never outlives it" yields to a D3 hold; D2 uses one tag clock (first refused or carried at a .0.0 cut counts in the major before it), and warns that a guidance's retiredIn 17.0.0 means R = 16.

The dev's gates at d3e67407: 21/21 derived commands exit 0. The PR body's Verification line now names d3e67407.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-28T13:37Z) · head d3e67407920a67ae48e29a62a7b1c8bb32a50cda

No reviewer is requested and the PR stays draft until a PASS is on file and the seat's Tier H record is posted (#26617).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head d3e67407920a67ae48e29a62a7b1c8bb32a50cda · 2026-10-28T13:58Z

Triage seat, taking the Contract-review-handoff: triage … line on this head (seat note 6470510491); a delta re-review from triage's FAIL on 01db9123. The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d3e67407920a67ae48e29a62a7b1c8bb32a50cda
Local-runs: none
Scope: docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md, docs/adr/README.md + docs/adr/0087-metadata-protocol-upgrade-contract.md, docs/adr/0049-no-unenforced-security-properties.md, AGENTS.md, ui/AGENTS.md, ui/packages/types/src/zod/tombstone.zod.ts, ui/packages/types/src/zod/base.zod.ts, ui/packages/types/src/zod/app.zod.ts, ui/packages/types/src/base.ts, ui/packages/types/src/strict-authoring-face.ts, ui/packages/types/src/tests/zod-mirror-parity.test.ts, packages/spec/scripts/build-schemas.ts, packages/spec/src/kernel/protocol-version.ts, packages/spec/package.json, scripts/check-changeset-no-major.mjs, scripts/check-adr-anchors.mjs, scripts/adr-index.mjs, scripts/pm/check-governed-merges.mjs, .changeset/config.json

Inputs read: card #25912 body and all 8 comments (options 6407810621, ruling 6407810703, transfer note 6407817058, order 6468844696, claim 6468920996, os-dev report 6469572550); #26080 ruling 6417494426 (read through REST GET, cited by the record); PR #27251 body, file list (2 files), net diff vs main at the merge-base 8d79bbd7, all 5 PR comments (seat handoff 6469599614, triage claim 6469772141, triage FAIL record 6470170569, seat rework note 6470510491, triage re-claim 6470978475); the 45 check-runs on d3e67407, all completed, none pending: 26 success (Lint & Repo Gates, Check Documentation Links, Governed Surface Queue Guard, PR-body guards, Check PR Size, every Type Check · and Lint · lane, Test Core, Test UI, Dogfood Regression Gate, Plan Test Core shards, filter, Auto Label) and 19 skipped (Check Changeset under skip-changeset; the ui build/type/lint/e2e lanes with no ui/ change; the opt-in and schedule gates). Live tree read at origin/main 1287b2fa30 with git show/git grep only. Open PRs' file lists re-read through REST for the ADR number.

Re-review on a moved head. Prior record: FAIL on 01db9123 (6470170569), one must-fix and three should-fixes. Delta since: exactly one commit, d3e67407 (parent 01db9123, not a merge, so there is no merge to recompute), touching the ADR file only, +29/−15; docs/adr/README.md is byte-identical to the prior head. diff net-old.diff net.diff shows the same 29/15 hunks and nothing else: no new file, no change outside the record. origin/main has moved past the branch point by three docs/adr/ commits (9c1214006e, c63ce2ad72: notes inside 0035, 0066 and the semantic-roles record, no status-line change), so the committed index is still current against today's main (git diff origin/main prr-27251 -- docs/adr/README.md is exactly the summary bump and the 0145 row). The lane's rework note (6470510491) claims all four fixes landed; each is checked against the tree in ④.

① Derived judgments

  • Accept set / public surface: none. Two markdown files under docs/adr/; no package source, no schema, no .d.ts, no generated baseline. Net diff +167 (new record) and +4/−3 (index) = 171/3, matching the PR's additions/deletions. Right.
  • Governed surface: docs/adr/** is register row adr, GOVERNED_TIER_H (scripts/pm/check-governed-merges.mjs; AGENTS.md PD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 names docs/adr/** Tier H). The PR is draft, requested_reviewers empty, needs:contract-review labelled; the maintainer's hand lands it after this record and the seat's ACCEPT. Right.
  • ADR number 0145. origin/main registry ends at 0143; of the open PRs read through REST, only docs(adr): ADR-0144 multi-currency — record currency, dated rates, write-time home projection; dated note on ADR-0104 #27239 adds 0144-… and only this PR adds 0145-…. Right.
  • Index regeneration (docs/adr/README.md). Head tree: 145 record files, 140 distinct numbers, 5 **Status**: Proposed first lines — the summary reads 145 records (140 numbers), Proposed 5, Total 145. The 0145 row's title, status and date cells are the prior record's emulated output, unchanged (the delta did not touch the status line or title). check:adr-index is warn-only in CI, so this is verified by reading. Right.
  • Status line. **Status**: Proposed (2026-10-28). … — the generator's spelling; keyword Proposed. Right.

② Semver level

skip-changeset label; Clause-②: no, Metadata-protocol: none on the PR body. The diff publishes nothing from any released package (two files under docs/adr/), the one case AGENTS.md Post-Task Checklist step 3 reserves skip-changeset for. Check Changeset skipped accordingly. The declaration lines match the diff. Right.

③ Boundary flags

  • Prior verdict's carry-over. None inherited: this is a full record on d3e67407; the prior findings are re-judged against the tree in ④.
  • Dev deviation — pnpm install --frozen-lockfile --ignore-scripts. Docs-only diff; the head's check-runs are the gate verdicts (Lint & Repo Gates, Check Documentation Links green on d3e67407). Answered, no effect.
  • Dev deviation — PR template "Human Minimum" checklist not carried. ## 维护者速读, Fixes #25912, both declaration lines present; PR-body guards green. Answered.
  • Dev deviation — commit trailers. All four commits carry Claude-Session: + Co-authored-by: Claude, the repo's model-free pair. Repo rule wins. Answered.
  • Tier. dispatch-gates --tier mandates the ceiling tier for docs/adr/**; the claim records the measured quota exemption and the seat handed the review to triage (6469599614, 6470510491). This record is that review at CONTRACT_REVIEW_TIER. Answered.
  • PR-open sequence (fleet-write: relay pr_create asks the governed approvers to review a Tier H draft before any contract review exists, so an approver can approve and merge it unreviewed (#26566, #26570) #26617). 11b3e687 adds, 98dcc57e reverts, 01db9123 re-applies, d3e67407 reworks; requested_reviewers is empty. Answered.
  • PR body's Gates block names 11b3e687 while its Verification header and the seat note name d3e67407. The body is the lane's account, not evidence; the head's own check-runs answer every derived gate family (all success/skipped, none pending). Answered; a body nit, not a finding on a hits file.
  • open_questions[0] — D2's ui-only bullet (a ui-only retirement runs the same window from its first @object-ui/types release). A, accepted, as on the prior head: the ui TS face carries RETIRED (objectui#…) members and no RETIRED (objectstack#…) ones; the ruling's own exemplar (ConditionalFormattingRule.field, objectui#11533) is ui-only; ruling item 4 puts the whole population under the batch's dating; objectui keeps no conversion registry (tombstone.zod.ts: "objectui keeps no conversion registry (the ADR-0087 D2 table lives in @objectstack/spec)"), so the ruling's clause has no literal referent for a ui-only key and D2's reading is the narrowest that gives the ruling its population; @object-ui/* sits in the one fixed group with @objectstack/* (.changeset/config.json; @object-ui/types@18.2.0 = @objectstack/spec@18.2.0), so the tag's major is the group's major. Not escalated.
  • Registry choice (root docs/adr/ vs the ruling's "objectui docs/adr/**"). The record now cites [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A (6417494426: "register ui/docs/adr/ in the root index under a ui- prefix without renaming files (Prime Directive 13)") and the anchor checker's root-only resolution (check-adr-anchors.mjs: ObjectUI ADR-NNNN is "another repo's registry"), and quotes PD [WIP] Add Chinese version of the documentation #13's own sentence ("An ADR lives in the repository whose code it governs", AGENTS.md). Root is the right reading after the transfer. Not escalated.

④ Design judgment

docs/adr/0145-….md — against the ruling, ADR-0087/0049, the spec's tombstone gate and the ui mirror ratchet on main, and its first consumer (the application batch).

Prior findings, each checked in the tree:

  • MUST-FIX 1 (D3 let a mirror pair's two faces diverge) — resolved exactly. D3's third bullet now reads: for a registered mirror pair (zod-mirror-parity.test.ts) or a z.input/z.infer-derived member "the two faces leave together or neither does: a zod container that would keep the key in silence holds the TS member too, and an open TS container holds the zod twin", names MirroredUndeclaredKeys as "the pin that turns a one-sided deletion red" and forbids growing its ledger; Applying step 4 now says "Delete the member, its JSDoc and its zod twin together (D3)" with "where D3 allows" gone. Checked against the ratchet: the fourth direction is MirroredUndeclaredKeys<M, D> = Exclude<MirroredKeys<M>, DeclaredKeys<D>>, reconciled per registered pair over MIRRORS (assertionMirroredUndeclaredMatchesLedger, judged by tsc -p tsconfig.test.json), and the test's own header says "⛔ Neither is the local fix for a red reconciliation" of ledgering or narrowing — so a TS ?: never deleted while its zod twin stays is a type error in the Type Check UI lane, exactly as the ADR now states; a pair deleted together leaves both subtractions empty. The test header also records "A tombstone is a mirrored member", so a ?: never member counts as declared and the pin fires on the one-sided case. The ruling's facet ① ("zod 面与 .d.ts 面同步退场") is now kept. Holds.
  • Should-fix 2 (D1's clock sentence) — resolved exactly. D1 now says "major" is the one fixed group's major, "lockstep with PROTOCOL_VERSION at every cut", that the deletion gate "reads its CURRENT_MAJOR off @objectstack/spec's package version", and that a changeset's level "is a different axis"; "never the npm major" is gone. Checked: build-schemas.ts SPEC_VERSION = … ../package.json … .version and CURRENT_MAJOR = Number.parseInt(SPEC_VERSION, 10); PROTOCOL_VERSION = '18.0.0' beside @objectstack/spec 18.2.0 (same major); check-changeset-no-major.mjs header §3 admits the one-major lead only between the bump PR and the line's first prerelease, i.e. never at a cut. Holds.
  • Should-fix 3 (registry citation) — resolved exactly. The Registry paragraph cites [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A, the anchor checker's root-only resolution and PD [WIP] Add Chinese version of the documentation #13's actual sentence; the index's "one registry per repository" gloss is no longer attributed to PD [WIP] Add Chinese version of the documentation #13. Holds.
  • Should-fix 4 (D1's absolute "never outlives it"; the retiredIn trap) — resolved exactly. D1's third bullet now ends "except as D3 holds it: a named refusal D3 keeps outlives the kit by design (the agedOutSpecKeyRefusal twins are that case)". D2's first bullet now warns that agedOutSpecKeyRefusal's retiredIn ('17.0.0' at every call site in app.zod.ts, complex.zod.ts, objectql.zod.ts, public-blocks.zod.ts) "names the first release that refused it; by the gate's clock that key was already [RETIRED] at 17.0.0, so R is 16, not 17". Checked against the gate: "[RETIRED] on the surface at @objectstack/spec@17.0.0 means retired at or before the 17 cut, i.e. a ≤16.x kit" (build-schemas.ts), and the 2026-10-26 addendum removed exactly that class at 18 — so the ADR's R = 16 is the gate's own answer. Holds.

What else holds on this head, read in the live tree:

Findings on this head:

Observation, not a finding: with D3 now holding both faces wherever the zod container is .passthrough(), the first batch will list most of the population as "held by D3 (with the container)" rather than "due" until containers are closed; the Consequences section says "some", the ADR writes no count by #9 and D3 says each held one "is a pointer at a container that is still open". That is the ruling's facet ③ (the loud refusal wins) applied, not a defect.

docs/adr/README.md — generated output of scripts/adr-index.mjs over the head tree; unchanged since the prior head; row and summary re-verified against the head tree's 145 files / 140 numbers / 5 Proposed; current against origin/main 1287b2fa30. Holds.

Implemented-by: claude/project-thread-dhlsh7
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(席位终稿)

  • 改了什么:新增 ADR-0145(根 docs/adr/)并重生成索引:ui 授权面上的 ?: never 墓碑在它所指的 spec 退役满一个 major 后移除,和 ADR-0087 的一个 major 转换窗同步,绝不更长。只立规则,不删任何墓碑、不改代码。
  • 为什么改:执行 docs/adr: a sunset rule for @deprecated tombstones — 574 tags (504 in packages/types, 640 'never' members) with no rule for when a tombstone leaves the authoring face #25912 维护者裁决 A(6407810703)。
  • 风险与回滚:纯文档,无运行时影响;第一次按规则删墓碑是另一张卡。回滚即 revert 本 PR。
  • 先看:docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md 的 D1(规则)、D2(R 怎么读,含 ADR-0087 之前的退役)、D3(删后必须仍然拒收,镜像对一起走)。
  • 两分钟验证:pnpm adr:index 后 git diff --exit-code docs/adr/README.md,应无差异。
  • 需要你做的:请 hotlong 或 os-zhuang 在 head d3e67407 上批准(Tier H,docs/adr/**)。批准后席位挂自动合并入队。

席位意见:契约复核第一轮 FAIL(6470170569,一条必改),开发在 d3e67407 修完,复审 PASS(6471216267),席位核对的 diff 与复审范围一致,同意落地。

席位核对

ACCEPT · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · head d3e67407 · 2026-10-28T14:09Z

  1. Shape: Fixes #25912; 2 files, +171/−3: the new record docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md and the regenerated docs/adr/README.md. Labelled skip-changeset (nothing published moves). A trial merge with origin/main 39ea1d67 is clean.
  2. Premise measured: the record reads ADR-0087 D2's one-major window and its 2026-10-26 addendum without changing them; it writes the pre-ADR-0087 counting rule (D2), the minor + Breaking changeset form (D4), and keeps the AGENTS.md pointer on docs: narrative cost — 42.5% of source lines are comments, workflows 58.6%, AGENTS.md is 103 KB with three stale claims, ROADMAP.md still says v3.3.2 and 80 Storybook stories #25919's PR and the sweep out (D5), as ruling A binds. Registry: root docs/adr/ under [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080 ruling A and the anchor checker's root-only resolution, as the review accepted.
  3. Gates / check-runs: on d3e67407: 26 success, 19 skipped, 0 failure; mergeable_state clean. Dev's gates: 21/21 derived commands exit 0.
  4. Open sequence: opened at an empty net diff (11b3e687 adds, 98dcc57e reverts), re-applied at 01db9123, reworked at d3e67407 (fleet-write: relay pr_create asks the governed approvers to review a Tier H draft before any contract review exists, so an approver can approve and merge it unreviewed (#26566, #26570) #26617 route). requested_reviewers is empty until this record.
  5. Accepted deviations: the re-review's two non-blocking should-fixes (5: D2's ui-only clock when a major's first @object-ui/types tag is not .0.0; 6: cite ADR-0087's [Decision] 两条裁决援引同一个 launch-window convention,却给出相反的 changeset 等级(minor vs major)—— 退役一个可写键到底发哪一级? #18003 level-half amendment by name) do not ride this PR, so the PASS stays pinned to its head. The seat files them as one follow-up card after the merge; should-fix 5 errs on the longer side, which D2 already prefers.
  6. Landing: Tier H (docs/adr/**). This record is pinned before ready. The PR gets needs-user-decision, is readied (which requests the GOVERNED_APPROVERS), and the seat arms auto-merge only after a governed approval given after this record.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 14:11
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit e9c88f7 Oct 28, 2026
54 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-dhlsh7 branch October 28, 2026 14:43
os-justin pushed a commit that referenced this pull request Oct 28, 2026
…'s spec tags; D1 names the #18003 level-half amendment

Two clauses of the record, one note each, under ADR-0087's amendment spelling;
the status entry records the acceptance by PR #27251's merge. The decision
(one major after the retirement, never longer) is unchanged; nothing is
implemented.

Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
Co-authored-by: Claude <noreply@anthropic.com>
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 needs-user-decision size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants