Skip to content

Phase 2 -- Implementation Alternatives

Decisions reached during Phase 2 implementation, with the alternatives considered and the trade-offs weighed. Decisions persistent enough to warrant a DQ-R1-NNN log entry are also recorded in ../../decision-log.md; this document captures the implementation context for the formal entries plus the smaller decisions that didn’t earn a formal entry.

A-1: Adopt-vs-create the existing ardamails.com zone

Section titled “A-1: Adopt-vs-create the existing ardamails.com zone”

Choice: adopt via cdk import against Z0721066239FWCD47EJDX (formal entry: DQ-R1-008).

Considered:

OptionMechanismTrade-offs
A cdk importOne-time IMPORT change-set adopts the live zone into RootConfiguration. Subsequent cdk deploy runs treat the zone as fully CDK/CFN-managed.Two-phase deploy choreography on first run (IMPORT change-set, then normal deploy). Properties on the CDK side must mirror the live zone’s properties exactly. Once imported, the experience is identical to a “created from scratch” zone.
BReference via r53.HostedZone.fromHostedZoneAttributes()Zone stays outside CDK control. CDK code can resolve its zone ID for export but cannot manage records on it (root-level SPF, DMARC) without separate tooling. Doesn’t match the “Phase 2 declares the zone” intent in phases.md.
CDelete-and-recreateDelete the auto-created zone via Route53 console, then cdk deploy to create a fresh CDK-managed zone. The new zone has different AWS-assigned nameservers, so the registrar NS chain must be updated (Route53 Domains console). Down-time risk: between the delete and the registrar NS update, the domain has no resolution. Not a serious option for a production-bound domain.

Why A: matches the project’s stated intent (Phase 2 declares and owns the zone), the existing zone has zero records that would be lost in a delete-and-recreate, and the IMPORT pattern — once worked through — generalises cleanly to other “AWS auto-created this for us” cases. The two-phase deploy is a one-time operator action; subsequent deploys are normal cdk deploy runs.

Why not B: the project plan explicitly puts the ardamails.com zone under CDK ownership for downstream record management (SPF, DMARC if needed at the apex). B would have meant maintaining a parallel non-CDK pathway for those records; not aligned with the architecture intent.

Why not C: the registrar NS chain is unchanged when adopting; recreating would require a registrar-side update with a brief resolution gap. Avoidable.

The full decision text and consequences live in DQ-R1-008.

A-2: Mirror the AWS-default zone comment vs adopt a project-context comment

Section titled “A-2: Mirror the AWS-default zone comment vs adopt a project-context comment”

Choice: mirror the live zone’s comment ("HostedZone created by Route53 Registrar").

Considered:

OptionComment stringTrade-offs
AMirror AWS’s default: "HostedZone created by Route53 Registrar"IMPORT change-set is read-only (Scope: []). Comment is technically misleading once the zone is CDK-managed.
BProject-context: "ardamails.com mail-root zone -- managed by RootConfiguration CDK stack"Comment accurately describes the current ownership. IMPORT change-set reports Scope: [Properties] and would write the comment change. Read-only-import intent is lost.
CMirror initially, change in a follow-up deployTwo writes: A then B in successive deploys. Adds operator complexity for a comment string.

Why A: the IMPORT discipline is “make the synthesized template’s resource block match the live zone byte-for-byte”. The comment string is a property of the hosted zone and falls under that rule. The cost (a slightly stale comment) is small; the benefit (a verifiable read-only import) is concrete. The strict-equality unit test locks the value so a future “let me make the comment more accurate” edit fails at test time, prompting the editor to either also update the live zone or revert.

If the comment ever genuinely needs updating, do it as a separate, intentional CFN update with its own diff and review.

A-3: Logical ID — preserve ArdamailsZone vs choose a different name

Section titled “A-3: Logical ID — preserve ArdamailsZone vs choose a different name”

Choice: keep new r53.PublicHostedZone(this, "ArdamailsZone", ...).

Considered:

OptionConstruct path / logical IDTrade-offs
AArdamailsZone (chosen)Reads naturally; CDK mangles to ArdamailsZone1DCDDC15; IMPORT uses the mangled form.
BMailRootZoneMore semantically descriptive (“this is the root of the mail subtree”). Logical ID becomes MailRootZone<hash>.
CArdamailsComMirrors the DNS name. Logical ID becomes ArdamailsCom<hash>.

