Skip to main content
An origin request and its explanation can finish at different times. The client may receive a native success or failure while the report is still partial, and downstream work can continue after the accepting call returns. Keep four states separate:
  1. Origin request state: accepted, refused, failed or completed according to the origin API.
  2. Target operation state: planned, attempted, accepted, converged, failed or unknown.
  3. Report state: not yet visible, collecting, sealed, consumed, expired or unavailable.
  4. Owner state: whether the request owner’s authenticated principal has exported or acknowledged the report.
A change in one state does not imply the others changed.

Lifecycle of an asynchronous explanation

Text equivalent: the adapter begins capture and, in ExecuteAndExplain, performs enough of the target interaction to produce the origin-native response. It returns that response and a report ID even though background persistence may still be pending or may fail. A coordinator or worker can append later target or child-work evidence and seal a final revision. The authenticated report owner exports needed evidence and acknowledges that exact revision. In Explain mode, the target API request lane is not executed. The report can still include hypothetical target mapping and evaluated constraints. In ExecuteAndExplain, the operation has real effects and a lost report response does not undo them.

Preserve the receipt and native response separately

An executing response can carry:
Store those values alongside the origin status, body, request identifiers and exact operation identity. The report ID is a correlation locator: it proves neither target completion nor successful report persistence. Conversely, report unavailability is not proof that the operation failed. If the response body is lost but the headers and report ID were captured:
  1. do not immediately replay the origin operation;
  2. inspect the origin operation’s idempotency contract;
  3. query the target or origin status operation by exact identity where supported;
  4. poll report-123, not the mutating request;
  5. retain every observed report revision and native state.
If no HTTP response or report ID survived, there is no safe report identifier to poll. Do not guess one. Use the exact operation or resource identity, the origin status API where supported, target-state inspection and the operation’s idempotency contract before deciding whether another mutation is safe.

Read revisions monotonically

A report can be available before it is sealed. Each read includes a revision. Make the first direct GET without minimumVersion. On later reads, request at least the last recorded revision to prevent regression, or request the next revision only when waiting for strictly newer evidence. The read never waits: it either returns the current qualifying snapshot or a disposition. Formatting an existing report as Human, Agent or JSON does not create a new revision and does not run work again.

Poll through the origin status operation

For a declared status read, an explanation-aware client can request the native status and explanation snapshot together:
This is a signed request. It does not use x-t9-explain, because it selects an existing report rather than starting a new explanation. Retain the exact operation identity. A provider-assigned ID recorded by an earlier revision is stronger than a display name. If the ID is not yet known, report that gap and wait for the relevant observation instead of selecting a similarly named resource. The envelope’s outer HTTP status is 200. It preserves the origin-native status, headers and body inside explanationEnvelope.native while including the explanation snapshot. An explanation-aware client unwraps the embedded native response before SDK retry or error classification and records the report revision separately. A status read should not be treated as a second create, publish or delete.

Follow causality, not adjacency

Asynchronous reports can contain evidence from several producers. Capture order is not necessarily a cross-service clock. Link child work to its parent through explicit causal fields:
  • parent and child span relationships;
  • causedBy event IDs;
  • intent and operation references;
  • producer-local sequence numbers;
  • facts with named supporting facts or events.
Do not attribute pre-existing target activity or unrelated customer traffic to the explained request merely because it appeared during the same time window. Retries need the same care. Each execution attempt can have its own report, and one report can record multiple target attempts. Preserve attempt and report identities instead of flattening them into “the retry succeeded.” A superseded attempt remains relevant when determining whether an earlier mutation had effects.

Reason about cancellation and lost responses

Cancellation of a client wait does not necessarily cancel provider work. If the client gives up while the request is still admitted, before conclusion or coordinator handoff, the adapter seals the report with an unknown outcome. After handoff, the report remains collecting and the coordinator owns later evidence and sealing. After conclusion has begun, the report seals according to that concluded result even if the client loses the transport response. Record separately:
  • whether the origin client stopped waiting;
  • whether the adapter accepted cancellation;
  • whether a target operation had already been attempted;
  • whether the target supports cancellation;
  • what later status observation was captured;
  • whether the report sealed or expired first.
If a report is incomplete, it can say that completion is unknown. Do not rewrite that as failure. If a provider accepted work but no final observation was captured, the safe next action is a status read or target-state inspection, not a blind replay.

Cross-service example: one publish, two recipients

Consider an SNS-shaped Publish adapted to two SQS-shaped recipients. The evidence model must keep these milestones distinct:
  1. the publish request is authenticated and accepted;
  2. recipient A is selected;
  3. recipient B is selected;
  4. delivery to A is attempted;
  5. queue A accepts or refuses the message;
  6. delivery to B is attempted;
  7. queue B accepts or refuses the message;
  8. any later consumer processing occurs outside queue acceptance.
One recipient’s success does not establish its sibling’s success. Queue acceptance does not establish downstream consumption or business processing. Publish acceptance does not establish that both deliveries settled. In Explain mode, the report can describe recipient selection and planned delivery paths without enqueueing a message. In ExecuteAndExplain, a useful report connects each delivery attempt and queue result to the publish using explicit causal references. The following is the structure to look for, not a captured provider transcript:
Do not infer these rows from the topology alone. Use the actual report’s events, facts, identities and revisions. A production claim about SNS-to-SQS delivery requires service-specific captured evidence; evidence from an S3 upload or a different messaging path cannot substitute for it.

Keep acknowledgment with the report owner

Only the authenticated principal recorded as the report owner can read or acknowledge through the report HTTP routes. For asynchronous incidents, decide which application or collector uses that owner identity and serializes acknowledgment attempts. Before acknowledging:
  • confirm the report is sealed;
  • use the exact x-t9-explain-acknowledge-revision supplied by the read;
  • export the formats needed by other people or systems through an approved, sanitized channel;
  • sanitize shared evidence without breaking event/fact references;
  • preserve the native response and target-state checks separately.
An identical acknowledgment retry by the same owner and sealed revision is idempotent and returns Consumed. A different revision returns RevisionMismatch. Do not have two components using the owner identity race to acknowledge; serialize the attempt and preserve the exported bundle first. See Diagnostic Response Headers for read and acknowledgment statuses, and Troubleshooting Explanations for missing receipts, unavailable reports and SDK retry behavior.