DECISION: [ADR-014] Universal Schema as Dict-of-Dicts
DATE: 2026-02-18
TYPE: 2 (reversible)
STATUS: Accepted
SOLITON SCORE: 0.80

## Context

The data fabric needs a canonical data representation that flows between
connectors, quality gates, lineage tracking, and the credit risk module.
The kernel is stdlib-only (ADR-007), so Pandas DataFrames and Polars
LazyFrames are not available in Rings 1-2.

## Options Considered

### Option A: Pandas DataFrame
Pros: Industry standard, rich API, fast vectorized operations.
Cons: External dependency (violates ADR-007 for Ring 2). Heavy import.
Version conflicts with downstream libraries.

### Option B: list[dict] with schema validation at boundaries (selected)
Pros: Zero dependencies. Same format as kernel Entity data and credit
module. JSON-serializable. Schema validation catches errors at ingest.
Adapter pattern enables Pandas/Polars in outer rings.
Cons: No vectorized operations. Slower for large datasets.

### Option C: Named tuples
Pros: Lightweight, immutable.
Cons: Fixed schema at class definition time. Less flexible for
dynamic column sets.

## Decision

Internal data flows as `list[dict[str, Any]]` with `SchemaSpec` validation
at system boundaries (ingest, export). Acceleration libraries deferred to
Ring 4+ where their benefits justify the dependency.

## Rationale

Consistency with kernel and credit module. Every component already speaks
`list[dict]`. Adding a different representation creates translation layers.
Schema validation at boundaries catches errors early without requiring
runtime type enforcement on every operation.

## Consequences

**Easier:** Zero-dependency data flow, JSON serialization, consistent API.
**Harder:** No vectorized operations. Large datasets (>100K rows) will
need Ring 4 acceleration adapters.
