apps/connectors would plan requests and normalize responses while the gateway performed
credentialed HTTP. That protocol is not implemented. There is no package, no image, no
deployed service, and no connector pack declares tier: trusted_worker. The compiled
WorkerExecutor still runs ADR-0014’s x-obol-upstream-authorization hand-off. Nothing
leaks today, because no worker exists to receive the header.
The rest of this page records the decision, the unbuilt protocol, and the shipped hand-off
the executor would use if a worker were added. Do not treat either as a running product
surface.
The intended job is connector velocity on the messy minority of vendors, and nothing
else. No Python and no per-vendor logic goes in the gateway process (invariant
2) — that decision is settled and deny.toml bans pyo3 by name.
What a worker is for
Under ADR-0024, a trusted worker would be a request planner and a response normalizer. That is the whole job. The tier decision would admit it only when exactly one of four things is true:- a mandatory 2–4 step sequence,
- cursor pagination that must be joined server-side into one result,
- a response shape needing a stable mapping, or
- a request shape OpenAPI genuinely cannot express.
What is not a reason to write a worker
Older guidance — ADR-0007’s own text included — predates ADRs 0020 through 0024. Each of the following is gateway-owned by decision.
A vendor whose request signing Obol cannot express as an auth profile is a reviewed
exception requiring a new ADR, not a secret hand-off. AWS SigV4 is the standing
example: it signs a canonical request with the secret, so either the gateway gains an
AwsSigV4 auth profile or the vendor is unsupported.
The credential boundary
Nothing is exposed by this today, because no worker exists to receive the header:apps/connectors ships no package and no image, and no deployment target defines a
connector-worker service. The boundary below is what ADR-0024 requires before one
returns.
Target design (ADR-0024, not implemented)
ADR-0024’s versioned protocol would be a bounded exchange in which the worker never holds a credential and never dials a vendor:1
prepare
The gateway would send the invocation, tool, canonical arguments, bundle and plan digest,
and idempotency key to the worker’s
prepare phase.2
RequestPlan
The worker would return a
RequestPlan: a declared operation id, method, relative path,
selected safe headers, and a bounded body. Never an absolute arbitrary destination, and
never a credential.3
Validate and dispatch
The gateway would validate the plan against the pinned
ConnectorBundle — endpoint and
method allowlists, schema, step budget, idempotency binding — then inject the Obol-vaulted
credential and perform the HTTP itself.4
Observe
The gateway would retain numeric status, selected headers, safe ids, body digest, and a
temporary bounded body as gateway-observed transport evidence.
5
normalize
Where normalization is required, the gateway would send only the bounded response data the
bundle allows to
normalize. The worker would return a WorkerCallReport that the
gateway converts into a DispatchObservation.connector_attested; HTTP
status, digest, and gateway-selected response fields gateway_observed. On today’s code
path the whole observation is connector_attested instead — see Evidence a worker route
can carry.
The shipped path is still ADR-0014
Under the shipped ADR-0014 hand-off, two credentials cross the gateway→worker hop and neither is an Obol virtual key — workers never see inboundob_live_ keys:
Authorization: Bearer <service JWT>— minted per call byServiceJwtMinterwithaud = obol-connectors, 60-second TTL, HS256 on the sharedOBOL_SERVICE_JWT_SECRET. It authenticates the gateway.x-obol-upstream-authorization— the rendered vendor credential, present only when the connection has one, markedsensitive, never logged, and never persisted by the worker.
worker, connection, tool, idempotency_key, last4, and an invariant test (upstream_authorization_never_logged)
holds that line.
Retry policy under ADR-0014 is exactly one retry after 200 ms, and only when the worker
declared the error retryable or the failure happened before the request was sent. A
post-send transport failure is never retried — the worker may have executed. ADR-0031
moves retry reservation into the gateway: each attempt is reserved before dispatch, and
a reservation failure means no second attempt is sent.
Evidence a worker route can carry
WorkerExecutor stamps EvidenceTrust::ConnectorAttested on the observation
(obol-mcp/src/worker/mod.rs). Two consequences follow.
connector_attestedverifies only by explicit permission. TheEffectSpecmust settrust: allow_connector_attested, ortrust_is_authoritativerejects it. A worker tool whose spec saysgateway_observed_onlycan never verify on today’s code path, because the whole observation is attested rather than just its derived fields — the gap ADR-0025’s per-source trust exists to fix.connector_attestedmay never contradict. Onlygateway_observedandvendor_signedevidence may assertcontradicted.
connector_attested may originate. Routing
federated evidence through an adapter in apps/connectors to acquire it is laundering
and is forbidden by name (ADR-0030): a federated route may never yield
connector_attested. See Evidence classes.
Worker obligations
These are the ADR-0014 contract the compiledWorkerExecutor expects. No worker exists
to honour them; they would apply if one were added on the shipped path, not under
ADR-0024’s unbuilt protocol.
- An idempotency store keyed on
idempotency_key. The gateway reuses the same key on a retry precisely so the worker’s store collapses duplicates. This is load-bearing, not optional. - No retries on non-idempotent writes without a vendor idempotency key.
- A stable error envelope. Vendor errors map to
{code, retryable, message}plusvendor_http_status;ok: falsebecomes an upstream dispatch error. A missingstructuredContentis a transport error. - Never log an
Authorizationheader or a PAN.
idempotency: unsupported forecloses redispatch entirely:
gateway_retry_allowed computes vendor_retry_safe as “the binding is not
Unsupported,” so an ambiguous responded-but-unknown result is never re-attempted. That
is correct behavior, and a real product limitation worth recording in the connector note.
Guardrails
- Hand-written workers are capped at five, each with a named reviewer, enumerated in the connector registry. A sixth proposal is a scope conversation.
- A worker is not a new deployable. When
apps/connectorsis built it will be one image hosting many connectors behind tool prefixes, scaled and circuit-broken independently — not one deployable per vendor. A standalone compiler, registry, catalog adapter, or verification service is prohibited without a new ADR (ADR-0005, ADR-0022). - Conformance fixtures are mandatory, and 2–4 days plus a security review is the realistic cost. Compare that against an overlay, which is hours.
There is nothing to deploy yet
The tier is parked (ADR-0058).apps/connectors/ contains one README and nothing else. There is no package, no lockfile,
no obol_connectors module, and no Dockerfile — the previous Dockerfile was a build recipe
for source that never existed, and was removed along with the connectors service in
Compose, infra/fly/connectors.fly.toml, and Helm’s connectors.yaml with its values
block, ServiceAccount and NetworkPolicy. No deployment target sets OBOL_CONNECTORS_URL.
No connector pack under packages/connectors/ declares tier: trusted_worker either —
all 103 bundles are native/obol. Every shipped connector executes in the gateway’s
in-process OpenAPI executor or federates over remote MCP; neither path touches this
directory.
apps/connectors/README.md records the four preconditions for an image to return:
ADR-0024’s protocol frozen in packages/proto, a Python package serving /mcp/{worker}
and /healthz, a Dockerfile citing ADR-0024 rather than ADR-0014, and deployment wiring
restored across all three infra/ targets in the same change.