Repository navigation
Commit da94c11
fix(app-crm): hide the Pipeline nav entry from a member who cannot read opportunities (#27805)
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgC1o88pHYeo2G8hvcGVV1s1)_
Fixes #27735
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6512784198
## 维护者速读
- 改了什么:CRM 侧栏的 Pipeline 入口加了一个显示条件 `current_user.can('crm_opportunity',
'read')`,读不到商机的成员看不到它;仪表盘本身、权限集和能力都不动。
- 为什么改:没有销售权限集的成员原来侧栏有 Pipeline,点进去六个组件全被拒。按裁决 G 用「藏」而不是「拒」:首版声明能力的做法会让
CRM 不能非托管安装,种子管理员也会被拒。
- 风险与回滚:只改示例的一个导航项和一个测试文件。拒绝仍由数据集查询的 403 负责,未变;种子管理员和销售用户照旧能看到。回滚就是
revert。
-
先看:`examples/app-crm/src/apps/crm.app.ts`(一行)、`examples/app-crm/test/pipeline-audience.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/example-crm exec vitest run
test/pipeline-audience.test.ts`,预期 2 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
Per ruling 6512784198 (letter G, which amends the mechanism of #27491
ruling 6508997538 item 5):
- `examples/app-crm/src/apps/crm.app.ts`: `nav_pipeline_dashboard` gains
`visible: "current_user.can('crm_opportunity', 'read')"`. Nothing else
on the entry changes. `opportunity_metrics`, the dataset every Pipeline
widget reads, is bound to `crm_opportunity`.
- The first revision's capability approach is fully reverted: no
`crm.view_pipeline` declaration, no stack `capabilities`, no
`systemPermissions` on `crm_sales_user`, and no `requiredPermissions` on
`pipeline_dashboard` or its nav entry. The branch diff against `main` is
the two files listed here.
`visible` hides and refuses nothing (ADR-0066 D4). The refusal stays the
dataset query's 403 for a caller who cannot read `crm_opportunity`,
unchanged. The server's meta read gate does not judge `visible`, so the
seeded administrator's reads of the dashboard (the dogfood's
managed-install lock control) are untouched, and the package stays
installable unmanaged.
Acceptance (as amended by the ruling): a member who cannot read
`crm_opportunity` sees no Pipeline entry in the CRM sidebar; a sales
user and the seeded administrator see it as today; a direct link for the
refused member shows the #27491 no-access panel (PR #27777), not a
blank.
## Tests
`examples/app-crm/test/pipeline-audience.test.ts` (new, 2 cases), read
off the parsed stack:
- the Pipeline nav entry carries a CEL `visible` of the form
`current_user.can(OBJECT, 'read')` (the stack parse normalises the
authored string to `{ dialect: 'cel', source }`);
- OBJECT equals the `object` of the dataset that every
`pipeline_dashboard` widget reads, derived from the definitions, not
hard-coded.
Ablation: `scripts/ablation-replace.mjs` replacing `crm_opportunity`
with `crm_account` in the predicate (anchor hit 1) turns the second case
red (`expected [ 'crm_opportunity' ] to deeply equal [ 'crm_account'
]`), restored to the HEAD blob with `git diff HEAD` empty.
## Acceptance notes
- What the tests do not run: the console's nav guard that evaluates
`current_user.can` (`ui/packages/app-shell`, objectui#4421). The example
has no renderer harness; the pins cover the predicate's shape and its
object.
- No changeset: `@objectstack/example-crm` is `private: true`; labelled
`skip-changeset`.
- The first revision (head `87e10674`) measured why a declared
capability does not fit here:
`install-local-unmanaged-mode.dogfood.test.ts` went 11 failed / 9 passed
(422 `UNMANAGED_INSTALL_UNCOPYABLE` on the stack's `capabilities`, and
403 `audience-permission` for the seeded administrator), against 20/20
at the merge-base. Recorded in the ruling.
## Verification
Local, at `a1cd6d8d` (after merging `origin/main` once more):
- `pnpm --filter @objectstack/example-crm test`: exit 0, Test Files 7
passed (7), Tests 48 passed (48); `typecheck` exit 0 (lock VERDICT
command-exit 0).
- `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2
test/install-local-unmanaged-mode.dogfood.test.ts` (closure built
first): exit 0, Test Files 1 passed (1), Tests 20 passed (20). At the
first revision `87e10674` the same file was 11 failed / 9 passed.
- `node scripts/pm/dispatch-gates.mjs --ran`: 39 derived, 38 run, 1
NOT-MEASURED (`check:dual-build-cjs-loads`, exit 3 PREREQUISITE NOT MET:
needs every package's dist; CI's). Three families first answered from a
mid-build tree and were re-run after the build settled:
`check:dts-closure` (exit 1 naming `@objectstack/runtime` /
`service-automation` while the dogfood closure was rebuilding, then
`driver-odata`, new on `main`, unlinked until `pnpm install`) exit 0;
`check:i18n-coverage` exit 0 ("OK (14 config(s), 527 baselined
untranslated string(s), none new)"); `check:type-check-debt` exit 0 ("0
raw tsc error(s) total, none above its recorded number"). The diff
touches no package, so none of these read it beyond the example.
- `node scripts/check-sdui-manifest.mjs`: exit 0.
<details><summary>Derived gate families, exit codes (final
readings)</summary>
```text
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-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:engine-double-contract :: exit 0
pnpm check:examples-live-imports :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n-coverage :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CXo8iLQ8vn2fXj6wxp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c91a004 commit da94c11
2 files changed
Lines changed: 68 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
34 | 39 | | |
35 | 40 | | |
36 | 41 | | |
| |||
| 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 | + | |
0 commit comments