Compatibility: Pactmark 0.1.x.
An Approval in Pactmark is not free-form text, a boolean column, or a Slack thumbs-up. It is a record bound to a tenant, a run, an effect, a policy, a grant, a scope and an expiry — created by atomically consuming a one-use proof.

The flow

Figure 1. The challenge is issued only after the actor is authenticated now, and it is consumed atomically together with the authorisation and credential reservations.

What the approver must actually see

A preview that says “the agent wants to send a message” is not a decision aid. Pactmark’s EffectPreview is a schema with required fields:
string
required
The canonical target — not the raw string the model produced.
string
required
What class of operation this is, independent of its arguments.
Digest
required
The digest of what will actually be sent, so the preview cannot drift from the payload.
compensatable | irreversible
required
Note that not_applicable is not an option here. If you are asking a human, it changes something.
string
required
A plain statement of what happens in the world if this is approved.
Digest
Optional, for effects that modify existing state.
Digest
required
The identity of the preview itself, so the decision binds to what was shown.

The one-use proof

1

Authenticate first

The host authenticates the actor at decision time. A session from an hour ago is not the same thing as authentication now.
2

Issue the challenge

issueDecisionChallenge returns an opaque challengeProof and an expiresAt. Submit it directly to the decision command.
3

Consume atomically

The runtime consumes the proof into a decision together with the authorisation and credential reservations, in one transaction.
4

Replay returns the same result

The same command replayed returns the same semantic result. Different bindings conflict rather than silently succeeding.
Never copy a challenge proof into a model prompt, a URL, a log line, an analytics event or an evidence record. The stable records retain only keyed digests and bindings — keep it that way in your own code too.

What approval can never waive

This list is enforced, not advisory. An approval that arrives is still subject to all of it:

Authentication

An unauthenticated decision is not a decision.

Tenant isolation

An approver in one tenant cannot approve another tenant’s effect.

Missing grants

Approval does not create capability. A denied tool stays denied.

Schema and budget enforcement

An approved call still has to validate and still has to fit the budget.

Secret boundaries

Approval does not resolve a credential into anything the model can see.

Egress policy

An approved effect still cannot reach an undeclared origin.

Uncertain-effect rules

Approval cannot convert “we don’t know” into “it didn’t happen”.

Role and strength requirements

A wrong-role or weak-authentication decision dispatches nothing.
Rejected and expired decisions produce zero effect dispatch. That is asserted directly in the test suite as a zero-dispatch counter, not inferred from the absence of an error.

Human input is a different thing

Do not confuse waiting_for_approval with waiting_for_input: InputRequested carries a safePrompt — a string intended for a human, already separated from the model’s raw output.

What a recorded approval proves

It proves that a named authenticated principal, holding a named role, consumed a one-use challenge bound to this exact effect preview at a recorded time. It does not prove that the human understood the preview, that the preview was a good summary, or that the decision was correct. Threat model row TM-05 states this explicitly: human misunderstanding of an accurate preview remains possible.

Approval integration guide

Building the operator-facing flow, including what not to render.

Customer refunds use case

A complete worked example with an irreversible effect behind an approval gate.