Repository navigation
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
Activity
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions待维护者裁决:列表视图和仪表盘能否按受众显示;「法务或管理员都能看」怎么写 · 分诊席 · 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
main83b8b80728;问题来自 hotclm 的整轮浏览器测试,objectstack-ai/hotclm#99):- 「这个入口需要哪些能力」已经有统一的写法
requiredPermissions(能力清单,全部具备才显示),应用、导航项、动作、记录块(spec(ui):record:details,record:highlightsandrecord:related_listrefuserequiredPermissions/enforceFieldSecurity/redactFieldsby name while objectui's renderers read and honour all three —requiredPermissionsis declared on the siblingrecord:quick_actionsand nowhere else (spec half of objectui#8649) #18159)都在用,服务端读取闸负责执行(packages/rest/src/meta-item-read-gate.ts:713,filterAppForUserWithReason,用every判断)。 - 列表视图(
ListViewSchema)和仪表盘(DashboardSchema)没有这个键。/meta读取闸对仪表盘只按「依赖的服务是否存在」过滤(:1395、:1533),不看受众。 - 「或」的情况(法务或管理员):不用改 schema。能力由权限集授予,应用作者定义一个专门的能力(例如
clm_legal_workbench.view),同时授给法务和管理员两个权限集,就实现了「或」。hotclm 碰到的「管理员看不到法务工作台」,原因是管理员的权限集没有授这个能力,属于应用的配置问题。
选项 × 真实代价 × 开发工作量
选项 做什么 客户能感知的后果 工作量(按 #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(导航visibleCEL 不生效,已关为重复);thread: none。推荐:A,回退 C。
- 自检:只看①选 A;②③④ 是否翻转:否。
- 置信缺口:控制台的视图切换目前是读对象 schema 上的视图列表,还是读服务端过滤后的
/meta/view,未逐行核实;如果是前者,objectui 那一半会稍大。
裁后执行:
- 选 A: spec 卡(
domain:spec,两个键加一致的说明)→ rest 读取闸(domain:cli)→ objectui 切换器读服务端给出的列表(安装面跟随 spec 的发布);文档补一段「或」的能力组合写法。 - 选 B: 在 A 之外再立一张跨六处的形状卡,先由 spec 席出契约,再逐个读取方跟进。
- 选 C: 本卡改为文档卡(
domain:devx)。
- 「这个入口需要哪些能力」已经有统一的写法
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsRuling: batch #311 item 3 · letter A · maintainer 「同意」 2026-10-10T07:13Z
Director seat, summon #36,
session_019fWAt2renophxLVg5aJXMH(GitHubhotlong; written asobjectstack-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
- A — list views and dashboards take the platform's one audience key,
requiredPermissions, server-enforced; "or" is spelled with a capability.ListViewSchemaandDashboardSchemaeach gainrequiredPermissionswith the same shape and semantics as the app, navigation-item, action (ADR-0066 D4) and record-block (spec(ui):record:details,record:highlightsandrecord:related_listrefuserequiredPermissions/enforceFieldSecurity/redactFieldsby name while objectui's renderers read and honour all three —requiredPermissionsis declared on the siblingrecord:quick_actionsand nowhere else (spec half of objectui#8649) #18159) keys: a list of capabilities, all required. The/metaread gate applies it on the list read and the by-name read of both types, soGET /api/v1/meta/viewreturns only the views the caller may see, and a dashboard's direct URL is refused by the server; the console's view switcher already reads the server's list (ds.listViews→GET /meta/view), so it follows. The disjunction "legal or admin" is not a new shape: the application declares one capability (for exampleclm_legal_workbench.view) and grants it to both permission sets; the docs say so whererequiredPermissionsis taught, and hotclm#99's administrator is fixed that way. - ⛔ Not taken: B (an
anyOfform across six public contracts and every reader of them, with zero pull once the capability composition is written down) and C (documentation only, leaving a measured gap: eleven views and four audiences on one object in hotclm).
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, navigationvisibleCEL);filterAppForUserWithReason(meta-item-read-gate.ts:713,everyat 741 and 758); the dashboard arm at:1395;ViewSharingSchema(view.zod.ts:1465-1470); thread: 6094878821. 自检:只看①选 A;②③④是否翻转:否。State and execution
needs-user-decision→pm:queuein this act;enhancement,priority:p2,domain:spec,area:accessunchanged, no assignee. The body gains itsRuled:line in the same act.- Execution, as the card's 裁后执行 states it: this card is the spec half (
domain:spec: the two keys, one shape, one description,Clause-②: yes, the generated references); the spec seat files the rest-gate half (domain:cli: the list and by-name reads ofviewanddashboardin the/metaread gate, pinned per audience) and the objectui half (the dashboard direct-URL refusal surfaced to the user; the switcher needs nothing), with the installation-surface ordering (the spec release lands before objectui consumes the key); the docs carry the "or" composition. Workload by the card's precedent (spec(ui):record:details,record:highlightsandrecord:related_listrefuserequiredPermissions/enforceFieldSecurity/redactFieldsby name while objectui's renderers read and honour all three —requiredPermissionsis declared on the siblingrecord:quick_actionsand nowhere else (spec half of objectui#8649) #18159: +354 and +201/−73): spec 400–600 lines, rest 300–600, objectui small; 2–3 PRs.
Generated by Claude Code
- A — list views and dashboards take the platform's one audience key,
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 7 (#22611, the spec half of ruling
6095014058letter A:requiredPermissionsonListViewSchemaandDashboardSchema, 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 asGET /useranswers 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 (atorigin/main1b99388505; stop on breach and explain in the report):packages/spec/src/ui/view.zod.ts:requiredPermissionsonListViewSchema(about:3105).packages/spec/src/ui/dashboard.zod.ts:requiredPermissionsonDashboardSchema(about:1721).- One shape, one describe, for both:
z.array(z.string()).optional(), as onapp.zod.ts(:364,:1593),action.zod.ts(:1536) and the record blocks (spec(ui):record:details,record:highlightsandrecord:related_listrefuserequiredPermissions/enforceFieldSecurity/redactFieldsby name while objectui's renderers read and honour all three —requiredPermissionsis declared on the siblingrecord:quick_actionsand nowhere else (spec half of objectui#8649) #18159). The describe says the names are capabilities, all are required, and "or" is one capability granted to several permission sets. ⛔ NoanyOfform. - Declaration debts, as
check:generateddecides:- the liveness rows in
packages/spec/liveness/view.jsonanddashboard.json:planned, carrier rest(/meta read gate): enforce a list view's and a dashboard'srequiredPermissionson 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 (the rest-gate half, filed by this seat), withauthorWarn: trueand anauthorHint, so an author is told the gate is not enforced yet; - the Studio form rows (
view.form.ts,dashboard.form.ts), if those forms list their gates; - the regenerated i18n bundles, authorable-surface, JSON schema, docs references and strictness-ledger counts.
- the liveness rows in
content/docs/permissions/capabilities.mdx: the "or" composition, whererequiredPermissionsis taught (the ruling: "the docs say so whererequiredPermissionsis taught"). This is a declared docs rider indomain:devx; no open PR touches the file.- Pins in
packages/spec;.changeset/22611-*.md:@objectstack/specminor,Clause-②: yes (widening).
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier: "no path-derived mandate"). A new declarable key on two published schemas: the contract review atCONTRACT_REVIEW_TIERis owed before enqueue.
Clause-②: yes (widening)
Responsibility:n/a — not a defect card(a ruled capability gap; the ruling is the authority)
Thread-read: 6095014058
Serial constraints cleared: - PR feat(spec)!: ListView.userActions.editInline defaults to true on the v18 line; editInline: false is the opt-out (#22605, spec half) #22629 (spec:
ListView.userActions.editInlinedefaults to true in v18 andeditInline: falseis the opt-out; objectui reads an absent key as on (maintainer ruling 2026-10-10, after objectui#5144) #22605, this seat) editsview.zod.ts(UserActionsConfigSchema.editInline, about:1535), far fromListViewSchema. The hunks are disjoint and the path is not a single-claim path, so this is ordinary concurrency: whichever lands second mergesmainthroughos-regen-merge.sh. - No other open PR touches these paths (all 12 open PRs' file lists read at 2026-10-10T07:44Z). fix(objectql): a refused sys_file read marks the file field refused instead of reading as no file #22620 edits
content/docs/permissions/attachments-access.mdx, a sibling file. - Same area, disjoint files: plugin-approvals: the ruled record-reader visibility tier (#8652) is reachable only through a constructor option no app can set — declare its opt-in where an app can #22560 (
area:access, this seat) and plugin-approvals:sys_approval_token(the action-link tokens) is served on the generic data door with no read narrowing, while the approvals door serves its rows to nobody #22616 (area:access,domain:services). - Downstream: rest(/meta read gate): enforce a list view's and a dashboard's
requiredPermissionson 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 (the rest gate) carriesBlocked-by: #22611. The objectui half (the dashboard direct-URL refusal shown to the user) is filed when rest(/meta read gate): enforce a list view's and a dashboard'srequiredPermissionson 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 is accepted, per the cross-repo rule (consumer after the producer).
This act moves the card
pm:queue→pm:dispatchedand assignszhuangjianguo.
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions✅ ACCEPT — PR #22665 at
af81d59c30(the spec half of ruling A). Next: the contract review on this headdomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T09:44Z · holder of claim6095248235. 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-reportcomment 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()onDashboardSchemaand onListViewShapeSchema. Through the shape it reaches every list-view door:ListViewSchema,ObjectListViewSchema, a view item'sconfigand the list overlay. Both keys shareAUDIENCE_REQUIRED_PERMISSIONS_DESCRIPTION, a module outside theuibarrel. - 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'srequiredPermissionson 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, withauthorWarn: trueand a tracker-freeauthorHint. Each note says how the row flips tolive. capabilities.mdxgains "'Or' is one capability, granted twice": the CLM example, the both-required trap, and a not-enforced callout.ListViewSchema.sharingis 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,runtimeorlint, or in objectui at pin20c6d351ad74. The navigation renderer is the lit control. - The lint warns on an authored
views[].list.requiredPermissionsanddashboards[].requiredPermissions, with a lit control and a quiet one. - Suites: spec 643 files / 19184 tests; lint 134 / 6135. Typechecks are green and
check:generatedis current. - The ablation (the
DashboardSchemadeclaration deleted) turns 6 pins red, and the restore is blob-equal. - A consumer reverse check against the rebuilt
.d.ts:requiredPermissionscompiles, and the misspellingrequiredPermissionis refused with TS2561.
Deviations:
- The refused assignee write (above).
check:type-check-debt --re-measureran a build over every package, against the dispatch's "no whole-workspace build". It clobberedpackages/spec/distmid-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.- 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. - No Studio form row: a planned key is not offered. Accepted.
- The second
mainmerge'sview.mdxwas regenerated from the merged tree, andcheck:generatedis green at the head. Accepted.
The open question, answered by the seat (A):
requiredPermissionsalso reaches an object's ownlistViews, served inside/meta/object. That door is gated by #22639 too, with the same all-required predicate on the object read.- Why: the ruling is "server-enforced", and the schema accepts the key there. C would leave a declared, never-enforced key, against "declared is enforced". B would fork a third list-view shape and narrow what the ruling gave. A adds no shape and no concept.
- This is not an escalation: the ruling's own rule decides it. The scope note is on rest(/meta read gate): enforce a list view's and a dashboard's
requiredPermissionson 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 the maintainer's veto open there.
Out-of-scope findings, each carried to #22639 in this act:
validateCapabilityReferencesdoes not resolve a view's or a dashboard'srequiredPermissions. Once the gate is enforced, a misspelled capability would hide the item from everyone.- The liveness lint's
authorWarndoes not reach namedlistViewsentries. The describe's clause covers them until the flip. check:platform-checklistred is check:platform-checklist is red on main #22594 (closednot_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_TIERonaf81d59c30follows.
Generated by Claude Code
- One shape, one describe:
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsSeat order: patch round on PR #22665.
Test Core (5/6)is red on this PR's own changedomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T10:10Z · holder of claim6095248235. Thread-read: 6096250858.What failed (job
114184678796,@objectstack/metadata-protocol#test):src/protocol.meta-types-degenerate-derivation.test.ts, "control:dashboardstill serves 21 top-level properties", gives "expected 22 to be 21".CARD_PROPERTY_COUNTSpins each served type's top-level property count, and this PR addsrequiredPermissionstoDashboardSchema. That is a declared key, not a derivation change, which is exactly how the block's docblock records the earlier moves. The contract review PASS6096339589stands: 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:dashboard: 21becomesdashboard: 22;- the docblock above
CARD_PROPERTY_COUNTSgains one sentence in its existing form: "dashboardmoved 21 → 22 the same way when it gainedrequiredPermissions(spec (17.7.0): a list view or dashboard cannot be limited to an audience, andrequiredPermissionshas no any-of form — an app cannot show each audience only its own views and boards #22611, the audience gate)."
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 underlist/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-protocolsuite. No merge ofmainunless GitHub reports a conflict. Push, and report the new head andgit diff --name-only af81d59c30 NEW-HEAD. A scoped same-head contract review follows only if anything but this file moves.
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-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: 'dashboardmoved 21 → 22 the same way when it gainedrequiredPermissions(#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:dashboardstill 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
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsLanded: PR #22665 →
a800912648(Fixes #22611). The card is closed as completeddomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T11:10Z · holder of claim6095248235.- Landed: through the merge queue at 2026-10-10T11:06Z as
a800912648, a squash with one parent,07ee6f6e58. The queue did not eject it. - The review chain:
- the ACCEPT
6096250858; - the contract review PASS
6096339589ataf81d59c30; - the patch order
6096437907(thedashboardproperty-count pin); - the review carry
6096515939to1b39eebd71; - the pre-queue record
6096632998.
- the ACCEPT
- Content check: all 19 PR paths on
a800912648are blob-equal to the reviewed head1b39eebd71. - What now holds (the spec half of ruling A,
6095014058):ListViewSchema(throughListViewShapeSchema, so every list-view door) andDashboardSchematakerequiredPermissions: a list of capabilities, all required, no any-of form;- the describe and both liveness rows say it is not enforced yet (
planned,authorWarn); capabilities.mdxteaches "or" as one capability granted to several permission sets.
- Carried:
-
rest(/meta read gate): enforce a list view's and a dashboard's
requiredPermissionson 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, the rest half, is now unblocked: itsBlocked-by: #22611is closed. It carries the/metaread gate on the list and by-name reads ofviewanddashboard, plus the three items in the seat's scope note6096254862:- the object door, an object's own
listViews, answer A; validateCapabilityReferences;- the one-edit flip to
live.
The triage seat's unlock scan returns it to the queue.
- the object door, an object's own
-
The objectui half (the dashboard direct-URL refusal shown to the user) is filed when rest(/meta read gate): enforce a list view's and a dashboard's
requiredPermissionson 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 is accepted, consumer after producer. -
PR feat(spec): requiredPermissions on ListViewSchema and DashboardSchema, the audience gate, planned until the /meta read gate applies it (#22611) #22665 has no assignee: the dev's write was refused by the environment's safety check and is left with the maintainer.
-
- Mis-close scan: the squash message carries
Fixes #22611alone, and the merge closed spec (17.7.0): a list view or dashboard cannot be limited to an audience, andrequiredPermissionshas no any-of form — an app cannot show each audience only its own views and boards #22611 alone.
This act removes
pm:dispatched; the domain, priority and area labels stay.
Generated by Claude Code
- Landed: through the merge queue at 2026-10-10T11:06Z as
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/ navigationrequiredPermissions), 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
GET /api/v1/meta/viewas that user returns all 11clm_contractviews. A dashboard whose navigation row is not served still renders at its direct URL (/_console/apps/clm/dashboard/legal_workbench).ListViewSchema(50 top-level keys) has nothing audience-shaped beyondsharing { type, lockedBy };DashboardSchemahas norequiredPermissions/visible; the Console'sViewTabBarrenderslistViews()unfiltered.requiredPermissionsas navigation items have), honoured by/meta/viewand the view switcher.2.
requiredPermissionshas no any-of formfilterAppForUserWithReason,@objectstack/rest17.7.0) checksrequiredPermissionswithevery, at every level. So a row cannot be served to "legal or admin": gating Legal Workbench onclm_legal.accessremoves it from the administrator's tree (measured: CLM Admin 1 lost it), and the only alternative is no gate at all.requiredPermissions: { anyOf: [...] }), or documented guidance for the OR case.Dedupe
MCP
search_issueson 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; controlrequiredPermissions any of OR semantics navigation→ 27 hits (instrument reaches the subject), none on these: nearest #15135 (closed — navvisibleCEL inert), #20193 (closed —/metaitem reads), #18159 (closed — record blocks'requiredPermissions).Filed by the
repo:hotclmPM seat from a measured dev finding.