Skip to content

Email Integration -- Architecture Overview (Revision 1)

Top-level architecture and runtime topology for the Email Integration project, restructured under the runtime-design principles adopted in Runtime Overview.

Revision 1 note. This document supersedes the prior architecture overview (now superseded by this document). Substantive architectural changes:

  • DNS topology re-shaped: Corporate gets a dedicated arda.ardamails.com zone, separate from the partition mail sub-zones used by Application Runtimes. Free Kanban Tool sends from freekanban.arda.ardamails.com.
  • IaC code organisation follows the Constructs / Stacks / Apps hierarchy with the platform/ vs instances/ distinction.
  • Postmark integration adopts a platform/constructs/postmark/ thin-wrapper pattern; per-resource creation in CDK proceeds via Custom Resources (target) or via Corporate-CLI two-phase orchestration (interim).
  • Instance groups: Application Runtimes, Platform-level (Root, OAM), and Corporate are first-class peers.

The build plan has been re-numbered; see phases.md for the build sequence.


The project integrates the Postmark ESP into the Arda platform and delivers the ShopAccess/Email module — a general-purpose backend capability for sending transactional email on behalf of tenants, with per-tenant domain isolation, delivery tracking, and secure credential management. See goal.md for the full scope.

In scope:

  • External-resource references (Postmark accounts, 1Password vault items) declared under platform/.
  • DNS infrastructure: root zone (ardamails.com), Corporate zone (arda.ardamails.com), per-partition mail sub-zones ({partition}.ardamails.com).
  • Corporate instance assets, beginning with the Free Kanban Tool (freekanban.arda.ardamails.com).
  • Per-partition email infrastructure: secrets, IAM roles, NS delegations.
  • Postmark API thin-wrappers at platform/constructs/postmark/ (Construct-line shape).
  • The ShopAccess/Email module within the operations component (L1 / L2 / L3 / L4).
  • common-module additions (sanitizeHeader, AppError.Application, etc.).
  • Async DNS verification, encrypted server-token storage, webhook event handling.

Out of scope:

  • Frontend (SPA, BFF) — consumed by a downstream project.
  • Procurement-specific use cases (email orders, purchase order by email).
  • HTML email template rendering for specific business entities.
  • Migration of amm.sh or deploy-root.sh to the principle-aligned operator-script shape (deferred per runtime-design-review.md § 2).

Per the runtime-design principles, the infrastructure repository is the single reference for every resource the platform depends on. Two perspectives organise the work:

PerspectiveWhat it isWhere it lives
IaCCode artifacts that define resourcesinfrastructure/src/main/cdk/ (TypeScript), infrastructure/tools/ (operator-facing CLIs and utilities), infrastructure/.github/workflows/ (CI / drift), plus the existing repo-root scripts (amm.sh, deploy-root.sh)
RuntimeLive resources, instantiated from the IaC artifactsAWS (CDK / CFN), Postmark (API-driven), 1Password (referenced), GitHub (Actions secrets), Kubernetes (Helm)

Each runtime resource has at most one IaC owner; references go the other direction (an IaC artifact may reference an external runtime resource that Arda does not create — e.g., the Postmark account itself).

The four IaC categories from the principles map to this project as follows:

CategoryExamples in this project
AWS resources managed by ArdaRoute53 hosted zones, Secrets Manager entries, IAM roles, CloudWatch alarms
External resources Arda consumes but does not createPostmarkProd / PostmarkNonProd accounts, Arda-SystemsOAM 1Password vault, Arda-cards GitHub organisation, ardamails.com domain registration
Resources in third-party services Arda creates and maintains via IaCPostmark servers + sending domains (per Free Kanban Tool, per tenant), GitHub Actions secrets

The high-level relationship between IaC artifacts and the runtime instances they create:

PlantUML diagram

The detailed runtime / IaC composition is shown in § 5 (where the Corporate instance group is the worked example).


The static dependency graph among the project’s architectural elements. Each row reads “the element on the left needs the element on the right to exist or be reachable.” These are dependencies of structure, not of build order; the temporal sequencing in which they are delivered is in phases.md.

