AccessDenied from a policy that refused the call and an AccessDenied from a backing store that refused
it are the same error. Reading one, an operator either goes and audits policies that are fine, or opens a
ticket with a cloud provider that did nothing.
Start with Debugging Service Adapters when you need more than the failing layer. Explain can show the selected mapping and, for covered requests, the decisions and constraints recorded by the adapter. Error provenance and Explain complement each other: the header below names a failure layer, while an explanation report describes the request path. Neither proves that an uncertain write had no effect.
The x-t9-request-error header
A failed response can carry a header naming the layer that produced the failure:
The header rides beside the AWS error rather than inside it. The status, the code, the message and the
envelope your SDK reads are unchanged, so retry and error handling that keys on the error code behaves
exactly as it did.
An absent header means the layer is not stated
It is not a hint about which layer failed. The header is present only when the adapter can identify one failure layer without weakening the origin service’s security behavior. Otherwise the response omits it rather than naming a layer that the available evidence does not establish. Treat a response without the header as having unstated provenance. Use Explain, origin request identifiers and target-state checks to narrow the failure without inferring a layer from absence.Where the header is available
The following service paths can state the listed values. Do not assume the same coverage for an adapter or failure path that is not listed.
The
authorization row is short on purpose. Telling your own policy denial apart from a refusal by the
service behind the adapter is the question this header exists for, and only the adapters listed there answer
it. Everywhere else, a policy denial arrives with no header at all.
DynamoDB is the one to reach for if you want to see both halves. A refusal from your policies says
authorization, a refusal from Spanner or Cosmos says backend, and the two are otherwise byte-identical
to your SDK.
That distinction matters more than it looks. On an adapter listed under backend but not under
authorization, a refusal from the backing service says backend while a refusal from your policies can
omit the header, so it is tempting to read an absent header as “not the backend”. Do not. Absence means
unstated. This includes responses where a single layer cannot be named safely and S3 paths that relay a
store response unchanged. The header tells you something when it is present and nothing when it is absent.
S3 needs one more sentence. Where the adapter translates a store failure into an S3 error it states
backend. Where it runs against an S3-compatible store and relays that store’s own response unchanged,
there is no translation step and no header.
Not every refusal states a layer, and that is deliberate. The S3 adapter answers the same AccessDenied for
a signature that did not verify and for a bucket owned by another account, so that a refusal tells an
unauthenticated caller nothing about which of their guesses was closest. Neither may claim a layer, so
neither does: a response with no header is one where naming a layer would have been a guess.
Related topics
- Debugging Service Adapters: Use Explain, collect a report safely and coordinate a BYOC investigation
- Explain request and response reference: The shared
x-t9-explaincontrols, receipts and report lifecycle - Cross-Cloud IAM: What the
authorizationlayer evaluates, and which policies survive adaptation - Service Adapters: The adaptation tiers, and which services run through an adapter at all
- Observability: Routing appliance telemetry to your own sinks