Skip to content

Contract (L1)

1. Boundary

1.1 Contract

The runtime domain defines the stateful backend that operates Posthaste for a UI renderer.

The UI does not own mail query truth, mail mutation settlement, provider access, or durable mail state. It renders state produced by a runtime and sends named actions to that runtime.

For the bundled application, the runtime is an embedded authority server: it owns the local SQLite store, provider sync, command execution, query evaluation, active view state, event history, config, secrets, and body/blob cache.

1.2 Motivation

Regular actions should be instant. Opening Inbox or Archive, toggling flags, applying tags, moving mail, and editing drafts should not wait for an HTTP round trip before the UI can update.

The clean way to provide that experience is to put state operation behind a runtime boundary. The bundled UI talks to the embedded authority server. A future hosted, multi-device, or offline deployment can put a local replica behind the same UI-facing boundary without changing renderer semantics.

1.3 Sub-domains

This L1 is the shared runtime contract. The implementation is documented in three sub-domains, each with its own L1–L3:

  • adapter — the renderer-facing surface: links, views, descriptors, the RuntimeFrame stream, mutation submission/settlement, resources, and the view-operation flow that produces served state.
  • mutations — the typed-operation pipeline and catalog: argument rules, the shared local-effect predictor, the per-action catalog, settlement, and reconciliation.
  • internals — assembly and guts: the contract crates, the runtime handle and builder, caller context, startup/shutdown, event/state streams, and security & storage.

2. Runtime roles

2.1 Shared runtime contract

The UI-facing runtime contract is shared by runtime implementations. It defines links, views, named mutations, resources, event frames, coverage fields, settlement, caller context, and error shapes.

The contract is carried by two crates (crate topology): posthaste-runtime-api — the typed, wire-free domain RPC returning serde domain types — and posthaste-client-link — the link ops (mutation forward, the RuntimeFrame stream, link/view-stream operations). The shared wire vocabulary both speak (the typed MailOperation, MutationRequest/MutationReceipt, opaque ids, view models, RuntimeAdapterError) lives in posthaste-contract-core.

The contract must not expose SQLite tables, provider clients, replica tables, invalidation commands, or transport framework types. It expresses user intent and runtime-produced state so different runtime implementations can sit behind the same renderer adapter.

2.2 Authority server

An authority server implements the shared runtime contract while owning canonical Posthaste state for its installation or account set. It can talk to providers and reconcile provider state into local canonical records.

Authority responsibilities:

  • provider sync and provider command reconciliation
  • canonical SQLite store
  • query evaluation and active view operation
  • named mutation execution
  • local mutation queue for provider-latency or offline-provider cases
  • event history and state assertions
  • body/blob cache
  • config and secret resolution

The bundled application uses an embedded authority server. That authority server should be reusable as a crate by the desktop host rather than living inside an Axum server binary.

2.3 Replica runtime

A replica runtime implements the shared runtime contract for deployments where the authority is remote. It is a coherent-link node (replication L1): it owns a local replica of replicated mail state, a pending set of pending mutations toward the remote authority, coverage, sync/resume state, and convergence by confirmation watermark.

The replica is not a separate state model. It runs the same named mutations, the same shared local effect (predictor), and the same before/after assertion vocabulary as the authority server, so the UI-facing boundary is identical whether it is served by an embedded authority or a replica. There is no separate replica-runtime crate: both roles implement the same posthaste-runtime-api + posthaste-client-link contract. Replica internals beyond the link contract are not specified here.

2.4 Transport adapters

The runtime boundary is transport-neutral. Implementations may use:

  • in-process calls
  • Tauri IPC
  • local channels
  • localhost HTTP/SSE
  • remote HTTP/SSE or another future transport

Transport adapters must not change mail semantics.

3. Authority server

3.1 Bundled application mode

Bundled application mode packages the renderer and the authority server together. The app does not require a separately installed daemon, hosted authority server, or external localhost service for ordinary use.

One local authority server serves all renderer windows in the application process tree. Focused windows, compose windows, settings windows, and the main window are UI links attached to the same runtime state.

3.2 Provider latency

Provider calls are not part of the instant UI path. A named mutation first updates local runtime state according to its mutation contract, then reconciles with the provider when required.

If provider reconciliation fails, the runtime emits authoritative settlement state, error state, or follow-up reconciliation work. The UI renders that state; it does not repair provider conflicts directly.

3.3 Offline provider operation

The authority server may accept provider-backed mutations while the provider is unavailable when the mutation contract supports queueing. Queued mutations are durable local runtime state.

