Architecture
Bounded contexts
How Matter's codebase is organised by domain. Seven bounded contexts (Authority, Entity lifecycle, Equity, Documents, Compliance, Operations, Platform) plus a Customer surfaces context. Each context has typed boundaries enforced by architecture tests. External providers are wrapped in anti-corruption layers.
Last updated
Matter's codebase is organised by bounded context — a domain-driven design pattern that maps each cluster of related responsibilities to a directory of packages with well-defined boundaries. Inside a context, packages know each other's types intimately. Across contexts, communication is typed, narrow, and (where it touches external systems) routed through anti-corruption layers.
The seven runtime contexts plus a customer-facing surfaces context are listed below. The context map at the bottom shows which contexts may depend on which. The boundary is enforced by architecture tests at apps/api/__contracts__/architecture/.
The seven contexts
Authority
The trust root. Tokens, scopes, authorisations, the Incorporator Protocol cryptographic chain, key management.
Packages. auth-api-key, token-service, authorization-service, crypto, incorporator-protocol.
Aggregate roots. Token (tok_*), Authorization (auth_*), IncorporatorReceipt (rcp_*), SigningSession (sig_*).
External boundary.
- Clerk (OAuth) — wrapped by
packages/auth. Provides human-principal session identity. - KMS (AWS / GCP) — wrapped by
packages/crypto/src/kms.ts. All cryptographic material lives in KMS; code never sees raw keys. - Sigstore Rekor — wrapped by
packages/crypto/src/rekor.ts. External anchor for the audit chain.
Invariants this context owns.
- Every token's
hashedSecretis argon2id with a KMS-resident pepper. - Every authorisation queue entry has a typed expiry and an audit trail.
- Every IncorporatorReceipt chain verifies against an m-of-n KMS-resident master signer.
- KMS rotation has overlap windows long enough that no in-flight signature is invalidated.
Entity lifecycle
The Create / Manage / Exit core. Entities, intents, formation sessions, corporate transactions.
Packages. entity-service, intent-resolver, formation-packet-service, dissolution-service, corporate-transaction-service.
Aggregate roots. Entity (ent_*), Intent (int_*), FormationSession (fsn_*), CorporateTransaction (ctx_*).
External boundary.
- State SOS filing providers — wrapped by
packages/api-providers/<state>/acl.ts. Per-state ACLs translate SOS responses into canonicalFilingtypes. - IRS — wrapped by
packages/api-providers/irs/acl.ts. EIN application, Form 966, tax election filing.
Invariants this context owns.
- The lifecycle state machine (
draft → submitted → registered → active → dissolving → dissolved) is exhaustively typed. Invalid transitions return 409. - Every Entity has a verifiable IncorporatorReceipt chain.
- Every Entity is anchored to a single Portfolio.
- The dissolution cascade saga (
POST /v1/entities/{id}/dissolve) is a TLA+-verified composite with typed compensations.
Equity
Cap-table truth. Stakeholders, share classes, the ledger, grants, valuations, rounds, convertibles, MFN cascades.
Packages. stakeholder-service, equity-plan-service, grant-service, convertible-service, round-close-service, equity-math.
Aggregate roots. Stakeholder (stk_*), EquityPlan (plan_*), ShareClass (cls_*), ShareLedgerEntry (led_*), Grant (grt_*), Convertible (cvt_*), Round (rnd_*), Valuation (val_*), VestingOverlay (vov_*).
External boundary.
- Transfer agents — wrapped by
packages/api-providers/transfer-agent/acl.ts. Reconciliation between Matter's cap table and the external roster. - Banks (for cash exercise) — wrapped by
packages/api-providers/bank/acl.ts.
Invariants this context owns.
- The cap-table read model (CapTable computed view) is replay-deterministic from the ShareLedgerEntry log + Grants + Convertibles + Valuations.
outstanding ≤ authorizedper share class at every snapshot.- Append-only ledger:
ShareLedgerEntryrows accumulate; amendments are new rows, not updates. - Every cap-table-touching mutation has a linked authorising Resolution.
- The Series A close package saga and MFN cascade saga are TLA+-verified composites with typed compensations.
Documents
Legal artefacts as data. Documents, versions, signing envelopes, files, templates.
Packages. document-service, signing-service, template-catalog, file-service.
Aggregate roots. Document (doc_*), DocumentVersion (docv_*), SigningEnvelope (env_*), File (file_*), FileLink (link_*).
External boundary.
- E-sign providers — wrapped by
packages/api-providers/esign/acl.ts. Per-provider ACLs translate envelope events. - Storage — wrapped by
@repo/storage(Vercel Blob).
Invariants this context owns.
- The document state machine (
draft → sent → partially_signed → executed | void | superseded) is exhaustively typed. - Every signing path enforces ESRA consent at the service layer (cannot be bypassed by direct service callers).
Documenthas both a JSON shape (fields) and a rendered PDF (pdf_url), kept in sync.- Versioning is append-only.
Compliance
The "stay legal" surface. Filings, mail, state registrations, qualifications, tax profiles, trademarks.
Packages. compliance, filing-providers, mail-service, state-registration-service, tax-service, qualification-service, trademark-service.
Aggregate roots. Filing (flg_*), Mail (mail_*), StateRegistration (reg_*), Qualification (qal_*), TaxProfile (tax_*), Trademark (tmk_*), ComplianceObligation, Routine.
External boundary.
- State SOS (per-state) — wrapped by
packages/api-providers/<state>/acl.ts. Same wrappers as Entity lifecycle but consumed for ongoing maintenance. - IRS (for ongoing tax filings).
- USPTO (for trademark filings) — wrapped by
packages/api-providers/uspto/acl.ts. - Mail forwarders — wrapped by
packages/api-providers/mail/acl.ts.
Invariants this context owns.
- The Compliance computed view is replay-deterministic from ComplianceObligations + Filings + Mail + StateRegistrations.
- Obligations are seeded automatically on entity formation per
@repo/jurisdictionsrules. - Per-state filing-provider circuit breakers + failover.
- AI mail categorisation defends against prompt injection (length cap, structural validation, output schema validation).
Operations
The "operate the live entity" surface. Banks, transfers, transfer agents, reports, batch, bulk, search, sweeps.
Packages. bank-account-service, transfer-service, transfer-agent-service, report-service, batch-service, bulk-service, search-service, sweep-service.
Aggregate roots. BankAccount (bka_*), Transfer (xfr_*), TransferAgent (ta_*), Report, ReportRun (run_*).
External boundary.
- Banks — wrapped by
packages/api-providers/bank/acl.ts. - Transfer agents (same as Equity).
- Reporting backends (CSV / JSON / signed PDF generators).
Invariants this context owns.
- Transfers above threshold require a linked authorising Resolution.
- Bank account closure cannot complete while creditor claims are open (during dissolution).
- Search index excludes PII fields by default.
Platform
The cross-cutting kernel. Portfolios, events, webhooks, CQRS, sagas, caching, feature flags, rate limits, observability, the database client, the request lifecycle primitives.
Packages. portfolio-service, event-emitter, webhooks, cqrs, saga, cache, feature-flags, rate-limit, observability, database, resource-ids, pagination, expand, idempotency, ioc, internal-call, request-context, consistency, events.
Aggregate roots. Portfolio (pf_*), WebhookEndpoint (whe_*), Event (evt_*), AuditEntry (aud_*), Request (req_*), SagaInstance (sga_*), BackgroundJob, IdempotencyRecord, Consent, TestClock (clk_*).
External boundary.
- Postgres + Neon serverless — wrapped by
@repo/database. - Upstash Redis — wrapped by
@repo/rate-limit. - Svix — wrapped by
@repo/webhooks. - Sentry + Logtail — wrapped by
@repo/observability. - Vercel Flags — wrapped by
@repo/feature-flags. - OTel collector — wrapped by
packages/observability/src/otel.ts. Vendor-neutral.
Invariants this context owns.
- The Event table is the canonical state-transition log; other contexts write to it via the canonical emitter.
- The audit chain is JCS-canonicalised, hash-chained, per-(org, mode) serialised, externally anchored.
- The CQRS read models are deterministically replayable from the Event log.
- Saga compensations are TLA+-verified.
- Per-entity sub-sequencing is partitioned and monotonic at the receiver.
Customer surfaces (cross-cutting consumer)
Customer-facing tooling that consumes everything above.
Packages. sdk-conformance, webhook-receiver-mock, cli, sdk-node, sdk-python, sdk-go, data-export-service, metering-service, quota-service, sla-engine.
Aggregate roots. Usage, Invoice, ExportJob, Quota.
External boundary.
- Stripe (for billing) — wrapped by
@repo/payments. - Customer-supplied OTel collectors / log sinks — for trace + log export.
Invariants this context owns.
- SDKs conform to the spec via the conformance suite.
- The CLI is a thin wrapper around the Node SDK.
- The in-browser API Console reads from the customer's sandbox token only; never live.
- Data export is per-org, signed-URL delivered, redaction-aware.
Context map
Allowed dependency arrows. Architecture tests at apps/api/__contracts__/architecture/no-cross-context-imports.test.ts enforce.
┌───────────┐
│ Authority │ (Tokens, scopes, IPP — trust root)
└─────┬─────┘
│ every other context depends on Authority
▼
┌───────────────────┬────┴────┬───────────────────┐
│ │ │ │
┌──────▼──────────┐ ┌─────▼────┐ ┌──▼──────────┐ ┌────▼───────┐
│ Entity lifecycle│ │ Equity │ │ Documents │ │ Compliance │
│ │◀─┤ │ │ │◀─┤ │
└────────┬────────┘ └─────┬────┘ └──────┬──────┘ └────┬───────┘
│ Entity lifecycle Equity → Compliance │
│ depends on Documents (cap-table docs) │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │Operations│ │
│ └────┬─────┘ │
│ │ │
└─────────────────┴─────────────────────────────┘
│ every context depends on Platform
▼
┌───────────┐
│ Platform │ (Events, webhooks, CQRS, saga, cache, DB)
└───────────┘
│
▼
┌───────────────────┐
│ Customer surfaces │ (SDK, CLI, console, exports)
│ consumes all │
└───────────────────┘Allowed arrows:
- Authority is the trust root. Every other context depends on Authority.
- Entity lifecycle → Documents (cap-table-touching mutations require executed Resolutions).
- Equity → Documents (grant agreements, side letters, indemnification).
- Equity → Compliance (cap-table changes drive compliance obligations).
- Operations → Equity (bank-account closure during dissolution).
- Compliance → Documents (filings carry attached Documents).
- All contexts → Platform.
- Customer surfaces → all.
Forbidden arrows:
- Authority → any domain context (Entity lifecycle, Equity, Documents, Compliance, Operations, Customer). The trust root has no domain upstream. Authority does depend on Platform (DB, KMS, observability).
- Platform → any domain context. The kernel does not know about domain concerns. Platform does depend on Authority (principal, scope, livemode signals).
- Documents → Entity lifecycle / Equity. Documents are inputs to other contexts, not coupled the other way.
- Cross-context circular dependencies between domain contexts.
The Authority ↔ Platform pair is tightly coupled by design — both are kernel-tier. The architecture test (apps/api/__contracts__/architecture/no-cross-context-imports.test.ts) permits this pair and forbids the asymmetric domain arrows.
Anti-corruption layers (ACLs)
Every external provider is wrapped in an ACL at packages/api-providers/<provider>/acl.ts. The ACL is the only code that knows the provider's response shape, error codes, retry semantics, rate limits, or quirks. It translates provider responses into canonical Matter types and provider errors into the Matter error taxonomy.
ACL contract:
export interface IFilingProvider {
submit(args: SubmitArgs, ctx: ProviderCallContext): Promise<MatterFilingResult>;
status(filingId: string, ctx: ProviderCallContext): Promise<MatterFilingStatus>;
cancel(filingId: string, ctx: ProviderCallContext): Promise<MatterFilingResult>;
// ... per-provider extensions in a typed map
}
// Every ACL passes a conformance suite at packages/api-providers/__tests__/acl-conformance/The conformance suite (packages/api-providers/__tests__/acl-conformance/) runs every ACL through a battery of input/output translation tests. New providers are added via a per-state PR that ships the ACL + the conformance pass + the runbook.
How a new context decision is made
Adding a new bounded context is a high-stakes architectural decision. Process:
- RFC draft. The proposer drafts an RFC at
apps/docs/rfcs/<yyyy-mm-dd>-<slug>.mdxdescribing: why a new context (not an extension of an existing one), what aggregate roots it owns, what invariants it owns, what its external boundary is, who its consumers are, what arrows it adds to the context map. - API Council review. Discussed in the weekly forum. Approval requires ≥ 3 reviewers including ≥ 2 from affected contexts.
- Architecture test update. The new context is added to
apps/api/__contracts__/architecture/no-cross-context-imports.test.ts; allowed arrows declared explicitly. - Documentation update. This page is updated with the new context's section.
Splitting an existing context follows the same process.
See also
- API design philosophy — the principles.
- Architecture overview — the system diagram.
- Capacity plan — how scaling interacts with context boundaries.
- API Council — the forum that approves context decisions.