You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: retire the two inert personal declarations — a view marked personal is visible to everyone, and the docs promise personal views that do not exist #29037
Filed by the record-page epic seat as an execution step of the ruling on #28971. The maintainer answered 「同意」 (2026-11-02) to the seat's recommendation of option C. Readings are on mainab085a0482. No domain:* label is set: triage routes this card.
Business reason
An author, usually AI, writes scope: 'personal' or sharing: { type: 'personal' } on a view. It saves, everyone who can read the object still sees the view, and the toolbar badge says personal. That is a silent promise about who can see the data, and it is false.
Real personal views, ruled on #28971, will be a user-owned data object. They will not use these keys.
Reading
packages/spec/src/ui/view.zod.ts:5788: ViewScopeSchema lists personal, and its docblock says nothing restricts such an item to one user. It is read at lines 5870 and 6542.
In examples/ and skills/, a search for these two values finds nothing. HotCRM's view files have no sharing.type and no scope: 'personal'; its two sharing blocks are public form links, which this card doesn't touch.
Target
personal leaves ViewScopeSchema. An authored value is refused with a prescription, not silently accepted, under ADR-0049 and ADR-0087. Use the spec-property-retirement skill.
personal leaves sharing.type, the same way. That leaves one value, so read whether type and the badge still earn their place, and retire them with it if not. State the reading in the PR.
Read fresh. The retirement lands in packages/spec/src/ui/view.zod.ts (ViewScopeSchema 'personal' and ViewSharingSchema type 'personal') under ADR-0049 and ADR-0087, with the plugin-list badge and one docs line as consequences, so domain:spec. It removes keys and adds none, and it fixes a user-visible false promise about who sees a view, so p2 rather than the new-protocol p1. It is a ruled execution step of [Decision] Named personal list views: can a reader save their own named list, and what is the end state? #28971, so pm:queue.
Claim: PM loop round 1 · 2026-11-02T13:55Z
Session: session_01ADHkmPi25XXKx92Xmg4viS (seat); dev = card thread session_01ADHkmTHDhPHuivpyvN8gfF
Account: os-steve
Branch: claude/project-thread-if04ti
Worktree: the card thread's own checkout of objectstack-ai/objectstack
Domain: domain:spec
Seat: domain:spec#1
File surface: packages/spec/src/ui/view.zod.ts (ViewScopeSchema, ViewSharingSchema, the "parked direction" docblocks) and its tests; packages/spec/src/conversions/registry.ts only if the retirement route needs a D2 conversion; the generated baselines, forms, pins and references the retirement touches; content/docs/ui/audience-based-interfaces.mdx; ui/packages/plugin-list/src/ListView.tsx badge only if sharing.type itself retires; root .changeset/ (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (a narrowing on the published schema face) before landing, effort: high
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: packages/spec view.zod.ts ViewScopeSchema / ViewSharingSchema | scope 'personal' and sharing.type 'personal' save but restrict nothing, while the badge says personal | authors and agents who believe a view is private to one user
Thread-read: 6571434336
Premise: holds (the card's readings are on main ab085a0482; the dev re-reads them on the base)
Serial constraints cleared: PR #29287 merged (b5e7ba5552, same file); #28522 follows this card on view.zod.ts
Ruling-ref: #28971 letter C (record 6570568131, 「同意」) — named personal views are a user-owned D3 object and do not reuse these keys, so both inert personal values retire with a prescription (ADR-0049, ADR-0087). If sharing.type is left with one value, it retires too unless a reader other than the badge needs it; the PR states the reading. The ADR notes ride PR #29321.
{
"issue": 29037,
"status": "done",
"branch": "claude/project-thread-if04ti",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29447",
"session": "session_01ADHkmTHDhPHuivpyvN8gfF",
"premise_still_valid": true,
"summary": "Both inert personal declarations retire per #28971 letter C. view.scope 'personal' is refused with a prescription by enumWithRetiredValues at every door that reads a view item's scope. view.list.sharing.type retires whole (retiredKey tombstone): once 'personal' goes it has one value left, and its only reader is the console badge (git grep over packages/ and ui/ finds no server or runtime reader). The emitted default 'collaborative' is accepted and stripped through acceptRetiredDefaultResidue (#12840 house rule). Route: D2 conversion view-personal-declarations-removed deletes both declarations on stored rows, assembled artifacts and os migrate meta. D3 entry ui-view-personal-declarations-retired, RETIRED_KEYS_BY_MAJOR ui/ViewSharing:type, one ADR-0087 marker. Console (in surface): the ListView badge becomes a lock-only 'Locked by' indicator, normalize-list-view drops the legacy visibility pair instead of writing type, and list.sharingPersonal/list.sharingCollaborative leave all 10 locales. The docs line changes to per-user list preferences.",
"tests": "head 92ac574d (main merged at 96fb7b5d via os-regen-merge.sh, authorable-surface regenerated): spec test 791 files passed, 23468 passed | 1 todo (lock VERDICT exit 0); spec typecheck exit 0 (pre-merge 933882e0); spec check:generated exit 0 (14 artifacts current, dist built this round); metadata-protocol protocol.graft-folded-form-sections 47/47; ui consumers (plugin-list ListView + inertKeys-13074 + specInputs-13047, core normalize-list-view, plugin-view 8980, types p1-spec-alignment, console registry-inputs-spec-parity) 7 files, 642/642 (a796462b, ui sources unchanged since); new file view-personal-declarations-retirement.test.ts in the repo project.",
"gates": "dispatch-gates --commands: 110 derived; each run with < /dev/null and its exit captured; --ran reconciliation: 110 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Two first-pass reds were environment: check-plugin-teardown-shape --self-test exit 3 (shallow clone, cleared after fetching its fixture commit) and pnpm --dir ui exec vitest run scripts/ exit 1 (4 tests timed out at 15s while dogfood shared the box); both exit 0 on re-run (ui scripts 169 files, 4921 passed). check:skill-examples and check:dual-build-cjs-loads ran after building their 7 extra packages. PR check-runs: not yet read (CI just started).",
"dogfood_battery": "92ac574d · os-verify-lock: VERDICT command-exit 1 · held the lock 2866s · Test Files 4 failed | 389 passed | 3 skipped (396) · Tests 6 failed | 3237 passed | 28 skipped. Failures: external-import-code-datasource-namespace (control) and wall-package-refusal (single CONTROL) timed out at 5000ms and pass when re-run alone; flow-runas x3 (404 RECORD_NOT_FOUND) and declarative-endpoint-anonymous-guest x1 come from main: #29365 (dfbe34d8, in my merge) added the flow-start record-read refusal, and #29451 (6af3d295, landed on main after my merge) re-points those pins. With #29451's three test files checked out on my tree: 2 files, 17/17 passed (VERDICT exit 0). None touches a view.",
"files_changed": 39,
"line_budget": "n/a: no skills/** lines; diff vs main +975 / -292 across 39 files (486 of the added lines are the new retirement test)",
"deviations": [
"The first dogfood run was killed before merging main so the battery would run on the final head; the battery reading above is the post-merge head.",
"Before the fix, the pre-merge closure build passed --concurrency=2 into the cli build script (sh syntax error); the post-merge build used --workspace-concurrency=2 and is green.",
"main moved 27 commits after my merge, so the PR does not re-merge; #29451 is among them."
],
"mcp_calls": "7 — reads only: mcp__github__issue_read x4, mcp__github__pull_request_read x3",
"api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41468778199, created #29447) + this comment via post-stamped.mjs",
"open_questions": [
{ "question": "sharing.type residue: should the old emitted default 'collaborative' still parse?", "options": ["A accept and strip it (shipped): views built by a released toolchain parse with no edit; 'personal' is refused", "B refuse 'collaborative' too: every stored list view with the default needs the D2 conversion or an edit"], "recommendation": "A, because #12840 rules a retired defaulted key's emitted default as residue, not an authoring choice, and the ruling's 'both values' names the two personal declarations" },
{ "question": "Conversion identifier: personalViewDeclarationsRemoved (id view-personal-declarations-removed) instead of viewPersonal..., because an identifier that sorts last trips scripts/conversions-major18-merge.test.ts", "options": ["A keep the identifier (shipped)", "B rename to viewPersonal... and fix the merge test's last-sort assumption in a separate card"], "recommendation": "A; the id string is unchanged, so nothing outside the registry reads the identifier" }
],
"security_findings": "withheld",
"out_of_scope_findings": [
"class: c · reach: scripts/conversions-major18-merge.test.ts synthetic definitions-end scenario assumes no rule-placed conversion sorts last; a new last-sorting identifier yields a 3rd finding · evidence: round-1 red on 5d20b513 · dedupe words: conversions-major18-merge last sort · noted, not filed",
"class: b · reach: view.manageShared gate has no enforcing reader (git grep) · dedupe words: manageShared inert · noted, not filed",
"class: b · reach: view.form.ts section describe still says 'who can see it' for a non-visibility key · dedupe words: form section who can see · noted, not filed",
"carrier: PR #29447"
]
}
Claim amendment on #29037 (amends claim 6578962562) · 2026-11-02T16:18Z
PR #29447 retires sharing.type whole, which the claim allowed. Its only reader was the ListView badge. That retirement forces edits outside the named surface, and the seat accepts all of them:
The ui emitter and its strings.ui/packages/core/src/utils/normalize-list-view.ts no longer writes type, and its test follows. The two badge i18n keys leave the 10 locale files under ui/packages/i18n/src/locales/.
ui pins of the spec surface. These follow the key's removal:
registry-inputs-spec-parity
ListView / ListViewBlock tests
ObjectView protocol-keys test
p1-spec-alignment
The retirement mechanics ADR-0087 requires:
one retired-keys entry
one semantic D3 entry
migrations/registry.ts.template
vitest.repo-tests.json
the retired-key removal-version pin
One metadata-protocol test fixture:protocol.graft-folded-form-sections.test.ts.
pm:dispatched is removed. The seat ACCEPTed, with a claim amendment for the ui/i18n/migration edits the retirement forces, and the CONTRACT_REVIEW_TIER review passed on 92ac574d.
What changed:
scope: 'personal' is refused, with a prescription, at every scope door.
Non-blocking residue, not filed: the useViewSharing.ts docblock in ui/packages/react still claims alignment with ViewSharingSchema. Next on view.zod.ts is #28522, which is free for another spec seat.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage:
domain:spec· p2 ·pm:queue· 2026-11-02T07:19Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Claim: PM loop round 1 · 2026-11-02T13:55Z
Session:
session_01ADHkmPi25XXKx92Xmg4viS(seat); dev = card threadsession_01ADHkmTHDhPHuivpyvN8gfFAccount:
os-steveBranch:
claude/project-thread-if04tiWorktree: the card thread's own checkout of
objectstack-ai/objectstackDomain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/ui/view.zod.ts(ViewScopeSchema,ViewSharingSchema, the "parked direction" docblocks) and its tests;packages/spec/src/conversions/registry.tsonly if the retirement route needs a D2 conversion; the generated baselines, forms, pins and references the retirement touches;content/docs/ui/audience-based-interfaces.mdx;ui/packages/plugin-list/src/ListView.tsxbadge only ifsharing.typeitself retires; root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (a narrowing on the published schema face) before landing,effort: highClause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility:
packages/spec view.zod.ts ViewScopeSchema / ViewSharingSchema | scope 'personal' and sharing.type 'personal' save but restrict nothing, while the badge says personal | authors and agents who believe a view is private to one userThread-read: 6571434336
Premise: holds (the card's readings are on main
ab085a0482; the dev re-reads them on the base)Serial constraints cleared: PR #29287 merged (
b5e7ba5552, same file); #28522 follows this card onview.zod.tsRuling-ref: #28971 letter C (record 6570568131, 「同意」) — named personal views are a user-owned D3 object and do not reuse these keys, so both inert
personalvalues retire with a prescription (ADR-0049, ADR-0087). Ifsharing.typeis left with one value, it retires too unless a reader other than the badge needs it; the PR states the reading. The ADR notes ride PR #29321.objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmTHDhPHuivpyvN8gfF
os-dev-report
{ "issue": 29037, "status": "done", "branch": "claude/project-thread-if04ti", "pr": "https://github.com/objectstack-ai/objectstack/pull/29447", "session": "session_01ADHkmTHDhPHuivpyvN8gfF", "premise_still_valid": true, "summary": "Both inert personal declarations retire per #28971 letter C. view.scope 'personal' is refused with a prescription by enumWithRetiredValues at every door that reads a view item's scope. view.list.sharing.type retires whole (retiredKey tombstone): once 'personal' goes it has one value left, and its only reader is the console badge (git grep over packages/ and ui/ finds no server or runtime reader). The emitted default 'collaborative' is accepted and stripped through acceptRetiredDefaultResidue (#12840 house rule). Route: D2 conversion view-personal-declarations-removed deletes both declarations on stored rows, assembled artifacts and os migrate meta. D3 entry ui-view-personal-declarations-retired, RETIRED_KEYS_BY_MAJOR ui/ViewSharing:type, one ADR-0087 marker. Console (in surface): the ListView badge becomes a lock-only 'Locked by' indicator, normalize-list-view drops the legacy visibility pair instead of writing type, and list.sharingPersonal/list.sharingCollaborative leave all 10 locales. The docs line changes to per-user list preferences.", "tests": "head 92ac574d (main merged at 96fb7b5d via os-regen-merge.sh, authorable-surface regenerated): spec test 791 files passed, 23468 passed | 1 todo (lock VERDICT exit 0); spec typecheck exit 0 (pre-merge 933882e0); spec check:generated exit 0 (14 artifacts current, dist built this round); metadata-protocol protocol.graft-folded-form-sections 47/47; ui consumers (plugin-list ListView + inertKeys-13074 + specInputs-13047, core normalize-list-view, plugin-view 8980, types p1-spec-alignment, console registry-inputs-spec-parity) 7 files, 642/642 (a796462b, ui sources unchanged since); new file view-personal-declarations-retirement.test.ts in the repo project.", "gates": "dispatch-gates --commands: 110 derived; each run with < /dev/null and its exit captured; --ran reconciliation: 110 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Two first-pass reds were environment: check-plugin-teardown-shape --self-test exit 3 (shallow clone, cleared after fetching its fixture commit) and pnpm --dir ui exec vitest run scripts/ exit 1 (4 tests timed out at 15s while dogfood shared the box); both exit 0 on re-run (ui scripts 169 files, 4921 passed). check:skill-examples and check:dual-build-cjs-loads ran after building their 7 extra packages. PR check-runs: not yet read (CI just started).", "dogfood_battery": "92ac574d · os-verify-lock: VERDICT command-exit 1 · held the lock 2866s · Test Files 4 failed | 389 passed | 3 skipped (396) · Tests 6 failed | 3237 passed | 28 skipped. Failures: external-import-code-datasource-namespace (control) and wall-package-refusal (single CONTROL) timed out at 5000ms and pass when re-run alone; flow-runas x3 (404 RECORD_NOT_FOUND) and declarative-endpoint-anonymous-guest x1 come from main: #29365 (dfbe34d8, in my merge) added the flow-start record-read refusal, and #29451 (6af3d295, landed on main after my merge) re-points those pins. With #29451's three test files checked out on my tree: 2 files, 17/17 passed (VERDICT exit 0). None touches a view.", "files_changed": 39, "line_budget": "n/a: no skills/** lines; diff vs main +975 / -292 across 39 files (486 of the added lines are the new retirement test)", "deviations": [ "The first dogfood run was killed before merging main so the battery would run on the final head; the battery reading above is the post-merge head.", "Before the fix, the pre-merge closure build passed --concurrency=2 into the cli build script (sh syntax error); the post-merge build used --workspace-concurrency=2 and is green.", "main moved 27 commits after my merge, so the PR does not re-merge; #29451 is among them." ], "mcp_calls": "7 — reads only: mcp__github__issue_read x4, mcp__github__pull_request_read x3", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41468778199, created #29447) + this comment via post-stamped.mjs", "open_questions": [ { "question": "sharing.type residue: should the old emitted default 'collaborative' still parse?", "options": ["A accept and strip it (shipped): views built by a released toolchain parse with no edit; 'personal' is refused", "B refuse 'collaborative' too: every stored list view with the default needs the D2 conversion or an edit"], "recommendation": "A, because #12840 rules a retired defaulted key's emitted default as residue, not an authoring choice, and the ruling's 'both values' names the two personal declarations" }, { "question": "Conversion identifier: personalViewDeclarationsRemoved (id view-personal-declarations-removed) instead of viewPersonal..., because an identifier that sorts last trips scripts/conversions-major18-merge.test.ts", "options": ["A keep the identifier (shipped)", "B rename to viewPersonal... and fix the merge test's last-sort assumption in a separate card"], "recommendation": "A; the id string is unchanged, so nothing outside the registry reads the identifier" } ], "security_findings": "withheld", "out_of_scope_findings": [ "class: c · reach: scripts/conversions-major18-merge.test.ts synthetic definitions-end scenario assumes no rule-placed conversion sorts last; a new last-sorting identifier yields a 3rd finding · evidence: round-1 red on 5d20b513 · dedupe words: conversions-major18-merge last sort · noted, not filed", "class: b · reach: view.manageShared gate has no enforcing reader (git grep) · dedupe words: manageShared inert · noted, not filed", "class: b · reach: view.form.ts section describe still says 'who can see it' for a non-visibility key · dedupe words: form section who can see · noted, not filed", "carrier: PR #29447" ] }Stamped 2026-11-02T16:16Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Claim amendment on #29037 (amends claim 6578962562) · 2026-11-02T16:18Z
PR #29447 retires
sharing.typewhole, which the claim allowed. Its only reader was the ListView badge. That retirement forces edits outside the named surface, and the seat accepts all of them:ui/packages/core/src/utils/normalize-list-view.tsno longer writestype, and its test follows. The two badge i18n keys leave the 10 locale files underui/packages/i18n/src/locales/.registry-inputs-spec-parityp1-spec-alignmentretired-keysentrysemanticD3 entrymigrations/registry.ts.templatevitest.repo-tests.jsonprotocol.graft-folded-form-sections.test.ts.The open questions are answered A and A:
collaborative. The old emitted defaultcollaborativeis accepted and stripped as residue, per 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. Onlypersonalis refused.personalViewDeclarationsRemoved. The id string is unchanged, and nothing outside the registry reads the identifier.The lines stay
Clause-②: no (narrowing)/Metadata-protocol: narrowed. The narrowing is ruled: #28971 letter C, record 6570568131, 「同意」.objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Landed · 2026-11-02T17:15Z
85ea12f475, through the merge queuecompletedbyFixes #29037pm:dispatchedis removed. The seat ACCEPTed, with a claim amendment for the ui/i18n/migration edits the retirement forces, and the CONTRACT_REVIEW_TIER review passed on92ac574d.What changed:
scope: 'personal'is refused, with a prescription, at every scope door.sharing.typeis retired whole. The emitted defaultcollaborativeis accepted and stripped as residue, per 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.view-personal-declarations-removedclears stored rows.Non-blocking residue, not filed: the
useViewSharing.tsdocblock inui/packages/reactstill claims alignment withViewSharingSchema. Next onview.zod.tsis #28522, which is free for another spec seat.