Administration Commands
These commands read what already happened and maintain what Rocky keeps. They cover run history, quality metrics and cost; storage compaction, profiling and archival; the state store and the scheduler; and the governance rollups.
rocky history
Section titled “rocky history”Show past pipeline runs from the embedded state store. Each run reports its duration, its status, and the detail for every model it touched.
rocky history [flags]| Flag | Type | Default | Description |
|---|---|---|---|
--model <NAME> |
string |
Show one model’s executions as a flat list. | |
--recipe <HASH> |
string |
Show every recorded execution of one exact program, by its recipe hash. Read the hash from any model record’s recipe_identity in history, trace or catalog JSON. Conflicts with --model. |
|
--since <DATE> |
string |
Only show runs since this date (ISO 8601 or YYYY-MM-DD). |
|
--rolling-stats |
bool |
false |
Add the mean, standard deviation, z-score and health score over the most recent successful executions. Requires --model. |
--window <N> |
integer |
20 |
Number of successful executions for --rolling-stats. |
--audit |
bool |
false |
Include the governance audit trail for each run in JSON output, and print a second governance table after the default summary in text output. See Audit trail below. |
--run <RUN_ID> |
string |
Show exactly one run by its id. The output has the same shape as the list, with count set to 1, so a consumer reads one shape either way. A run the store does not hold is an error naming the id, not an empty list. Conflicts with --model, --recipe and --since; composes with --audit. |
Examples
Section titled “Examples”Show all recent run history:
rocky history{ "version": "1.6.0", "command": "history", "count": 2, "runs": [ { "run_id": "run_20260401_143022", "started_at": "2026-04-01T14:30:22Z", "duration_ms": 45200, "status": "Success", "trigger": "Manual", "models_executed": 14, "models": [ { "model_name": "stg_orders", "duration_ms": 1200, "rows_affected": 150000, "status": "success" }, { "model_name": "fct_revenue", "duration_ms": 2300, "rows_affected": 8900, "status": "success" } ] }, { "run_id": "run_20260401_080015", "started_at": "2026-04-01T08:00:15Z", "duration_ms": 52100, "status": "PartialFailure", "trigger": "Manual", "models_executed": 13, "models": [ /* per-model records */ ] } ]}A run made with rocky run --branch <name> also carries rocky_branch, the literal <name>. Rocky leaves the field out of any other run. It is not an audit field, so it appears with or without --audit. rocky preview diff and rocky preview cost find a branch’s run by it. It differs from git_branch, the git branch you had checked out, which appears only with --audit.
A run can start without leaving a record. It may still be running, it may have crashed, or its record write may have failed. When the ledger holds evidence of such a run, the document adds unrecorded_runs. A replication run leaves a checkpoint header. Every other kind of run leaves a run_started marker. These runs are not counted in count. When the list is present, runs is not the complete history. Rocky leaves the field out when it is empty, and when the MCP history tool filters by trigger.
"unrecorded_runs": [ { "run_id": "run_20260401_120000", "started_at": "2026-04-01T12:00:00Z", "pipeline": "orders", "evidence": "checkpoint", "total_tables": 12, "superseded": false }]Show one run by id. The document has the list’s shape with count set to 1; add --audit for its governance trail:
rocky history --run run_20260401_143022 --audit --output jsonShow history for a specific model since a date. The --model variant returns a flat list of that model’s executions:
rocky history --model fct_revenue --since 2026-03-01{ "version": "1.6.0", "command": "history", "model": "fct_revenue", "count": 2, "executions": [ { "started_at": "2026-04-01T14:30:22Z", "duration_ms": 2300, "rows_affected": 8900, "status": "success", "sql_hash": "a3f2b1c4..." }, { "started_at": "2026-03-31T14:30:05Z", "duration_ms": 8900, "rows_affected": 150000, "status": "success", "sql_hash": "b4e3c2d5..." } ]}sql_hash is stable across runs where the compiled SQL is identical; useful for detecting model body changes across runs.
Show history with table output:
rocky -o table history --since 2026-04-01RUN ID STARTED STATUS MODELS TRIGGER------------------------------------------------------------------run_2026040 2026-04-01 14:30:22 Success 14 Manualrun_2026040 2026-04-01 08:00:15 Failure 13 ManualThe RUN ID column is truncated to 11 characters and the timestamp is rendered without the T/Z separators.
Audit trail
Section titled “Audit trail”--audit adds an eight-field governance trail to each run record. Every rocky apply and rocky run records it. The default output leaves these fields out.
| Field | Description |
|---|---|
triggering_identity |
Principal that initiated the run (OS user, CI service account, etc.). |
session_source |
One of cli, dagster, lsp, http_api, auto-detected at run start. |
git_commit |
Commit SHA at the project root (None when not a git repo). |
git_branch |
Branch name at the project root (None when not a git repo). |
idempotency_key |
Echoed value of --idempotency-key (None when the flag wasn’t used). |
target_catalog |
Resolved target catalog for the executed pipeline. |
hostname |
Hostname where the run executed. "unknown" on rows written before the audit trail existed. |
rocky_version |
The engine version at run time. "<pre-audit>" on rows written before the audit trail existed. |
rocky history --audit{ "version": "1.16.0", "command": "history", "count": 1, "runs": [ { "run_id": "run_20260423_143022", "started_at": "2026-04-23T14:30:22Z", "duration_ms": 45200, "status": "Success", "trigger": "Manual", "models_executed": 14, "models": [ /* per-model records */ ], "triggering_identity": "alice@acme.io", "session_source": "dagster", "git_commit": "a3f2b1c4...", "git_branch": "main", "idempotency_key": "nightly-2026-04-23", "target_catalog": "acme_warehouse", "hostname": "runner-12", "rocky_version": "1.16.0" } ]}Text output appends a Governance audit trail (--audit): section after the default run summary with short-form columns (git_commit truncated to 8 chars, hostname to 11) plus a per-run detail line carrying rocky_version and idempotency_key.
Related Commands
Section titled “Related Commands”rocky apply– execute a planned pipeline (rocky runcontinues to work as an alias)rocky state– view current watermarksrocky metrics– view quality metrics for a model
rocky metrics
Section titled “rocky metrics”Show quality metrics for a model, including row counts, null rates, freshness, and trend data across recent runs.
rocky metrics <model> [flags]Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
model |
string |
(required) | Model name to show metrics for. |
| Flag | Type | Default | Description |
|---|---|---|---|
--trend |
bool |
false |
Show metric trends over recent runs. |
--column <NAME> |
string |
Filter to a specific column. | |
--alerts |
bool |
false |
Show quality alerts (anomalies, threshold breaches). |
Examples
Section titled “Examples”Show metrics for a model. Output carries one snapshot per recent run with row count, freshness lag, and per-column null rates:
rocky metrics fct_revenue{ "version": "1.6.0", "command": "metrics", "model": "fct_revenue", "count": 1, "snapshots": [ { "run_id": "run_20260401_143022", "timestamp": "2026-04-01T14:30:22Z", "row_count": 148203, "freshness_lag_seconds": 300, "null_rates": { "customer_id": 0.0, "revenue_month": 0.0, "net_revenue": 0.0 } } ]}--trend keeps the same snapshot shape but returns multiple entries (one per recent run):
rocky metrics fct_revenue --trend{ "version": "1.6.0", "command": "metrics", "model": "fct_revenue", "count": 3, "snapshots": [ { "run_id": "run_20260401_143022", "timestamp": "2026-04-01T14:30:22Z", "row_count": 148203, "null_rates": { /* … */ } }, { "run_id": "run_20260331_143005", "timestamp": "2026-03-31T14:30:05Z", "row_count": 145890, "null_rates": { /* … */ } }, { "run_id": "run_20260330_143010", "timestamp": "2026-03-30T14:30:10Z", "row_count": 143200, "null_rates": { /* … */ } } ]}--alerts adds a non-empty alerts array (omitted otherwise). --column <name> sets the top-level column field and populates column_trend with per-run null-rate points:
rocky metrics fct_revenue --column net_revenue --alerts{ "version": "1.6.0", "command": "metrics", "model": "fct_revenue", "column": "net_revenue", "count": 3, "snapshots": [ /* … */ ], "column_trend": [ { "run_id": "run_20260401_143022", "timestamp": "2026-04-01T14:30:22Z", "null_rate": 0.023 }, { "run_id": "run_20260331_143005", "timestamp": "2026-03-31T14:30:05Z", "null_rate": 0.0 } ], "alerts": [ { "type": "anomaly", "severity": "warning", "run_id": "run_20260401_143022", "column": "net_revenue", "message": "null rate rose from 0.0% to 2.3% vs. 7-run baseline" } ]}Related Commands
Section titled “Related Commands”rocky history– view execution history for the modelrocky apply– execute a planned pipeline to generate fresh metricsrocky optimize– get strategy recommendations based on metrics
rocky optimize
Section titled “rocky optimize”Analyze materialization costs and recommend strategy changes. Reviews each model’s run history, output size, and downstream consumers to suggest whether it should be a view or a table.
rocky optimize [flags]| Flag | Type | Default | Description |
|---|---|---|---|
--model <NAME> |
string |
Filter to a specific model. If omitted, analyzes all models. | |
--models <PATH> |
string |
models |
Models directory. Rocky reads each model’s strategy and its downstream models from it. If the path is not a directory, every model reports current_strategy: "unknown" and gets no recommendation. |
Examples
Section titled “Examples”Analyze all models. Each recommendation includes the current and recommended strategy, a free-text reasoning, and an estimated monthly saving. current_strategy comes from the model’s own configuration, not a guess. A full_refresh model reads table. Every other strategy reads as its own name, such as view, merge, or incremental:
rocky optimize{ "version": "1.74.0", "command": "optimize", "recommendations": [ { "model_name": "stg_events", "current_strategy": "view", "recommended_strategy": "view", "estimated_monthly_savings": 0.0, "reasoning": "insufficient history: 1 runs (need 5)", "compute_cost_per_run": 2.6666666666666673e-6, "storage_cost_per_month": 0.0023, "downstream_references": 1 }, { "model_name": "user_metrics", "current_strategy": "table", "recommended_strategy": "table", "estimated_monthly_savings": 0.0, "reasoning": "insufficient history: 1 runs (need 5)", "compute_cost_per_run": 8.000000000000001e-6, "storage_cost_per_month": 0.0023, "downstream_references": 0 } ], "total_models_analyzed": 2}rocky optimize recommends table or view. A model needs at least min_history_runs recorded runs, which is 5 unless [cost] says otherwise. With fewer, it keeps its current strategy. Prices come from the [cost] block in rocky.toml. Rocky falls back to its built-in rates when the project has no rocky.toml or no [cost] block. A rocky.toml that exists but fails to load stops the command with an error.
current_strategy is "unknown" when Rocky cannot find the model in the compiled project, for example a model seen only in run history:
{ "model_name": "events", "current_strategy": "unknown", "recommended_strategy": "unknown", "estimated_monthly_savings": 0.0, "reasoning": "current strategy is unknown (model not found in the compiled project); no recommendation", "compute_cost_per_run": 0.000010666666666666669, "storage_cost_per_month": 0.0023, "downstream_references": 0}Analyze a single model:
rocky optimize --model dim_customersSame recommendations shape, single entry.
Related Commands
Section titled “Related Commands”rocky metrics– view quality metrics that inform optimizationrocky history– review execution historyrocky compact– optimize storage layout after strategy changes
rocky compact
Section titled “rocky compact”Generate OPTIMIZE and VACUUM SQL for storage compaction on Delta tables. Combines small files, removes old versions, and optionally targets a specific file size.
rocky compact <model> [flags] # generate + persist a compaction planrocky compact --catalog <name> [flags] # every Rocky-managed table in the catalogrocky compact --measure-dedup [flags] # experimental, project-wide scoperocky compact apply <plan-id> [flags] # execute a previously-generated planExactly one scope is required for plan generation: a fully-qualified <model>, --catalog <name>, or --measure-dedup. The three forms are mutually exclusive. rocky compact apply <plan-id> is a separate subcommand that executes a plan persisted by an earlier generate run.
Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
model |
string |
(one scope required) | Target table in catalog.schema.table format. |
| Flag | Type | Default | Description |
|---|---|---|---|
--catalog <NAME> |
string |
Aggregate per-table OPTIMIZE/VACUUM SQL across every Rocky-managed table in the catalog. The managed-table set is resolved from the pipeline config (replication discovery or transformation model files); no warehouse round trip. Mutually exclusive with <model> and --measure-dedup. Errors with the available catalogs listed if no managed tables match. |
|
--target-size <SIZE> |
string |
Target file size (e.g., 256MB, 512MB, 1GB). |
|
--dry-run |
bool |
false |
Show SQL without executing. |
--measure-dedup |
bool |
false |
Experimental. Measure the cross-table dedup ratio across all Rocky-managed tables in the project (Layer 0 storage experiment). Project-wide; does not take a model argument. |
--exclude-columns <COLS> |
string |
Rocky-owned metadata cols | Comma-separated columns to exclude from the “semantic” dedup hash. Defaults to _loaded_by,_loaded_at,_fivetran_synced,_synced_at. Requires --measure-dedup. |
--calibrate-bytes |
bool |
false |
Run byte-level calibration on a sampled subset of tables. Produces a sharper but more expensive second estimate alongside the cheap partition-level one. Requires --measure-dedup. |
--all-tables |
bool |
false |
Scan all warehouse tables instead of only Rocky-managed ones. Requires --measure-dedup. |
Examples
Section titled “Examples”rocky compact generates SQL and persists it as a plan under .rocky/plans/<plan-id>.json; it does not execute against the warehouse. The plan id is printed in text output and returned as plan_id in JSON. Pipe the SQL to your warehouse yourself once you’re happy with it, or execute the persisted plan with rocky compact apply <plan-id>.
Compact a table (dry run):
rocky compact acme_warehouse.staging__us_west__shopify.orders --dry-run{ "version": "1.6.0", "command": "compact", "model": "acme_warehouse.staging__us_west__shopify.orders", "dry_run": true, "target_size_mb": 256, "statements": [ { "purpose": "compact small files", "sql": "OPTIMIZE acme_warehouse.staging__us_west__shopify.orders WHERE true\n -- target file size: 256MB" }, { "purpose": "remove stale data files", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.orders RETAIN 168 HOURS" } ]}With a target file size:
rocky compact acme_warehouse.staging__us_west__shopify.orders --target-size 256MBThe generated OPTIMIZE carries the target file size as a trailing -- target file size: <N>MB comment; target_size_mb echoes the parsed value (e.g. 256 for 256MB). When --target-size is omitted it defaults to 256.
Catalog-scoped compaction
Section titled “Catalog-scoped compaction”--catalog <name> resolves every Rocky-managed table in the named catalog and emits a single envelope keyed by FQN. The flat statements list still carries every SQL statement across all tables in iteration order; consumers that just want to execute the plan don’t need to walk tables.
rocky compact --catalog acme_warehouse --target-size 256MB --dry-run{ "version": "1.20.0", "command": "compact", "catalog": "acme_warehouse", "scope": "catalog", "dry_run": true, "target_size_mb": 256, "statements": [ { "purpose": "compact small files", "sql": "OPTIMIZE acme_warehouse.staging__us_west__shopify.orders WHERE true\n -- target file size: 256MB" }, { "purpose": "remove stale data files", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.orders RETAIN 168 HOURS" }, { "purpose": "compact small files", "sql": "OPTIMIZE acme_warehouse.staging__us_west__shopify.events WHERE true\n -- target file size: 256MB" }, { "purpose": "remove stale data files", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.events RETAIN 168 HOURS" } ], "tables": { "acme_warehouse.staging__us_west__shopify.events": { "statements": [ { "purpose": "compact small files", "sql": "OPTIMIZE acme_warehouse.staging__us_west__shopify.events WHERE true\n -- target file size: 256MB" }, { "purpose": "remove stale data files", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.events RETAIN 168 HOURS" } ] }, "acme_warehouse.staging__us_west__shopify.orders": { "statements": [ { "purpose": "compact small files", "sql": "OPTIMIZE acme_warehouse.staging__us_west__shopify.orders WHERE true\n -- target file size: 256MB" }, { "purpose": "remove stale data files", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.orders RETAIN 168 HOURS" } ] } }, "totals": { "table_count": 2, "statement_count": 4 }}The single-model envelope is byte-stable: catalog, scope, tables, and totals are all skip_serializing_if = "Option::is_none". The catalog identifier is normalized to lowercase to match the managed-table resolver.
Layer 0 dedup measurement (experimental)
Section titled “Layer 0 dedup measurement (experimental)”--measure-dedup runs a project-wide analysis that hashes each row on its semantic columns and reports the fraction of duplicate content across Rocky-managed tables. It is a research tool for Rocky’s Layer 0 storage work; the output is a measurement, not a plan, and no SQL is issued against the target tables.
# Cheap partition-level estimate across all Rocky-managed tablesrocky compact --measure-dedup
# Add a byte-level calibration pass on a sampled subsetrocky compact --measure-dedup --calibrate-bytes
# Include every warehouse table, not just Rocky-managed onesrocky compact --measure-dedup --all-tables
# Customize which metadata columns are excluded from the dedup hashrocky compact --measure-dedup --exclude-columns "_loaded_at,_loaded_by,_synced_at"The command emits a distinct compact-dedup JSON shape with per-table contributions and a project-wide summary. Use --output json in CI to track the ratio over time.
Related Commands
Section titled “Related Commands”rocky profile-storage– analyze storage layout before compactingrocky optimize– get materialization strategy recommendationsrocky archive– archive old partitions before compacting
rocky profile-storage
Section titled “rocky profile-storage”Profile the storage layout of a table and recommend column encodings, partitioning strategies, and file format optimizations.
rocky profile-storage <model>Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
model |
string |
(required) | Target table in catalog.schema.table format. |
Examples
Section titled “Examples”Profile a table. Rocky emits a profile_sql query you can run against the warehouse to gather column cardinalities, plus a per-column recommendations list derived from the schema alone:
rocky profile-storage acme_warehouse.staging__us_west__shopify.orders{ "version": "1.6.0", "command": "profile-storage", "model": "acme_warehouse.staging__us_west__shopify.orders", "profile_sql": "SELECT column_name, approx_count_distinct(...) FROM ... GROUP BY column_name", "recommendations": [ { "column": "status", "data_type": "STRING", "estimated_cardinality": "low (< 100)", "recommended_encoding": "dictionary", "reasoning": "Low-cardinality string — dictionary encoding dramatically shrinks storage and speeds up filtering." }, { "column": "customer_notes", "data_type": "STRING", "estimated_cardinality": "high", "recommended_encoding": "lz4", "reasoning": "High-cardinality free-text — LZ4 gives the best compression without hurting scan latency." } ]}rocky profile-storage is advisory; it does not run the profile SQL for you. Pipe profile_sql into rocky shell (or any SQL client) to collect the actual cardinality numbers.
Related Commands
Section titled “Related Commands”rocky compact– optimize storage based on profiling resultsrocky optimize– materialization strategy recommendationsrocky metrics– quality metrics for the same table
rocky archive
Section titled “rocky archive”Archive old data partitions by moving them to cold storage or deleting them based on an age threshold. Supports dry-run mode for previewing which partitions would be affected.
rocky archive [flags] # generate + persist an archive planrocky archive --catalog <name> [flags] # every Rocky-managed table in the catalogrocky archive apply <plan-id> [flags] # execute a previously-generated plan| Flag | Type | Default | Description |
|---|---|---|---|
--older-than <DURATION> |
string |
(required) | Age threshold. Accepted formats: 90d (days), 6m (months), 1y (years). |
--model <NAME> |
string |
Filter to a specific model. If omitted, archives across all models. Mutually exclusive with --catalog. |
|
--catalog <NAME> |
string |
Aggregate per-table archive SQL across every Rocky-managed table in the named catalog. The managed-table set is resolved from the pipeline config (no warehouse round trip). Mutually exclusive with --model. Errors with the available catalogs listed if no managed tables match. |
|
--dry-run |
bool |
false |
Show SQL without executing. |
Examples
Section titled “Examples”Like compact, archive generates SQL and persists it as a plan under .rocky/plans/<plan-id>.json; it does not execute against the warehouse. It builds a Delta-flavoured DELETE FROM <target> WHERE _fivetran_synced < DATEADD(DAY, -<N>, CURRENT_TIMESTAMP()) plus a trailing VACUUM. Pipe the SQL to your warehouse yourself, or execute the persisted plan with rocky archive apply <plan-id>. The generator is gated to Delta-on-Databricks targets and errors on other dialects.
Preview archival for data older than 90 days:
rocky archive --older-than 90d --dry-runWithout --model or --catalog, the target resolves to a * wildcard placeholder — pass --model <fqn> (below) or --catalog <name> to emit concrete per-table SQL:
{ "version": "1.6.0", "command": "archive", "older_than": "90d", "older_than_days": 90, "dry_run": true, "statements": [ { "purpose": "delete rows older than 90 days", "sql": "DELETE FROM *\nWHERE _fivetran_synced < DATEADD(DAY, -90, CURRENT_TIMESTAMP())" }, { "purpose": "reclaim storage after deletion", "sql": "VACUUM * RETAIN 0 HOURS" } ]}Archive a specific model’s old data. The persisted plan is executed later with rocky archive apply <plan-id>:
rocky archive --older-than 6m --model acme_warehouse.staging__us_west__shopify.eventsSame output shape: model is set when --model filters the run, and <target> in the DELETE/VACUUM becomes the fully-qualified table. older_than_days is the parsed duration (6m → 180), which lets orchestrators compute retention windows without re-parsing the string.
Catalog-scoped archival
Section titled “Catalog-scoped archival”--catalog <name> mirrors rocky compact --catalog: it resolves every Rocky-managed table in the catalog from the pipeline config and aggregates per-table DELETE + VACUUM SQL into a single envelope keyed by FQN. The flat statements list still carries every statement across every table.
rocky archive --older-than 90d --catalog acme_warehouse --dry-run{ "version": "1.20.0", "command": "archive", "catalog": "acme_warehouse", "scope": "catalog", "older_than": "90d", "older_than_days": 90, "dry_run": true, "statements": [ { "purpose": "delete rows older than 90 days", "sql": "DELETE FROM acme_warehouse.staging__us_west__shopify.orders\nWHERE _fivetran_synced < DATEADD(DAY, -90, CURRENT_TIMESTAMP())" }, { "purpose": "reclaim storage after deletion", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.orders RETAIN 0 HOURS" }, { "purpose": "delete rows older than 90 days", "sql": "DELETE FROM acme_warehouse.staging__us_west__shopify.events\nWHERE _fivetran_synced < DATEADD(DAY, -90, CURRENT_TIMESTAMP())" }, { "purpose": "reclaim storage after deletion", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.events RETAIN 0 HOURS" } ], "tables": { "acme_warehouse.staging__us_west__shopify.events": { "statements": [ { "purpose": "delete rows older than 90 days", "sql": "DELETE FROM acme_warehouse.staging__us_west__shopify.events\nWHERE _fivetran_synced < DATEADD(DAY, -90, CURRENT_TIMESTAMP())" }, { "purpose": "reclaim storage after deletion", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.events RETAIN 0 HOURS" } ] }, "acme_warehouse.staging__us_west__shopify.orders": { "statements": [ { "purpose": "delete rows older than 90 days", "sql": "DELETE FROM acme_warehouse.staging__us_west__shopify.orders\nWHERE _fivetran_synced < DATEADD(DAY, -90, CURRENT_TIMESTAMP())" }, { "purpose": "reclaim storage after deletion", "sql": "VACUUM acme_warehouse.staging__us_west__shopify.orders RETAIN 0 HOURS" } ] } }, "totals": { "table_count": 2, "statement_count": 4 }}The single-model envelope is byte-stable: catalog, scope, tables, and totals are absent on the existing rocky archive and rocky archive --model paths.
Related Commands
Section titled “Related Commands”rocky compact– compact remaining data after archivalrocky profile-storage– analyze storage to identify archival candidatesrocky history– review when data was last accessed
rocky replay
Section titled “rocky replay”Inspect, audit, or re-execute a recorded run from the state store. The default view surfaces the per-model SQL hashes, row counts, bytes, and timings captured by RunRecord at execution time: the concrete artefact behind the reproducibility claim. --check runs a read-only replayability audit, and --execute re-runs the recorded recipes and re-derives each output hash — locally on DuckDB, or against the live warehouse with --warehouse.
rocky replay <target> [flags]rocky replay --at <RUN_ID> [flags]Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
target |
string |
A specific run_id, or the literal latest for the most recent run. Give it here or with --at. With neither, the command exits 1 with provide a run id (positional or --at <RUN_ID>), or the literal 'latest'. |
| Flag | Type | Default | Description |
|---|---|---|---|
--at <RUN_ID> |
string |
The run to read, instead of the positional argument. Takes the same values, including latest. When both are given, --at wins. |
|
--model <NAME> |
string |
Filter to a single model within the run. Errors if the model wasn’t executed. | |
--check |
bool |
false |
Read-only replayability audit instead of the inspection view. Classifies each model as replayable or non_replayable from the ledger alone and flags static non-determinism. Executes nothing. |
--execute |
bool |
false |
Re-execute the recorded recipe (reconstructed from provenance, never the working tree) and re-derive the output hash. Runs on an ephemeral in-memory DuckDB engine by default. |
--verify |
bool |
false |
With --execute, compare the re-derived blake3 against the recorded hash and emit a per-model verdict (bit_exact / diverged / non_replayable). Requires --execute. |
--warehouse |
bool |
false |
With --execute, re-run against the live warehouse configured in rocky.toml instead of a local engine. Content-addressed models only. All replay writes land in an isolated hcv2_replay_<run> schema — never the production location of any recorded target — and it is dropped afterwards unless --keep. Requires --execute. |
--keep |
bool |
false |
With --warehouse, keep the isolated replay schema (and the replayed tables) after the run for inspection. Requires --warehouse. |
Examples
Section titled “Examples”Replay the most recent run:
rocky replay latest{ "version": "1.11.0", "command": "replay", "run_id": "run_20260420_143022", "status": "success", "trigger": "manual", "started_at": "2026-04-20T14:30:22Z", "finished_at": "2026-04-20T14:31:07Z", "config_hash": "cfg_a3f2b1c4", "models": [ { "model_name": "stg_orders", "status": "success", "started_at": "2026-04-20T14:30:22Z", "finished_at": "2026-04-20T14:30:23Z", "duration_ms": 1200, "sql_hash": "hash_a3f2b1c4", "rows_affected": 150000, "bytes_scanned": 41943040, "bytes_written": 20971520 }, { "model_name": "fct_revenue", "status": "success", "started_at": "2026-04-20T14:30:24Z", "finished_at": "2026-04-20T14:31:07Z", "duration_ms": 43000, "sql_hash": "hash_b4e3c2d5", "rows_affected": 8900, "bytes_scanned": 20971520, "bytes_written": 4194304 } ]}Filter to a single model within a specific run:
rocky replay run_20260420_143022 --model fct_revenuesql_hash is stable across runs where the compiled SQL is identical, so diffing two replays is a fast way to detect whether a re-run would execute the same statements.
Re-execution
Section titled “Re-execution”--execute reconstructs each model’s recipe from its recorded provenance — the canonical ModelIr embedded at run time, never the current working tree — and re-runs it, re-deriving the output artifact’s blake3. With --verify, that digest is compared against the recorded hash:
rocky replay latest --execute --verify{ "command": "replay --execute --verify", "run_id": "run_20260420_143022", "verified": true, "model_count": 2, "bit_exact_count": 2, "models": [ { "model_name": "stg_orders", "verdict": "bit_exact", "rows": 150000 }, { "model_name": "fct_revenue", "verdict": "bit_exact", "rows": 8900 } ]}Each per-model verdict is a classification, not a tool failure — bit_exact, diverged, executed (without --verify), or non_replayable — so the command exits 0 and you read the verdict. A model that reads a mutable source is classified non_replayable rather than silently re-run against current data; a model whose recipe contains a non-deterministic construct (now(), random()) is flagged and a diverged there is expected.
By default re-execution runs on an ephemeral in-memory DuckDB engine. --warehouse re-runs against the live warehouse in rocky.toml instead, for content-addressed models whose artifacts live in the lakehouse:
rocky replay latest --execute --verify --warehouseThe warehouse path re-derives each output’s blake3 using the target table’s own physical column mapping, read from its Delta log. A bit_exact verdict therefore means the warehouse reproduced exactly the bytes the content-addressed writer recorded.
Re-execution never touches production. Rocky materializes each replayed model into a fresh schema and redirects every in-run upstream reference into it:
production replay schema hcv2_replay_<run> ────────── ───────────────────────────────── prod.stg_orders stg_orders ◄── re-run from recipe (never written) │ │ the downstream model reads │ the replayed upstream here, ▼ not production prod.fct_revenue fct_revenue ◄── re-run from recipe (never written)
the whole replay schema is dropped after the run, unless --keepThree rules make that isolation hold:
- Rocky materializes every replayed model into a fresh
hcv2_replay_<run>schema, never into the production location of any recorded target. That schema is dropped after the run unless you pass--keep. - Rocky writes no object-store objects. It hashes the recomputed artifact in memory and never touches an existing content-addressed file.
- A model whose upstream the run did not itself produce is classified
non_replayable, as is a model that reads a mutable source. Rocky reports the classification rather than falling back to production data.
Related Commands
Section titled “Related Commands”rocky trace– same data rendered as a Gantt timelinerocky history– list past runsrocky apply– record a new run
rocky trace
Section titled “rocky trace”Render a completed run as a Gantt-style timeline. Sibling to rocky replay: it reads the same RunRecord, but lays out models by start offset and assigns them to concurrency lanes so overlapping models show up on separate rows.
rocky trace <target> [flags]Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
target |
string |
(required) | A specific run_id, or the literal latest. |
| Flag | Type | Default | Description |
|---|---|---|---|
--model <NAME> |
string |
Filter to a single model within the run. |
Examples
Section titled “Examples”Trace the most recent run:
rocky trace latest{ "version": "1.11.0", "command": "trace", "run_id": "run_20260420_143022", "status": "success", "trigger": "manual", "started_at": "2026-04-20T14:30:22Z", "finished_at": "2026-04-20T14:31:07Z", "run_duration_ms": 45000, "lane_count": 2, "models": [ { "model_name": "stg_orders", "status": "success", "start_offset_ms": 0, "duration_ms": 1200, "sql_hash": "hash_a3f2b1c4", "lane": 0, "rows_affected": 150000, "bytes_scanned": 41943040, "bytes_written": 20971520 }, { "model_name": "stg_customers", "status": "success", "start_offset_ms": 100, "duration_ms": 900, "sql_hash": "hash_x9y8z7w6", "lane": 1, "rows_affected": 8000, "bytes_scanned": 2097152, "bytes_written": 1048576 }, { "model_name": "fct_revenue", "status": "success", "start_offset_ms": 2000, "duration_ms": 43000, "sql_hash": "hash_b4e3c2d5", "lane": 0, "rows_affected": 8900, "bytes_scanned": 20971520, "bytes_written": 4194304 } ]}Lane assignment is greedy first-fit over sorted start offsets, so lane_count is the observed maximum concurrency. The table-mode output prints a bar chart:
$ rocky -o table trace latestrun: run_20260420_143022status: success trigger: manual duration: 45.00sparallelism: 2 lanes
model timeline duration status stg_orders [###.....................................] 1.20s success stg_customers [##......................................] 0.90s success fct_revenue [....####################################] 43.00s successRelated Commands
Section titled “Related Commands”rocky replay– flat per-model dump of the same runrocky cost– per-model cost rollup for the same runrocky history– list recent runsrocky metrics– quality metrics captured during runs
rocky cost
Section titled “rocky cost”Historical cost allocation for a completed run. rocky cost reads the same
RunRecord as rocky replay and
rocky trace. It recomputes per-model dollar values from the
recorded metrics and the current configuration’s selected adapter and formula.
The command does not contact the warehouse or read an invoice. Its dollar
values can change when you change the configured rates or selected adapter. In
the output, cost_usd is an allocation. It is not a billed amount.
When a model lacks the metrics for its formula, its dollar value is null.
The total includes only models with a dollar value. It is not a complete bill.
rocky cost <target> [flags]Arguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
target |
string |
(required) | A specific run_id, or the literal latest for the most recent run. |
| Flag | Type | Default | Description |
|---|---|---|---|
--model <NAME> |
string |
Filter to a single model within the run. | |
--by <DIMENSION> |
tenant | model |
Roll the per-model cost up by a dimension and add a groups array to the output. tenant reads the discover-time schema-pattern {tenant} component. per_model is present either way. Any other value errors. |
--output is a global flag. Its default depends on stdout: table on an interactive terminal, json otherwise. Pass --output json or --output table to force one.
Examples
Section titled “Examples”Roll up cost for the most recent run:
rocky cost latestThe dollar values in this example are illustrative allocations. They are not invoice amounts.
{ "version": "1.11.0", "command": "cost", "run_id": "run-20260421-142430-017", "status": "success", "trigger": "manual", "started_at": "2026-04-21T14:24:29.881087+00:00", "finished_at": "2026-04-21T14:24:30.031036+00:00", "duration_ms": 149, "adapter_type": "databricks", "total_cost_usd": 0.101, "total_duration_ms": 910000, "total_bytes_scanned": 104857600, "total_bytes_written": 20971520, "per_model": [ { "model_name": "stg_orders", "status": "success", "duration_ms": 310000, "rows_affected": 150000, "bytes_scanned": 41943040, "bytes_written": 10485760, "cost_usd": 0.034 }, { "model_name": "fct_revenue", "status": "success", "duration_ms": 600000, "rows_affected": 8900, "bytes_scanned": 62914560, "bytes_written": 10485760, "cost_usd": 0.067 } ]}Human-readable table form:
rocky cost latest --output tablerun: run-20260421-142430-017status: success adapter: databricks total: $0.101
model duration rows bytes_scanned cost stg_orders 5m 10s 150,000 40 MB $0.034 fct_revenue 10m 0s 8,900 60 MB $0.067Adapter coverage
Section titled “Adapter coverage”- Databricks / Snowflake: derived from recorded duration, the configured warehouse size, and
[cost].compute_cost_per_dbu. The adapter does not report a billed amount. An omitted[cost]block uses Rocky’s defaults. - BigQuery: the compiled bytes formula runs only when the saved run has
bytes_scanned. Otherwisecost_usdisnull. - DuckDB / local: the cost model reports
$0.00. This excludes machine and infrastructure costs. DuckDBEXPLAINsupplies no numeric byte, row, or compute estimate. - Discovery adapters (Fivetran, Airbyte, etc.): skipped; cost is
None.
If the configuration file is missing, the command keeps recorded duration and
bytes but leaves cost_usd as null. A discovery-only adapter also has no
dollar allocation. A configured DuckDB adapter reports the local $0.00
model. An unreadable configuration is an error. Missing BigQuery bytes is
null, not zero.
Related Commands
Section titled “Related Commands”rocky replay– sameRunRecord, shown as a per-model execution dumprocky trace– sameRunRecord, shown as a Gantt-style timelinerocky history– list recent runs to find arun_id[budget]– run-level budget that firesbudget_breachevents during the run itself
rocky state
Section titled “rocky state”Inspect or manage the embedded state store. rocky state is a subcommand group. The bare form still shows watermarks, for backwards compatibility.
rocky state # show watermarks (default)rocky state show # same as bare `rocky state`rocky state reconcile-watermark --pipeline <name> [--table <target>]... [--dry-run]rocky state clear-schema-cache [--dry-run] # flush the DESCRIBE cacherocky state retention sweep [--dry-run] # trim the history tablesrocky state schedule pause <pipeline> # hold a pipeline's schedulerocky state schedule resume <pipeline> # release the holdrocky state schedule spool # list queued webhook demandsSubcommands
Section titled “Subcommands”| Subcommand | Description |
|---|---|
show (default) |
Display stored watermarks. Same output as bare rocky state; the named form is provided so scripts can be explicit. |
reconcile-watermark |
Set incremental cursors from physical target tables. See rocky state reconcile-watermark. |
clear-schema-cache |
Flush the DESCRIBE TABLE schema cache. See rocky state clear-schema-cache. |
retention sweep |
Delete history rows that fall outside [state.retention]. See rocky state retention sweep. |
schedule pause / schedule resume |
Hold or release one pipeline’s schedule at runtime. See rocky state schedule. |
schedule spool |
List webhook demands accepted but not yet run. See rocky state schedule. |
State-path resolution
Section titled “State-path resolution”When --state-path is omitted, Rocky resolves the state file via rocky_core::state::resolve_state_path:
<models>/.rocky-state.redb: canonical location for new projects; matches the LSP convention so inlay hints observe the same filerocky applywrites.- Legacy CWD
.rocky-state.redb: still works; emits a one-time deprecation warning on stderr. - Both present: CWD wins (preserves existing watermarks, branches, and partition bookkeeping); a louder warning asks you to reconcile. Merge is lossy, so delete one copy to silence the warning.
- Neither present: fresh project lands on
<models>/.rocky-state.redbwhen amodels/directory exists; otherwise it falls back to CWD (keeps replication-only pipelines working without inventing amodels/directory just to hold state).
Explicit --state-path <PATH> always wins; no resolver logic is applied.
Related Commands
Section titled “Related Commands”rocky state clear-schema-cache– flush the DESCRIBE cacherocky state reconcile-watermark– repair incremental cursorsrocky history– read persisted run recordsrocky replay/rocky trace/rocky cost– read the sameRunRecordthe state store persists
rocky state reconcile-watermark
Section titled “rocky state reconcile-watermark”Repair a replication pipeline’s incremental cursors from its target tables. Rocky reads each target’s effective timestamp column from the pipeline and its table overrides. It then reads the target’s maximum timestamp. An empty target has no cursor, so the next incremental run replaces it. Rocky updates all selected cursors in one state transaction. The remote state upload uses the same authority and CAS rules as a run.
rocky state reconcile-watermark --pipeline bronze --dry-runrocky state reconcile-watermark --pipeline bronze --table wh.raw.orders| Flag | Type | Default | Description |
|---|---|---|---|
--pipeline <NAME> |
string |
required | Name the replication pipeline. |
--table <TARGET> |
string (repeatable) |
all recorded targets | Select physical catalog.schema.table targets. Repeat the flag for more targets. |
--dry-run |
bool |
false |
Show the values without saving new watermarks. |
Stop earlier warehouse writes before repair. Use --dry-run to inspect the values.
If a recorded plan includes a removed target, select the current targets with --table.
The command refuses an unpinned remote catalog.
It also refuses a connector-specific override when it cannot identify the target’s source connector.
Related Commands
Section titled “Related Commands”- Interrupted replication – choose the recovery route
rocky state clear-schema-cache
Section titled “rocky state clear-schema-cache”Flush the DESCRIBE TABLE schema cache. Complement to the TTL-driven eviction on the read path; use this when the project needs a fresh typecheck now (e.g., after a manual warehouse DDL change, before a strict-CI run, while debugging a suspected stale-cache mismatch).
rocky state clear-schema-cacherocky state clear-schema-cache --dry-run| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run |
bool |
false |
Report how many entries would be removed without touching the store. Useful for automation that asserts emptiness before a scheduled flush. |
Behavior
Section titled “Behavior”- Removes every row from the
SCHEMA_CACHEredb table. The cache is cheap to rebuild; the nextrocky apply(write tap) orrocky discover --with-schemaswarms it back up. - No prompt. Entries are disposable; explicit opt-in by running the command is sufficient.
- Missing state store is a no-op. A fresh CI runner with no
.rocky-state.redbyet exits zero withentries_deleted: 0. This keeps “flush before build” safe to run unconditionally on ephemeral runners. - Uninitialised cache tables return
entries_deleted: 0without touching redb. - JSON output is
ClearSchemaCacheOutput(entries_deleted,dry_run).
Example
Section titled “Example”rocky state clear-schema-cache --dry-run --output json{ "version": "1.16.0", "command": "state-clear-schema-cache", "entries_deleted": 12, "dry_run": true}Related Commands
Section titled “Related Commands”rocky discover --with-schemas– warm the cache after a flushrocky --cache-ttl– per-invocation TTL override (use--cache-ttl 0for one-shot bypass without flushing)[cache.schemas]– disable the cache entirely withenabled = false
rocky state retention sweep
Section titled “rocky state retention sweep”Delete the history rows that fall outside [state.retention] in rocky.toml. The sweep covers three domains: run history, DAG snapshots, and quality snapshots.
rocky state retention sweeprocky state retention sweep --dry-run| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run |
bool |
false |
Plan the sweep and delete nothing. Reports the same counts a real sweep would produce, give or take concurrent writers. |
What the sweep never touches
Section titled “What the sweep never touches”The sweep only trims history. Two guardrails bound it:
- Operational tables are out of scope: the schema cache, the watermarks, and the partition records are never swept.
- The most recent
min_runs_keptrows in each domain survive unconditionally, whatever the age policy says.
Related Commands
Section titled “Related Commands”rocky retention-status– the data-retention policy on warehouse tables, a separate surface from the state store’s historyrocky history– the run history the sweep trims[state.retention]– the policy the sweep reads
rocky state schedule
Section titled “rocky state schedule”Hold or release one pipeline’s schedule at runtime, or list the webhook demands waiting to run. A hold is durable: it lives in the state store, not in rocky.toml.
rocky state schedule pause <pipeline>rocky state schedule resume <pipeline>rocky state schedule spoolArguments
Section titled “Arguments”| Argument | Type | Default | Description |
|---|---|---|---|
pipeline |
string |
(required for pause and resume) |
The pipeline whose schedule to pause or resume. spool takes no argument – it lists every queued demand. |
No command-specific flags. Uses the global flags only.
Which scheduler a hold reaches
Section titled “Which scheduler a hold reaches”A hold controls whichever scheduler reads the same state file this command writes. Point both at the same --state-path or they will not see each other.
rocky --state-path X state schedule pause <pipeline> │ ▼ writes the hold ┌────────────────────────────┐ │ state file X │ └────────────┬───────────────┘ │ read on every tick ▼ ┌────────────────────────────┐ suppresses the cron, │ rocky --state-path X │ after, freshness, and │ serve --scheduler │ webhook demand sources, └────────────────────────────┘ and records a `paused` skip on each tickA pause reaches a running scheduler immediately. Editing [schedule] enabled in rocky.toml does not: a resident scheduler cannot see that change until it restarts. The command’s output names the state file it acted on, so pausing the wrong instance is never silent.
resume re-enables autonomous runs, so it is deliberately human-only. Agents get the pause_schedule MCP tool and no matching resume.
Queued webhook demands (spool)
Section titled “Queued webhook demands (spool)”rocky state schedule spool lists the webhook demands the ingress accepted and no tick has consumed yet. Read it when a webhook was delivered and its pipeline never ran.
GET /api/v1/schedule cannot answer that question. It reports claims, and a claim exists only once a tick has picked a demand up, so a demand still sitting in the queue appears nowhere in it. (That route is API-only; there is no rocky state schedule status verb.)
POST /api/v1/hooks/trigger/<pipeline> │ ▼ accepted, written to .rocky/pending-demands ┌──────────────────────────┐ │ the spool ────────────────▶ rocky state schedule spool └────────────┬─────────────┘ (this command: what is WAITING) │ a tick claims it ▼ ┌──────────────────────────┐ │ a claim ──────────────────▶ GET /api/v1/schedule └──────────────────────────┘ (what is IN FLIGHT)Consumed demands are excluded: a .done tombstone is the 24-hour duplicate-suppression window for kind = id, not outstanding work.
An entry the command cannot read is reported under skipped with a reason, never dropped – it is still a demand blocking the queue. counts.corrupt reports files a previous tick quarantined.
A spool directory that is present but unreadable is an error, not an empty list. “Nothing is queued” and “we cannot tell” must never look the same: that conflation once let every wrapper see a healthy tick while webhook demand was silently not firing. A spool that is simply absent is fine and reports an empty queue – no webhook has ever been accepted for that project.
token is the duplicate-suppression key. For kind = id it is caller-supplied text, taken verbatim from the delivery header and never interpreted by Rocky, so anything rendering it must treat it as inert text. It is listed because it is what you match against your provider’s own delivery log.
rocky state schedule spool --output tableWebhook spool: /srv/analytics/.rocky/pending-demands
4f1c... orders id token 8a72-delivery received 2026-09-10T10:00:00+00:00Related Commands
Section titled “Related Commands”rocky serve– the resident process a hold controls, and the webhook ingress that fills the spoolrocky mcp– the agent-facingpause_scheduletool
rocky tick
Section titled “rocky tick”Experimental. Evaluate the schedule demand once and run what is due. Rocky reads each [pipeline.<name>.schedule] block (cron, after, freshness), works out what is due now, and runs it, one rocky run child process at a time. There is no daemon. Drive it from an external timer such as a systemd timer, cron or CI.
rocky tick [flags]| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run |
bool |
false |
Report what would run. Start no pipeline and advance no scheduler cursor. It still opens the state store read-write. See Preview before you wire the timer. |
--pipeline <NAME> |
string |
(every scheduled pipeline) | Evaluate one pipeline only. |
--now <RFC3339> |
string |
the wall clock | Evaluate demand as of this instant, for tests and catch-up previews. It moves scheduling decisions only. It does not age files on disk, so it cannot expire the webhook dedup window. |
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
Nothing was due, or every run succeeded. Also when another rocky process holds the state store: the tick skips with a state_busy entry in skipped[], and the next tick retries. |
2 |
At least one run failed or was partial. |
1 |
The tick could not proceed, for example an invalid config or a state store that does not open. |
Exit 0 does not mean the estate is healthy. A pipeline in failure_backoff produces quiet exit-0 ticks. Alert on the skipped reasons and on consecutive_failures in the JSON, not on the exit code alone.
rocky serve --scheduler runs the same loop in process. Run one scheduler per project directory. rocky doctor --check scheduler reports a stuck or silent scheduler.
Examples
Section titled “Examples”rocky tick --dry-run --output jsonRelated Commands
Section titled “Related Commands”rocky state schedule– pause or resume a pipeline’s schedulerocky serve– the resident scheduler (--scheduler)
rocky compliance
Section titled “rocky compliance”Governance rollup over classification sidecars plus the project [mask] policy. Static resolver: answers “are all classified columns masked wherever policy says they should be?” without issuing a single warehouse call.
rocky compliance [--env NAME] [--exceptions-only] [--fail-on exception]| Flag | Type | Default | Description |
|---|---|---|---|
--env <NAME> |
string |
Scope the report to a single environment. When unset, the report expands across the defaults plus every [mask.<env>] override block declared in rocky.toml. A named env that has no matching [mask.<env>] block still reports under that label; the resolver falls back to the [mask] defaults. |
|
--exceptions-only |
bool |
false |
Filter per_column to rows that produced at least one exception. The exceptions list is unaffected; allow-listed tags are suppressed from per_column under this flag. |
--fail-on <CONDITION> |
exception |
Gate condition. The only value is exception: exit 1 when any exception is emitted. Use it as a CI gate that blocks a merge that leaves a classified column unmasked. |
|
--models <PATH> |
string |
models |
Models directory to scan for [classification] sidecars. |
Behavior
Section titled “Behavior”- Loads
rocky.tomland every model sidecar with a non-empty[classification]block. Each(model, column, env)triple is evaluated against the resolved masking strategy. - A project with no
models/directory yet is an empty report. Amodelspath that is there but cannot be read — a dangling symlink, or one under a broken ancestor — is an error that names the path, never an empty report: an empty answer would say the estate has no classified columns when it could not be read at all. One gap: on Windows, an ancestor directory that denies search can read as absent when the OS reports the denied lookup as not-found, because the probe that tells the two apart does not work there. MaskStrategy::None(“explicit identity”) counts as masked: the project has deliberately opted out, which is a conscious policy decision, not an enforcement gap.- Tags listed on
[classifications] allow_unmaskedsuppress exception emission but still reportenforced = falsein the per-column breakdown; the allow list doesn’t pretend the column is masked. - Exit
1with--fail-on exceptionwhen any exception is emitted; otherwise exit0regardless of exception count (the JSON payload still reports them). - Static rollup, no warehouse calls. Fast enough to run in every PR.
- JSON output is
ComplianceOutput(summary,per_column,exceptions).
Example
Section titled “Example”rocky compliance --env prod --fail-on exception{ "version": "1.16.0", "command": "compliance", "summary": { "total_classified": 5, "total_masked": 4, "total_exceptions": 1 }, "per_column": [ { "model": "users", "column": "email", "classification": "pii", "envs": [ { "env": "prod", "masking_strategy": "hash", "enforced": true } ] }, { "model": "users", "column": "ssn", "classification": "confidential", "envs": [ { "env": "prod", "masking_strategy": "unresolved", "enforced": false } ] } ], "exceptions": [ { "model": "users", "column": "ssn", "env": "prod", "reason": "no masking strategy resolves for classification tag 'confidential'" } ]}Related Commands
Section titled “Related Commands”rocky apply– applies classification tags + masking policies inline during the post-DAG governance passrocky retention-status– sibling governance rollup for retention declarations- Governance configuration –
[mask],[mask.<env>],[classifications] allow_unmasked
rocky retention-status
Section titled “rocky retention-status”Report each model’s declared data-retention policy. Walks the compiled model set and emits one row per model with its declared retention = "<N>[dy]" value (or null when unset).
rocky retention-status [--model NAME] [--drift]| Flag | Type | Default | Description |
|---|---|---|---|
--model <NAME> |
string |
Scope the report to a single model by name. | |
--drift |
bool |
false |
Keep only the models that declare a policy, and probe the warehouse for the value it currently applies. |
--models <PATH> |
string |
models |
Models directory. |
Behavior
Section titled “Behavior”- Compiles the project so each model’s resolved
retentionsidecar value surfaces as a typedOption<RetentionPolicy>. configured_daysisNonewhen the model’s sidecar carries noretentionkey.warehouse_daysisNoneunless--driftprobed a warehouse that reports a value.in_syncistrueiffconfigured_days == warehouse_days. Without--drift, unconfigured models collapse toin_sync = true(both sides areNone).- The
--driftprobe reads Deltadelta.logRetentionDuration+delta.deletedFileRetentionDurationon Databricks, andDATA_RETENTION_TIME_IN_DAYSon Snowflake. BigQuery and DuckDB have no probe and reportwarehouse_days = null. - A probe failure prints a warning on stderr naming the model and its adapter. The command continues with
warehouse_days = nullrather than aborting. - JSON output is
RetentionStatusOutput(a flatmodelsarray ofModelRetentionStatus).
A selector that matches nothing fails
Section titled “A selector that matches nothing fails”rocky retention-status --model <NAME> exits 1 when no model carries that name. Stderr reads model '<NAME>' not found (no transformation model with that name). Rocky refuses before it writes any output, so stdout stays empty even under --output json.
--drift keeps only the models that declare a policy, so it can legitimately return nothing. That case exits 0 and the payload gains a message field explaining the empty result. The field is absent whenever models is non-empty.
| Situation | message |
|---|---|
--drift --model <NAME>, and that model declares no policy |
model '<NAME>' declares no retention policy |
--drift with no model selected, and none declares a policy |
no models declare a retention policy |
{ "version": "1.70.1", "command": "retention-status", "models": [], "message": "no models declare a retention policy"}Example
Section titled “Example”rocky retention-status --output json{ "version": "1.16.0", "command": "retention-status", "models": [ { "model": "fct_revenue", "configured_days": 90, "in_sync": true }, { "model": "dim_customers", "in_sync": true }, { "model": "events", "configured_days": 30, "in_sync": true } ]}Text mode is a fixed-width table:
rocky -o table retention-statusMODEL CONFIGURED WAREHOUSE IN SYNC----------------------------------------------------------------------------------fct_revenue 90 days - yesdim_customers - - yesevents 30 days - yesRelated Commands
Section titled “Related Commands”rocky compliance– sibling governance rollup for classification + maskingrocky apply– applies retention policies inline during the post-DAG governance pass- Model sidecar
retention– configureretention = "<N>[dy]"