Project note
Schema on split
A pipeline rule: land raw, split deterministically, enrich point-in-time, then shape for consumers.
Data architecture / Published May 15, 2026
- Project: homelab-analytics
- Homelab Analytics
- The household data and decision platform that owns long-lived semantics, scenarios, policies, and approvals.
Explore this note with AI
Apply and challenge it in your own context.
A post-hoc prompt for applying and extending this note. It is not a reconstruction of how the note was written.
Use this note as a worked rule, not a naming scheme to adopt. The transferable question: in a data pipeline, which transformations can be replayed from their inputs alone, and where does the boundary between those and the rest actually fall? The worked answer draws the line at purity. A split may read the raw payload, the parser code, and mapping tables bundled with that code; anything reading current time, a catalog, a geocoder, an FX service, or mutable reference data is enrichment, not splitting. The operational test is direct: if the same input bytes and the same splitter version stop producing the same output later, it was enrichment all along, and hiding it in the normalization layer is where replay stories rot. Enriched output can only be reproduced if its sources were captured as content-addressed snapshots or, more weakly, version strings. Apply the question to a pipeline you own. Find every transform in your normalization layer, mark the ones that read external state, and say what it would cost to move them or to snapshot their sources. Say where your constraints diverge -- current-state recomputation is the desired behaviour, no replay or audit pressure exists, or the split is only type coercion. Produce a classification of your existing transforms with the lineage each would need, not a layer-renaming plan.On this page
Rule
Schema-on-split names a common compromise:
raw input
|
v
bronze: source artifact
|
| pure splitter(input, splitter_version)
v
silver: normalized record
|
| point-in-time enrichment(input, source_snapshot)
v
silver-enriched: contextual record
|
| consumer shaping
v
gold: mart / view / product output
The split is pure.
Anything that reads external state is not a split.
Boundary
splitter may read:
- raw payload
- parser code
- declared mapping tables bundled with that code
splitter may not read:
- current time
- catalog service
- geocoder
- FX service
- user-agent registry
- mutable reference data
If the same input bytes and same splitter version do not produce the same output later, the operation was enrichment.
Contract
bronze -> silver
deterministic
cheap to replay
safe to backfill broadly
silver -> enriched
stateful
replay requires historical source state
backfill changes meaning unless snapshots are preserved
Do not hide enrichment in silver because the diagram is cleaner. That is where replay stories usually rot.
Mapping
| Layer | Contract | Failure mode |
|---|---|---|
| Bronze | Preserve source | Source loss |
| Silver | Pure normalized output | Non-deterministic split |
| Enriched | Point-in-time context | Missing source snapshot |
| Gold | Consumer shape | Accidental authority |
Gold is not truth. It is a useful opinion.
Silver is not prestigious. It is the cheap reproducible layer.
Enrichment is not dirty. It is the expensive layer that needs stronger lineage.
Replay
good replay:
bronze_event
+ splitter_digest
= same silver_record
audit replay:
silver_record
+ enrichment_source_snapshot
+ enrichment_operator_digest
= same enriched_record
bad replay:
silver_record
+ today's geocoder
= plausible but not historical
Pure split outputs can be regenerated. Enriched outputs can only be reproduced if the enrichment sources were recorded as artifacts or snapshots.
Discipline
Record enrichment metadata with the output:
record.lineage = {
raw: content_hash,
splitter: content_hash,
enrichment_operator: content_hash,
enrichment_sources: [
{ name: "fx_rates", snapshot: "sha256:..." },
{ name: "catalog", snapshot: "catalog-2026-05-15T09:00Z" }
]
}
Content-addressed snapshots are the audit-grade form. Version strings are a weaker but often acceptable form.
Apply When
Use the rule when a pipeline has at least one of:
- replay pressure
- audit pressure
- backfills after bug fixes
- external reference data in transformations
- mixed pure and stateful transformation logic
Avoid it when the pipeline is small, current-state recomputation is the desired behavior, or the split is already only type coercion and field renaming.
Project Use
For homelab analytics and adjacent data-platform work:
first pass:
find every silver transform
mark reads of external state
move those reads to enrichment
attach source snapshot metadata
do not:
rename every model
add process ceremony
split stages where no replay value exists
The rule is a refactoring lens. Apply it where replay or audit can pay rent.