Capability Manifest
Overview
Section titled “Overview”A Capability Manifest declares the full set of operations a Tier 1 application exposes. Unlike Tier 2 apps, where capabilities are derived by inspecting the accessibility tree, Tier 1 apps define their capabilities explicitly with typed parameters, typed return values, and versioned schemas.
The manifest is shipped alongside the app binary and signed with the developer’s Ed25519 key. The daemon verifies the signature at registration time. On verification failure, the app falls back to Tier 2 (AT-SPI2-derived capabilities).
Capability Struct
Section titled “Capability Struct”Each capability in a manifest describes one operation the app can perform.
pub struct Capability { pub id: String, // fully qualified: domain.app.action pub name: String, // human-readable name pub description: String, // what this capability does pub category: CapabilityCategory, // taxonomy placement pub parameters: serde_json::Value, // JSON-Schema-shaped pub returns: serde_json::Value, // JSON-Schema-shaped pub side_effects: SideEffect, // default None pub confirmation_required: bool, // default false pub undo_window_ms: Option<u64>, // undo time window pub version: String, // default "1.0" pub auth_level: AuthLevel, // default User}CapabilityCategory (16 named variants + Custom)
Section titled “CapabilityCategory (16 named variants + Custom)”| Category | Purpose |
|---|---|
TextInput |
Type text, insert at cursor, replace selection |
TextQuery |
Read content, search, extract text |
ElementInteraction |
Click buttons, toggle checkboxes, select items |
Navigation |
Open URLs, switch tabs, navigate within apps |
StateQuery |
Get counts, check status, list items |
AppManagement |
Launch, quit, install, configure applications |
SurfaceManagement |
Move, resize, tile, minimize, close windows |
LayoutPreset |
Apply predefined window arrangements |
MediaPlayback |
Play, pause, skip, seek media |
System |
System-level operations (audio, display, power) |
Communication |
Send messages, make calls |
FileOperation |
Move, copy, delete files |
Hardware |
Interact with devices (camera, microphone) |
Accessibility |
Screen reader, magnifier, and a11y controls |
DataManagement |
Import, export, sync data |
MediaControl |
Volume, brightness, and playback controls |
Custom(String) |
Extension point for app-specific categories |
SideEffect
Section titled “SideEffect”pub enum SideEffect { None, Read, Write, Destructive, Network, System }Operations with None side effects can be auto-approved. Destructive and Network operations require confirmation or elevated trust. This field drives the permission check behavior in the daemon.
AuthLevel
Section titled “AuthLevel”pub enum AuthLevel { Public, User, Privileged, System }Ordered from lowest to highest privilege. is_at_least() provides comparison. A capability with auth_level: Privileged can only be invoked by callers at Privileged or System level.
| Level | Meaning |
|---|---|
Public |
No trust required. Read-only state queries. |
User |
Standard user trust. Most capability invocations. |
Privileged |
Elevated trust. System configuration changes. |
System |
System identity only. Reserved for the daemon and the intelligence layer. |
PcpServer Trait
Section titled “PcpServer Trait”Tier 1 applications implement this trait to handle capability invocations. The daemon calls these methods on the per-app socket. This is the entire contract a Tier 1 app implements.
#[async_trait]pub trait PcpServer: Send + Sync { /// Returns the capability manifest for this application. async fn manifest(&self) -> CapabilityManifest;
/// Execute a capability invocation. async fn invoke( &self, capability_id: &str, ctx: &InvocationContext, params: Value, ) -> Result<CapabilityResult, PcpError>;
/// Validate parameters before execution. async fn validate_params( &self, capability_id: &str, params: &Value, ) -> Result<(), PcpError>;
/// Subscribe to event types from this app. async fn subscribe_events( &self, event_types: Vec<String>, ) -> Result<(), PcpError>;
/// Query the app's current dynamic state for a capability. async fn query_dynamic_state( &self, capability_id: &str, ) -> Result<Value, PcpError>;
/// Graceful shutdown. No response expected. async fn shutdown(&self);}Tier 1 capabilities operate on the app’s data model in Rust. The renderer (webview, terminal canvas, or any display surface) is a pure consumer of state. APIs like evaluate_script(), document.execCommand(), or any UI automation that operates on the rendered surface rather than the underlying data model are forbidden in capability handlers.
InvocationContext
Section titled “InvocationContext”The daemon passes an InvocationContext to every invoke() call. It carries everything the app needs to make routing and authorization decisions.
pub enum AuthPrincipal { User, // default -- regular invocation Admin, // requires admin flag on the invoking client System, // daemon-internal (auto-degradation, learning recording)}
pub struct InvocationContext { pub app_id: String, pub surface_id: Option<SurfaceId>, pub element_id: Option<ElementId>, pub user_confirmed: bool, pub audit_trail_hash: Option<String>, pub auth_principal: AuthPrincipal,}CapabilityResult
Section titled “CapabilityResult”pub struct CapabilityResult { pub success: bool, pub data: Option<serde_json::Value>, pub error: Option<String>, pub audit_hash: Option<String>, pub state_source: StateSource,}
pub enum StateSource { Rust, // default -- fulfilled from the app's Rust state model Renderer, // permitted ONLY for pixel-capture capabilities (screenshots) Stale, // could not read current state Unknown, // app cannot determine the source}The state_source field tells the caller where the result’s authoritative state lives. Rust is the default for all Tier 1 capabilities. Renderer is only allowed for capabilities that need to capture rendered pixels (screenshots, frame grabs). Stale and Unknown indicate degraded state.
CapabilityManifest
Section titled “CapabilityManifest”The container that holds all capability declarations for one application, plus optional event definitions and the Ed25519 signature.
pub struct CapabilityManifest { pub app_id: String, pub version: String, pub manifest_version: String, // default "1.0" pub capabilities: Vec<Capability>, pub events: Vec<ManifestEvent>, pub signature: Option<String>, // Ed25519}
pub struct ManifestEvent { pub event_type: String, pub description: String, pub payload_schema: serde_json::Value,}Manifest Example
Section titled “Manifest Example”Based on the V5 deployed manifest format, with vendor-specific names replaced by a generic example app:
{ "app_id": "com.example.present", "version": "0.1.0", "manifest_version": "1.0", "capabilities": [ { "id": "slide.add", "name": "Add slide", "description": "Insert a new slide; appends when index is omitted", "category": "APP_MANAGEMENT", "parameters": { "properties": { "html": { "type": "string" }, "index": { "type": "integer", "minimum": 0 }, "notes": { "type": "string" } }, "type": "object" }, "returns": { "properties": { "index": { "type": "integer" } }, "type": "object" }, "side_effects": "write", "confirmation_required": false, "undo_window_ms": null, "version": "1.0", "auth_level": "user" }, { "id": "slide.remove", "name": "Remove slide", "description": "Delete a slide from the deck", "category": "APP_MANAGEMENT", "parameters": { "properties": { "index": { "type": "integer", "minimum": 0 } }, "required": ["index"], "type": "object" }, "returns": { "properties": { "removed_count": { "type": "integer" } }, "type": "object" }, "side_effects": "destructive", "confirmation_required": true, "undo_window_ms": 10000, "version": "1.0", "auth_level": "user" } ], "events": [], "signature": null}The slide.add capability is a write operation that can be auto-approved. The slide.remove capability is destructive, requires user confirmation, and allows undo within 10 seconds.
Ed25519 Signing
Section titled “Ed25519 Signing”Manifests are signed with Ed25519 to ensure authenticity and integrity.
Signature Format
Section titled “Signature Format”The signature field uses the format "ed25519:<base64>". The base64 portion is the 64-byte Ed25519 signature over the canonicalized manifest content.
Canonicalization
Section titled “Canonicalization”Before signing or verifying, the manifest is canonicalized:
- Remove the
signaturefield entirely from the JSON object. - Sort all remaining keys lexicographically.
- Serialize with compact JSON (no whitespace after commas or colons).
The canonicalization step ensures that different JSON serializers produce identical byte sequences for the same logical manifest.
Key Lifecycle
Section titled “Key Lifecycle”- The developer generates an Ed25519 key pair (32-byte private key, 32-byte public key).
- The public key is placed in a trusted keys location where the daemon can read it.
- The developer signs the manifest with the private key and ships the signature inside the manifest.
- Key rotation: when a developer generates a new key pair, the updated public key must be placed in the trusted keys directory before the new manifest is deployed.
Verification Flow
Section titled “Verification Flow”- The daemon receives the manifest at registration time.
- It extracts the
signaturefield value and removes it from the payload. - It canonicalizes the remaining JSON.
- It looks up the trusted public key for the app.
- It verifies the Ed25519 signature against the canonicalized payload.
- On success, the manifest is accepted and capabilities are registered.
- On failure, the app falls back to Tier 2 (AT-SPI2-derived capabilities).
Performance
Section titled “Performance”The manifest load and verify operation must complete in under 50ms, with a maximum acceptable latency of 100ms. This covers JSON parsing, canonicalization, and the Ed25519 signature check.
Capability ID Format
Section titled “Capability ID Format”Capability IDs use the format {domain}.{app}.{action} where app may contain dots (reverse-DNS names are supported). The minimum is three dot-separated non-empty parts.
Examples:
app.com.example.present.slide.add(domain=app, app=com.example.present, action=slide.add)editor.org.xfce.mousepad.read(domain=editor, app=org.xfce.mousepad, action=read)
The CapabilityId::new() constructor validates this format at construction time.