Cross-team contracts
When one team’s models read another team’s tables, a column the producer drops or
narrows breaks the consumer’s pipeline — usually discovered at run time, in
production, far from the change that caused it. Cross-team contracts move that
failure to compile time on the consumer’s side: rocky compile fails when an
upstream project changes a column the downstream project actually reads.
The two projects stay fully independent. There is no shared repository, no shared state, and no runtime coupling — only a vendored snapshot file and a diff.
How it works
Section titled “How it works”-
The producer publishes a snapshot.
rocky publish-ircompiles the producer project and writes its typedProjectIr— every model’s resolved columns and types — to a JSON file.Terminal window rocky publish-ir --with-seed --out project-ir.jsonPass
--with-seedfor a self-contained DuckDB producer so leaf models resolve concrete column types. Without resolved types the contract has nothing to check, sopublish-irrefuses to write a snapshot whose models are all empty rather than ship one that looks enforced but checks nothing. -
The consumer vendors it and declares an
[imports.<name>]block:[imports.orders]path = "vendor/orders" # directory holding the vendored snapshotssnapshot = "current.json" # the producer's current published snapshotbaseline = "baseline.json" # the reviewed-and-accepted "before" imagepin = "*" # optional recipe-hash pin ("*" = trust any) -
The consumer’s
rocky compilechecks the contract. It links each consumer model to the producer table it reads (via the model’s[[sources]]entry), diffs thebaselineagainst the currentsnapshot, and emits a diagnostic for any change that touches a column the consumer actually reads.
The diagnostics
Section titled “The diagnostics”| Code | Producer change | Severity | Fires when the consumer… |
|---|---|---|---|
E030 | column dropped | error | references the column |
E031 | column type narrowed | error | references the column |
E032 | column went nullable → NOT NULL | error | references the column |
W031 | column type widened | warning | references the column |
W030 | column added | info | reads the producer via SELECT * |
E033 | snapshot drifted from a concrete pin | error | always (whole-project tripwire) |
E034 | snapshot is a newer format than this build | error | always (fail closed) |
A consumer that selects explicit columns is unaffected by changes to columns it
does not read. A consumer that uses SELECT * can’t enumerate its columns, so
Rocky falls back to flagging every relevant change — over-reporting rather than
letting a breaking change slip through.
pin and baseline answer different questions
Section titled “pin and baseline answer different questions”They are complementary, not redundant:
baselineis the column-level before image. It is the only input that lets the diff emit the column codes (E030–E032,W030/W031) for the columns the consumer reads.pinis a whole-project drift tripwire. Set it to a concrete recipe hash androcky compilefails (E033) if the vendored snapshot differs at all — even for changes that touch no column the consumer reads. Leave it at"*"(the recommended default) to fail only on changes to your reads.
Accepting a producer change
Section titled “Accepting a producer change”Nothing advances the baseline automatically — that is deliberate. The baseline is
the schema you have reviewed and accepted. When the producer ships a change and
you’ve decided to take it, advance the baseline with:
rocky imports update # advance every import's baseline to its snapshotrocky imports update --check # CI guard: fail if any baseline is behind or pin is stale--check writes nothing and exits non-zero when an import is out of date — drop it
in CI to ensure vendored contracts stay in sync.
Distributing the snapshot
Section titled “Distributing the snapshot”Rocky reads a file at the configured path; how it gets there is up to you. The
simplest durable transport is a git submodule: add the producer’s repository (or
a small artifact repo it publishes to) as a submodule under path, and the producer
commits its publish-ir output there. The consumer then runs:
git submodule update --remote # pull the producer's latest snapshotrocky imports update # advance the baseline once you've reviewed itThe snapshot carries a snapshot_version header, so a consumer on an older build
fails closed (E034) against a newer format rather than silently mis-reading it.
See also the governance guide for how contracts fit alongside Rocky’s other trust controls.