Transactions and compensation Use this page to understand how AIP represents a consequential operation before, during, and after a connector attempt. It is for application, runtime, and connector developers who need to plan a mutation, own one commit attempt, recover an uncertain provider outcome, or compensate prior work. An AIP transaction is a durable coordination record around one or more action attempts. It is not a database transaction spanning the runtime, connector, and external product. Planning can bind intent, commit claiming can fence competing workers, and reconciliation can stop a blind retry, but none of these controls makes an external side effect atomic or exactly once. This page describes the reviewed Rust implementation of AIP 1.0 at source revision 97be86e9efedf07ecf1783b03800f683f107fb04. Concrete product semantics still depend on the selected capability, connector handler, provider operation, and durable backends configured by a deployment. Keep five identities separate | Identity | Scope | What it binds | | Action ID | One AIP invocation | Action lifecycle, idempotent replay checks, cancellation, and connector route assignment | | Transaction ID | One durable coordination record | Plan, commit, execution, reconciliation, or compensation state | | Plan ID | One immutable plan | A later commit to the planned capability and input | | Provider operation ID | One provider-accepted operation | Checkpoint and reconciliation against the external system | | Compensation target action ID | Earlier work to offset | The result and transaction evidence that make compensation eligible | These identifiers are correlated, not interchangeable. A transaction ID does not select a connector replica. A plan ID does not prove commit ownership. A provider operation ID does not prove that the provider committed. A compensation target does not guarantee that reversal is possible. flowchart LR I["Governed action intent"] --> D["Dry run"] I --> P["Durable plan"] I --> E["Direct execute"] P --> C["Atomic commit claim"] E --> R["Connector attempt"] C --> R R --> K["Committed"] R --> F["Failed before known commit"] R --> U["Outcome unknown"] U --> Q["Reconcile provider operation"] Q --> Z["Reconciled"] K --> X["New compensating action"] X --> Y["Compensated or failed"] Capabilities declare transaction support TransactionContract contains only three declarations: | Field | Meaning | | supportedmodes | Transaction modes admitted for the capability | | requiresplanbeforecommit | Capability declaration that the runtime uses when selecting its commit branch | | dryrunfidelity | Strength of the advertised preview | The seven transaction modes are: | Mode | Reviewed runtime behavior | | execute | Prepare and execute directly under a transaction ID | | dryrun | Return a runtime preview or call a handler for declared downstream validation | | plan | Create a durable plan, optionally after handler validation | | commit | Atomically claim a plan, or use an internal direct-commit branch when its narrower preconditions apply | | compensate | Validate a prior target and execute the declared compensation path | | reconcile | Re-enter a transaction whose provider outcome is unknown | | rollbacknotsupported | Return an explicit non-retryable policy result | The four dry-run fidelity levels are deliberately different: | Fidelity | Meaning of the declaration | | schemaonly | Only schema validation is represented | | policyandschema | Runtime schema and policy checks are represented | | downstreamvalidation | The handler is invoked for provider-side validation without intended commit | | fullsimulation | The provider is declared capable of simulating the complete operation | For schemaonly and policyandschema, the reviewed runtime creates its own preview without invoking the handler. For downstreamvalidation and fullsimulation, it calls the handler and records the returned status and output inside a dry-run result. The fidelity value is a contract declaration; the generic runtime cannot prove that a connector or provider avoided side effects. CompensationContract independently declares notrequired, supported, besteffort, or rollbacknotsupported. It can name a separate compensating capability, limit the compensation window, and require approval for the compensating action. Action context carries the transaction intent Action.transaction is the canonical semantic context. Its fields are the mode plus optional transaction ID, plan ID, and compensationfor action ID. The first-class TransactionRequest wraps the same action with a required transaction ID, requester, optional reason and metadata, and optional provider operation and reconciliation cursor. The native request path checks that the requestedby ID matches the runtime principal ID and that the wrapper transaction ID matches the action when both are present. If the action omits the ID, the runtime copies the wrapper value into the action. The ordinary action path also creates a transaction ID when a mode needs one; a commit can recover it from the referenced plan. The first-class wrapper and ordinary action path then share capability lookup, schema checks, authorization, approval, idempotency, transaction admission, and handler execution. The wrapper adds a TransactionResult and a transaction receipt chain; it is not a second transaction engine. Public envelopes first pass the core message validator, whose commit rule is stricter than one branch inside the runtime, as described below. Preview comes before approval; mutation does not The reviewed runtime handles dryrun and plan before creating a pending approval. Their result records whether later execution requires approval and can retain the applicable approval policy. This allows a caller or approver to inspect a stable preview without first authorizing the mutation. execute, commit, compensate, and reconcile do not use that preview-first exception. They pass the normal approval gate before the runtime starts their pre-execution state transition. If a compensation contract requires approval, the runtime forces approval and supplies a default tenant-policy approval when no stronger policy was already selected. Approval and transaction state remain separate. An approved action can still fail to claim a plan, reach a connector, or settle at the provider. A plan that records approvalrequired is not an approved commit. A durable plan binds a bounded set of facts For plan, the runtime creates a TransactionPlan and stores it in a planned transaction record. The implementation-generated plan expires after 15 minutes. Its default ID is plan: when the action does not supply one. The plan retains: | Group | Stored values | | Coordinates | Plan, transaction, planning action, and capability IDs | | Requester | Principal that created the plan | | Intent | SHA-256 input hash and an input snapshot | | Governance | Predicted side effects, approval requirement and policy, compensation contract, and dry-run fidelity | | Identity | The action identity snapshot | | Time | Creation and expiration timestamps | | Metadata | Commit requirements plus data, idempotency, and service-level contract snapshots | The plan input hash covers the capability ID and action input. It does not include Action.transaction. When the capability data contract requires redaction, the input snapshot contains only redacted: true and the hash. Otherwise it also contains the raw input value. Downstream plan validation runs only for downstreamvalidation or fullsimulation. The handler has to advertise transaction support. A failed validation creates a failed transaction record without a plan; a successful validation is retained in plan metadata and the transaction record. What a planned commit rechecks A commit that references a plan is admitted only when: • the plan exists and its transaction record is still planned; • the current capability ID matches the planned capability; • the committing principal ID and kind match the planning principal; • the plan has not expired; • a supplied transaction ID matches the planned transaction ID; • the hash of the current capability ID and input matches the plan hash; • presence of identity context matches, and its tenant and external account equal the planned values. The commit check does not compare every field retained in the plan. In particular, it does not directly compare the entire approval policy, compensation, data, idempotency, service-level metadata, every identity field, or the planning action ID. Current capability, authorization, approval, schema, credential, and idempotency checks still run through the ordinary action path. This bounded comparison is the implemented contract. A deployment that needs a stronger policy freeze has to enforce it at its own admission boundary or use a future protocol extension. It should not infer that stored metadata is already part of commit equality. Commit ownership is atomic After validation, claimplan changes one durable record from planned to committing, associates it with the commit action ID, and increments its compare-and-set revision. A competing claimant observes a non-planned state and is rejected. Durable backends have to perform that state change atomically. The runtime also contains a direct-commit branch. When the selected capability contract sets requiresplanbeforecommit to false and the action has no plan ID, claimdirectcommit creates the transaction directly in committing; an existing transaction ID causes a conflict. That branch is not reachable through a validated public AIP envelope at the reviewed revision. Core validateaction unconditionally requires planid for every commit, including a commit inside TransactionRequest. It is reachable only through a trusted direct runtime call that bypasses envelope validation. Public clients therefore need a plan ID even when the capability contract field is false. The contract and validator are not yet one uniform direct-commit surface. This claim fences control-plane ownership. It does not prove that only one network request reached the provider. Provider-side idempotency, a durable provider operation checkpoint, and connector-specific request semantics remain necessary at the side-effect boundary. Direct execution has its own replay gate execute creates a prepared record before handler execution. If the same transaction ID already exists, the action ID, capability ID, full transaction context, and identity snapshot have to match the stored execution contract. The existing state then controls the outcome: | Stored state | Reviewed behavior | | prepared | Continue the same attempt path | | committed | Return the durable result when it belongs to the same action | | Retryable failed | Reopen as prepared only with an idempotency key and no provider operation, cursor, or reconcile deadline | | Other failed | Return the stored failure or transaction.retrydenied | | committing, outcomeunknown, or reconciling | Reject re-execution with transaction.reconciliationrequired | | Any other state | Reject with transaction.executestateinvalid | This replay gate is stricter than reusing a transaction ID as a correlation label. It treats the stored execution contract as immutable. Checkpoint provider acceptance before uncertainty A connector can publish a ProviderOperationRef through the execution context after a provider accepts work. It contains a provider name, provider-local operation ID, and optional request ID. Provider and operation ID must be non-empty, and a transaction cannot replace an existing reference with a different one. The checkpoint can be written while the transaction is prepared, committing, compensating, or reconciling. It uses the transaction record's revision fence and can also retain an opaque reconciliation cursor. On another attempt, the connector execution context exposes the recovered provider operation ID and cursor without making the whole durable record part of the public action payload. This checkpoint is the bridge between a request sent to a provider and later reconciliation. If a connector learns the provider operation ID but does not publish it before losing the response, the generic runtime cannot invent that identifier. Unknown outcome blocks blind execution retry For execute and commit, a completed action result settles the transaction as committed. A failed result becomes outcomeunknown when its error code is sla.timeoutexceeded or its error details contain uncertainoutcome: true. Other failed results settle as failed. An uncertain record retains any provider operation parsed from error details, an available reconciliation cursor, the redacted error, and an earliest reconciliation time. The default delay is five seconds when the error does not provide retryafterms. The runtime rejects execute while the record is outcomeunknown, reconciling, or committing. This is the central safety property: a timeout is not interpreted as proof that nothing happened. Reconciliation asks the provider what happened A reconcile action requires a transaction ID, an existing transaction in outcomeunknown, and a durable provider operation reference. The first-class request can provide the reference and cursor; otherwise the runtime recovers them from the transaction record. The runtime uses a compare-and-set transition from outcomeunknown to reconciling before invoking the handler. The connector receives the provider operation ID and last cursor in its trusted execution context. A completed handler result settles the record as reconciled; a failed result returns it to outcomeunknown with updated recovery data. reconciled means the reconciliation action produced a definitive result under the connector contract. The generic runtime does not reinterpret provider output as committed, roll back work, or automatically launch compensation. Compensation is a new governed action compensate is admitted only when the capability's compensation mode is supported or besteffort, and the action names compensationfor. The target normally needs a completed action result. A narrow partial-failure path also qualifies when a non-retryable error explicitly records both sideeffectscommitted: true and compensationrequired: true. If the target has transaction records, one must be committed, or a failed record must carry that partial-side-effect evidence. An optional compensation window is measured from the selected target record's last update. When no transaction record exists, the implementation can use an RFC 3339 originalcompletedat value from action observability metadata. That fallback is caller-carried metadata, not equivalent to a durable completion timestamp. The runtime creates a separate compensating transaction record for the new action. If compensationcapabilityid differs from the requested capability, it resolves the alternate capability, rewrites the connector-facing action to that capability, and adds the original result and compensation coordinates to runtime memory context. The public action still identifies the governed compensation request. A completed handler result settles the record as compensated; another result settles it as failed. Neither supported nor besteffort means that the original provider effect was erased. Compensation can itself need approval, idempotency, reconciliation, and operator follow-up. Wire status and durable status are not identical enums The durable runtime record has these statuses: dryruncompleted, planned, prepared, committing, committed, compensating, compensated, failed, outcomeunknown, reconciling, reconciled, and rollbacknotsupported. The wire-facing TransactionProtocolStatus adds requireshuman and cancelled. Those two values are projected from the surrounding action result; they are not durable TransactionStatus variants in the reviewed store. Every durable transition names the allowed current states and expected record revision. The backend applies the update only when both match, then increments the revision. A transaction view can be selected by exactly one of transaction ID, plan ID, or action ID and can optionally include the terminal action result and receipt chain. Normal transport identity, owner, tenant, and query scopes still govern that read. Connector routing is action-scoped The connector registry's immutable RouteAssignment is keyed by action ID. It pins the capability, verified tenant, connector instance and replica, endpoint, peer identity and DID, version and manifest, policy and credential revisions, health revision, admission reservation, and fence token. Resolving the same action ID again reuses that assignment only when capability and tenant still match. Retries therefore remain on the pinned route, and cancellation looks up that same action assignment. A reconciliation action also reuses it only when it carries the same action ID. There is no transaction-ID or plan-ID lookup in the reviewed route resolver. Distinct plan, commit, compensation, or reconciliation action IDs resolve independently and may select different replicas. A schema- or policy-only plan does not invoke a connector at all, so it creates no remote assignment. This boundary is important for connector-local state. An integration that requires plan and commit on one provider-side replica cannot infer that stickiness from the transaction record. It needs an action-scoped route that is actually reused or a provider operation that is portable across replicas. Trust and data boundaries | Boundary | Runtime evidence | What it does not prove | | Capability to caller | Supported modes, fidelity, and compensation declaration | That the connector honors the declaration or has been qualified | | Plan to commit | Exact implemented principal, expiry, capability, input, transaction, tenant, and account checks | Equality of every policy or identity field retained in plan metadata | | Commit claimant to store | Atomic state and revision transition | Exactly-once delivery to an external provider | | Runtime to connector | Trusted transaction ID, provider operation ID, and cursor | Provider outcome before the connector reconciles it | | Action to route registry | Immutable action-scoped assignment | Transaction-wide replica affinity across distinct action IDs | | Original action to compensation | Durable result or explicit partial-side-effect evidence | Guaranteed reversal or restoration of all external state | | Operator to transaction view | Authorized durable record, optional result, and receipts | State newer than the selected store revision or provider truth not yet reconciled | Input snapshots can contain raw action data when the capability does not require redaction. Transaction stores and evidence exports therefore need the same tenant isolation, retention, and access controls as the governed action. Design choices and trade-offs • Explicit modes separate preview, ownership, recovery, and reversal intent, but connector authors have to implement the declared semantics correctly. • A bounded plan comparison is deterministic and inexpensive, but stored plan metadata is broader than the fields currently enforced at commit. • Atomic commit claims prevent two runtime workers from owning the same durable plan, but provider-side idempotency is still required. • Provider-operation checkpoints make uncertain outcomes recoverable, but only when a connector publishes the identifier before losing it. • Reconciliation blocks blind retry and preserves evidence, at the cost of a provider-specific lookup path and durable work scheduling. • Compensation models business reversal honestly as another action, but it cannot provide distributed rollback. • Action-scoped route pinning keeps retries and cancellation consistent, but it does not create transaction-wide connector affinity. What transactions do not establish The reviewed implementation does not establish any of the following: • distributed ACID behavior across runtime, connector, and provider; • exactly-once provider effects; • proof that dryrun or plan is side-effect-free beyond the declared and implemented handler contract; • automatic equality of every plan metadata or identity field at commit; • a public planless commit merely because requiresplanbeforecommit is false; • permission to retry an outcomeunknown operation without reconciliation; • automatic conversion of reconciled into committed or compensated; • guaranteed compensation, even when the mode is supported; • route reuse across distinct action IDs merely because their transaction or plan ID matches; • process durability when an in-memory transaction backend is selected; • live qualification of these paths against a particular provider. Related documentation • Capabilities and contracts (capabilities.md) explains how transaction, idempotency, data, and compensation support is declared. • Actions and sessions (actions-and-sessions.md) owns action identity, queue, result, cancellation, and replay behavior. • Approvals and policy (approvals-and-policy.md) explains the durable approval gate used before commit and compensation. • Observe and recover (../guides/observe-and-recover.md) provides operational recovery workflows for uncertain and stalled work. • Connector registry and routing (../architecture/connector-registry-and-routing.md) owns route assignment, admission, settlement, and replica selection details.