Skip to content

Phase 3 -- Phase B Deploy Log

Concise record of the cdk deploy step for the Corporate stacks (after corporate-cli prepare free-kanban Phase A had populated cdk.context.json and the Postmark Sender Signature was registered).

  • AWS account: platformRoot (841876193886)
  • AWS region: us-east-1
  • AWS profile: Admin-PlatformRoot
  • CDK app entry: tools/cdk-corporate.ts

Both stacks created cleanly, no manual intervention, total wall time ~2.5 min:

StackResultDeploy timeNotable
CorporateMailDns✅ CREATE_COMPLETE90 sNS-delegation CR Custom::CrossAccountNsDelegation completed in ~6 s after the framework Lambda was warm; SPF + DMARC RecordSets followed
FreeKanbanToolMailDns✅ CREATE_COMPLETE46 sDKIM TXT + Return-Path CNAME

Hosted zone ID created: Z059300336Y7ZG0WVQOF6 (arda.ardamails.com). Exported as Corporate-MailZoneId and Corporate-I-MailZoneId (the -I- form is the internal CDK-to-CDK consumer; consumed by FreeKanbanToolMailDns via Fn.importValue).

Verification (public DNS resolved via 8.8.8.8 within minutes)

Section titled “Verification (public DNS resolved via 8.8.8.8 within minutes)”
NS arda.ardamails.com → ns-1004.awsdns-61.net (+3 others)
TXT arda.ardamails.com → "v=spf1 include:spf.mtasv.net ~all"
TXT _dmarc.arda.ardamails.com → "v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:dmarc-reports@arda.cards"
TXT 20260509065909pm._domainkey.freekanban.arda.ardamails.com → public DKIM key
CNAME pm-bounces.freekanban.arda.ardamails.com → pm.mtasv.net

The NS resolution through a public resolver (Google DNS) is the load-bearing check: it proves the Custom::CrossAccountNsDelegation CR wrote the new zone’s nameservers into the parent ardamails.com zone via AllowCreatingNSRecordsRole (cross-account assume). If the CR had silently no-op’d, public DNS would still answer with the previous (or NXDOMAIN) state.

Confirms the documentation on PR #77 (cdk-infrastructure.md Lambda topology section):

  • CorporateMailDns: 3 Lambda functions
    • CorporateNsDelegation/Handler (the CR business logic)
    • CorporateNsDelegation/Provider/framework-onEvent (CDK provider wrapper)
    • LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a (per-stack LogRetention singleton — services log-group retention for logRetention:-enabled Lambdas in the stack; in CorporateMailDns both of the Lambdas above opt in, so both are covered, but the singleton is not “every Lambda” in general)
  • FreeKanbanToolMailDns: 0 Lambdas (pure RecordSet stack, no CR needed because the zone is owned by the stack below)

The LogRetention singleton confirms the “one per stack, scoped to logRetention: opt-ins” claim in the doc. The framework wrapper is created per Provider instance — CorporateMailDns has one Provider, so one framework Lambda.

I-1: CR ordering inside CFN happened to be benign

Section titled “I-1: CR ordering inside CFN happened to be benign”

CorporateNsDelegation/Resource (the actual Custom::CrossAccountNsDelegation) initiated before the LogRetention for the framework Lambda completed. CFN happily proceeded; the CR call into Route53 succeeded because the Lambda’s log group already existed (Lambda created it on first invoke). The LogRetention CR’s job is only to set a retention policy on a log group that may or may not exist yet — it does not gate Lambda invocability. This is reassuring: no deploy-ordering hazard between the framework Lambda and its retention policy.

End-to-end time for the Custom::CrossAccountNsDelegation CR (init → CREATE_COMPLETE): ~6 s. The cold start of the framework wrapper Lambda is the dominant component; the actual Route53 ChangeResourceRecordSets call is in the noise.

I-3: DKIM published values match what corporate-cli prepare wrote to cdk.context.json

Section titled “I-3: DKIM published values match what corporate-cli prepare wrote to cdk.context.json”

The DKIM TXT served from Route53 byte-matches postmark.ardaArdamailsCom.dkimKey in cdk.context.json. Confirms the DKIM-pending fallback in corporate-cli.ts (read from DKIMPending* when the active fields are empty) produced the correct key. Without this fallback the synthesised template would have published a record with an empty p= field and Postmark verification would have failed silently.

  • Trigger corporate-drift.yml once manually (gh workflow run) to confirm the steady-state check is green on the just-deployed config; capture the run URL.
  • Postmark Console → server Free Kanban Generator: confirm Sender Signature for freekanban.arda.ardamails.com flips DKIM + Return-Path to Verified (may take up to a few minutes after public DNS propagation).
  • Send a “Send-a-test” from the Console and verify headers: DKIM=pass, Return-Path = the configured bounce domain.
  • Mark PR #450 (infrastructure) and PR #77 (documentation) ready for human review.

Resolution — DQ-R1-009 divergence found and fixed

Section titled “Resolution — DQ-R1-009 divergence found and fixed”

The Postmark verification follow-up above surfaced a divergence between Phase A’s Postmark registration (parent, per DQ-R1-009) and Phase B’s CDK DKIM record placement (leaf). Diagnosis, root cause, and the fix are recorded in dqr1009-divergence.md. Summary:

The DQ-R1-009 decision was prose only — in the decision log, in a docstring, in the runbook — with no value or function any code consumed. The CLI honored it inline by accident; the CDK construct’s API conflated DKIM placement with Return-Path placement under a single subdomain parameter and silently followed the simpler convention (both records at the leaf), contradicting the decision. PR #450 commit cd85527 introduces a typed source-of-truth function sendingDomainPlacement() in platform/constructs/postmark/sending-domain.ts that all three consumers (CLI, construct, drift check) now read; the construct’s API takes absolute record FQDNs composed via helper functions, so name composition no longer happens inline at each call site. A new cross-seam assertion in corporate-drift.ts compares Postmark’s reported Name, DKIMPendingHost / DKIMHost, and ReturnPathDomain against the same function. A redeploy (~47 s, single-resource update-with-replace on the DKIM TXT) moved DKIM to the parent; verifyDkim and verifyReturnPath flipped both verification flags on first poll. The Lambda topology claim made earlier in this document is unchanged by the fix.