--- title: "Connector tiers" description: "The ordered tier decision: Gate 0 on risk class, then the first-match table that fixes custody, executor, and evidence ceiling for the life of the connector." --- The tier is not a preference. It fixes, for the life of the connector, who holds the vendor credential, who executes the call, and what evidence trust every receipt can ever carry. Decide it explicitly, cite the vendor brief, and record the reason in the pack's `CONNECTOR.md`. You do not choose a tier directly. You author `custody`, `kind`, and `target`, and `ConnectionSnapshot::tier()` reads them back — see [Connectors overview](/connectors/overview) for the derivation. The table below tells you which triple to write. Governing ADRs: **0020** (three tiers, `ConnectorBundle`; superseded in part by 0030), **0024** (worker protocol, accepted-not-implemented; parked by ADR-0058), **0030** (federated catalogs as primary coverage, Proposed — but its authoring rules already bind through `AGENTS.md` invariant 9 and PRD §6), **0025** (per-source evidence trust, Proposed). ## Gate 0 — risk class, before the table Run this first. It decides which half of the table you are allowed to land in. **A prod destructive tool, or an official/high-risk connector, is native or trusted worker. Federation is default-deny there.** The trusted-worker landing is parked (ADR-0058): there is no worker deployable, so choosing it today is proposing to build one, not landing on a running tier. A prod destructive tool that cannot be native has nowhere to go until ADR-0024 is built. ADR-0030's policy supremacy clause: prod destructive on a federated route is default-deny, and permitting it is an explicit recorded workspace decision. The reviewed Stripe and GitHub paths stay native or trusted-worker. If the vendor is reachable *only* through a broker or a remote MCP server, the tool cannot ship as prod destructive — ADR-0024 rejects running connectors as remote third-party MCP because that "cannot provide native custody or gateway-observed transport evidence." Say so and stop. **Otherwise, for long-tail breadth, prefer federated.** ADR-0030 adopts Nango and Composio as v1 brokers and makes federation the primary path to breadth. Building a native or worker connector for a long-tail vendor is the treadmill ADR-0020 and PRD §6 both forbid. Federation is cheap coverage bought with evidence. ADR-0030 states the price exactly: for a purely federated invocation with no asynchronous stage, the fraction of receipts that can ever read `verified` is exactly zero, enforced at validation time. **Do not federate anything whose value is the receipt.** ## The decision table Ordered. First match wins. Answer each row from the vendor brief produced by [research](/connectors/adding-a-connector), not from memory — walking it from memory is how a form-encoded body or an OAuth-only vendor gets discovered *after* the tier is recorded. Nango, Composio, another catalog, or a customer-run server. If yes → **federated**, stop. Author `custody: federated`, `kind: catalog_remote`, `broker_ref` set. Obol stores only a workspace-scoped upstream credential or an account-scoped reference. Pooled broker accounts and a shared broker OAuth app are forbidden ([invariant 8](/concepts/invariants), ADR-0030) — a federated connection uses the customer's own OAuth client. If yes → **federated**, stop, even when Obol vaults the MCP bearer token. Author `target: mcp_remote`. `tier()`'s own doc comment puts it plainly: remote MCP is federated even when Obol vaults the MCP bearer, because the upstream still owns the vendor connection. `RemoteMcpExecutor` stamps `EvidenceTrust::Untrusted` and returns no numeric vendor status and no upstream id (`apps/gateway/crates/obol-mcp/src/relay/mod.rs`). One request whose arguments map to path, query, header, or body by JSON Schema, against a declared host allowlist, with Obol holding the vaulted credential. If yes → **native**. Author `kind: openapi`, `target: open_api`, `custody: obol`. This is the strongest observation boundary: `OpenApiExecutor` stamps `EvidenceTrust::GatewayObserved`. See [Native connectors](/connectors/native). Fixed GraphQL is native: declare N `POST /graphql` operations with the query document pinned in the bundle body and arguments filling variables. It is not a worker reason. (a) a mandatory 2–4 step sequence, (b) cursor pagination that must be joined server-side into one result, (c) a response shape needing a stable mapping, or (d) a request shape OpenAPI genuinely cannot express. If yes → **trusted worker**. Author `target: worker`, `custody: obol`. Under ADR-0024 the worker would plan and normalize and the gateway would perform the credentialed HTTP. **That protocol is not implemented** and there is no worker deployable, so choosing this tier today means proposing to build one, not adding to something running. The shipped executor would still pass the credential to the worker (ADR-0014). See [Trusted workers](/connectors/trusted-workers). **Stop.** Propose a new ADR. ADR-0024: unsupported signing or native-SDK behavior stays a reviewed exception requiring a new ADR, not a raw secret hand-off. ## What is no longer a reason to write a worker ADR-0007 and older guidance predate ADRs 0020–0024. Each of the following is gateway-owned by decision; none justifies tier 2. | Old reason | Where it lives now | |---|---| | OAuth exchange and refresh | Gateway (ADR-0023). See [OAuth](/security/oauth) | | Webhook receipt and signature verification | Gateway (ADR-0021, ADR-0022). Raw webhooks are signature-checked by a trusted boundary before any mapping code sees them | | Credential injection, SSRF, egress policy, redirects | Gateway (ADR-0024 destination, ADR-0020). **That protocol is not implemented** — on the shipped ADR-0014 path, netguard is skipped for Worker targets because the gateway is not the one dialling out | | Idempotency keys, retries, receipts, redaction | Gateway (ADR-0019; ADR-0031 gives the gateway the worker's retries too) | | Pagination the agent can do itself | Not a reason. Only *mandatory* server-side joins are | ## What each tier costs and buys The trusted-worker column mixes the **ADR-0024 target** with today's compiled executor. **That protocol is not implemented.** On the shipped ADR-0014 path `WorkerExecutor` stamps `connector_attested` on the whole observation, and there is no worker deployable. See [Trusted workers](/connectors/trusted-workers). | | Native | Trusted worker (parked) | Federated | |---|---|---|---| | `TargetSpec` | `OpenApi { base_url, spec, allowed_headers, body_encoding }` | `Worker { worker }` | `McpRemote { url }` | | Executor | `OpenApiExecutor` (`obol-httpx`) | `WorkerExecutor` (`obol-mcp`) | `RemoteMcpExecutor` (`obol-mcp`) | | Trust stamped on the immediate observation | `gateway_observed` | `connector_attested` | `untrusted` | | Per-source trust (ADR-0025) | transport and derived both `gateway_observed` | ADR-0024 target: transport `gateway_observed`, derived `connector_attested`. Today the whole observation is `connector_attested` | immediate facts `untrusted`; readback `untrusted` unless a workspace-scoped identity is authenticated → `broker_attested` | | Routing evidence ceiling | `gateway_observed` | `connector_attested` | `broker_attested` | | Can reach `verified` | Yes | Only with `trust: allow_connector_attested` on the `EffectSpec` | **No** | | Can `contradict` | Yes | No — only `gateway_observed` and `vendor_signed` may contradict | No | | Vendor credential custody | Obol vault, injected on the outbound hop | Obol vault (see the note in [Trusted workers](/connectors/trusted-workers)) | Third party; Obol holds a `FederatedBrokerRef` only | | Enterprise self-host | Full | Parked — no deployable (ADR-0058) | Requires the customer's own broker account | | Marginal effort | ~1 h without an overlay; +4–8 h engineering and ~2 h review with one | 2–4 days plus security review and conformance fixtures | ~1–2 h, plus drift review and a per-call broker fee | The routing ceiling row is enforced, not advisory: `apps/gateway/crates/obol-types/src/routing.rs` computes the maximum trust per tier and excludes any binding whose declared `evidence_ceiling` exceeds it, with reason `EvidenceFloor`. ## Guardrails - **Hand-written workers are capped at five**, each with a named reviewer. A sixth proposal is a scope conversation, not an authoring task. - **Federated tools still execute.** A remote MCP server can run a destructive tool; its result is simply never `verified`. Gate those with [Cedar and approvals](/policy/approvals), and render them as *executed — not verified* rather than as a green check. - **Freeze federated schemas.** Remote MCP tool lists are introspected once and frozen into a reviewed workspace snapshot, never trusted to change during a call (ADR-0020). Re-introspect on a schedule and land drift as a reviewed republication, never a hot swap. New tools arrive invisible until policy names them. - **Revocation is two-step for federated connections** and must be disclosed as such: Obol-key revocation covers the Obol hop only; control must call the broker's disconnect API (ADR-0030). - **Namespacing** (ADR-0015): `{connector}.{tool}`, split on the first dot. Connector prefixes are validated dot-free and `[a-z0-9_-]`; upstream names may contain dots (`stripe.charges.list` → connector `stripe`, upstream `charges.list`). Identities are capped at 256 characters. ## Known vendor blockers - **AWS (S3, SQS, SigV4) breaks the ADR-0024 model.** SigV4 signs a canonical request with the secret, which a worker must never receive under ADR-0024. **That protocol is not implemented**; on the shipped path the worker would receive the secret (ADR-0014). Either the gateway gains an `AwsSigV4` auth profile — a bounded standard scheme — or AWS is unsupported. Both need a decision on the record; neither is a secret hand-off. - **Composio is barred from the destructive path** (ADR-0030). Its credential custody is closed on every tier, so hybrid custody is structurally unavailable, and its normal execution path returns a computed `successful` boolean with no vendor status and no typed vendor object id. Use it as a read-biased long-tail source only. - **Nango requires the `proxy-forward-all-response-headers` account flag** — ADR-0030 calls it a contractual precondition, not a configuration detail: without it the vendor's request id never reaches a receipt. Use Nango's raw proxy, never its normalized action endpoint, because the proxy is the only shape that preserves the vendor's numeric status. - **Form-encoded bodies are no longer a native blocker.** ADR-0034 added `body_encoding` to `TargetSpec::OpenApi`, the bundle, and the overlay; `obol-httpx` writes bracket-nested `application/x-www-form-urlencoded`. Stripe and Twilio ship as native packs.