Skip to content

Contracts (L1)

1. Boundary

1.1 Contract

The authority server is the single authority for runtime mail operations in local-authority deployment modes. It resolves account configuration, resolves secrets, supervises account runtimes, talks to providers, reconciles provider state into the local store, and publishes domain events. The API server adapts the authority server to /v1 rather than owning separate mail behavior.

The authority server is the far (authoritative) end of the runtime↔authority-server coherent link (replication L1): it accepts forwarded mutations (MutationRequest carrying the typed MailOperation), applies them against provider truth, and emits the authoritative down-channel — before/after assertions plus the confirmation watermark a near node uses to retire its optimistic mutations.

Renderers reach authority-server behavior through the runtime boundary. External clients reach authority-server behavior through the API. Authority-server internals may be split across modules or crates, but those internal boundaries do not create additional public client contracts.

Authority-server code keeps provider protocol behavior, local persistence, and client serialization behind separate contracts.

1.1.1 Motivation

Mail clients fail when authority is split casually: provider state, local projections, renderer mirrors, and UI state all change at different speeds. The authority-server boundary exists so runtime mail state has one place where provider facts are interpreted before clients act on them. The runtime and API boundaries also let clients (desktop renderer, MCP adapter, tests, scripts) vary their presentation or transport without forking the definition of mail state.

1.1.2 Consequences

Authority-server internals can change shape as long as the runtime, API, and authority-server domain contracts remain valid. Client-facing behavior must be expressed through a runtime, API, event, config, or auth contract: a client should not depend on a crate, table, provider object, or runtime task that is not part of a declared contract.

2. State ownership

2.1 Ownership rule

The authority server owns runtime mail facts. Renderers own interaction state. Provider systems own remote mail truth. Config and secrets are durable local inputs to authority-server behavior.

2.1.1 Motivation

State ownership is the main authority-server design constraint. Posthaste needs a fast local mail experience, but speed should not blur which layer owns the facts. The local store is rich because it is authority-server-owned; renderer mirrors are disposable presentation copies; secrets are separate because they carry a different threat model from ordinary cache state.

2.2 Authority-server-owned state

The authority server owns the local authority mail store, sync cursors, account runtime status, provider connection state, cached message bodies, attachment metadata, source projections, event history, and authority-server-managed derived views.

The authority server also owns sanitized email body output. Raw or provider-supplied HTML must be sanitized before it is returned through client-facing contracts.

Authority-server-owned state may be rebuilt from provider state when the provider remains authoritative. State that cannot be rebuilt, such as credentials or local configuration, uses the config and secret contracts instead of ordinary cache storage.

2.3 Renderer-owned state

Renderer state is limited to interaction and presentation concerns: current focus, selection, pane geometry, scroll positions, open surfaces, form-local edits before submission, and transport mirrors of runtime state.

Renderer mirrors are copies of runtime output. They are not a second authority-server store and do not define mail truth.

3. Domain service

3.1 Contract

Authority-server mail operations pass through a domain service boundary before they mutate mail state or expose mail projections. The service coordinates configuration, store access, provider commands, sync application, and event creation.

The service accepts explicit account context for account-scoped operations. Account identity must not be inferred from unscoped message, mailbox, or derived conversation identifiers.

The service returns domain results and domain errors. Runtime adapters, API handlers, or other adapters translate those results into transport-specific responses.

3.1.1 Motivation

The service boundary keeps authority-server behavior testable, prevents endpoint handlers from accumulating domain rules, and separates long-lived runtime concerns from one-shot domain operations. Explicit account context is required because mail identifiers are not globally meaningful enough to protect data by themselves: account scoping must be present before a service reads or mutates mail state.

3.2 Provider connections

The service does not own long-lived provider connections. Provider connectivity belongs to account runtimes, which provide the service with a provider gateway when an operation requires remote access.

3.2.1 Motivation

Provider connections have lifecycle, retry, status, and credential-refresh behavior. Keeping those concerns outside the service lets the service model a domain operation without becoming a runtime supervisor.

4. Provider gateways

4.1 Contract

A provider gateway is the authority-server contract for remote mail access. It translates provider-specific protocols into Posthaste domain records, sync batches, command outcomes, body fetches, blob downloads, identity data, and send results.

Provider gateways expose unsupported operations as explicit errors. Silent partial behavior is only acceptable when the domain contract defines the fallback and its user-visible effect.

4.1.1 Motivation

Provider APIs do not agree on identity, threading, labels, mutation semantics, push, or sent-message behavior. A single client-facing model is still necessary. The gateway contract is where provider-specific truth is preserved long enough to map it safely.

4.2 Normalization

Provider gateways preserve provider-specific correctness internally. JMAP state strings, IMAP UID validity, SMTP submission behavior, OAuth token refresh, provider labels, provider threading, and push semantics are normalized before other authority-server layers consume them.

Normalization may produce provider-policy decisions when a clean provider-neutral value would hide important behavior.

4.2.1 Motivation

The goal is not to hide every provider difference. The goal is to prevent provider differences from leaking accidentally into unrelated layers.

