Skip to content

The Architecture of Trust

This page grades each of Rocky’s trust primitives against what ships today. Where a primitive is partial or still on the roadmap, the grade says so.

The expensive failures on a mature data platform are trust failures, not slow queries. They share a shape. A change happens somewhere, nothing breaks loudly, and the damage surfaces days later in a number someone trusted. The Introduction lists the specific ones.

Every primitive below exists to turn one of those failures into something you catch before you merge.

The mental model: code, typed IR, warehouse

Section titled “The mental model: code, typed IR, warehouse”

Rocky sits between your code and your warehouse as a typed compiler.

your code Rocky your warehouse
───────── ───── ──────────────
.sql / .rocky ────────► typed IR ────────► storage
.toml sidecars every model, compute
contracts every column, the tables
every type
│
│ before anything runs
▼
E### errors, W### warnings,
lineage, drift, cost, contracts

Your SQL and config compile down to a typed intermediate representation, or IR. The IR knows every model, every column, and every type across the full dependency graph. The warehouse still owns storage and compute. Rocky owns the graph, and the compiler is the trust boundary.

That single decision is what makes the rest possible. Once the graph is typed and known before execution, three things follow. A schema change becomes a type error. A missing contract column becomes a diagnostic with a code. A column’s downstream blast radius becomes a query you can run in CI. None of that is reachable from a template that substitutes strings, because a string template never has to know what a column is.

Rocky is not a warehouse, not a table format, and not a query engine. It is the typed layer above whichever of those you have chosen, and it stays warehouse-neutral on purpose.

Each primitive below names its real CLI surface or diagnostic code. The grade table at the end says how far each one ships today, so you can plan around the edges instead of finding them later.

Rocky infers column-level types across the whole DAG. It reports problems as diagnostic codes you can grep in a CI log. Errors carry an E code, warnings a W code, and lints a P code.

Compilation fails on any error-level diagnostic. That is the whole point: the failure becomes a non-zero exit code at PR time, not a wrong number in production.

rocky lineage <model> traces a model’s inputs and outputs. rocky lineage <model>.<column> traces a single column through every transformation that touches it. The edges come from the compiler’s semantic analysis, so Rocky computes lineage at compile time rather than reconstructing it afterwards.

rocky lineage-diff main turns that into a blast-radius report for PR review. Change a column, and see exactly which downstream columns it affects before you merge.

The lineage graph is intra-project. It knows the columns inside one Rocky project, not across project boundaries.

rocky branch create and rocky run --branch <name> give you isolated branches for development and review. A branch today is a schema prefix: models for branch feature_x materialize under a branch__feature_x namespace, so a branch never touches a production table. rocky branch approve writes an artifact under .rocky/approvals/<branch>/ by default. --out sends it somewhere else, and the gate only reads the default directory, so an artifact written elsewhere does not count.

The gate that reads those artifacts is off by default. Set [branch.approval] required = true to turn it on. With it off, the promote path does not read the directory at all.

With it on, the gate runs when the promote plan is built. That is rocky plan promote, and bare rocky branch promote <name>, which builds a plan first. Applying a promote plan that already exists does not repeat the check, so the approvals are the ones that were valid at plan time. The gate loads every artifact in that directory and counts the valid ones against min_approvers.

An artifact is valid when its blake3 digest still matches its own contents, its recorded branch state hash matches the branch now, it is not dated in the future, and it is not older than max_age_seconds. When allowed_signers is not empty, the approver’s email must also be on that list.

min_approvers counts artifact FILES, not distinct people. Nothing de-duplicates by identity, so one approver can satisfy a threshold of two by writing two artifacts. It defaults to 1, and setting it to 0 makes the count pass with no approvals at all.

Two things bypass the gate even when it is on. rocky branch promote --skip-approval skips it, and so does the ROCKY_BRANCH_APPROVAL_SKIP environment variable. Both record the reason as an audit event rather than failing.

That digest is not a cryptographic signature, and it is not a tamper boundary. It is unkeyed, so anything that can write the file can change the artifact and recompute the digest together. What it catches is a modification that was not re-hashed. It authenticates nobody: the approver’s email is a self-asserted git identity hashed with the rest of the artifact, and a writer can set it to anything.

The plans directory, .rocky/plans/, is a trusted input for the same reason. A plan’s id is the blake3 digest of its kind and payload. Rocky recomputes that digest on every read and refuses a plan that no longer matches its own id. The id is unkeyed, so it catches a file changed without re-hashing, not a plan written whole. A promote plan runs the SQL statement it records. The checks at apply read the table names recorded beside it, not names parsed from the SQL. The AiAuthored review marker is unsigned too. Anyone who can write .rocky/plans/ can therefore author a plan that rocky apply runs with the applier’s warehouse credentials. For a promote plan, the policy gate still runs at apply, against the principal applying it. It judges the plan by the table names it records, not by the table the SQL writes. Rocky tries to keep .rocky/ out of git by writing a .gitignore there. It skips that when one already exists, and ignores a failed write, so check what your repository excludes. Protect write access to the directory as you would the project itself.

