Public Access
Add ADRs 0001-0006 and README (M0)
Records the decisions the milestone plan already made, with their costs stated rather than only their benefits: - 0001 e2ee-trust-model: key hierarchy, the AAD-to-row binding that stops the server moving ciphertext between rows, and the four-layer public-key trust story. States plainly that revocation is not retroactive, that Connect cannot be a security boundary, and that the IdP becomes a key-distribution trust root. - 0002 minimal-apis: feature modules with explicit registration; capability negotiation instead of Asp.Versioning, since client and server upgrade independently when self-hosted. - 0003 sync-protocol: single write path, revision cursors, and the bigserial pre-commit sequence gap that silently corrupts sync — plus the per-vault advisory lock that fixes it and the test that must prove it. - 0004 relay-authorization: relay forwards bytes rather than terminating SSH, so zero-knowledge survives; server-resolved target IPs in the ticket to defeat DNS rebinding; why host addresses must be plaintext when relay is enabled. - 0005 no-application-layer: why the usual Application/mediator layer earns nothing here, with the trigger that would make us revisit it. - 0006 observability-stack: OTel plus built-in ILogger; liveness excludes dependencies so a database blip cannot restart the container and kill live SSH sessions. Also adds a README covering layout, build, enforced conventions and milestones.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# ADR 0005 — No separate Application layer
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-07-28
|
||||
|
||||
## Context
|
||||
|
||||
A conventional layering for a .NET service of this size would be Domain → Application →
|
||||
Infrastructure → Api, with a mediator and one handler per use case.
|
||||
|
||||
DodoSSH's server is, in substance, CRUD plus one interesting write path
|
||||
([ADR 0003](0003-sync-protocol.md)) plus a byte relay ([ADR 0004](0004-relay-authorization.md)).
|
||||
It performs **no cryptography on secrets and no business logic over item contents**, because it
|
||||
cannot read them. The genuinely hard logic — merge, key wrapping, trust decisions — lives on
|
||||
the client.
|
||||
|
||||
## Decision
|
||||
|
||||
Three projects: `DodoSSH.Domain` (entities, enums, invariants, no EF), `DodoSSH.Infrastructure`
|
||||
(DbContext, configurations, migrations, queries), `DodoSSH.Api` (host, with vertical slices
|
||||
under `Features/`). Plus `DodoSSH.Contracts` for shared DTOs and `DodoSSH.Crypto` for the
|
||||
envelope format.
|
||||
|
||||
No `Application` project, no mediator.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A feature is one folder under `Features/`: endpoint, request/response mapping, and the query
|
||||
or command inline. Reading an endpoint means reading one file, not tracing a request through
|
||||
a handler, a validator, a behaviour pipeline and a repository interface.
|
||||
- The testability argument for an Application layer does not apply here. The tests that matter
|
||||
for this server are authorization tests and sync-protocol tests, and both must run against a
|
||||
real PostgreSQL to be meaningful — an in-memory handler test would pass while the EF query
|
||||
filter, the advisory lock and the CHECK constraints all went unexercised. Integration tests
|
||||
with Testcontainers are the primary suite, so an extra seam buys nothing.
|
||||
- Business invariants that are genuinely invariant (permission algebra, relay target
|
||||
validation, cursor encoding) live in `Domain` as pure functions and are unit-tested there.
|
||||
That is where the fast tests belong.
|
||||
- **Accepted risk:** if the server later grows real domain logic — server-side policy
|
||||
evaluation, workflow, notifications with side effects — endpoints will start to get long.
|
||||
The mitigation is a rule, not a layer: when an endpoint file exceeds ~80 lines, extract a
|
||||
named service into `Features/<Area>/`. Revisit this ADR if that happens three times in one
|
||||
area.
|
||||
- `DodoSSH.Crypto` is referenced by the API but only for format and fingerprint constants. It
|
||||
must stay free of any code path that could decrypt a payload server-side, so that the
|
||||
dependency cannot quietly become a capability.
|
||||
|
||||
### Rejected
|
||||
|
||||
- **Application layer with MediatR.** Adds a hop and a handler per feature. Its usual payoffs —
|
||||
transaction/validation/logging behaviours, and decoupling from the web framework — are either
|
||||
already provided (endpoint filters, ProblemDetails) or irrelevant (there is no second host).
|
||||
- **Repository interfaces over EF Core.** `DbContext` is already a unit of work and EF's global
|
||||
query filters *are* the authorization backstop ([ADR 0002](0002-minimal-apis.md)). Wrapping
|
||||
it would hide the mechanism that keeps the API failing closed.
|
||||
Reference in New Issue
Block a user