Infrastructure DNS

Purpose

Describe owner-local Terraform DNS declarations and runtime source-ownership validation for global and dc1 records. This contract describes checked-in source; it does not establish live adoption or authorize infrastructure writes.

Sources: component documentation, target definitions, runtime linter, and Terraform root.

Requirements

Requirement: Aggregate records from their owning components

DNS declarations SHALL remain in their owners’ dnsconfig.json files. Runtime inventory SHALL discover all canonical files, including nested modules and empty declarations, without a checked-in ownership registry. The inventory SHALL print a deterministic table of files, DNS names, types and views.

Project landing declarations retain their declared direct targets.

Scenario: A new project adds a canonical declaration

  • WHEN the runtime linter scans a workspace containing the new file
  • THEN it includes the file without requiring a registry update

Requirement: Preserve distinct global and site-local views

The Terraform module SHALL preserve Cloudflare global and MikroTik dc1 views. It SHALL retain every type member, expand and deduplicate destinations, reject unknown inputs and preserve effective values, TTLs, priorities and multiplicity.

Scenario: An entry declares A and AAAA in all views

  • WHEN the shared module normalizes the entry
  • THEN both types appear exactly once in each destination view

Scenario: An input names an unsupported destination

  • WHEN a record names an unsupported destination
  • THEN normalization fails with an actionable diagnostic

Requirement: Validate configuration without provider access

The linter and module tests SHALL validate source ownership, names, inputs, normalization and provider mappings without credentials or live provider access. Existing JSON-based VM consumers SHALL remain compatible.

Scenario: Validate a changed declaration

  • WHEN offline checks run
  • THEN they validate the declaration and fixtures without deploying records

Requirement: Inject credentials into operational wrappers

Owner Terraform roots SHALL use the existing AL/Vault authentication, backend and credential injection workflow. They SHALL obtain only their required view credentials. Operational provider initialization may require live credentials even with resource creation disabled. Central DNSControl write entrypoints SHALL be removed from the candidate.

Scenario: A global-only owner operates its root

  • WHEN an authorized operator invokes the root after bootstrap
  • THEN it uses that owner’s AppRole and Cloudflare credentials
  • AND RouterOS credentials and resources are not required

Requirement: Reconcile DNS through its owner’s Terraform state

Each owner SHALL instantiate the shared module in tf_setup when present, otherwise in tf. Missing roots and AppRoles SHALL be added. Related AppRole resources SHALL be grouped in modules under infra/vault/tf/approles/<name>. Provider instance keys SHALL be stable across value-only changes; unrelated owners SHALL remain outside the state.

Scenario: An owner has both stages

  • WHEN the integration is prepared
  • THEN only its setup root declares the DNS module

Requirement: One declaration file owns each DNS name

The runtime linter SHALL reject different files managing the same canonical fully qualified DNS name, across types and views. Multiple records within one file SHALL be allowed. Diagnostics SHALL identify both files.

Scenario: Different files split a name across types or views

  • WHEN one file declares an A record and another an AAAA record or a different view for the same name
  • THEN lint fails and identifies both source files

Scenario: One file manages multiple records for a name

  • WHEN one file declares several values, types or views for a name
  • THEN ownership lint accepts the sole source

Requirement: Preserve live records during transfer

Operational cutover SHALL audit active and scheduled central writers, stop identified competing writers, and coordinate one owner at a time. Evidence SHALL state the audit’s coverage and unavailable observations. Each existing record SHALL be imported using its actual provider ID into exactly one owner state, followed by a no-change adoption plan before writes. Fresh complete inventories MAY establish missing declarations for an exact additions-only plan that preserves every existing record. Endpoint-owning batches SHALL verify an independent authenticated recovery path first.

The shared apex and mail records owned by infra/dns SHALL remain disabled by default in its tf root before the authorized adoption revision and SHALL retain enabled ownership after import. Reconciliation against the adopted state and unchanged declarations SHALL propose no record additions, changes, replacements, or deletions and SHALL preserve unrelated provider records.

Scenario: An owner is adopted

  • WHEN its authorized cutover runs
  • THEN exact-ID imports preserve existing records
  • AND the operator coordinates exclusive active writers and verifies unrelated records remain unchanged

Scenario: Deploy verified missing declarations

  • WHEN complete provider inventories establish that declared names are absent and conflict-free
  • THEN the reviewed DNS plan creates only those missing declarations
  • AND every existing provider identity and attribute remains unchanged
  • AND a follow-up DNS plan contains no changes

Scenario: Inspect the prepared shared-record defaults

  • WHEN the shared tf root uses its checked-in defaults before its authorized adoption revision
  • THEN shared-record ownership is disabled and the root retains the canonical shared declaration and module inputs

Scenario: Inspect adopted shared-record defaults

  • WHEN the adopted shared tf root uses its checked-in source defaults
  • THEN ownership is enabled for the shared apex and mail records in their declared views

Scenario: Reconcile existing shared records

  • WHEN the shared tf root plans against adopted state and unchanged declarations
  • THEN it proposes no record additions, changes, replacements, or deletions
  • AND unrelated provider records remain unchanged

Requirement: Avoid a separate DNS exporter

The implementation SHALL derive its declaration views from the owning dnsconfig.json files rather than maintaining a normalized inventory or BIND exporter. Historical provider snapshots SHALL NOT be presented as current desired state, and no provider snapshot SHALL be committed as a declaration source.

Scenario: Inspect declared ownership

  • WHEN the runtime linter succeeds
  • THEN its table reports the discovered declarations without an export step

Requirement: Publish declaration pages per destination view

The implementation SHALL render one documentation page per destination view from the declared records, with the owning declaration for every record, and SHALL fail its offline check when a checked-in page differs from the declarations it projects.

Scenario: Regenerate a declaration page

  • WHEN an owner changes a declaration and the generation command runs
  • THEN the affected view’s page lists the declared records with their owner
  • AND the check passes against the regenerated page

Scenario: A declaration page is stale

  • WHEN a checked-in page no longer matches the declarations
  • THEN the offline check fails rather than serving a stale inventory