Bronze, silver and gold are the least controversial diagram in data engineering and the most frequently misread. The contract below is what stops the diagram from being decoration.
Bronze, silver and gold describe how refined data is. They say nothing about how it is structured. The three layers carry no information about grain, keys, conformance or history — which means adopting medallion has not yet modelled anything.
The star schema is a separate decision, made in gold and only in gold. Watch what happens to one business concept as it moves through the layers — the name barely changes and the grain changes completely:
Why
The failure this prevents is the most common one in warehouse projects that adopt Databricks quickly: gold tables that are silver tables with a nicer prefix. They have no surrogate keys, no conformed dimensions, and a fact grain inherited from whatever the source happened to deliver.
Nothing is visibly wrong until the first question that spans two sources. Someone asks for revenue by customer segment across SAP and the webshop, and there is no conformed customer to group by — because conformance was never a step, it was assumed to be a side effect of the arrow between two boxes.
How
textOne concept, three layers, three different grains
bronze.sap_vbap one row per order-item CHANGE EVENT (CDC)
keys: whatever SAP sent
history: everything, forever, append-only
silver.sales_order_line one row per ORDER LINE, current state
keys: order_id + line_number (business keys)
history: SCD1 — overwritten
gold.fct_order_line one row per ORDER LINE, analysis-ready
keys: order_line_sk (surrogate)
+ customer_sk, product_sk, date_sk, region_sk
history: none itself; it points at SCD2 dimensions
currency: converted, amount_eur materialised
Three layers, three grains, three key strategies. Deciding bronze → silver → gold decided none of that.
Doing it
Write the grain sentence — 'one row per …' — before you create any tableIf it takes more than one sentence, the grain is wrong.
Put that sentence in the table COMMENT, deployed in DDL, not typed into the UI
Give every gold table surrogate keys; leave business keys visible alongside them
Conform dimensions in gold. Silver stays source-shaped and honest
In every review, ask one question: 'what is one row of this table?'A team that cannot answer in a sentence has not modelled it, and now knows that.
Make them draw the star on a whiteboard before any DDL is writtenTen minutes at a whiteboard is cheaper than a migration.
Have the team own the layer-contract page on their own wikiIf it lives in your handover deck, it dies at handover.
When you find a gold table with no _sk column, do not fix it yourselfShow them how you spotted it in ten seconds.
Defaults
One sentence of grain per table, in the COMMENT, deployed as code
Surrogate keys are a gold concern. Silver keeps business keys
gold reads only silver; silver reads only bronze. No diagonal arrows
If a gold table is a renamed silver table, delete it and let BI read silver directlyAn honest shortcut beats a dishonest layer.
Gotchas
Gold tables that are silver with a prefixYou find them in seconds: no column ends in _sk. If the whole layer looks like that, the project has a medallion diagram and no data model.
Skipping silver because 'bronze is clean enough'It is, for exactly one source. When the webshop arrives there is nowhere to conform customer identity, and the fix is now a rewrite of everything downstream instead of a new silver table.
Inventing a fourth layer — platinum, or an extra mart schemaIt is almost always a symptom that gold was never modelled, and the new layer is where the modelling is quietly being done instead.
Letting a dashboard read silver 'just for now'It becomes permanent, and it pins silver's shape forever: you can no longer refactor silver without breaking a report nobody told you about.
One table, agreed once, that makes code review mechanical instead of argumentative. Every row of it answers a question that otherwise gets re-litigated in every pull request.
What each layer may contain
bronze
silver
gold
May enter
raw source output, unmodified
bronze only
silver only
Grain
as delivered by the source
one row per business entity or event
declared per table; facts at the finest useful grain
surrogate keys, joins to dimensions, aggregation, currency conversion
History
everything, append-only, never deleted
SCD1 or SCD2 per table, declared
dimensions carry SCD2 intervals; facts are insert/upsert
Who reads it
engineers only
engineers; analysts by exception
everyone — BI, Genie, metric views
Forbidden
business logic of any kind
surrogate keys, aggregation
reading bronze; source-specific column names
Bronze's one permitted modification is technical metadata. Every bronze table in this warehouse carries the same three columns, which is what makes 'when did this row arrive and from which file' answerable without archaeology:
Why
Without a written contract, 'should this go in silver or gold?' is settled by whoever argues longest, and the answer differs per table. Six months later the layers no longer mean anything, and a new joiner has to read the code of every table to know what it is.
With it, review becomes a lookup. This gold table reads bronze — the contract forbids it, here is the row. No opinion required, which is exactly what you want a junior reviewer to be able to say to a senior engineer.
How
sqlThe bronze technical columns — identical on every bronze table
_tech_loaded_at TIMESTAMP -- when this row landed
_tech_source_file STRING -- which file or extract it came from
_tech_batch_id STRING -- which run wrote it
Doing it
Agree the table in one session, in week one, before there are tables to argue about
Put it in the repository next to the code, not in a slide deck
Add the three technical columns to bronze in the ingestion frameworkThe framework adds them so no table can forget them.
Add a CI check that fails a gold model importing from bronzeThe contract that is only prose gets violated within a month.
Embedding it
Have the team fill in the empty table themselves; you facilitate and push backA contract they wrote is one they enforce.
First time someone cites the contract in a review instead of citing you, the coaching worked — say so out loud
Review the contract once at the mid-pointIf nothing needed changing, they probably are not using it.
Defaults
The contract lives in the repo and changes by pull request like anything else
Technical columns are added by the framework, never hand-written per table
Encode the read-direction rule as a CI check, not as a paragraph
Exceptions are allowed but must be written down with an expiry date
Gotchas
A contract with no enforcement is a wishThe read-direction rule in particular is violated within weeks unless CI checks it, because reading bronze directly is always the fastest way to close today's ticket.
Making the contract too detailedIf it specifies naming for every column type it stops being read at all. One page, six rows.
Writing 'analysts may not read silver' and then giving analysts SELECT on the whole catalogThe contract and the grants must say the same thing — see the governance routes.