---
title: "Evidence trust"
description: "The five evidence trust classes Obol assigns from the route, which connector tier can reach which, and why untrusted evidence can never produce a verified conclusion."
---
Verification keeps three dimensions independent, and collapsing them is the most common way to state something false about Obol.
| Dimension | Values | What it says |
|---|---|---|
| Effect claim | `accepted`, `persisted`, `delivered` | What the evidence claims happened. A `202` is `accepted`, never `persisted` |
| Evidence trust | `gateway_observed`, `vendor_signed`, `connector_attested`, `broker_attested`, `untrusted` | Where the evidence came from |
| Verification conclusion | `not_required`, `pending`, `verified`, `contradicted`, `inconclusive` | What Obol concluded from it |
`verified` is a **conclusion**, not a trust class. There is no trust class named `verified`, and no evidence class entitles a call to that conclusion on its own — a plan's completion predicate has to name the specific `(claim, trust)` pair that satisfies it.
## The five trust classes
Obol assigns trust from the configured route and the evidence source. An upstream payload cannot select or upgrade its own class, and neither can a worker, a remote MCP server, or a catalog broker.
| Class | Source | May produce `verified` | May produce `contradicted` |
|---|---|---|---|
| `gateway_observed` | Obol made the request and read the response itself | Yes | Yes |
| `vendor_signed` | Obol validated the vendor's own signature over the original bytes | Yes | Yes |
| `connector_attested` | A reviewed first-party worker derived the field | Yes | No |
| `broker_attested` | An authenticated intermediary — a catalog broker, or an MCP server — reported it under a workspace-scoped identity Obol authenticated | Yes | No |
| `untrusted` | Anything else a federated route returned | No | No |
Two asymmetries in that table are deliberate.
**Only `gateway_observed` and `vendor_signed` may contradict.** A hostile or broken federated upstream must not be able to raise a customer-visible alarm about a real effect. Everything below that line can fail to prove a claim; it cannot disprove one.
**`untrusted` can do neither.** Schema validation rejects any completion predicate that would let `untrusted` evidence reach `verified`, so this is not a runtime convention you can configure around — it is refused at publication.
## Which tier reaches which class
Trust is derived per evidence source, not per stage, so one receipt can carry gateway-observed transport facts beside connector-attested derived fields without either laundering the other ([ADR-0025](/connectors/tiers)).
The trusted-worker column is the **ADR-0024 target**, not today's behavior. That protocol
is unimplemented and no worker deployable exists; on the shipped ADR-0014 path
`WorkerExecutor` stamps `connector_attested` on the *whole* observation, so its transport
facts do not reach `gateway_observed` — the gap ADR-0025's per-source trust exists to
close. See [Trusted workers](/connectors/trusted-workers).
| Evidence source | Native (Obol custody) | Trusted worker (ADR-0024 target) | Federated |
|---|---|---|---|
| Immediate transport facts | `gateway_observed` | `gateway_observed` (today: `connector_attested`) | `untrusted` |
| Immediate derived fields | `gateway_observed` | `connector_attested` | `untrusted` |
| HTTP readback | `gateway_observed` | — | `untrusted`, or `broker_attested` if Obol authenticates a workspace-scoped identity at the far end |
| MCP readback | `broker_attested` | — | `untrusted`, or `broker_attested` under the same condition |
| Direct vendor webhook | `vendor_signed` | `vendor_signed` | — |
| Broker-forwarded webhook | — | — | `broker_attested` once Obol authenticates the broker identity, otherwise `untrusted` |
An MCP readback is `broker_attested` even against an Obol-custodied connection, because an MCP server is by construction an authenticated intermediary rather than the originating vendor, and Obol validated no vendor signature there.
The contract above is wider than what the runtime currently admits. Readback stages are admitted only on a native, Obol-custody, OpenAPI connection; MCP and broker readbacks are rejected at admission. See [per-tier limits](/receipts/verification#per-tier-limits).
### What this means for a federated route
A [federated connector](/connectors/federated) executes through a catalog broker that holds the customer's vendor credential and constructs the vendor request. Obol did not observe the hop, so:
- A federated read concludes `not_required` — reads are not verified because there is no effect to prove.
- A federated write concludes `inconclusive`. Its immediate facts are `untrusted`, which cannot satisfy the authoritative-trust check, and `broker_attested` webhook evidence still has to satisfy a completion predicate that names it.
- A federated route can never read `verified`, and never `contradicted`.
The connector UI must state this ceiling at connect time, not at audit time. A dashboard that renders five trust classes as one green check has made verification weaker, not stronger.
## Where the assignment happens
The gateway normalizes each executor's result into a non-serializable `DispatchObservation` carrying dispatch state, evidence trust, the numeric vendor status, safe ids, a response digest, and temporarily selected evidence. A deterministic in-process verifier turns that into the receipt's verification summary. There is no I/O, no clock, no randomness, and no policy lookup in the verifier ([ADR-0019](/concepts/invariants)).
Workers return a bounded, versioned wire report that the gateway converts into that observation. A worker cannot declare a call verified on any path. ADR-0024 additionally requires that it never see a vendor credential and never call a vendor; that protocol is unimplemented, and the shipped path still hands the credential over (ADR-0014). No worker deployable exists to exercise either.
## Readback probes
A readback stage carries a closed, tagged `ReadbackProbe` union restricted to transports the gateway already speaks: `http` (a `GET` or `HEAD`) and `mcp` (a `tools/call` on a bundle-declared read tool). Database, message-broker, filesystem, gRPC, and object-store probes are refused permanently, not deferred — reading a queue means consuming it, which competes with the customer's real consumer.
A probe never carries a host, URL, or connection string. It names a `probe_target_id` resolved only against the pinned bundle, and its inputs are restricted to:
- the invocation's safe upstream id,
- a bundle-authored literal, or
- for HTTP path and query parameters only, a JSON Pointer into the canonical arguments that resolves to a scalar.
MCP probe arguments admit no request-derived input at all: the bundle chooses the tool, and a caller-parameterized upstream call would otherwise bypass the CEL prefilter, Cedar, and approval.
### Proving a deletion
An HTTP probe declares which statuses constitute a decisive negative for its resource, and only `404` and `410` are admissible ([ADR-0035](/connectors/effectspec)). A response matching one of them is an observation the verifier evaluates, not an errored probe. Any other non-success status is an errored probe and resolves assertions to `missing`.
`403` means the caller may not look, `401` that it did not authenticate, `422` that the request was malformed, and a 5xx that the vendor failed. Each is consistent with the resource being intact, so none may be declared. An `absent` assertion is legal only on a `delete` effect.
```json Delete readback stage
{
"stage_id": "gone",
"kind": "readback",
"claim": "persisted",
"after_s": 5,
"probe": {
"transport": "http",
"probe_target_id": "pt_vendor",
"method": "GET",
"path_template": "/v1/contacts/{id}",
"path_params": [{ "name": "id", "input": { "from": "upstream_id" } }],
"decisive_negative": [{ "type": "exact", "status": 404 }]
},
"assertions": [
{ "type": "absent", "name": "contact_gone", "actual": { "source": "response_body", "pointer": "/id" } }
]
}
```
## Screening a broker's response
The gateway's federated connect-session hop asks Nango or Composio to open a hosted authorization link, and reads exactly one value out of the answer: the URL to send the operator to.
That response is screened for **authority-shaped keys** at any depth — `target`, `policy`, `evidence`, `receipt`, `idempotency`, `idempotency_key`, `retry`, `credential` — and the call fails if one is present. Every other field is ignored ([ADR-0038](/connectors/federated)).
The distinction matters. Refusing a body because a vendor added a benign field couples every federated connection in the fleet to that vendor's changelog and buys no security. Refusing a body because a broker tried to assert authority is invariant 9 enforced: a broker that offers Obol a receipt, a trust level, a policy decision, a target, or an idempotency key is confused or hostile, and that is worth failing on rather than parsing and quietly filtering.
## Mutations with no evidence path
Some vendors offer nothing to verify against: an empty `200` on success, no read-by-id endpoint, and no signed webhook any reviewed signature profile can express. A rule that forbids saying so produces fabricated assertions — an `http_status == 200` check against an accepted range of `200..=299` proves nothing and yet passes, which is worse than an empty claim.
So a mutation may declare `no_evidence` with a closed reason ([ADR-0036](/connectors/effectspec)):
| Reason | Meaning |
|---|---|
| `empty_response_body` | The vendor returns no content on success |
| `no_readback_endpoint` | No read-by-id exists for the mutated resource |
| `effect_leaves_vendor_state` | A send, notification, or payment whose effect is not readable from the vendor at all |
| `write_only_transport` | The transport admits no safe-method readback — the GraphQL POST-only case |
The rules are validated: `no_evidence` is legal only on a mutation, a tool declaring it must declare `verification: none`, and the reason must come from the enum. Such a tool concludes permanently `inconclusive` — never `verified`, never `contradicted` — which is the honest verdict. The connector UI has to render it at connect time.
## Related
Native, trusted worker, and federated, and what each one can prove.
The required per-tool contract that declares assertions and evidence policy.