What you get today is schema-prefix isolation, not a warehouse-native zero-copy clone. Delta SHALLOW CLONE and Snowflake zero-copy CLONE would make branch creation near-instant and free of storage cost. That integration is a follow-up, not what runs now.

Rocky records per-model cost on every run, which makes cost a property of a model rather than a line on an invoice.

The accuracy depends on the warehouse:

  • BigQuery. Bytes scanned maps directly to billing, so the figure is billing-exact.
  • Databricks and Snowflake. The figure is a duration × DBU-rate estimate. A DBU is Databricks’ compute billing unit. Databricks also reports warehouse-scanned bytes, surfaced for observability rather than pricing, because Databricks prices by DBU. Snowflake’s bytes plumbing is still a follow-up.
  • DuckDB. Zero.

A .contract.toml declares what a model must produce. The compiler checks the model’s inferred schema against it. E010 to E014 cover a missing required column, a type mismatch, a nullability violation, a removed protected column, and an undeclared nullable column. The last applies only while [rules] no_new_nullable is on, which is off by default. Any of them fails compilation, so a broken contract is a red CI check rather than a production surprise. The Checks and Tests page has the format and every code.

Those codes are intra-project. Enforcement across a project boundary also ships, through a vendored snapshot: the consuming team keeps its own committed copy of the producing team’s compiled schema, and diffs against that copy. A producer runs rocky publish-ir. A consumer declares an [imports.<name>] block and the consumer’s rocky compile fails on a breaking change with E030 to E034. W030 and W031 cover an added column and a widened type. See Cross-Team Contracts for the workflow and the meaning of each code.

Rocky models governance as code through a GovernanceAdapter. The surface covers tag management, grants, workspace bindings, column tags, masking policies bound to classification tags, and role-graph reconciliation. The adapter trait has a revoke method (revoke_grants), but rocky run does not call it. Rocky grants and never revokes.

How much of that is real depends entirely on the warehouse:

  • Databricks implements the full surface through Unity Catalog.
  • Snowflake reconciles object tags (ALTER … SET TAG) and GRANT role grants, plus retention policy. Workspace binding and masking are not driven.
  • BigQuery reconciles tags as labels (ALTER … SET OPTIONS(labels=…)). Grants map to IAM, so apply_grants and revoke_grants currently log and do nothing. Real IAM integration is a follow-up.
  • DuckDB does nothing, because it has no governance model to drive.

So governance at depth is a Databricks capability today. The skeleton is warehouse-neutral. The depth is not yet portable.

Rocky checks for drift as it copies each table, whenever the target already exists. By default it grades the response and applies it in the same run: add a column, widen a safe type, or drop and rebuild the target for any other type change. Set auto_apply_additive_drift = true under [resilience] to route drift through the policy plane. The run then applies only a nullable new column, and only when policy resolves to allow for schema_change.additive. With no [policy] block, nothing is granted. Every other change is refused before it touches the target, including a safe widening. See Schema Drift.

Three limits matter. The drop happens in the same run that finds the change: no grace period runs first. Rocky does not detect a column that disappeared from the source. And the run’s drift output lists only the actions of an attempt that finished. If the rebuild fails after the drop, that entry is lost. A retry, one by default, can then rebuild the table and report it as materialized, with no sign of the drop. See Failure modes for each action and its recovery.

Replay means two distinct things. Being precise about which one ships matters.

The first is deterministic recording with ledger verification. Rocky records each run’s per-model SQL hashes, row counts, bytes, and timings when the execution path supplies them. Deterministic content-addressed materializations produce hash-named artifacts; ordinary warehouse runs do not provide byte-level replay proof. On the content-addressed path, each file is named by the hash of its own bytes, so the same inputs and code produce the same physical files. rocky replay <run_id> inspects that record and verifies it against the ledger. That ships today.

Every materialization also stamps a recipe-identity triple: three hashes that together answer “what produced this row set”.

  • recipe_hash fingerprints the model’s canonical typed IR, so the same program hashes the same no matter when it ran.
  • input_hash covers the inputs it read.
  • env_hash covers the engine, adapter, and dialect it ran under.

rocky history --recipe <hash> answers the audit question directly: what produced this, and every other time this exact program ran. The triple is honest about its own strength. An input_hash proven by an observed freshness signature is tagged heuristic and is never presented as a claim about byte content. A content-addressed input is tagged strong. This is an identity and audit primitive, not a reproducibility claim.

The second is re-execution from the pinned record. To replay a past run, Rocky rebuilds each model’s recipe from provenance, never from the working tree. It then re-runs that recipe to reproduce the output from scratch. rocky replay --execute --verify does this and compares the re-derived BLAKE3 hash against the recorded one.

