approvals
v0.1.0-selfupdate.12
Durable approval authority, inbox feeds and thread projection outbox for Bee
Run
wippy run bee/approvalsBee approvals
The approval owner for one node. A request binds an exact proposal, its
canonical digest, the requesting operation owner and a host-selected approver
policy; a decision settles the pending revision by compare-and-set for that
digest; the requester consumes an approved decision under one effect identity
before acting. Every change commits its history row, its inbox change and,
when the request projects onto a thread, its outbox row in the same
transaction. The worker delivers outbox rows through the narrow thread
ingress bee.threads.binding:append under a stable event id, and
acknowledges a row only after the ingress replies; a lost acknowledgement
repeats the delivery and the thread replays the same record.
A transition record states the outcome but owes nobody anything, and only a
message commit creates the recipient obligation the delivery layer carries. So
every terminal change of a thread-bound request enqueues a second outbox row
beside its transition, under <approval_id>:<revision>:notice: a
notification addressed to the requester naming the outcome and the approval.
A denial, an expiry and a withdrawal are announced as an approval is, because
what leaves an agent waiting is the silence rather than the answer. The notice
is a side effect of the decision and never a condition of it: a thread that
refuses it is retried, and an exhausted row stays visible with its last error
while the decision it announces stands.
States run pending to decided, expired or withdrawn; the thread
projection records them as approved/denied, expired and cancelled.
Expiry is enforced by the owner when a decision or withdrawal arrives and by
the worker on every pass. Delivery retries and retention are separate from
request lifetime: an exhausted delivery stays visible and a manager returns it
to the queue; settled requests are forgotten after the retention window
together with their deduplication receipts.
The authority service starts bee.approvals.service:authority, which registers
the stable owner name bee.approvals.authority before any request is served;
a restart is a new incarnation. An effect owner presents the incarnation it observed; an
observation of an older authority, or a decision made under one, returns
REVALIDATE naming the current incarnation. The effect owner re-checks the
decision in its own domain and calls revalidate, which records the
validation under the current incarnation; only then does consume proceed.
Another restart invalidates that validation again. The
delivery worker holds its own lease name and never touches the incarnation.
A request that projects onto a thread is bound when it is made: the requester
must be an active owner or participant, an attempt proposal must name an
attempt the thread prepared under its action, and the binding is persisted so
delivery never depends on later membership. Consumption belongs to the effect
owner holding bee.approvals.consume, bound to the exact proposal digest and
one effect key. Retention forgets a request only after its lifetime plus the
retention window, with every delivery acknowledged; an idempotency key older
than that horizon creates a fresh request.
The host-authorized Hub installation worker reads its bounded queue through
bee.approvals.binding:installation_effects, which returns approved installation
requests whose effect result is not complete. After Hub returns an applied or
terminal result, the requesting attempt records its bounded status outcome through
bee.approvals.binding:complete_installation_effect. The operation checks the
requester, workspace consume authority, proposal digest and consumed effect key;
an identical completion replays and a different result conflicts. A worker
restart can therefore submit an already-consumed request again until the Hub
outcome is recorded, without reading the approvals table from the gateway store.
Hub retains the complete receipt, including migration details; the approval
owner stores only the bounded state and message needed for status.
The host-authorized Hub publication worker reads its bounded queue through
bee.approvals.binding:publication_effects, which returns approved publication
requests whose effect result is not complete, and records its bounded outcome
through bee.approvals.binding:complete_publication_effect under the same
requester, digest and effect-key checks. A committed decision wakes the
publication worker, so an approved publication uploads without any status
poll.
Approver policies are host-owned under bee:approver_policies:
each names its approvers and the longest lifetime a request may ask for. An
approver needs both the bee.approvals.decide action on the workspace and a
place in the policy. Workspace membership alone exposes nothing. Approvals are
node-local: decide and withdraw are never Hive-exposed, so a mapped remote
principal reads the feed and a request but cannot decide one, and the host
ceiling names only the feed, read and replica operations.
| Slice | Responsibility |
|---|---|
| root bee.approvals | Contract, stable local binding, linked host references, default database, linked database and host-policy readers, and the owner domain library |
| binding/ | Callable approval operations, including the host-authorized installation effect queue and completion, and the Hive policy operations |
| persist/ | Approval request, history, inbox, incarnation and thread-projection outbox storage |
| migrations/ | Immutable approval schema ledger |
| service/ | Authority and outbox worker processes |
Approval views expose requesting_session when the authenticated requester is a SessionRef. The read-only bee.approvals.binding:attention_count accepts {workspace_id} and returns {ok=true,value={count=N}} for pending, unexpired requests. It requires the exact bee.approvals.attention grant for that workspace and provides neither request details nor decision authority.
The Approvals owner exposes runtime approval leases through bee.approvals.binding:runtime_lease (also local.runtime_lease). Its operations are grant, check, use, revoke, list; requests carry operation, lease_ref?, workspace_id?, tool?, input_digest?, effect_key?. An ordinary permission approval with operation proposal ref bee.approvals:runtime-lease carries {subject, workspace_id, tool, input_digest, expires_ms, max_uses}. The digest is lowercase SHA-256; expiry is within 30 days and uses are 1..10000. Grant consumes that exact approved proposal, revalidating its owner incarnation after a restart. Approval migration 5 stores leases and per-effect receipts in the Approvals ledger.
Check/use require consume authority and the exact subject/workspace. Use additionally checks tool/input digest, expiry, revocation and the use bound; the same effect key replays only the same exact operation. Persisted runtime authority survives an owner restart. Subject or workspace manager may revoke; list exposes only the caller's records in one workspace. Saved profile references cannot transfer authority. The shared permission exchange uses matching references before requesting another decision and rechecks the same receipt before dispatch/recovery; Deny still wins.