import type { IDnsAuthorityDrift, IDnsAuthoritySettings, IDnsDelegationProbe, TDnsAuthorityState } from '../../dist_ts_interfaces/data/dns-authority.js'; /** * Result of applying an authority change to the running process. * Reconciliation is all-or-nothing: a partial application is rolled back. */ export interface IDnsAuthorityMutationResult { success: boolean; message?: string; probe?: IDnsDelegationProbe; settings?: IDnsAuthoritySettings; } /** Re-derives runtime state after the effective authority set changed. */ export type TDnsAuthorityReconciler = (reasonArg: string) => Promise; /** * DnsAuthorityManager — owns which zones dcrouter may answer for * authoritatively, and proves that claim rather than accepting it. * * Why this exists: `dnsScopes` was the last un-migrated bootstrap option in an * otherwise DB-driven router. It was read once at startup and had no mutation * path, so claiming a newly delegated zone required a restart — measured at * 30–60 s of total public outage. But simply making the declared list editable * through the ops API would have been worse than the disease: the domain * ownership predicate accepts zone coverage as *proof* precisely because a * caller cannot write it. A `setDnsScopes` mutation would let an operator append * any domain and manufacture that proof — the identical self-assertion vector * that `createDcrouterDomain` used to have. * * So a zone earns authority by delegation instead: its public NS records must * name our `dnsNsDomains`. An ops-API caller cannot fake that without actually * controlling the domain, so the proof stays unforgeable while becoming * mutable at runtime. It is also the comparison that was missing — claimed * authority versus real delegation, checked by the mechanism itself rather than * by an audit bolted on afterwards. * * **There is exactly one source of authority: this database document.** An * earlier revision kept bootstrap `dnsScopes` as an always-in-effect floor and * unioned it with the verified set. That is gone. A floor is a second * representation of the same fact, it can only be changed by a redeploy, and it * cannot be revoked through the API — all three of which are the properties * that produced the outage this path exists to prevent. * * Removing the floor makes the *unreadable database* case load-bearing, so it is * handled explicitly rather than by accident. Three situations, three answers: * * - **document missing** — a readable database that has never been seeded. The * authority set is known, and it is empty: claim nothing. Logged at `error` * with the remediation, and the startup drift audit enumerates every zone * delegated to us that we are refusing to serve, which is the worklist. * - **document present, no zones** — identical handling, different message: * somebody revoked everything, which is a legitimate state we must not * silently repopulate. * - **document unreadable** — the authority set is *unknown*. `start()` throws * rather than reporting an empty set, because rendering an unknown as "claim * nothing" is exactly how a transient database fault would take every zone * off the air. `DnsServerRuntime.setup()` then refuses to start on * `getState() === 'unavailable'`. That second check is not redundant: * `DnsServer.dependsOn('DnsManager')` only *orders* startup, and taskbuffer * starts later levels even when an earlier optional service failed, so the * consumer has to read the state itself. Both services stay failed and * dcrouter runs degraded until it is restarted with a readable database — * DNS down and visibly failed, rather than up and asserting nothing. * * Fail closed on authority, never silently on service: with an empty set the * DNS server still starts, still serves DoH, and still picks zones up the moment * one is verified — without a restart. */ export declare class DnsAuthorityManager { private getExpectedNameservers; private verifiedZones; private updatedAt; private updatedBy; private state; private reconciler?; constructor(getExpectedNameservers: () => string[]); start(): Promise; stop(): Promise; /** * Wire the callback that re-derives runtime state (zone handlers, route * certificate warnings, private-route overlay) after the effective set changes. */ setReconciler(reconciler?: TDnsAuthorityReconciler): void; /** * The authority set: delegation-verified zones, sorted so every consumer that * needs a stable "first zone" (the DNSSEC signing zone, for one) gets the same * answer across restarts regardless of database insertion order. * * This is what the ownership predicate consumes, and it is the only input to * it. There is no bootstrap contribution. */ getEffectiveZoneNames(): string[]; /** Whether the stored authority set was readable when it was last loaded. */ getState(): TDnsAuthorityState; getSettings(): IDnsAuthoritySettings; /** * Ask the public DNS whether `zone` is delegated to our nameservers. * * Deliberately uses `strategy: 'doh'` — a single DNS-over-HTTPS attempt * against a public resolver, with no system-resolver fallback. smartdns' * `getNameServers()` uses `dns.resolveNs`, i.e. the *system* resolver, which on * a dcrouter host may be dcrouter itself: it would happily answer with the very * NS records we generated, making the probe self-confirming. An independent * vantage point is the whole point of the proof. * * The three verdicts are kept distinct on purpose. A timeout is not evidence * that a zone is not ours; collapsing 'undeterminable' into 'not-delegated' * would let a transient resolver fault revoke authority. */ probeDelegation(zoneArg: string): Promise; /** * Claim authority over a zone, but only against a positive delegation proof. * An undeterminable probe refuses the mutation — never assume either way. */ verifyZone(zoneArg: string, verifiedBy: string): Promise; /** * Drop a verified zone. Every zone is revocable — there is no undroppable * deployment-declared floor any more, so withdrawing authority never needs a * redeploy and never leaves the router asserting a claim it cannot retract. */ revokeZone(zoneArg: string, updatedBy: string): Promise; /** * Persist a new zone set and reconcile the running process against it. * * Fail closed: if reconciliation throws, the previous set is restored in the * database and re-reconciled, so the router is never left half-converted. * Reconciliation is idempotent, which is what makes the rollback safe. */ private applyZones; private persist; /** * Compare claimed authority against what is actually true, in every direction. * * This is the check whose absence let one zone sit declared in `dnsScopes` * while four live zones were delegated to our nameservers and unclaimed. * * Advisory by design. It never mutates the authority set: a resolver blip at * startup must not revoke authority for every zone and convert a transient * fault into the outage this whole path exists to avoid. Undeterminable probes * are skipped rather than reported as drift. */ auditDelegationDrift(): Promise; /** Run the audit and log every finding at `error`. Never throws. */ logDelegationDrift(): Promise; }