Verification is the part of a receipt that says how much the evidence is worth. It is declared per tool, evaluated by the gateway, and deliberately conservative: missing, contradictory, or ambiguous evidence becomes inconclusive or contradicted, never optimistic success.

What a tool declares

Every tool carries a required EffectSpec, and its verification field is one of three shapes: A read concludes not_required — there is no effect to prove. EffectSpec::validate refuses the one shape that would let a mutation quietly assert nothing: a non-read effect with verification: none and no no_evidence reason.

Immediate verification

A response binding is evaluated by a deterministic in-process verifier with no I/O, no clock, no randomness, and no policy lookup. It selects values through a closed selector language — request body pointer, response body pointer, response header, HTTP status — and evaluates a closed assertion language: exists, equals_literal, equals_request, one_of, and (in staged plans) absent. Assertion outcomes reach the receipt. Compared values never do. The conclusion follows from the outcomes and the trust of the observation. A failure produces contradicted only if the trust class may contradict. Otherwise any failure, any missing, any invalid, or a trust class that is not authoritative for this binding yields inconclusive. An empty assertion set also yields inconclusive — defence in depth, because a verdict must never be optimistic about evidence it did not evaluate. The binding’s trust rule chooses whether a trusted worker’s derived fields count: gateway_observed_only refuses them, allow_connector_attested admits them. gateway_observed_only is also what keeps a federated binding away from verified.

Verification plans

A VerificationPlan is immutable, versioned data. Its digest and the containing bundle digest are pinned to the invocation, so republishing a bundle can never change an in-flight verification. A plan carries:
  • a non-empty set of uniquely identified stages, each tagged immediate, readback, or webhook, each with an effect claim and its assertions;
  • an overall deadline;
  • a completion predicate: explicit (claim, trust) pairs, never an ordering.
There is no “minimum assurance” shortcut. The predicate names exactly which combinations satisfy the plan, and schema validation rejects any predicate that would let untrusted evidence produce verified.
Verification plan
A webhook stage is the only path to vendor_signed.

How a staged plan runs

1

Pin at finalization

When the invocation finalizes, the gateway pins the reviewed bundle, the plan, a digest of the connection target, and bounded safe probe parameters into workspace-qualified Redis state, alongside the scheduling projection: one job per asynchronous stage plus a deadline job. Immediate stages are evaluated at this point.
2

Control leases the work

The control worker owns durable scheduling — due times, leases, attempt counts, deadlines, correlation, retries, and Postgres projections. Jobs are keyed by invocation and stage and processed at least once. Control never synthesizes a conclusion or a receipt.
3

Control mints a single-use capability

For a readback, control mints a short-lived capability with a dedicated issuer and audience, bound to workspace, invocation, connection, stage id, plan digest, declared operation, nonce, expiry, and expected revision, and calls the internal gateway endpoint with both service authentication and that capability.
4

The gateway rechecks everything

The gateway consumes the nonce atomically, then re-verifies the connection, the plan digest, the bundle digest, and the expected revision against pinned Redis state. It resolves the probe descriptor, selectors, and auth only from the pinned plan — a caller can never supply a destination or a selector — and loads only Redis and snapshot state, failing closed rather than reaching for the control/Postgres fallback. A control-minted capability authorizes a request; its claims are never authority by themselves.
5

Evaluate, then revise

The gateway performs the readback (or reads the correlated webhook evidence), applies the deterministic verifier, and atomically advances the evidence sequence. It emits one append-only VerificationEvent per accepted evidence item, and mints a new receipt revision only when the public summary changes.
Webhook evidence follows the same shape, except the evidence arrives first and is correlated afterwards. See Webhook evidence.

Correlation

The correlation index binds workspace, connection, subscription, signed event type, and safe upstream id. It deliberately excludes arbitrary stage labels, so renaming a stage cannot make an ambiguous resource match unique. The gateway checks uniqueness and repeats the exact matched-tuple check inside the evidence commit; a correlation that matches more or fewer than one live invocation is refused. Retained candidate lookup is bounded and fails closed on overflow.

Withdrawal and contradiction

Both directions fail closed.
  • Evidence of a given trust class may withdraw a verified conclusion the same class established, returning the invocation to inconclusive while retaining the first-verified timestamp.
  • Only gateway_observed and vendor_signed evidence may assert contradicted.
  • A positive later read cannot erase an authoritative contradiction already recorded for that stage.
A deadline that expires with an unmet required stage reconciles to a terminal inconclusive revision. Pending is the initial conclusion only while an unmet required asynchronous stage remains.

Per-tier limits

What the runtime admits is narrower than what the contract can express, and a plan that fails admission is refused at snapshot validation rather than silently downgraded. The federated consequence is the one to keep in view: a federated write has no admissible readback, so its strongest reachable outcome today is accepted with untrusted or broker_attested evidence, concluding inconclusive. That is the honest ceiling, and the connector UI must state it at connect time. See Evidence trust.

Declaring the evidence a mutation already names

A routed capability (cap.*) is a provider-independent job description, so its EffectSpec cannot assume any one vendor’s response shape. It still may not opt out of proving anything: the same validation that refuses an assertion-free mutation applies to a capability spec, and because capability specs are validated as part of the workspace snapshot, one bad effect takes the whole snapshot down. The resolution is to assert the evidence the effect already names rather than to invent one. Every mutating capability declares its upstream_id at /id, so a job named cap.<noun>.create is only served by a provider whose response carries the created id — asserting that it exists is provider-independent and demands nothing a concrete binding does not already satisfy. Two details of that fix are load-bearing:
  • The trust rule is gateway_observed_only, not allow_connector_attested. A federated binding cannot reach it, which is exactly the rule that a federated route never reads verified, and it keeps a remote-MCP target out of the attestation path in the same validation pass.
  • Declaring no_evidence instead would have been false for most providers and would have made every routed mutation permanently inconclusive. Reach for no_evidence when the vendor genuinely offers no evidence path, not when the assertion is inconvenient to write.

Where verification lives

Control is the scheduler, correlator, and persistence owner. The gateway is the receipt authority. That split is what keeps plaintext credentials inside the gateway process and keeps a second component from becoming a second auditor.

Evidence trust

The five classes, the per-source table, and probe rules.

Adding a connector

Where an EffectSpec and a VerificationPlan are authored and reviewed.