docs/adr/0092-community-first-run-resolver-latency.md
Rama master · réplica de lectura del repositorio original.
# ADR-0092: The community first run does not wait for a resolver that is not there
## Metadata
| Field | Value |
| -------------- | --------------------------------------------------------- |
| **Status** | Accepted |
| **Date** | 2026-10-20 |
| **Authors** | Alessio Brillo |
| **Deciders** | Alessio Brillo |
| **Supersedes** | — |
| **Relates to** | ADR-0002, ADR-0072, ADR-0075, ADR-0076, ADR-0091 |
| **Project** | DOMINUS |
## Context
Unbound is the single source of truth for DNS (ADR-0072, ADR-0075). The
application waits for the resolver to become healthy and to prove DNSSEC
validation before it accepts traffic, and in cloud mode (`DNS_UNBOUND_STRICT`
on) it refuses to start without it.
The wait was configured with a single default: 30 seconds, in every
deployment mode. In the community edition that default is paid on every
command, by every user, for a resolver that in most community installations
is not there at all: the self-hosted "Personal" profile in the README is the
CLI alone, with no Unbound sidecar.
The measured cost, from the first run of the published artifact
(`scripts/verify-release.mjs`, ADR-0091): the CLI spent ~30 seconds in
"Unbound readiness check failed — retrying" before falling back to the Node
resolver. The command that the README's Quick Start advertises —
`npx dominus-oss@latest score vintagecoffee.com` — took half a minute
before it printed anything. The fallback was correct; the latency was not.
The fallback is already the accepted community posture (ADR-0076): it is
explicitly non-strict, it never produces an `Available` verdict that the
DNSSEC-validating resolver did not produce, and the strictness of the
production path is unaffected. The wait changes nothing about the outcome in
the community edition; it only changes how long the user waits to reach the
same outcome.
## Decision Drivers
1. **The first run is the product's first impression.** The public release
makes `npx dominus-oss` a one-command experience. A half-minute of
silence before the first output reads as a hang, and users who read it
that way do not run the command again.
2. **The wait must not weaken the guarantee.** DNSSEC validation must stay
mandatory for anything the product calls `Available`. Nothing here may
change what a verdict means.
3. **The two modes want opposite defaults.** Cloud needs the long wait (the
resolver must be there, and a sidecar that is still booting deserves the
time). The community edition does not (the resolver is optional there).
4. **The change must be visible in the configuration.** A hidden latency
change is a surprise; a documented default that differs per mode is a
fact.
## Considered Options
### Option A: One default for both modes (status quo)
**Advantages:** simple.
**Disadvantages:** the community edition pays 30 seconds for a resolver that
is usually absent, on every command.
### Option B: Skip the readiness wait entirely in the community edition
**Advantages:** the fastest possible first run.
**Disadvantages:** it also skips the Unbound configuration validation
(`validator` module, `val-permissive-mode: no`), which is a diagnostic a
self-hoster with a broken `unbound.conf` would want. A misconfigured
resolver in a Docker deployment would degrade more quietly.
### Option C: A mode-dependent default for the readiness timeout
**Advantages:** the readiness path stays intact — the health check, the
DNSSEC proof, the configuration validation — and only its budget changes.
A resolver that is starting up still gets its chance; a machine with no
resolver stops waiting after five seconds instead of thirty.
**Disadvantages:** two defaults to remember, and the community figure is
still a wait.
## Decision
**Option C.**
- `DNS_UNBOUND_READINESS_TIMEOUT_MS` defaults to **30 000 ms in cloud mode**
and **5 000 ms in the community edition**. The deployment mode is the one
the configuration already computes (`detectCloudMode`: a `DATABASE_URL`
or an `AUTH_PROVIDER` other than `env`).
- The mode detection is the same one that drives `DNS_UNBOUND_STRICT`, so
the two defaults cannot disagree: the mode that is strict about the
resolver is the mode that waits longest for it.
- An explicit value always wins. The variable stays overridable, and the
`.env.example` entry documents both figures.
- Nothing else changes: the readiness path, the health check, the DNSSEC
proof, the configuration validation and the strict-mode refusal all
behave exactly as before.
## Consequences
### Positive
- The community first run reports in seconds instead of half a minute, with
the same verdict and the same guarantees.
- The cloud path is untouched: a resolver that is still booting still gets
the thirty seconds it needs, and a missing resolver still fails the boot.
- The fallback still never produces an `Available` verdict that the
DNSSEC-validating resolver did not produce (ADR-0002, ADR-0076).
### Negative
- A resolver that becomes available between the fifth and the thirtieth
second is no longer waited for in the community edition. The
`FallbackResolver` re-evaluates the primary on every call, so the
resolver is picked up as soon as it is healthy; the window is a slower
first lookup, never a wrong verdict.
- Two defaults exist. The test
(`src/__tests__/config-readiness-timeout.test.ts`) pins both, so they
cannot drift.
## Links
- `src/config.ts` — the mode-dependent default.
- `src/__tests__/config-readiness-timeout.test.ts` — the pinned defaults.
- `src/app/provider-factory.ts` — the readiness wait and the fallback.
- `scripts/verify-release.mjs` — the verification that measured the latency.