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).
Environment
Section titled “Environment”- AWS account:
platformRoot(841876193886) - AWS region:
us-east-1 - AWS profile:
Admin-PlatformRoot - CDK app entry:
tools/cdk-corporate.ts
Outcome
Section titled “Outcome”Both stacks created cleanly, no manual intervention, total wall time ~2.5 min:
| Stack | Result | Deploy time | Notable |
|---|---|---|---|
CorporateMailDns | ✅ CREATE_COMPLETE | 90 s | NS-delegation CR Custom::CrossAccountNsDelegation completed in ~6 s after the framework Lambda was warm; SPF + DMARC RecordSets followed |
FreeKanbanToolMailDns | ✅ CREATE_COMPLETE | 46 s | DKIM 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 keyCNAME pm-bounces.freekanban.arda.ardamails.com → pm.mtasv.netThe 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.
Lambda count observed at deploy
Section titled “Lambda count observed at deploy”Confirms the documentation on PR #77 (cdk-infrastructure.md Lambda topology section):
CorporateMailDns: 3 Lambda functionsCorporateNsDelegation/Handler(the CR business logic)CorporateNsDelegation/Provider/framework-onEvent(CDK provider wrapper)LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a(per-stackLogRetentionsingleton — services log-group retention forlogRetention:-enabled Lambdas in the stack; inCorporateMailDnsboth 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.
Insights worth keeping
Section titled “Insights worth keeping”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.
I-2: NS-delegation CR is fast when warm
Section titled “I-2: NS-delegation CR is fast when warm”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.
Follow-ups (next session)
Section titled “Follow-ups (next session)”- Trigger
corporate-drift.ymlonce 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 forfreekanban.arda.ardamails.comflips 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.
Copyright: © Arda Systems 2025-2026, All rights reserved