Skip to content

spec (17.7.0): a list view or dashboard cannot be limited to an audience, and requiredPermissions has no any-of form — an app cannot show each audience only its own views and boards #22611

Description

@objectstack-fleet

Ruled: 6095014058 · letter A · 2026-10-10T07:14Z

Filing class: ① product defect (capability gap with a user-visible cost) — reach: public door, measured on @objectstack/* 17.7.0 (spec, console, rest). Maintainer instruction for platform problems found in testing, verbatim, 2026-10-10: 「你遇到的平台问题应该提交issue」.

Reader: objectstack triage → the spec lane (ListViewSchema / DashboardSchema / navigation requiredPermissions), with an objectui half for the view switcher.

Source: objectstack-ai/hotclm#99 (draft PR objectstack-ai/hotclm#104; report on #99), after the full browser pass objectstack-ai/hotclm#87 (finding 19).

1. No audience gate on a list view or a dashboard

  • As a business requester, the Console view switcher on the contract list shows every list view of the object — "Awaiting Intake · My Reviews · 7 more" (legal, finance and records queues). GET /api/v1/meta/view as that user returns all 11 clm_contract views. A dashboard whose navigation row is not served still renders at its direct URL (/_console/apps/clm/dashboard/legal_workbench).
  • No data leaks — every list and widget is row-scoped — but each audience is shown the other audiences' queues and boards, which are empty or irrelevant to it.
  • ListViewSchema (50 top-level keys) has nothing audience-shaped beyond sharing { type, lockedBy }; DashboardSchema has no requiredPermissions / visible; the Console's ViewTabBar renders listViews() unfiltered.
  • Expected: a server-enforced audience gate on a list view and a dashboard (e.g. requiredPermissions as navigation items have), honoured by /meta/view and the view switcher.

2. requiredPermissions has no any-of form

  • Navigation pruning (filterAppForUserWithReason, @objectstack/rest 17.7.0) checks requiredPermissions with every, at every level. So a row cannot be served to "legal or admin": gating Legal Workbench on clm_legal.access removes it from the administrator's tree (measured: CLM Admin 1 lost it), and the only alternative is no gate at all.
  • Expected: an any-of spelling (e.g. requiredPermissions: { anyOf: [...] }), or documented guidance for the OR case.

Dedupe

MCP search_issues on this repo: list view audience gate requiredPermissions view switcher dashboard requiredPermissions any-of OR navigation → 0 hits; list view visible only to some users permission gate view tabs → 0 hits; control requiredPermissions any of OR semantics navigation → 27 hits (instrument reaches the subject), none on these: nearest #15135 (closed — nav visible CEL inert), #20193 (closed — /meta item reads), #18159 (closed — record blocks' requiredPermissions).


Filed by the repo:hotclm PM seat from a measured dev finding.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    待维护者裁决:列表视图和仪表盘能否按受众显示;「法务或管理员都能看」怎么写 · 分诊席 · 2026-10-10T06:56Z

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim, ⛔ not a dispatch. Filing gate ②:新增公开的 spec 键,属于功能和契约形状的提案,按规矩进决策箱。本卡升为决策卡(needs-user-decision)。

    一句话问题: 一个应用里各种角色看到的列表视图和仪表盘是同一套:业务申请人打开合同列表,会看到法务、财务、档案管理员各自的工作队列(数据本身按行权限过滤,不泄露,但界面又乱又吵)。平台今天没有办法说「这个视图只给法务看」。

    背景(实测,objectstack main 83b8b80728;问题来自 hotclm 的整轮浏览器测试,objectstack-ai/hotclm#99):

    选项 × 真实代价 × 开发工作量

    选项 做什么 客户能感知的后果 工作量(按 #18159 等同类 PR 估)
    A 给视图和仪表盘加受众门;「或」靠能力组合 ListView 和 Dashboard 各加 requiredPermissions(同一写法、同一语义),/meta 读取闸在列表读和按名读里过滤,控制台的视图切换只显示服务端给出的视图;文档写清「或」怎么用能力组合实现 每个角色只看到自己的视图和仪表盘;直接打开不该看的仪表盘地址时由服务端拒绝 spec 约 400–600 行(参照 #18159 的两个 PR:+354、+201/−73);rest 闸约 300–600 行;objectui 切换器约 100–300 行;2–3 个 PR,spec、cli、ui 三个车道
    B 在 A 之上,requiredPermissions 新增 { anyOf: [...] } 写法 应用、导航、动作、记录块、视图、仪表盘六处的形状都放宽,所有读取方(rest 闸、动作路由的 403、objectui 的隐藏逻辑)都要认这个新形状 「或」可以直接写在入口上 A 的工作量再加约 1–2k 行,跨 3 个车道;改动 6 处公开契约(Clause-②: yes)
    C 不加新键,只写指南 文档建议按角色拆应用、按角色配导航 同一个对象的不同角色视图仍然全部可见 约 100 行文档

    业务含义: A 是给每个视图和仪表盘挂上「谁能看」的牌子,用的是平台已经在用的那块牌子;B 是让牌子支持「甲或乙」的写法,但每个认这块牌子的地方都要改;C 是不挂牌子,靠分开建应用绕过去。

    四轴(从业务看):

    • 项目长远合理性: A 把已有的一个写法推广到两个缺席的入口,没有新概念;B 让同一个键有两种形状,六处契约一起放宽;C 留着缺口。
    • 实际业务拉动: hotclm 实测:11 个视图、多个仪表盘、4 类角色,问题真实存在。「或」的需求用能力组合就能满足,B 目前没有拉动。
    • 防 AI 犯错: A 由服务端执行,AI 写了就生效,不会出现「只在界面上隐藏」的假门禁。B 让 AI 面对两种形状,更容易写错。C 让 AI 无处可写。
    • 创业阶段不扩散: A 只在两处补上已有的键;B 扩张六处契约;C 零扩张但需求还在。

    维护者速读: 现在每个角色都会看到别人的视图和仪表盘。A 给它们加上平台已有的「需要哪些能力才显示」门禁,由服务端执行,工作量约 1–1.5k 行、2–3 个 PR;「法务或管理员都能看」不改写法,而是定义一个能力同时授给两个角色。B 让这个门禁本身支持「或」,要多改六处契约;C 只写指南。推荐 A。选 A、B 还是 C?

    os-decision-facets

    • ① 项目长远合理性:A 推广已有写法,不增生;B 一个键两种形状;C 留缺口。
    • ② 实际业务拉动:hotclm 实测 11 个视图、4 类角色;「或」靠能力组合即可,B 零额外拉动。
    • ③ 防 AI 犯错:A 服务端执行,写了就生效;B 两种形状易错;C 无处可写。
    • ④ 创业阶段不扩散:A 两处补齐;B 扩张六处;C 不扩张。

    Prior rulings read: ADR-0066 D4(动作的 requiredPermissions,服务端 403);#18159(记录块的 requiredPermissions);#15135(导航 visible CEL 不生效,已关为重复);thread: none。

    推荐:A,回退 C。

    • 自检:只看①选 A;②③④ 是否翻转:否。
    • 置信缺口:控制台的视图切换目前是读对象 schema 上的视图列表,还是读服务端过滤后的 /meta/view,未逐行核实;如果是前者,objectui 那一半会稍大。

    裁后执行:

    • 选 A: spec 卡(domain:spec,两个键加一致的说明)→ rest 读取闸(domain:cli)→ objectui 切换器读服务端给出的列表(安装面跟随 spec 的发布);文档补一段「或」的能力组合写法。
    • 选 B: 在 A 之外再立一张跨六处的形状卡,先由 spec 席出契约,再逐个读取方跟进。
    • 选 C: 本卡改为文档卡(domain:devx)。
  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #311 item 3 · letter A · maintainer 「同意」 2026-10-10T07:13Z

    Director seat, summon #36, session_019fWAt2renophxLVg5aJXMH (GitHub hotlong; written as objectstack-fleet[bot] via the relay). Batch #311 (three cards, this one third) was presented in chat with this seat's own readings and the recommendation A, fallback C; the maintainer answered 「同意」, which takes the recommendation. Freshness gate: the body (2,752 bytes) and the one comment (the triage presentation 6094878821) were re-read before this record. Thread-read: 6094878821.

    The ruling

    Why A on the first axis

    It extends the one existing spelling to the two entry types that lack it and adds no concept. ListViewSchema.sharing (type: personal | collaborative, lockedBy) is ownership and locking, not audience, so A creates no second audience mechanism. Declared is enforced: the gate is the server's, as it is for the four surfaces that already carry the key. Salesforce spells "or" the same way, a custom permission assigned to several permission sets, never an OR on the entry.

    Prior rulings read: ADR-0066 D4; #18159 (record blocks' requiredPermissions); #15135 (closed, navigation visible CEL); filterAppForUserWithReason (meta-item-read-gate.ts:713, every at 741 and 758); the dashboard arm at :1395; ViewSharingSchema (view.zod.ts:1465-1470); thread: 6094878821. 自检:只看①选 A;②③④是否翻转:否。

    State and execution


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 (#22611, the spec half of ruling 6095014058 letter A: requiredPermissions on ListViewSchema and DashboardSchema, the same shape and semantics as the app, navigation-item, action and record-block keys) · 2026-10-10T07:44Z
    Session: session_01KNKBCRDJCu5tGy3TEbvtrF
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22611-view-dashboard-required-permissions
    Worktree: objectstack-issue-22611
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main 1b99388505; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR #22665 at af81d59c30 (the spec half of ruling A). Next: the contract review on this head

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T09:44Z · holder of claim 6095248235. Thread-read: 6095248235.

    The dev's report reached the seat in this session, but it is not on this card. The dev's PR-assignee write was refused by the environment's safety check ("[External System Writes]"). Under the dispatch's stop rule it made no further write, so no os-dev-report comment exists. This ACCEPT records the report's substance. ⛔ The seat does not redo the refused assignee write; the PR's assignee is left for the maintainer.

    Checked in the diff, not from the report (net diff against the merge base 3d0eeefa4a: 18 files, +346 / −4):

    • One shape, one describe: requiredPermissions: z.array(z.string()).optional() on DashboardSchema and on ListViewShapeSchema. Through the shape it reaches every list-view door: ListViewSchema, ObjectListViewSchema, a view item's config and the list overlay. Both keys share AUDIENCE_REQUIRED_PERMISSIONS_DESCRIPTION, a module outside the ui barrel.
    • Its describe says three things:
      • all of the capabilities are required, and absent or empty means no gate;
      • there is no any-of form, and "or" is one capability granted to each permission set;
      • [PLANNED — not enforced yet]: no server reads the key today. ⛔ No sentence is false at this head.
    • Both liveness rows are planned, carrier rest(/meta read gate): enforce a list view's and a dashboard's requiredPermissions on the list read and the by-name read, so each audience is served only its own views and boards (the rest half of #22611) #22639, with authorWarn: true and a tracker-free authorHint. Each note says how the row flips to live.
    • capabilities.mdx gains "'Or' is one capability, granted twice": the CLM example, the both-required trap, and a not-enforced callout.
    • ListViewSchema.sharing is untouched, and no any-of shape or alias is added.

    Measurements accepted:

    • Before this PR, both schemas refused the key (unrecognized_keys), and no alias maps it.
    • No reader exists in rest, runtime or lint, or in objectui at pin 20c6d351ad74. The navigation renderer is the lit control.
    • The lint warns on an authored views[].list.requiredPermissions and dashboards[].requiredPermissions, with a lit control and a quiet one.
    • Suites: spec 643 files / 19184 tests; lint 134 / 6135. Typechecks are green and check:generated is current.
    • The ablation (the DashboardSchema declaration deleted) turns 6 pins red, and the restore is blob-equal.
    • A consumer reverse check against the rebuilt .d.ts: requiredPermissions compiles, and the misspelling requiredPermission is refused with TS2561.

    Deviations:

    1. The refused assignee write (above).
    2. check:type-check-debt --re-measure ran a build over every package, against the dispatch's "no whole-workspace build". It clobbered packages/spec/dist mid-run. The dev killed only its own processes, rebuilt and re-ran the overlapping gates, and records that gate NOT MEASURED in the final pass. Accepted. The seat carries the hazard: a derived gate that launches a whole-workspace build under concurrent devs.
    3. File surface beyond the claim: the reconciliation omit rows (the repo pattern for a planned key, and required for dashboard), the lint pin, the liveness README notes and the generated references. All are owed. Accepted.
    4. No Studio form row: a planned key is not offered. Accepted.
    5. The second main merge's view.mdx was regenerated from the merged tree, and check:generated is green at the head. Accepted.

    The open question, answered by the seat (A): requiredPermissions also reaches an object's own listViews, served inside /meta/object. That door is gated by #22639 too, with the same all-required predicate on the object read.

    Out-of-scope findings, each carried to #22639 in this act:

    • validateCapabilityReferences does not resolve a view's or a dashboard's requiredPermissions. Once the gate is enforced, a misspelled capability would hide the item from everyone.
    • The liveness lint's authorWarn does not reach named listViews entries. The describe's clause covers them until the flip.
    • check:platform-checklist red is check:platform-checklist is red on main #22594 (closed not_planned). By the maintainer's decision it is not wired into CI (.github/workflows/lint.yml), so it does not reach this PR's checks.

    A contract review at CONTRACT_REVIEW_TIER on af81d59c30 follows.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat order: patch round on PR #22665. Test Core (5/6) is red on this PR's own change

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T10:10Z · holder of claim 6095248235. Thread-read: 6096250858.

    What failed (job 114184678796, @objectstack/metadata-protocol#test): src/protocol.meta-types-degenerate-derivation.test.ts, "control: dashboard still serves 21 top-level properties", gives "expected 22 to be 21". CARD_PROPERTY_COUNTS pins each served type's top-level property count, and this PR adds requiredPermissions to DashboardSchema. That is a declared key, not a derivation change, which is exactly how the block's docblock records the earlier moves. The contract review PASS 6096339589 stands: the head's contract is unchanged, and only this pin is owed.

    The one change, and nothing else: in packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts:

    It is a declared test-only rider in packages/metadata-protocol. No open PR touches that file (all 14 open PRs' file lists, read at 2026-10-10T10:10Z). view's key sits under list / listViews, not at the top level, so no other count moves. Confirm that by the suite.

    Verify: the closure build for @objectstack/metadata-protocol, then the failing file red at the head (reproduce first) and green after, then the whole @objectstack/metadata-protocol suite. No merge of main unless GitHub reports a conflict. Push, and report the new head and git diff --name-only af81d59c30 NEW-HEAD. A scoped same-head contract review follows only if anything but this file moves.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22611,
    "status": "done",
    "branch": "claude/issue-22611-view-dashboard-required-permissions",
    "pr": "#22665",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF",
    "premise_still_valid": true,
    "round": "patch round per seat order 6096437907 (Test Core 5/6, job 114184678796)",
    "head": "1b39eebd716d95164adb38b5f508b8390edbc5f5",
    "previous_head": "af81d59c30a3c4100d7a595bf213d0a5d7aa50a1",
    "diff_name_only": "git diff --name-only af81d59 1b39eeb => packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts (one file, nothing else moved)",
    "summary": "This round makes the one change the seat ordered. In packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts, CARD_PROPERTY_COUNTS moves from dashboard: 21 to dashboard: 22. The docblock above it gains one sentence in its existing form: 'dashboard moved 21 → 22 the same way when it gained requiredPermissions (#22611, the audience gate).' I recreated the worktree from the remote branch head af81d59 and did not merge main: GitHub reported no conflict, and the seat ordered none. No other count moved, as the whole suite confirms; view's key sits under list and listViews, not at the top level.",
    "tests": "All runs went through os-verify-lock with slot issue-22611.\n- Closure build: pnpm --workspace-concurrency=2 --filter '@objectstack/metadata-protocol^...' build, VERDICT command-exit 0. It built spec, core, types, formula, lint, metadata, metadata-core, metadata-fs, observability, sdui-parser, driver-sql and driver-sqlite-wasm. No build ran over every package.\n- Red reproduced at af81d59: vitest run src/protocol.meta-types-degenerate-derivation.test.ts gave 'control: dashboard still serves 21 top-level properties' with 'AssertionError: expected 22 to be 21', 'Tests 1 failed | 17 passed (18)', exit 1.\n- Green at 1b39eeb: the same file gave 'Tests 18 passed (18)', exit 0.\n- Whole @objectstack/metadata-protocol suite at 1b39eeb: 'Test Files 224 passed | 3 skipped (227)', 'Tests 28314 passed | 19 skipped (28333)', VERDICT command-exit 0.\n- pnpm check:nul-bytes: OK, exit 0.\n- Not run: check:type-check-debt and anything that builds every package, per the order. metadata-protocol's typecheck (tsc --noEmit) was not run locally; the change is one number literal and comment text, so I declare it to CI.",
    "mcp_calls": "0",
    "api_writes": "1: this os-dev-report comment, posted through scripts/pm/post-stamped.mjs and the fleet-write relay as POST /repos//issues/22611/comments. Not REST: 1 git push (af81d59 to 1b39eeb). No assignee or label write.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "gates": "Ran the whole metadata-protocol suite and check:nul-bytes on the new head. No other gate family moved: this round touches one test file in packages/metadata-protocol.",
    "line_budget": "n/a: no skills/** and no governed surface touched.",
    "deviations": [
    "The PR assignee stays unset, as the seat ordered: the earlier refused write is the maintainer's, and this round makes no assignee write."
    ],
    "files_changed": [
    "packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts"
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22665 → a800912648 (Fixes #22611). The card is closed as completed

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T11:10Z · holder of claim 6095248235.

    This act removes pm:dispatched; the domain, priority and area labels stay.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions