Skip to content

Phase 4 -- Runtime Platform Updates -- Specification

The contract for Phase 4 implementation. Each task lists its scope, file targets, AWS impact, and a STOP point where the implementer pauses for review before proceeding.

This specification is derived from requirements.md and analysis.md. The verification regime is in verification.md. The cross-phase exports Phase 4 produces are catalogued in exports.md. The execution plan is in the ../plan/ tree — ../plan/choreography.md for cross-run orchestration plus per-run plans under ../plan/runs/.

Phase 4 implementation spans TypeScript / CDK on the infrastructure repository (the bulk: the partition-email stack, IAM construct generalization, the tools/register-partition-mail-signature.ts entry script and tools/lib/ shared helpers), bash on the infrastructure repository (amm.sh extension), and Markdown / Starlight on the documentation repository (planning artifacts, the encryption-key rotation runbook, current-system pages). The full inventory of agent-execution skills is orchestration metadata in ../plan/choreography.md § 8.

Per the project-level CLAUDE.md, every change is classified as None / Synth-only / Resource-touching. Each task below carries its impact tag. Tasks at Resource-touching require a cdk diff summary surfaced in the PR description (one per partition, since each PR-per-partition deploys a different stack) and explicit user confirmation before deploy.

Phase 4 deploys to two AWS accounts: Alpha001 (Admin-Alpha1 profile) hosts prod + demo partitions; Alpha002 (Alpha002-Admin profile) hosts dev + stage partitions. Each partition’s mail sub-zone lives in its hosting Infrastructure’s account — not in platformRoot. NS-delegation writes back into platformRoot’s ardamails.com zone via WriteNSRecordsToUpstreamDns (DQ-R1-006).

Wherever an API equivalent exists, the operator path uses the API. Postmark Account API for Sender Signature registration and DKIM / Return-Path verification (via tools/register-partition-mail-signature.ts); AWS CLI / CDK for AWS resources; gh CLI for GitHub operations; 1Password SDK / op CLI for 1P resolution. The Postmark Console click-through is reserved for steps without an API equivalent — Phase 4 has none such (arda-prod is already approved; arda-nonprod approval reply is an email response, not a Console step).

Phase 4’s new partition-email stack has a pinned CloudFormation stack name: ${infrastructure}-${partition}-Email (e.g., Alpha002-dev-Email). The literal <Infrastructure>-<Partition>-Email shape is passed as the CDK id argument to the stack constructor. Once deployed, the name must not change — changing the id argument forces CloudFormation to delete and recreate the stack, destroying every resource it owns (the partition mail sub-zone, both SM secrets, both IAM roles, the NS-delegation Custom Resource). An inline source-code comment immediately above each stack instantiation in apps/Al1x/partition.ts documents the constraint, mirroring Phase 3’s CorporateMailDns / FreeKanbanToolMailDns and Phase 2’s RootConfiguration patterns. (Pre-design follow-up C1.)

The CDK class name matches the CFN stack-name shape: class PartitionEmailStack extends cdk.Stack, in src/main/cdk/stacks/purpose/partition-email.ts.

Every task that introduces or modifies executable code (constructs, stacks, app, instance configs, TypeScript helpers, the Phase A entry script, amm.sh callees, drift driver, lambdas) ships with parallel tests in the same PR, and the tests must pass before the STOP gate clears. The “tests” criterion includes:

  • Jest unit tests with adequate coverage for new public surfaces (CDK Template-matcher for constructs / stacks; injectable-dependency tests for code crossing external boundaries).
  • Integration / synth-only smoke tests where applicable (npx cdk synth against fixture context produces a valid template; CFN stack name matches the spec).
  • The repo’s lint and type-check gates (npm run lint, tsc --noEmit) green.
  • The repo’s CI matrix exercises the new code path.

Two byte-equality tests are load-bearing for Phase 4:

  • CDK Template.fromStack() snapshot equality for RootDnsStack post-AllowCreatingNSRecordsRole generalization (REQ-IAC-002). Fails closed if Phase 4’s construct change regresses Phase 2’s Root-account synth output.
  • CDK cdk synth no-op for the encryption-key SM secret on re-runs (REQ-PART-016 + REQ-IAC-008). Catches accidental generateSecretString config drift.

Every task that produces durable documentation (planning artifacts beyond this analysis-spec-verification triple, the encryption-key rotation runbook, current-system/ pages, byproducts, CHANGELOG entries) is reviewed by a specialized documentation reviewer before its STOP gate clears. The reviewer is a technical-writer (or quality-reviewer with a docs-specific brief) launched as a sub-agent. Findings are returned in a structured report; the implementer addresses each finding before the STOP gate clears.

This rule applies to: T-D4, T-D5, T-D6, T-D7 (system-doc pages and runbook); T-D2, T-D3, T-D8 (CHANGELOG entries — light review, but still passes through the reviewer for en_US-locale and link-integrity checks).

Per requirements.md § Out of scope and analysis.md §14:

  • Per-tenant Postmark Sender Signatures (deferred to Phase 5b; tracked as DQ-R1-023).
  • TokenCipher primitive (Phase 5a common-module).
  • Backend ShopAccess/Email module in operations (Phase 5b).
  • Tightening AllowCreatingNSRecordsRole.allowedParentHostedZoneIds on the Root-account instantiation (future security-hardening project).
  • kyle partition mail capability (deferred; DQ-R1-021).
  • Apex SPF/DMARC on the root ardamails.com zone (out of project).
  • AWS SM Rotation Lambda for the encryption-key secret (deferred; the design accommodates it).
  • CR Lambda migration of PostmarkSendingDomain thin-wrapper internals (deferred; DQ-R1-022).

The tasks divide into three groups by execution phase. Numbering is content-typed, not execution-ordered: T-D* for documentation, T-I* for infrastructure (CDK + TypeScript helpers + bash), T-O* for operator. Execution order is in § 3.

Per-partition note. The CDK code (partition-email stack, register-partition-mail-signature.ts, amm.sh extension, tools/lib/ helpers) lands once in PR #2 (alongside the dev deploy). Subsequent per-partition PRs (stage, demo, prod) only add the partition’s instance configuration plus invoke amm.sh for that partition. The code is partition-agnostic; the partition is the operator’s argument at invocation time.


Task T-I1: Generalize AllowCreatingNSRecordsRole construct

Section titled “Task T-I1: Generalize AllowCreatingNSRecordsRole construct”

Goal: parameterize the construct’s trust principal so the same construct can serve both the Phase-2 Root use case (Lambda + OrgID) and the Phase-4 per-partition use case (account principal + ArnLike on ${fqn}-*). Mechanical class-rename optional in the same PR. Preserves the canonical three-interface pattern (Configuration / Props / Built) and the validateProps discipline per ../../../../../technology/cdk-infrastructure.md § Constructs.