Queued provider work uses the same named mutation and settlement model as online mutations.

3.4 Package contents

The bundled application contains the desktop host, packaged renderer assets, authority server code, provider drivers, schema migrations, and local resource files needed to open existing local mail state.

Production bundled mode loads the renderer from packaged assets. A development build may load renderer assets from a local development server, but that server only serves the renderer; it is not the mail authority.

3.5 Local data roots and secrets

Bundled mode stores durable runtime state in local application data roots owned by the authority server. This includes SQLite state, event history, sync cursors, body/blob cache data, configuration, account assets, and non-secret account settings.

Long-lived credentials and provider tokens live in the runtime secret store. They are not written into renderer storage, packaged JavaScript, connection-profile files, logs, URLs, or ordinary presentation-state stores.

Renderer-owned presentation state may use a separate local store, but it must not contain mail authority state, message bodies, attachment bytes, event history, provider secrets, or long-lived API tokens.

3.6 Runtime adapter

The bundled renderer reaches mail behavior through a local runtime adapter. The preferred implementation is in-process, IPC, or local-channel transport. A protected loopback HTTP adapter may exist for API clients, debugging, tests, or as a temporary bridge, but renderer components still target the runtime adapter contract.

If a loopback adapter is present in bundled mode, it binds only to local interfaces, uses an unguessable process-local token or capability, validates host and origin, rejects tokens in URLs, and exposes no provider credentials to JavaScript.

3.7 Startup and shutdown

The authority server starts before the main mail view opens. Failure to open required local dependencies, such as the state store or secret backend, is an application startup error. Account-specific provider failures become account runtime status when possible and should not prevent the UI from opening existing local state.

On shutdown, the runtime stops account runtimes, flushes durable local state, closes provider connections, and leaves queued provider work recoverable on the next launch.

4. UI runtime contract

4.1 Renderer rule

The UI is a renderer over runtime state. It owns presentation-only state such as focus, selection, pane sizes, scroll anchors, open surfaces, text input draft buffers before submission, and transient animation state.

The UI does not own:

  • mail query evaluation
  • active query-window repair
  • message mutation settlement
  • provider access
  • mail event ordering
  • durable body/blob cache state

4.2 Runtime client interface

The UI talks to a runtime client interface with operations shaped like:

openLinkView(linkId, scope)
closeLinkView(linkId, viewId)
subscribeRuntimeFrames(linkId, afterSeq)
getMessage(accountId, messageId)
getConversation(conversationId, token)
runMutation(operation, clientMutationId)
subscribeRuntimeEvents(afterSeq)

Exact function names belong to implementation. The contract is that the UI passes typed scopes and typed operations — operation is the typed MailOperation, not a stringly name/args pair — then renders runtime-produced state.

4.3 View state

The runtime owns active view state for the renderer. A view can represent a mailbox, smart mailbox, search, conversation, message detail, settings surface, compose surface, or another runtime-owned state projection.

For mail list views, the runtime evaluates the QueryScope, maintains the current view window, applies local mutation effects, reconciles provider/origin events, and pushes replacement view state when the window changes.

4.4 Mutation state

The UI sends named mutations with stable client mutation IDs. The runtime applies the local effect immediately when the mutation contract permits it, records pending state, and later emits settlement.

Named mutation contracts define args, local effect, provider work, queued behavior, settlement states, undo/retry support, and conflict-resolution options. The renderer submits user intent; it does not translate that intent into provider commands or direct store patches.

Accepted mutation idempotency is durable runtime state. If the app restarts after accepting a mutation, retrying the same clientMutationId must resolve to the same runtime mutation record or settled result rather than applying the user intent twice.

The renderer does not keep a mutation pending set; it renders pending markers and settlement supplied by its runtime. The pending set belongs to the runtime node that the renderer talks to. In bundled mode that node is the embedded authority, where the replication pending set collapses to a pass-through (colocated-unchanged) and the closest analog is the Tier-2 compose/send outbox — a different queue, toward the provider, not the authority. When the runtime is remote, the client-layer link node (a local replica) holds the renderer-facing pending set and converges with the remote authority by confirmation watermark (replication L1 §4–§5). Either way the renderer holds optimistic state only as runtime-served view state, never as its own authority.

5. View operation

5.1 Query ownership

The runtime owns query membership, ordering, page boundaries, replacement rows, and cursors for active views.

The UI may request a view and render the current window. It must not evaluate mail queries as authoritative state.

5.2 Message and conversation state

