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/.
1. Working agreements
Section titled “1. Working agreements”1.1 Implementation skill domain
Section titled “1.1 Implementation skill domain”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.
1.2 AWS-impact discipline
Section titled “1.2 AWS-impact discipline”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).
1.3 API-over-GUI rule
Section titled “1.3 API-over-GUI rule”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).
1.4 CFN stack-name immutability
Section titled “1.4 CFN stack-name immutability”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.
1.5 Tests ship with code
Section titled “1.5 Tests ship with code”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 synthagainst 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 forRootDnsStackpost-AllowCreatingNSRecordsRolegeneralization (REQ-IAC-002). Fails closed if Phase 4’s construct change regresses Phase 2’s Root-account synth output. - CDK
cdk synthno-op for the encryption-key SM secret on re-runs (REQ-PART-016 + REQ-IAC-008). Catches accidentalgenerateSecretStringconfig drift.
1.6 Documentation quality review
Section titled “1.6 Documentation quality review”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).
1.7 Out of scope (restated)
Section titled “1.7 Out of scope (restated)”Per requirements.md § Out of scope and analysis.md §14:
- Per-tenant Postmark Sender Signatures (deferred to Phase 5b; tracked as DQ-R1-023).
TokenCipherprimitive (Phase 5acommon-module).- Backend
ShopAccess/Emailmodule inoperations(Phase 5b). - Tightening
AllowCreatingNSRecordsRole.allowedParentHostedZoneIdson the Root-account instantiation (future security-hardening project). kylepartition mail capability (deferred; DQ-R1-021).- Apex SPF/DMARC on the root
ardamails.comzone (out of project). - AWS SM Rotation Lambda for the encryption-key secret (deferred; the design accommodates it).
- CR Lambda migration of
PostmarkSendingDomainthin-wrapper internals (deferred; DQ-R1-022).
2. Tasks
Section titled “2. Tasks”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-emailstack,register-partition-mail-signature.ts,amm.shextension,tools/lib/helpers) lands once in PR #2 (alongside thedevdeploy). Subsequent per-partition PRs (stage,demo,prod) only add the partition’s instance configuration plus invokeamm.shfor 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:
- Edit
src/main/cdk/constructs/oam/allow-creating-ns-records-role.ts:- Extend
Configurationwith a new discriminated-union fieldtrustPrincipal: TrustPrincipalConfigwhereTrustPrincipalConfigis 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 ConfigurationandBuiltunchanged. validateProps(props: Props): Error[]validates the discriminated union (kind === "lambdaOrgID"requires non-emptyorgId;kind === "stsAssumeRole"requiresaccountmatches the AWS-account-ID pattern andarnLikePatternmatches the IAM-role-ARN pattern). Errors throw viamisc.MultiError.- The construct’s existing
allowedParentHostedZoneIdsscope-tightening prop is preserved onConfiguration. - Permissions remain unchanged:
route53:ChangeResourceRecordSets,route53:ListResourceRecordSets,route53:ListHostedZonesByName.route53:GetChangeis NOT added (DQ-R1-020 — see analysis.md §7.3).
- Extend
- Optional rename in the same PR (mechanical; CDK construct ID at the call site preserved):
AllowCreatingNSRecordsRole→AllowCreatingDnsRecordsRole; 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 tonew …(this, "AllowCreatingNSRecordsRole", …)) stays"AllowCreatingNSRecordsRole"to preserve the logical ID and pass T-I2’s byte-equality check. - Update the construct’s tests (
allow-creating-ns-records-role.test.ts→allow-creating-dns-records-role.test.tsif renamed) to cover bothtrustPrincipal.kindmodes plus thevalidatePropserror 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:
- Author or extend
src/main/cdk/stacks/root/root-dns-stack.test.ts(orallow-creating-ns-records-role.test.ts) with a CDKTemplate.fromStack()snapshot-equality test:- Synthesize the
RootDnsStack(or just theAllowCreatingNSRecordsRoleconstruct 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.
- Synthesize the
- The test must fail closed if any future PR alters the Root-account synth output — including a rename of the construct, a
roleNamechange, aDescriptionchange, etc. - 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:
- Edit
src/main/cdk/platform/postmark-service.ts:- Add
export function postmarkCredentialOpReference(partition: PartitionId): stringreturningop://Arda-{Env}OAM/Postmark/credentialfor the partition’s bound Env vault (Devfordev,Stageforstage,Demofordemo,Prodforprod). - The function is pure and side-effect-free. No 1P access at synth time.
- Add
- Author / extend
postmark-service.test.tsto assert the four expected references for the four active partitions. - 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.
Task T-I4: Author partition-email stack
Section titled “Task T-I4: Author partition-email stack”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:
-
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 configexport 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.tsexport 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
idargument fromapps/Al1x/partition.tsis 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 byImageStorageStack— not the 4-arg(scope, prefix, id, props)formRootDnsStackuses. ThepublishingPrefixis computed inside the stack fromprops.locator(typically viapurpose.fqn(props.locator)or an equivalent helper). -
Compose, in order (inside the constructor, after
super(scope, id, props)):- NoEcho parameter: the caller in
apps/Al1x/partition.tsdeclares aCfnParameternamedPostmarkAccountTokenwithnoEcho: trueand passes it intoPartitionEmailStackProps.postmarkAccountTokenParam. DnsZonefor{partition}.ardamails.com(REQ-PART-001), withRemovalPolicy.RETAIN.WriteNSRecordsToUpstreamDnswithsubdomain: partitionandnameServersfrom the zone’shostedZoneNameServerstoken (REQ-PART-003).- SPF TXT record at
{partition}.ardamails.comapex with value"v=spf1 include:spf.mtasv.net ~all"(REQ-PART-004). - DMARC TXT record at
_dmarc.{partition}.ardamails.comwith 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 (tryGetContextvalue vs. source separation), the stack constructor reads the Postmark-issued public values fromcdk.context.jsonviathis.node.tryGetContext('postmark.{partition}.dkimSelector')(anddkimKey,returnPathTarget), validates them via a stack-layer helper (present + non-empty + typedstringcoercion), then passes them as typed props toDnsEmailRecords.DnsEmailRecordsitself 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 inemail-server-key-encryption.md. - DNS-records role via the generalized
AllowCreatingNSRecordsRoleconstruct (T-I1) with the Phase 4 trust principal shape andallowedParentHostedZoneIds: [zone.hostedZoneId](REQ-PART-017). EmailEncryptionKeyFallbackRoledeclared fresh in this stack: trust principal = account principal + ArnLike on${fqn}-*; permission =secretsmanager:GetSecretValueon${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) callsstack.publish()after instantiation to materialize the CFN Outputs (cdk-infrastructure.md § Stack ordering).
- NoEcho parameter: the caller in
-
Author
partition-email.test.ts(CDKTemplate-matcher tests):- Stack name matches
${infrastructure}-${partition}-Email. - One
AWS::Route53::HostedZoneresource withName: {partition}.ardamails.com.andDeletionPolicy: 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 usesFn::Refto theCfnParameter(withNoEcho: trueon the parameter declaration in the parent stack). - Two IAM roles with the expected trust-policy condition (
ArnLikeon${fqn}-*) and per-role scoped permissions. - All six exports present with expected names + values.
- Idempotent re-synth: re-running
cdk synthagainst the same fixture context produces an identical template (no-op invariant; REQ-IAC-008).
- Stack name matches
-
Also assert that
route53:GetChangeis 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:
- New files (or extensions of existing instance config files):
src/main/cdk/instances/Alpha001/prod.ts—prodpartition’spartition-emailconfig: 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-zonedemo.ardamails.com.src/main/cdk/instances/Alpha002/dev.ts— Postmark account ref =POSTMARK_NONPROD_ACCOUNT; sub-zonedev.ardamails.com.src/main/cdk/instances/Alpha002/stage.ts— same shape; sub-zonestage.ardamails.com.kyle.ardamails.comexcluded per DQ-R1-021 — the file forkyleeither doesn’t exist or has the partition-email config commented out with a reference to DQ-R1-021.
- 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/andAlpha002/.
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:
- Edit
src/main/cdk/apps/Al1x/partition.ts:- For each active partition (
prod,demo,dev,stage), declare aCfnParameternamedPostmarkAccountTokenwithnoEcho: true. The parameter is passed intoPartitionEmailStackviaProps.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 fromthis.exportDefinitionsonly when the App calls it. Same discipline asapps/Corporate/index.ts(Phase 3 precedent:mailDns.publish()). - Call
partitionEmailStack.addDependency(...)against any partition stack whoseBuiltproperties 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, useaddDependency()to make the order explicit at the CloudFormation level. The IAM roleArnLikepattern 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). kyleexcluded — either no instantiation or an explicit guard with a reference to DQ-R1-021.
- For each active partition (
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.
Task T-I7: Reserved-words list extension
Section titled “Task T-I7: Reserved-words list extension”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:
- Identify the registry mechanism Phase 3 used for
ardaat theardamails.comlevel (likely inplatform/ari-configuration.tsor a sibling). Extend it withprod,demo,dev,stage,kyle(per pre-design follow-up B4 — extend the same mechanism; no new registry). - 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:
- 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 mirrorsamm.sh’s<infrastructure> <partition>shape (amm.shline 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 | prodAlpha002: dev | stageReads the partition's Postmark account-level token from the partition'sArda-{Env}OAM 1P vault, registers (idempotent list-then-create) thePostmark Sender Signature for {partition}.ardamails.com on the boundPostmark account (PostmarkProd for prod/demo; PostmarkNonProd fordev/stage), and writes the issued DKIM selector / public key /Return-Path target into cdk.context.json underpostmark.{partition}.{dkimSelector,dkimKey,returnPathTarget}.Invoked by amm.sh; not a standalone operator-facing CLI (DQ-R1-022). -
Argparse: validate the two positionals.
<infrastructure>must beAlpha001orAlpha002(rejectSandboxKyle002with 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 apartition '<x>' does not belong to infrastructure '<y>'line. This catches an operator typo before any Postmark API call.
-
Flow:
- Resolve the partition’s Postmark account token via the 1Password SDK helper (in
tools/lib/) usingpostmarkCredentialOpReference(partition)(T-I3). - Use the Postmark Account API client from
tools/lib/(T-I9) to register the Sender Signature for{partition}.ardamails.comagainst the partition’s bound account (PostmarkProdorPostmarkNonProd). Idempotent: list-then-create. - If the Sender Signature exists with the same DKIM selector / Return-Path values, skip creation; otherwise create and capture the issued values.
- Call
verifyDkimandverifyReturnPathif the DNS records exist (best-effort — DNS may not yet be in place on first run). - Write the captured values into
cdk.context.jsonkeys: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 fromtools/lib/to ensure no token bleeds into the JSON.
- Resolve the partition’s Postmark account token via the 1Password SDK helper (in
-
Exit codes:
0on success; non-zero on Postmark API error, 1P resolution failure, or context-write failure. Redacted summary on stderr.
-
- The script is not a standalone operator-facing CLI — it’s invoked only by
amm.sh. Per DQ-R1-022’s “nopartition-mail-cli” framing. - Author
register-partition-mail-signature.test.tswith 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:
- Identify the helpers in
tools/corporate-cli.ts(and / or its sibling source files) thatregister-partition-mail-signature.tsactually needs. Minimal cut — do not extract anythingregister-partition-mail-signature.tsdoesn’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.jsonwrites. - Optional: conflict-check helper (per DQ-R1-016 Phase 3 pattern) if Phase 4 needs a pre-flight check for an existing Signature collision.
- 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 singletools/lib/index.ts— implementer’s call). Bothcorporate-cli.tsandregister-partition-mail-signature.tsimport from the new location. - Update
corporate-cli.tsto import fromtools/lib/instead of having the helpers inline. - Author / extend
tools/lib/*.test.tswith 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:
- 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:
op read 'op://Arda-{Env}OAM/Postmark/credential'to obtain the Postmark account-level token (env-vars the value asPOSTMARK_ACCOUNT_TOKEN).echo "::add-mask::$POSTMARK_ACCOUNT_TOKEN"(and the same for any subsequent values that originate fromop) — required in GHA runs to prevent log exposure; harmless locally.npx ts-node tools/register-partition-mail-signature.ts <infrastructure> <partition>— Phase A. If it exits non-zero, abort the step.cdk deploy ${infrastructure}-${partition}-Email --parameters PostmarkAccountToken=$POSTMARK_ACCOUNT_TOKEN— Phase B.
- The partition is parameterized:
amm.shfollows its existing convention for partition selection (the implementer should match the existingpartitionSecretsstep’s interface — likely a--partition <name>flag or positional arg).
- Add a partition-mail step that, for a given partition argument, executes three direct calls in order:
- 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).
- No
cd <path>in the shell flow. Per cdk-infrastructure.md § 9 (cd in shell helpers) and workspace memoryfeedback_git_dash_c: use absolute paths forcdk deploy --app <path>,git -C <path>,make -C <path>, andnpx --prefix <path>(orts-nodeinvoked with absolute script paths). Keeps the shell working directory stable, avoids agent permission prompts, and matches the existingpartitionSecretsstep’s convention. - 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:
- New file
infrastructure/.github/workflows/runtime-platform-drift.yml:- Cron schedule: daily.
- Manual
workflow_dispatchtrigger. - 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) ascorporate-drift— drift probes use Postmark API + AWS SDK; no per-partition 1P vault read needed per pre-design follow-up B5.
corporate-drift.ymlcontinues 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.
Task T-I12: Drift driver script
Section titled “Task T-I12: Drift driver script”Goal: per-partition Postmark + DNS + AWS-SM cross-seam asserter, enumerating from instances/Alpha*/.
Scope:
- 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,ReturnPathDomainvalues matchingsendingDomainPlacement()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 viadescribe-secret). - IAM: both partition IAM roles exist with the expected trust-policy condition and permissions.
- Postmark Account API: Sender Signature exists with expected
- Report shape matches Phase 1 / Phase 3 drift drivers.
- Exit code: 0 on no drift; non-zero on any drift detected.
- Enumerates
- 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:
- 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). - Move them into
infrastructure/tools/lib/drift/(or equivalent shared location). Bothcorporate-drift.tsandruntime-platform-drift.tsimport from the new location. corporate-drift.tscontinues to function unchanged after the refactor — regression-tested by runningcorporate-drift.ymlagainst the livearda.ardamails.comSender Signature (no drift expected) and confirming the existing report shape is unchanged.- The
corporate-driftworkflow 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.
Task T-O1: Pre-deploy state assertion
Section titled “Task T-O1: Pre-deploy state assertion”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):
- 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).
- Once for the rollout (before the first partition deploy):
dmarc-reports@arda.cardsmailbox 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.
Task T-O2: Root no-drift verification
Section titled “Task T-O2: Root no-drift verification”Goal: confirm the generalized AllowCreatingNSRecordsRole construct (T-I1) hasn’t drifted the Root account’s deployed CloudFormation.
Scope:
- Operator step (one-time, after T-I1 + T-I2 merge to
main, before the first partitionamm.shinvocation):- Synthesize
RootDnsStackfrom the currentmainbranch. - Compare against the Root account’s currently-deployed CloudFormation template via
aws cloudformation get-template --stack-name RootConfiguration. - Expected diff: empty.
- Synthesize
- 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.
Task T-O3: Deploy dev partition
Section titled “Task T-O3: Deploy dev partition”Goal: the first non-prod partition rollout. Includes G-POSTMARK-5 — the arda-nonprod account approval unlock step.
Scope (operator-driven via amm.sh):
- Run
amm.shfor thedevpartition (./amm.sh Alpha002 devor equivalent per the script’s interface). This invokes the partition-mail step from T-I10:op readthe Postmark NonProd account token fromArda-DevOAM.- GHA mask the token.
- Run
register-partition-mail-signature.ts Alpha002 devto register thedev.ardamails.comSender Signature onPostmarkNonProd; capture DKIM selector / key / Return-Path target; write tocdk.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 thecdk diffsummary in the PR description; user confirmation before proceeding.
- Wait for CFN deploy completion.
- Confirm via
dig:dig NS dev.ardamails.com @8.8.8.8returns the partition zone’s nameservers via root delegation.dig TXT dev.ardamails.comreturns the SPF record.dig TXT _dmarc.dev.ardamails.comreturns the DMARC record.dig TXT <selector>._domainkey.dev.ardamails.comreturns the DKIM record.dig CNAME pm-bounces.dev.ardamails.comreturnspm.mtasv.net.
- Confirm via Postmark Console (or
verifyDkim/verifyReturnPathAPI) 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
digchecks pass. - Postmark Console shows the
dev.ardamails.comSender 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:
- 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). - The reply is sent via the same email thread that’s been active with Postmark Support since 2026-05-01.
- Documented assumption (per REQ-OPS-004): Postmark’s policy accepts one verified non-prod Sender Signature as sufficient evidence for
arda-nonprodaccount approval, matching thearda-prodprecedent (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.comahead 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.
Task T-O5: Deploy stage partition
Section titled “Task T-O5: Deploy stage partition”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.
Task T-O6: Deploy demo partition
Section titled “Task T-O6: Deploy demo partition”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.
Task T-O7: Deploy prod partition
Section titled “Task T-O7: Deploy prod partition”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:
- Operator manually triggers
runtime-platform-drift.ymlviagh workflow run(or waits for the first scheduled run). - Confirms the workflow completes without opening a GitHub issue.
- 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
## CHANGELOGsection per.github/pull_request_template.mdwith at least one valid category and entry. Valid categories:Added/Changed/Deprecated/Fixed/Removed/Security. Do not editCHANGELOG.mddirectly — thechangelog-checkworkflow rejects the PR. On merge,changelog-assembly.yamlextracts the section, computes the next semver, prepends a release block toCHANGELOG.md, and creates a git tag + GitHub Release. To amend post-PR-open, post a PR comment with an updated## CHANGELOGsection (last one found wins). Escape hatch: themanual-changeloglabel allows directCHANGELOG.mdedits (rare; reserved for historical corrections). - T-D3 (infrastructure repo — file-edit model, unchanged): edit
infrastructure/CHANGELOG.mddirectly with one new release entry for the workspace-refactors PR (PR #1). Section order within the entry:Changed/Removed → Added/Deprecated → Fixed/Securityper workspace memoryfeedback_changelog_section_order. CI gate:clq(orvalidate-release) check. - T-D8 (infrastructure repo — file-edit model): edit
infrastructure/CHANGELOG.mdfor 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.mdedit). - 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.mdcontent withpartitionSecrets.cfn.yaml+ Phase 4 Postmark token as worked examples. Bump frontmattermaturity: draft→review. - T-D5: per-partition mail pages in
current-system/runtime/(final filenames pinned by the implementer; suggested:partition-mail-topology.mddescribing 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.mdor a sibling undercurrent-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 1operator-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.
3. Execution sequence
Section titled “3. Execution sequence”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.
| # | PR | Group(s) | Tasks bundled |
|---|---|---|---|
| 1 | phase-4-G-A (workspace refactors) | G-A | T-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) |
| 2 | phase-4-G-B-C-D-dev (per-partition: dev) | G-B+C+D | T-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) |
| 3 | phase-4-G-B-C-D-stage (per-partition: stage) | G-B+C+D | T-I5 (stage only) — code-side carries over from PR #2; T-D8 (infra CHANGELOG); T-O1 (pre-flight for stage); T-O5 (deploy stage) |
| 4 | phase-4-G-B-C-D-demo (per-partition: demo) | G-B+C+D | T-I5 (demo only); T-D8 (infra CHANGELOG); T-O1 (pre-flight for demo); T-O6 (deploy demo) |
| 5 | phase-4-G-B-C-D-prod (per-partition: prod) | G-B+C+D | T-I5 (prod only); T-D8 (infra CHANGELOG); T-O1 (pre-flight for prod); T-O7 (deploy prod) |
| 6 | phase-4-G-E (drift workflow) | G-E | T-I11, T-I12, T-I13; T-D1 (verification entries); T-D8 (infra CHANGELOG); T-O8 (first scheduled drift run) |
| 7 | phase-4-G-F (documentation) | G-F | T-D4, T-D5, T-D6, T-D7; T-D2 (docs CHANGELOG) |
Sequencing rules
Section titled “Sequencing rules”- 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 diffdiscipline: 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):
- Operator opens the PR with the instance config + (PR #2 only) the code-side changes.
- CI runs unit tests +
cdk synthsmoke test; surfaces the per-partitioncdk diffsummary in the PR description. - Operator runs T-O1 pre-flight against the partition.
- 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. - Operator merges the PR.
- Operator records sign-off in the verification doc’s table.
- Operator opens the next partition’s PR.
4. Worktree strategy
Section titled “4. Worktree strategy”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.
5. Risks and Mitigations
Section titled “5. Risks and Mitigations”| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| T-I1’s construct generalization regresses Phase 2’s Root-account synth output | Low | High | T-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 logs | Low | High | Inline 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 iteration | Medium | Medium | T-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 Signature | Medium | Medium | REQ-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-rollout | Medium | Low | amm.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 regeneration | Low | Critical | T-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 fails | Medium | Low | Phase 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 account | Low | Medium | CFN stack-name uniqueness (${infrastructure}-${partition}-Email) prevents collision; CFN naturally serializes stack-level operations. |
First scheduled runtime-platform-drift run produces false positives | Medium | Low | T-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. |
6. Open Questions and Decisions
Section titled “6. Open Questions and Decisions”| # | Question | Options | Recommendation | Decision |
|---|---|---|---|---|
| — | All Phase 4 design questions | DQ-R1-017..022 + pre-design follow-ups B1-B5, C1-C3 | See analysis.md § 15 for the resolved set | Resolved; see ../../decision-log.md for full entries |
| 1 | DQ-R1-023 — per-tenant Postmark Sender Signature introduction | Status quo (α) / per-tenant from v1 (β) / hybrid opt-in (γ) / remediation-only (δ) | To be informed by pilot-phase tenant data; see DQ-R1-023 | Open — deferred to Phase 5b planning. No Phase 4 dependency; the infrastructure (EmailDnsProvisioningRole) is provisioned regardless. |
7. Out of scope of this specification
Section titled “7. Out of scope of this specification”- Phase 5b implementation — the Email module, per-tenant Postmark Servers,
TokenCipherconsumer wiring, ESOExternalSecretHelm definitions. Phase 5b is a separate phase with its own specification.md authored in5b-email-module/. - Phase 5a implementation — the
common-moduleTokenCipherprimitive. Phase 5a is a separate phase. - CR Lambda migration for Postmark API calls — deferred per DQ-R1-022; the
PostmarkSendingDomainthin-wrapper’s public surface stays unchanged. - AWS SM Rotation Lambda for the encryption-key secret — deferred; the design accommodates it (DQ-R1-019).
8. References
Section titled “8. References”requirements.md— numbered requirements traced from Capabilities.analysis.md— Capability decomposition; gap rows; Implementation Groups; readiness criteria.verification.md— test catalogue (V-PART-NNN/V-IAC-NNN/V-CLI-NNN/V-OPS-NNN/V-CI-NNN/V-DOC-NNN).exports.md— cross-phase contracts Phase 4 produces.../plan/choreography.md— execution view (run sequencing, operator gates, artifact dependencies, completion criteria, project closure). Per-run plans under../plan/runs/run-N-<name>/project-plan.md.../goal.md— project intent and success criteria.../../decision-log.md— DQ-R1-017..023 + pre-design follow-ups closed.email-server-key-encryption.md— encryption-key envelope, dispatch model, lifecycle invariants.- Phase 3 analogue:
3-corporate-updates/specification.md.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved