Phase 2 -- Root Updates -- Verification
Test catalogue for Phase 2. Every requirement in requirements.md traces to one or more tests here. Every test traces back to a requirement.
Phase 2’s verification surface is purely static / unit: CDK Template-matcher assertions on the synthesized CloudFormation, plus grep / AST assertions on the source-code preservation comment, plus repo-state assertions on the documentation patches. There is no live dig or operator walkthrough in Phase 2. The first cross-account NS-record write happens in Phase 3 (Corporate) and is verified there.
1. Test categories and how they run
Section titled “1. Test categories and how they run”| Category | What it asserts | Runner | Where the test lives |
|---|---|---|---|
| Unit (CDK Template) | Synthesized CloudFormation matches expected resources, exports, and stack name. | Jest + aws-cdk-lib/assertions (existing pattern — see src/main/cdk/stacks/purpose/image-storage.test.ts for a reference). | src/main/cdk/stacks/root/root-dns-stack.test.ts (new). |
| Source-state grep | The CFN-name preservation comment is present at the constructor call site. | Jest test using node:fs to read the source file (same pattern as platform.test.ts in Phase 1). | src/main/cdk/apps/Root/root-app.test.ts (new) — or extend an existing test file in apps/Root/ if convenient. |
| Repo-state grep | Phase 2 documentation patches landed (planning artefacts present, phases.md patched, DQ-R1-006 recorded). | Local make test-links and human-readable diff review. | documentation/ repo’s existing link-check pipeline. |
| Build / lint / synth gate | The repo still builds, lints, tests, and synthesises after the renames. | npm run lint, npm run build, npm test, npx cdk synth --app ... | Already in place; Phase 2 must keep them green. |
2. Traceability table
Section titled “2. Traceability table”| Requirement | Test ID(s) |
|---|---|
REQ-ROOT-001 (ardamails.com zone declared) | V-ROOT-001 |
REQ-ROOT-002 (zone ID exported) | V-ROOT-002 |
REQ-ROOT-003 (existing zones preserved) | V-ROOT-003 |
REQ-IAC-001 (apps folder rename) | V-IAC-001 |
REQ-IAC-002 (stack class + file rename) | V-IAC-002 |
REQ-IAC-003 (CFN name preserved + comment) | V-IAC-003 |
REQ-IAC-004 (instances/Root/dns.ts exists) | V-IAC-004 |
REQ-OPS-001 (deploy-root.sh path update) | V-OPS-001 |
REQ-DOC-001 (planning artefacts published) | V-DOC-001 |
REQ-DOC-002 (DQ-R1-006 and patches present) | V-DOC-002 |
3. Test descriptions
Section titled “3. Test descriptions”V-ROOT-001 — ardamails.com hosted zone is declared
Section titled “V-ROOT-001 — ardamails.com hosted zone is declared”Type: CDK Template-matcher unit test in root-dns-stack.test.ts.
Setup: instantiate RootDnsStack with the standard test props (matching the existing RootConfigurationStack test pattern, if any; otherwise, mirror image-storage.test.ts’s cdk.App + new RootDnsStack(...) shape).
Assert:
template.hasResourceProperties("AWS::Route53::HostedZone", { Name: "ardamails.com.",});Pass criterion: the synthesised template contains exactly one AWS::Route53::HostedZone whose Name property is "ardamails.com." (note trailing dot per Route53 convention).
V-ROOT-002 — ardamails.com zone ID is exported
Section titled “V-ROOT-002 — ardamails.com zone ID is exported”Type: CDK Template-matcher unit test in root-dns-stack.test.ts.
Assert:
template.hasOutput("*", { Export: { Name: "arda-ardamails-zone" },});(Or, equivalently, template.findOutputs("*", { Export: { Name: "arda-ardamails-zone" } }) and assert the result’s length is 1.)
Pass criterion: the synthesised template contains exactly one CloudFormation Output whose Export.Name is "arda-ardamails-zone". Its Value is a Ref to the ardamails.com hosted-zone resource.
V-ROOT-003 — Existing zones and exports preserved
Section titled “V-ROOT-003 — Existing zones and exports preserved”Type: CDK Template-matcher unit test in root-dns-stack.test.ts.
Setup: same instantiation as V-ROOT-001.
Assert (one assertion per existing zone + one for the role + one for each existing export):
template.hasResourceProperties("AWS::Route53::HostedZone", { Name: "app.arda.cards." });template.hasResourceProperties("AWS::Route53::HostedZone", { Name: "io.arda.cards." });template.hasResourceProperties("AWS::Route53::HostedZone", { Name: "auth.arda.cards." });template.hasResourceProperties("AWS::Route53::HostedZone", { Name: "assets.arda.cards." });template.hasResource("AWS::IAM::Role", {}); // AllowCreatingNSRecordsRole
// Exports["arda-app-zone", "arda-io-zone", "arda-auth-zone", "arda-assets-zone", "arda-allow-create-ns-record-role"] .forEach(name => template.hasOutput("*", { Export: { Name: name } }));Pass criterion: every existing zone, the role, and every existing export are present in the synthesised template.
V-IAC-001 — apps/ folder rename
Section titled “V-IAC-001 — apps/ folder rename”Type: filesystem grep test (Jest).
Assert:
expect(fs.existsSync("src/main/cdk/apps/Root/r53-zones.ts")).toBe(true);expect(fs.existsSync("src/main/cdk/apps/Root/live-url.ts")).toBe(true);expect(fs.existsSync("src/main/cdk/apps/rootConfiguration")).toBe(false);
// Plus: no source file under src/ references the old path.const repoSources = walkRepoSources("src/");const offendingFiles = repoSources.filter(f => fs.readFileSync(f, "utf8").includes("apps/rootConfiguration"));expect(offendingFiles).toEqual([]);Pass criterion: the new path exists, the old path is gone, no source file references the old path.
V-IAC-002 — Stack class and file rename
Section titled “V-IAC-002 — Stack class and file rename”Type: filesystem grep test (Jest) plus a TypeScript import-resolution check.
Assert:
expect(fs.existsSync("src/main/cdk/stacks/root/root-dns-stack.ts")).toBe(true);expect(fs.existsSync("src/main/cdk/stacks/root/root-configuration-stack.ts")).toBe(false);
// No source file references the old class or the old file.const repoSources = walkRepoSources("src/");const offendingClassRefs = repoSources.filter(f => fs.readFileSync(f, "utf8").match(/\bRootConfigurationStack\b/));const offendingPathRefs = repoSources.filter(f => fs.readFileSync(f, "utf8").includes("root-configuration-stack"));expect(offendingClassRefs).toEqual([]);expect(offendingPathRefs).toEqual([]);Plus: npx tsc --noEmit (run as part of npm run build) succeeds, which catches any missed import.
Pass criterion: the new file exists, the old file is gone, no source references the old class or path, tsc resolves cleanly.
V-IAC-003 — CFN stack name preserved with inline comment
Section titled “V-IAC-003 — CFN stack name preserved with inline comment”Type: source-state grep test (Jest).
Assert: read src/main/cdk/apps/Root/r53-zones.ts and verify:
const source = fs.readFileSync("src/main/cdk/apps/Root/r53-zones.ts", "utf8");
// 1. The constructor call passes "RootConfiguration" as the third positional arg.expect(source).toMatch(/new\s+RootDnsStack\s*\(\s*[^,]+,\s*[^,]+,\s*"RootConfiguration"/);
// 2. The inline preservation comment is present somewhere above the call.expect(source).toMatch(/CFN stack name MUST remain "RootConfiguration"/);Pass criterion: both regexes match. The comment text must contain the exact phrase CFN stack name MUST remain "RootConfiguration" so future agents grep-find it.
Optional reinforcement: a CDK Template assertion on the Stack’s stackName property:
expect(stack.stackName).toBe("RootConfiguration");(Not required if V-ROOT-001 already asserts the stack name implicitly via export naming, but recommended.)
V-IAC-004 — instances/Root/dns.ts exists and is imported
Section titled “V-IAC-004 — instances/Root/dns.ts exists and is imported”Type: filesystem + import-graph test (Jest).
Assert:
expect(fs.existsSync("src/main/cdk/instances/Root/dns.ts")).toBe(true);
// Confirm at least one `apps/Root/*` file imports from it.const appFiles = ["src/main/cdk/apps/Root/r53-zones.ts", "src/main/cdk/apps/Root/live-url.ts"];const importsRootDns = appFiles.some(f => fs.existsSync(f) && fs.readFileSync(f, "utf8").includes("arda/instances/Root/dns"));expect(importsRootDns).toBe(true);Plus: the file exports the expected named symbols (ARDAMAILS_ZONE_NAME or equivalent constant; the export-keys constant). The exact symbol set is fixed in exports.md.
Pass criterion: the file exists, at least one app entry imports from it, and the expected named exports are present.
V-OPS-001 — deploy-root.sh points at renamed app
Section titled “V-OPS-001 — deploy-root.sh points at renamed app”Type: filesystem grep test (Jest or shell).
Assert:
grep -F "src/main/cdk/apps/Root/r53-zones.ts" deploy-root.sh# (matches one line)! grep -F "src/main/cdk/apps/rootConfiguration" deploy-root.sh# (matches no lines)Pass criterion: the new path appears once, the old path appears zero times.
V-DOC-001 — Phase 2 planning artefacts published
Section titled “V-DOC-001 — Phase 2 planning artefacts published”Type: filesystem assertion in the documentation repo, plus link-checker (make test-links) inclusion.
Assert: the five files exist under src/content/docs/roadmap/in-progress/email-integration/2-root-updates/:
analysis.mdrequirements.mdspecification.mdverification.mdexports.md
Plus: each file has the standard Starlight frontmatter (title, tags, domain, maturity, author).
Pass criterion: all five files exist with valid frontmatter; make test-links passes.
V-DOC-002 — DQ-R1-006 and phases.md patch present
Section titled “V-DOC-002 — DQ-R1-006 and phases.md patch present”Type: filesystem grep test against the documentation repo.
Assert:
grep -F "DQ-R1-006" decision-log.md# (matches at least three lines: the table row, the section heading, the summary row)
grep -F "ardamails.com zone declaration" phases.md# (matches the new Phase 2 row)
! grep -F "after Phase 3 lands the zone" phases.md# (the dropped language is gone)Pass criterion: DQ-R1-006 is present in the decision log; the Phase 2 NS-delegation row is gone and replaced; the “after Phase 3 lands the zone” phrasing is removed.
4. Test-execution sequence
Section titled “4. Test-execution sequence”Phase 2’s tests are run together as part of the standard infrastructure pre-push gate. There is no operator-driven sequence:
| Order | Step | Command |
|---|---|---|
| 1 | Lint | npm run lint |
| 2 | Type-check + build | npm run build |
| 3 | Unit tests (CDK Template + source-state grep) | npm test |
| 4 | Synth (manual confirmation) | npx cdk synth --app "npx ts-node ... apps/Root/r53-zones.ts" |
| 5 | cdk diff against deployed Root stack (manual confirmation) | npx cdk diff --app ... (operator validates the diff is additive only) |
| 6 | Documentation gate | make pr-checks (in the documentation repo) |
Steps 1-4 are CI-gated. Steps 5-6 are PR-review gates — the human reviewer confirms the cdk diff output is additive only and the documentation pr-checks log is clean before approving the PR.
5. Out of scope of this verification
Section titled “5. Out of scope of this verification”- Live
dig NS arda.ardamails.com: this assertion belongs to Phase 3 because Phase 3 is what creates thearda.ardamails.comzone and writes the parent NS record. Phase 2 only ensures the parent zone (ardamails.com) exists; it emits no NS records. - Live cross-account assume-role test: the IAM role’s trust policy and permissions are unchanged in Phase 2; coverage of that role’s behaviour rests on the existing partition
IngressStacktests and on Phase 3 / Phase 4 deploys exercising it. - Operator walkthroughs: Phase 2 has no operator-driven steps. The operator’s only role is reviewing the PR and confirming the
cdk diffoutput before merging.
6. References
Section titled “6. References”analysis.md— gap analysis.requirements.md— requirement IDs.specification.md— task contract.exports.md— downstream contracts.../phases.md— phase plan.../decision-log.md— decisions includingDQ-R1-006.../1-external-resources/verification.md— Phase 1 verification, used as a structural reference for this document.
Copyright: © Arda Systems 2025-2026, All rights reserved