Message state assertions carry before/after MessageSummaryState, including ConversationRef, bodyToken, and attachmentToken.

The runtime uses those facts to update active views, stale visible conversation envelopes, and stale visible message detail/body state. The UI receives updated runtime state rather than deriving mail repair rules itself.

5.3 Regular-action latency

Regular UI actions should complete their local runtime phase without waiting for provider or remote-origin round trips.

A regular action may still show pending or syncing state while provider reconciliation is outstanding. The invariant is that the visible local effect is produced by the local runtime immediately when the mutation contract allows it.

6. Deployment transparency

The UI-facing runtime contract must not assume that the implementation is the bundled authority server.

The runtime is a node on a chain of coherent links (replication L1 §2). Deployment chooses where each node runs and which transport carries each link, not what the contract means:

  • Bundled: an embedded authority server co-located with the renderer; links are in-process/IPC; the renderer needs no pending set because its authority is local.
  • Hosted runtime / multi-device: a remote authority. The client-layer node is a replica runtime with its own pending set, optimistic state, and cache, running the shared local effect; the renderer stays a pure view over it.
  • Hosted authority server: the runtime is the buffering near node of a remote runtime↔authority-server link.
  • Hosted authority server, multi-runtime: several runtime near nodes share one remote authority server, each with its own AuthorityServerLinkId; settlement is routed per runtime while base assertions broadcast (replication L1 §10, authority-server-link L1 §3.1). The runtime's UI-facing contract is unchanged — it is one near node among siblings.

Coverage, gap, and freshness affordances are authority-authored coverage fields on served state. The bundled authority server reports complete coverage for its local reads; a replica reports its actual coverage. The renderer operation model — send typed actions, open views, render served state — is identical in every case.

7. Assertions

ID Sev. Assertion
ui-renderer-only MUST The UI renders runtime state and does not own authoritative mail query, mutation, or provider logic.
runtime-contract-shared MUST Authority and future replica runtimes implement the same UI-facing links, views, named mutations, resources, events, settlement, coverage, caller context, and error contract.
bundled-authority-server MUST Bundled application mode uses a local embedded authority server with SQLite and provider access.
bundled-no-external-daemon MUST Bundled application mode does not require a separately installed daemon or hosted authority server for ordinary use.
bundled-shared-runtime MUST All renderer windows in bundled mode observe the same embedded authority server.
bundled-local-data-roots MUST Bundled mode stores durable runtime state in authority-owned local application data roots.
bundled-secret-boundary MUST Long-lived provider secrets stay in the runtime secret store and are not persisted in renderer storage, logs, URLs, packaged JavaScript, or connection-profile files.
renderer-storage-nonauthoritative MUST Renderer-owned storage contains presentation/profile state only, not mail authority state, message bodies, attachment bytes, event history, or provider secrets.
bundled-private-adapter MUST Bundled renderer mail access goes through a local runtime adapter; any loopback bridge is local, capability-protected, URL-token-free, and not a provider-secret channel.
runtime-transport-neutral MUST Runtime semantics are independent of in-process, IPC, localhost, or remote transport adapters.
runtime-owns-query-windows MUST The runtime owns active query membership, ordering, replacement rows, and cursors.
instant-local-phase MUST Regular actions produce their local runtime effect without waiting for provider or remote-origin round trips when their mutation contract permits it.
named-mutation-ids MUST UI-submitted mutations carry stable client mutation IDs for idempotency and settlement.
named-mutation-contracts MUST Runtime mutation contracts define args, local effects, provider work, settlement, undo/retry support, and conflict options.
mutation-idempotency-durable MUST Accepted client mutation IDs are persisted by the runtime until their dedupe/settlement retention window expires.
provider-reconciliation-runtime MUST Provider mutation reconciliation and failure handling are runtime responsibilities, not UI responsibilities.
replica-link-node MUST A replica runtime is a coherent-link node running the same named mutations, shared local effect, and assertion vocabulary as the authority server; the UI-facing contract is identical whether served by an authority or a replica.
renderer-no-pending-set MUST The renderer keeps no mutation pending set; the pending set belongs to its runtime node (embedded authority, or a client-layer replica when the runtime is remote).
deployment-transport-only SHOULD Deployment changes where nodes run and which transport carries a link, not the UI-facing operation model or the mutation/assertion vocabulary.
multi-runtime-contract-unchanged MUST In a multi-runtime hosted-authority-server deployment the runtime's UI-facing contract (links, views, mutations, settlement, coverage) is unchanged; only the authority-server far node gains per-runtime identity, idempotency, and settlement routing.