Repository navigation
Commit 066dabc
fix(platform-objects): the sys_user page's Security tab shows only on the viewer's own record (#25100)
Fixes #24938
Clause-②: no (platform metadata only: the Security tab's self-service
sections are shown on the viewer's own record only; no action, param,
response, spec key or export moves)
## What changed
The `sys_user` record page's **Security** tab item
(`packages/platform-objects/src/pages/sys-user.page.ts`) now carries an
item-level gate: `visibleWhen: P` + "`has(record.id) && record.id ==
ctx.user.id`". This is the same `P` (CEL) envelope and `has()` guard
that the page's unverified-email `record:alert` uses. On the viewer's
own record, the tab is unchanged. On any other user's record, both the
tab header and its panel are omitted. Nothing else moves: the actions
and their own gates in `sys-user.object.ts`, the other tabs and the
alert are all unchanged.
## Measured before the edit
- **Tab item `visibleWhen` is declared and honoured.**
- Spec: `PageTabsProps.items[].visibleWhen`
(`packages/spec/src/ui/component.zod.ts`, an
`EvaluatedExpressionInputSchema`). The docs say a FALSE gate omits the
whole tab (header and panel), and that a child component's own
`visibleWhen` "hides only the panel content and would leave an empty tab
header behind".
- Renderer, objectui at the pinned `.objectui-sha` `cf153eb2e535`:
`packages/components/src/renderers/layout/containers.tsx`,
`isItemVisible` in `PageTabsRenderer`. It calls
`ExpressionEvaluator.evaluateCondition`
(`packages/core/src/evaluator/ExpressionEvaluator.ts`), which sends an
explicit `{ dialect: 'cel' }` envelope to the CEL engine. Bare strings
stay on the legacy evaluator. The scope is `buildNodeEvaluationScope`:
the ambient scope binds `ctx: { user }`
(`packages/app-shell/src/providers/ExpressionProvider.tsx`), with
`record` set to the row.
- **Component-node `visibleWhen` is honoured too.**
`PageComponentSchema.visibleWhen` (`packages/spec/src/ui/page.zod.ts`)
is evaluated by objectui's `SchemaRenderer`. The alert above already
relies on it.
- **The whole Security tab is self-service.** It holds four headed
sections whose `record:quick_actions` name `change_my_password`,
`change_my_email`, `enable_two_factor`, `disable_two_factor`,
`generate_backup_codes`, `resend_verification_email` and
`delete_my_account`. Each of those actions has a `visible` led by
`has(record.id) && record.id == ctx.user.id` in `sys-user.object.ts`.
Nothing in the tab serves an administrator. On another user's record, an
administrator sees only the four headings and captions.
- **Choice: gate the tab, not the sections.** A per-node gate would
leave an empty "Security" tab header on every other user's record. The
item-level gate is the shape the spec documents for this case.
- **Pin census.** Before this change, no test asserted the Security
tab's structure. `sys-user-details-tab.test.ts` pins only the first
(Details) tab. `canonical-expression-envelopes.test.ts` covers every
expression position on the page, and stays green with the new envelope.
## Pin
`packages/platform-objects/src/pages/sys-user-security-tab-own-record.test.ts`
asserts:
- the page parses through `PageSchema`;
- the Security item's `visibleWhen` equals `{ dialect: 'cel', source:
'has(record.id) && record.id == ctx.user.id' }`;
- Security is the only tab item that carries an item-level gate;
- every action the tab names exists on `SysUser` and is a pure
conjunction (no `||`) led by the own-record guard. If someone later adds
an administrator action to this tab, the test goes red, because gating
the whole tab would hide that action.
**Ablation.** I deleted the `visibleWhen` line with
`scripts/ablation-replace.mjs` (anchor 1 → 0, blob `c4dd9db6ae30` →
`200f0d8d63f6`). The pin went red: 2 failed, 2 passed ("carries the
own-record gate…" and "gates only the Security tab…"). After restore:
blob == HEAD (`c4dd9db6ae30`) and `git diff HEAD` is empty. The test
imports the page from `src/`, so no build or `dist/` leg is involved.
## Verification (head `0c2c418b8`, after merging `origin/main`
`28c9a2cb6`)
- `pnpm exec turbo run build --concurrency=2`: 77/77 tasks, lock VERDICT
command-exit 0.
- `pnpm --filter @objectstack/platform-objects typecheck`: exit 0
(`check:test-typecheck: OK`).
- `pnpm --filter @objectstack/platform-objects test`: exit 0, Test Files
85 passed (85), Tests 1247 passed (1247).
- `pnpm --filter @objectstack/core test:repo`: exit 0, Test Files 6
passed (6), Tests 82 passed (82).
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 66 lines, all run on this head, every one
exit 0. Before the full build, three families exited 3
(`check:dual-build-cjs-loads`, `check:i18n`,
`check:lean-entry-closure`); after it, all three exit 0. `--ran`: "66
derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED
zero — all 66 recorded an exit code and none of them is 3)".
- Browser reading: not taken.
## Acceptance notes
- **Tab URL tokens shift on another user's record.** No item on this
page sets `value`, so the renderer derives the `?tab=` token from the
index in the FILTERED item list (`tab-INDEX`). On another user's record,
hiding Security moves API Keys from `tab-9` to `tab-8` and Preferences
from `tab-10` to `tab-9`. On the viewer's own record the tokens are
unchanged. A `?tab=` link copied from your own record to someone else's
can therefore open the tab after the intended one. The spec already
recommends a semantic `value` for durable links. Giving the items one
touches the other tabs, which this card leaves out of scope. Carrier:
none.
- `#24277` is not addressed here. It cites this tab as precedent for a
platform-default Settings tab.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011VbHLLCsMcBUhfcGuZikJp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5050442 commit 066dabc
3 files changed
Lines changed: 92 additions & 0 deletions
File tree
- .changeset
- packages/platform-objects/src/pages
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
364 | 364 | | |
365 | 365 | | |
366 | 366 | | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
367 | 380 | | |
368 | 381 | | |
369 | 382 | | |
| 383 | + | |
370 | 384 | | |
371 | 385 | | |
372 | 386 | | |
| |||
0 commit comments