# Design Notes for dxaws-dns This document captures the design intent and architectural rules for the `dxaws-dns` primitive. The module is intentionally small: it exists to provide a stable, composable contract for managing DNS record sets in AWS Route 53 using **declarative convergence**. --- ## Architectural Position in dxaws `dxaws-dns` is a **primitive module**. That means: - It exposes **stable Python contracts**. - Its public interfaces are **immutable and only extendable**. - It contains **no orchestration** beyond converging a single DNS record. - It is designed to be composed by higher-level modules (e.g., website/CDN orchestration). In the dxaws stack, the rough layering looks like: - **dxaws-core**: shared session, provider base classes, o11y - **dxaws-dns**: DNS record primitives (this module) - **higher-level modules**: orchestration (CloudFront, websites, multi-account) --- ## Core Pattern: Declarative Convergence The core workflow is: 1. **Normalize desired** (complete inputs; canonicalize) 2. **Read current** (from provider) 3. **Plan** (diff desired vs current) 4. **Apply** (execute the plan) 5. Return a result with an outcome: - `noop` if no changes were required - `applied` if AWS was mutated The contract goal is deterministic behavior: - Apply once → `applied` - Apply again → `noop` - Introduce drift → `applied` - Re-apply → `noop` This is validated via AWS-backed acceptance tests. --- ## Layering ### Models (contracts) Models define the stable API surface and must not leak AWS/boto3 shapes. - `DnsRecordDesired` - `DnsRecordCurrent` - `DnsRecordPlan` - `DnsManagerResult` **Contract notes:** - `zone_name` is the human-facing identifier and is always required. - `zone_id` is optional at call sites and may be resolved by the system. ### Manager (orchestration + normalization) The manager owns: - Input normalization (canonicalization) - Workflow orchestration (current → plan → apply) - Emitting o11y events (when enabled) **Normalization rules** live here because: - It keeps the planner deterministic. - It prevents duplication across call sites (CLI, tests, orchestrators). ### Planner (pure diff) The planner: - Takes `(desired, current)` - Produces a plan - Must be side-effect free (no AWS calls) ### Executor (mutation) The executor: - Takes a plan - Calls provider write operations - Must not perform input enrichment (e.g., resolving `zone_id`) ### Provider (AWS isolation) The provider is the only layer that should: - Touch boto3 - Encode AWS/Route 53 quirks The provider surface should stay small and explicit. --- ## Canonicalization Rules These are the canonicalization rules that make diffs stable and idempotency real. ### Hosted zone identity Callers provide: - `zone_name` (e.g., `dev.dxaws.com`) The system may resolve: - `zone_id` via `provider.resolve_zone_id(zone_name=...)` This keeps AWS implementation details out of call sites. ### Record names Record names are normalized to a fully-qualified name based on `zone_name`. Example: - `record_name="_test"` + `zone_name="dev.dxaws.com"` - becomes `"_test.dev.dxaws.com."` ### Record types Record types are normalized to uppercase. ### TXT values (quoting) Route 53 returns TXT values wrapped in double quotes. To keep diffs stable: - Desired TXT values are normalized to quoted form. - Provider write operations also normalize to quoted form. This ensures that a second apply becomes `noop`. ### CNAME targets Route 53 stores DNS names with a trailing dot. Callers should prefer canonical targets (e.g., `example.com.`). The planner operates on the normalized values. --- ## Acceptance Tests as Contract Tests Acceptance tests in this module are treated as **contract tests**. They validate: - Create / read / delete lifecycle - Idempotency (`noop` on second apply) - TTL drift detection + correction - Multiple record types (TXT, A, CNAME) Safety features: - Tests are opt-in (`DXAWS_AWS_TESTS=1`). - Tests require explicit zone confirmation (`DXAWS_ACCEPTANCE_CONFIRM`). - Tests print AWS identity before mutation. - Tests clean up after themselves and verify deletion. --- ## Tradeoffs and Intentional Limitations - This module focuses on **record sets**, not zone provisioning. - It avoids complex routing policies (weighted, latency, geo) until the core contracts are proven. - It intentionally prefers explicit, composable APIs over convenience wrappers. If/when the module expands, it should do so by extending interfaces rather than changing existing fields or semantics. --- ## Extensibility Guidelines When extending this module: - Prefer adding new optional fields to `DnsRecordDesired` over changing meaning. - Keep planner logic deterministic and side-effect free. - Put AWS quirks in the provider and canonicalization in the manager. - Add acceptance coverage for any new record type or behavior.