4.3 Push inputs

Provider push is an input to authority-server synchronization.

A provider gateway or account runtime may treat push as a precise change signal, a hint, or unavailable, depending on provider capability and policy. The authority server must still produce client-facing state through sync and domain events.

4.3.1 Motivation

Provider push is not consistent enough to expose as the client event model. Keeping it as an authority-server input lets each provider use the strongest safe observation strategy while clients observe one local event contract.

5. Store

5.1 Contract

The store is the authority-server contract for local mail state and read projections. It persists account-scoped mail state, sync state, cached body data, event history, source projections, and authority-server-managed derived state.

Read models returned by the store are authority-server projections. They are shaped for authority-server services and API handlers, not for one UI component's private state.

SQLite connection policy is store-owned. The store may keep a serialized write connection, pooled read connections, prepared statement caches, WAL settings, FTS indexes, and denormalized counters behind this boundary; callers must not manage SQLite handles or duplicate these projection-maintenance rules.

5.1.1 Motivation

The store is a replica and projection layer, not just a cache lookup table: it needs enough structure to support search, smart mailboxes, event replay, lazy body loading, and stable reads while sync is running.

5.2 Account scoping

Store writes maintain account scoping. Rows or records derived from provider data must not become globally visible without an explicit cross-account projection rule.

5.3 Write validity

Sync application is transactional at the level needed to avoid exposing half-applied provider state. If a sync batch changes messages, mailboxes, cursors, and events, readers must either see the previous valid projection or the new valid projection.

5.3.1 Motivation

The transactional rule matters because clients read during sync. A fast local API is only useful if it does not expose impossible intermediate states.

6. Config and secrets

6.1 Configuration contract

Configuration stores non-secret account and application settings. Config reads use a consistent snapshot. Config writes are atomic from the point of view of later authority-server reads.

Every loaded snapshot must pass semantic validation before it becomes observable. Validation covers cross-file relationships and domain invariants such as duplicate identifiers, dangling default-account references, required account transport fields, and automation-rule shape. Hand-edited TOML with semantic errors is rejected as a whole, and the previous in-memory snapshot remains active.

Reload behavior reports what changed so account runtimes can be started, stopped, restarted, or left alone.

6.1.1 Motivation

Configuration is inspectable local intent. Users and tools should be able to understand account setup, provider choices, and sync preferences without exposing credentials.

6.2 Secret contract

Secret storage stores credentials, refresh tokens, access tokens, and other secret material.

Configuration may reference a secret, but it must not inline long-lived secret material unless the security specification permits that specific case.

Secret resolution happens before provider access that needs credentials. Secret updates caused by token refresh or credential rotation update secret storage without rewriting unrelated account configuration.

6.2.1 Motivation

Config and secrets change for different reasons and carry different risks. Credentials and tokens should follow the platform secret model rather than be copied into ordinary files for convenience. Separating reload from secret resolution also lets the authority server restart the right account runtimes without treating every token refresh as a config rewrite.

7. Account runtimes

7.1 Contract

An account runtime owns long-lived operational state for one account: provider connection lifecycle, scheduled sync, manual sync requests, push or polling inputs, runtime status, and maintenance work.

Account runtimes call the domain service to apply sync results and publish domain effects. They do not bypass the service to mutate mail projections directly.

7.1.1 Motivation

Provider connections are long-lived, failure-prone, and account-specific. Keeping them in endpoint handlers would tie request latency to provider lifecycle and make background sync impossible.

The runtime gives each account one operational owner while the service keeps state changes consistent.

7.2 Runtime status

Runtime status is domain-observable state. Connection failures, authentication failures, sync failures, and recovery are represented so clients and operators can understand account health.

7.3 Recovery and fallback

The runtime may reconnect providers, retry failed work, or fall back from push to polling without changing client-facing API contracts.

8. Events

8.1 Publication contract

Authority-server domain changes that clients need to observe are appended to event history and published on the local event stream.

Events are ordered. Resume tokens or sequence numbers refer to authority-server event history, not provider push sequence state.

Authoritative state delivery carries a confirmation watermark for the consumer's mutations: a settled mutation reports its terminal outcome by mutation id (and the assigned entity id when it created one), and account/object state delivery (such as sync.completed state tokens) is high enough to let a near node retire every optimistic mutation the authority server has applied. A mutation is confirmed when the watermark passes it, not when its provider push returned. This is what lets the runtime stop showing an optimistic effect only once the authoritative after reflects it, rather than on send. When multiple runtimes share one authority server (replication L1 §10) the watermark and mutation-id idempotency are tracked per authority-server link (AuthorityServerLinkId, authority-server-link L1 §3.1); a settlement routes only to the link that forwarded the mutation, so sibling runtimes never retire one another's optimistic effects.

Message changes publish before/after post-state assertions. A message assertion carries previous and current MessageSummaryState values, including ConversationRef, bodyToken, and attachmentToken when a summary is present. Other client-visible mail state changes publish before/after assertions for their named state, such as mailbox or smart-mailbox state. The authority server does not publish query-scope invalidation commands. Message changes do not emit separate conversation projection assertions. Coarse lifecycle changes publish topic-specific payloads only when no stable object or projection state is useful.

