Repository navigation
Commit 1c563af
fix(platform-objects): Setup identity pages open on the tenant-wide list; a caller-scoped list view is never first (#21983)
Fixes #21972
Clause-②: no
## What changes
An administrator who opens Setup → API Keys, Sessions, OAuth
Applications, Identity Links, User Preferences or Record Shares now
lands on the tenant-wide "All" list. Before this, each of those entries
named no view. The console then opened the object's first declared list
view, and on every one of them that view was filtered to the caller
(`user_id = {current_user_id}`, or `recipient_id` for record shares).
The administrator saw only their own rows.
This applies triage's rule for the family (6012877503): a caller-scoped
list view is never an object's first, and every entry that wants one
names it.
- **Seven objects are reordered, and nothing else in them changes.** In
each, the unscoped "All" view moves to first place and the caller-scoped
view follows it. The other views keep their relative order. No view is
added, removed or edited.
- **Six Setup entries name their unscoped view with `viewName`,** as
`nav_users` already does. That is the key the spec already declares on
an object navigation item, so there is no new key.
- **The Account app's Linked Accounts entry (`nav_account_linked`) now
names `mine`.** It was the one Account object entry that relied on the
declared order. With `mine` no longer first, it would otherwise have
opened `all_links`.
- **Two comments are corrected.**
- `sys_user`'s `me` comment said RLS stops non-admins reading other
users' rows. `member_default` admits the caller's organization's users
through `sys_user_org_members`, and the comment now says so.
- `nav_users`' comment now says `me` *used to be* first.
- **The generated translation bundles follow the new order.** Eight
files changed (four in each package), regenerated with `node
scripts/check-i18n-bundles.mjs --write`. These are pure reorders of the
`_views` keys: no translated text changed (+84 / −84).
- **New pins:**
`packages/platform-objects/src/apps/caller-scoped-first-list-view.test.ts`,
with 29 cases (described below).
- **Two changesets, both `patch`:** `@objectstack/platform-objects` and
`@objectstack/plugin-sharing`. Each carries the `Clause-②: no` line.
⛔ No new key, no objectui change, no `packages/spec` edit, and no
governed surface. In `plugin-sharing`, only the two declared files
change (`sys-record-share.object.ts` and `sharing-plugin.ts`), plus
their four regenerated bundles.
Implemented by the os-dev run of session
`session_017ErfyP2Rx7XWHJA27QjyUi` on branch
`claude/issue-21972-caller-scoped-views-not-first`.
| object | listViews at | old first | new order | Setup entry ·
`viewName` | Account entry · `viewName` |
|:--|:--|:--|:--|:--|:--|
| `sys_user` | `sys-user.object.ts:603` | `me` | `all_users`, `me`,
`unverified`, `two_factor`, `banned` | `nav_users` · `all_users`
(already set by #21971) | none routes to `sys_user` |
| `sys_api_key` | `sys-api-key.object.ts:121` | `mine` | `all_keys`,
`mine`, `active`, `revoked` | `nav_api_keys` · `all_keys` |
`nav_account_api_keys` · `mine` (unchanged) |
| `sys_session` | `sys-session.object.ts:113` | `mine` | `all_sessions`,
`mine`, `revoked` | `nav_sessions` · `all_sessions` |
`nav_account_sessions` · `mine` (unchanged) |
| `sys_oauth_application` | `sys-oauth-application.object.ts:210` |
`mine` | `all_apps`, `mine`, `active`, `disabled_apps` |
`nav_oauth_apps` · `all_apps` | `nav_account_oauth_apps` · `mine`
(unchanged) |
| `sys_account` | `sys-account.object.ts:80` | `mine` | `all_links`,
`mine`, `by_provider` | `nav_accounts` · `all_links` |
`nav_account_linked` · `mine` (**new**) |
| `sys_user_preference` | `sys-user-preference.object.ts:36` | `mine` |
`all_preferences`, `mine`, `by_user` | `nav_user_preferences` ·
`all_preferences` | none |
| `sys_record_share` |
`plugin-sharing/src/objects/sys-record-share.object.ts:46` |
`granted_to_me` | `all_shares`, `granted_to_me`, `granted_by_me`,
`by_object`, `manual_grants`, `rule_grants` | `nav_record_shares`
(`sharing-plugin.ts:591`) · `all_shares` | none |
## The dispatch's hypotheses, measured
- **H1 holds: each of the seven objects declares an unscoped list
view.** Each one's "All" view carries no filter, except `all_sessions`,
whose only filter is `revoked_at is_null`. That view is the one now
first (table above). Stop condition 2 does not fire.
- **H2 holds: the order changes which view opens, not which rows a
caller may read.** This was read from `member_default`'s row-level
security in
`packages/plugins/plugin-security/src/objects/default-permission-sets.ts`.
No real-door read was run.
- **`sys_api_key`, `sys_session`, `sys_oauth_application` and
`sys_user_preference`** carry `_self` policies with `user_id ==
current_user.id`, operation `all` (`:921`, `:891`, `:955`, `:915`).
**`sys_account`** carries one for `select` (`:897`). Each is the same
predicate as `mine`, so a member's "All" view returns exactly the rows
`mine` returns.
- **`sys_user`** carries `sys_user_self` (`:871`) and
`sys_user_org_members`, `id in current_user.org_user_ids` (`:885`). So a
member's "All Users" view returns their organization's users, and `me`
did not. That is the declared staff-directory policy, and the header of
the same file names it as intended. It is not an access defect: the
member already had those rows through the existing "All Users" tab and
through `GET /data/sys_user`, and this diff changes neither. I read stop
condition 1 as not met. The reasoning is stated here so the seat can
disagree.
- **`sys_record_share`:** `member_default` has no wildcard object grant
and names no `sys_record_share` permission, so a member reads no rows
through either view. The Setup entry also requires
`manage_platform_settings`.
- **H3 holds: these are the readers of the declared order in this
repository.**
- **Account entries:** `nav_account_linked` was the only object entry
without a view. All six Account object entries now name `mine`.
- **Setup entries:** after this change, no Setup entry in
`platform-objects` names an object whose first view is caller-scoped.
The pin's (a) cases enumerate all of them.
- **Code:** `packages/cli/src/commands/lint.ts:168` (`firstListViewKey`)
reads the first key only to place a label diagnostic when no list view
has a label. Every view here has one.
- **Pages:** `sys-user.page.ts` related lists name these objects with
`showViewAll: true` and no view. How objectui's "View all" picks a view
was not read (NOT MEASURED).
- **Dashboards:** `system.datasets.ts` reads `sys_user` and
`sys_session` by object, not by view.
- **Tests:** none assert a view index. `setup-users-nav-view.test.ts`
and `i18n-resolver.object-list-views.test.ts` read views by name.
- **objectui (out of scope):** the `views[0]` fallback, the breadcrumb
and the object switcher are covered by the reorder itself.
- **H4 holds: the reorder moved eight generated files.** These are
`packages/platform-objects/src/apps/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts`
and
`packages/plugins/plugin-sharing/src/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts`.
`pnpm check:i18n` first read "platform-objects DRIFTED (4)" and
"plugins/plugin-sharing DRIFTED (4)". After `--write` it exits 0. The
other seven packages regenerated with no diff. No count or order pin
moved.
## Pins
`caller-scoped-first-list-view.test.ts`, 29 cases. The population is
derived from this package's navigation, not hand-listed. It is every
`type: 'object'` entry of `SETUP_NAV_CONTRIBUTIONS` and `ACCOUNT_APP`
(18 entries, 13 objects), looked up in this package's own exports.
- **(a), 11 cases: a named object's first declared list view carries no
`{current_user_id}`.** That is checked anywhere in the view, so the
`${current_user_id}` spelling is included.
- **(b), 12 cases:** every entry whose object declares a caller-scoped
view names a `viewName`. The object must declare that view under that
same name, and on a Setup entry the named view must not be
caller-scoped.
- **Population and non-vacuity, 4 cases:**
- both apps contribute object entries;
- the six objects this card reordered in this package are judged by both
(a) and (b);
- the named objects this package cannot read are exactly
`sys_inbox_message`, owned by `service-messaging`. (b) therefore
requires its entry to name a view, and it names `mine`;
- the named objects that declare only caller-scoped views are exactly
`sys_member`. Only the Account app names it, with `mine`.
- **Parse, 2 cases:** every Setup contribution parses through
`NavigationContributionSchema` and the Account app through `AppSchema`,
and each object entry keeps its `viewName`.
- **#21960's `setup-users-nav-view.test.ts`** stays green (7 of 7).
**Reach of the pins.**
- **Plugin-contributed entries are outside them.** Setup entries
contributed by plugins at runtime (`nav_record_shares`,
`nav_approval_requests`, …) are not visible from `platform-objects`,
which cannot import the plugins because they depend on it.
`plugin-sharing` gets no test file here: the dispatch declares only its
two files.
- **The sharing half was measured once instead.** Each built plugin was
booted with a fake manifest context, the way `check-app-nav-i18n` boots
them, at `a4d4688cfd`. That read gives `nav_record_shares →
sys_record_share | first=all_shares (unscoped) | viewName=all_shares
(unscoped)`.
## Reverse verification (on committed `f757947e6c`, through
`scripts/ablation-replace.mjs`)
The test imports its subjects by relative path from `src/`, so no
`dist/` sits between the mutation and the run. Directions were predicted
before each run: one red case each.
- **(a): `sys_session` restored to its old order** (`mine`,
`all_sessions`, `revoked`), by one literal swap of the two adjacent view
blocks.
- **Mutation landed:** anchor 1 → 0, replacement 0 → 1, blob
`4e46fda0773c` → `884ccaa2b537`.
- **Result: 1 failed | 28 passed (29).** The failing case is `(a) … ›
sys_session`: "sys_session declares the caller-scoped list view "mine"
first".
- **Restored:** blob `4e46fda0773c` equals HEAD, `git diff HEAD` is
empty, and `git status --porcelain` is empty.
- **(b): `viewName: 'all_sessions', ` deleted from `nav_sessions`.**
- **Mutation landed:** anchor 1 → 0, blob `17f1725c3cad` →
`2d7a16c6a894`.
- **Result: 1 failed | 28 passed (29).** The failing case is `(b) … ›
setup nav_sessions → sys_session`: "nav_sessions names no view".
- **Restored:** blob `17f1725c3cad` equals HEAD, `git diff HEAD` is
empty, and `git status --porcelain` is empty.
## Tests and gates, at `a4d4688cfd`
That commit is the merge of `origin/main` at `dcf3eb494a`, which brought
only `packages/spec` test files. The whole workspace was built before it
(`turbo run build --concurrency=2`, 72 tasks).
- **`pnpm --filter @objectstack/platform-objects exec vitest run
--maxWorkers=2`: Test Files 62 passed (62), Tests 996 passed (996).**
- **`pnpm --filter @objectstack/plugin-sharing exec vitest run
--maxWorkers=2`: Test Files 40 passed (40), Tests 980 passed (980).**
- **`pnpm --filter @objectstack/platform-objects typecheck` and `pnpm
--filter @objectstack/plugin-sharing typecheck`: both exit 0, with
`check:test-typecheck: OK`.** The new test file is in the
`tsconfig.test.json` program, counted with `--listFiles`.
- **`node scripts/pm/dispatch-gates.mjs --commands` derived 66 commands,
and all 66 exited 0.** `--ran` reads "66 derived, 66 run, 0
NOT-MEASURED, 0 UNRUN". That zero is derived: every line carried its
exit code.
- **14 are new against the dispatch-time list:**
`check-adr-0087-registration` (base and self-test),
`check-empty-changeset` (base and self-test), `release-rehearsal-clone
--self-test`, `release-pending-publish --self-test`,
`check:engine-double-contract`, `check:objectql-double-limit`,
`check:objectui-changeset`, `check:pm-changeset-deadline-census`,
`check:query-options-erasure`, `check:type-check-coverage`,
`check:type-check-debt` and `check:where-matcher`.
- **`check:type-check-debt` ran twice.** The first run was killed by my
own batch timeout. The rerun exited 0 in 221 s: "1 ledger entr(ies)
re-measured … none above its recorded number".
- **Artifact-roster block: 55 families, all run. 52 exited 0.**
- `check-closing-target-claim.mjs`, `check-partof-closing-keyword.mjs`
and `check-single-claim-paths.mjs` answer NOT WIRED without a PR
context. CI runs them with one.
- **Symbol-anchor sweeps:** `check:adr-symbol-anchors`,
`check:scripts-symbol-anchors`, `check:spec-docblock-symbol-anchors` and
`check:adr-anchors` all exited 0.
- **ESLint, narrowed to the 19 touched TypeScript files:** 19 files, 0
errors, 0 warnings, none reported as ignored.
- The population is read from `eslint.config.mjs`: its
`packages/**/*.{ts,tsx,mts,cts}` blocks match all 19.
- The count comes from `--format json`.
- The config enables no type-aware linting (no `parserOptions.project`),
so this diff cannot move a verdict on an untouched file. The repo-wide
`pnpm lint` is CI's.
## Acceptance notes
- **One more member of the family sits outside this card's lane.**
- **The defect:** Setup → Approvals → Requests (`nav_approval_requests`,
`packages/plugins/plugin-approvals/src/approvals-plugin.ts:147`) names
no view. `sys_approval_request` declares `my_pending` first, filtered on
`pending_approvers contains {current_user_id}`, so an administrator sees
only the requests pending on them.
- **How it was measured:** by the same built-plugin read as above.
- **Why it is not here:** it is in `domain:services`, and the dispatch
allows nothing in that lane beyond the two `plugin-sharing` files. It is
reported to the seat on the card.
- **Two named objects declare only caller-scoped list views:**
`sys_member` (`mine`) and `sys_inbox_message` (`mine`). Only Account
entries name them, and those entries name `mine`. No unscoped view is
added here; that is triage's decision. Both are pinned as exact sets, so
a third object cannot join unnoticed.
- **The bare-object doors now open "All" for members too.** RLS scopes
the rows (H2). On `sys_user`, a member's object breadcrumb now opens
their organization's user list rather than My Profile.
- **Sidebar highlight.** Each entry that now names a view lights up only
on that view, as `nav_users`, `nav_notifications` and the Account `mine`
entries already do. This was not measured in a browser.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a7df552 commit 1c563af
21 files changed
Lines changed: 433 additions & 166 deletions
File tree
- .changeset
- packages
- platform-objects/src
- apps
- translations
- identity
- plugins/plugin-sharing/src
- objects
- translations
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
157 | 160 | | |
158 | 161 | | |
159 | 162 | | |
160 | 163 | | |
| 164 | + | |
161 | 165 | | |
162 | 166 | | |
163 | 167 | | |
| |||
Lines changed: 209 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
Lines changed: 10 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
105 | 106 | | |
106 | 107 | | |
107 | 108 | | |
108 | | - | |
| 109 | + | |
109 | 110 | | |
110 | 111 | | |
111 | 112 | | |
| |||
142 | 143 | | |
143 | 144 | | |
144 | 145 | | |
145 | | - | |
| 146 | + | |
146 | 147 | | |
147 | 148 | | |
148 | 149 | | |
| |||
151 | 152 | | |
152 | 153 | | |
153 | 154 | | |
154 | | - | |
| 155 | + | |
155 | 156 | | |
156 | 157 | | |
157 | 158 | | |
| |||
182 | 183 | | |
183 | 184 | | |
184 | 185 | | |
185 | | - | |
186 | | - | |
| 186 | + | |
| 187 | + | |
187 | 188 | | |
188 | 189 | | |
189 | 190 | | |
0 commit comments