QuorumRelay architecture
Candidate 0.1.0.dev8 · schema 2.0.0
QuorumRelay is a transport-neutral civic-data engine. The Python engine owns the shared operation and result contract, provider adapters, capture normalization, bounded search, interchange, and write coordination. Applications own caller identity, access policy, service lifecycle, persistent database location, editorial review, and mapping QuorumRelay records into their own canonical records.
The package is independently installable. Its four current consumer examples—TownReporter, Townlight, TownRecord and Brief—are reference integrations; none is a required runtime dependency. The dev8 wheel and source were checked independently; these checks do not establish full provider catalogs, vendor acceptance, or live provider-write acceptance. See the installed development receipt and the coverage map.
1. Components and call path
All interfaces call the same Engine.execute(operation, request, content, context) boundary. The standard factory composes built-in handlers but does not start a server, open a database or contact a provider. A caller can also register a typed connector and an application-owned Storage implementation.
Diagram: scroll horizontally to see all details.
flowchart LR
subgraph Consumer["Consumer product and operator"]
App["Application workflow"]
Principal["Authenticated caller policy<br/>principal, client, tenant,<br/>operations, namespaces"]
Secrets["Trusted private configuration<br/>provider credentials and grants"]
AppDB["Application-owned records<br/>and editorial decisions"]
end
subgraph Surfaces["QuorumRelay interfaces"]
SDK["Python SDK"]
CLI["JSON / JSONL CLI"]
HTTP["REST / file / job API"]
MCP["MCP stdio / Streamable HTTP"]
TS["TypeScript HTTP client"]
end
subgraph Core["Standalone QuorumRelay package"]
Factory["create_engine composition"]
Engine["Engine.execute<br/>shared schema 2.0.0 envelope"]
ProviderOps["Provider operations<br/>identify / validate / discover / fetch"]
Parse["Compatibility baseline<br/>and additive capture parsers"]
Search["Local and registered native search"]
Interchange["Import preview / persist<br/>and neutral export"]
Writes["Typed write coordinator<br/>plan / commit / receipt"]
Storage["Optional scoped Storage<br/>SQLite or consumer implementation"]
end
Provider["Configured provider origin"]
Records["CaptureBundle / Records<br/>originals, identities, citations,<br/>relationships, text and coverage"]
SDK --> Engine
CLI --> Engine
HTTP --> Engine
MCP --> Engine
TS --> HTTP
Factory --> Engine
Principal --> HTTP
Principal --> CLI
Principal --> SDK
Secrets --> Principal
Engine --> ProviderOps
Engine --> Parse
Engine --> Search
Engine --> Interchange
Engine --> Writes
ProviderOps --> Provider
Parse --> Records
ProviderOps --> Records
Search --> Records
Interchange --> Records
Engine <--> Storage
Interchange <--> Storage
Writes <--> Storage
Records --> App
App --> AppDB
The arrows to the provider are owned by provider-specific adapters and bounded transport helpers. The HTTP, CLI, MCP and TypeScript layers do not implement provider-specific parsing. The TypeScript package is an HTTP client; it does not start the service. A provider write also uses the shared coordinator rather than a transport-specific mutation path.
2. Read and capture lifecycle
A read can begin with an explicitly configured live source or with saved bytes supplied by the caller. The engine normalizes only the available evidence. It retains raw captures and unknown upstream fields so consumers can inspect exactly what was received alongside the shared normalized projection.
Diagram: scroll horizontally to see all details.
flowchart TD
Request["SDK, CLI, REST or MCP request"] --> Validate["Validate operation, Source,<br/>namespace policy and Bounds"]
Validate --> Capability{"Provider operation<br/>registered and permitted?"}
Capability -- "No" --> Unsupported["unsupported or failed result<br/>with explicit reason"]
Capability -- "Yes, live route" --> Route["Provider adapter selects<br/>fixed documented/evidenced route"]
Capability -- "Yes, saved bytes" --> Saved["Caller supplies exact capture bytes<br/>and input_format"]
Route --> Bound["Bounded public/account transport<br/>origin, TLS, byte, page,<br/>deadline and cancellation rules"]
Bound --> Raw["Capture bytes and source metadata<br/>with hash and citation"]
Saved --> Raw
Raw --> Parser["Baseline reader or additive parser"]
Parser --> Model["CaptureBundle and Records<br/>identity, normalized fields,<br/>original values, relationships, text"]
Model --> Coverage["Coverage and status<br/>dates, pages, records, unknowns,<br/>failures, truncation, checkpoint"]
Model --> Persist{"Caller explicitly<br/>requests persistence?"}
Persist -- "No" --> Return["Return full result envelope"]
Persist -- "Yes, storage configured" --> Scoped["Atomic scoped storage<br/>records, captures and history"]
Scoped --> Return
Shared defaults are bounded: up to 1,000 records, 25 pages, 16 MiB parser bytes, 200 MiB transfer bytes and a 45-second operation deadline. Service transports impose their own outer request and timeout limits. Providers can impose smaller limits or require stricter filters. A response reaching one of these boundaries is reported as partial when appropriate; a bounded window is not a complete provider archive.
A CaptureBundle groups records with the configured source, capture metadata and a compatibility projection. Each record keeps source identity separately from normalized hashes, upstream revision and the destination application’s record ID. Local IDs derived from capture position retain their derivation and stability limits; they are never relabeled as provider-issued IDs. Text segments retain the supplied field, citation and provenance. A missing transcript, document, timezone or meeting body remains unknown.
The current fixed baseline preserves all 34 historical provider/input-format pairs. Twenty-eight additive parser hooks replay additional captured formats. These are separate from live discovery and from the selected provider’s exact operation coverage. Use the installed providers manifest and capability inventory for provider-by-provider detail.
3. Trust boundaries and scoped state
The service authenticates a consumer before constructing a trusted principal context. The context and provider credentials are private inputs to the engine, not public request fields. A source namespace describes an upstream source identity; consumer tenant identifies whose stored work is being accessed. Those values serve different purposes and are checked independently.
Diagram: scroll horizontally to see all details.
flowchart LR
subgraph Untrusted["Untrusted caller input"]
Req["JSON request<br/>source, filters, bounds"]
File["Uploaded bytes<br/>or linked source content"]
ProviderData["Provider response<br/>HTML, JSON, PDF, captions"]
end
subgraph ServiceBoundary["Application-owned trust boundary"]
Auth["Authenticate client/user"]
Policy["Trusted principal policy<br/>operations and namespaces"]
Credential["Secret manager / protected config<br/>provider-specific credentials"]
Context["OperationContext<br/>caller scope, current grants,<br/>private credentials, cancellation"]
Store["Storage partitioned by hash of<br/>principal + client + consumer tenant"]
end
subgraph EngineBoundary["QuorumRelay boundary"]
Gate["Reject identity/grants/credentials<br/>from public request JSON"]
Guard["Validate source namespace,<br/>fixed routes and public transport"]
Engine["Shared engine and provider adapter"]
Capture["Hash, preserve and parse<br/>bounded supplied data"]
end
Upstream["Configured upstream origin"]
App["Consumer review and<br/>application record mapping"]
Req --> Auth --> Policy --> Context
Credential --> Context
Req --> Gate --> Engine
File --> Capture
Context --> Gate
Gate --> Guard --> Upstream
Upstream --> ProviderData --> Capture --> Engine
Engine <--> Store
Engine --> App
The HTTP service defaults to loopback. Binding to another interface requires explicit network configuration, authenticated token policies and an explicit non-wildcard Host allowlist. Origin policy applies before request dispatch. Deployments terminate TLS at an operator-managed reverse proxy. Authenticated MCP additionally requires its resource URL, issuer and a verifier appropriate to the host; the built-in opaque service-token map is not a general OAuth authorization server.
An operation scope is a hash of principal, client ID and consumer tenant. SQLite storage uses that scope for key/value state and capture blobs; compare-and-set and create-only operations protect updates. Caller identity must not be supplied by request JSON or an arbitrary tenant header. An access token is not forwarded to provider URLs. Provider credentials are loaded by the application into the private context and bound to the provider’s exact origin/tenant/namespace contract.
Provider content is untrusted data, never an instruction to the engine or consumer. Public outbound requests are constrained to expected HTTP(S) origins, validated DNS destinations and bounded redirects/bytes/deadlines. Cross-origin credential forwarding is stripped or rejected. File uploads resolve to opaque scoped handles rather than filesystem paths. Downloads are limited by caller scope, current grants, expiry and recorded byte/hash information.
4. Write plan, commit and recovery
Provider writes are a separately authorized lifecycle. The plan binds the exact source, actor scope, provider-native change, parent/resource identity, expected version, credentials policy and any staged file hashes. A plan hash is not authorization; the application’s trusted policy must authorize planning and the exact plan commit.
Diagram: scroll horizontally to see all details.
sequenceDiagram actor Caller as Application caller participant Engine as QuorumRelay Engine participant Adapter as Typed provider adapter participant Store as Atomic scoped storage participant Provider as Configured provider Caller->>Engine: plan_write(source, changes) Engine->>Engine: Check operation grant, namespace and exact fields Engine->>Adapter: Validate native mapping and target Adapter->>Provider: Read before state when mapping requires it Provider-->>Adapter: Bounded before state Adapter-->>Engine: Immutable native request and preview Engine->>Store: Create exact plan and SHA-256 plan hash Store-->>Engine: Durable plan ID Engine-->>Caller: Plan preview, plan ID and hash Caller->>Caller: Review and authorize this exact plan Caller->>Engine: commit_write(plan ID, plan hash) Engine->>Store: Atomically claim and persist execution intent Engine->>Adapter: Apply only the approved plan Adapter->>Provider: Fixed documented mutation request Provider-->>Adapter: Bounded response or uncertain outcome Adapter->>Provider: Read back / reconcile according to mapping Provider-->>Adapter: Current provider state Adapter-->>Engine: Success, conflict, failure or uncertainty Engine->>Store: Persist receipt and per-item state Engine-->>Caller: Commit result and queryable receipt
Planning requires write:plan; commit requires write:commit plus the exact stored plan ID and digest. The engine does not accept edited client-returned plans. An execution intent is persisted before sending a mutation. Duplicate commits consult the durable receipt; if a response is lost, the adapter reconciles the original intent before any retry. Uncertain outcomes remain uncertain until the provider state can be identified. Successful siblings remain in partial batches; remote side effects are not rolled back automatically.
The engine exposes no arbitrary delete, vendor administration, unrestricted upload, guessed provider route or generic “publish this meeting” operation. An adapter exists only for its documented native mappings. Required provider account permissions and request/response behavior still need acceptance in an authorized test environment. For this candidate, live provider-write acceptance is NOT_RUN: the authorized test tenants and credentials were not supplied. Synthetic transport results exercise the same mapping logic without changing provider data.
5. Module boundaries
| Area | Source | Responsibility |
|---|---|---|
| Public types and engine | models.py, engine.py, factory.py |
Versioned contracts, dispatch, operation bounds and standard composition |
| Provider identity, discovery and fetch | providers/, capabilities.py |
Canonical IDs, fixed route mappings, provider captures, attachment/detail handlers and additive parser registration |
| Historical compatibility readers | compat.py, normalization.py, legacy.py |
Preserve the 34 original reader outputs and carefully project legacy identities |
| Shared state | storage.py, extensions.py |
Scoped atomic key/value storage and content-addressed captures |
| Search and interchange | search.py, imports.py, interchange.py |
Local/native search, preview, explicit persistence, portable export and file artifacts |
| Writes | writes.py, write_adapters.py, write_transport.py, write_files.py |
Immutable plans, native field mapping, bounded transport, file staging and durable receipts |
| Interfaces | transports/, cli.py, client/ |
HTTP, MCP, CLI and TypeScript request/response adaptation |
Read the engine contract for types, bounds and extension protocols, the write contract for native mapping details, and the user manual for installation and operating procedures.