It runs on a local DuckDB engine by default, or against the live warehouse with --warehouse. The warehouse path materializes into an isolated hcv2_replay_<run> schema, never the recorded target’s production location. It encodes the recomputed artifact with the target table’s own physical column mapping. So a bit_exact verdict means the warehouse reproduced the recorded bytes exactly.

Re-execution is scoped honestly. It covers deterministic, content-addressed models. A model that reads a mutable source is classified non_replayable instead of being re-run against current data. A non-deterministic recipe is flagged, so a diverged verdict there is expected rather than a failure.

Content-addressed materialization itself ships for single-writer Delta and UniForm, a Delta feature that also publishes Iceberg metadata. It writes blake3-hashed Parquet files plus a Delta log commit, and Iceberg-compatible readers see the same snapshot. It is single-writer. It does not yet cover multi-writer concurrency, broad schema evolution, or deletion vectors. A deletion vector is a Delta feature that records deleted rows in a side file rather than rewriting the Parquet.

The VS Code extension draws the lineage graph and paints five trust signals onto it. Each one is backed by Rocky output.

  1. Cost: the compile-time cost estimate for each model.
  2. Freshness: the declared freshness policy.
  3. Breaking: breaking changes from the semantic CI diff.
  4. Last run: the last recorded run for each model.
  5. Governance: classified columns and unmasked exceptions.

There is no drift overlay. Rocky has no standalone rocky drift command. It detects drift inside rocky run and rocky plan.

Every load-bearing claim, in one table. The partial rows are where teams get surprised.

Claim Grade What that means
Compile-time column-level types and diagnostics (E### errors) Shipped Compilation fails on any error-level diagnostic.
Compile-time column-level lineage + lineage-diff blast radius Shipped Intra-project; computed at compile time.
Compile-time contracts (E010–E014) Shipped Intra-project contract validation against inferred schema.
Schema drift handling (add column / safe widen / drop-and-recreate) Shipped Graded response, applied in the run that detects it by default; with auto_apply_additive_drift, only a nullable addition that policy resolves to allow. No grace period before a drop. A column removed from the source is not detected. The drift output can omit a drop whose first rebuild failed.
Dialect-divergence lint (P001) Shipped Opt-in via --target-dialect or [portability] target_dialect; error severity.
VS Code trust overlays Shipped Five: Cost, Freshness, Breaking, Last run, Governance. No drift overlay.
Branches Partial Schema-prefix isolation with promotion. The approval gate is opt-in ([branch.approval] required = true) and checks an unkeyed digest: an integrity checksum, not a tamper boundary, and it authenticates nobody. No warehouse-native zero-copy clones yet.
Replay Partial Deterministic recording + ledger verification, plus re-execution (rocky replay --execute --verify, local or --warehouse) for deterministic content-addressed models; mutable-source models are non_replayable, non-deterministic recipes flagged.
Content-addressed writes Partial Single-writer Delta/UniForm; no multi-writer, broad schema evolution, or deletion vectors yet.
Per-model cost Partial Billing-exact on BigQuery; a duration × DBU-rate estimate on Databricks and Snowflake; zero on DuckDB. Databricks surfaces scanned bytes for observability; Snowflake’s warehouse-reported-bytes plumbing is the follow-up.
Declarative governance Partial Full on Databricks (Unity Catalog); tags + GRANT on Snowflake; label-based tagging only on BigQuery (grants no-op, IAM follow-up); no-op on DuckDB.
Cross-team / cross-project contract enforcement Shipped Producer rocky publish-ir → consumer [imports.<name>] vendored snapshot → E030–E034 enforced at the consumer’s rocky compile.

If you are deciding whether Rocky is worth your team’s time, lead with the enforcement plane: branches, content-addressed replay, per-model cost, declarative governance, the dialect-divergence lint (P001), and compile-time contracts. The lint alone is useful the day you start a warehouse migration, and essential the day you finish one.

Rocky being written in Rust matters for speed, and for the existence of a real LSP. It is not the reason to choose it. The reason is that the failure modes above become compile errors and CI gates.

Where Rocky sits next to the adjacent tools

Section titled “Where Rocky sits next to the adjacent tools”

You are probably holding Rocky up against something already in your stack. Here is the honest framing for two of them.

Databricks LakeFlow (head-to-head, with a caveat)

Section titled “Databricks LakeFlow (head-to-head, with a caveat)”

LakeFlow is coupled to the warehouse and comes free with the platform. Rocky differentiates if portability across warehouses matters to you, and if a real compiler with serious tooling matters. If neither does, the warehouse-native option may simply be good enough for your team. That is a legitimate answer.

Polaris and the open table formats (category clarification)

Section titled “Polaris and the open table formats (category clarification)”

This one is a category question, not a head-to-head. Polaris is Snowflake’s Iceberg REST catalog. Iceberg and Delta are open table formats. Rocky is none of those. Rocky targets them. It writes content-addressed Delta and UniForm that an Iceberg-compatible reader can consume. It treats the format and the catalog as the substrate it sits above.


Rocky is the typed graph between your code and whichever warehouse, table format, or query engine you’ve chosen.