Skip to content

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.

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.

DecisionSubjectPhase 5a-relevant constraint
DQ-201L1 / L2 / L3 / L4 sub-layersLibrary 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-202AES-256-GCM versioned envelopeDQ-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-203HKDF derivation from 64-byte SM materialThe HKDF wrapper is a candidate common-module helper. Phase 5a decides whether to expose it.
DQ-204STS role chain for outbound AWS callsPhase 5a’s AppError mapping must accommodate STS errors raised by L1 / L2 callers.
DQ-205Persist-first lifecycleIdempotent-key helpers (Phase 5a) must support the persist-then-check pattern so L3 services can de-duplicate retries safely.
DQ-206Outbound encryption-key handlingPlaintext lives only on the call stack of getUnlockedConfiguration(). Phase 5a helpers must not introduce logging or caching of plaintext tokens.
DQ-207Bounded DNS-verification polling per-podHelpers for polling / activePolling-map management may be common-module candidates.
DQ-208Async-tx boundariesL3 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.

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:

  • TokenCipher class encapsulating the two-axis envelope a{N}.k{SM-VERSION-ID}, HKDF-SHA256 derivation with caller-supplied info string, 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.

These aren’t email-specific but constrain any common-module API addition:

  • Stable API discipline. common-module is 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-coding skill 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 EntityReference family 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.md independent 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”

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