Scope:

  1. Edit src/main/cdk/constructs/oam/allow-creating-ns-records-role.ts:
    • Extend Configuration with a new discriminated-union field trustPrincipal: TrustPrincipalConfig where TrustPrincipalConfig is one of:
      • { kind: "lambdaOrgID"; orgId: string } — legacy Phase-2 shape. Default for backwards compatibility (the Root call site continues to pass this shape).
      • { kind: "stsAssumeRole"; account: string; arnLikePattern: string } — Phase-4 shape: iam.AccountPrincipal(account).withConditions({ ArnLike: { 'aws:PrincipalArn': arnLikePattern } }).
    • Extend Props extends Configuration and Built unchanged.
    • validateProps(props: Props): Error[] validates the discriminated union (kind === "lambdaOrgID" requires non-empty orgId; kind === "stsAssumeRole" requires account matches the AWS-account-ID pattern and arnLikePattern matches the IAM-role-ARN pattern). Errors throw via misc.MultiError.
    • The construct’s existing allowedParentHostedZoneIds scope-tightening prop is preserved on Configuration.
    • Permissions remain unchanged: route53:ChangeResourceRecordSets, route53:ListResourceRecordSets, route53:ListHostedZonesByName. route53:GetChange is NOT added (DQ-R1-020 — see analysis.md §7.3).
  2. Optional rename in the same PR (mechanical; CDK construct ID at the call site preserved): AllowCreatingNSRecordsRoleAllowCreatingDnsRecordsRole; rename the file accordingly; update import sites. Both Phase 2’s existing usage and Phase 4’s new usage refer to the new class name post-rename. The Root call site’s CDK construct ID (second argument to new …(this, "AllowCreatingNSRecordsRole", …)) stays "AllowCreatingNSRecordsRole" to preserve the logical ID and pass T-I2’s byte-equality check.
  3. Update the construct’s tests (allow-creating-ns-records-role.test.tsallow-creating-dns-records-role.test.ts if renamed) to cover both trustPrincipal.kind modes plus the validateProps error paths.

File targets:

  • Edit (and optionally rename): src/main/cdk/constructs/oam/allow-creating-ns-records-role.ts + its test.
  • Edit import sites in src/main/cdk/stacks/root/root-dns-stack.ts (if class is renamed).

AWS impact: Synth-only. (Resource-touching is only triggered when a downstream task instantiates the construct in a new context — that’s T-I4.)

STOP — review after T-I1: npm run build and npm test pass. The Root-account synthesis output is unchanged at this point (T-I2 verifies). The construct’s tests cover both trust-principal modes.


Task T-I2: Byte-equality unit test for the Root-account instantiation

Section titled “Task T-I2: Byte-equality unit test for the Root-account instantiation”

Goal: lock in the byte-identical Root-account CloudFormation post-T-I1, preventing any future Phase-4 (or post-Phase-4) construct change from silently altering the Phase 2 output.

Scope:

  1. Author or extend src/main/cdk/stacks/root/root-dns-stack.test.ts (or allow-creating-ns-records-role.test.ts) with a CDK Template.fromStack() snapshot-equality test:
    • Synthesize the RootDnsStack (or just the AllowCreatingNSRecordsRole construct as instantiated in Root) against a fixture context.
    • Compare the resulting template against a checked-in baseline (the pre-Phase-4 Root output) using expect(template.toJSON()).toEqual(baseline).
    • The baseline is checked into the repo as a JSON fixture next to the test.
  2. The test must fail closed if any future PR alters the Root-account synth output — including a rename of the construct, a roleName change, a Description change, etc.
  3. Document the byte-equality invariant in an inline source comment above the test, citing DQ-R1-020 (REQ-IAC-002).

File targets:

  • New / edit: src/main/cdk/stacks/root/root-dns-stack.test.ts (or sibling test in the construct’s folder).
  • New: baseline JSON fixture.

AWS impact: None (test only).

STOP — review after T-I2: the test passes against the Phase 2 Root output (sanity check that the baseline correctly captures the existing state); the test fails closed on a deliberate breaking change (sanity check that the gate works).


Task T-I3: Add postmarkCredentialOpReference accessor

Section titled “Task T-I3: Add postmarkCredentialOpReference accessor”

Goal: expose a partition-aware 1Password reference resolver that amm.sh can call to obtain the per-partition Postmark account-token reference. CDK has no 1P dependency; this is for amm.sh’s pre-flight read.

Scope:

  1. Edit src/main/cdk/platform/postmark-service.ts:
    • Add export function postmarkCredentialOpReference(partition: PartitionId): string returning op://Arda-{Env}OAM/Postmark/credential for the partition’s bound Env vault (Dev for dev, Stage for stage, Demo for demo, Prod for prod).
    • The function is pure and side-effect-free. No 1P access at synth time.
  2. Author / extend postmark-service.test.ts to assert the four expected references for the four active partitions.
  3. Pure addition — do not modify any existing exports.

File targets:

  • Edit: src/main/cdk/platform/postmark-service.ts + its test.

AWS impact: Synth-only (function isn’t called by CDK constructs; this is a pure helper for amm.sh).

STOP — review after T-I3: tests pass; tsc clean.


Goal: the central Phase 4 deliverable — one CDK stack class that composes every per-partition mail resource (zone, NS-delegation, SPF/DMARC, DKIM/Return-Path records, both SM secrets, both IAM roles, all six -API- exports). Parametrized by partition so the same class instantiates four times. Follows the canonical three-interface stack pattern (Configuration / Props extends Configuration / Built) plus validateProps + ExportKeys + the newer ExportDefinition factory form, per ../../../../../technology/cdk-infrastructure.md § Stacks + § Export-key pattern. Closest codebase precedent: infrastructure/src/main/cdk/stacks/purpose/image-storage.ts.

Scope:

  1. New file src/main/cdk/stacks/purpose/partition-email.ts. Public interfaces:

    // Imports follow the canonical Arda stack pattern; see ImageStorageStack
    // for the reference shape. Typical set: cdk-lib core, aws_secretsmanager,
    // aws_iam, aws_route53, Construct, purpose / awsUtil / stackTypes / misc
    // utilities from `arda/utils/*`, and the Phase 3 constructs
    // (DnsZone, WriteNSRecordsToUpstreamDns, DnsEmailRecords,
    // AllowCreatingNSRecordsRole, postmark-service).
    // Configuration: design-time parameters from the instance config
    export interface Configuration {
    readonly locator: purpose.Locator;
    readonly postmarkAccount: postmarkService.AccountReference;
    readonly dmarcReportingMailbox: string; // "dmarc-reports@arda.cards"
    }
    // Props: Configuration + runtime injections from apps/Al1x/partition.ts
    export interface Props extends Configuration {
    readonly postmarkAccountTokenParam: cdk.CfnParameter;
    readonly podRoleArnPattern: string; // "arn:aws:iam::<account>:role/<fqn>-*"
    }
    // Built: cross-CDK exposures (sparsely populated — Phase 5b consumes via
    // CFN exports, not CDK Built; Built exists for codebase consistency)
    export interface Built {
    readonly partitionMailZone: r53.IHostedZone;
    readonly emailEncryptionKeySecret: secretsmanager.ISecret;
    readonly emailPostmarkAccountTokenSecret: secretsmanager.ISecret;
    readonly emailDnsProvisioningRole: iam.IRole;
    readonly emailEncryptionKeyFallbackRole: iam.IRole;
    }
    // ExportKeys: CFN export key identifiers (every key suffixed "API";
    // publish() regex enforces alignment with the "-API-" export-name marker)
    export type ExportKeys =
    | "partitionMailZoneIdAPI"
    | "partitionMailZoneNameAPI"
    | "emailPostmarkAccountTokenArnAPI"
    | "emailEncryptionKeyArnAPI"
    | "emailDnsProvisioningRoleArnAPI"
    | "emailEncryptionKeyFallbackRoleArnAPI";
    export class ExportDefinition extends stackTypes.ExportDefinitions<ExportKeys> {}
    export function exportDefinition(publishingPrefix: string): ExportDefinition {
    return new ExportDefinition(publishingPrefix, {
    partitionMailZoneIdAPI: {
    exportName: `${publishingPrefix}-API-PartitionMailZoneId`,
    description: "Route53 hosted zone ID for the partition's mail sub-zone.",
    },
    partitionMailZoneNameAPI: { /* ... */ },
    emailPostmarkAccountTokenArnAPI: { /* ... */ },
    emailEncryptionKeyArnAPI: { /* ... */ },
    emailDnsProvisioningRoleArnAPI: { /* ... */ },
    emailEncryptionKeyFallbackRoleArnAPI: { /* ... */ },
    });
    }
    export class PartitionEmailStack extends cdk.Stack {
    public readonly built: Built;
    public readonly outputs: ExportValues;
    public readonly exportDefinitions: ExportDefinition;
    private static validateProps(props: Props & cdk.StackProps): Error[] {
    const errors: Error[] = [
    ...purpose.validatePurposeLocator(props.locator),
    ...awsUtil.validateStackProps(props),
    ];
    if (!awsUtil.IAM_ROLE_ARN_PATTERN_REGEX.test(props.podRoleArnPattern)) {
    errors.push(new Error(`Invalid podRoleArnPattern[${props.podRoleArnPattern}]...`));
    }
    // ... domain-specific validations (DMARC mailbox shape, Postmark account ref present, etc.)
    return errors;
    }
    constructor(scope: Construct, id: string, props: Props & cdk.StackProps) {
    const errors = PartitionEmailStack.validateProps(props);
    if (errors.length > 0) {
    throw new misc.MultiError(
    "Errors with the provided configuration for PartitionEmailStack",
    errors,
    );
    }
    super(scope, id, props);
    // ... compose resources; populate this.built;
    // assign this.exportDefinitions = exportDefinition(publishingPrefix).
    // Note: publish() is NOT called here. The App calls
    // stack.publish() after instantiation, per
    // cdk-infrastructure.md § Stack ordering.
    }
    }

    The id argument from apps/Al1x/partition.ts is the literal ${infrastructure}-${partition}-Email (CFN stack-name immutability; § 1.4). Note the constructor signature is the newer 3-arg form (scope, id, props) used by ImageStorageStack — not the 4-arg (scope, prefix, id, props) form RootDnsStack uses. The publishingPrefix is computed inside the stack from props.locator (typically via purpose.fqn(props.locator) or an equivalent helper).

  2. Compose, in order (inside the constructor, after super(scope, id, props)):

    • NoEcho parameter: the caller in apps/Al1x/partition.ts declares a CfnParameter named PostmarkAccountToken with noEcho: true and passes it into PartitionEmailStackProps.postmarkAccountTokenParam.
    • DnsZone for {partition}.ardamails.com (REQ-PART-001), with RemovalPolicy.RETAIN.
    • WriteNSRecordsToUpstreamDns with subdomain: partition and nameServers from the zone’s hostedZoneNameServers token (REQ-PART-003).
    • SPF TXT record at {partition}.ardamails.com apex with value "v=spf1 include:spf.mtasv.net ~all" (REQ-PART-004).
    • DMARC TXT record at _dmarc.{partition}.ardamails.com with the DQ-R1-015 / C2 policy (REQ-PART-005).
    • DnsEmailRecords (Phase 3 construct) for the partition’s DKIM TXT + Return-Path CNAME records — REQ-PART-010. Per cdk-infrastructure.md § 9 (tryGetContext value vs. source separation), the stack constructor reads the Postmark-issued public values from cdk.context.json via this.node.tryGetContext('postmark.{partition}.dkimSelector') (and dkimKey, returnPathTarget), validates them via a stack-layer helper (present + non-empty + typed string coercion), then passes them as typed props to DnsEmailRecords. DnsEmailRecords itself is unchanged — Phase 3’s design has it taking props-only inputs; it does not know about the CDK App. Keys are populated by T-I8’s Phase A.
    • Postmark account-token SM secret: new aws_secretsmanager.Secret(this, "EmailPostmarkAccountToken", { secretName: ${fqn}-I-EmailPostmarkAccountToken, secretStringValue: cdk.SecretValue.cfnParameter(props.postmarkAccountTokenParam), removalPolicy: RETAIN }) (REQ-PART-011).
    • Encryption-key SM secret: new aws_secretsmanager.Secret(this, "EmailEncryptionKey", { secretName: ${fqn}-I-EmailEncryptionKey, generateSecretString: { passwordLength: 64, excludeCharacters: '"@/\\', excludePunctuation: false }, removalPolicy: RETAIN }) (REQ-PART-014). Inline comment cites the lifecycle invariants in email-server-key-encryption.md.
    • DNS-records role via the generalized AllowCreatingNSRecordsRole construct (T-I1) with the Phase 4 trust principal shape and allowedParentHostedZoneIds: [zone.hostedZoneId] (REQ-PART-017).
    • EmailEncryptionKeyFallbackRole declared fresh in this stack: trust principal = account principal + ArnLike on ${fqn}-*; permission = secretsmanager:GetSecretValue on ${encryptionKeySecret.secretArn}* (REQ-PART-019).
    • Six export definitions assigned to this.exportDefinitions = exportDefinition(publishingPrefix): ${publishingPrefix}-API-PartitionMailZoneId, -API-PartitionMailZoneName, -API-EmailPostmarkAccountTokenArn, -API-EmailEncryptionKeyArn, -API-EmailDnsProvisioningRoleArn, -API-EmailEncryptionKeyFallbackRoleArn (REQ-PART-002, 012, 015, 018, 020). All use the -API- marker per § 1.4 / cdk-infrastructure.md § -API- vs -I- naming (operations Helm chart is the non-CDK consumer). publish() is NOT called from the constructor — the App (T-I6) calls stack.publish() after instantiation to materialize the CFN Outputs (cdk-infrastructure.md § Stack ordering).
  3. Author partition-email.test.ts (CDK Template-matcher tests):

    • Stack name matches ${infrastructure}-${partition}-Email.
    • One AWS::Route53::HostedZone resource with Name: {partition}.ardamails.com. and DeletionPolicy: Retain.
    • One NS-write Custom Resource.
    • SPF TXT record (Name + Value match the spec).
    • DMARC TXT record (Name + Value match).
    • DKIM TXT + Return-Path CNAME records (Name shape matches; value comes from context).
    • Two SM secrets (resource shape, names, DeletionPolicy: Retain); Postmark token uses Fn::Ref to the CfnParameter (with NoEcho: true on the parameter declaration in the parent stack).
    • Two IAM roles with the expected trust-policy condition (ArnLike on ${fqn}-*) and per-role scoped permissions.
    • All six exports present with expected names + values.
    • Idempotent re-synth: re-running cdk synth against the same fixture context produces an identical template (no-op invariant; REQ-IAC-008).
  4. Also assert that route53:GetChange is NOT in the DNS-records role’s policy statements (negative test for REQ-PART-017).

File targets:

  • New: src/main/cdk/stacks/purpose/partition-email.ts, partition-email.test.ts.

Lambda fan-out (per cdk-infrastructure.md § 4 — Custom resources → Lambda topology): each partition-email stack instantiates one WriteNSRecordsToUpstreamDns Provider, which produces 2 Lambdas (the user-supplied handler + the framework-injected framework-onEvent). Because WriteNSRecordsToUpstreamDns uses the deprecated logRetention: property, the stack also gets the 1 stack-singleton LogRetention Lambda. Total: 3 Lambdas per partition stack × 4 partitions = 12 Lambdas Phase 4 adds. Reviewer of each per-partition PR confirms no additional Custom Resources were added to the stack beyond WriteNSRecordsToUpstreamDns; future cleanup of WriteNSRecordsToUpstreamDns’s deprecated logRetention: would reduce the per-partition count from 3 to 2 (and the total from 12 to 8).

AWS impact: Synth-only (the stack file itself). Resource-touching is triggered when an apps/Al1x/partition.ts invocation deploys it for a specific partition — that’s T-O3+ (per-partition deploy operator tasks).

STOP — review after T-I4: tests pass; cdk synth for a fixture partition produces a valid template; the CFN stack name matches the spec exactly.


Task T-I5: Per-partition instance configurations

Section titled “Task T-I5: Per-partition instance configurations”

Goal: declarative configuration for each active partition’s PartitionEmailStack. One file per partition.

Scope:

  1. New files (or extensions of existing instance config files):
    • src/main/cdk/instances/Alpha001/prod.tsprod partition’s partition-email config: Postmark account ref = POSTMARK_PROD_ACCOUNT; sub-zone name = prod.ardamails.com; DMARC mailbox = dmarc-reports@arda.cards.
    • src/main/cdk/instances/Alpha001/demo.ts — same shape; sub-zone demo.ardamails.com.
    • src/main/cdk/instances/Alpha002/dev.ts — Postmark account ref = POSTMARK_NONPROD_ACCOUNT; sub-zone dev.ardamails.com.
    • src/main/cdk/instances/Alpha002/stage.ts — same shape; sub-zone stage.ardamails.com.
    • kyle.ardamails.com excluded per DQ-R1-021 — the file for kyle either doesn’t exist or has the partition-email config commented out with a reference to DQ-R1-021.
  2. Per pre-design follow-up C2: every partition reuses dmarc-reports@arda.cards (no per-partition mailboxes).

File targets:

  • New / edit: four files in src/main/cdk/instances/Alpha001/ and Alpha002/.

AWS impact: Synth-only.

STOP — review after T-I5: cdk synth for all four partition apps produces valid templates; the partition-email stacks have the correct CFN names.


Task T-I6: apps/Al1x/partition.ts extension

Section titled “Task T-I6: apps/Al1x/partition.ts extension”

Goal: instantiate PartitionEmailStack per active partition in the partition app entry point.

Scope:

  1. Edit src/main/cdk/apps/Al1x/partition.ts:
    • For each active partition (prod, demo, dev, stage), declare a CfnParameter named PostmarkAccountToken with noEcho: true. The parameter is passed into PartitionEmailStack via Props.postmarkAccountTokenParam.
    • Instantiate the stack: const partitionEmailStack = new PartitionEmailStack(app, ${infrastructure}-${partition}-Email, {...}) per partition, wiring the partition’s instance config from T-I5.
    • Call partitionEmailStack.publish() after instantiation — per cdk-infrastructure.md § Stack ordering, publish() is never called inside the stack constructor; it materializes the CFN Outputs from this.exportDefinitions only when the App calls it. Same discipline as apps/Corporate/index.ts (Phase 3 precedent: mailDns.publish()).
    • Call partitionEmailStack.addDependency(...) against any partition stack whose Built properties are not consumed via a typed import — e.g., if the partition-email stack’s deploy ordering should follow the partition’s authn / ingress / secrets stacks, use addDependency() to make the order explicit at the CloudFormation level. The IAM role ArnLike pattern targets the partition pod role by name (not by Built reference), so the dependency is not captured automatically; if deploy ordering matters, addDependency() is the canonical mechanism. (If the partition-email stack truly has no upstream dependency among CDK siblings, omit the call.)
    • Inline comment above each new PartitionEmailStack(...) call documenting CFN stack-name immutability (§ 1.4).
    • kyle excluded — either no instantiation or an explicit guard with a reference to DQ-R1-021.

File targets:

  • Edit: src/main/cdk/apps/Al1x/partition.ts.

AWS impact: Synth-only.

STOP — review after T-I6: cdk synth for the partition app produces valid templates for all four active partitions; tests pass.


Goal: extend the arda reservation at the ardamails.com level (Phase 3) with the partition names so future tenant slugs cannot collide.

PR placement: lands in PR #1 (G-A workspace refactors), alongside T-I1 / T-I2 / T-I3 / T-I9. The reserved-words registry is a workspace-level concern, not partition-scoped — co-locating with the other workspace-only refactors keeps each per-partition PR’s diff focused on per-partition resources. (Previously this task was bundled into PR #2 alongside the dev rollout; moving to PR #1 tightens scope per content-type.)

Scope:

  1. Identify the registry mechanism Phase 3 used for arda at the ardamails.com level (likely in platform/ari-configuration.ts or a sibling). Extend it with prod, demo, dev, stage, kyle (per pre-design follow-up B4 — extend the same mechanism; no new registry).
  2. Update the existing tests that exercise the registry to include the new reservations.

File targets:

  • Edit: src/main/cdk/platform/ari-configuration.ts (or equivalent) + its test.

AWS impact: Synth-only.

STOP — review after T-I7: tests pass; the reserved-words list contains all expected entries.


Task T-I8: Author tools/register-partition-mail-signature.ts

Section titled “Task T-I8: Author tools/register-partition-mail-signature.ts”

Goal: the Phase A imperative entry script that registers the partition’s Sender Signature, captures the Postmark-issued values, and writes them into cdk.context.json for CDK synth.

Scope:

  1. New file infrastructure/tools/register-partition-mail-signature.ts:
    • CLI shape: npx ts-node tools/register-partition-mail-signature.ts <infrastructure> <partition>. Both arguments required positional. The signature mirrors amm.sh’s <infrastructure> <partition> shape (amm.sh line 140 — Usage: ./amm.sh [--profile ...] [--region ...] <infrastructure> (<partition>)).

    • Usage output when invoked without arguments (or with --help / -h): emit a usage block to stderr and exit non-zero. Shape:

      Usage: npx ts-node tools/register-partition-mail-signature.ts <infrastructure> <partition>
      <infrastructure> Infrastructure name. One of: Alpha001 | Alpha002
      (SandboxKyle002 is retired per PDEV-438; not a valid input)
      <partition> Partition name within the infrastructure.
      Alpha001: demo | prod
      Alpha002: dev | stage
      Reads the partition's Postmark account-level token from the partition's
      Arda-{Env}OAM 1P vault, registers (idempotent list-then-create) the
      Postmark Sender Signature for {partition}.ardamails.com on the bound
      Postmark account (PostmarkProd for prod/demo; PostmarkNonProd for
      dev/stage), and writes the issued DKIM selector / public key /
      Return-Path target into cdk.context.json under
      postmark.{partition}.{dkimSelector,dkimKey,returnPathTarget}.
      Invoked by amm.sh; not a standalone operator-facing CLI (DQ-R1-022).
    • Argparse: validate the two positionals.

      • <infrastructure> must be Alpha001 or Alpha002 (reject SandboxKyle002 with a PDEV-438 reference; reject anything else).
      • <partition> must be a member of the infrastructure’s active partition set (Alpha001{demo, prod}; Alpha002{dev, stage}). A partition / infrastructure mismatch (e.g., Alpha001 dev) is a hard error — exit non-zero with the usage block and a partition '<x>' does not belong to infrastructure '<y>' line. This catches an operator typo before any Postmark API call.
    • Flow:

      1. Resolve the partition’s Postmark account token via the 1Password SDK helper (in tools/lib/) using postmarkCredentialOpReference(partition) (T-I3).
      2. Use the Postmark Account API client from tools/lib/ (T-I9) to register the Sender Signature for {partition}.ardamails.com against the partition’s bound account (PostmarkProd or PostmarkNonProd). Idempotent: list-then-create.
      3. If the Sender Signature exists with the same DKIM selector / Return-Path values, skip creation; otherwise create and capture the issued values.
      4. Call verifyDkim and verifyReturnPath if the DNS records exist (best-effort — DNS may not yet be in place on first run).
      5. Write the captured values into cdk.context.json keys: postmark.{partition}.serverId (not relevant for Sender Signature only, may be omitted), postmark.{partition}.dkimSelector, postmark.{partition}.dkimKey, postmark.{partition}.returnPathTarget. Use the redaction helper from tools/lib/ to ensure no token bleeds into the JSON.
    • Exit codes: 0 on success; non-zero on Postmark API error, 1P resolution failure, or context-write failure. Redacted summary on stderr.

  2. The script is not a standalone operator-facing CLI — it’s invoked only by amm.sh. Per DQ-R1-022’s “no partition-mail-cli” framing.
  3. Author register-partition-mail-signature.test.ts with dependency-injectable mocks for the Postmark client, 1P SDK, and filesystem; cover the happy path, idempotent re-run, Postmark API failure, 1P resolution failure, partial-success states.

File targets:

  • New: infrastructure/tools/register-partition-mail-signature.ts, register-partition-mail-signature.test.ts.

AWS impact: None (the script doesn’t deploy anything; it writes cdk.context.json).

STOP — review after T-I8: tests pass; running the script with mocked dependencies produces the expected cdk.context.json shape; the script is callable from a bash subprocess (verified by bash -c 'npx ts-node tools/register-partition-mail-signature.ts Alpha002 dev' against fixtures).


Task T-I9: Extract TypeScript helpers to tools/lib/

Section titled “Task T-I9: Extract TypeScript helpers to tools/lib/”

Goal: minimal cut of corporate-cli’s utilities into a shared location consumed by both corporate-cli and register-partition-mail-signature.ts. Per pre-design follow-up B3 and DQ-R1-022 implementation route.

Scope:

  1. Identify the helpers in tools/corporate-cli.ts (and / or its sibling source files) that register-partition-mail-signature.ts actually needs. Minimal cut — do not extract anything register-partition-mail-signature.ts doesn’t use. Likely candidates:
    • Postmark Account API client with retry / backoff (idempotent list-then-create for Sender Signatures).
    • 1Password SDK resolution wrapper (a single resolveOpReference(ref: string): Promise<string> function).
    • Output redaction helper for logs and cdk.context.json writes.
    • Optional: conflict-check helper (per DQ-R1-016 Phase 3 pattern) if Phase 4 needs a pre-flight check for an existing Signature collision.
  2. Move the helpers into infrastructure/tools/lib/ (final filename layout pinned at implementation: tools/lib/postmark-client.ts, tools/lib/op-resolver.ts, tools/lib/redact.ts, or a single tools/lib/index.ts — implementer’s call). Both corporate-cli.ts and register-partition-mail-signature.ts import from the new location.
  3. Update corporate-cli.ts to import from tools/lib/ instead of having the helpers inline.
  4. Author / extend tools/lib/*.test.ts with the helpers’ own unit tests; corporate-cli’s integration tests should continue to pass after the refactor.

File targets:

  • New: infrastructure/tools/lib/*.ts + tests.
  • Edit: infrastructure/tools/corporate-cli.ts (import paths).

AWS impact: None.

STOP — review after T-I9: tests pass for both the new tools/lib/ files and the refactored corporate-cli.ts; the corporate-drift.yml workflow continues to run unchanged (the shared utilities for drift are extracted separately in T-I12 — this task only covers the corporate-cli extraction).


Task T-I10: Extend amm.sh with the partition-mail step

Section titled “Task T-I10: Extend amm.sh with the partition-mail step”

Goal: wire amm.sh to invoke the Phase A script and then cdk deploy for a given partition. Includes GHA ::add-mask:: hygiene.

Scope:

  1. Edit infrastructure/amm.sh (or its callee shell scripts):
    • Add a partition-mail step that, for a given partition argument, executes three direct calls in order:
      1. op read 'op://Arda-{Env}OAM/Postmark/credential' to obtain the Postmark account-level token (env-vars the value as POSTMARK_ACCOUNT_TOKEN).
      2. echo "::add-mask::$POSTMARK_ACCOUNT_TOKEN" (and the same for any subsequent values that originate from op) — required in GHA runs to prevent log exposure; harmless locally.
      3. npx ts-node tools/register-partition-mail-signature.ts <infrastructure> <partition> — Phase A. If it exits non-zero, abort the step.
      4. cdk deploy ${infrastructure}-${partition}-Email --parameters PostmarkAccountToken=$POSTMARK_ACCOUNT_TOKEN — Phase B.
    • The partition is parameterized: amm.sh follows its existing convention for partition selection (the implementer should match the existing partitionSecrets step’s interface — likely a --partition <name> flag or positional arg).
  2. The step is idempotent: re-running with the same partition argument and unchanged 1Password value is a no-op on AWS resources (Phase A’s list-then-create is idempotent; Phase B’s CDK deploy is no-op if synth matches deployed).
  3. No cd <path> in the shell flow. Per cdk-infrastructure.md § 9 (cd in shell helpers) and workspace memory feedback_git_dash_c: use absolute paths for cdk deploy --app <path>, git -C <path>, make -C <path>, and npx --prefix <path> (or ts-node invoked with absolute script paths). Keeps the shell working directory stable, avoids agent permission prompts, and matches the existing partitionSecrets step’s convention.
  4. Update / extend amm.sh’s integration tests (if any) and the operator-facing help / usage output.

File targets:

  • Edit: infrastructure/amm.sh (and any callee scripts it shells out to).

AWS impact: None at this task level. Triggering ./amm.sh <infrastructure> <partition> (T-O3..T-O7) is Resource-touching per partition.

STOP — review after T-I10: a dry-run of amm.sh with the new step against a fixture partition emits the expected CLI calls in order; GHA masking is applied to all op-sourced values.


Task T-I11: runtime-platform-drift.yml workflow

Section titled “Task T-I11: runtime-platform-drift.yml workflow”

Goal: parallel drift workflow per DQ-R1-018. Daily cron; failure-issue labels per pre-design follow-up C3.

Scope:

  1. New file infrastructure/.github/workflows/runtime-platform-drift.yml:
    • Cron schedule: daily.
    • Manual workflow_dispatch trigger.
    • Steps: checkout, install, run the driver script (T-I12), parse the driver’s exit status; on failure, open a GitHub issue with labels drift + runtime-platform.
    • Reuses the same GHA secret (OP_SERVICE_ACCOUNT_TOKEN) as corporate-drift — drift probes use Postmark API + AWS SDK; no per-partition 1P vault read needed per pre-design follow-up B5.
  2. corporate-drift.yml continues to function unchanged.

File targets:

  • New: infrastructure/.github/workflows/runtime-platform-drift.yml.

AWS impact: None.

STOP — review after T-I11: YAML lints; the workflow’s workflow_dispatch trigger runs end-to-end against fixture state without errors.


Goal: per-partition Postmark + DNS + AWS-SM cross-seam asserter, enumerating from instances/Alpha*/.