Why A: the four existing family zones use construct names tied to their DNS labels (AppZone, IoZone, AuthZone, AssetsZone); ArdamailsZone matches the convention. Logical IDs are stable as long as the construct path doesn’t move, so the choice has no operational implications post-import; it’s purely a naming decision.

A-4: Strict-equality unit test vs a property-subset assertion

Section titled “A-4: Strict-equality unit test vs a property-subset assertion”

Choice: strict-equality on the full Properties + DeletionPolicy + UpdateReplacePolicy block (Template.hasResource(...) with the entire expected block).

Considered:

OptionAssertion shapeTrade-offs
AFull strict-equality (chosen)Locks every property the import target depends on. Future drift fails at test time. Slightly more verbose.
BProperty-subset (Template.hasResourceProperties(...))Asserts only the properties listed; future additions of new properties to the resource block don’t break the test. Less strict; can miss accidental property additions.
CNo strict assertion; rely on the existing “zone exists with correct DNS name” testMinimum coverage. A future code edit that adds a property without intent (e.g., tags) wouldn’t be caught; would land in the next deploy as a CFN modification.

Why A: the import target is read-only by contract. Any deviation from the live zone’s properties is a defect, regardless of whether the property is “added” or “modified”. Strict-equality flags both cases. The verbosity cost is small (one extra assertion in one test file).

The pattern is documented in learnings.md L-3 and is a candidate for promotion to a current-system/architecture/patterns/ page (see suggestions.md S-3).

A-5: Locus of cross-zone NS-delegation writes

Section titled “A-5: Locus of cross-zone NS-delegation writes”

Choice: child stack writes upstream via WriteNSRecordsToUpstreamDns (formal entry: DQ-R1-006).

The full options table and rationale are in DQ-R1-006. Implementation context here:

The choice was settled before Phase 2 implementation began; the implementation pass made the coupling concrete. With Option A (Root stack writes), every subsequent phase that adds a child zone (Phase 3 Corporate, Phase 4 per-partition) would have introduced a back-edit on the Root stack, plus a deploy-order dependency where Root’s NS writes wait on the child zones’ nameserver tokens.

With Option B (chosen), Phase 2 ships fully self-contained: the IAM role exists, the parent zone exists, and that’s the entire surface Phase 3 / Phase 4 consume. The WriteNSRecordsToUpstreamDns construct already exists (used by every partition’s IngressStack to write NS records into the arda.cards family). Re-using it is a zero-cost reuse, not a new abstraction.

A-6: Folder rename target — apps/Root/ vs apps/RootInstance/

Section titled “A-6: Folder rename target — apps/Root/ vs apps/RootInstance/”

Choice: apps/Root/.

Considered:

OptionFolder nameTrade-offs
Aapps/Root/ (chosen)Matches the “InstanceGroup” naming used elsewhere in the rev1 design (Root, Corporate, <partition>). Short.
Bapps/RootInstance/Disambiguates from any other “root” concept. Slightly verbose.
CKeep apps/rootConfiguration/No rename; the folder name continues to read as a configuration concern even though the stack now declares both DNS and IAM.

Why A: the rev1 design uses Root as the InstanceGroup name in instances/<InstanceGroup>/<asset>.ts (Phase 2 introduces instances/Root/dns.ts). Symmetry between the apps/<InstanceGroup>/ and instances/<InstanceGroup>/ directories is the navigation aid. C was rejected because the folder name had drifted from the stack’s actual concern (the prior stack was a configuration miscellany; the rev1 stack is DNS + the IAM role for cross-zone delegation, which together justify the renames in T-I1 and T-I2).

A-7: Stack class rename target — RootDnsStack vs RootStack

Section titled “A-7: Stack class rename target — RootDnsStack vs RootStack”

Choice: RootDnsStack.

Considered:

OptionClass nameTrade-offs
ARootDnsStack (chosen)Names the dominant concern (DNS / Route53). The IAM role is a supporting cast member.
BRootStackGeneric; future Root-instance-group additions (e.g., shared logging, KMS) could land in the same stack without a rename.
CRootInfrastructureStackMost generic; least informative.

