AuthRail

Execution and recovery

Find out whether an action happened before trying it again.

Your application asks a provider to make a payment. The request times out. Did the payment fail, or did the provider complete it and lose the response? Trying again immediately could pay twice.

Use this guide when an application crashes, a provider response is lost or an Action has an unknown outcome. The first task is to establish what happened to the original request.

AuthRail keeps the permission decision and the application's execution reports separately. An allowed decision tells you the request had permission. The application must check the provider or its own saved business records to establish the effect.

Investigate the original request

The developer or operator responsible for the protected application should:

  1. Stop automatic retries for this request. Keep its saved request, Action and execution-attempt identifiers. Keep the original arguments and provider request key too.
  2. Open its Action in AuthRail. Read the request decision and execution reports. An attempt marked started may have reached the provider.
  3. Look up the original operation. Use the trusted provider account or the application's saved effect record. Match the request key, resource and relevant amount or other business details.
  4. Report the established outcome through the SDK. Report success when the source confirms the effect. Report failure only when the source confirms that no effect occurred.
  5. Leave it unknown if the source cannot establish either answer. Keep the request for an operator to investigate.

This is called reconciliation: checking the original effect and updating its reported outcome. A timeout, HTTP error or the agent's recollection is not enough to establish that nothing happened.

Expected result: the same Action has an established outcome or remains explicitly unknown. The investigation itself does not repeat the payment.

Save the attempt and report what you establish

The code below continues the Enforce setup in the SDK guide. client, operation and payment come from your protected application. The operation keeps the original reviewed version, authenticated identity and stable business Action key. Run reconciliation in a worker that exclusively claims that task.

recoveryStore / recovery_store is an application-owned helper: encrypt and persist the recovery context, binding it to the original organisation, user, deployment and task. Its load function must check the same binding. Save the context before calling the provider. Keep the original payment arguments and business request with it; do not reconstruct them from agent messages.

provider.pay / Pay performs your actual protected operation using the SDK's idempotency key. lookupOriginal / LookupOriginal / lookup_original must query that same trusted provider account and original request. It returns a validated state (succeeded, failed or unknown) and a bounded provider reference. Here failed means the lookup confirmed no effect, not a timeout. These provider and storage methods are your code, not AuthRail SDK APIs.

import { EnforceOutcomeUnknownError, type ExecutionContext } from '@authrail/sdk-node';

async function executePayment() {
  try {
    return await client.enforce(operation, payment, async (checked, execution) => {
      await recoveryStore.saveEncrypted(binding, JSON.stringify(execution));
      return provider.pay(checked, execution.providerIdempotencyKey);
    });
  } catch (error) {
    if (error instanceof EnforceOutcomeUnknownError && error.recovery) {
      try {
        await recoveryStore.saveEncrypted(binding, JSON.stringify(error.recovery));
      } catch {
        console.error('Recovery storage unavailable; keep this task unresolved');
      }
    }
    throw error; // Keep the task unresolved; do not pay again here.
  }
}

async function reconcilePayment() {
  const recovery: ExecutionContext = JSON.parse(await recoveryStore.loadEncrypted(binding));
  const found = await provider.lookupOriginal(recovery.providerIdempotencyKey);
  if (found.state === 'unknown') return; // Leave the saved task for investigation.
  await client.reconcile({ ...recovery,
    providerReference: found.reference, outcome: found.state });
}

A recovery-storage failure must also keep the task held for investigation, not mark it failed or authorize another attempt.

A returned reconciliation receipt updates the original attempt; it does not repeat the payment. Update your task after checking that response. An unknown lookup stays unresolved, even though the example worker returns without error. A result-blocking error can also mean an effect exists: inspect and reconcile that original effect instead of treating every SDK error as a failed payment.

Retry only after confirming no effect

A confirmed no-effect reconciliation can allow another execution attempt. The application still needs a fresh decision, any required approval, and fresh business facts used by the rule.

Keep the same business arguments and provider request key. Keep the same AuthRail Action key—the value your application assigned to this business request. Changing that key creates a different request; it does not resolve the uncertainty around the first one. Do not reuse an Action key with different arguments.

A provider idempotency key is a request key it can use to recognize a repeat of the same operation. Pass the SDK's provider key to services that support it, and keep it in your application's saved records. Confirm the provider's own idempotency behavior before relying on it to prevent duplicate effects.

More about saving recovery information

The protected application keeps provider credentials and performs the business function. Before that function runs in Enforce, the adapter sends a signed started receipt to AuthRail. Your application must also save the recovery context with its business request so it survives a process restart.

Keep the Action ID, attempt ID, original input, provider idempotency key and any approval permit reference. The Enforce callback receives a provider key of the form ar-<Action ID>. Use the recovery context supplied by your adapter, rather than reconstructing a different attempt after a crash.

Node's reconcile, Go's Reconcile and Python's reconcile report an outcome established by the protected application's lookup. They accept recovery context, a provider reference and a known outcome. The resulting receipt is reconciled_succeeded or reconciled_failed. Neither method automatically repeats the business function.

A signed receipt establishes what the registered executor key reported. It does not independently retrieve or verify the provider record.

Check whether the result was withheld

An operation can finish even if AuthRail stops its result from being returned to the agent. For example, the provider may create an invoice, then the result check may prevent the agent from seeing sensitive invoice fields.

Inspect the execution reports and provider record before retrying. A withheld response does not delete the invoice. If the business operation needs to be reversed, use your application's supported reversal process.

Restore report delivery without repeating the action

The sidecar may have a report waiting to reach AuthRail. Read its recording status before deciding what to do:

StatusMeaningWhat to do
retainedAuthRail saved the report.Keep its record references with the application task.
queuedThe sidecar saved the report locally for delivery.Restore connectivity and keep the complete state directory.
unavailableDurable recording could not be established.Investigate storage and delivery; preserve the application's recovery information.

Enforce holds a new operation when its required call report has not been retained. Later report delivery does not start that operation automatically. A reporting problem after execution cannot undo an effect that already happened. Observe remains advisory during a reporting outage.

Keep the entire sidecar state directory through restarts, including queues, saved session history and recording-gap files. Deleting those files can remove the information needed to recover. See monitoring to inspect connection status and local logs.

On this page