Skip to content

arch: Ability-linked permission resolution across all interface layers (tools, REST, CLI) #924

@chubes4

Description

@chubes4

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, idempotent

But 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 tier

Default 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 (chat only) → 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 / abilities field to BaseTool::registerTool()
  • Add access_level fallback for tools without a linked ability
  • Update ToolPolicyResolver::resolve() to check ability permissions
  • Enforce datamachine_use_tools baseline gate in resolver
  • Default unlinked tools to admin access level

Phase 2: Map all 83 tools to their abilities

  • Audit every tool class, identify which ability/abilities it wraps
  • Add ability declarations 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions