Files
pad/internal/store/migrations/017_agent_roles.sql
T
xarmian be576d9e24 feat: agent roles — role-based (user, role) assignment for items (#58)
* feat: agent roles — role-based (user, role) assignment for items (#PHASE-9)

Introduce agent roles as a first-class concept for human-agent work
assignment. Roles describe capability specializations (Planner,
Implementer, Reviewer, etc.) and items can be assigned to a (user, role)
pair, enabling natural handoff workflows between different AI tools.

Migration:
- New `agent_roles` table (workspace-scoped, slug-unique)
- `assigned_user_id` + `agent_role_id` columns on `items` with FKs
- Removed legacy `assignee` text field from Tasks schema

Backend:
- AgentRole model + full CRUD store/API
- All item queries updated with LEFT JOINs to resolve assignment
- Item list filtering by assigned_user_id and agent_role_id
- Role transitions tracked in activity feed metadata

CLI:
- `pad role list/create/delete` commands
- `--role` and `--assign` flags on item create/update/list
- Assignment displayed in `pad item show` output

Web:
- TypeScript types + API client for agent roles
- Role badge on item cards in list/board views
- Assignment display on item detail page

* fix: enforce workspace-scoped assignments and fail fast on unresolved --assign filter

Addresses code review feedback from PR #58:

P1: Add validateAssignmentScope() to the store layer, called by both
CreateItem and UpdateItem. Verifies that assigned_user_id belongs to
the workspace (via IsWorkspaceMember) and agent_role_id exists in the
workspace (via GetAgentRole) before writing. Prevents cross-workspace
assignment leaks.

P2: The CLI `pad item list --assign <name>` now errors instead of
silently returning unfiltered results when the member lookup fails or
no workspace member matches the provided name.
2026-04-03 21:16:38 -04:00

37 lines
1.5 KiB
SQL

-- Agent roles: workspace-scoped capability roles for human-agent assignment
CREATE TABLE IF NOT EXISTS agent_roles (
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL REFERENCES workspaces(id),
slug TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
icon TEXT NOT NULL DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(workspace_id, slug)
);
CREATE INDEX IF NOT EXISTS idx_agent_roles_workspace ON agent_roles(workspace_id);
-- Assignment columns on items: (user, role) pairs
ALTER TABLE items ADD COLUMN assigned_user_id TEXT REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE items ADD COLUMN agent_role_id TEXT REFERENCES agent_roles(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_items_assigned_user ON items(assigned_user_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_items_agent_role ON items(agent_role_id) WHERE deleted_at IS NULL;
-- Remove assignee text field from existing Tasks collection schemas.
-- This replaces the free-text assignee with the structured (user, role) assignment above.
UPDATE collections
SET schema = REPLACE(schema, '{"key":"assignee","label":"Assignee","type":"text"},', '')
WHERE slug = 'tasks'
AND schema LIKE '%"assignee"%';
-- Handle alternate JSON formatting (with spaces)
UPDATE collections
SET schema = REPLACE(schema, '{"key": "assignee", "label": "Assignee", "type": "text"},', '')
WHERE slug = 'tasks'
AND schema LIKE '%"assignee"%';