feat: add personal skill storage, API, and SDK (#25363)

> Mux updated this PR on behalf of Mike.

## Stack Context

This PR is the storage, permissions, API, and SDK layer for experimental
personal skills. #25362 has landed on `main`, so this branch is
restacked directly on `main`.

Stack order:
1. #25363 storage, permissions, API, and SDK
2. #25365 API test coverage
3. #25366 chattool and chatd integration
4. #25066 settings UI and docs
5. #25386 personal skills slash menu

## What?

Adds the `user_skills` database table, generated queries, RBAC resources
and scopes, audit resource handling, experimental user-scoped CRUD
endpoints, SDK types, and generated API/site types.

Follow-up review and restack fixes:
- Enforce a bounded personal skill description in parser and database
constraints.
- Return `403 Forbidden` for unauthorized create and update attempts.
- Return explicit conflict responses when soft-deleted users are
targeted.
- Keep user admins out of personal skills, while site owners can read
and delete but not create or update.
- Document trigger-raised constraint names and keep schema constants
covered by tests.
- Reuse `UserSkillMetadata` in the full `UserSkill` SDK response type.
- Generate user skill IDs in Go instead of relying on a database
default.
- Rebase on latest `main` and renumber the user skills migration to
`000502_user_skills`.

## Why?

Personal skills need durable user-owned storage with owner
authorization, limited site-owner moderation, and a hidden API surface
before chatd can consume them.

## Validation

- `make gen`
- `go test ./coderd/database -run '^TestUserSkillSchemaConstants$'
-count=1`
- `go test ./coderd/database/dbauthz -run
'^TestMethodTestSuite/TestUserSkills$' -count=1`
- `go test ./coderd -run '^TestPatchUserSkill$' -count=1`
- `go test ./codersdk ./coderd/database/db2sdk`
- `make lint`
- pre-commit hook on `97fd58108d`
This commit is contained in:
Michael Suchacz
2026-05-20 00:09:09 +02:00
committed by GitHub
parent 3c9c8c708d
commit 5a8d0016a5
46 changed files with 2315 additions and 70 deletions
+23
View File
@@ -390,6 +390,29 @@ func Test_diff(t *testing.T) {
},
})
runDiffTests(t, []diffTest{
{
// User skill content is user-authored instruction text, not secret
// material, so audit diffs can include the content change.
name: "UserSkillContentTracked",
left: audit.Empty[database.UserSkill](),
right: database.UserSkill{
ID: uuid.UUID{1},
UserID: uuid.UUID{2},
Name: "review-guidance",
Description: "How to review private projects",
Content: "review markdown",
},
exp: audit.Map{
"id": audit.OldNew{Old: "", New: uuid.UUID{1}.String()},
"user_id": audit.OldNew{Old: "", New: uuid.UUID{2}.String()},
"name": audit.OldNew{Old: "", New: "review-guidance"},
"description": audit.OldNew{Old: "", New: "How to review private projects"},
"content": audit.OldNew{Old: "", New: "review markdown"},
},
},
})
runDiffTests(t, []diffTest{
{
name: "Create",
+10
View File
@@ -34,6 +34,7 @@ var AuditActionMap = map[string][]codersdk.AuditAction{
"AuditableGroupAiBudget": {codersdk.AuditActionWrite, codersdk.AuditActionDelete},
"Chat": {codersdk.AuditActionCreate, codersdk.AuditActionWrite}, // chats get 'archived' by users, not deleted.
"UserSecret": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete},
"UserSkill": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete},
}
type Action string
@@ -446,6 +447,15 @@ var auditableResourcesTypes = map[any]map[string]Action{
"plan_mode": ActionIgnore, // Can flip back and forth during a session.
"client_type": ActionIgnore, // Set at creation.
},
&database.UserSkill{}: {
"id": ActionTrack,
"user_id": ActionTrack,
"name": ActionTrack,
"description": ActionTrack,
"content": ActionTrack,
"created_at": ActionIgnore,
"updated_at": ActionIgnore,
},
&database.UserSecret{}: {
"id": ActionTrack,
"user_id": ActionTrack,