Skip to content

feat(apps,skills): show what you may actually do, and name the creator - #8

Merged
squarezw merged 1 commit into
mainfrom
feat/skill-autobind-tools
Aug 5, 2026
Merged

feat(apps,skills): show what you may actually do, and name the creator#8
squarezw merged 1 commit into
mainfrom
feat/skill-autobind-tools

Conversation

@squarezw

@squarezw squarezw commented Aug 4, 2026

Copy link
Copy Markdown
Owner

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.

  • Skill list — 编辑 / 删除 rendered unconditionally. A dept admin saw both buttons lit on everyone's skills (the reported case). Now: editable → 编辑 + 删除; not editable → 查看, because 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 another 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:

  • list / card edit + delete buttons
  • 绑定工具 and the per-tool row actions
  • the role editor — the backend has allowed owner-or-super on Agent.md all along, so this one was purely the UI keeping owners out

lib/appPermissions.ts and lib/skillPermissions.ts are 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:

  • Plain user owning an app: edit/delete on their own row; 绑定工具 / 绑定 Skill / editable role on their own app; on someone else's app only 返回 remains
  • Dept admin (the reported case): 编辑 + 删除 on their own skill, 查看 on the other four

229 tests pass (+13). tsc unchanged from baseline apart from the new test files' pre-existing TS5097 class.

🤖 Generated with Claude Code

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>
@squarezw
squarezw merged commit 86d703e into main Aug 5, 2026
1 check passed
@squarezw
squarezw deleted the feat/skill-autobind-tools branch August 5, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant