This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

OpenSpec

1 - Infrastructure DNS

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.

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.

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

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
  • WHEN a record names an unsupported destination
  • THEN normalization fails with an actionable diagnostic

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.

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

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.

  • 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

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.

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

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.

  • 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
  • WHEN one file declares several values, types or views for a name
  • THEN ownership lint accepts the sole source

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.

  • 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
  • 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
  • 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
  • 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
  • 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

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.

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

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.

  • 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
  • WHEN a checked-in page no longer matches the declarations
  • THEN the offline check fails rather than serving a stale inventory