---
title: Application architecture
description: Transport adapters, command/query handlers, permission gates, transactions, audit, repositories, providers, and failure boundaries.
---

The application layer is the shared use-case boundary between delivery mechanisms and infrastructure. A server action, REST route, or MCP tool may authenticate differently, but it must not grow its own version of validation, permission-resource construction, or persistence rules.

## Request-to-effect path

```mermaid
flowchart LR
  subgraph transports [Transport adapters]
    action[Next.js server action]
    rest[REST route /api/v1]
    mcp[MCP tool]
  end

  identity[Resolve tenant, actor and policy]

  subgraph application [packages/application]
    command[Command handler]
    query[Query handler]
    outcome[Typed discriminated outcome]
  end

  permission[packages/domain can()]
  scope[Build one permission resource]

  subgraph persistence [Tenant persistence boundary]
    tx[withTenantTransaction]
    repo[packages/db repository]
    audit[appendAudit]
    outbox[Optional transactional outbox]
  end

  provider[Injected provider or index port]
  response[HTTP / ActionResult / MCP result]

  action --> identity
  rest --> identity
  mcp --> identity
  identity --> command
  identity --> query
  command --> scope --> permission
  query --> scope
  permission -->|allow command| tx
  permission -->|allow query| repo
  permission -->|deny + rule| outcome
  tx --> repo
  tx --> audit
  tx -.when a durable async effect is required.-> outbox
  repo --> outcome
  command -.post-commit or injected dependency.-> provider
  query --> outcome
  outcome --> response
```

Caption: transports resolve authentication and map typed results; handlers own use-case semantics and `can()` gates; repositories own PostgreSQL writes and command-side audit. `emitMutationCommitted()` is the stronger mutation + outbox transaction for capabilities that have adopted it. It is not yet the producer path for every existing worker job, so delivery semantics must be described per call site.

## Responsibilities by layer

| Layer | Owns | Must not own | Source anchor |
| --- | --- | --- | --- |
| Transport | Session/OAuth scope, request parsing, localized labels, HTTP/status mapping, `revalidatePath`, transport metadata | A second authorization or persistence implementation | `apps/web/src/lib/actions/tasks.ts`, `apps/web/app/api/v1/tasks/route.ts`, `apps/mcp-server/src/mcp.ts` |
| Command handler | Normalization required by the use case, `can()` calls, permission-resource construction, race-to-typed-outcome mapping, injected side effects | Next.js, cookies, React, concrete provider construction | `packages/application/src/commands/shared.ts`, `packages/application/src/commands/tasks.ts`, `packages/application/src/commands/matters.ts` |
| Query handler | Permission-filtered projections, `not_found` versus `forbidden`, list filtering | Transport DTO/status mapping | `packages/application/src/queries/tasks.ts`, `packages/application/src/queries/list/runner.ts` |
| Repository | Drizzle/PostgreSQL operations, row locks, transaction-scoped audit | Request/session concerns | `packages/db/src/repos/tasks.ts`, `packages/db/src/client.ts` |
| Provider port | External capability behind an interface; deterministic substitute in tests | Domain authorization or tenant selection | `packages/integrations/src/contract.ts`, `packages/integrations/src/factory.ts` |

## Command and query contracts

Commands receive a `CommandContext` containing a tenant handle, actor, and policy. They return discriminated unions such as `created`, `invalid`, `forbidden`, and `not_found`; genuine infrastructure faults still throw. Queries reuse the same context rather than introducing a near-identical read context.

The task handlers demonstrate two security-sensitive distinctions:

- `getTaskQuery()` refuses an individually unreadable record with `forbidden`.
- `listTasksQuery()` filters unreadable rows and excludes them from `total`; a list must not leak the existence or count of hidden records.
- Matter-derived department and access-level scope is constructed once in the application query and reused by task commands.
- A selected relation or matter ID is input, not authority; handlers reload it through the readable/active boundary before use.

## Transaction, audit, and outbox boundaries

```mermaid
sequenceDiagram
  participant T as Transport
  participant C as Command
  participant P as can()
  participant DB as Tenant transaction
  participant A as audit_event
  participant O as job_outbox
  participant Q as Queue/provider

  T->>C: context + typed input + injected deps
  C->>P: actor, action, resource
  alt denied
    P-->>C: allow false + rule
    C-->>T: typed forbidden outcome
  else allowed
    P-->>C: allow true
    C->>DB: begin pinned transaction
    DB->>DB: business mutation / row locks
    DB->>A: append audit in same transaction
    opt capability uses transactional outbox
      DB->>O: pending event + idempotency key
    end
    DB-->>C: commit
    C->>Q: post-commit index/provider call, or dispatcher publishes outbox
    C-->>T: typed success outcome
  end
```

`withTenantTransaction()` pins one connection, rolls back on throw, and joins an existing transaction instead of nesting `BEGIN`/`COMMIT`. Repositories such as `createTask()` mutate and append audit in that same boundary. `commitMutationWithOutbox()` adds a pending job row atomically and derives an idempotency key from capability, resource, and version. The dispatcher performs queue I/O after commit, with at-least-once delivery and inbox deduplication; it never promises exactly-once execution.

Provider or index calls made directly after a repository commit are a separate failure boundary: a search outage can leave committed source data requiring reconciliation. Do not imply that such an external call rolls the database mutation back.

## Invariants

- Transport authentication selects an actor; only the shared `can()` decision authorizes the resource action.
- Permission-resource construction belongs with the use case, not in every transport.
- A business mutation and its command-side audit either commit together or roll back together.
- Provider and queue network I/O never runs while the transaction that created an outbox row is open.
- Lists filter hidden rows; get-by-id endpoints return a structured refusal.
- Typed business outcomes cross the application boundary; unexpected infrastructure faults remain observable failures.

## Failure modes to design for

| Failure | Expected behavior | Evidence |
| --- | --- | --- |
| Forged or stale related-record ID | Reload through the active/readable boundary and return `invalid` or `not_found` | `packages/application/src/commands/matters.ts` |
| Parent matter deleted between gate and insert | Repository lock/check fails; command maps the known race to `not_found` | `packages/application/src/commands/tasks.ts`, `packages/db/src/repos/tasks.ts` |
| Transaction throws after mutation or audit | Whole pinned transaction rolls back | `packages/db/src/client.ts`, `packages/application/src/events/mutation-event.test.ts` |
| Queue publish fails | Pending outbox remains retryable or becomes `dead` after the configured threshold | `packages/db/src/repos/outbox.ts` |
| Post-commit index call fails | Source-of-truth row remains committed; reconciliation/retry must repair the derived index | `packages/application/src/commands/tasks.ts`, `apps/workers/src/jobs/matter-search-reconcile.ts` |

## Focused validation

```bash
pnpm --filter @blinqx/application test -- --maxWorkers=2
pnpm --filter @blinqx/application typecheck
pnpm --filter @blinqx/db test -- --maxWorkers=2
```

## Source anchors

- Application contracts: `packages/application/src/commands/shared.ts`, `packages/application/src/commands/tasks.ts`, `packages/application/src/queries/tasks.ts`
- Transport adapters: `apps/web/src/lib/actions/tasks.ts`, `apps/web/app/api/v1/tasks/route.ts`, `apps/mcp-server/src/mcp.ts`
- Transactions and audit: `packages/db/src/client.ts`, `packages/db/src/repos/tasks.ts`
- Transactional events: `packages/application/src/events/mutation-event.ts`, `packages/db/src/repos/outbox.ts`

See also: [Architecture](/developers/architecture) · [Tenant isolation](/developers/tenant-isolation) · [Workers and integrations](/developers/workers-integrations)
