pub struct Executable {
pub capability: Capability,
pub handler: Arc<dyn Handler>,
pub cases: CaseProvider,
}Expand description
A descriptor with its behaviour and its benchmark cases: what the builtin source contributes.
Fields§
§capability: CapabilityThe descriptor.
handler: Arc<dyn Handler>The behaviour, behind the JSON boundary.
cases: CaseProviderThe representative inputs, from the input type’s BenchmarkCases.
Implementations§
Source§impl Executable
impl Executable
Sourcepub fn cancellable(self) -> Self
pub fn cancellable(self) -> Self
Declare that this handler looks at its cancellation flag and stops.
The one thing about a capability that its kind cannot decide, because it is a fact
about the handler’s own code rather than about what the call means. Everything a
client does with it — whether the Cockpit offers a Cancel button, what
executions.cancel reports — is read from here, so a handler that ignores its flag
never produces a control that lies.
use majordomus_cli::capability;
use majordomus_cli::capability::{BenchmarkCases, CaseContext, Context, CapabilityError, Exposure, NamedCase, Stability};
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct In {}
impl BenchmarkCases for In {
fn benchmark_cases(_: &CaseContext<'_>) -> Vec<NamedCase<Self>> { vec![NamedCase::new("default", In {})] }
}
#[derive(serde::Serialize, schemars::JsonSchema)]
struct Out { ok: bool }
fn walk(ctx: &Context, _: In) -> Result<Out, CapabilityError> {
if ctx.progress.cancelled() { return Err(ctx.progress.cancellation()); }
Ok(Out { ok: true })
}
let e = capability! {
id: "demo.walk", title: "Walk", description: "Walks.", input: In, output: Out,
stability: Stability::Experimental, exposure: Exposure::default(), tags: [],
handler: walk,
}.cancellable();
assert!(e.capability.execution.cancellable);Sourcepub fn writes_repository(self) -> Self
pub fn writes_repository(self) -> Self
Declare that this handler writes the repository’s own tracked files.
The second thing a kind cannot decide, and for the same reason: two commands are the same kind whether one announces a peer into this process’s memory and the other stamps a field into a record a commit will carry. The difference is the whole of what a caller needs before saying yes, and every projection reads it from here — the Cockpit’s confirmation, and the exposure ceiling that decides which surfaces may reach it at all.
Declaring it is the handler author’s obligation, not an optimisation. A handler that writes a tracked file without saying so is projected as though it did not, onto surfaces whose ceiling exists to exclude exactly that.
use majordomus_cli::capability;
use majordomus_cli::capability::{BenchmarkCases, CapabilityKind, CaseContext, Context, CapabilityError, Effect, Exposure, NamedCase, Stability};
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct In {}
impl BenchmarkCases for In {
fn benchmark_cases(_: &CaseContext<'_>) -> Vec<NamedCase<Self>> { vec![NamedCase::new("default", In {})] }
}
#[derive(serde::Serialize, schemars::JsonSchema)]
struct Out { ok: bool }
fn write(_: &Context, _: In) -> Result<Out, CapabilityError> { Ok(Out { ok: true }) }
let e = capability! {
id: "demo.write", kind: CapabilityKind::Command, title: "Write", description: "Writes.",
input: In, output: Out, stability: Stability::Experimental,
exposure: Exposure::default(), tags: [], handler: write,
}.writes_repository();
assert_eq!(e.capability.execution.effect, Effect::RepositoryMutation);Sourcepub fn skill(self, alias: &str, argument: &str) -> Self
pub fn skill(self, alias: &str, argument: &str) -> Self
Declare the short name a person invokes this by from an agent’s prompt, and the
input field the text typed after it fills. See crate::capability::SkillExposure;
the registry refuses a name that is malformed, reserved or taken, and an argument
the input does not have.
use majordomus_cli::capability;
use majordomus_cli::capability::{BenchmarkCases, CapabilityKind, CaseContext, Context, CapabilityError, Exposure, NamedCase, Stability};
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct In { text: String }
impl BenchmarkCases for In {
fn benchmark_cases(_: &CaseContext<'_>) -> Vec<NamedCase<Self>> { vec![] }
}
#[derive(serde::Serialize, schemars::JsonSchema)]
struct Out {}
fn say(_: &Context, _: In) -> Result<Out, CapabilityError> { Ok(Out {}) }
let e = capability! {
id: "demo.say", kind: CapabilityKind::Command, title: "Say", description: "Says.",
input: In, output: Out, stability: Stability::Experimental,
exposure: Exposure::default(), tags: [], handler: say,
}.skill("say", "text");
let skill = e.capability.skill.unwrap();
assert_eq!((skill.alias.as_str(), skill.argument.as_str()), ("say", "text"));Sourcepub fn authenticates_its_input(self) -> Self
pub fn authenticates_its_input(self) -> Self
Declare that the input is a signed message the handler verifies before it changes
anything, so a caller on another host is admitted by its key rather than refused by
its address. See crate::capability::ExecutionPolicy::authenticates_its_input.
use majordomus_cli::capability;
use majordomus_cli::capability::{BenchmarkCases, CapabilityKind, CaseContext, Context, CapabilityError, Effect, Exposure, NamedCase, Stability};
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct Signed {}
impl BenchmarkCases for Signed {
fn benchmark_cases(_: &CaseContext<'_>) -> Vec<NamedCase<Self>> { vec![NamedCase::new("default", Signed {})] }
}
#[derive(serde::Serialize, schemars::JsonSchema)]
struct Out { ok: bool }
fn hello(_: &Context, _: Signed) -> Result<Out, CapabilityError> { Ok(Out { ok: true }) }
let e = capability! {
id: "demo.hello", kind: CapabilityKind::Command, title: "Hello", description: "Verifies a signed hello.",
input: Signed, output: Out, stability: Stability::Experimental,
exposure: Exposure::default(), tags: [], handler: hello,
}.authenticates_its_input();
assert!(e.capability.execution.signed_input && e.capability.execution.admits_remote());
assert_eq!(e.capability.execution.effect, Effect::ProcessState);