Crosshire
Architecture
Foundation01 Architecture · 1 of 3

The layer contract

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.

Stand 29.07.2026

#Medallion is not a data model

What
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
text
One 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
  • Enforce the read direction: gold reads silver, silver reads bronze, nothing skips
Embedding it
  • 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.

#The contract itself

What
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
bronzesilvergold
May enterraw source output, unmodifiedbronze onlysilver only
Grainas delivered by the sourceone row per business entity or eventdeclared per table; facts at the finest useful grain
Transformations allowednone — add technical columns onlytyping, cleansing, deduplication, conformance, historisationsurrogate keys, joins to dimensions, aggregation, currency conversion
Historyeverything, append-only, never deletedSCD1 or SCD2 per table, declareddimensions carry SCD2 intervals; facts are insert/upsert
Who reads itengineers onlyengineers; analysts by exceptioneveryone — BI, Genie, metric views
Forbiddenbusiness logic of any kindsurrogate keys, aggregationreading 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
sql
The 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.
Platform facts on this page verified 29.07.2026 against the official documentation. Volatile claims are anchored to the currency register. This is section 1 of 3 in 01 Architecture.