-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Problem
Every interface layer wraps abilities but re-implements permissions independently:
Abilities (source of truth — permission_callback, category, annotations)
├── Tools → 83 tools, ZERO permission checks at resolution time
├── REST API → 71 routes, each with its own permission_callback (often duplicated)
├── CLI → commands with their own capability checks
└── Admin UI → React components with their own auth logic
The Abilities API already carries everything needed:
$ability->check_permissions() // who can execute
$ability->get_category() // organizational grouping
$ability->get_meta()['annotations'] // readonly, destructive, idempotentBut none of the interface layers consult the ability's permission model. Tools don't check if the user can execute the underlying ability before offering it to the AI. REST endpoints duplicate permission logic instead of delegating to the ability. This creates drift, gaps, and maintenance burden.
Architecture
The pattern should be: every interface declares which ability (or abilities) it wraps. Permissions flow from the ability through all interfaces consistently.
Interface registration:
ability: 'datamachine/local-search' // single ability
abilities: ['datamachine/create-flow', ...] // composed (multiple abilities)
Permission resolution:
1. Check ALL linked abilities via check_permissions()
2. If ANY fails → interface is unavailable for this user
3. No hardcoded permission logic at the interface layer
Tools (AI agent interface)
Each tool declares its linked ability:
$this->registerTool('local_search', [
'description' => '...',
'contexts' => ['chat'],
'ability' => 'datamachine/local-search', // NEW
]);ToolPolicyResolver::resolve() checks $ability->check_permissions() before including the tool in the resolved set. Tools the user can't execute are never offered to the AI.
For tools that compose multiple abilities:
'abilities' => ['datamachine/create-pipeline', 'datamachine/create-flow'],All must pass permission checks.
For tools with no underlying ability (e.g. web_fetch calls external APIs directly):
'ability' => null,
'access_level' => 'authenticated', // fallback: explicit access tierDefault for unlinked tools: admin (safe fallback — opt-in to lower tiers).
REST API
Routes already declare permission_callback, but many duplicate ability permission logic:
// Current — duplicated
'permission_callback' => fn() => current_user_can('manage_options'),
// Better — delegate to ability
'permission_callback' => fn() => wp_get_ability('datamachine/create-flow')->check_permissions(),This is a cleanup task, not a new pattern. The REST layer is already close — it just needs to delegate instead of duplicate.
CLI
CLI commands run as WP-CLI (root context), so permissions are less relevant. But the pattern still applies for documentation and consistency.
Baseline gate
datamachine_use_tools capability is enforced as a baseline in ToolPolicyResolver::resolve(). If the acting user (or agent owner) lacks datamachine_use_tools, zero tools are returned regardless of individual ability permissions.
This means:
- Subscribers (
chatonly) → can chat but get NO tools - Authors (
chat+use_tools) → get tools their abilities allow - Editors (
chat+use_tools+view_logs) → get tools + log tools - Admins (everything) → get everything
Implementation phases
Phase 1: Tool → ability linking + resolver enforcement
- Add
ability/abilitiesfield toBaseTool::registerTool() - Add
access_levelfallback for tools without a linked ability - Update
ToolPolicyResolver::resolve()to check ability permissions - Enforce
datamachine_use_toolsbaseline gate in resolver - Default unlinked tools to
adminaccess level
Phase 2: Map all 83 tools to their abilities
- Audit every tool class, identify which ability/abilities it wraps
- Add
abilitydeclarations to all tool registrations - Tag ability-less tools with explicit
access_level - Handle composed tools (multiple abilities)
Phase 3: REST API delegation (cleanup)
- Audit REST permission_callbacks that duplicate ability permissions
- Replace with
wp_get_ability(...)->check_permissions()delegation - Document the pattern for future endpoints
Phase 4: Clean up
- Reorganize tool directory to match ability categories
- Self-service tool policy management for agent owners
- Admin UI for tool permission visibility
Related
- feat: Scoped agent creation and ability execution for non-admin users #919 — Scoped agent creation
- feat: Self-service agent creation for non-admin users #920 — Self-service agent creation (shipped)
- extrachill-artist-platform#17 — Artist Agent Manager