The callback
The problem a module has
Give a module an endpoint that transforms a document with a stylesheet. The caller passes
stylesheet=urn:file:report.xsl.
The module cannot resolve that. It has no file space, no catalog, no cache, and no capability of its own. The name means something only in the host’s kernel.
So either the host resolves every argument eagerly before dispatching — which would defeat
laziness, break Exists, and force it to know which arguments are resource references —
or the module asks. It asks.
Where the seam already was
The good news is that nothing had to be invented. An endpoint never touches the kernel
directly in the first place; it reaches it through its Invocation, which holds an
Issuer. That indirection was already there so that sub-requests could inherit the
caller’s capability and be traced.
So a module is simply handed the host as its issuer. From inside the endpoint, the call is the same one you would write in a linked-in endpoint:
/// `urn:greet:hello` — the module's one endpoint.
///
/// It takes a `name` argument that is *an IRI naming another resource*, and resolves it.
/// That resolution is the whole point of this book: the module does not own the resource,
/// cannot see it, and has no catalog of its own to find it in. `inv.source(…)` crosses
/// back into the **host's** kernel to get it.
pub fn hello() -> AsyncFnEndpoint {
AsyncFnEndpoint::new("hello", |inv: &Invocation<'_>| -> InvokeFuture<'_> {
Box::pin(async move {
let target = inv.inline_str("name")?;
let iri = Iri::parse(target).map_err(|e| Error::InvalidArgument {
name: "name".into(),
detail: format!("not a valid IRI: {e}"),
})?;
// ← THE CALLBACK. This is a resolution against the host, from inside the
// module, in the middle of the module's own invocation.
let resolved = inv.source(&iri).await?;
let who = String::from_utf8_lossy(&resolved.bytes).trim().to_string();
let greeting = format!("Hello, {who}!");
Ok(Representation::new(
text_plain_utf8(),
greeting.into_bytes(),
))
})
})
.with_description(
Description::new("hello")
.title("Greet")
.summary("Greets whoever the `name` resource resolves to.")
.verb(Verb::Source)
.verb(Verb::Meta),
)
}
/// The module's space — everything it offers, independent of any host.
pub fn module_space() -> EndpointSpace {
EndpointSpace::new().bind(Exact::new("urn:greet:hello"), hello())
}
inv.source(&iri) is the callback. The module is in the middle of its own invocation, and
that line resolves a name in the host’s kernel — with the host’s spaces, the host’s cache,
and the capability the invocation is already carrying.
That last clause matters: the module does not get authority by being a module. It borrows the caller’s, which can only narrow on the way down.
Why this is the interesting property
A remote peer is autonomous: it answers with its own resources. A module is parasitic — deliberately — it contributes endpoints while continuing to live in the host’s world.
That is what makes a module composable in a way a peer is not. The module’s endpoint can take any name the host can resolve, including endpoints from other modules, without knowing any of them exist.
It is also what makes a module harder to isolate than a peer, since the callback is a hole in whatever boundary you put around it. That tension is the subject of Where this actually stands.