Scope:

  1. New file infrastructure/tools/runtime-platform-drift.ts (final filename pinned at implementation):
    • Enumerates instances/Alpha001/{prod,demo}.ts + instances/Alpha002/{dev,stage}.ts.
    • For each partition, asserts:
      • Postmark Account API: Sender Signature exists with expected Name, DKIMPendingHost, DKIMHost, ReturnPathDomain values matching sendingDomainPlacement() output.
      • DNS: dig (or AWS SDK Route53) returns expected DKIM TXT, Return-Path CNAME, SPF TXT, DMARC TXT records.
      • AWS Secrets Manager: both partition SM secrets exist with RemovalPolicy.RETAIN (verifiable via describe-secret).
      • IAM: both partition IAM roles exist with the expected trust-policy condition and permissions.
    • Report shape matches Phase 1 / Phase 3 drift drivers.
    • Exit code: 0 on no drift; non-zero on any drift detected.
  2. The driver uses shared helpers from tools/lib/drift/ (T-I13) — Postmark probe, DNS lookup, report rendering.

File targets:

  • New: infrastructure/tools/runtime-platform-drift.ts (+ tests with mocked external boundaries).

AWS impact: None at this task level. Running the script against live state is read-only.

STOP — review after T-I12: tests pass; running the script against a fixture asset list produces the expected report shape; the script correctly fails closed on a synthetic drift fixture.


Task T-I13: Extract shared drift utilities

Section titled “Task T-I13: Extract shared drift utilities”

Goal: extract from corporate-drift.ts the utilities that both corporate-drift and runtime-platform-drift need; refactor corporate-drift to source from the extracted location. Per DQ-R1-018.

Scope:

  1. Identify the shared utilities in tools/corporate-drift.ts: Postmark API probe helpers; DNS lookup helpers; report-shape rendering; GitHub-issue creation; the cross-seam assertion machinery (sendingDomainPlacement-vs-Postmark-vs-DNS).
  2. Move them into infrastructure/tools/lib/drift/ (or equivalent shared location). Both corporate-drift.ts and runtime-platform-drift.ts import from the new location.
  3. corporate-drift.ts continues to function unchanged after the refactor — regression-tested by running corporate-drift.yml against the live arda.ardamails.com Sender Signature (no drift expected) and confirming the existing report shape is unchanged.
  4. The corporate-drift workflow YAML is not renamed (DQ-R1-018).