8.1.1 Motivation

The event stream is a client synchronization aid, not a provider push tunnel: clients need to know what authority-server state changed and how to resume after disconnects, not every internal reason the authority server decided to sync. Post-state assertions prevent runtime adapters and API clients from becoming a matrix of patch-vs-invalidate choices — the authority server already knows the durable state after a write, so the event asserts before/after state and freshness tokens instead of asking clients to rediscover them.

8.2 State-before-event rule

Event publication follows authority-server state changes. A runtime adapter or API client receiving an event should be able to read through the runtime/API and observe a state consistent with that event or newer events.

8.2.1 Motivation

The state-before-event rule prevents clients from receiving an event and then reading stale state.

8.3 Shared API state rule

HTTP reads and SSE assertions use the same API projection constructors when they expose the same state. An authority-server change that adds, removes, or renames a projection field must update both the HTTP and event contract paths.

8.3.1 Motivation

HTTP and SSE coexist because they serve different clients and different phases of a client's lifecycle. Shared projection constructors stop them from drifting into separate APIs.

8.4 Internal signals

Authority-server-internal signals that only schedule work do not need to become client-facing events. Provider push hints, polling ticks, and maintenance triggers become client-visible only when they produce domain changes or status changes.

9. API implementation boundary

9.1 Contract

The API contract defines what clients can call and observe. Authority-server code implements that contract through routing, authorization, request validation, service calls, error mapping, serialization, and event streaming.

Endpoint semantics belong to the API domain. Authority-server docs may describe how a route is resolved internally.

9.1.1 Motivation

The API must be understandable without authority-server internals because clients depend on it. The authority server still describes how it implements the API, because route resolution, authorization, runtime access, and service calls are authority-server concerns.

9.2 Handler responsibilities

Endpoint handlers parse transport-specific input, enforce API authorization, call authority-server services or account runtimes, and map results to API responses.

Domain decisions live behind authority-server service, provider, store, config, or secret contracts.

9.2.1 Motivation

Thin handlers keep the API/authority-server division clear. Transport details stay near the route; domain decisions stay behind authority-server contracts.

9.3 Dependency direction

Adding an API path requires a client-facing API contract and an authority-server implementation path. Authority-server implementation sections should depend on the API sections they implement when both are specified.

10. Assertions

ID Sev. Assertion
authority-server-entry-boundary MUST Renderers and external clients reach authority-server mail behavior through runtime or API contracts rather than authority-server internals.
account-scoping MUST Authority-server mail operations carry explicit account context when reading or mutating account-scoped state.
service-before-mutation MUST Authority-server mail mutations pass through the domain service boundary or an explicitly specified lower-level maintenance path.
gateway-normalization MUST Provider gateways normalize provider-specific protocol behavior into domain records, sync batches, command outcomes, or explicit provider-policy decisions.
store-transaction-validity MUST Store writes that apply provider state preserve valid read projections before and after the write.
secret-config-split MUST Long-lived credentials and tokens are stored through secret storage rather than ordinary configuration.
config-snapshot-validation MUST Loaded config snapshots are semantically validated as a whole before they replace the active snapshot.
runtime-owns-connection MUST Long-lived provider connection lifecycle belongs to account runtimes, not endpoint handlers or client adapters.
event-after-state MUST Client-facing domain events are published only after the authority-server state change they describe is durable or otherwise observable through runtime/API reads.
event-post-state MUST Object-state events carry post-state assertions rather than operational deltas.
confirmation-watermark-emitted MUST Authoritative delivery carries a confirmation watermark (settlement by mutation id plus account/object state tokens) sufficient for a near node to retire confirmed optimistic mutations; confirmation follows the durable authoritative write, not the provider push return.
confirmation-watermark-per-link MUST When multiple runtimes share one authority server, the confirmation watermark and mutation-id idempotency are tracked per authority-server link (AuthorityServerLinkId); a settlement routes only to the link that forwarded the mutation.
message-events-conversation-ref MUST Message events include the same ConversationRef as message HTTP projections.
message-events-content-tokens MUST Message events include body and attachment tokens from message HTTP projections.
message-events-before-after MUST Message events carry before/after summary state with nulls for create/destroy.
no-query-invalidation-events MUST Authority-server events assert state changes rather than issuing query-scope invalidation commands.
no-conversation-events MUST Message changes do not emit separate conversation projection events.
conversations-derived MUST Conversation data exposed by the authority server is a derived projection over messages, not independently mutable canonical state.
shared-api-state MUST Runtime reads, HTTP reads, and SSE assertions use shared projection constructors when they expose the same state.
provider-push-separation MUST Provider push, polling, and maintenance signals are authority-server runtime inputs, not client-facing event contracts.
thin-handlers SHOULD API handlers should translate transport concerns and delegate domain decisions to authority-server contracts.