Compatibility: Pactmark 0.1.x.

The sequence you are implementing

Figure 1. Your application owns the two middle lanes. Get the ordering right and the security properties come along for free; get it wrong and no amount of runtime enforcement saves you.

1. Detect that a decision is needed

Watch the event stream for ApprovalRequested, or read RunProjection.waitingDecisionId:
The event carries digests, not content. Fetch the preview through the authenticated inspection API rather than trusting anything that arrived over a stream.

2. Render a preview a human can actually act on

Render normalizedTarget, never the raw model output. The normalized value is what will actually be used; showing anything else means the human approved a different thing from the one that happens.

3. Authenticate the actor now

Not “is there a session” — authenticate at decision time, with the strength your policy requires for this risk class. AuthenticationStrength and DecisionRole travel in the authority context, and a weak-authentication or wrong-role decision dispatches nothing.

4. Issue and submit the challenge

Never do this

Persist the proof, put it in a query string, include it in an email, log it at debug level, or let it enter model context.

Do this instead

Hold it in memory for the length of one request, submit it, and let it expire everywhere else.
The reference nextjs-vercel fixture keeps proofs in a short-lived server-side challenge vault rather than round-tripping them through the browser. That pattern is worth copying.

5. Handle rejection explicitly

ApprovalRejected carries a reasonCode and a nextStatus of planning, failed or cancelled. Rejection is a first-class outcome with its own path, not an error.

Idempotency and replay

Every command carries a CommandContext with a commandId. Replaying the same command returns the same semantic result. A command with the same id but different content conflicts with KAF_HTTP_IDEMPOTENCY_CONFLICT rather than silently overwriting. This matters most exactly here: a double-clicked approve button must not become two approvals.
KAF_COMMAND_IDEMPOTENCY_EXPIRED means the idempotency horizon has elapsed — the command is too old to be safely deduplicated. Treat it as “ask the human again”, not as “retry harder”.

What to test

The zero-dispatch assertion is the important one. Asserting that an error was thrown is weaker than asserting that the external target was never called.

What you are still responsible for

Pactmark binds the decision. It cannot ensure the human understood the preview, that the preview was a fair summary, or that the desk limit in your policy is the right number. Threat model row TM-05 records that residual risk explicitly.

Human decisions concept

What an approval binds, and the eight things it can never waive.

Refund use case

The same flow in a complete, opinionated scenario.