ElementDepends on
apps/Corporate/platform/postmark-service.ts (PostmarkProd account ref); platform/one-password.ts (vault refs); arda.ardamails.com zone is delegated by Root
CorporateMailDns stackconstructs/xgress/dns-zone.ts; the ardamails.com parent zone existing in Root
FreeKanbanToolMailDns stackCorporateMailDns stack outputs (zone reference); platform/constructs/postmark/ thin-wrapper constructs
Per-partition mail stacks (apps/Al1x/...)platform/postmark-service.ts (per-partition Postmark account ref); the partition’s mail sub-zone is delegated by Root
operations email modulePer-partition Secrets Manager entries (Postmark account token, encryption key); per-partition IAM provisioning role; common-module library APIs
Root (apps/Root/)platform/postmark-service.ts and platform/one-password.ts reference files (used to keep declaration shape consistent across instances)
Postmark thin-wrapper constructsplatform/postmark-service.ts (account refs); 1Password vault for credentials; offline test fixtures for unit testing
Drift-detection workflowsOP_SERVICE_ACCOUNT_TOKEN GitHub Actions secret; the runtime resources whose state they assert

Runtime instances sit downstream of these dependencies in the obvious way: a Route53 zone exists once its NS delegation is in place; a Postmark server exists once its account-token credential is reachable; an operations pod can send email once its partition’s secrets are projected through ESO and its IAM provisioning role is assumable.


The mail-domain hierarchy is reorganised so Corporate and Application Runtime traffic do not share zones.

PlantUML diagram

Properties of the layout:

  • Root owns ardamails.com only. NS records there delegate all sub-zones; SPF / DMARC for the whole tree are anchored there.
  • Corporate owns arda.ardamails.com (lives in the Root AWS account for now; see § 6.4). Sub-domains (e.g., freekanban.arda.ardamails.com) host DKIM and Return-Path for individual Corporate Postmark servers.
  • Application Runtime partitions own {partition}.ardamails.com zones in their own AWS account (Alpha001 for prod / demo, Alpha002 for dev / stage, SandboxKyle002 for kyle). Per-tenant sending sub-domains are provisioned at runtime by the operations component’s email service.
  • No cross-instance dependency between Corporate and Application Runtimes. Free Kanban Tool’s DNS records are entirely within Corporate’s zone; partition zones are entirely within partitions.

Source: Runtime Overview § 6. The general layered-architecture rules — dependency direction, layer responsibilities, and reuse model — are documented under IaC Functional Design; this section pins those rules to the email-integration project’s concrete additions.

infrastructure/
├── amm.sh # operator script -- Application Runtimes (existing, unchanged)
├── deploy-root.sh # operator script -- Root (existing; minimal path-only edit)
├── tools/ # operator-facing CLIs and ad-hoc utilities
│ ├── corporate-cli.ts # Corporate CLI: orchestrates Phase A (Postmark) + Phase B (CDK)
│ └── gha-secret.ts # transition-state ad-hoc CLI for GHA-secret provisioning
├── src/main/cdk/
│ ├── apps/ # CDK App entry points (one per concrete instance)
│ │ ├── Al1x/ # Application-Runtime apps
│ │ ├── Corporate/ # Corporate apps (free-kanban-tool, future hubspot, ...)
│ │ └── Root/ # Root apps (folder renamed from rootConfiguration/; CFN stack name preserved)
│ ├── instances/ # Concrete instance declarations (under repo control)
│ │ ├── Alpha001/ Alpha002/ SandboxKyle002/ # Application Runtimes
│ │ ├── Corporate/ # free-kanban-tool.ts and future siblings
│ │ └── Root/ # ardamails.com zone declaration + delegation table
│ ├── platform/ # Decoupled config / external "magic" / pre-defined patterns
│ │ ├── postmark-service.ts # PostmarkProd / PostmarkNonProd account refs
│ │ ├── one-password.ts # OAM_VAULT and other 1P references
│ │ ├── ari-configuration.ts # MAIL_RESERVED_SLUGS and related platform-wide naming
│ │ └── constructs/ # thin wrappers for non-AWS service APIs (one folder per provider, lowercase)
│ │ └── postmark/ # PostmarkServer, PostmarkSendingDomain
│ ├── stacks/
│ │ ├── corporate/ # corporate-mail-dns.ts, free-kanban-tool-mail-dns.ts
│ │ ├── infrastructure/ # per-Infrastructure stacks
│ │ ├── purpose/ # per-Partition stacks (incl. partition-email.ts)
│ │ └── root/ # Root DNS stacks
│ ├── constructs/
│ │ ├── compute/ network/ oam/ storage/ # functional categories for AWS-resource constructs
│ │ └── xgress/ # ingress / egress -- DNS, API gateway, etc.
│ │ ├── dns-zone.ts # generalised hosted-zone construct
│ │ ├── dns-email-records.ts # DKIM TXT + Return-Path CNAME (generic for any sending sub-domain)
│ │ └── (existing xgress constructs)
│ └── utils/ # cross-cutting TS utilities (logging + redact, 1Password SDK wrapper, ...)
└── .github/workflows/ # CI + drift detection

