{
  "schemaVersion": "1.0",
  "title": "Transactions and compensation",
  "description": "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",
  "canonical": "https://getaip.org/docs/concepts/transactions-and-compensation",
  "route": "/docs/concepts/transactions-and-compensation",
  "source": "docs/concepts/transactions-and-compensation.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Core Concepts",
  "documentType": "Concept",
  "language": "en",
  "revision": {
    "lastReviewedRevision": "97be86e9efedf07ecf1783b03800f683f107fb04",
    "documentationSourceRevision": "9192fef3695ad294994f2712f6d156241e5e92fb",
    "basis": "frontmatter"
  },
  "downloads": {
    "md": "/docs/download/concepts/transactions-and-compensation.md",
    "txt": "/docs/download/concepts/transactions-and-compensation.txt",
    "json": "/docs/download/concepts/transactions-and-compensation.json",
    "pdf": "/docs/download/concepts/transactions-and-compensation.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Transactions and compensation\ndescription: Understand AIP planning, commit ownership, reconciliation, and compensation boundaries\nkind: explanation\naudience: developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\n---\n\n# Transactions and compensation\n\nUse this page to understand how AIP represents a consequential operation before,\nduring, and after a connector attempt. It is for application, runtime, and\nconnector developers who need to plan a mutation, own one commit attempt,\nrecover an uncertain provider outcome, or compensate prior work.\n\nAn AIP transaction is a durable coordination record around one or more action\nattempts. It is not a database transaction spanning the runtime, connector, and\nexternal product. Planning can bind intent, commit claiming can fence competing\nworkers, and reconciliation can stop a blind retry, but none of these controls\nmakes an external side effect atomic or exactly once.\n\nThis page describes the reviewed Rust implementation of AIP 1.0 at source\nrevision `97be86e9efedf07ecf1783b03800f683f107fb04`. Concrete product semantics\nstill depend on the selected capability, connector handler, provider operation,\nand durable backends configured by a deployment.\n\n## Keep five identities separate\n\n| Identity | Scope | What it binds |\n|---|---|---|\n| Action ID | One AIP invocation | Action lifecycle, idempotent replay checks, cancellation, and connector route assignment |\n| Transaction ID | One durable coordination record | Plan, commit, execution, reconciliation, or compensation state |\n| Plan ID | One immutable plan | A later commit to the planned capability and input |\n| Provider operation ID | One provider-accepted operation | Checkpoint and reconciliation against the external system |\n| Compensation target action ID | Earlier work to offset | The result and transaction evidence that make compensation eligible |\n\nThese identifiers are correlated, not interchangeable. A transaction ID does\nnot select a connector replica. A plan ID does not prove commit ownership. A\nprovider operation ID does not prove that the provider committed. A\ncompensation target does not guarantee that reversal is possible.\n\n```mermaid\nflowchart LR\n    I[\"Governed action intent\"] --> D[\"Dry run\"]\n    I --> P[\"Durable plan\"]\n    I --> E[\"Direct execute\"]\n    P --> C[\"Atomic commit claim\"]\n    E --> R[\"Connector attempt\"]\n    C --> R\n    R --> K[\"Committed\"]\n    R --> F[\"Failed before known commit\"]\n    R --> U[\"Outcome unknown\"]\n    U --> Q[\"Reconcile provider operation\"]\n    Q --> Z[\"Reconciled\"]\n    K --> X[\"New compensating action\"]\n    X --> Y[\"Compensated or failed\"]\n```\n\n## Capabilities declare transaction support\n\n`TransactionContract` contains only three declarations:\n\n| Field | Meaning |\n|---|---|\n| `supported_modes` | Transaction modes admitted for the capability |\n| `requires_plan_before_commit` | Capability declaration that the runtime uses when selecting its commit branch |\n| `dry_run_fidelity` | Strength of the advertised preview |\n\nThe seven transaction modes are:\n\n| Mode | Reviewed runtime behavior |\n|---|---|\n| `execute` | Prepare and execute directly under a transaction ID |\n| `dry_run` | Return a runtime preview or call a handler for declared downstream validation |\n| `plan` | Create a durable plan, optionally after handler validation |\n| `commit` | Atomically claim a plan, or use an internal direct-commit branch when its narrower preconditions apply |\n| `compensate` | Validate a prior target and execute the declared compensation path |\n| `reconcile` | Re-enter a transaction whose provider outcome is unknown |\n| `rollback_not_supported` | Return an explicit non-retryable policy result |\n\nThe four dry-run fidelity levels are deliberately different:\n\n| Fidelity | Meaning of the declaration |\n|---|---|\n| `schema_only` | Only schema validation is represented |\n| `policy_and_schema` | Runtime schema and policy checks are represented |\n| `downstream_validation` | The handler is invoked for provider-side validation without intended commit |\n| `full_simulation` | The provider is declared capable of simulating the complete operation |\n\nFor `schema_only` and `policy_and_schema`, the reviewed runtime creates its own\npreview without invoking the handler. For `downstream_validation` and\n`full_simulation`, it calls the handler and records the returned status and\noutput inside a dry-run result. The fidelity value is a contract declaration;\nthe generic runtime cannot prove that a connector or provider avoided side\neffects.\n\n`CompensationContract` independently declares `not_required`, `supported`,\n`best_effort`, or `rollback_not_supported`. It can name a separate compensating\ncapability, limit the compensation window, and require approval for the\ncompensating action.\n\n## Action context carries the transaction intent\n\n`Action.transaction` is the canonical semantic context. Its fields are the\nmode plus optional transaction ID, plan ID, and `compensation_for` action ID.\nThe first-class `TransactionRequest` wraps the same action with a required\ntransaction ID, requester, optional reason and metadata, and optional provider\noperation and reconciliation cursor.\n\nThe native request path checks that the `requested_by` ID matches the runtime\nprincipal ID and that the wrapper transaction ID matches the action when both\nare present. If the action omits the ID, the runtime copies the wrapper value\ninto the action. The ordinary action path also creates a transaction ID when a\nmode needs one; a commit can recover it from the referenced plan.\n\nThe first-class wrapper and ordinary action path then share capability lookup,\nschema checks, authorization, approval, idempotency, transaction admission, and\nhandler execution. The wrapper adds a `TransactionResult` and a transaction\nreceipt chain; it is not a second transaction engine. Public envelopes first\npass the core message validator, whose commit rule is stricter than one branch\ninside the runtime, as described below.\n\n## Preview comes before approval; mutation does not\n\nThe reviewed runtime handles `dry_run` and `plan` before creating a pending\napproval. Their result records whether later execution requires approval and\ncan retain the applicable approval policy. This allows a caller or approver to\ninspect a stable preview without first authorizing the mutation.\n\n`execute`, `commit`, `compensate`, and `reconcile` do not use that preview-first\nexception. They pass the normal approval gate before the runtime starts their\npre-execution state transition. If a compensation contract requires approval,\nthe runtime forces approval and supplies a default tenant-policy approval when\nno stronger policy was already selected.\n\nApproval and transaction state remain separate. An approved action can still\nfail to claim a plan, reach a connector, or settle at the provider. A plan that\nrecords `approval_required` is not an approved commit.\n\n## A durable plan binds a bounded set of facts\n\nFor `plan`, the runtime creates a `TransactionPlan` and stores it in a\n`planned` transaction record. The implementation-generated plan expires after\n15 minutes. Its default ID is `plan:<transaction_id>` when the action does not\nsupply one.\n\nThe plan retains:\n\n| Group | Stored values |\n|---|---|\n| Coordinates | Plan, transaction, planning action, and capability IDs |\n| Requester | Principal that created the plan |\n| Intent | SHA-256 input hash and an input snapshot |\n| Governance | Predicted side effects, approval requirement and policy, compensation contract, and dry-run fidelity |\n| Identity | The action identity snapshot |\n| Time | Creation and expiration timestamps |\n| Metadata | Commit requirements plus data, idempotency, and service-level contract snapshots |\n\nThe plan input hash covers the capability ID and action input. It does not\ninclude `Action.transaction`. When the capability data contract requires\nredaction, the input snapshot contains only `redacted: true` and the hash.\nOtherwise it also contains the raw input value.\n\nDownstream plan validation runs only for `downstream_validation` or\n`full_simulation`. The handler has to advertise transaction support. A failed\nvalidation creates a failed transaction record without a plan; a successful\nvalidation is retained in plan metadata and the transaction record.\n\n### What a planned commit rechecks\n\nA commit that references a plan is admitted only when:\n\n- the plan exists and its transaction record is still `planned`;\n- the current capability ID matches the planned capability;\n- the committing principal ID and kind match the planning principal;\n- the plan has not expired;\n- a supplied transaction ID matches the planned transaction ID;\n- the hash of the current capability ID and input matches the plan hash;\n- presence of identity context matches, and its tenant and external account\n  equal the planned values.\n\nThe commit check does not compare every field retained in the plan. In\nparticular, it does not directly compare the entire approval policy,\ncompensation, data, idempotency, service-level metadata, every identity field,\nor the planning action ID. Current capability, authorization, approval, schema,\ncredential, and idempotency checks still run through the ordinary action path.\n\nThis bounded comparison is the implemented contract. A deployment that needs a\nstronger policy freeze has to enforce it at its own admission boundary or use a\nfuture protocol extension. It should not infer that stored metadata is already\npart of commit equality.\n\n## Commit ownership is atomic\n\nAfter validation, `claim_plan` changes one durable record from `planned` to\n`committing`, associates it with the commit action ID, and increments its\ncompare-and-set revision. A competing claimant observes a non-planned state and\nis rejected. Durable backends have to perform that state change atomically.\n\nThe runtime also contains a direct-commit branch. When the selected capability\ncontract sets `requires_plan_before_commit` to false and the action has no plan\nID, `claim_direct_commit` creates the transaction directly in `committing`; an\nexisting transaction ID causes a conflict.\n\nThat branch is not reachable through a validated public AIP envelope at the\nreviewed revision. Core `validate_action` unconditionally requires `plan_id`\nfor every `commit`, including a commit inside `TransactionRequest`. It is\nreachable only through a trusted direct runtime call that bypasses envelope\nvalidation.\n\nPublic clients therefore need a plan ID even when the capability\ncontract field is false. The contract and validator are not yet one uniform\ndirect-commit surface.\n\nThis claim fences control-plane ownership. It does not prove that only one\nnetwork request reached the provider. Provider-side idempotency, a durable\nprovider operation checkpoint, and connector-specific request semantics remain\nnecessary at the side-effect boundary.\n\n## Direct execution has its own replay gate\n\n`execute` creates a `prepared` record before handler execution. If the same\ntransaction ID already exists, the action ID, capability ID, full transaction\ncontext, and identity snapshot have to match the stored execution contract.\n\nThe existing state then controls the outcome:\n\n| Stored state | Reviewed behavior |\n|---|---|\n| `prepared` | Continue the same attempt path |\n| `committed` | Return the durable result when it belongs to the same action |\n| Retryable `failed` | Reopen as `prepared` only with an idempotency key and no provider operation, cursor, or reconcile deadline |\n| Other `failed` | Return the stored failure or `transaction.retry_denied` |\n| `committing`, `outcome_unknown`, or `reconciling` | Reject re-execution with `transaction.reconciliation_required` |\n| Any other state | Reject with `transaction.execute_state_invalid` |\n\nThis replay gate is stricter than reusing a transaction ID as a correlation\nlabel. It treats the stored execution contract as immutable.\n\n## Checkpoint provider acceptance before uncertainty\n\nA connector can publish a `ProviderOperationRef` through the execution context\nafter a provider accepts work. It contains a provider name, provider-local\noperation ID, and optional request ID. Provider and operation ID must be\nnon-empty, and a transaction cannot replace an existing reference with a\ndifferent one.\n\nThe checkpoint can be written while the transaction is `prepared`,\n`committing`, `compensating`, or `reconciling`. It uses the transaction record's\nrevision fence and can also retain an opaque reconciliation cursor. On another\nattempt, the connector execution context exposes the recovered provider\noperation ID and cursor without making the whole durable record part of the\npublic action payload.\n\nThis checkpoint is the bridge between a request sent to a provider and later\nreconciliation. If a connector learns the provider operation ID but does not\npublish it before losing the response, the generic runtime cannot invent that\nidentifier.\n\n## Unknown outcome blocks blind execution retry\n\nFor `execute` and `commit`, a completed action result settles the transaction as\n`committed`. A failed result becomes `outcome_unknown` when its error code is\n`sla.timeout_exceeded` or its error details contain\n`uncertain_outcome: true`. Other failed results settle as `failed`.\n\nAn uncertain record retains any provider operation parsed from error details,\nan available reconciliation cursor, the redacted error, and an earliest\nreconciliation time. The default delay is five seconds when the error does not\nprovide `retry_after_ms`.\n\nThe runtime rejects `execute` while the record is `outcome_unknown`,\n`reconciling`, or `committing`. This is the central safety property: a timeout\nis not interpreted as proof that nothing happened.\n\n## Reconciliation asks the provider what happened\n\nA `reconcile` action requires a transaction ID, an existing transaction in\n`outcome_unknown`, and a durable provider operation reference. The first-class\nrequest can provide the reference and cursor; otherwise the runtime recovers\nthem from the transaction record.\n\nThe runtime uses a compare-and-set transition from `outcome_unknown` to\n`reconciling` before invoking the handler. The connector receives the provider\noperation ID and last cursor in its trusted execution context. A completed\nhandler result settles the record as `reconciled`; a failed result returns it to\n`outcome_unknown` with updated recovery data.\n\n`reconciled` means the reconciliation action produced a definitive result under\nthe connector contract. The generic runtime does not reinterpret provider\noutput as `committed`, roll back work, or automatically launch compensation.\n\n## Compensation is a new governed action\n\n`compensate` is admitted only when the capability's compensation mode is\n`supported` or `best_effort`, and the action names `compensation_for`. The\ntarget normally needs a completed action result. A narrow partial-failure path\nalso qualifies when a non-retryable error explicitly records both\n`side_effects_committed: true` and `compensation_required: true`.\n\nIf the target has transaction records, one must be `committed`, or a failed\nrecord must carry that partial-side-effect evidence. An optional compensation\nwindow is measured from the selected target record's last update. When no\ntransaction record exists, the implementation can use an RFC 3339\n`original_completed_at` value from action observability metadata. That fallback\nis caller-carried metadata, not equivalent to a durable completion timestamp.\n\nThe runtime creates a separate `compensating` transaction record for the new\naction. If `compensation_capability_id` differs from the requested capability,\nit resolves the alternate capability, rewrites the connector-facing action to\nthat capability, and adds the original result and compensation coordinates to\nruntime memory context. The public action still identifies the governed\ncompensation request.\n\nA completed handler result settles the record as `compensated`; another result\nsettles it as `failed`. Neither `supported` nor `best_effort` means that the\noriginal provider effect was erased. Compensation can itself need approval,\nidempotency, reconciliation, and operator follow-up.\n\n## Wire status and durable status are not identical enums\n\nThe durable runtime record has these statuses:\n\n`dry_run_completed`, `planned`, `prepared`, `committing`, `committed`,\n`compensating`, `compensated`, `failed`, `outcome_unknown`, `reconciling`,\n`reconciled`, and `rollback_not_supported`.\n\nThe wire-facing `TransactionProtocolStatus` adds `requires_human` and\n`cancelled`. Those two values are projected from the surrounding action result;\nthey are not durable `TransactionStatus` variants in the reviewed store.\n\nEvery durable transition names the allowed current states and expected record\nrevision. The backend applies the update only when both match, then increments\nthe revision. A transaction view can be selected by exactly one of transaction\nID, plan ID, or action ID and can optionally include the terminal action result\nand receipt chain. Normal transport identity, owner, tenant, and query scopes\nstill govern that read.\n\n## Connector routing is action-scoped\n\nThe connector registry's immutable `RouteAssignment` is keyed by action ID. It\npins the capability, verified tenant, connector instance and replica, endpoint,\npeer identity and DID, version and manifest, policy and credential revisions,\nhealth revision, admission reservation, and fence token.\n\nResolving the same action ID again reuses that assignment only when capability\nand tenant still match. Retries therefore remain on the pinned route, and\ncancellation looks up that same action assignment. A reconciliation action also\nreuses it only when it carries the same action ID.\n\nThere is no transaction-ID or plan-ID lookup in the reviewed route resolver.\nDistinct plan, commit, compensation, or reconciliation action IDs resolve\nindependently and may select different replicas. A schema- or policy-only plan\ndoes not invoke a connector at all, so it creates no remote assignment.\n\nThis boundary is important for connector-local state. An integration that\nrequires plan and commit on one provider-side replica cannot infer that\nstickiness from the transaction record. It needs an action-scoped route that is\nactually reused or a provider operation that is portable across replicas.\n\n## Trust and data boundaries\n\n| Boundary | Runtime evidence | What it does not prove |\n|---|---|---|\n| Capability to caller | Supported modes, fidelity, and compensation declaration | That the connector honors the declaration or has been qualified |\n| 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 |\n| Commit claimant to store | Atomic state and revision transition | Exactly-once delivery to an external provider |\n| Runtime to connector | Trusted transaction ID, provider operation ID, and cursor | Provider outcome before the connector reconciles it |\n| Action to route registry | Immutable action-scoped assignment | Transaction-wide replica affinity across distinct action IDs |\n| Original action to compensation | Durable result or explicit partial-side-effect evidence | Guaranteed reversal or restoration of all external state |\n| Operator to transaction view | Authorized durable record, optional result, and receipts | State newer than the selected store revision or provider truth not yet reconciled |\n\nInput snapshots can contain raw action data when the capability does not require\nredaction. Transaction stores and evidence exports therefore need the same\ntenant isolation, retention, and access controls as the governed action.\n\n## Design choices and trade-offs\n\n- Explicit modes separate preview, ownership, recovery, and reversal intent,\n  but connector authors have to implement the declared semantics correctly.\n- A bounded plan comparison is deterministic and inexpensive, but stored plan\n  metadata is broader than the fields currently enforced at commit.\n- Atomic commit claims prevent two runtime workers from owning the same durable\n  plan, but provider-side idempotency is still required.\n- Provider-operation checkpoints make uncertain outcomes recoverable, but only\n  when a connector publishes the identifier before losing it.\n- Reconciliation blocks blind retry and preserves evidence, at the cost of a\n  provider-specific lookup path and durable work scheduling.\n- Compensation models business reversal honestly as another action, but it\n  cannot provide distributed rollback.\n- Action-scoped route pinning keeps retries and cancellation consistent, but it\n  does not create transaction-wide connector affinity.\n\n## What transactions do not establish\n\nThe reviewed implementation does not establish any of the following:\n\n- distributed ACID behavior across runtime, connector, and provider;\n- exactly-once provider effects;\n- proof that `dry_run` or `plan` is side-effect-free beyond the declared and\n  implemented handler contract;\n- automatic equality of every plan metadata or identity field at commit;\n- a public planless commit merely because `requires_plan_before_commit` is\n  false;\n- permission to retry an `outcome_unknown` operation without reconciliation;\n- automatic conversion of `reconciled` into `committed` or `compensated`;\n- guaranteed compensation, even when the mode is `supported`;\n- route reuse across distinct action IDs merely because their transaction or\n  plan ID matches;\n- process durability when an in-memory transaction backend is selected;\n- live qualification of these paths against a particular provider.\n\n## Related documentation\n\n- [Capabilities and contracts](capabilities.md) explains how transaction,\n  idempotency, data, and compensation support is declared.\n- [Actions and sessions](actions-and-sessions.md) owns action identity, queue,\n  result, cancellation, and replay behavior.\n- [Approvals and policy](approvals-and-policy.md) explains the durable approval\n  gate used before commit and compensation.\n- [Observe and recover](../guides/observe-and-recover.md) provides operational\n  recovery workflows for uncertain and stalled work.\n- [Connector registry and routing](../architecture/connector-registry-and-routing.md)\n  owns route assignment, admission, settlement, and replica selection details.\n",
    "text": "Transactions and compensation\n\nUse this page to understand how AIP represents a consequential operation before,\nduring, and after a connector attempt. It is for application, runtime, and\nconnector developers who need to plan a mutation, own one commit attempt,\nrecover an uncertain provider outcome, or compensate prior work.\n\nAn AIP transaction is a durable coordination record around one or more action\nattempts. It is not a database transaction spanning the runtime, connector, and\nexternal product. Planning can bind intent, commit claiming can fence competing\nworkers, and reconciliation can stop a blind retry, but none of these controls\nmakes an external side effect atomic or exactly once.\n\nThis page describes the reviewed Rust implementation of AIP 1.0 at source\nrevision 97be86e9efedf07ecf1783b03800f683f107fb04. Concrete product semantics\nstill depend on the selected capability, connector handler, provider operation,\nand durable backends configured by a deployment.\n\nKeep five identities separate\n\n| Identity | Scope | What it binds |\n\n| Action ID | One AIP invocation | Action lifecycle, idempotent replay checks, cancellation, and connector route assignment |\n| Transaction ID | One durable coordination record | Plan, commit, execution, reconciliation, or compensation state |\n| Plan ID | One immutable plan | A later commit to the planned capability and input |\n| Provider operation ID | One provider-accepted operation | Checkpoint and reconciliation against the external system |\n| Compensation target action ID | Earlier work to offset | The result and transaction evidence that make compensation eligible |\n\nThese identifiers are correlated, not interchangeable. A transaction ID does\nnot select a connector replica. A plan ID does not prove commit ownership. A\nprovider operation ID does not prove that the provider committed. A\ncompensation target does not guarantee that reversal is possible.\n\nflowchart LR\n    I[\"Governed action intent\"] --> D[\"Dry run\"]\n    I --> P[\"Durable plan\"]\n    I --> E[\"Direct execute\"]\n    P --> C[\"Atomic commit claim\"]\n    E --> R[\"Connector attempt\"]\n    C --> R\n    R --> K[\"Committed\"]\n    R --> F[\"Failed before known commit\"]\n    R --> U[\"Outcome unknown\"]\n    U --> Q[\"Reconcile provider operation\"]\n    Q --> Z[\"Reconciled\"]\n    K --> X[\"New compensating action\"]\n    X --> Y[\"Compensated or failed\"]\n\nCapabilities declare transaction support\n\nTransactionContract contains only three declarations:\n\n| Field | Meaning |\n\n| supportedmodes | Transaction modes admitted for the capability |\n| requiresplanbeforecommit | Capability declaration that the runtime uses when selecting its commit branch |\n| dryrunfidelity | Strength of the advertised preview |\n\nThe seven transaction modes are:\n\n| Mode | Reviewed runtime behavior |\n\n| execute | Prepare and execute directly under a transaction ID |\n| dryrun | Return a runtime preview or call a handler for declared downstream validation |\n| plan | Create a durable plan, optionally after handler validation |\n| commit | Atomically claim a plan, or use an internal direct-commit branch when its narrower preconditions apply |\n| compensate | Validate a prior target and execute the declared compensation path |\n| reconcile | Re-enter a transaction whose provider outcome is unknown |\n| rollbacknotsupported | Return an explicit non-retryable policy result |\n\nThe four dry-run fidelity levels are deliberately different:\n\n| Fidelity | Meaning of the declaration |\n\n| schemaonly | Only schema validation is represented |\n| policyandschema | Runtime schema and policy checks are represented |\n| downstreamvalidation | The handler is invoked for provider-side validation without intended commit |\n| fullsimulation | The provider is declared capable of simulating the complete operation |\n\nFor schemaonly and policyandschema, the reviewed runtime creates its own\npreview without invoking the handler. For downstreamvalidation and\nfullsimulation, it calls the handler and records the returned status and\noutput inside a dry-run result. The fidelity value is a contract declaration;\nthe generic runtime cannot prove that a connector or provider avoided side\neffects.\n\nCompensationContract independently declares notrequired, supported,\nbesteffort, or rollbacknotsupported. It can name a separate compensating\ncapability, limit the compensation window, and require approval for the\ncompensating action.\n\nAction context carries the transaction intent\n\nAction.transaction is the canonical semantic context. Its fields are the\nmode plus optional transaction ID, plan ID, and compensationfor action ID.\nThe first-class TransactionRequest wraps the same action with a required\ntransaction ID, requester, optional reason and metadata, and optional provider\noperation and reconciliation cursor.\n\nThe native request path checks that the requestedby ID matches the runtime\nprincipal ID and that the wrapper transaction ID matches the action when both\nare present. If the action omits the ID, the runtime copies the wrapper value\ninto the action. The ordinary action path also creates a transaction ID when a\nmode needs one; a commit can recover it from the referenced plan.\n\nThe first-class wrapper and ordinary action path then share capability lookup,\nschema checks, authorization, approval, idempotency, transaction admission, and\nhandler execution. The wrapper adds a TransactionResult and a transaction\nreceipt chain; it is not a second transaction engine. Public envelopes first\npass the core message validator, whose commit rule is stricter than one branch\ninside the runtime, as described below.\n\nPreview comes before approval; mutation does not\n\nThe reviewed runtime handles dryrun and plan before creating a pending\napproval. Their result records whether later execution requires approval and\ncan retain the applicable approval policy. This allows a caller or approver to\ninspect a stable preview without first authorizing the mutation.\n\nexecute, commit, compensate, and reconcile do not use that preview-first\nexception. They pass the normal approval gate before the runtime starts their\npre-execution state transition. If a compensation contract requires approval,\nthe runtime forces approval and supplies a default tenant-policy approval when\nno stronger policy was already selected.\n\nApproval and transaction state remain separate. An approved action can still\nfail to claim a plan, reach a connector, or settle at the provider. A plan that\nrecords approvalrequired is not an approved commit.\n\nA durable plan binds a bounded set of facts\n\nFor plan, the runtime creates a TransactionPlan and stores it in a\nplanned transaction record. The implementation-generated plan expires after\n15 minutes. Its default ID is plan: when the action does not\nsupply one.\n\nThe plan retains:\n\n| Group | Stored values |\n\n| Coordinates | Plan, transaction, planning action, and capability IDs |\n| Requester | Principal that created the plan |\n| Intent | SHA-256 input hash and an input snapshot |\n| Governance | Predicted side effects, approval requirement and policy, compensation contract, and dry-run fidelity |\n| Identity | The action identity snapshot |\n| Time | Creation and expiration timestamps |\n| Metadata | Commit requirements plus data, idempotency, and service-level contract snapshots |\n\nThe plan input hash covers the capability ID and action input. It does not\ninclude Action.transaction. When the capability data contract requires\nredaction, the input snapshot contains only redacted: true and the hash.\nOtherwise it also contains the raw input value.\n\nDownstream plan validation runs only for downstreamvalidation or\nfullsimulation. The handler has to advertise transaction support. A failed\nvalidation creates a failed transaction record without a plan; a successful\nvalidation is retained in plan metadata and the transaction record.\n\nWhat a planned commit rechecks\n\nA commit that references a plan is admitted only when:\n• the plan exists and its transaction record is still planned;\n• the current capability ID matches the planned capability;\n• the committing principal ID and kind match the planning principal;\n• the plan has not expired;\n• a supplied transaction ID matches the planned transaction ID;\n• the hash of the current capability ID and input matches the plan hash;\n• presence of identity context matches, and its tenant and external account\n  equal the planned values.\n\nThe commit check does not compare every field retained in the plan. In\nparticular, it does not directly compare the entire approval policy,\ncompensation, data, idempotency, service-level metadata, every identity field,\nor the planning action ID. Current capability, authorization, approval, schema,\ncredential, and idempotency checks still run through the ordinary action path.\n\nThis bounded comparison is the implemented contract. A deployment that needs a\nstronger policy freeze has to enforce it at its own admission boundary or use a\nfuture protocol extension. It should not infer that stored metadata is already\npart of commit equality.\n\nCommit ownership is atomic\n\nAfter validation, claimplan changes one durable record from planned to\ncommitting, associates it with the commit action ID, and increments its\ncompare-and-set revision. A competing claimant observes a non-planned state and\nis rejected. Durable backends have to perform that state change atomically.\n\nThe runtime also contains a direct-commit branch. When the selected capability\ncontract sets requiresplanbeforecommit to false and the action has no plan\nID, claimdirectcommit creates the transaction directly in committing; an\nexisting transaction ID causes a conflict.\n\nThat branch is not reachable through a validated public AIP envelope at the\nreviewed revision. Core validateaction unconditionally requires planid\nfor every commit, including a commit inside TransactionRequest. It is\nreachable only through a trusted direct runtime call that bypasses envelope\nvalidation.\n\nPublic clients therefore need a plan ID even when the capability\ncontract field is false. The contract and validator are not yet one uniform\ndirect-commit surface.\n\nThis claim fences control-plane ownership. It does not prove that only one\nnetwork request reached the provider. Provider-side idempotency, a durable\nprovider operation checkpoint, and connector-specific request semantics remain\nnecessary at the side-effect boundary.\n\nDirect execution has its own replay gate\n\nexecute creates a prepared record before handler execution. If the same\ntransaction ID already exists, the action ID, capability ID, full transaction\ncontext, and identity snapshot have to match the stored execution contract.\n\nThe existing state then controls the outcome:\n\n| Stored state | Reviewed behavior |\n\n| prepared | Continue the same attempt path |\n| committed | Return the durable result when it belongs to the same action |\n| Retryable failed | Reopen as prepared only with an idempotency key and no provider operation, cursor, or reconcile deadline |\n| Other failed | Return the stored failure or transaction.retrydenied |\n| committing, outcomeunknown, or reconciling | Reject re-execution with transaction.reconciliationrequired |\n| Any other state | Reject with transaction.executestateinvalid |\n\nThis replay gate is stricter than reusing a transaction ID as a correlation\nlabel. It treats the stored execution contract as immutable.\n\nCheckpoint provider acceptance before uncertainty\n\nA connector can publish a ProviderOperationRef through the execution context\nafter a provider accepts work. It contains a provider name, provider-local\noperation ID, and optional request ID. Provider and operation ID must be\nnon-empty, and a transaction cannot replace an existing reference with a\ndifferent one.\n\nThe checkpoint can be written while the transaction is prepared,\ncommitting, compensating, or reconciling. It uses the transaction record's\nrevision fence and can also retain an opaque reconciliation cursor. On another\nattempt, the connector execution context exposes the recovered provider\noperation ID and cursor without making the whole durable record part of the\npublic action payload.\n\nThis checkpoint is the bridge between a request sent to a provider and later\nreconciliation. If a connector learns the provider operation ID but does not\npublish it before losing the response, the generic runtime cannot invent that\nidentifier.\n\nUnknown outcome blocks blind execution retry\n\nFor execute and commit, a completed action result settles the transaction as\ncommitted. A failed result becomes outcomeunknown when its error code is\nsla.timeoutexceeded or its error details contain\nuncertainoutcome: true. Other failed results settle as failed.\n\nAn uncertain record retains any provider operation parsed from error details,\nan available reconciliation cursor, the redacted error, and an earliest\nreconciliation time. The default delay is five seconds when the error does not\nprovide retryafterms.\n\nThe runtime rejects execute while the record is outcomeunknown,\nreconciling, or committing. This is the central safety property: a timeout\nis not interpreted as proof that nothing happened.\n\nReconciliation asks the provider what happened\n\nA reconcile action requires a transaction ID, an existing transaction in\noutcomeunknown, and a durable provider operation reference. The first-class\nrequest can provide the reference and cursor; otherwise the runtime recovers\nthem from the transaction record.\n\nThe runtime uses a compare-and-set transition from outcomeunknown to\nreconciling before invoking the handler. The connector receives the provider\noperation ID and last cursor in its trusted execution context. A completed\nhandler result settles the record as reconciled; a failed result returns it to\noutcomeunknown with updated recovery data.\n\nreconciled means the reconciliation action produced a definitive result under\nthe connector contract. The generic runtime does not reinterpret provider\noutput as committed, roll back work, or automatically launch compensation.\n\nCompensation is a new governed action\n\ncompensate is admitted only when the capability's compensation mode is\nsupported or besteffort, and the action names compensationfor. The\ntarget normally needs a completed action result. A narrow partial-failure path\nalso qualifies when a non-retryable error explicitly records both\nsideeffectscommitted: true and compensationrequired: true.\n\nIf the target has transaction records, one must be committed, or a failed\nrecord must carry that partial-side-effect evidence. An optional compensation\nwindow is measured from the selected target record's last update. When no\ntransaction record exists, the implementation can use an RFC 3339\noriginalcompletedat value from action observability metadata. That fallback\nis caller-carried metadata, not equivalent to a durable completion timestamp.\n\nThe runtime creates a separate compensating transaction record for the new\naction. If compensationcapabilityid differs from the requested capability,\nit resolves the alternate capability, rewrites the connector-facing action to\nthat capability, and adds the original result and compensation coordinates to\nruntime memory context. The public action still identifies the governed\ncompensation request.\n\nA completed handler result settles the record as compensated; another result\nsettles it as failed. Neither supported nor besteffort means that the\noriginal provider effect was erased. Compensation can itself need approval,\nidempotency, reconciliation, and operator follow-up.\n\nWire status and durable status are not identical enums\n\nThe durable runtime record has these statuses:\n\ndryruncompleted, planned, prepared, committing, committed,\ncompensating, compensated, failed, outcomeunknown, reconciling,\nreconciled, and rollbacknotsupported.\n\nThe wire-facing TransactionProtocolStatus adds requireshuman and\ncancelled. Those two values are projected from the surrounding action result;\nthey are not durable TransactionStatus variants in the reviewed store.\n\nEvery durable transition names the allowed current states and expected record\nrevision. The backend applies the update only when both match, then increments\nthe revision. A transaction view can be selected by exactly one of transaction\nID, plan ID, or action ID and can optionally include the terminal action result\nand receipt chain. Normal transport identity, owner, tenant, and query scopes\nstill govern that read.\n\nConnector routing is action-scoped\n\nThe connector registry's immutable RouteAssignment is keyed by action ID. It\npins the capability, verified tenant, connector instance and replica, endpoint,\npeer identity and DID, version and manifest, policy and credential revisions,\nhealth revision, admission reservation, and fence token.\n\nResolving the same action ID again reuses that assignment only when capability\nand tenant still match. Retries therefore remain on the pinned route, and\ncancellation looks up that same action assignment. A reconciliation action also\nreuses it only when it carries the same action ID.\n\nThere is no transaction-ID or plan-ID lookup in the reviewed route resolver.\nDistinct plan, commit, compensation, or reconciliation action IDs resolve\nindependently and may select different replicas. A schema- or policy-only plan\ndoes not invoke a connector at all, so it creates no remote assignment.\n\nThis boundary is important for connector-local state. An integration that\nrequires plan and commit on one provider-side replica cannot infer that\nstickiness from the transaction record. It needs an action-scoped route that is\nactually reused or a provider operation that is portable across replicas.\n\nTrust and data boundaries\n\n| Boundary | Runtime evidence | What it does not prove |\n\n| Capability to caller | Supported modes, fidelity, and compensation declaration | That the connector honors the declaration or has been qualified |\n| 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 |\n| Commit claimant to store | Atomic state and revision transition | Exactly-once delivery to an external provider |\n| Runtime to connector | Trusted transaction ID, provider operation ID, and cursor | Provider outcome before the connector reconciles it |\n| Action to route registry | Immutable action-scoped assignment | Transaction-wide replica affinity across distinct action IDs |\n| Original action to compensation | Durable result or explicit partial-side-effect evidence | Guaranteed reversal or restoration of all external state |\n| Operator to transaction view | Authorized durable record, optional result, and receipts | State newer than the selected store revision or provider truth not yet reconciled |\n\nInput snapshots can contain raw action data when the capability does not require\nredaction. Transaction stores and evidence exports therefore need the same\ntenant isolation, retention, and access controls as the governed action.\n\nDesign choices and trade-offs\n• Explicit modes separate preview, ownership, recovery, and reversal intent,\n  but connector authors have to implement the declared semantics correctly.\n• A bounded plan comparison is deterministic and inexpensive, but stored plan\n  metadata is broader than the fields currently enforced at commit.\n• Atomic commit claims prevent two runtime workers from owning the same durable\n  plan, but provider-side idempotency is still required.\n• Provider-operation checkpoints make uncertain outcomes recoverable, but only\n  when a connector publishes the identifier before losing it.\n• Reconciliation blocks blind retry and preserves evidence, at the cost of a\n  provider-specific lookup path and durable work scheduling.\n• Compensation models business reversal honestly as another action, but it\n  cannot provide distributed rollback.\n• Action-scoped route pinning keeps retries and cancellation consistent, but it\n  does not create transaction-wide connector affinity.\n\nWhat transactions do not establish\n\nThe reviewed implementation does not establish any of the following:\n• distributed ACID behavior across runtime, connector, and provider;\n• exactly-once provider effects;\n• proof that dryrun or plan is side-effect-free beyond the declared and\n  implemented handler contract;\n• automatic equality of every plan metadata or identity field at commit;\n• a public planless commit merely because requiresplanbeforecommit is\n  false;\n• permission to retry an outcomeunknown operation without reconciliation;\n• automatic conversion of reconciled into committed or compensated;\n• guaranteed compensation, even when the mode is supported;\n• route reuse across distinct action IDs merely because their transaction or\n  plan ID matches;\n• process durability when an in-memory transaction backend is selected;\n• live qualification of these paths against a particular provider.\n\nRelated documentation\n• Capabilities and contracts (capabilities.md) explains how transaction,\n  idempotency, data, and compensation support is declared.\n• Actions and sessions (actions-and-sessions.md) owns action identity, queue,\n  result, cancellation, and replay behavior.\n• Approvals and policy (approvals-and-policy.md) explains the durable approval\n  gate used before commit and compensation.\n• Observe and recover (../guides/observe-and-recover.md) provides operational\n  recovery workflows for uncertain and stalled work.\n• Connector registry and routing (../architecture/connector-registry-and-routing.md)\n  owns route assignment, admission, settlement, and replica selection details.\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "6b845690294f592558e297af7f4fa815c37f55e8356edf79023072e37b7cac9a"
  }
}
