Type each column once, as early as you dare
Re-CASTing the same physical column in every medallion layer feels careful. It is usually how drift multiplies. Bronze casts to decimal. Silver casts again "to be safe." Gold casts a third time and invents an unknown literal with yet another type. Consumers inherit a guessing game.
Type each published column once, as early as you dare (usually bronze, sometimes the first stable clean view). Silver and gold keep aliases, business calculations, and typed unknown-row literals only. UNION alignment casts are the exception, not the lifestyle. Lint the rest.

Figure 1. Type each column once, as early as you dare. Downstream layers should inherit the contract, not reinvent CASTs. Source: Microsoft Learn: copy data activity.
I learned this reading silver SQL that spent more lines re-declaring types than expressing business rules. The lakehouse looked disciplined. The types still disagreed with the semantic model because gold had its own CAST folklore. Rematerializing half the lineage after a type change made the cost obvious.
The core idea
A type contract belongs at the earliest stable publish boundary. Downstream layers should consume types, not renegotiate them.
Ritual CAST in silver and gold creates parallel truths: float here, decimal there, stringly dates in a third notebook. Unknown members need explicit typed literals in gold, not accidental nulls that break relationships. When the contract changes, rematerialize dependent lineage on purpose. Half-fix is how yesterday's types haunt tomorrow's measures.
A model that stays explainable
1. Choose the earliest stable typing boundary
Prefer bronze (or the thin clean view on trusted shortcuts) once the source contract is understood. Typing in raw landing before the source stabilizes just freezes churn. Typing only in gold pushes ambiguity through every upstream consumer. Pick the layer where the column is published for reuse, and declare types there.
2. Ban ritual CAST in silver and gold
Silver transforms grain and business rules. Gold shapes facts and dims for consumption. Neither should restate CAST(x AS DECIMAL(18,2)) because someone was nervous. Nervousness belongs in tests against the bronze contract. If silver must CAST, it should be because it creates a new derived column, not because it distrusts bronze.
3. Allow UNION alignment casts as named exceptions
UNION and UNION ALL branches need aligned types. Those casts are real engineering. Name them in comments or helpers, keep them next to the UNION, and do not copy them into every select list "for consistency." Consistency without a UNION is noise.
4. Gold owns typed unknown rows, not type renegotiation
Unknown and not-applicable members need keys and literals that match relationship types. That is gold work. Inventing a string 'unknown' next to an int64 key is how blank visuals appear later. Keep unknown-row typing in the same contract as the fact grain.
5. Lint CAST noise
SQLFluff or equivalent rules should fail noisy CAST patterns in silver/gold except allowlisted files (UNION helpers). Linting beats code review folklore. If the team can explain why a CAST is required, put it on the allowlist with a reason. If they cannot, delete it.
6. Rematerialize after type contract changes
Changing bronze types without rebuilding dependents leaves mixed generations in the lake. Plan rematerialization as part of the type change, not as a surprise incident. Document which MLVs and tables must rebuild and in which order.
Failure modes I design against
CAST as comfort blanket. Every layer "fixes" types; none owns them.
Stringly typed keys in one layer. Relationships and joins become seasonal.
Unknown literals with accidental types. Semantic models fail quietly.
Half-refresh after type changes. Old parquet files keep old types in practice.
Lint optional. Reviewers tire; CAST ritual returns.
Typing in unstable raw. You cement vendor dump chaos as platform truth.
How to introduce the ban without a holy war
Start with a report of CAST density per layer. Pick the top noisy gold objects and delete redundant casts while proving parity. Add lint in warn mode, then error mode for new changes. Do not rewrite the estate in one PR. The goal is a ratchet: new ritual CAST stops landing, old ritual CAST dies during normal touches. Pair the ratchet with a short ADR so reviewers have something to cite besides taste.
Unknown members are where typing discipline shows
Teams that ban ritual CAST still trip on unknown rows: a fact left outer joins a dim, someone coalesces a missing key to the string "UNK", and the relationship dies. Decide unknown key types with the same seriousness as money types. Put the unknown member in the dimension with a typed key. Reference that key from facts. Document the rule beside the grain statement so a future notebook does not invent a second unknown convention for the same entity.
Trade-offs
Early typing couples you to source understanding sooner. Late typing couples every consumer to ambiguity longer. Lint false positives need an allowlist process or people bypass CI. Rematerialization costs capacity; skipping it costs incidents. Derived columns will still CAST. The rule is about ritual retyping of the same physical field, not about forbidding SQL.
What I would put on an ADR
- Published columns receive definitive types at bronze (or documented earliest stable layer).
- Silver/gold ritual CAST on unchanged physical columns is banned.
- UNION alignment casts are allowed and localized.
- Gold unknown-row literals must match relationship types.
- CI lint fails CAST noise outside allowlists.
- Type contract changes include an explicit rematerialization plan.
Primary references: Spark SQL data type docs and Fabric lakehouse medallion guidance on Microsoft Learn. Pair them with a lint rule set in the repo so the contract is enforceable.
Closing
If every layer retypes the same column, you do not have a type system. You have a habit.
Type once early. Calculate downstream. Lint the ritual away. Rematerialize when the contract moves. The estate gets quieter, and so do the blank visuals.
