Stop conditions
Refuse the task and name the ADR that forbids it.Step 0 — ADR check
Always first. Grepdocs/adr/ for the vendor and the mechanism, read docs/adr/README.md,
and name in your write-up which ADR governs each decision. Reversing an accepted one means
stopping and proposing a superseding ADR from docs/adr/template.md.
Accepted and binding: 0002 language split, 0004 credential custody, 0005 four apps, 0006
Postgres off the hot path, 0015 namespacing, 0017 contract pack, 0018 crate DAG and fleet
TDD, 0019 receipt authority and the required EffectSpec, 0020 tiers, 0021 async
verification, 0022 sandboxes, 0023 OAuth, 0024 worker HTTP, 0034 request body encoding,
0038 broker authority screening.
Proposed — read before designing, never cite as settled: 0025 (two ADRs share the number,
so cite
0025-closed-probe-union-existing-transports.md by filename), 0030 federated catalogs,
0031 bounded outbound MCP, 0035 decisive negative, 0036 evidence-free mutation.
Step 1 — Research the vendor
Before the tier table. Two MCP servers, two jobs:
Shortlist only the operations you intend to expose — a full spec lands inline in
TargetSpec::OpenApi.spec and is republished to every replica on any workspace change.
The output is a vendor brief, recorded in the pack’s CONNECTOR.md. Every later step
cites it:
Step 2 — Pick the tier
Run Gate 0, then the ordered first-match table in Connector tiers. Answer every row from the Step 1 brief, not from memory. Record the tier, theCustodyMode, and the reason — the tier fixes evidence trust for the
life of the connector.
Step 3 — Contract-first order
In this order (AGENTS.md, ADR-0017):1
Wire types
apps/gateway/crates/obol-types/src/*.rs, with a schemars derive.2
Schema and example
Regenerate into
packages/proto/ with make -C apps/gateway schemas. Hand-editing a
schema fails CI’s drift gate.3
The overlay
The hand-authored
ConnectorOverlay: effect, idempotency, verification, egress, probe
targets, webhook, body_encoding.4
Cedar
packages/cedar/ for any new attribute, mirrored into packages/fixtures/cedar/.5
Rust importer, executor, verifier
obol-httpx, obol-mcp, obol-route.6
Worker
Only if the tier is trusted worker, in
apps/connectors/.7
Fixtures and conformance
packages/fixtures/.8
Docs
ADR plus
ARCHITECTURE.md plus GLOSSARY.md, in the same PR.apps/gateway/scripts/check-dag.sh (it iterates a
hardcoded list, so an unlisted crate is unconstrained), a row in ARCHITECTURE.md §3.1,
and — ADR-0018 being immutable — a new ADR carrying it.
Step 4 — Author the EffectSpec
Every tool gets one. Write it into the overlay underoperations[].effect; see
EffectSpec for the full field reference, the traps, and every
validation error. The vendor brief supplies the real inputs: the idempotency binding, the
definite-reject statuses, and the upstream_id pointer.
Gate it:
Step 5 — Author the VerificationPlan, or choose none on the record
Stages are immediate | readback | webhook, at most 8. CompletionPredicate names
acceptable (claim, trust) pairs explicitly — no ordering, no minimum-assurance shortcut
— and validation rejects any predicate holding untrusted.
Readback probes are http or mcp and nothing else. HTTP is a GET or HEAD on a
/-leading template against a declared ProbeTargetId; MCP is tools/call on a declared
read tool, admitting no request-derived argument. A probe never carries a host, URL, or
connection string.
Trust follows the probe, not the stage: an HTTP probe yields gateway_observed, an
MCP probe only broker_attested, because an MCP server is an authenticated intermediary
rather than the originating vendor.
Choosing none — or a permanent inconclusive via no_evidence — is legitimate. Write
down why, in CONNECTOR.md, with the same standard of evidence as the tier decision.
Step 6 — Cedar
Tool names are{connector}.{tool}, split on the first dot; prefixes must be dot-free and
[a-z0-9_-] (ADR-0015). tools/list must never show a tool the key cannot call
(invariant 6).
A new Cedar entity attribute has two producers, and they must move in one PR: the
gateway’s tool_entity() synthesis in obol-policy, and every published entities_json —
packages/cedar/fixtures/, its byte mirror packages/fixtures/cedar/, and
packages/fixtures/snapshots/*.json. While both producers live in this repo the attribute
may be required, because entities are validated against the schema at compile time, so
a missed producer fails CI rather than production. That window closes once
control publishes entities_json from Postgres on its own deploy
cadence; after that a required attribute is a cross-plane breaking migration and new
attributes default to optional.
Step 7 — Fixtures and conformance
Fixtures live inpackages/fixtures/. Never write a test that hits a live vendor.
A declarative verification fixture carries four required keys plus an optional
fake
block:
observation doubles as the HTTP script for the fake upstream. obol_testkit supplies
the fakes: VerificationSandbox::start() brings up synthetic OpenAPI, worker, and
remote-MCP upstreams together; FakeOpenApi, FakeWorker, FakeMcpServer, FakeControl,
and InMemoryStores cover the rest.
The mandatory matrix
Every connector proves these, whatever its tier: input-schema rejection before any dispatch; an off-schema 2xx yieldinginconclusive rather than a client-side error;
redaction (no plaintext credential, raw body, or compared value in a log or receipt); the
numeric vendor status surviving the failure path; the error map with retryability; the
non-revealing unknown_tool response that still mints an owner-visible receipt;
idempotency propagation, replay, conflict, in-flight collision, and fail-closed behavior on
a store outage; and EffectSpec validation.
Step 8 — Security-review posture
State explicitly, in the PR description and in your summary, if the diff: logs headers or bodies; widens a tool allowlist, header allowlist, or egress default (a new probe target or egress host counts); moves secret handling; adds an outbound destination; flips deny to allow; or touches idempotency, trust derivation, or signatures. Say “this is security-sensitive because …” — do not leave a reviewer to notice.Step 9 — Gates
Fromapps/gateway/:
test(<crate>): … (RED) with feat(<crate>): … (GREEN), enforced by
check-tdd-pairs.sh. Never #[ignore] — the ignored budget is zero.
Deliverables checklist
- ADR check done; governing ADRs named, the probe ADR cited by filename
- Vendor brief recorded in
CONNECTOR.md(note the fallback if a research server was absent) - Tier and
CustodyModedecided by the table, citing the brief, with the reason recorded - Contract types → schemas → examples, in that order; an
EffectSpecper tool, green -
VerificationPlanin the overlay or bundle only — never on aToolSnapshot -
help.jsonauthored to the contract inpackages/connectors/HELP.md - Cedar entries plus both fixture mirrors; conformance cases for every mandatory row
- Security-sensitive changes called out; ADR,
ARCHITECTURE.md, andGLOSSARY.mdin the same PR - Gates green; RED/GREEN commit pairs