Opsmaru.Incus.Operations defines each agent-facing Incus operation once: a
canonical incus_<resource>_<verb> name, input/output JSON schemas, a required
scope, a destructive-confirmation rule and bounds. Macus and central Opsmaru
expose the same descriptors, so names and schemas are public contract from the
first consumer pin. The library defines and runs them; how a host exposes
them to agents (MCP tools, a search/execute index, a REST surface) is the
host's choice. Nothing here starts a process, endpoint, task store or
connection.
Every operation runs synchronously through the public Opsmaru.Incus client.
Tool success is not client coverage: priv/incus/coverage.json and the
endpoint ledgers are unchanged by tools.
Catalogue
| Operation | Scope | Confirmation (confirm must repeat) | Client evidence |
|---|---|---|---|
incus_image_alias_list | incus.read | — | GetImageAliases |
incus_image_delete | incus.delete | the full fingerprint | DeleteImage, GetOperationWait |
incus_image_get | incus.read | — | GetImage |
incus_image_list | incus.read | — | GetImages |
incus_instance_create | incus.write | — | CreateInstance, GetOperationWait |
incus_instance_delete | incus.delete | the instance name | DeleteInstance, GetOperationWait |
incus_instance_exec | incus.exec | — | ExecInstance, stream:S01 |
incus_instance_get | incus.read | — | GetInstance |
incus_instance_list | incus.read | — | GetInstances |
incus_instance_snapshot_create | incus.write | — | CreateInstanceSnapshot, GetOperationWait |
incus_instance_snapshot_delete | incus.delete | <instance>/<snapshot> | DeleteInstanceSnapshot, GetOperationWait |
incus_instance_snapshot_get | incus.read | — | GetInstanceSnapshot |
incus_instance_snapshot_list | incus.read | — | GetInstanceSnapshots |
incus_instance_snapshot_restore | incus.delete | <instance>/<snapshot> | UpdateInstance, GetOperationWait |
incus_instance_state_get | incus.read | — | GetInstanceState |
incus_instance_state_update | incus.write | — | UpdateInstanceState, GetOperationWait |
incus_instance_update | incus.write | — | PatchInstance, GetInstance |
incus_network_get | incus.read | — | GetNetwork |
incus_network_list | incus.read | — | GetNetworks |
incus_operation_cancel | incus.write | — | GetOperation, DeleteOperation |
incus_operation_get | incus.read | — | GetOperation |
incus_operation_list | incus.read | — | GetOperations |
incus_operation_wait | incus.read | — | GetOperationWait |
incus_profile_get | incus.read | — | GetProfile |
incus_profile_list | incus.read | — | GetProfiles |
incus_project_get | incus.read | — | GetProject |
incus_project_list | incus.read | — | GetProjects |
incus_server_get | incus.read | — | helper:connect |
incus_storage_pool_get | incus.read | — | GetStoragePool |
incus_storage_pool_list | incus.read | — | GetStoragePools |
incus_storage_volume_get | incus.read | — | GetStoragePoolVolume |
incus_storage_volume_list | incus.read | — | GetStoragePoolVolumes |
The full schemas, titles, descriptions and bounds are pinned in
test/fixtures/incus/operations-catalogue.json;
Operations.describe/1 returns the same data at runtime.
Not exported (Operations.unavailable/0): incus_instance_copy,
incus_instance_rename and incus_instance_move, deferred by scope
(user decision 2026-11-04), not for missing evidence. Events, console
attachment and file or image binary transfer are excluded because they need
streaming beyond a bounded result.
Evidence gate
Each descriptor names the client requirements it uses: a native method, a
helper (helper:connect) or a streaming contract (stream:S01).
Opsmaru.Incus.Operations.Evidence reads endpoint-inventory.json and
coverage.json at compile time. A requirement has evidence only when every
endpoint row mapping it is behavior-complete under a completion record (a
stream contract must be certified) and its Go coverage entry, where one exists,
names an implementation and evidence files. all/0 exports only descriptors
whose requirements all have evidence; any other descriptor is listed by
unavailable/0 with its missing requirements.
Scopes and confirmation
| Scope | Operations |
|---|---|
incus.read | server, instance, snapshot, image, project, profile, network, storage and operation inspection; operation get/wait |
incus.write | instance create/update/state, snapshot create, operation cancel |
incus.exec | bounded guest exec |
incus.delete | instance, snapshot and image deletion; snapshot restore |
Scopes are the host-verified grants; they never come from tool input. A
destructive operation also requires confirm to repeat the affected resource
exactly (web, web/snap0 or the full 64-character image fingerprint).
Inferred consent such as "yes" is refused.
Bounds
| Bound | Value |
|---|---|
| Input | 16 KiB for reads, 64 KiB for mutations, 1 MiB for exec |
| Tool output | 256 KiB of JSON |
| List items | 100 by default, at most 500 (limit) |
| Mutation and operation wait | 10 s by default, at most 60 s (wait_seconds) |
| Exec duration | 30 s by default, at most 300 s (timeout_seconds) |
| Exec argv / environment | 64 arguments of at most 4096 characters; 64 variables |
| Exec stdin | 64 KiB UTF-8 |
| Exec capture | first 64 KiB per stream, the rest counted |
| Exec stream total | 64 MiB, or half the connection's max_stream_bytes |
Lists keep native order and report the native total (count) and
truncated. A list that exceeds the output bound drops items from the end; a
single item that alone exceeds it fails with output_limit.
Every native request is bounded by the host connection's timeout. The
effective server-side wait is min(wait_seconds, floor(timeout / 1000) - 1),
reported as waited_seconds; configure at least 61 s to allow the full 60 s
wait. The exec duration is likewise capped at stream_timeout minus the 5 s
kill grace and reported as timeout_seconds.
Host binding
A host implements Opsmaru.Incus.Operations.Host:
defmodule MyHost.IncusOperations do
@behaviour Opsmaru.Incus.Operations.Host
# The trusted backend for this caller: a discovered client, or a connection
# and context that the operation discovers after authorization.
@impl true
def binding(caller, _operation), do: {:ok, MyHost.connection(), MyHost.context(caller)}
# Validated, normalized input, including any native "project".
@impl true
def authorize(caller, operation, input), do: MyHost.Policy.check(caller, operation, input)
# Optional: host-approved image servers, named in input only by remote name.
@impl true
def image_remote(_caller, "images"),
do: {:ok, %{"server" => "https://images.linuxcontainers.org", "protocol" => "simplestreams"}}
def image_remote(_caller, _name), do: {:error, :unknown_remote}
# Optional: allow restricted config keys or devices.
@impl true
def allow_restricted?(_caller, _operation, _items), do: false
end
Opsmaru.Incus.Operations.run("incus_instance_list", %{"project" => "default"},
host: MyHost.IncusOperations,
caller: verified_caller,
scopes: verified_scopes
)run/3 checks, in order: scope, input schema and byte bounds, confirmation,
restricted input, binding/2, authorize/3, image remote resolution and then
discovery (when the binding returned a connection and context). The native
project is selected only after authorization. A missing, raising, exiting or
malformed callback denies the call before any backend request; tool input
cannot name a socket, URL, credential, owner or host path.
Restricted create/update input, denied unless allow_restricted?/3 returns
true: config keys raw.*, volatile.*, security.privileged,
security.idmap.*, security.syscalls.* and linux.kernel_modules; any
device except a disk with a pool and no host-path source, or a nic
with a network and no nictype or parent.
Results
Success results are JSON objects; failures are {"error": {"kind", "message", ...}}
and match each tool's output schema.
- Inspection:
{"items", "count", "truncated"}or{"item"};incus_instance_getadds the nativeetag. Native data, including VM-shaped instances and unknown fields, passes through unchanged. - Mutations:
{"status", "operation_id", "operation", "waited_seconds", "wait_error"}.completedandfailedmean the native outcome was observed;runningmeans it was not observed within the wait (including a local wait deadline, which never cancels anything);uncertainmeans the request may have been accepted but no response was decoded. Nothing is resent automatically. A native rejection before acceptance is an error withhttp_status,error_codeand the nativemessage. incus_instance_updatesends a conditional PATCH of only the given fields. Withoutetagit reads the current ETag first;etag_sourcesays which.incus_operation_cancelreads the operation first and reportsalready_final,unsupported(may_cancelis false),requested(one native DELETE was accepted) oruncertain. Completion stays authoritative.incus_instance_execruns one noninteractive command (no PTY, no recorded output). Output is returned as UTF-8, or base64 when it is not UTF-8, with byte counts and atruncatedflag per stream. At the duration, or past the stream total, a short-lived process linked to the call sendsSIGKILLonce over the control channel and collection has 5 s to observe the exit (timed_outoroutput_limited). An exit not observed isrunningwith the operation identity.
Exposing operations
The library ships no protocol binding. A host builds its own over all/0,
fetch/1, describe/1 and run/3, for example one tool per operation, or a
search tool over describe/1 plus an execute tool that calls run/3.
If one tool fronts many operations, pass the caller's verified scopes to
run/3: it enforces each operation's own scope, so access to the fronting
tool does not grant every operation. An earlier optional TamaMCP binding was
removed on 2026-11-04 by user decision, before any consumer pinned it.
Evidence classes
| Class | Where | Status |
|---|---|---|
| Fixtures | test/operations_*_test.exs over fake Unix and mutual HTTPS Incus | run by mise run check |
| Isolated consumers | test/consumer (Macus-compatible host running an operation through its binding) and test/consumer/client_only (no tama_mcp, Plug, Cowboy or Phoenix) | run by mise run check:consumer at a pushed revision |
| Real Incus | explicit isolated opt-in (task 4.2) | recorded separately in real-incus-acceptance.md |
| Macus composition and hardware | Macus add-elixir-server | consumer-owned |