Files
hq/02-DECISIONS
jschoubben 8d9282d86b Resolve the ingress gap: a route is a grant
ADR 0048 named ingress as an unclosed hole -- nothing said what terminates
TLS, how a public name reaches a container, or which tier owned it. Resolving
it needed no new concepts, which is why it survived: nobody had applied the
rules already written to it.

Ingress is not substrate. The control plane does not need a route to start,
and no node needs one to reach it -- the node dials out and has no listening
control surface. It grants itself a route afterwards, like a bucket.

A route is an instantiation edge under ADR 0044. The direction mirrors a
database -- the consumer supplies a target and receives a name rather than
credentials -- but it is the same edge.

The substantive finding is that exposure is three facts at two scopes: name
resolution and certificate issuance need to know which node is publicly
reachable, and only the proxy mapping is a single machine's business. That is
why it belongs to the connectivity context, and why Traefik doing all three on
the node is wrong.

Which matters beyond tidiness: research 006 counted traefik as one of two
modules opening a direct Postgres connection, reading nodes and mesh_ca. That
violates 0037, 0045 and 0039 at once, and is why every node permanently holds
a credential to the control plane's database. Deriving the config centrally and
delivering it as `file` resources removes it, costs zero new host vocabulary,
and closes the set 0039 identified -- wireguard was the other.

Left open deliberately: the mesh's internal CA is the other thing traefik
reads, and it belongs to the link's mutual authority, not to exposure.
Conflating the two is what made the gap hard to see.

Also fixes an inconsistency from the previous commit: 06 still claimed the
virtual host was raised from the bundle.

Proposed, not accepted -- for review.
2026-08-27 00:22:52 +02:00
..

02-DECISIONS

Architecture decision records — the "why" trail behind the rules in 00-META and the specifications in 03-DESIGN.

Numbered 02 because a decision precedes the design it authorises. Research concludes, the decision is recorded here, and only then is the design written. Following the folder numbers walks the process in the order it happens.

One file per decision, numbered, never deleted. A superseded record has its status: changed and gains a pointer to what replaced it — its text is never edited. The reasoning that was rejected is the expensive half to rediscover.

The records run in the order the decisions were taken, oldest first.

Every decision is a record. There is no ledger and no index file — if a decision is worth recording it is worth a record, and if it is not worth a record it is not recorded (ADR 0026). A "decision" small enough to be one line is almost always a rule, and a rule belongs in 00-META/how-we-build.md, where it is enforced and keeps the incident that earned it.

Frontmatter

---
status: proposed | accepted | superseded
date: YYYY-MM-DD          # when the decision was taken, not when it was written down
deciders: name
reconstructed: true|false # true when the record was written after the fact from evidence
superseded-by:            # 02-DECISIONS/NNNN-....md, when status is superseded
extends:                  # 02-DECISIONS/NNNN-....md, when this record widens an earlier one
---

Body

# N. Title in plain language

## Context            what was true, with evidence
## Considered Options numbered, each with why it was rejected
## Decision           what was decided
## Consequences       what follows, including what got harder
## References         commits, pull requests, knowledge-base entries, prior art

State evidence, not assertion. "Zero of 124 modules declare brain as a dependency" outranks "the dependency rule is not followed".

Reconstructed records

Records 0001–0014 were written on 2026-08-23, after the decisions they describe. Records 0015 onward were taken as records. Those decisions were taken in implementation rather than in a document; the records state what was decided and the evidence it was decided from, and each carries reconstructed: true and says so in its first lines.

A reconstructed record is not a transcript. Where the deliberation is not recoverable, the options section states what the alternatives were and why the chosen one won on the evidence available — not a discussion that did not happen. Where a date is not establishable it says so rather than guessing.

Index

The index is generated, not maintained — run the hq-status skill, which reads the frontmatter of every record. A hand-written index drifts from the folder it describes, and this one had already done so after a single addition.