Phase 5a -- Pre-Existing Decisions
Phase 5a’s scope is cross-cutting library additions to common-module consumed by the Phase 5b Email module (sanitizeHeader, AppError.Application, idempotent-key helpers, possibly cryptographic primitives — see DQ-R1-019 below). This file enumerates the decisions that constrain Phase 5a’s design, organised by source. When Phase 5a’s goal.md and design artifacts are written, they should reference these decisions rather than re-deriving the underlying constraints.
This is a reference document, not a planning artifact. It is read by the next-session agent / engineer to bring relevant context forward without re-reading every prior round of the decision log.
Project goal constraints (always-applicable)
Section titled “Project goal constraints (always-applicable)”From ../goal.md:
- Cross-Universe rule: entities owned by different services must not share foreign keys or transactions. Applies to any common-module API that crosses service boundaries — even helpers must not introduce hidden coupling.
- Webhook authentication is in-component, Bearer-token validated. Helpers (e.g.,
sanitizeHeader) must support the Bearer-token validation path; not the API-Gateway-authorizer path which is explicitly out of scope. - Server tokens are application-encrypted at rest (DQ-012). The encryption envelope codec may live in common-module (Phase 5a) or in the operations component (Phase 5b) — see DQ-R1-019 below.
Application-layer decisions (DQ-201..208)
Section titled “Application-layer decisions (DQ-201..208)”These were taken during the original Round 1 design and pin the Email module’s internal structure. Phase 5a helpers must compose cleanly with this structure.
| Decision | Subject | Phase 5a-relevant constraint |
|---|---|---|
| DQ-201 | L1 / L2 / L3 / L4 sub-layers | Library helpers compose at multiple layers; sanitizeHeader (L4 inbound), AppError.Application (cross-layer), idempotent-key helpers (L3 persistence). Each helper must be usable at its target layer without dragging in dependencies from other layers. |
| DQ-202 | AES-256-GCM versioned envelope | DQ-R1-019 refined this to a two-axis envelope a{N}.k{SM-VERSION-ID}. If the envelope codec ships in common-module, Phase 5a owns its API; if in operations, Phase 5a does not. Open: see DQ-R1-019 implications below. |
| DQ-203 | HKDF derivation from 64-byte SM material | The HKDF wrapper is a candidate common-module helper. Phase 5a decides whether to expose it. |
| DQ-204 | STS role chain for outbound AWS calls | Phase 5a’s AppError mapping must accommodate STS errors raised by L1 / L2 callers. |
| DQ-205 | Persist-first lifecycle | Idempotent-key helpers (Phase 5a) must support the persist-then-check pattern so L3 services can de-duplicate retries safely. |
| DQ-206 | Outbound encryption-key handling | Plaintext lives only on the call stack of getUnlockedConfiguration(). Phase 5a helpers must not introduce logging or caching of plaintext tokens. |
| DQ-207 | Bounded DNS-verification polling per-pod | Helpers for polling / activePolling-map management may be common-module candidates. |
| DQ-208 | Async-tx boundaries | L3 services own transactions; common-module helpers must not open or close transactions on the caller’s behalf. |
Full text of DQ-201..208 lives in the project decision log (Round 1). When Phase 5a planning starts, re-read those entries for the full context.
Round R1 decisions affecting Phase 5a
Section titled “Round R1 decisions affecting Phase 5a”DQ-012 — Per-tenant Postmark server tokens encrypted at rest
Section titled “DQ-012 — Per-tenant Postmark server tokens encrypted at rest”The encryption envelope (DQ-202 + DQ-R1-019) operates on per-tenant server tokens via EmailConfigurationService (L3). Phase 5a may contribute the primitive building blocks (HKDF wrapper, AES-GCM wrapper, envelope codec) if they’re judged generically reusable; the service-level composition (loading the key map, dispatching on envelope prefix, migrating rows) stays in the operations component (Phase 5b).
DQ-R1-019 — Per-partition encryption key (Phase 5a deliverable: TokenCipher primitive)
Section titled “DQ-R1-019 — Per-partition encryption key (Phase 5a deliverable: TokenCipher primitive)”The Phase 4 design 4-runtime-platform-updates/design/email-server-key-encryption.md flagged the EnvelopeAlgorithm family’s library location as an open Phase-5a question. Resolved (B1, pre-design follow-up to Round R1-Phase4): ship in common-module as a general-purpose encrypted-field primitive, not Email-specific. Any future component needing encrypted-field-at-rest semantics (not just email server tokens) reuses the same TokenCipher API.
Phase 5a deliverables therefore include:
TokenCipherclass encapsulating the two-axis envelopea{N}.k{SM-VERSION-ID}, HKDF-SHA256 derivation with caller-suppliedinfostring, and AES-256-GCM encrypt / decrypt.EnvelopeAlgorithmRegistry(code-indexed algorithm versions).- Two
ConcurrentHashMap-backed registries for derived keys and raw SM materials. - SDK-fallback hook (caller supplies a function
(versionId) -> ByteArray) so the primitive remains AWS-SDK-agnostic; the operations component (Phase 5b) wires its own SDK client.
Library boundary discipline: TokenCipher’s API must accept the info string from the caller (so different components can derive non-overlapping keys from the same SM material) and must not log or cache plaintext per DQ-206.
The two-axis envelope structure (a{N}.k{SM-VERSION-ID}) is the contract; the Email module (Phase 5b) and any future consumer both produce and consume that format via the common-module primitive.
Workspace-level patterns
Section titled “Workspace-level patterns”These aren’t email-specific but constrain any common-module API addition:
- Stable API discipline.
common-moduleis consumed by multiple components (operations, future others). Any new public API in 5a should be designed with stable-versioning expectations (Semantic Versioning; once shipped, breaking changes require a major bump and coordination with all consumers). - No backwards-incompatible changes to existing common-module APIs in Phase 5a’s scope. Additions only.
- Unit-test discipline. Per
kotlin-codingskill conventions, every new public function gets unit tests with Kotest assertions and MockK mocks where needed. Integration tests (Testcontainers) are warranted if the helper has runtime behaviour beyond pure functions. - Cross-Universe rule applies to any helper that touches data models. Helpers that propagate references between services must use the
EntityReferencefamily rather than raw foreign keys.
What Phase 5a does NOT depend on (deliberately)
Section titled “What Phase 5a does NOT depend on (deliberately)”- No dependency on Phase 4 outputs. Phase 5a is per
phases.mdindependent of Phases 1-4 (Kotlin-library work). It can be planned and shipped in parallel with Phase 4 implementation. - No dependency on Phase 5b. Phase 5b consumes Phase 5a; Phase 5a does not consume Phase 5b. The decision flow is one-way: Phase 5a’s APIs precede their Phase 5b consumers.
Other artifacts to consult during Phase 5a planning
Section titled “Other artifacts to consult during Phase 5a planning”../goal.md— project-level acceptance criteria and constraints.../phases.md§ Phase 5a — phase scope and exit criteria.../decision-log.md— full text of DQ-201..208 (application-layer decisions) and DQ-R1-019 (encryption-key design).../cross-cutting-design.md— Auth, secrets, drift, OAM concerns Phase 5a helpers must accommodate.../4-runtime-platform-updates/design/email-server-key-encryption.md— the encryption envelope design that may inform a Phase 5a helper.- The
common-modulerepository’sknowledge-base/directory (in thecommon-modulerepo, not indocumentation) for repo-specific conventions discovered during prior work.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved