Verification keeps three dimensions independent, and collapsing them is the most common way to state something false about Obol. 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. 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). 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. 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.

What this means for a federated route

A federated connector 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). 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). 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.
Delete readback stage

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). 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): 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.

Connector tiers

Native, trusted worker, and federated, and what each one can prove.

EffectSpec

The required per-tool contract that declares assertions and evidence policy.