Skip to content

Contract (L1)

1. Crates and handle

The workspace-wide table is architecture/L2-crate-topology.

The runtime contract is separate from the node implementations so the bundled desktop app embeds the authority now and a remote-authority deployment puts a replica behind the same contract later:

posthaste-contract-core
  the shared wire vocabulary: typed MailOperation (the one operation enum),
  MutationRequest/MutationReceipt, MutationSettlementState, opaque ids,
  RuntimeFrame/ViewFrame/ViewSnapshot/MailListViewState, RuntimeAdapterError,
  mutation_args, mail_query

posthaste-runtime-api
  the typed, wire-free domain RPC trait: returns serde domain types, no frames

posthaste-client-link
  the client↔runtime link-ops trait (RuntimeLink): forward_mutation,
  subscribe_runtime_frames -> RuntimeFrame stream, link/view-stream ops
  caller context, links, views, coverage, resources

posthaste-runtime
  the near node: RuntimeHandle implements runtime-api + client-link
  RuntimeBuildConfig and RuntimeBuild output, RuntimeShutdownHandle
  view service, mutation coordinator, resource resolver, pending set
  assembly of config, secrets, SQLite store, domain service, event bus,
  and the account supervisor (owned by posthaste-authority-server, the far node)

There is no separate replica-runtime crate: a deployment whose authority is remote implements the same two contract traits from the same near-node crate, with the pending set converging toward the remote authority.

The contract crates are free of Axum, Tauri, React, provider-client, SQLite-table, and replica-table types. posthaste-contract-core additionally sits on the wasm-pure frontier (serde-only; no tokio, reqwest, rusqlite, mail-parser, axum).

2. Authority server handle shape

The authority implementation splits transport-neutral runtime code from adapter code:

RuntimeHandle      cloneable implementation of the runtime-api + client-link
                            contract traits (the split of the former RuntimeCore)
AuthorityRuntimeCore        config repo, secret store, SQLite store, domain service,
                            account supervisor, event bus/history, query/view service,
                            mutation coordinator, resource resolver
RuntimeShutdownHandle       graceful shutdown and task joins

The seam is load-bearing: Axum and Tauri adapters call the same runtime contract instead of rebuilding service/store/supervisor state. Mail behavior is never duplicated in the two transports.

3. Adapter-neutral caller context

Runtime handle methods receive an adapter-neutral caller context:

RuntimeCaller { linkId?, capabilities, accountScope?, operationSource, correlationId }

Adapters authenticate and translate transport identity into this context. The runtime uses it for authorization, diagnostics, mutation attribution, and settlement routing. The handle must not depend on HTTP headers, Tauri window labels, or React component state for authorization decisions.

4. Runtime API groups

The shared runtime contract exposes these method groups:

Group Purpose
Lifecycle Start enabled accounts, reload config, report status, shut down gracefully.
Reads Local runtime/API projections for settings, accounts, mailboxes, smart mailboxes, tags, messages, conversations, and query pages.
Views Connect renderer links, open/update/close views, subscribe to view frames.
Mutations Run named mutations, inspect mutation state, retry, undo, resolve conflicts.
Events Subscribe to runtime events and read event history for API catch-up/snapshot.
Resources Resolve body, attachment, and source resources into bytes, handles, or scoped capability URLs.

These groups do not live on one god-trait: the typed domain reads are posthaste-runtime-api (wire-free, serde domain types) and the link, view, frame-stream, and mutation-forward ops are posthaste-client-link.

Adapters do not reach around the handle to private service/store fields for a method that belongs to the runtime contract.

5. Security and storage contract

5.1 Runtime-owned roots

Bundled mode resolves runtime-owned roots before building the handle:

Root Contents
Config root Non-secret app/account settings, smart-mailbox definitions, automation rules, secret references, account assets (e.g. logos).
State root SQLite store, event history when stored outside SQLite, sync cursors, mutation idempotency records, provider work queues, runtime status snapshots, daemon/bridge metadata only when a daemon mode owns it.
Cache root Body/blob, attachment, and source/raw-message cache data and metadata.

The cache root may be a subdirectory of the state root; it is still runtime-owned and not renderer storage. Dev/test builds may override roots through explicit process configuration; production uses platform application data/config/cache locations. Overrides must not make renderer-owned directories the runtime roots.

5.2 Renderer-owned storage

Renderer-owned storage is limited to presentation and connection-profile state: theme/appearance, window geometry and pane sizes, layout/sort UI and recent surfaces, connection-profile metadata with no secrets, and transient adapter/link state. It must not contain SQLite copies, message bodies, attachment bytes, event history, mutation idempotency records, provider credentials, refresh/access/API tokens, or runtime secret references usable without the runtime.

5.3 Secret stores

Provider credentials, refresh tokens, and access tokens stay in the authority server secret store; config may contain secret references and redacted status only. A desktop client profile's remote API token for a non-bundled deployment lives in a client secret store distinct from the runtime provider-secret store; connection-profile JSON may reference it but must not inline it. Secrets are never written to packaged JavaScript, localStorage, renderer-owned JSON, logs, crash reports, URLs, query strings, or static assets.

6. Assertions

ID Sev. Assertion
runtime-contract-implementation-neutral MUST The shared runtime contract is free of SQLite-table, provider-client, replica-table, invalidation-command, Axum, Tauri, and React types.
runtime-handle-transport-neutral MUST Mail behavior is exposed through one runtime contract implemented by the authority server handle rather than duplicated in Axum and Tauri transports.
runtime-handle-no-transport-types MUST Runtime handle methods use domain/runtime inputs and adapter-neutral caller context, not Axum, Tauri, or React types.
runtime-owned-roots MUST Config, state, and cache roots that contain mail authority data are owned by the runtime, not renderer storage.
renderer-storage-limited MUST Renderer-owned storage contains presentation/profile metadata only and no mail authority state, bodies, attachments, event history, mutation idempotency records, or provider secrets.
provider-secrets-runtime-store MUST Provider credentials and tokens live in the runtime secret store with only references/redacted status in config.
client-token-store-split MUST Non-bundled remote API tokens, when supported by the desktop client, live in a client secret store distinct from runtime provider secrets and are not inlined in profile JSON.
no-secret-logs-urls MUST Secrets, bearer tokens, capability URLs, Authorization headers, sensitive query strings, message bodies, and attachment bytes are not written to logs or diagnostics.