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:
2026-07-28 12:28:44 +02:00
parent 3a81f3c90b
commit ce43f397a6
7 changed files with 551 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
# ADR 0002 — Minimal APIs with explicitly registered feature modules
- Status: accepted
- Date: 2026-07-28
## Context
The API is roughly 60 endpoints: identity and enrollment, a public-key directory, teams,
vaults and grants, sync, read-only item queries, relay, audit and admin. It is consumed by
one first-party desktop client.
## Decision
Minimal APIs, grouped into feature modules under `Features/`, registered **explicitly** from
`Setup/EndpointRegistration.cs`. Typed results (`Results<Ok<T>, ForbidHttpResult,
ProblemHttpResult>`) throughout, one endpoint per file.
Version with a hard-coded `/api/v1` prefix and **no `Asp.Versioning` package**. Instead ship
capability negotiation:
- `GET /api/v1/meta` — server version, sync protocol version, crypto spec version, feature
flags, `minClientVersion`, and the push caps.
- `GET /.well-known/dodossh-configuration` — OIDC authority, client id, scopes, relay URL.
## Consequences
- Per-endpoint filters and metadata compose cleanly, and typed results give an accurate
OpenAPI document without attribute noise.
- Explicit registration over reflection scanning: predictable startup cost, trimming-friendly,
and every route is greppable. The cost is one line per module, which is worth paying.
- Minimal APIs' real weakness is that a cross-cutting concern is easy to forget. Two
mitigations, both required: group-level `RequireAuthorization`, and an **endpoint-inventory
test** that enumerates `EndpointDataSource` and fails the build if any endpoint is absent
from an explicit authorization expectations table. A new endpoint therefore cannot be added
without making an authorization decision.
- Capability negotiation over version routing is the right trade for a self-hosted product,
where client and server upgrade independently and skew is normal rather than exceptional.
`.well-known` also *is* the onboarding story: the user types one server URL and the client
discovers the rest. `Asp.Versioning` gets added when a v2 actually exists, not before.
- The generated OpenAPI document is for third parties and a future CLI. It is emitted at build
time to `artifacts/openapi/v1.json`, committed and CI-diffed. **The client's real contract is
the `DodoSSH.Contracts` assembly**, guarded by PublicApiAnalyzers so an accidental DTO change
is a build error rather than a runtime deserialisation failure on a user's laptop.
### Rejected
- **MVC controllers.** Would work, but bring filter/model-binding machinery this API does not
need and blur the one-endpoint-one-file structure that keeps the authz surface reviewable.
- **Reflection-based endpoint discovery.** Convenient until a route silently disappears
because an assembly was not loaded, or trimming removes it.