Human approval checkpoints

Create the proposal before requesting approval

Save a complete proposed change, render the review from that record and execute from the same record. This removes a common gap between what people see and what systems submit.

In this article

Give the proposal its own record

Create a server-owned proposal containing the operation type, target identifier, material payload and relevant target version. Record who requested it and the authority context needed for later checks. Assign a proposal identifier before opening the approval screen.

Use explicit states such as draft, awaiting review, approved, rejected, expired and executed. Define allowed transitions in application code. A client should not be able to set the state to approved by updating a general-purpose record endpoint.

Once submitted for review, treat material changes as a new proposal revision. This keeps the review tied to stable information and makes the history understandable.

Render from saved values

Load the proposal on the server and show the reviewer the actual target and changes. For an address update, show the order identity, current address and proposed address. Include any uncertainty the agent could not resolve.

JSON example
{
  "proposalId": "proposal-example-31",
  "operation": "changeDeliveryAddress",
  "targetId": "order-example-88",
  "targetVersion": 7,
  "revision": 1,
  "state": "awaitingReview"
}

The payload fields are omitted here for brevity, but the stored record must contain them. A hash can help bind a decision to canonical data, yet it does not replace retaining the readable proposal or validating the server-side state transition.

Accept a decision through a dedicated endpoint

The decision request should identify the proposal and revision. The server loads the proposal, verifies the reviewer's authority and confirms it remains eligible for review. Record approval or rejection with the actor and time.

Handle concurrent decisions deliberately. If two reviewers act at once, an atomic state transition should enforce the workflow's rule. Do not allow a late approval to overwrite a rejection unless the product explicitly supports that transition with a new review cycle.

Set expiry according to the operation's consequence and expected delay. Expiry should be checked by the executor, not only by hiding a button in the interface.

Execute the saved operation

The executor loads the approved proposal, rechecks current eligibility and submits the exact approved values. Associate execution with a durable operation identifier so retries do not create a new business effect.

If a target change invalidates the proposal, move it to a state requiring review and explain why. Do not silently repair the payload and retain the old approval. Test the whole sequence with changed fields, stale target state, an unauthorised reviewer and a lost execution response. The records should explain every outcome without relying on the model's memory.

Primary sources

OWASP: transaction authorisationPostgreSQL: explicit locking

References checked 11 September 2026.