docs/adr/0051-rdap-consensus-rescue-and-probe.md
Rama master · réplica de lectura del repositorio original.
# ADR-0051: RDAP Consensus Rescue Leg and Startup Probe — Closure of ADR-0050 Gaps
## Metadata
| Field | Value |
|-------|-------|
| **Status** | Accepted |
| **Date** | 2026-08-10 |
| **Authors** | AlessioBrillo |
| **Deciders** | AlessioBrillo |
| **Supersedes** | N/A (alters ADR-0050 §1 fail-closed rule in one opt-in mode) |
| **Relates to** | ADR-0002, ADR-0035, ADR-0039, ADR-0045, ADR-0049, ADR-0050 |
| **Project** | DOMINUS |
## Context
ADR-0050 shipped the opt-in 2-of-2 RDAP consensus gate, but three gaps
surfaced during the hardening review:
1. **The startup probe was never implemented.** ADR-0050 §6 promises a boot
probe of the second leg, mirroring `probeConsensusProvider` in the DNS
layer (ADR-0045); `composition-root` only probes the DNS secondary. A dead
second leg is therefore discovered at the first degraded run, not at boot.
2. **`RDAP_CONSENSUS_TIMEOUT_MS` is dead config.** Defined in the schema
(default 10000) with test fixtures locking it, but `createRdapConsensusConfig`
never passes it to the consensus provider, which hardcodes
`DEFAULT_RDAP_TIMEOUT_MS`. The knob cannot be tuned and docs drift from
behaviour.
3. **The 2-of-2 gate has no rescue path.** ADR-0050 §4 explicitly rejected a
fabricated third RDAP leg, but left the gate binary: a second leg that is
flaky (registry slowness, transport errors, rate limiting) or down
downgrades every `Available` to `Unknown`. DNS solved this exact problem
with the ADR-0045 tertiary rescue; RDAP has no equivalent. Fail-closed is
correct, but a gate that cannot distinguish "second leg vetoed" from
"second leg was down" converts a provider outage into silent run-wide
filtering.
The unique structural fact is unchanged: RDAP has one authoritative source
per TLD, so a *third RDAP channel* would be a rubber stamp. But the
WHOIS bounded enrichment already racing inside the stage (ADR-0035) is a
second, genuinely different channel — port 43, different operator, different
failure modes. It is the weakest channel, but it is already paid for: the
stage runs it inside a 1s budget with zero extra volume when disabled.
## Decision
Close all three gaps in one change:
1. **Startup probe (gap 1).** Add `probeRdapConsensusEndpoint`, mirroring
`probeConsensusProvider` (DNS): when the gate is enabled, the second leg
is probed at boot with a known-domain query; a failure is logged
prominently (non-fatal, gate stays enabled, run-level degraded flags
still apply). The probe runs from the factory wiring in `composition-root`.
2. **Wire the timeout (gap 2).** `FailoverRdapProvider.fromConfig` gains an
optional `timeoutMs` (default `DEFAULT_RDAP_TIMEOUT_MS`); the RDAP
consensus factory passes `config.RDAP_CONSENSUS_TIMEOUT_MS`. The primary
leg is untouched — only the second leg honors the knob.
3. **Opt-in WHOIS rescue leg (gap 3).** New flag
`RDAP_CONSENSUS_RESCUE_WHOIS_ENABLED` (default `false`). When enabled and
a WHOIS provider is configured, candidates whose *second leg cannot
answer* (`error`/`timeout`/`Unknown`) are re-checked through WHOIS within
the same bounded budget as the stage's normal WHOIS race:
- WHOIS `available` → verdict confirmed, counted `verified` and
`tertiaryRescued` (new `RdapConsensusStats` field, mirroring DNS).
- WHOIS `registered` → veto, counted `disagreed` — "registered wins"
(ADR-0002) and a rescue can never create an `Available`.
- WHOIS timeout/error → stays `unverifiable` → `Unknown` (unchanged).
- A *definitive Registered from the second RDAP leg* remains a final
veto — the rescue leg is never consulted on a veto (ADR-0050 §1).
- Degradation accounting is unchanged (ADR-0039): rescues reduce the
`unverifiable` numerator, so a healthy WHOIS backstop can itself clear
the degraded flag.
This is a **narrow, opt-in override** of ADR-0050 §1's "failure to answer
downgrades to Unknown — never Available": it applies only to the
unverifiable class, never to disagreements, and only when the operator
explicitly enables the flag. The default posture remains fully
fail-closed.
### Options considered and rejected
- **Status quo (binary 2-of-2).** Correct conservatism; unusable when the
second leg flakiness is transient — exactly the case consensus should
absorb. Rejected as the end state; kept as the default mode.
- **Third RDAP leg.** Rejected in ADR-0050 (rubber stamp); re-rejected here.
- **WHOIS rescue on disagreements.** Rejected: a WHOIS `available` must never
counter a second-leg `Registered` — "registered wins" is absolute.
- **Default-on rescue.** Rejected: WHOIS is the weakest channel; defaulting
to a weaker backstop would silently relax the gate on every default
install. The rescue is an operator decision.
## Consequences
### Positive
- Operator discovers a dead second leg at boot, not after N degraded runs.
- `RDAP_CONSENSUS_TIMEOUT_MS` becomes tunable end-to-end; docs and schema
converge.
- A transiently failing second leg no longer empties the `Available` set:
the WHOIS channel rescues only the verifiable class, still vetoed by any
`Registered`. Zero extra query volume when disabled.
### Negative
- `unverifiable → Available` is now reachable under an explicit opt-in —
a conscious trade of a sliver of fail-closed strictness for resilience.
- Rescue queries draw from the WHOIS port-43 budget on the rescued subset
only (the stage's existing rate limiter is shared); on a heavy run the
rescued subset competes with WHOIS enrichment volume.
### Risks and mitigation
- **False Available via WHOIS "no match" on a registered domain.** A WHOIS
"not found" for a registered domain is rare (registrar WHOIS privacy
changes this; mitigated by the second-leg veto already having failed).
Acceptable residual risk under explicit opt-in; measurable via
`tertiaryRescued` vs `disagreed` tallies.
- **WHOIS floods on gate outages.** The rescue is bounded by the same
concurrency ceiling (`RDAP_CONSENSUS_BULK_CONCURRENCY`) and the 1s budget;
it never adds unbounded per-candidate traffic (the race is sequential over
the rescued subset).
### Related ADRs
- ADR-0002 (conservatism — "registered wins", fail-closed default)
- ADR-0035 (RDAP authoritative bootstrap; WHOIS bounded enrichment)
- ADR-0039 (consensus degradation policy — accounting unchanged)
- ADR-0045 (DNS tertiary rescue — the pattern mirrored, adapted to WHOIS)
- ADR-0049 (RDAP transport parity — the transport the gate rides on)
- ADR-0050 (RDAP 2-of-2 consensus — the gate this ADR closes the gaps on)