Skip to content

feat(spec)!: a view no longer declares itself personal — scope 'personal' and sharing.type retire - #29447

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-if04ti
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-if04ti

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #29037

Clause-②: no (narrowing)
Metadata-protocol: narrowed

维护者速读

  • 改了什么:视图不能再自称"个人视图"。scope: 'personal' 和列表视图的 sharing.type 都被拒收并给出改法;控制台列表工具栏不再显示"个人视图 / 共享视图"标签,只保留"由谁锁定"。
  • 为什么改:这两个声明从来没有限制过谁能看到视图,标成个人的视图其实所有能读这个对象的人都看得到,标签在说假话([Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971 裁决 C:个人视图将来是用户自有记录,不复用这两个键)。
  • 风险与回滚:BREAKING 的 spec minor(只收窄)。已存的行和组装产物在读取时自动转换,旧版本写入的默认值 collaborative 照收并丢弃,所以现有视图不用改就能加载;回滚就是 revert 本 PR。
  • 先看:packages/spec/src/ui/view.zod.ts(两处退役与改法文案)和 packages/spec/src/conversions/registry.ts 的 personalViewDeclarationsRemoved 转换。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run --project repo src/ui/view-personal-declarations-retirement.test.ts,看 personal 两处被拒、collaborative 被丢弃、转换后的行能通过。
  • 需要你做的:看 open questions 里 collaborative 残留是照收丢弃还是一起拒收(默认按 feat(spec): retired-defaulted-key tolerance — the retired default parses as inert residue and strips; non-default values keep the loud refusal (#12497 class rule) #12840 惯例照收丢弃)。

What changes

Per ADR-0049 enforce-or-remove and ADR-0087, the two inert personal declarations retire.

  • view.scope: 'personal' is refused by enumWithRetiredValues with a prescription at every door that reads a view item's scope: ViewItemSchema, the wire schema, the ViewMetadataSchema record and overlay arms, and AssembledViewArtifactSchema. package and shared are unchanged.
  • view.list.sharing.type retires as a whole key with a retiredKey tombstone, not just its personal value. Once personal goes, collaborative is the only value left, and the only reader was the console badge. A measured git grep over packages/ and ui/ finds no server, runtime or service reader. lockedBy stays.
  • The emitted default type: 'collaborative', which a released toolchain wrote into every parsed sharing block, is accepted and stripped through acceptRetiredDefaultResidue (house rule feat(spec): retired-defaulted-key tolerance — the retired default parses as inert residue and strips; non-default values keep the loud refusal (#12497 class rule) #12840). type: 'personal' is refused.
  • D2 conversion view-personal-declarations-removed (identifier personalViewDeclarationsRemoved, retiredFromLoadPath: true, retiredAfter: '18.2.0') serves stored-row rehydration, assembled artifacts and os migrate meta. It strips scope: 'personal' from views / viewItems rows, and sharing.type from list payloads, list view item configs, flattened list overlays and objects[].listViews.*. Route: it deletes the declaration and does not invent shared, because nothing a reader sees changes.
  • D3 entry ui-view-personal-declarations-retired carries the part a migration cannot decide (keep the view as everyone-visible, delete it, or wait for user-owned personal views). One ADR-0087 marker sits in the changeset. There is also a RETIRED_KEYS_BY_MAJOR entry ui/ViewSharing:type and a STEP18 rationale fragment.
  • The docblocks that called per-user views a "parked direction" now point at [Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971 letter C and the ADR notes in PR docs(adr): record the #28971 personal-view ruling at ADR-0131 D13 and ADR-0017 #29321.
  • Console (in surface, the target-2 reading):
    • ui/packages/plugin-list/src/ListView.tsx: the personal/shared badge is gone, and a view with sharing.lockedBy still shows "Locked by …" (data-testid="sharing-locked-by").
    • ui/packages/core/src/utils/normalize-list-view.ts: drops the legacy sharing: { visibility, enabled } pair instead of folding it onto the retired type.
    • list.sharingPersonal / list.sharingCollaborative are removed from all 10 locales.
  • Docs: content/docs/ui/audience-based-interfaces.mdx now says per-user list preferences instead of personal views. The liveness row view.json#list.sharing is re-cited to the lock reader.
  • Regenerated, riding this PR: authorable-surface/ui.json, authorable-defaults/ui.json, and content/docs/references/{api/protocol,data/object,ui/view}.mdx.

Forced test edits

These are existing tests that pinned the retired values, and each one changes only its fixture:

  • packages/spec/src/ui/view.test.ts: the ViewSharingSchema describe was rewritten (an empty block parses to {}, and the lock is accepted). Three list-view literals change from sharing.type to sharing.lockedBy.
  • packages/spec/src/ui/view-item-owner-hidden-retirement.test.ts: the bystander row's scope: 'personal' changes to 'shared'. The owner/hidden conversion fixture in conversions/registry.ts gets the same edit.
  • packages/spec/src/ui/view-overlay-viewkind-arm.test.ts: { sharing: { lockedBy: 'admin' } }.
  • packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts: { sharing: { lockedBy: 'admin' } }.
  • ui/packages/plugin-list/src/__tests__/ListView.test.tsx: the sharing tests were rewritten around sharing-locked-by.
  • ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx and ListViewBlock.specInputs-13047.test.tsx: they mount { sharing: { lockedBy: 'admin' } } and assert sharing-locked-by.
  • ui/packages/core/src/utils/__tests__/normalize-list-view.test.ts: the legacy pair is dropped, and no type is written.
  • ui/packages/plugin-view/src/__tests__/ObjectView.namedViewProtocolKeys-8980.test.tsx: lockedBy values.
  • ui/packages/types/src/__tests__/p1-spec-alignment.test.ts: { lockedBy } only.
  • ui/apps/console/src/__tests__/registry-inputs-spec-parity.test.ts: the list-view.sharing reason prose.

New test file: packages/spec/src/ui/view-personal-declarations-retirement.test.ts. It covers every door, defineStack 422, the type-level pins, the D2 conversion and the D3 entry, plus a tree-scoped absence walk.


Generated by Claude Code

`scope: 'personal'` leaves ViewScopeSchema (enumWithRetiredValues) and
`sharing.type` leaves ViewSharingSchema (retiredKey tombstone, the released
`collaborative` default accepted as residue). Nothing ever restricted a
view to one user, so a view marked personal was listed for everyone under
a badge that said otherwise.

ADR-0087 kit: D2 `view-personal-declarations-removed`, D3
`ui-view-personal-declarations-retired`, retired key
`ui/ViewSharing:type`. The console list toolbar draws only the lock from
`sharing.lockedBy`; the legacy visibility fold writes no type.

Claude-Session: https://claude.ai/code/session_01ADHkmTHDhPHuivpyvN8gfF
Co-authored-by: Claude <noreply@anthropic.com>
…aration retirement; changeset

Claude-Session: https://claude.ai/code/session_01ADHkmTHDhPHuivpyvN8gfF
Co-authored-by: Claude <noreply@anthropic.com>
…n order; read union-door issues through invalid_union

Claude-Session: https://claude.ai/code/session_01ADHkmTHDhPHuivpyvN8gfF
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:ui tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 17 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

⛔ 1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object personalViewDeclarationsRemoved), retiredFromLoadPath (symbol, a field of const object personalViewDeclarationsRemoved))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json, …) — pages documenting those are invisible to this run
  • 7 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 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 a644163669cc57d6d1471008caad6099f1f49413 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b05c3ad60190f47bdc32d0357a0ea67208779d4a — the merge of head 92ac574df7221a069c1a7047e0c29b1c008d69a5 into base a644163669cc57d6d1471008caad6099f1f49413, 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 b05c3ad60190f47bdc32d0357a0ea67208779d4a && git checkout b05c3ad60190f47bdc32d0357a0ea67208779d4a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a644163669cc57d6d1471008caad6099f1f49413 92ac574df7221a069c1a7047e0c29b1c008d69a5 && git checkout -B drift-repro a644163669cc57d6d1471008caad6099f1f49413 && git merge --no-ff 92ac574df7221a069c1a7047e0c29b1c008d69a5

node scripts/docs-audit/affected-docs.mjs --json a644163669cc57d6d1471008caad6099f1f49413

⚠️ 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 a644163669cc57d6d1471008caad6099f1f49413 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Head-sha: 92ac574d · read 2026-11-02T16:19Z

维护者速读

  • 改了什么:视图的 scope: 'personal' 和 sharing.type: 'personal' 退役,在每道 scope 门都会被拒,并给出改法;sharing.type 整个键退役(只剩列表徽标在读,徽标改为只看锁定状态)。旧工具链写出的默认值 collaborative 照收并剥掉。带 D2 转换,存量视图自动去掉这两个声明;另有 D3 条目和 ADR-0087 标记。
  • 为什么改:裁决 [Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971 选 C(6570568131,「同意」):命名个人视图走用户自有对象,不复用这两个键,所以两个无效的 personal 值退役。
  • 风险与回滚:收窄(BREAKING,带迁移)。写了 personal 的视图会被拒,转换会自动清理存量。回滚即撤销本 PR。
  • 先看:packages/spec/src/ui/view.zod.ts 和 packages/spec/src/conversions/registry.ts 的新转换。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/view-personal-declarations-retirement.test.ts。
  • 需要你做的:无,席位落地。

席位意见:可落地,等契约复审。

席位核对 — ACCEPT · domain:spec#1 · head 92ac574d

  1. Shape: draft, base main (main 96fb7b5d merged in), Fixes #29037, Clause-②: no (narrowing) / Metadata-protocol: narrowed in the body and the changeset. 39 files, +975/-292. Not governed.
  2. Ruling: [Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971 letter C (6570568131, 「同意」) rules the narrowing. Both inert personal values retire with a prescription; sharing.type retires whole because the badge was its only reader (the PR states the reading).
  3. Surface: claim 6578962562 as amended on this card. The ui emitter, the badge i18n keys, ui pins and the ADR-0087 retirement mechanics are forced by the key's retirement, and the seat accepts them.
  4. Open questions, the seat's answers: collaborative is accepted and stripped as emitted residue (feat(spec): retired-defaulted-key tolerance — the retired default parses as inert residue and strips; non-default values keep the loud refusal (#12497 class rule) #12840): A. The identifier personalViewDeclarationsRemoved is kept, and the id string is unchanged: A.
  5. Readings (dev, on 92ac574d): spec 791 files / 23468 tests; check:generated 0; metadata-protocol 47/47; ui consumers 642/642; 110/110 derived gates, DERIVED zero. Dogfood 389/396: two control timeouts pass alone, and four flow reds come from main's fix(runtime): every flow start door judges a start input against the object the flow's definition reads it from #29365, which main's test(dogfood): re-point the runAs and anonymous-guest flow pins to flow start admission's record rule #29451 fixes (17/17 with its three files). CI runs on the merge ref, so it carries test(dogfood): re-point the runAs and anonymous-guest flow pins to flow start admission's record rule #29451.
  6. Landing: a narrowing on the published schema face, so one CONTRACT_REVIEW_TIER review by the seat (claim line follows). After a PASS and green CI, the seat readies and queues. Next on view.zod.ts is spec(view): the any-of filter group still says [PLANNED] and "no console applies it yet" after #28370 landed #28522.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head 92ac574 · 2026-11-02T16:20Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 92ac574df7221a069c1a7047e0c29b1c008d69a5
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ViewSharing__type.ts, packages/spec/src/migrations/entries/semantic/18.ui-view-personal-declarations-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/view.zod.ts, ui/packages/core/src/utils/normalize-list-view.ts, ui/packages/plugin-list/src/ListView.tsx + packages/spec/src/shared/retired-key.ts, packages/spec/src/conversions/walk.ts, packages/spec/src/conversions/types.ts, packages/spec/src/ui/assembled-views.zod.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/data/object.zod.ts, packages/spec/src/ui/view-personal-declarations-retirement.test.ts, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/metadata-protocol/src/protocol-metadata-issues.ts, packages/metadata-protocol/src/protocol-stored-item-conversion.ts, packages/metadata/src/loaders/database-loader.ts, ui/packages/plugin-list/src/tests/ListView.test.tsx, ui/packages/core/src/utils/tests/normalize-list-view.test.ts, ui/packages/react/src/hooks/useViewSharing.ts, .changeset/29037-view-personal-declarations-retired.md, .github/workflows/ci.yml

Inputs read: card #29037 (body, triage 6571434336, seat claim 6578962562, dev report 6582129886, claim amendment 6582177714), the ruling of record 6570568131 on #28971 (letter C, 「同意」), the PR body, its file list, git diff origin/main...92ac574d and the 66 check-runs on the head. The seat's conclusions were not adopted; every claim below was re-read in the tree.

① Derived judgments

The accept-set narrowing — scope: 'personal'. ViewScopeSchema is enumWithRetiredValues(['package', 'shared'], VIEW_SCOPE_RETIRED) (view.zod.ts); the helper refuses at construction a retired member still listed, a blank prescription, or no retired member, and answers personal with the prescription through the enum's own error map. Every door that reads a view item's scope derives from the two ViewScopeSchema reads at view.zod.ts (the strict record and the wire member): ViewMetadataSchema's record and overlay arms and AssembledViewArtifactSchema build on those shapes, and the new test pins five doors plus defineViewItem. The union doors (view save door, assembled channel) report the refusal inside invalid_union.errors; protocol-metadata-issues.ts already descends invalid_union branches on the wire, so the HTTP author sees the prescription, not Invalid input. package and shared are kept and parsed through (CONTROL rows). git grep on the head: no writer of scope: 'personal' in packages/, ui/, examples/, templates/, skills/, content/ outside the retirement kit; ui/ has no reader of scope at all (the switcher groups by viewKind + object).

sharing.type retires whole, as a tombstone. ViewSharingSchema is acceptRetiredDefaultResidue(strictObject({ type: retiredKey(VIEW_SHARING_TYPE_RETIRED), lockedBy }), { type: 'collaborative' }). The tombstone carries the prescription (version 18.3.0, what replaced it, the one-line fix, the os migrate meta --from 17 sentence) and the TypeScript face is never; personal, private and any other value are refused with the tombstone's text at all six list-view doors the test pins (ListViewSchema, view.list, view.listViews.*, ObjectListViewSchema, a list item's config, the flattened list overlay), at the object door and at defineStack as STACK_SCHEMA_INVALID / 422. Retiring the key rather than narrowing it to one value is what the card's target 2 asked for once no reader beyond the badge needed it; the head confirms that reading: the one production reader of sharing.type on main was ListView.tsx, and ui/packages/types takes ViewSharingSchema by reference. The ruling names the personal declarations; the whole-key retirement is the card's conditional target, exercised with the reading stated in the PR body, so it is within what the card authorises.

collaborative as residue (#12840). acceptRetiredDefaultResidue strips the key only when its value equals the captured literal 'collaborative', copy-on-write, in a z.preprocess ahead of the closed shape; any other value falls through to the tombstone. This is exactly the house rule that helper encodes (a retired DEFAULTED key's emitted default is residue, not an authoring choice, discriminated by value because JSON carries no provenance). main's schema had .default('collaborative'), so every artifact a released toolchain parsed with a sharing block carries that literal, and refusing it would strand every stored list view with a sharing block for nothing. The authoring face still rejects a fresh type: 'collaborative' at tsc. Sound.

Nothing refused beyond ruling C and the card. scope: 'package' | 'shared' and sharing.lockedBy are unchanged (CONTROL rows pass through by value). The form view's public-link sharing block is a different schema (SharingConfigSchema) and the D2 test proves it is untouched. normalize-list-view.ts no longer folds the legacy objectui visibility/enabled pair onto a type; it drops the pair and keeps lockedBy, which is the only change that could reach the Console's own normalisation and it invents no retired key.

The D2 conversion covers every shape the narrowing newly refuses. Newly refused stored shapes are (a) scope: 'personal' on a view item record or flattened overlay in views or the assembled viewItems channel, and (b) sharing.type on a list payload wherever a list payload can sit: a record's config, a container's list and listViews.*, a flattened list overlay, the same three in viewItems, and objects[].listViews.*. personalViewDeclarationsRemoved reaches (a) through mapCollection over both collections (containers skipped, which is right: no container shape ever declared scope), and (b) through mapViewPayloads (walk.ts: record config, container slots, flattened overlay, in that guarded order), an explicit viewItems pass with the same three spellings, and an objects pass over listViews. component.zod.ts lifts columns, selection, rowActions, bulkActions, calendar, conditionalFormatting off ListViewSchema.shape but never sharing, so no page block carries the key and the "no page block declares sharing" claim holds. The fixture exercises all eight paths and asserts expectedNotices: 8; the new test replays a stored record, a stored overlay and a stored container through applyConversionsToStoredItem('view', …) and parses the output through all four item schemas, proves idempotence (second replay returns the same reference) and proves the entry is retired from the authoring funnel. applyConversionsToStoredItem is the seam both database-loader.ts and protocol-stored-item-conversion.ts read stored rows through, so no stored view is stranded. retiredFromLoadPath: true with retiredAfter: '18.2.0' matches types.ts (a retired entry must carry the version it retired after) and the ten other 18.2.0 entries; the prescription's 18.3.0 is the version the minor changeset produces from 18.2.0, which retired-key-removal-version.pin.test.ts now pins for both refusals. Order 82 is the highest in MAJOR_18_CONVERSIONS and nothing earlier in the chain produces either declaration (no other conversion touches a list sharing block; viewItemOwnerHiddenRemoved at 37 deletes owner/hidden and its bystander fixture row moved from scope: 'personal' to 'shared' to keep whole-table replay disjoint). The (removed) route, deleting rather than writing shared, changes nothing a reader sees and matches the prescription's own instruction.

② Semver level

Clause-②: no (narrowing) and Metadata-protocol: narrowed are the right lines: a value is refused and a key is tombstoned, nothing is added or re-meant. The PR body and the changeset carry both, identically. The changeset is minor on @objectstack/spec, @object-ui/plugin-list, @object-ui/core and @object-ui/i18n, the four published packages whose source the diff moves (the metadata-protocol, plugin-view, types and console edits are test-only), which is the "a kind takes at least minor on a package the diff moves" rule; it opens with a feat(spec)!: summary and a BREAKING banner, carries the FROM → TO mapping and the one-line fix, states the parse text, the residue rule and what is unchanged, and carries exactly one ADR-0087 marker, an HTML comment reading adr-0087: registered view-personal-declarations-removed, ui-view-personal-declarations-retired, in the comma-separated registered form check-adr-0087-registration.mjs documents; both ids resolve in the registries on the head (MIGRATIONS_BY_MAJOR[18].conversionIds and .semantic, pinned by the new test). Check Changeset, Lint & Repo Gates and the PR-body guards are green on the head.

③ Boundary flags

  • Surface breach, all forced. Everything outside the claim's named surface is either a pin of the removed key (ui registry-inputs-spec-parity, ListView/ListViewBlock, ObjectView protocol-keys, p1-spec-alignment, protocol.graft-folded-form-sections), the emitter and strings the badge removal makes dead (normalize-list-view.ts and its test; list.sharingPersonal / list.sharingCollaborative out of all ten locales, list.sharingLockedBy kept in all ten), or the ADR-0087 mechanics a tombstone requires (one retired-keys entry ui/ViewSharing:type in the 18.ui__Def__key.ts form of its neighbours, one semantic entry named after its id, one STEP18 rationale fragment at order 117, vitest.repo-tests.json, the removal-version pin). The claim amendment 6582177714 accepts exactly this set. Nothing in the diff is outside it.
  • Open question A on collaborative: sound, see ①. Open question A on the identifier personalViewDeclarationsRemoved: sound; the registry id string view-personal-declarations-removed is what every registry, notice, changeset marker and test reads, and the TypeScript identifier is file-local.
  • Badge. ListView.tsx reads only sharing.lockedBy (sharingLockedBy, string-typed and empty-guarded), draws Locked by … under data-testid="sharing-locked-by", counts it in the toolbar's left-side layout, and imports neither ViewSharingSchema nor Share2 any more. The test mounts a stored row still carrying type: 'personal' / 'collaborative' and asserts no label and the lock drawn, which is the right behaviour for a served row the conversion has not yet rewritten. No other ui/ consumer used the old share-button test id. liveness/view.json re-cites the row to ListView.tsx#sharingLockedBy and Spec property liveness is green.
  • Dogfood. The head's Dogfood Regression Gate (n/3) shards are skipped because ci.yml runs them only off pull_request and merge_group events; the aggregate success verifies attestations, so the head's check-runs neither confirm nor deny the dev's local reds. Checked another way: fix(runtime): every flow start door judges a start input against the object the flow's definition reads it from #29365 (dfbe34d8, in the head) changes packages/runtime flow-start admission and its docs; test(dogfood): re-point the runAs and anonymous-guest flow pins to flow start admission's record rule #29451 (6af3d295, on origin/main but not in the head) re-points three packages/qa/dogfood flow pins. Neither touches a view path, and the merge queue's own run carries test(dogfood): re-point the runAs and anonymous-guest flow pins to flow start admission's record rule #29451. Every other check-run on the head is success or an opt-in skipped.
  • Docs and readers. view.mdx, protocol.mdx and object.mdx are regenerated ([REMOVED] tombstone row for type, scope enum down to two members, the owner row re-worded off "parked direction"); audience-based-interfaces.mdx:51 now says per-user list preferences. git grep on the head finds no server, objectql, services, cloud-connection, examples, templates or skills reader or writer of scope: 'personal', sharing.type or 'collaborative' in a view context; the only remaining spellings are the retirement kit, release-owned CHANGELOGs and the content/docs/releases pages the tree-scoped absence walk excludes by design. main's ADR-0017 and ADR-0131 already carry the [Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971 amendment notes (PR docs(adr): record the #28971 personal-view ruling at ADR-0131 D13 and ADR-0017 #29321, merged); ADR-0017's historic z.enum(['package','shared','personal']) line sits under that note and the card excludes the ADRs.
  • Residue, non-blocking, for a follow-up card: ui/packages/react/src/hooks/useViewSharing.ts still says "aligned with ViewSharingSchema" over a four-value visibility enum and "Implements ViewSharingSchema from @objectstack/spec v2.0.7"; it is local component state with no reader or writer of either retired key, so it is not a stranded reader, but its docblock promises a spec alignment that has not existed for many majors. The dev's own out-of-scope notes (view.manageShared has no enforcing reader; view.form.ts's navigation_sharing section describe) are pre-existing and correctly not folded into this PR.

④ Design judgment

  • view.zod.ts against ruling C and the card: the narrowing takes nothing a consumer could use away. Both declarations were inert on main (both switcher read paths list every item bound to the object; the badge was the only reader), so the change makes the spec's refusal real where the old accept-set made a false visibility promise; the prescriptions name the version, the cause, the fix, the replacement (user-owned personal views) and the migrate command, with no tracker numbers in runtime strings. The docblocks now point to the ruling instead of a parked direction.
  • conversions/registry.ts against its first consumers (database-loader.ts, protocol-stored-item-conversion.ts, os migrate meta, the artifact door): lossless by construction, copy-on-write, reach proven against every carrier, order and retirement stamp consistent with the chain.
  • The retired-keys / semantic entries and the template fragment against ADR-0087: one D3 entry for two declarations that leave for one reason and share one replacement is a defensible single disposition; the value-level retirement has no RETIRED_KEYS_BY_MAJOR row, matching the 18.ui-form-view-type-layout-only precedent, and the key-level one has its row. The D3 text states what the conversion cannot decide (keep, delete or rebuild the view) and its acceptance criteria are checkable.
  • normalize-list-view.ts and ListView.tsx against the spec they consume: the emitter writes no retired key and the badge reads the one live key. A legacy-only pair now normalises to sharing: {} rather than being removed, which parses and draws nothing; harmless, noted.
  • @ts-expect-error pins in the new test are compiled: packages/spec's typecheck runs check:test-typecheck over the test files its tsconfig.json excludes, and the file is not in the debt ledger.

No must-fix found. The retirement is complete on every door, every stored shape converts, the declaration is consistent and at the right level, and the surface breach is exactly what the retirement forces.

Implemented-by: claude/project-thread-if04ti
Reviewed-by: session_01ADHkmPi25XXKx92Xmg4viS

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 16:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 85ea12f Nov 2, 2026
74 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-if04ti branch November 2, 2026 16:55
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/xl tests tooling

Projects

None yet

1 participant