Notes on placement:

  • Operator scripts: existing amm.sh (Application Runtimes) and deploy-root.sh (Root) stay at the repo root. New TypeScript launchers live under tools/ (one entry point per class of resource, matching the established granularity).
  • Cross-cutting utilities: the 1Password SDK wrapper, logging + redaction, and similar repo-wide helpers live under src/main/cdk/utils/. Tools that need them import from there. The gha-secret.ts tool’s libsodium-and-Octokit logic lives inside gha-secret.ts itself (single-consumer, not promoted to a shared utility).
  • File-size discipline: TypeScript modules over ~500 lines should be split into a folder (<module-name>/index.ts plus siblings), kept in the same target location.
TypePathNotes
AWS resources (CDK L1 / L2)aws-cdk-lib plus Arda’s L2 / L3 in constructs/<category>/Existing pattern. Email-specific additions in xgress/.
Third-party API thin-wrappersplatform/constructs/<provider>/Construct-line shape: Configuration / Built types, leaner than full L2 CDK Constructs (no full type-system integration). Lowercase provider folder names. New in this project.
Helm releases (deferred)TBD src/main/helm/Documented in the principles; implementation deferred per reviews/R1/runtime-design-review.md § 7 Q4.

Stacks group resources sharing a deploy lifecycle. The CorporateMailDns stack owns the arda.ardamails.com zone, plus its SPF and DMARC records:

  • SPF at arda.ardamails.com: v=spf1 include:spf.mtasv.net ~all (Postmark’s SPF include).
  • DMARC at _dmarc.arda.ardamails.com: a Corporate-aggregate-report mailbox plus an initial monitoring policy (p=quarantine; sp=quarantine); the policy hardens to p=reject once Free Kanban Tool sending has bedded in.

The FreeKanbanToolMailDns stack composes the email-records construct with the Postmark thin-wrapper construct; the Stack passes Built values from the Postmark wrapper to the records construct so neither construct depends on the other.

Per DQ-R1-009, DKIM TXT lives at the Corporate zone parent (arda.ardamails.com); Return-Path CNAME lives at the per-asset leaf (pm-bounces.freekanban.arda.ardamails.com). The function sendingDomainPlacement() in platform/constructs/postmark/sending-domain.ts is the typed source-of-truth that the Corporate CLI (Phase A), the CDK construct (Phase B), and the drift check all consume identically — so the three sides cannot drift apart by re-deriving the same value independently. The same pattern generalises to per-partition Sender Signatures in Phase 4.

The Corporate App (apps/Corporate/index.ts) defines the reusable CorporateApp class; the entry script tools/cdk-corporate.ts calls new cdk.App() and instantiates both stacks with names and configuration sourced from instances/Corporate/corporate.ts.

5.3 Postmark-server creation: interim and target

Section titled “5.3 Postmark-server creation: interim and target”

The interim mechanism is in effect today; the target mechanism is the destination. The construct’s external surface is identical between the two; only its internals change. A decision-log entry (DQ-R1-NNN) records the rationale and migration trigger.

  • Target mechanism: a CDK Custom Resource (Lambda-backed) inside platform/constructs/postmark/PostmarkServer. The construct emits a custom resource into the CFN template; the Lambda calls Postmark on stack create / update / delete. A single cdk deploy invocation creates everything declaratively.
  • Interim mechanism: the Corporate CLI orchestrates the work in two conceptual phases. Phase A invokes the Postmark thin-wrapper imperatively (creates / reconciles the Postmark server, captures DKIM and Return-Path public values into cdk.context.json, writes the server token directly to 1Password). Phase B runs cdk deploy; the stacks read the captured public values from cdk.context.json and emit the DNS records. The two phases are conceptually two Apps deployed in sequence; no formal Apps are declared for them. CLI source comments name the phases explicitly so the eventual migration is unambiguous.
  • Value-transfer channel (interim): cdk.context.json. Public values only (postmark.free-kanban.serverId, postmark.free-kanban.dkimSelector, postmark.free-kanban.dkimKey, postmark.free-kanban.returnPathTarget). The Postmark server token is the secret and never enters CDK context, file artifacts, or env vars in the deploy pipeline — it is written by Phase A directly to the Free-Kanban-Generator-Postmark-Server 1Password item in the Arda-CorporateOAM vault.
  • Migration trigger: when Lambda-backed Custom Resources become a wider repo pattern, the PostmarkServer construct’s internals are swapped over and the CLI’s Phase A is retired. No caller code changes.