Why A: the stack’s primary concern is DNS. The IAM role exists because of cross-zone DNS-delegation writes (DQ-R1-006); it is not a standalone concern. A future Root-instance-group concern that doesn’t fit the DNS framing should go in a new stack (per DQ-013, the project standard is “don’t extract into more stacks unless required”; but if a future genuinely-different concern arrives, it should be its own stack rather than diluting RootDnsStack’s name).

If the IAM role were extracted (rejected by DQ-013), the stack name RootDnsStack would be even more accurate.

A-8: instances/Root/dns.ts content — minimal vs full

Section titled “A-8: instances/Root/dns.ts content — minimal vs full”

Choice: minimal — export the zone-name constants and the expectedExports keys; do not refactor the existing apps/Root/r53-zones.ts to source every literal from this file.

Considered:

OptionScope of instances/Root/dns.tsTrade-offs
AMinimal (chosen)The new file is the source of truth for the new ardamails.com zone name and the expected export keys. The four arda.cards family literals continue to come from platform/ari-configuration.ts (their pre-existing source).
BFull pull-upAll zone-name literals in apps/Root/r53-zones.ts come from instances/Root/dns.ts. Refactor sweeps the four family zones too.
CInline-onlyDon’t add instances/Root/dns.ts at all; declare ardamails.com inline in apps/Root/r53-zones.ts. Doesn’t establish the rev1 declarative pattern.

Why A: the pull-up in B is a refactor with its own surface area (four family zones × spec / test impact), unrelated to Phase 2’s actual scope (introducing ardamails.com). Performing it under Phase 2’s banner would risk re-opening test verification for the family zones for no functional reason. The minimal pattern still establishes instances/Root/dns.ts as the rev1 declarative-config home; future phases (Phase 3 Corporate, Phase 4 partitions) follow the pattern in their own instance-group directories. C was rejected because it doesn’t establish the pattern at all — defers the convention to Phase 3, which makes Phase 2 the only InstanceGroup without one.

A-9: Two-phase deploy — import then cdk deploy vs forward-port the import to a one-shot

Section titled “A-9: Two-phase deploy — import then cdk deploy vs forward-port the import to a one-shot”

Choice: two-phase (IMPORT change-set with stripped template, then normal cdk deploy).

Considered:

OptionMechanismTrade-offs
ATwo-phase (chosen)First operation is a CFN IMPORT change-set with deployed-state + ArdamailsZone resource only. Second operation is a normal cdk deploy that adds the Output and reconciles CDKMetadata. Each phase obeys the constraint of its respective change-set type.
BOne-shot via cdk import CLIThe CDK CLI’s cdk import command would, in principle, produce the same effect. In practice (Phase 2’s investigation), cdk import synthesises the full template and feeds it to a CFN IMPORT change-set, which then errors on the Output addition (per learnings.md L-2). The CLI provides no flag to strip the Output; the operator would have to hand-edit the synth output anyway.
CPre-create the Output by hand, then cdk deployAdd the arda-ardamails-zone CFN export by direct CFN edit, then run cdk deploy. Drives a CFN modification operation outside CDK; muddles the IaC contract.

Why A: A is the cleanest split between “adopt the resource” and “publish the export”. CFN’s IMPORT-change-set ban on Outputs and other modifications is documented; planning around it is straightforward. B and C both invent workarounds for a constraint that A respects.

A-10: Decision-round bucketing — DQ-R1-008 under R1-Phase1 or R1-Phase2

Section titled “A-10: Decision-round bucketing — DQ-R1-008 under R1-Phase1 or R1-Phase2”

Choice: R1-Phase2.

Considered: DQ-R1-008 was discovered during Phase 2 implementation. Earlier DQ-R1-NNN entries used a Round R1-Phase1 heading because they were authored during Phase 1.

Why R1-Phase2: the heading is the operator-readable timeline marker. R1-Phase2 communicates “this decision was made during Phase 2 work”. DQ-R1-006 (settled before Phase 2 implementation began but in service of Phase 2 / Phase 3 ownership) is also under R1-Phase2 — both are decisions resolved in the Phase 2 round. Phase 1 work that surfaces follow-up decisions (none did) would be R1-Phase1; Phase 3 follow-ups will be R1-Phase3. The heading taxonomy is “round-of-decision-resolution”, not “round-of-original-question”.