Ctrl K
ComunidadGuías…

docs/adr/0051-rdap-consensus-rescue-and-probe.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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)