The runtime is organised as three instance groups with distinct ownership conventions.

Members: Alpha001 (prod, demo), Alpha002 (dev, stage), SandboxKyle002 (kyle). Each Infrastructure has its own AWS account and runs Arda’s commercial product. Mail sub-zones ({partition}.ardamails.com) live in the Infrastructure’s account; per-tenant sending sub-domains are runtime-provisioned by the operations component.

The application layer’s ShopAccess/Email module deploys into each partition’s existing operations component.

  • Root: shared resources that span the whole platform — the ardamails.com zone, the existing root DNS structure, NS delegations to other instance groups. Lives in platformRoot AWS account.
  • OAM (Operate, Administer, Maintain): placeholder; not exercised by this project.

Members: free-kanban-tool. Future: HubSpot integrations, marketing-website assets, other Arda-internal information systems.

The Free Kanban Tool sends from freekanban.arda.ardamails.com. Its Postmark server is created in PostmarkProd; its DKIM and Return-Path records are entirely within the Corporate zone. No cross-instance dependency on partition resources.

6.4 AWS-account-vs-instance-group decoupling

Section titled “6.4 AWS-account-vs-instance-group decoupling”

Instance group is not the same as AWS account. Today: Application Runtimes ↔ Alpha001 / Alpha002 / SandboxKyle002 accounts; Root ↔ platformRoot account; Corporate ↔ platformRoot account (for now). Documentation must make this explicit. Future: Corporate may move to its own AWS account when its asset count justifies the operational separation; the trigger and migration mechanism are out of scope for this project but worth a one-line policy entry in the runtime IaC docs.

Separation of Corporate domains under arda.ardamails.com adds a constraint: zone-names at the ardamails.com level (currently arda plus the partition names) must not collide with tenant slugs in any partition zone. The reserved-words list, in platform/ari-configuration.ts, is updated:

  • arda (and other future Corporate zone names) are reserved as zone names at the ardamails.com level.
  • Existing partition reserved slugs (mail, dmarc, postmaster, abuse, api, www, admin, arda, arda-cards, platform) remain reserved within partition zones for defence-in-depth.
  • Sub-zone slugs within arda.ardamails.com (e.g., freekanban, future hubspot) follow the same uniqueness discipline; a Corporate-level registry is documentation-only at this stage.

The application layer is structured into four sub-layers as the binding architectural decision (see DQ-201). Structure unchanged from the prior revision.

  • L1 — Protocol proxies. Stateless wrappers around external HTTP / SDK APIs. One per external surface (postmarkAccountProxy, postmarkServerProxy, route53ZoneProxy). One credential strategy per proxy. runCatching-bodied methods returning Result<T>.
  • L2 — Capability composers. Stateless. Choreograph L1 calls into capability operations (provision, decommission, verifyDns, sendOne). Map external errors to capability errors. No DB access.
  • L3 — Application services. Stateful. Hold all DB access via DataAuthority. Hold the encryption key (DQ-202 / DQ-203). Encrypt before persist; decrypt on demand. Spawn bounded DNS-verification polling rounds via per-pod activePolling map (DQ-207).
  • L4 — HTTP endpoints. REST entry points: email-configuration, email-job, postmark-events (webhook).

Layer separation is more than packaging: L1 and L2 are stateless by contract, simplifying testing (mock at the layer boundary) and clarifying cross-pod behaviour (idempotent UPDATEs in L3 carry the cross-pod safety guarantees per DQ-207.e).

Relationship to IaC thin-wrappers. The platform/constructs/postmark/ thin-wrappers are the IaC-side analogue of L1 — structurally similar but used at deploy time, not runtime. Runtime calls (per-tenant server creation, per-tenant sending) traverse the Kotlin L1 proxies in the operations pod; deploy-time calls (the singleton Free Kanban Tool server) traverse the TypeScript thin-wrappers from the Corporate CLI / CDK Custom Resource.

