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:
- configuration repository
- local store
- secret store
- domain service
- account supervisor
- event log and event broadcast channel
- query/view service and view registry
- mutation coordinator and idempotency store
- body/blob cache service
- resource resolver
- 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. |