ADR-006: Tauri IPC Command Contract
Date: 2026-03-08 Status: Accepted Deciders: Maekon maintainers Related: ADR-005: Tauri v2 Governance
Context
Tauri v2 exposes Rust functions to the JavaScript frontend via the tauri::generate_handler! macro (registered through the .invoke_handler() builder method). This creates a typed IPC surface between the Rust backend (src-tauri/src/commands.rs) and the React frontend. Without a documented contract, IPC calls become an implicit API that is difficult to audit, version, or test.
This ADR documents the current IPC surface, defines the error handling pattern, and establishes the versioning policy for breaking changes.
Decision
All Tauri IPC commands are defined in src-tauri/src/commands.rs and registered in src-tauri/src/main.rs via tauri::generate_handler!. The full set of registered commands is the authoritative IPC surface. No other entry points exist.
Current Command Surface
As of 2026-03-08, the following commands are registered:
get_metrics
Returns current system and agent resource usage.
Input: none
Output:
{
agent_cpu: number; // Agent process CPU usage (%)
agent_memory_mb: number; // Agent process memory (MB)
system_cpu: number; // Total system CPU usage (%)
system_memory_used_mb: number;
system_memory_total_mb: number;
}
Errors: String — sysinfo failure (rare; returns zero values on partial failure)
get_settings
Returns the current AppConfig as a JSON object.
Input: none
Output: Full AppConfig JSON object. Shape matches crates/maekon-core/src/config/mod.rs (AppConfig struct).
Errors: String — serialization failure (should never occur in practice)
update_setting
Applies a partial config patch. Only allowlisted top-level keys are accepted; all others return an error.
Input:
config_json: string // JSON string containing the partial config object
Allowed top-level keys (enforced server-side; any other key returns an error):
monitoringcapturenotificationwebscheduletelemetryprivacyupdatelanguagetheme
The patch is deep-merged into the current config. Keys not present in the patch are preserved.
Output: void
Errors: String — invalid JSON, disallowed key, or config serialization failure
Security note: Keys such as server, sandbox, ai_provider, file_access, and grpc cannot be modified from the WebView. They can only be changed by editing config.json directly (requires OS-level access to the config directory).
get_update_status
Returns the current state of the auto-updater.
Input: none
Output: JSON object with phase field. When updates are disabled: {"phase": "Disabled", "message": "Updates disabled"}. When enabled, the phase reflects the updater state machine (e.g., Idle, Checking, Available, Downloading, Ready).
Errors: String — serialization failure
approve_update
Triggers installation of a pending update. The user must confirm before calling this command.
Input: none
Output: void
Errors: String — no pending update, or update action channel closed
defer_update
Defers a pending update to the next check interval.
Input: none
Output: void
Errors: String — update action channel closed
get_automation_status
Returns whether the automation controller is configured and active.
Input: none
Output: boolean — true if the AutomationController is initialized
Errors: String (none expected in practice)
Error Handling Pattern
All commands return Result<T, String>. Errors are string-serialized for JavaScript consumption. The frontend should treat any non-null error string as a failure and display it to the user or log it.
// Idiomatic frontend usage
const result = await invoke<MetricsResponse>('get_metrics');
// Tauri throws on Err(_) — wrap in try/catch
Errors from infrastructure (sysinfo failures, config I/O errors) are wrapped into strings at the command boundary. No structured error codes are used in the current version.
Security Model
IPC commands are only callable from the embedded WebView frontend. The Tauri security model enforces this at the process level — no external process, no network request, and no browser extension can invoke these commands.
The update_setting command enforces an allowlist at the Rust layer (not in JavaScript). The frontend cannot bypass this check by constructing a raw IPC message.
Sensitive config sections (server, grpc, ai_provider, sandbox, file_access) are intentionally excluded from the allowlist. These are administrator-managed fields configured via the file system, not user-controlled via the UI.
Versioning Policy
Non-Breaking Changes (allowed without version bump)
- Adding a new field to an existing command's output (with
#[serde(default)]on the Rust side) - Adding a new command to the handler (new commands are additive)
- Changing internal implementation without changing input/output types
Breaking Changes (require major version bump)
- Removing a command
- Renaming a command
- Changing an existing command's input type in a non-backward-compatible way
- Changing an existing command's output type in a non-backward-compatible way
- Removing a field from a command's output without a deprecation period
When a breaking change is necessary:
- Increment the major version in
Cargo.tomlworkspaceversion. - Update this ADR with the new command surface.
- Update
CHANGELOG.mdwith aBREAKING CHANGEentry. - Notify downstream teams before releasing.
Adding New Commands
To add a new IPC command:
- Define the function in
src-tauri/src/commands.rswith#[command]. - Add the function to the
tauri::generate_handler![]call insrc-tauri/src/main.rs. - Update this ADR with the new command's input, output, and error contract.
- Add a TypeScript type declaration for the response shape in the frontend.
Do not add commands that expose file system paths, process lists, or network configuration to the frontend. Route those through the existing get_settings / update_setting pattern.