PlantUML diagram

Detailed treatment of each layer will be documented in the phase specifications as each module is designed (see phases.md).


8. Runtime topology (operations component)

Section titled “8. Runtime topology (operations component)”

This section is preserved from the prior revision; substantive content unchanged.

The ShopAccess/Email module ships as part of the existing operations component — a Kotlin / Ktor application running on JVM 21 / Netty in EKS. No new component, no new deployable image. The module contributes:

  • Source code under cards.arda.operations.shopaccess.email (operations repo).
  • A small Helm chart contribution (apis.system.shopAccess.email entry; ESO ExternalSecret entries).
  • A Flyway migration set under shopaccess/email/database/migrations/.
  • API Gateway routes for the module’s L4 endpoints declared in the operations repo’s CloudFormation files.

This rides the existing operations release cadence; no separate versioning.

The operations component runs with two replicas in production. Implications:

  • No leader election. Two replicas run identical code. Cross-pod coordination via idempotent state-guarded UPDATEs at the L3 boundary — e.g., transition to UNLOCKED is a no-op if the row is no longer in PENDING_VERIFICATION (DQ-207.e).
  • Per-pod state. Each pod maintains a ConcurrentHashMap<UUID, Job> of in-flight bounded DNS-verification polling tasks (DQ-207.f). Pod restart drops in-flight tasks; recovery is trigger-driven.
  • Graceful shutdown. On ApplicationStopping, L3 cancels all activePolling jobs (DQ-207.k).
DirectionSystemProtocolCredentialUsed for
OutboundPostmark Account APIHTTPSX-Postmark-Account-Token (per Postmark account; partition-wide)Server / domain CRUD, DKIM / Return-Path verification
OutboundPostmark Server APIHTTPSX-Postmark-Server-Token (per-tenant; decrypted on demand)Send email, configure webhook
OutboundAWS Route53AWS SDK over HTTPSSTS-assumed role (auto-chained from pod IRSA role)Tenant DNS record CRUD
OutboundAurora PostgreSQLJDBCDB credentials (existing operations component pattern)email_configuration / email_job persistence
InboundPostmark webhookHTTPSBearer token (ARDA_API_KEY); validated in-component by the receiving Ktor routeDelivery / Bounce / SpamComplaint events
InboundConsumer (SPA / BFF)HTTPSARDA_API_KEY validated in-component (Application Runtime’s existing scheme)Consumer-facing HTTP endpoints (email-configuration, email-job)

Route53 access traverses a role chain settled in DQ-204:

EKS Pod (IRSA service-account role)
└─ has sts:AssumeRole on EmailDnsProvisioningRole
└─ EmailDnsProvisioningRole (per partition)
└─ Route53 ChangeResourceRecordSets on partition mail zone

The AWS SDK’s StsAssumeRoleCredentialsProvider is configured at module startup with a 15-minute session duration. The provider auto-refreshes lazily; the L1 route53ZoneProxy makes Route53 calls without per-call AssumeRole.

PlantUML diagram

Inbound traffic transits API Gateway as a forwarding layer; authentication for the email service routes is performed in-component, in each route’s Ktor server configuration. The webhook route validates a Bearer token; consumer-facing routes use the Application Runtime’s existing in-component authentication scheme. API-gateway-level authorisers for the email-service routes are out of scope for this project; if and when the platform later adopts gateway-level authorisation as a cross-cutting concern, the email-service routes migrate alongside the rest. See cross-cutting-design.md § 2.2.


9. Functional → runtime → artifact mapping

