Ctrl K
ComunidadGuías…

docs/adr/0040-dns-consensus-fallback-parity.md

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

Archivo original ↗Preguntar a KiBot
# ADR-0040: DNS Consensus Fallback Parity

## Metadata

| Field | Value |
|-------|-------|
| **Status** | Superseded by [ADR-0072](0072-dns-consensus-revalidation-hardening.md) and [ADR-0075](0075-dns-unbound-single-source-completion.md) |
| **Date** | 2026-08-07 |
| **Authors** | AlessioBrillo |
| **Deciders** | AlessioBrillo |
| **Supersedes** | N/A |
| **Relates to** | ADR-0002, ADR-0039 |
| **Project** | DOMINUS |

## Context

The 2-of-3 DNS consensus gate introduced by ADR-0039 only ran on the
`DnsPreFilterStage` bulk-success path and the cross-validation-recovered
path. When the bulk check failed entirely (`checkBulk` throws or returns a
mismatched count), `#perDomainFallback` resolved every candidate through the
primary provider alone and returned the verdicts **without** asking the
secondary provider for confirmation.

The effect was an ADR-0002 asymmetry that depended on the *happy path* of
the transport layer:

> A candidate that reached the retry path received a verdict resting on a
> single provider's opinion, while the identical candidate on the bulk path
> received a two-independent-opinion verdict.

Bulk failures are not exotic — they are exactly what happens under network
saturation, a temporarily unavailable resolver group, or a Redis rate-limiter
`RateLimiterWaitTimeoutError`. In other words, the availability guarantee
weakened precisely then the run was under stress, which is the moment the
engine must be strictest, not laxest (ADR-0002 conservatism).

A secondary symptom: fallback-path runs never produced `verified` /
`disagreed` / `unverifiable` tallies or the `consensus-unverified`
degradation signal, so a run that silently lost its guarantee was
indistinguishable from one that genuinely had no eligible domains (the
ADR-0039 "silent throughput collapse" failure mode, reappearing at the
fallback layer).

## Decision

**Consensus is now applied on every resolution path.** `#resolveBulkWithFallback`
routes every degraded exit through a single helper,
`#fallbackWithConsensus`, that runs the per-domain fallback and then the
same strict 2-of-3 cross-check (`#applyConsensusIfConfigured`) used on the
bulk path:

- A per-domain verdict of `Available` from the fallback is only final when
  the secondary provider independently confirms it.
- A secondary failure (reject/`Unknown`/throw) downgrades the candidate to
  `Unknown`, and is counted in `consensusStats` / the degradation signal
  exactly as on the bulk path.
- Stages configured without a secondary (consensus disabled) behave
  exactly as before — `#applyConsensusIfConfigured` is a no-op.

### Options considered and rejected

- **Accept primary-opinion verdicts on the fallback path (status quo)** —
  rejected: it is the ADR-0002 gap being closed, and it only manifested on
  the degraded path of a degraded network layer.
- **Abort the run instead of falling back when the bulk check fails** —
  rejected: the bulk failure is typically transient and the primary check is
  still a legitimate fast signal (ADR-0039 rationale); aborting forfeits the
  run over a retryable transport hiccup.
- **Only log a warning without downgrading** — rejected: a warning does not
  enforce the conservatism principle; the verdict itself must not rest on a
  single provider.

## Consequences

### Positive

- ADR-0002 parity across **all** resolution paths: a bulk failure can no
  longer strip the two-provider availability guarantee.
- Fallback runs produce the same `consensusStats` and degradation signals as
  bulk runs, so degraded output is visible to operators (ADR-0039 machinery
  is preserved on the fallback layer).
- Single source of truth: the consensus entry point is one helper used by
  every exit of `#resolveBulkWithFallback`.

### Negative

- On a genuinely sick secondary, a run whose bulk check failed will now see
  all Available verdicts downgraded to `Unknown` (fail-closed is the desired
  outcome; there is no throughput win to re-claim).
- Adds one round of consensus queries on an already-degraded path, marginally
  extending wall-clock where the fallback was already slow.

### Risks and mitigation

- **Regression risk in fallback timing:** the fallback path now runs an extra
  consensus pass with the same `fallbackConcurrency` bound; the pass is
  bounded per-query by the provider timeouts that already cover the bulk
  path. Monitoring series (`dominus_dns_consensus_*`) expose whether
  fallback-downgraded runs are becoming frequent.