2.3 KiB
Promote a sub-aggregate with stable distinct fields to its OWN @dataclass(frozen=True, slots=True) — do not share one mega-dataclass across multiple concepts
What it says
When a sub-aggregate has a known set of stable, distinct fields (e.g., CommsLogEntry has ts, role, kind, direction, model, source_tier, content, error; FileItem has path, view_mode, custom_slices; RAGChunk has id, document, path, score, metadata), promote it to its OWN @dataclass(frozen=True, slots=True) with its OWN fields. Do NOT share one mega-dataclass across multiple concepts.
Why
The per-aggregate dataclass is the "names for shapes" pattern extended to the structural level. Each concept gets its own type, its own fields, its own to_dict() / from_dict() round-trip. Consumers use direct field access (entry.ts, t.depends_on, chunk.document) which compiles to a single C-level field read with 0 branches.
When NOT to promote
When the shape is genuinely unknown at type level and the fields are heterogeneous (e.g., log entries from 5 different vendors with mutually-exclusive keys). Use Metadata: Metadata (the dataclass) as the catch-all — its 36 explicit fields cover the common wire schema, and its dict-compat methods allow ad-hoc keys for vendor-specific extensions. Do NOT use dict[str, Any] directly anywhere; Metadata is the typed replacement.
Canonical pattern (from src/openai_schemas.py and src/type_aliases.py)
@dataclass(frozen=True, slots=True)
class CommsLogEntry:
ts: str = ""
role: str = ""
kind: str = ""
direction: str = ""
model: str = "unknown"
source_tier: str = "main"
content: Any = None
error: str = ""
def to_dict(self) -> Metadata:
return asdict(self)
@classmethod
def from_dict(cls, raw: Metadata) -> "CommsLogEntry":
valid = {f.name for f in fields(cls)}
return cls(**{k: v for k, v in raw.items() if k in valid})
The rule (Tier 1 audit 2026-06-25)
If the original data_structure_strengthening_20260606 design intent was per-concept promotion (it was — see spec.md §3.3: "Phase 2 can convert Metadata to a TypedDict (or split into per-concept TypedDicts)..."), then metadata_promotion_20260624 must continue in that direction: per-aggregate dataclasses, not a shared mega-dataclass.