Section titled “9. Functional → runtime → artifact mapping”
Functional elementRuntime resourceSource artifact
Postmark account referencesPostmark-Prod and Postmark-NonProd items in Arda-SystemsOAM (global-utility, qualified names); Postmark items in per-partition Arda-{Env}OAM vaults (service-name-only, created during Phase 4)src/main/cdk/platform/postmark-service.ts
1Password vault referencesArda-SystemsOAM vaultsrc/main/cdk/platform/one-password.ts
GitHub Actions CI secretOP_SERVICE_ACCOUNT_TOKEN repository secret in Arda-cards/infrastructureprovisioned via tools/gha-secret.ts (transition utility)
ardamails.com root zoneRoute53 hosted zone in platformRootRoot stack at apps/Root/; declared in instances/Root/dns.ts
arda.ardamails.com Corporate zone (incl. SPF and DMARC)Route53 hosted zone in platformRoot (Corporate AWS-account-decoupled)stacks/corporate/corporate-mail-dns.ts consuming constructs/xgress/dns-zone.ts
freekanban.arda.ardamails.com recordsRoute53 TXT (DKIM) + CNAME (Return-Path) within arda.ardamails.comstacks/corporate/free-kanban-tool-mail-dns.ts consuming constructs/xgress/dns-email-records.ts
Free Kanban Tool Postmark serverPostmark server instance in PostmarkProd accountplatform/constructs/postmark/PostmarkServer (interim: created by Corporate CLI; target: Custom Resource)
Per-partition mail zones (prod.ardamails.com, etc.)Route53 hosted zones in partition AWS accountsstacks/purpose/partition-email.ts consuming constructs/xgress/dns-zone.ts
L4 endpoints (email-job, email-configuration, postmark-events)API Gateway routes → ALB → operations podoperations: shopaccess/email/endpoint/...; routes declared in operations CFN files
L3 services (EmailConfigurationService, EmailJobService)EKS Deployment, EKS Pod (one of two replicas)operations: shopaccess/email/service/...
L2 capability composers (TenantProvisioner, EmailSender)Same pod, in-process Kotlin objectsoperations: shopaccess/email/servers/capability/...
L1 protocol proxies (postmark{Account,Server}Proxy, route53ZoneProxy)Same pod, in-process Kotlin objectsoperations: shopaccess/email/servers/api/...
Encryption / KDF (TokenCipher, Hkdf)Same pod, in-process Kotlin objectsoperations: shopaccess/email/crypto/...; primitives in common-module
DNS verification bounded-polling tasksSame pod, kotlinx coroutines on Application scope; activePolling mapin EmailConfigurationService
PersistenceAurora RDS (Postgres), Database per Module patternFlyway migrations: shopaccess/email/database/migrations/
HOCON configurationapplication.conf + secrets.properties (mounted from K8s Secret)operations: src/main/resources/shopaccess/email/application.conf + Helm
Postmark account-level secretsAWS Secrets Manager → ESO → K8s Secret → podinfrastructure repo (stacks/purpose/partition-email.ts); ESO entries in operations Helm chart
Per-tenant Postmark server tokensDB column server_token_encrypted (AES-256-GCM versioned envelope, DQ-202)email_configuration table
Per-partition encryption keyAWS Secrets Manager → ESO → HOCON (extras.email.encryptionKey) → HKDF in podinfrastructure repo (partition-email stack) + L3 service
IAM provisioning role (EmailDnsProvisioningRole)AWS IAM, per-partitioninfrastructure repo (partition-email stack)
Cross-cutting redact + structured loggerIn-process TS code reused across CDK and toolssrc/main/cdk/utils/logging.ts
1Password SDK wrapper (CI + tools)In-process TS code with dual-auth discoverysrc/main/cdk/utils/one-password.ts
Drift-detection workflow (Corporate)Scheduled GitHub Actions run; auto-issue on failure.github/workflows/corporate-drift.yml (instance-group-scoped per DQ-R1-012)

Three decision logs cover the project after this revision:

  • Upstream decisions (decision-log.md, DQ-001 through DQ-013): mail root domain, tenant slug source, sending-domain shape, webhook authentication, server-token storage approach, IAM-role placement.
  • Application-layer decisions (decision-log.md, DQ-201 through DQ-208): server module partitioning (L1 / L2 / L3), encryption algorithm + key derivation, STS strategy, persist-first lifecycle, slug derivation, trigger-driven DNS verification.
  • Revision-1 decisions (numbered DQ-R1-NNN, to be added in decision-log.md): instance-group taxonomy, arda.ardamails.com topology, Postmark-server creation interim/target mechanism, AWS-account-vs-instance-group decoupling, reserved-name discipline, Postmark thin-wrapper placement (platform/constructs/<provider>/), CDK code-hierarchy adoption.

Prior decisions invalidated by Revision 1 are marked as [superseded by DQ-R1-NNN] in the original log; new DQ-R1-NNN entries capture the replacement state.

The two current design documents — this overview and cross-cutting-design.md — cite the relevant DQs at each section. Phase-specific functional design and information model documents (planned for Phases 2–4) will follow the same convention.