Skip to content

Structure and flows (L2)

1. Crate structure

1.1 Authority-server crate roles

The workspace-wide crate set, hierarchy, and wasm frontier are named once in Crate topology. This section keeps only the authority-server-tier detail:

Crate Role
posthaste-domain-model The pure shared domain types: ids, messages, records, commands, errors (including ConfigError/ValidationError), vocab, sync/outbox/rev-log types, and the pure cache/imap/provider type slices.
posthaste-domain-service The hexagonal core: MailService, the domain service boundary, provider/store/config/secret ports, provider policy, sync batch application logic, and the semantic validate_* functions. The port architecture stays here; only the pure types lift out to posthaste-domain-model.
posthaste-config TOML-backed configuration repository and atomic config writes.
posthaste-store SQLite-backed authority store, read projections, sync application, command persistence, event history, cache state. Exports only what it owns (DatabaseStore, RepairReport, …); domain symbols are imported from the domain crates, never re-exported here.
posthaste-engine JMAP gateway, JMAP sync, JMAP push transports, JMAP compose helpers, mock gateway support.
posthaste-imap IMAP/SMTP gateway, IMAP discovery, sync, mutation, lazy body/blob fetch, SMTP submission.
posthaste-authority-server The far (authoritative) node of the runtime↔authority-server link: authority-server node assembly over config, secrets, SQLite store, domain service, and provider gateways; account supervisor; sync; push; OAuth flow state; AuthorityServerLink implementations; and its own link wire (link_router + link auth — the far node owns the surface it serves). No re-exports of near-node symbols.
posthaste-server Composition root: assembles nodes and mounts routers (the /v1 API adapter with auth/authz, OAuth HTTP routes, the authority server's link wire), OpenAPI/AsyncAPI serving, CORS, tracing, optional static app serving, and daemon/loopback process concerns; no facade re-exports and no logic beyond assembly (one shared node-assembly helper, one fail-closed LinkAuth constructor). Ships the role binaries posthaste-authority-runtime-server (the bundled all-in-one: authority server + runtime behind one HTTP server) and posthaste-authority-server (the standalone far node).
posthaste-observability Logging and tracing helpers shared by authority-server crates.
posthaste-lab Verification and lab tooling outside the primary mail runtime, including config validate for checking a TOML config directory before use.

1.2 Dependency direction

posthaste-domain-model defines the shared domain language; posthaste-domain-service defines the authority-server domain contracts over it. Store, config, and provider crates implement domain-service ports. The authority-server crate assembles those implementations behind the link surface (posthaste-authority-server-link). The server crate is the composition root. The workspace-wide graph — including the shared vocabulary tier (posthaste-contract-core), the runtime contracts (posthaste-runtime-api, posthaste-client-link), and the wasm-pure frontier — is in Crate topology §2.

posthaste-server
  -> posthaste-http-api-adapter
  -> posthaste-authority-server
  -> posthaste-observability

posthaste-authority-server
  -> posthaste-authority-server-link
  -> posthaste-domain-service (-> posthaste-domain-model)
  -> posthaste-config
  -> posthaste-store
  -> posthaste-engine
  -> posthaste-imap
  -> posthaste-observability

posthaste-config -> posthaste-domain-service
posthaste-store  -> posthaste-domain-service
posthaste-engine -> posthaste-domain-service
posthaste-imap   -> posthaste-domain-service

Provider crates should not depend on API handler types, store code should not depend on provider crate types, and API handlers should not become the place where provider/store normalization happens.

1.2.1 Motivation

The domain crates are the common language: keeping the model types and service ports below provider, store, config, and server code lets authority-server pieces vary independently while preserving one set of mail concepts.

2. Runtime assembly

2.1 Runtime handle

The runtime crate (posthaste-runtime) assembles one runtime handle — the near node — before any transport adapter is built; the far node it links to is assembled by posthaste-authority-server (Crate topology §1.4). The handle is the process-local implementation of the shared runtime contracts (posthaste-runtime-api for typed wire-free domain RPC, posthaste-client-link for link/view/frame/mutation-forward link ops) for mail behavior.

The handle contains or references:

  1. configuration repository
  2. local store
  3. secret store
  4. domain service
  5. account supervisor
  6. event log and event broadcast channel
  7. query/view service and view registry
  8. mutation coordinator and idempotency store
  9. body/blob cache service
  10. resource resolver
  11. account/setup and OAuth flow state when enabled

2.2 Handle construction

Handle construction is transport-free: from RuntimeBuildConfig { roots, process settings, adapter capabilities } the builder opens the config repository (importing defaults when needed), opens the secret store, opens and migrates the SQLite store, builds the domain service, syncs source projections, and creates the event bus, account supervisor, query/view service, mutation coordinator, and resource resolver, returning an RuntimeHandle and ShutdownHandle. It may also return process support values (log guards, API auth material) that are not part of mail behavior. (Construction order assertion: runtime-build-before-adapters.)

2.3 Handle method groups

The runtime handle exposes method groups, not transport routes:

Group Examples
Lifecycle start enabled accounts, reload config, report runtime/account status, graceful shutdown.
Reads settings/accounts/mailboxes/smart mailboxes/tags, message detail, conversation view, local query pages.
Links/views open renderer runtime links, subscribe to RuntimeFrames, open/update/close views in a link.
Mutations run mutation, retry, undo, resolve conflict, inspect mutation state.
Events subscribe to runtime events, read event history for API catch-up/snapshot.
Resources resolve attachment/body/source resources into bytes, handles, or scoped local capabilities.
API compatibility build shared API projections and command responses for /v1 adapters.

Methods accept domain/runtime inputs plus an adapter-neutral caller context. They do not accept Axum extractors, HTTP headers, Tauri window handles, or React/UI types.

2.4 Server and API adapter assembly

The Axum API router is an adapter over the runtime handle. It owns HTTP extraction, authorization, error serialization, and OpenAPI/AsyncAPI serving. It does not own a second service/store/supervisor graph.

Server startup follows this shape:

load process settings
 -> build the runtime handle (near node) and the authority-server far node
 -> build API adapter state around the runtime contracts (posthaste-runtime-api + posthaste-client-link) and the RuntimeHandle
 -> build API router over adapter state
 -> start account runtimes for enabled accounts
 -> listen for API requests and event subscribers

API adapter state may contain HTTP-only concerns such as auth middleware configuration, OpenAPI/AsyncAPI artifacts, CORS settings, account-logo static roots, and OAuth redirect helpers. Mail reads, mutations, view state, provider access, and event history still flow through the runtime handle. The concrete /v1 handler patterns over this handle — link/view routes and event streaming (the sessionless /v1/views pair deleted, D51/M10) — are specified in L3.

2.5 Bundled application embedding

The desktop host embeds the same runtime handle. It may add Tauri startup, window management, local capability injection, renderer IPC, or a temporary loopback bridge, but it must not fork authority-server mail contracts.

Bundled startup resolves platform paths, builds the runtime handle, starts enabled account runtimes, registers the renderer runtime adapter over the handle, and opens renderer windows. The Tauri adapter uses the handle's link, view, mutation, resource, and event methods; it does not call Axum handlers for primary renderer mail behavior, except while a contained loopback bridge is intentionally used during migration.

2.6 Startup and shutdown policy

Startup fails early when a required local dependency cannot be opened; account-specific provider failures usually become account runtime status, not whole-server startup failure. The handle exposes a graceful shutdown path that stops renderer links, closes view subscriptions, asks account runtimes to stop, drains or persists queued mutation/provider work, flushes stores, and releases provider connections. Adapters call shutdown; they do not tear down account runtimes directly.

3. Service structure

3.1 Service inputs

The domain service receives a config repository, store traits (reads, writes, sync state, events, commands, source projections, cache work), explicit account context for account-scoped operations, and provider gateway references when an operation needs remote access. The service does not create provider connections; it uses a gateway supplied by the account runtime or caller for the duration of the operation.

3.2 Service outputs

The service returns domain values, command results, sync results, and domain errors; transport adapters map those into API responses, logs, tests, or runtime status updates. When the service creates domain events, the caller publishes them through the runtime event channel after the relevant state change is durable or observable.

3.3 Service operation groups

Service methods group around authority-server contracts:

Group Shape
Configuration Read, update, reload, and diff account/app settings.
Reads Compose local projections from config and store state.
Sync Load cursors, call provider sync through a gateway, apply sync batches, record follow-up work.
Lazy content Fetch body/blob data through a gateway and persist cacheable results.
Mutations Execute provider commands, apply local command results, and create events.
Automation/cache work Use local projections and provider gateways to perform authority-server maintenance work.

4. Provider structure

4.1 Gateway implementations

Provider gateway implementations live outside the server crate:

Driver Gateway implementation
JMAP posthaste-engine live gateway.
IMAP/SMTP posthaste-imap live gateway.
Mock development/test gateway implementing the same provider contract.

The server chooses the gateway implementation from account configuration and provider policy when an account runtime connects.

4.2 Provider connection structure

A live provider connection includes the authenticated protocol client, provider profile/policy, account settings needed by the driver, and any transport state needed for sync or push.

Provider drivers may cache discovery results or live capability state for the connection lifetime; durable sync state belongs in the store, not only in the live connection.

4.3 Provider operation flow

Remote operations follow this shape:

runtime or service has account context
 -> runtime resolves or creates provider gateway
 -> service calls gateway operation
 -> gateway speaks provider protocol
 -> gateway returns domain record/result/error
 -> service applies local state changes through store contracts

Provider-specific retries, token refresh, and reconnects belong at the runtime/gateway edge. Domain service code should see provider results through the gateway contract.

5. Store structure

5.1 Local database role

The store contains local mail state, sync state, event history, and projections used by authority-server reads. It is optimized for local runtime/API reads while preserving enough provider identity and cursor state for correct sync.

The database is authority-server-private: API responses are projections over store state, not direct table exposure.

5.2 Write path

Sync and command writes enter through store write contracts. A write may touch several record families in one transaction:

  • mailbox records
  • message records
  • optional conversation/thread projection indexes
  • mailbox/message membership
  • keyword state
  • message body or attachment metadata
  • sync cursors
  • source projections
  • event records
  • cache work queues

5.3 Read path

Read methods return projections needed by the service and API handlers:

  • accounts and source metadata composed with config
  • mailbox and smart mailbox views
  • message lists and derived conversation views
  • message detail and cached body state
  • sender, identity, tag, and command support data
  • runtime-visible event history

Read code should preserve account scoping and pagination rules before data leaves the authority server.

6. Config and secret structure

6.1 Config structure

Configuration is loaded into a snapshot. Authority-server reads use the snapshot, and writes replace the relevant TOML state atomically before updating the in-memory view.

posthaste-domain-service owns the semantic config validators (the validate_* functions); the ConfigError and ValidationError enums live in posthaste-domain-model so wire consumers reach the error vocabulary without the service crate. The TOML repository assembles files into a candidate snapshot, validates the whole snapshot, and only then swaps it into the repository view. Runtime mutation paths use the same domain validators for account settings, default-account selection, and automation settings so API/runtime writes and hand-edited TOML share one source of validation truth.

A reload compares the new validated snapshot with the previous snapshot and reports a diff. If parsing or semantic validation fails, reload rejects the candidate snapshot and leaves the previous snapshot active. Runtime code uses the diff from accepted reloads to decide which account runtimes need lifecycle changes.

Config files contain non-secret settings, account/provider metadata, secret references, and redacted secret status. They do not contain long-lived provider credentials or access tokens.

6.2 Root structure

Authority-server roots are resolved before runtime handle construction. Config, state, and cache roots are runtime-owned. They are not renderer local-storage locations and are not inferred from webview state.

The state root contains the SQLite store, event history when external to SQLite, sync cursors, mutation idempotency records, and provider work queues. The cache root contains body/blob and attachment cache data. The config root contains non-secret settings and account assets.

6.3 Secret structure

Secret storage is addressed through secret references stored in configuration. Provider connection code resolves the reference to credentials or tokens before building a gateway.

OAuth refresh or credential rotation updates the secret store. It does not rewrite unrelated account TOML and must not copy refreshed tokens into config, logs, or renderer storage.

6.4 Account lifecycle after config changes

Config changes that affect account enablement, provider settings, credential references, or sync behavior flow through the supervisor:

config write or reload
 -> config diff
 -> supervisor compares affected accounts
 -> stop, start, restart, or leave runtime unchanged
 -> publish status or config-related events when needed

7. Assertions

ID Sev. Assertion
domain-is-shared-language MUST Authority-server implementation crates communicate through the shared domain language — posthaste-domain-model types and posthaste-domain-service contracts — rather than API handler types or provider-specific private types.
runtime-contract-crate MUST The UI/API-facing runtime contracts (posthaste-runtime-api, posthaste-client-link) are available outside posthaste-server so server and desktop adapters can share them.
runtime-handle-assembly MUST The runtime implementation assembles one runtime handle that contains or references config, store, secrets, service, supervisor, events, views, mutations, cache, and resource services.
runtime-build-before-adapters MUST Config/store/secret/service/supervisor construction happens in the runtime builder before API or desktop adapter assembly.
handle-methods-transport-free MUST Runtime contract and runtime handle methods do not use Axum extractors/responses, Tauri handles, or frontend component types.
transports-use-runtime-handle MUST API and desktop adapters use the shared runtime contracts and runtime handle rather than constructing separate mail behavior.
adapter-state-http-only MUST API adapter state contains HTTP/auth/artifact concerns but does not own a second mail service/store/supervisor graph.
runtime-shutdown-owned MUST Adapters request runtime shutdown through the handle rather than stopping account runtimes or provider connections directly.
authority-server-roots-runtime-owned MUST Config, state, and cache roots are resolved for the runtime and are not renderer-owned storage locations.
config-secret-reference-only MUST Config files contain secret references/redacted status rather than provider credentials or access tokens.
domain-config-validation-source MUST Config load and runtime mutation paths use the domain-service validators (over the domain-model error enums) as their shared semantic validation source.