Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

Bound approvals

An approval of a governed action is bound to a digest of the exact request. Anlyon recomputes the digest before dispatch and refuses on any difference.

An approval of a governed action is not an approval of “a refund” or “an email”. It is an approval of one preview: the exact normalized request, the resolved target, the action and adapter versions, the preconditions observed at review, and an expiry. Anlyon hashes those together into a bindingDigest and stores it on the approval. Immediately before dispatch it recomputes the digest from the persisted effect. Any difference refuses the dispatch.

This applies to every governed action. That means an action with an adapter (stripe.refund, github.file_update, resend.email_send) and an action that carries a declaration (impact, verify or governed: true). The preview is always on. It is not a field you set, and it has no opt-out.

What a preview shows

A preview is typed data, never provider HTML. Its blocks are:

  • fields: the environment (name and kind), target account, resource, action version, adapter, what success means, when the evidence was observed, and when the preview expires.
  • text_diff: before and after text, for file changes.
  • impact: the quantities the operation consumes. allowances shows each applicable limit at review time.
  • limitations: each guarantee labelled for what it is: guarantee, freshness, unsupported or irreversible.
const input = { path: 'data/status.txt', content: 'status: green\n', message: 'Mark green' };
const { data: preview } = await agent.actions.preview('update-fixture', input);

for (const block of preview!.blocks) console.log(block.type, block.title);

// Send the same input. preview.request is the normalized request, not the input.
await agent.actions.invoke('update-fixture', input, { previewId: preview!.id });
preview = agent.actions.preview(
    "update-fixture",
    {"path": "data/status.txt", "content": "status: green\n", "message": "Mark green"},
).data

agent.actions.invoke("update-fixture", {"path": "data/status.txt", "content": "status: green\n", "message": "Mark green"},
                     preview_id=preview["id"])

A governed invocation gets a preview whether or not you create one beforehand. A gated invocation’s approval shows it in the dashboard.

The preview of a declared action

Anlyon has no adapter for the API behind a declared action, so the preview is the request itself. It is built from the action definition and the input. The provider is not read, and nothing is written.

The request block shows the method, the URL, each header, the body and the read-back. Secret references stay as {{secret:NAME}} placeholders, so the reviewed request and its digest never hold a credential.

{
  "adapter": { "type": "http.declared" },
  "impact": [{ "dimension": "emails", "amount": 1, "bound": "exact" }],
  "request": { "headers": { "Authorization": "Bearer {{secret:MAILER_TOKEN}}" } }
}

What each kind of action guarantees

All four execute exactly the reviewed request. They differ on stale-state protection, and the preview says so:

Exact request Stale-state protection
github.file_update Yes Atomic. The write sends the blob SHA shown at review, and GitHub refuses it (409) if the file changed since. The effect is failed with the stale version recorded as evidence, and the newer content is not overwritten.
stripe.refund Yes Not atomic. The adapter sends no compare-and-set on a payment intent. The account is re-checked immediately before dispatch, and the adapter relies on Stripe to refuse a refund above what remains refundable. The refundable amount shown is as of review. The preview labels this freshness.
resend.email_send Yes None. As of 2026-10-03, Resend has no conditional send. Nothing about the recipients or the domain is checked atomically with the write.
A declared action Yes None. Anlyon does not know the API’s preconditions. The provider’s state is not checked between review and dispatch.

What invalidates an approval

Three different mechanisms can stop a reviewed operation. They are not the same thing, and the receipt says which one acted.

The request changed. An invocation that names a previewId and sends a different destination, resource, amount or content is refused at admission with 409 PREVIEW_MISMATCH. No effect is created and nothing is sent.

Anlyon’s own state changed. Dispatch is refused, with nothing sent, and the receipt grades refused when:

  • the action was edited after review (action_changed)
  • the preview expired (preview_expired). An approval never outlives its preview. Invoking with an expired previewId is refused earlier, with 409 PREVIEW_EXPIRED.
  • the approval itself expired before anyone decided (approval_expired)
  • a secret the action references is missing, or its host or placement binding no longer allows the request (credential_unavailable)
  • the environment is halted, the requesting key was revoked, or a policy now denies
  • the impact limits no longer have room, even if they did at review
  • the stored preview, effect and approval no longer agree on the digest (binding_mismatch)

The provider’s state changed. This is caught by the provider, where the provider has a conditional write. For GitHub, the write carries the reviewed blob SHA and GitHub refuses it. The receipt grades failed, not refused, because Anlyon did send the request.

Duplicate approval callbacks cannot run the operation twice. Consuming the approval and claiming dispatch are one conditional database write.

Refreshing a preview (refreshPreview) re-reads the provider into a new preview that supersedes the old one. Its binding is different, so an approval of the old preview never carries over.

See When approval expires or changes.

How it behaves

  • The approval binds the exact request. For stripe.refund the refundable amount shown is as of review, and the adapter re-checks the account before dispatch.
  • A change at GitHub is caught by GitHub. The write carries the reviewed blob SHA, GitHub refuses a mismatch, and the receipt grades failed.
  • The preview of a declared action shows the request. The provider is not read at review, so the preview describes the request and not the resource it will change.
  • The dispatch-time digest refusal guards stored state. binding_mismatch fires when Anlyon’s stored preview, effect and approval disagree. A changed request is refused earlier, at admission, with PREVIEW_MISMATCH.
  • An action with no declaration and no adapter is approved on its request snapshot. See Approvals and policies.
  • A credential is checked again at dispatch. A secret revoked or rebound after review refuses the dispatch.
  • Any workspace member can decide an approval.
  • A preview expires. An approval given after the expiry does not run the operation.

Was this page helpful?