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
+30 -2
View File
@@ -312,6 +312,11 @@ const (
ApiKeyScopeAiProviderRead APIKeyScope = "ai_provider:read"
ApiKeyScopeAiProviderUpdate APIKeyScope = "ai_provider:update"
ApiKeyScopeChatShare APIKeyScope = "chat:share"
ApiKeyScopeUserSkillCreate APIKeyScope = "user_skill:create"
ApiKeyScopeUserSkillRead APIKeyScope = "user_skill:read"
ApiKeyScopeUserSkillUpdate APIKeyScope = "user_skill:update"
ApiKeyScopeUserSkillDelete APIKeyScope = "user_skill:delete"
ApiKeyScopeUserSkill APIKeyScope = "user_skill:*"
)
func (e *APIKeyScope) Scan(src interface{}) error {
@@ -567,7 +572,12 @@ func (e APIKeyScope) Valid() bool {
ApiKeyScopeAiProviderDelete,
ApiKeyScopeAiProviderRead,
ApiKeyScopeAiProviderUpdate,
ApiKeyScopeChatShare:
ApiKeyScopeChatShare,
ApiKeyScopeUserSkillCreate,
ApiKeyScopeUserSkillRead,
ApiKeyScopeUserSkillUpdate,
ApiKeyScopeUserSkillDelete,
ApiKeyScopeUserSkill:
return true
}
return false
@@ -792,6 +802,11 @@ func AllAPIKeyScopeValues() []APIKeyScope {
ApiKeyScopeAiProviderRead,
ApiKeyScopeAiProviderUpdate,
ApiKeyScopeChatShare,
ApiKeyScopeUserSkillCreate,
ApiKeyScopeUserSkillRead,
ApiKeyScopeUserSkillUpdate,
ApiKeyScopeUserSkillDelete,
ApiKeyScopeUserSkill,
}
}
@@ -3322,6 +3337,7 @@ const (
ResourceTypeAiProvider ResourceType = "ai_provider"
ResourceTypeAiProviderKey ResourceType = "ai_provider_key"
ResourceTypeGroupAiBudget ResourceType = "group_ai_budget"
ResourceTypeUserSkill ResourceType = "user_skill"
)
func (e *ResourceType) Scan(src interface{}) error {
@@ -3392,7 +3408,8 @@ func (e ResourceType) Valid() bool {
ResourceTypeUserSecret,
ResourceTypeAiProvider,
ResourceTypeAiProviderKey,
ResourceTypeGroupAiBudget:
ResourceTypeGroupAiBudget,
ResourceTypeUserSkill:
return true
}
return false
@@ -3432,6 +3449,7 @@ func AllResourceTypeValues() []ResourceType {
ResourceTypeAiProvider,
ResourceTypeAiProviderKey,
ResourceTypeGroupAiBudget,
ResourceTypeUserSkill,
}
}
@@ -5725,6 +5743,16 @@ type UserSecret struct {
ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"`
}
type UserSkill struct {
ID uuid.UUID `db:"id" json:"id"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
Content string `db:"content" json:"content"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
// Tracks the history of user status changes
type UserStatusChange struct {
ID uuid.UUID `db:"id" json:"id"`