feat(apps,skills): show what you may actually do, and name the creator - #8
Merged
Conversation
Needs ragent-service (auto-bound tools in the bind response, `SkillResponse.author`, owner-or-super on the app write endpoints). ## Buttons now match what the server will allow Two places showed actions to people who cannot perform them. Both fail in the same direction: the UI says "you can change this", then the server says 403 after the user commits to the action. That order reads as a broken system rather than as a permission boundary. - **Skill list**: 编辑 / 删除 rendered unconditionally. A dept admin saw both buttons lit on everyone's skills. Now: editable → 编辑 + 删除; not editable → **查看**, since they *can* read the detail page — hiding the button entirely would suggest the page is off limits. - **Skill detail**: edit rights were `canReview || owner`, and `canReview` used `checkTenantAdmin` **without tenant scoping** — a tenant admin from a different tenant could edit and save here, while the backend's `is_reviewer` requires the same tenant. The UI was the looser side. Conversely, three places were *stricter* than the backend and locked owners out of their own apps: the list/card edit+delete buttons, the 绑定工具 / per-tool actions, and the role editor — the backend has allowed owner-or-super on Agent.md all along. `lib/appPermissions.ts` and `lib/skillPermissions.ts` are the single frontend implementations, each mirroring one backend rule. Both carry the same warning: they only decide *display*. Hiding a button is not a security boundary — anyone can call the API directly. The enforcement lives on the server. Role booleans are passed in rather than imported from `clientPermissions`, so these stay pure functions the test runner can load. (Importing it broke the suite: the runner resolves neither the `@/` alias nor extensionless relative TS.) ## Skill list: 创建者 column Shows the creator (nickname → username). Empty when the account was deleted: the skill still exists and shouldn't vanish from the list because its author closed their account, so the cell shows — rather than going blank, which reads as a rendering fault. Header cells for the three narrow columns are `whitespace-nowrap` — at narrow widths two- and three-character Chinese headers were stacking one character per line. ## Binding a skill reports the tools it brought along The backend auto-binds a skill's `requires.tools`; the toast names them, and the tools list is invalidated so the count updates. Without invalidation the user sees an unchanged tools panel and concludes the auto-bind didn't work. Warnings (tool missing or disabled → the skill still won't inject) surface as separate warning toasts, since they aren't part of the success. ## Verified in the browser, per role - Plain user owning an app: edit/delete on their own row, 绑定工具 / 绑定 Skill / editable role on their own app, and on someone else's app only 返回 - Dept admin (the reported case): 编辑 + 删除 on their own skill, 查看 on the other four 229 tests pass (+13). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Needs ragent-service#67 (auto-bound tools in the bind response,
SkillResponse.author, owner-or-super on the app write endpoints).Buttons now match what the server will allow
Two places offered actions to people who can't perform them. Both fail in the same direction: the UI says "you can change this", then the server answers 403 after the user commits to the action. That order reads as a broken system rather than as a permission boundary.
canReview || owner, andcanReviewusedcheckTenantAdminwithout tenant scoping: a tenant admin from another tenant could edit and save here, while the backend'sis_reviewerrequires the same tenant. The UI was the looser side.Conversely, three places were stricter than the backend and locked owners out of their own apps:
lib/appPermissions.tsandlib/skillPermissions.tsare the single frontend implementations, each mirroring one backend rule. Both carry the same warning in the file: they only decide display. Hiding a button is not a security boundary — anyone can call the API directly. Enforcement lives on the server.Role booleans are passed in rather than imported from
clientPermissions, so these stay pure functions the test runner can load. Importing it broke the suite outright — the runner resolves neither the@/alias nor extensionless relative TS.Skill list: 创建者 column
Creator name (nickname → username). Empty when the account was deleted — the skill still exists and shouldn't vanish from the list because its author closed their account, so the cell shows
—rather than going blank, which reads as a rendering fault.The three narrow column headers get
whitespace-nowrap: at narrow widths two- and three-character Chinese headers were stacking one character per line.Binding a skill reports the tools it brought along
The backend auto-binds a skill's
requires.tools; the toast names them and the tools list is invalidated so the count updates. Without invalidation the user sees an unchanged tools panel and concludes the auto-bind didn't work. Warnings (tool missing or disabled → the skill still won't inject) surface as separate warning toasts, since they aren't part of the success.Verified in the browser, per role
Not only unit tests — logged in as each role against a real backend:
229 tests pass (+13).
tscunchanged from baseline apart from the new test files' pre-existingTS5097class.🤖 Generated with Claude Code