Skip to content
Back to Blog

Approval Gates in Code: The Destructive-Mode Handshake

Published Updated Editorial policy & corrections

Share:XBSMRedditHNEmail
Approval Gates in Code: The Destructive-Mode Handshake illustration

An approval must authorize the action the approver actually reviewed: its actor, recipient, amount, arguments, evidence and current policy. A button that sends only an evidence hash leaves too much of that action unbound.

Correction, October 16, 2026: the original example used a top-level JSON replacer that could discard nested evidence, checked a caller-supplied redemption window and did not compare every stored proposal/request identifier. Those examples have been replaced with tested reference helpers. The original publication date and URL are retained. The earlier claim that rehashing immutable snapshots detects a changed real world was also incorrect: freshness needs a separate current observation.

This article uses ContextOS’s deterministic TypeScript reference. It validates trusted stored records and already verified broker observations. It does not supply a signing service, approval UI, credential broker or atomic database. Governance defines the broader boundary; Runtime API Schemas distinguishes compatible base envelopes from additive profile attachments.

Bind the complete action

ActionBinding includes the contract, run, tenant, stable operation and proposal IDs; actor and authority references; capability revision; complete JSON arguments; resources and destinations; immutable evidence; pinned policy, profile and release artifacts; native ActionRisk; and any amount in explicit currency minor units.

digestActionBinding uses the exact wrapper { domain: "contextos.action_binding.v1", binding } and complete RFC 8785 canonical JSON. Object key order does not change the digest. Array order and every nested value do. Invalid JSON values, accessors, cycles and malformed Unicode reject rather than being omitted or coerced. Byte parsers must reject duplicate keys before constructing an object.

The human-readable approval view must show the material action and data movement, relevant evidence and remaining validity. An opaque digest is useful for binding and inspection; it is insufficient informed consent by itself. Changing an amount, recipient, evidence snapshot or policy revision creates a new proposal and approval request.

Store the request before accepting a decision

The trusted store supplies StoredActionProposal and ApprovalRequestRecord. Both identify the request, proposal, run, tenant, operation, full action digest and pinned policy/grant references. The request adds eligible approvers and roles, a distinct-principal quorum, separation-of-duties policy and a validity interval.

Each ApprovalBinding must agree with those stored identifiers and revisions. A deployment verifies the signed decision with the correct identity/key, binds that verification to the complete decision digest and supplies current entitlement observations. A role string sent by a browser cannot establish entitlement. Denied, unverified, future or expired decisions block; repeated approvers do not satisfy a quorum.

Validity is the stored half-open interval [not_before, expires_at): expiry itself is outside the interval. A decision cannot extend its stored request’s window, and a redemption caller supplies no replacement window. Current grant revocation remains an independent authorization check even after a valid approval.

Run the reference checks

The complete function below accepts a synthetic ApprovalValidationInput; production obtains that input from trusted brokers and stores. The companion test supplies the fixed fixture from tests/fixtures/execution-contract.ts, executes this marked fence and exercises the actual imported helpers. Run npx vitest run tests/approval-tutorial.test.ts from the repository.

import { digestActionBinding, validateApprovalBinding, transitionApprovalRedemption } from "@/lib/contextos/authorization";
import type { ApprovalValidationInput } from "@/lib/contextos/authorization";
 
function runApprovalExample(
  input: ApprovalValidationInput,
  scenario: "valid" | "nested-change" | "wrong-proposal" | "stored-expiry" | "same-operation",
) {
  const candidate = structuredClone(input);
  const before = digestActionBinding(candidate.binding);
  if (scenario === "nested-change") {
    candidate.binding.arguments.details = { amount_minor: 4201, shipped: true };
  }
  if (scenario === "wrong-proposal") candidate.proposal.proposal_id = "proposal_other";
  if (scenario === "stored-expiry") candidate.evaluation_time = candidate.request.expires_at;
 
  const validation = validateApprovalBinding(candidate);
  let intent = transitionApprovalRedemption({
    approval: candidate, expected_revision: candidate.redemption_state.revision,
  });
  if (scenario === "same-operation" && intent.status === "redeem") {
    // Simulate the trusted atomic store's persisted state, not a second dispatch.
    candidate.redemption_state = intent.state;
    intent = transitionApprovalRedemption({
      approval: candidate, expected_revision: candidate.redemption_state.revision,
    });
  }
  return {
    digest_changed: before !== digestActionBinding(candidate.binding),
    allowed: validation.allowed,
    reason_codes: validation.reason_codes,
    redemption_status: intent.status,
    permits_new_effect: intent.permits_new_effect,
  };
}

The unchanged useful case validates and produces a redeem intent. Nested changes alter the digest and invalidate the old approval. Wrong-proposal and exact-expiry cases reject. Recovery by the same operation returns existing; another operation cannot consume the approval.

Both redeem and existing explicitly return permits_new_effect: false. The first is a proposed state transition with an expected revision; the second is recovery of a prior association. Neither proves a database transaction occurred or permits a duplicate external effect.

Implement the production boundary

The following sequence is integration pseudocode. The reference test does not exercise its signing, storage, network or provider behavior.

1. Authenticate the workload and any required owner mandate.
2. Load the immutable proposal and approval request from the trusted store.
3. Verify signed decisions, current approver entitlements and grant revocation.
4. Evaluate exact approval binding and every independent required control.
5. Atomically persist approval redemption for operation_id using expected_revision.
6. Coordinate the durable operation record, budget reservation and worker fence.
7. Dispatch only the already bound action through the mediated adapter.
8. Record the external receipt or preserve an unknown outcome for reconciliation.

The atomic transaction must prevent two workers from consuming one approval for different operations. Recovery of the same operation must consult its durable effect state. A crash or timeout after dispatch may leave the effect committed even when no response arrived; blind retry is unsafe. Compensation is a separately authorized effect and can fail.

Frozen evidence and current facts are different checks

An immutable evidence snapshot keeps the same digest when the world changes. Rehashing it proves the same bytes were reviewed; it cannot reveal that an order shipped later. The adapter needs a separately verified current precondition, with a policy-defined freshness bound and a provider-side version/conditional update where available. If freshness, ownership or postcondition verification is unavailable, keep the action blocked or preserve uncertainty according to policy.

Historical signature verification also differs from current permission. Retained historical keys may help audit an old receipt. A revoked grant or disallowed key cannot authorize a new dispatch merely because an old signature remains mathematically valid.

Found this useful? Share it.

Share:XBSMRedditHNEmail

Continue through the same topic without returning to the index.

View the series