gateway_observed.
Native is reserved for connectors whose receipt is the product: prod-destructive
writes, identity, money, and infrastructure mutations, and the official high-risk set
named in ADR-0030. It is not a breadth exercise — long-tail coverage stays
federated. The reviewed native cut-list and its Gate 0 reasoning
live in packages/connectors/NATIVE.md.
What you author
An OpenAPI document is an input, never the contract. The contract is the reviewed overlay plus the hand-authored bundle.The x-obol-effect extension
The importer does not guess an effect kind for mutations.
A missing extension fails the import with
"{method} operation requires x-obol-effect"
(apps/gateway/crates/obol-httpx/src/spec.rs). That is a stop condition, not a warning.
The extension’s value is deserialized and validated at import time.
destructive is a separate signal that feeds Cedar and
approvals: it is method == "delete" || x-obol-destructive == true.
It is not the effect kind, and nothing cross-checks the two. A conservative EffectSpec
gates nothing on its own — if a POST mutation must require approval in prod, set
destructive: true or write a Cedar rule.
Request body encoding
TargetSpec::OpenApi, the bundle, and the overlay all carry body_encoding (ADR-0034),
and a single tool may override it under annotations.openapi.body_encoding. It is
declared in the reviewed overlay and never inferred from the vendor’s OpenAPI
document — a spec that lists several media types, or simply gets it wrong, must not
silently decide what the gateway sends.
This changes only how the body is serialized. Credential injection, the host allowlist,
idempotency binding, and evidence trust are unaffected: a native form-encoded call is
gateway_observed exactly as a JSON one is.
Dispatch annotations
annotations on a tool is executor dispatch config only — never a place to park
effect data.
Egress and probe targets
The bundle declares its own egress policy and any probe target a readback stage names.How a native call executes
1
Admission
The Obol virtual key authenticates, CEL prefilters visibility, and Cedar decides
permission against the concrete tool and its arguments. Default-deny.
tools/list never
shows a tool the key cannot call. A destructive tool in prod requires approval unless
policy explicitly grants it.2
Invocation identity
One stable
InvocationId and one Obol idempotency key are minted before dispatch and
retained across approval, transport retries, and safe redispatch (ADR-0019). Idempotency
is coordinated atomically in Redis, keyed by workspace, resolved connection, tool, and
key. A request-hash conflict never dispatches.3
Request construction
Arguments are validated against the tool’s input schema before any dispatch. Bad
arguments mean the vendor receives nothing. Path, query, header, and body are filled from
the schema mapping; the body is serialized per
body_encoding.4
Egress check and credential injection
Netguard resolves and screens the destination — loopback, private, link-local, multicast,
and metadata ranges are refused — pins the resolved address, and revalidates every
redirect. The vaulted vendor credential is unwrapped inside the gateway process only
(invariant 1) and injected on the outbound hop. See
Credential brokering.
5
Idempotency propagation
If the
EffectSpec declares a header or body_pointer binding, the Obol key is
propagated outbound under it. unsupported propagates nothing — and forecloses automatic
redispatch after an ambiguous attempt.6
Observation
The executor normalizes the result into a non-serializable
DispatchObservation stamped
EvidenceTrust::GatewayObserved (obol-httpx/src/lib.rs). The numeric vendor status is
preserved on every failure path, including an off-schema 2xx, which becomes
inconclusive rather than a client-side schema error.7
Verification and receipt
The deterministic verifier applies the
EffectSpec’s response binding and produces a
versioned InvocationReceipt plus an append-only VerificationEvent. Raw bodies,
credentials, and compared values never enter a receipt or a log — an assertion result
carries a name and an outcome (passed, failed, missing, invalid) and nothing
else. See Receipts.Evidence a native route can carry
Native is the only tier that reaches the top of the trust vocabulary on its own.- Immediate transport facts and derived fields are both
gateway_observed(ADR-0025). - An HTTP readback against an Obol-custodied connection is
gateway_observed. - An MCP readback is
broker_attestedeven here, because an MCP server is by construction an authenticated intermediary rather than the originating vendor. - A directly verified vendor webhook is
vendor_signed.
gateway_observed and vendor_signed evidence may assert contradicted. See
Evidence classes.
Readback and webhook stages
A native pack’s overlay is where asynchronous evidence is declared, because aVerificationPlan may only live in an overlay or bundle — never on a published
ToolSnapshot (see EffectSpec for why that rule is
load-bearing).
Readback
A readback probe never carries a host, URL, or connection string. It names aProbeTargetId declared in 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 resolving to a scalar
(ADR-0025).
http and mcp — the two transports the gateway already
speaks. Database, message-broker, filesystem, gRPC, and object-store probes are refused
permanently, not deferred (ADR-0025). An HTTP path_template must be origin-relative
and start with /; that is what rejects an absolute URL. An MCP probe admits no
request-derived argument at all.
absent assertions are legal only on kind: delete, and only against a decisive
negative the probe declares — HttpProbe.decisive_negative, restricted to 404 and 410
(ADR-0035). An errored probe yields missing, never a passing absence.
The gateway serves readbacks at /internal/v1/verification/readback, driven by the
control worker’s scheduler with a short-lived single-use capability bound to workspace,
invocation, connection, stage, plan digest, and nonce. The caller can never supply a
destination or a selector.
Webhook
A webhook stage is the only route tovendor_signed. The bundle pins the signature
profile, header, timestamp tolerance, and declared event types; only signature schemes in
the closed SignatureProfile registry can be expressed.
/webhooks/{workspace}/{connection}/{subscription}, aliased at /hooks/…). The gateway
verifies the original bytes, timestamp, and replay identity before parsing. An
invalid signature produces security telemetry only — never evidence, and never a
contradiction. If a vendor’s scheme is not in the registry, author "webhook": null and
say so in CONNECTOR.md; several shipped packs do exactly that.
Traps worth knowing before you author
- Never publish a
VerificationPlanon aToolSnapshot. The runtime verifier matches onlyresponse_binding. A published tool carriesverification: none(reads) or a bareresponse_binding(mutations); plans stay in the overlay or bundle. destructiveis set by hand. Nothing derivesBundleTool.destructivefromeffect.kind, so a mutation with the flag unset skips prod approval.- Do not write tautologies. An assertion derivable from
dispatch_statuses—existsoverhttp_statusagainstaccepted: [200..=299]— mints a green check from no evidence. Nothing enforces this rule; it is on the author and the reviewer. If a tool genuinely has no evidence path, declare it (ADR-0036) rather than fabricating one. - Author the real idempotency binding. Never default to
unsupportedbecause the vendor brief was thin;unsupportedforecloses automatic redispatch and is a real product limitation worth writing down.