Skip to content
Search Engineering

Build Connector Contracts

Classify connectors by role, return typed outcomes, budget retries, and reconcile ambiguous effects before replay.

Outcome

This chapter turns connector failures into typed harness decisions. One result envelope supports ingestion, identity, live-read, and action roles while preserving their different completion and retry rules.

Declare the role and contract

For each connector, document its role, authority input, request and response schema, source and record version, freshness bound, timeout, rate limit, retry budget, completion requirement, and emitted event fields.

Ingestion owns synchronization and provenance. Identity owns trusted claims and validity. Live reads own current external evidence. Actions own effects and reconciliation. A connector that performs more than one role should expose separate operations so the harness can apply the correct policy.

The runnable result envelope contains a role, typed status, payload-usability flag, optional error class, and optional idempotency key:

result = ConnectorResult(
    role="action",
    status="indeterminate",
    payload_usable=False,
    error_class="timeout_after_commit",
    idempotency_key="watchlist/add/film-1",
)

Map outcomes to harness actions

The decision function is intentionally deterministic:

# The course excerpt is generated from region:evaluate_connector.

A successful usable payload proceeds. A denied identity result reauthorizes. An indeterminate action reconciles. An optional unavailable read narrows. Required failures stop.

The runnable envelope already carries retry guidance, provenance, observed and source-effective times, schema version, quota, effect state, and an idempotency reference. Keep provider-specific exceptions behind the adapter so the rest of the trajectory reasons over these stable states.

ingestion_connector(), identity_connector(), and live_read_connector() provide deterministic local doubles for their roles. InMemoryWatchlistConnector commits to a synthetic set, binds each idempotency key to one payload, and exposes reconcile() for a response lost after commit. These fixtures exercise the contracts without reaching a vendor service or the production film corpus.

Bound retries across the trajectory

Set per-call timeout, maximum attempts, maximum concurrent calls per target, and a request-level deadline. Honor server retry guidance when it fits the remaining budget. Coordinate retries across parallel branches so one throttled dependency does not multiply load.

bounded_live_read() enforces attempt, time, and cost budgets together. The adapter declares a maximum cost per attempt before the call, and the harness passes a timeout bounded by the remaining trajectory time. Measured call time and retry delay both reduce that budget. The helper records one categorical event per attempt and stops when another call or retry delay no longer fits. governed_connector_trace() emits the shared authority, provenance, freshness, quota, status, retry, idempotency, effect, and terminal fields while excluding payloads and credentials.

Reads can usually be retried when their operation is bounded and their freshness contract still holds. A write that timed out after send has an unknown outcome. Query its operation status or reuse the original idempotency key with the exact same payload. Do not issue a fresh effect simply because the response was lost.

validate_action_retry() rejects both a new key before reconciliation and reuse of the same key with a different payload. A production service should enforce the same mapping durably.

Preserve provenance

For policy-relevant records, retain source id, source record version, schema version, observed time, transformation version, and deletion state beside the indexed derivative. Reconciliation should detect changed, missing, and duplicated records rather than only appending successful fetches.

An identity assertion needs issuer, subject, audience, validity, actor when delegated, and assurance appropriate to the operation. A live fact needs source, observation time, and freshness class. These fields let policy distinguish an unavailable source from an assertion too stale to use.

Run it

.venv/bin/python code/chapters/chapter_10.py
cd code/chapters && ../../.venv/bin/python -m unittest test_chapter_10.py

Together with Chapters 9 and 10, this continuation implements governed search as inspectable behavior at each boundary.

Success criteria

  • An optional unavailable source narrows the result explicitly; a required-source failure stops the trajectory.
  • Per-call cost and timeout budgets are enforced before each attempt.
  • An indeterminate action reconciles against its original operation before any replay.
  • A new idempotency key before reconciliation, and key reuse with a different payload, are both rejected.

Further reading