Skip to content

ADR-111: Flatten All DynamoDB Records to Top-Level Attributes

Status: Accepted Date: 2026-01-25 Issue: #180

Context

The codebase accumulated three DynamoDB schema patterns: nested data.M maps (entities, limits, audit events, version records), hybrid (buckets with one flat counter), and flat (usage snapshots and config records). ADR-006 and ADR-010 introduced flat attributes to solve DynamoDB's "overlapping document paths" limitation. ADR-101 standardized flat schema for all new config records and explicitly recommended flattening existing record types in v0.6.0.

The mixed patterns create inconsistency in serialization/deserialization code, complicate the aggregator Lambda (which must navigate nested paths), and make the codebase harder to maintain. Every new feature must decide which pattern to use, adding cognitive overhead.

Decision

All DynamoDB record types must use flat schema (top-level attributes, no nested data.M wrapper). Deserialization reads flat format only. Pre-1.0.0 semver allows breaking changes without migration — existing nested records are not supported. Serialization produces only flat format.

Consequences

Positive: - Uniform schema across all record types eliminates pattern inconsistency - Simpler serialization code without data.M wrapper construction - Aggregator Lambda reads flat attributes directly instead of navigating nested paths - Enables atomic operations on any attribute without "overlapping paths" errors - No branching logic in deserializers

Negative: - Existing nested data.M records from pre-0.6.0 deployments must be recreated (no migration path provided pre-1.0.0) - DynamoDB reserved words (name, resource, action, timestamp) require ExpressionAttributeNames aliases in all expressions

Alternatives Considered

Keep nested data.M for existing records

Rejected because: Perpetuates schema inconsistency; ADR-101 already committed to flattening in v0.6.0.

Big-bang migration (write stops until all records migrated)

Rejected because: Requires downtime; pre-1.0.0 semver makes migration unnecessary — breaking changes are expected.

Flatten only on read (no serialization changes)

Rejected because: Leaves old-format records indefinitely; new writes would still produce nested format, preventing convergence.