Expand description
An HTTP surface for the people who have to look after a plane.
§The rule this module is shaped by
Who is acting comes from the request’s identity, never from its body.
Four-eyes is enforced in TaskStore::claim,
which takes an actor and a set of roles. Inside the process those come from
the embedder’s own code. Over HTTP they would come from whoever is on the
socket — and a reviewer who can name themselves can name the person who
proposed the action, which is the exact control four-eyes exists to be.
Discipline is not enough for that. So the wire types here have no actor
field: DecisionRequest carries a verdict, a reason, and an optional
amendment. A caller cannot spoof what they cannot express, and a later
handler cannot be talked into reading it, because there is nothing to read.
Roles work the same way. They come from Caller::roles, which the
Authenticator produced, so a caller cannot grant themselves eligibility
for a queue they are not on.
§Two gates, both mandatory
Authentication says who. It does not say what they may do, and an
operator surface that stops at authentication grants every authenticated
caller the whole plane. So every route also passes through the answering
plane’s PolicyEngine with an api: action,
and Api::new refuses to build if any plane has none — one ungoverned
tenant among governed ones is the one an attacker looks for.
That refusal is the point. Inside the process an absent engine is a choice —
the caller is the embedder. On a socket it is a hole, and the failure mode of
a permissive default is that nobody finds out until the port is reachable.
DenyAll exists for wiring the surface up before its
rules are written; starting closed and opening deliberately is the order that
fails safe.
§One surface, many tenants
Api::new takes Planes — a registry keyed by tenant — so one process
can serve several. A single-tenant deployment passes its runtime.
Which plane answers comes from Caller::tenant, which the
Authenticator derives from the credential like actor and roles. That
is the field selecting a store, so a body-supplied one would be a
cross-tenant read with an authentication step in front of it.
The gate hands each route the resolved plane with the caller, and this
struct holds no runtime of its own. A handler therefore cannot read a store
without having established whose it is, and every lookup on the registry
names a caller rather than a tenant — so the tenant a route can reach is
the one its credential resolved to, and reaching another is
Planes::cross, which records the crossing first. A caller whose tenant
has no plane is refused, never served by a default — a fallback would turn
an unregistered tenant into somebody else’s data, and it would look like
working software.
§The authenticator is the deployment’s
Same reasoning as the policy engine and the tracing exporter: the deployment
owns its identity system, and a bearer-token parser baked in here would be
wrong for the mutual-TLS deployment and load-bearing for the other. What this
crate owns is the shape — every route runs behind
Authenticator::authenticate, and there is no route that does not. The
one implementation shipped, tokens, exists for the binary, which cannot
ask its operator to write one.
§What an operator actually needs
The endpoints come from the questions a person asks at three in the morning, not from the crate’s type graph:
- What is this run doing, and why is it not finishing? — a suspended run reports what it is waiting for, because “suspended” alone sends someone into the journal.
- And what did it do? — the journal itself, cursored, under its own verb. The status view answers from a few fields; this answers with the run’s inputs, its model exchanges and every argument it sent, which is a different grant to make.
- What is waiting for me? — the worklist, filtered to the caller’s roles, with each item saying whether this caller may decide it and who has already reserved it.
- This one is mine; don’t let a colleague duplicate the work. — claim, and release again if it turns out not to be theirs after all.
- Let me approve this. — with four-eyes intact.
- This message arrived; wake whoever wanted it. — event delivery.
- Stop it. — the other half of oversight, and the one most surfaces omit.
- What has happened on this matter? — the case, its deadlines, its tasks.
Every cursored list reads one more row than the page. That extra row is
the only thing that distinguishes a full page from an overflowing one:
inferring truncation from len() == limit calls a queue of exactly the
limit truncated, and a backlog of 140 shown as 100 reads as a backlog of
100. It also bounds the read — a cursored endpoint that loaded the whole
remainder per page would do work proportional to the total for every page.
There is no /health and no /metrics. The embedder owns the port and the
process; a liveness probe answered by this router would be one more route
that must not authenticate, and the one route that skips the gate is the one
that eventually grows a feature.
§Wiring it up
// `Api::new` fails here if the runtime has no policy engine.
let router = Api::new(runtime, Arc::new(MyAuth))?.router();
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
axum::serve(listener, router).await?;Modules§
- a2a
- Serving A2A: this plane, as an agent other agents can call.
- action
- Actions this surface asks the policy engine about.
- dev
- A page for trying an agent, on the author’s own machine.
- openapi
- The operator API as an
OpenAPI3.1 document. - tokens
- Bearer tokens mapped to callers, from a file the operator writes.
Structs§
- Acknowledge
Request - An account of a missed obligation, on the wire.
- Actor
View - A person (or a channel) on a run’s record, with what established the name.
- Api
- Everything the routes need.
- Caller
- Who is on the other end of the request.
- Cancel
Request - What a stop request looks like on the wire.
- Dead
Letter View - A message that arrived and reached nobody.
- Decision
Request - What a decision looks like on the wire.
- Lift
Halt Request - Lifting it.
- Parked
Push View - A webhook registration a delivery worker gave up on.
- Place
Halt Request - Throwing the emergency stop.
- Place
Hold Request - Placing a legal hold on one matter.
- Planes
- The tenants this process serves, one plane each.
- Quarantine
Request - Answering a quarantine.
- Rearm
Request - Naming one webhook registration.
- Reconcile
Request - Asserting what happened to one effect the runtime could not decide.
- Release
Hold Request - Lifting one.
- RunView
- A run, to somebody working out why it has stopped.
- Task
View - A task as an operator sees it.
- Worklist
- One page of the worklist.
Enums§
- ApiSetup
Error - Why the surface could not be built.
- Auth
Error - Why a request has no usable identity.
Traits§
- Authenticator
- Establishes who is calling.
Functions§
- policy_
problems - Whether
enginecan evaluate every request the operator API puts to it.