Skip to main content

Module handler

Module handler 

Source
Expand description

What a capability’s behaviour is, and what it is allowed to know.

A handler is a typed function — fn(&Context, Input) -> Result<Output, CapabilityError> — placed behind a JSON boundary by handler. The boundary is what lets one registry hold capabilities with different input and output types; the typing is what keeps the handler from doing its own deserialisation.

§What a handler may know

Context is the whole of it: the index of the repository, the registry it belongs to, the board of peers attached to this process, the Why catalogue, the executor, the resolved web topology, and — only when the call arrived through an MCP session — which peer made it. A handler never learns whether MCP, HTTP or the command line called it, and that is deliberate: an answer that depended on the transport would be a second implementation hiding inside the first.

Index, registry and topology are immutable for the life of a process and are shared by Arc, so a request never rebuilds canonical state. The peer board is the one thing that changes, and it lives in memory only.

§The error model

CapabilityError has four cases and names no status code and no JSON-RPC code. Each transport maps them into its own vocabulary, so adding a transport cannot change what a handler is able to say.

use majordomus_cli::capability::{CapabilityError, Context, handler::handler};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct In { n: u32 }
#[derive(Serialize)]
struct Out { doubled: u32 }

fn double(_: &Context, input: In) -> Result<Out, CapabilityError> {
    if input.n > 100 {
        return Err(CapabilityError::Refused("n is above the limit".into()));
    }
    Ok(Out { doubled: input.n * 2 })
}

// the JSON boundary: the same handler, called the way every transport calls it
let boxed = handler::<In, Out, _>(double);
assert_eq!(
    CapabilityError::Refused("n is above the limit".into()).to_string(),
    "refused: n is above the limit"
);
// an input the type cannot read is the caller's fault, and says so
assert!(matches!(
    serde_json::from_value::<In>(serde_json::json!({ "n": "twelve" })),
    Err(_)
));
let _ = boxed;

Structs§

Context
What a handler may read: the index of the repository, the registry it belongs to, the board of peers attached to this process, and, when the call came through an MCP session, which peer made it. Index and registry are immutable for the life of a process; the board is the one thing that changes, and it lives in memory only.
Executable
A descriptor with its behaviour and its benchmark cases: what the builtin source contributes.

Enums§

CapabilityError
Why a call did not produce an output. Transport adapters map these to their own vocabularies; nothing here names a status code or a JSON-RPC code.

Traits§

Handler
The JSON boundary of a handler.

Functions§

handler
Wrap a typed function as a handler.