File targets:

  • New: infrastructure/tools/lib/drift/*.ts (+ tests).
  • Edit: infrastructure/tools/corporate-drift.ts (import paths).

AWS impact: None.

STOP — review after T-I13: tests pass for the extracted helpers and the refactored corporate-drift.ts; a full corporate-drift.yml workflow run produces an unchanged report shape against the live Corporate Sender Signature.


Goal: operator confirms all preconditions before any amm.sh partition-mail invocation.

Scope (the operator runs these checks; a script can guide but does not gate):

  1. Per partition being deployed:
    • op read "$(postmarkCredentialOpReference <partition>)" returns a non-empty value (REQ-OPS-001).
    • aws sts get-caller-identity --profile <profile> returns the expected partition Infrastructure account ID (REQ-OPS-002).
  2. Once for the rollout (before the first partition deploy):
    • dmarc-reports@arda.cards mailbox is provisioned and receives mail (REQ-OPS-003).
    • The Root no-drift verification (T-O2) has passed.

File targets: none (operator-driven; the operator companion in verification.md carries the checklist).

AWS impact: None (read-only checks).

STOP — review after T-O1: all checks green per partition; the operator records the partition’s pre-flight state in the verification sign-off table.


Goal: confirm the generalized AllowCreatingNSRecordsRole construct (T-I1) hasn’t drifted the Root account’s deployed CloudFormation.

Scope:

  1. Operator step (one-time, after T-I1 + T-I2 merge to main, before the first partition amm.sh invocation):
    • Synthesize RootDnsStack from the current main branch.
    • Compare against the Root account’s currently-deployed CloudFormation template via aws cloudformation get-template --stack-name RootConfiguration.
    • Expected diff: empty.
  2. If a diff is detected, abort the rollout and investigate. The byte-equality unit test (T-I2) should have caught it pre-merge; a post-merge drift would indicate either a bug in the test, a CFN-side drift unrelated to T-I1, or a manual mutation of the Root stack outside CDK.

File targets: none (operator-driven; the verification entry V-PART-002 in verification.md carries the procedure).

AWS impact: None (read-only against Root).

STOP — review after T-O2: empty diff against Root; operator records the result in the sign-off table.


Goal: the first non-prod partition rollout. Includes G-POSTMARK-5 — the arda-nonprod account approval unlock step.

Scope (operator-driven via amm.sh):

  1. Run amm.sh for the dev partition (./amm.sh Alpha002 dev or equivalent per the script’s interface). This invokes the partition-mail step from T-I10:
    • op read the Postmark NonProd account token from Arda-DevOAM.
    • GHA mask the token.
    • Run register-partition-mail-signature.ts Alpha002 dev to register the dev.ardamails.com Sender Signature on PostmarkNonProd; capture DKIM selector / key / Return-Path target; write to cdk.context.json.
    • Commit the updated cdk.context.json (the file is committed per DQ-R1-014; this commit lands in the PR or as a follow-up).
    • Run cdk deploy Alpha002-dev-Email --parameters PostmarkAccountToken=$POSTMARK_ACCOUNT_TOKEN. Surface the cdk diff summary in the PR description; user confirmation before proceeding.
  2. Wait for CFN deploy completion.
  3. Confirm via dig:
    • dig NS dev.ardamails.com @8.8.8.8 returns the partition zone’s nameservers via root delegation.
    • dig TXT dev.ardamails.com returns the SPF record.
    • dig TXT _dmarc.dev.ardamails.com returns the DMARC record.
    • dig TXT <selector>._domainkey.dev.ardamails.com returns the DKIM record.
    • dig CNAME pm-bounces.dev.ardamails.com returns pm.mtasv.net.
  4. Confirm via Postmark Console (or verifyDkim / verifyReturnPath API) that the Sender Signature is verified for DKIM and Return-Path.

File targets: none (operator-driven). The cdk.context.json write from Phase A is committed in the per-partition PR.

AWS impact: Resource-touching (Alpha002 account; dev partition).

STOP — review after T-O3:

  • All dig checks pass.
  • Postmark Console shows the dev.ardamails.com Sender Signature with DKIM verified and Return-Path verified.
  • Operator records the deploy outcome in the sign-off table.

Task T-O4: arda-nonprod account approval reply

Section titled “Task T-O4: arda-nonprod account approval reply”

Goal: reply to Postmark Compliance ticket #11236089 with the verified-domain evidence from T-O3, requesting arda-nonprod account approval.

Scope:

  1. After T-O3 STOP gate clears, the operator drafts a reply to Postmark Compliance citing the verified Sender Signature at dev.ardamails.com (with the Postmark Account API response or Console screenshot as evidence).
  2. The reply is sent via the same email thread that’s been active with Postmark Support since 2026-05-01.
  3. Documented assumption (per REQ-OPS-004): Postmark’s policy accepts one verified non-prod Sender Signature as sufficient evidence for arda-nonprod account approval, matching the arda-prod precedent (ticket #11236087, approved 2026-05-11). If Postmark replies requesting additional evidence, the operator updates the rollout plan and either provisions additional Signatures (e.g., stage.ardamails.com ahead of schedule) or supplies whatever Postmark requests.

File targets: none (operator-driven; reply via the existing email thread).

AWS impact: None.

STOP — review after T-O4: Postmark account approval received OR Postmark response requesting additional evidence captured and the rollout plan adjusted accordingly. Operator records the outcome in the sign-off table.


Same flow as T-O3, parameterized for stage. No external-approval gate. Deploy proceeds in Alpha002 account.

STOP — review after T-O5: equivalent to T-O3.


Same flow as T-O3, parameterized for demo. Deploys to Alpha001 (Admin-Alpha1 profile). No external-approval gate (arda-prod is already approved per K-10).

STOP — review after T-O6: equivalent to T-O3.


Same flow as T-O3, parameterized for prod. Deploys to Alpha001. Production-grade deploy — extra care: surface the full cdk diff to the user before deploy; obtain explicit confirmation. arda-prod is already approved (K-10).

STOP — review after T-O7: equivalent to T-O3. Phase 4 rollout complete after this task.


Task T-O8: First scheduled run of runtime-platform-drift.yml

Section titled “Task T-O8: First scheduled run of runtime-platform-drift.yml”

Goal: confirm the drift workflow runs end-to-end against the live partition surface; produces an empty / no-drift report on the first scheduled (or workflow_dispatch) run.

Scope:

  1. Operator manually triggers runtime-platform-drift.yml via gh workflow run (or waits for the first scheduled run).
  2. Confirms the workflow completes without opening a GitHub issue.
  3. Inspects the workflow’s logs to confirm the per-partition probe reports match expected state (no false positives).

File targets: none (operator-driven; observability check).

AWS impact: None (read-only).

STOP — review after T-O8: workflow run successful; no spurious issue opened.


Task T-D1: V-PART verification entries in verification.md

Section titled “Task T-D1: V-PART verification entries in verification.md”

Goal: author the verification regime — one V-PART-NNN / V-IAC-NNN / V-CLI-NNN / V-OPS-NNN / V-CI-NNN / V-DOC-NNN entry per REQ from requirements.md. Lands in the same PR as the requirement it verifies (i.e., the verification entries land in PR #1 for T-I1..T-I3, in PR #2 for T-I4..T-I10, etc.).

Scope: see verification.md (the catalog of test entries Phase 4 authors).

File targets: verification.md (per-PR additions).

AWS impact: None.

STOP — review after T-D1: each PR’s verification entries match the requirements landing in that PR; the verification doc continues to be coherent and well-cross-linked.


Task T-D2 / T-D3 / T-D8: CHANGELOG entries

Section titled “Task T-D2 / T-D3 / T-D8: CHANGELOG entries”

Goal: every PR (documentation repo or infrastructure repo) carries exactly one new CHANGELOG entry — per each repo’s convention. The two repos have different CHANGELOG models post-PR #78 (which landed on Arda-cards/documentation/main 2026-05-12).

Scope:

  • T-D2 (documentation repo — PR-body model): the Phase 4 docs PR description includes a ## CHANGELOG section per .github/pull_request_template.md with at least one valid category and entry. Valid categories: Added / Changed / Deprecated / Fixed / Removed / Security. Do not edit CHANGELOG.md directly — the changelog-check workflow rejects the PR. On merge, changelog-assembly.yaml extracts the section, computes the next semver, prepends a release block to CHANGELOG.md, and creates a git tag + GitHub Release. To amend post-PR-open, post a PR comment with an updated ## CHANGELOG section (last one found wins). Escape hatch: the manual-changelog label allows direct CHANGELOG.md edits (rare; reserved for historical corrections).
  • T-D3 (infrastructure repo — file-edit model, unchanged): edit infrastructure/CHANGELOG.md directly with one new release entry for the workspace-refactors PR (PR #1). Section order within the entry: Changed/Removed → Added/Deprecated → Fixed/Security per workspace memory feedback_changelog_section_order. CI gate: clq (or validate-release) check.
  • T-D8 (infrastructure repo — file-edit model): edit infrastructure/CHANGELOG.md for each per-partition PR (PRs #2-#5), the drift-workflow PR (PR #6), and any subsequent infrastructure PR. Fold mid-PR fix bullets into the existing top entry; never introduce a second [X.Y.Z] header in the same PR.

File targets:

  • T-D2: documentation PR description and PR comments (no CHANGELOG.md edit).
  • T-D3 / T-D8: infrastructure/CHANGELOG.md (file edits).

AWS impact: None.

STOP — review after each CHANGELOG: documentation PR — changelog-check workflow green (and the PR description’s ## CHANGELOG section validates against .github/clq/changemap.json). Infrastructure PR — clq / validate-release check green; section order matches the workspace convention.


Tasks T-D4 / T-D5 / T-D6 / T-D7: Documentation deliverables

Section titled “Tasks T-D4 / T-D5 / T-D6 / T-D7: Documentation deliverables”

Goal: fill the system-doc pages and the operator runbook authored as the analysis-doc references. Lands in PR #7 (the docs-only PR).

Scope — see requirements.md REQ-DOC-001..005:

  • T-D4: fill current-system/oam/security/secret-delivery-pattern.md content with partitionSecrets.cfn.yaml + Phase 4 Postmark token as worked examples. Bump frontmatter maturity: draftreview.
  • T-D5: per-partition mail pages in current-system/runtime/ (final filenames pinned by the implementer; suggested: partition-mail-topology.md describing the 4 sub-zones, the NS-delegation chain, the Infrastructure-to-account mapping).
  • T-D6: Postmark-service updates in current-system/oam/postmark-service/ to cover the multi-partition / multi-Signature post-Phase-4 inventory.
  • T-D7: encryption-key rotation operator runbook (final location pinned by the implementer; suggested: current-system/oam/postmark-service/email-encryption-key-rotation.md or a sibling under current-system/runtime/). Covers the AWS-SM-native rotation flow per DQ-R1-019; includes the migration-completion verification query and the SDK-fallback alarm playbook. Linked from the Phase 1 operator-runbook.md.

File targets: new / edited files in documentation/src/content/docs/current-system/.

AWS impact: None.

STOP — review after each T-D task: the page renders cleanly under make pr-checks; the documentation quality reviewer’s findings are addressed.


The 7-PR rollout sequence pinned in analysis.md § 13.2. Each PR maps to a set of tasks; per-partition PRs apply the same task set with a different partition argument.

#PRGroup(s)Tasks bundled
1phase-4-G-A (workspace refactors)G-AT-I1, T-I2, T-I3, T-I7 (reserved-words extension — workspace-only), T-I9 (helper extraction); T-D1 (verification entries for those tasks); T-D3 (infra CHANGELOG); T-O2 (Root no-drift verification, runs after merge)
2phase-4-G-B-C-D-dev (per-partition: dev)G-B+C+DT-I4, T-I5 (dev only), T-I6, T-I8, T-I10; T-D1 (verification entries for those tasks); T-D8 (infra CHANGELOG); T-O1 (pre-flight for dev); T-O3 (deploy dev); T-O4 (arda-nonprod approval reply)
3phase-4-G-B-C-D-stage (per-partition: stage)G-B+C+DT-I5 (stage only) — code-side carries over from PR #2; T-D8 (infra CHANGELOG); T-O1 (pre-flight for stage); T-O5 (deploy stage)
4phase-4-G-B-C-D-demo (per-partition: demo)G-B+C+DT-I5 (demo only); T-D8 (infra CHANGELOG); T-O1 (pre-flight for demo); T-O6 (deploy demo)
5phase-4-G-B-C-D-prod (per-partition: prod)G-B+C+DT-I5 (prod only); T-D8 (infra CHANGELOG); T-O1 (pre-flight for prod); T-O7 (deploy prod)
6phase-4-G-E (drift workflow)G-ET-I11, T-I12, T-I13; T-D1 (verification entries); T-D8 (infra CHANGELOG); T-O8 (first scheduled drift run)
7phase-4-G-F (documentation)G-FT-D4, T-D5, T-D6, T-D7; T-D2 (docs CHANGELOG)
  • PR #1 must merge before PR #2 (hard dependency: G-A’s construct generalization unblocks G-D’s IAM role instantiation; T-O2 must pass before any partition deploy).
  • PRs #2–#5 are sequential, not parallel. Per DQ-R1-021, each partition’s rollout completes (operator sign-off in T-O3..T-O7) before the next partition’s PR opens. Per-partition cdk diff discipline: each PR carries the diff for its partition only, refreshed before merge.
  • PR #6 may land anywhere after PR #2 is live (drift probes need at least one partition’s state to confirm coverage). Recommended to land after PR #2 to start exercising the workflow early.
  • PR #7 (docs) lands last — documents what’s been built.

Per-partition operator workflow (T-O3..T-O7)

Section titled “Per-partition operator workflow (T-O3..T-O7)”

For each partition’s PR (#2–#5):

  1. Operator opens the PR with the instance config + (PR #2 only) the code-side changes.
  2. CI runs unit tests + cdk synth smoke test; surfaces the per-partition cdk diff summary in the PR description.
  3. Operator runs T-O1 pre-flight against the partition.
  4. Pre-merge verification (recommended): operator runs bash ./amm.sh <infrastructure> <partition> against the PR’s branch (locally) to verify the deploy works end-to-end before merging.
  5. Operator merges the PR.
  6. Operator records sign-off in the verification doc’s table.
  7. Operator opens the next partition’s PR.

Phase 4 implementation is per-PR sequential — one agent owns each PR end-to-end; PRs are merged in order (#1 → #2 → #3 → … → #7). No two agents work on the same PR concurrently.

Single directory — no per-task worktrees needed for any individual PR. The existing Phase 4 worktrees provide phase-level isolation (Phase 4 work isolated from main while Phase 3 closes) but are not used for parallel agent work within Phase 4:

  • phase-4/documentation/ (jmpicnic/email-integration-phase-4) — all Phase 4 documentation work lands here, sequentially.
  • phase-4/infrastructure/ (jmpicnic/email-integration-phase-4) — all Phase 4 infrastructure work lands here, sequentially.

Each PR is opened off the phase branch on the relevant worktree, reviewed, merged, then the next PR begins. The user / Team Lead owns sequencing and operator-task execution between merges.

Cleanup: the phase-4 worktrees stay for the duration of Phase 4. After PR #7 merges and Phase 5b takes over, the phase-4 worktrees are removed via the per-phase retirement procedure in the project CLAUDE.md.

RiskLikelihoodImpactMitigation
T-I1’s construct generalization regresses Phase 2’s Root-account synth outputLowHighT-I2’s byte-equality unit test fails closed pre-merge; T-O2 verifies post-merge against deployed Root template. Two layers of guard.
T-I8 / T-I10 GHA ::add-mask:: is missed on a sub-step, leaking the Postmark token in CI logsLowHighInline comment in amm.sh immediately above each op read call mandates ::add-mask::; CI workflow review checklist surfaces it; per pre-design follow-up B5, drift workflow avoids 1P access entirely.
Postmark API rate limits Phase A’s list-then-create on rapid retries during PR #2’s iterationMediumMediumT-I9’s retry / backoff helper handles transient 429s; the helper’s tests cover the backoff. Operator can wait between iterations if needed.
arda-nonprod Postmark approval requires more than one verified SignatureMediumMediumREQ-OPS-004 documents the assumption explicitly; operator monitors Postmark Compliance’s reply after T-O4 and adjusts the rollout (provision stage ahead of schedule) if Postmark asks for more.
Operator’s AWS SSO session for Alpha001 / Alpha002 expires mid-rolloutMediumLowamm.sh re-invocation after re-authentication is idempotent (Phase A list-then-create; CDK deploy no-op if synth matches). T-O1 pre-flight verifies session validity.
A future PR modifies generateSecretString config on the encryption-key secret, triggering silent regenerationLowCriticalT-I4’s idempotent re-synth test + REQ-PART-016 + REQ-IAC-008 catch the change pre-merge. Encryption-design doc § CDK lifecycle invariants documents the rule for future authors.
cdk.context.json write from Phase A is not committed (forgotten); next clean checkout’s synth failsMediumLowPhase A appends to cdk.context.json in the working tree; the per-partition PR’s reviewer confirms the diff includes the context file; commit message convention surfaces the change.
Stage / Demo / Prod partition deploy races with another team’s deploy in the same Infrastructure accountLowMediumCFN stack-name uniqueness (${infrastructure}-${partition}-Email) prevents collision; CFN naturally serializes stack-level operations.
First scheduled runtime-platform-drift run produces false positivesMediumLowT-O8 surfaces the first-run output to the operator; expected vs observed shape is reviewed; the workflow is tuned (probe thresholds, retry counts) before the next scheduled run if needed.
#QuestionOptionsRecommendationDecision
All Phase 4 design questionsDQ-R1-017..022 + pre-design follow-ups B1-B5, C1-C3See analysis.md § 15 for the resolved setResolved; see ../../decision-log.md for full entries
1DQ-R1-023 — per-tenant Postmark Sender Signature introductionStatus quo (α) / per-tenant from v1 (β) / hybrid opt-in (γ) / remediation-only (δ)To be informed by pilot-phase tenant data; see DQ-R1-023Open — deferred to Phase 5b planning. No Phase 4 dependency; the infrastructure (EmailDnsProvisioningRole) is provisioned regardless.
  • Phase 5b implementation — the Email module, per-tenant Postmark Servers, TokenCipher consumer wiring, ESO ExternalSecret Helm definitions. Phase 5b is a separate phase with its own specification.md authored in 5b-email-module/.
  • Phase 5a implementation — the common-module TokenCipher primitive. Phase 5a is a separate phase.
  • CR Lambda migration for Postmark API calls — deferred per DQ-R1-022; the PostmarkSendingDomain thin-wrapper’s public surface stays unchanged.
  • AWS SM Rotation Lambda for the encryption-key secret — deferred; the design accommodates it (DQ-R1-019).

Copyright: (c) Arda Systems 2025-2026, All rights reserved