Skip to content

Governance & Reclamation Commands

These commands govern what an agent, or a person, may change. They also record what was decided and reclaim storage.

One rule runs through all of them: the review gate. Every mutating plan on this page needs an approval marker before rocky apply will execute it. rocky review <plan-id> --approve writes that marker. The read-only commands write nothing at all.

For the concepts behind the policy plane and the agent authoring loop, see Operating Rocky with agents.


Inventory Rocky-managed, content-addressed artifacts whose recorded recipe makes them reclamation candidates, and plan their eviction.

Terminal window
# Read-only inventory: what is derivable, and why (or why not)
rocky gc --derivable --dry-run
# Write a review-gated reclamation plan (never deletes directly)
rocky gc --derivable
Flag Type Default Description
--derivable bool false Restrict to the derivability inventory / plan (the only mode today).
--dry-run bool false Emit the read-only inventory instead of writing a plan.
--min-age-days <DAYS> int 7 Minimum written-age an artifact must reach to pass the age check. Measures build time, not read recency.

An artifact is derivable only when all six checks pass.

  1. Its recipe identity was recorded, with a strong input closure, in a shape rocky restore can rebuild: content-addressed, with no partition columns and no recorded upstreams. A partitioned artifact, or one built from other tables (a multi-input recipe), is not evicted yet, because rocky restore cannot rebuild it. gc and restore run the same check, so the two cannot disagree.
  2. The recipe’s provenance records this artifact’s exact output hash. The recipe must be bound to these specific bytes, not to a sibling output and not to a re-materialization at a new hash.
  3. The ledger’s replay-check verdict says the artifact is replayable and deterministic.
  4. Nothing references it.
  5. Policy allows reclamation.
  6. It is past the age threshold.

Every check fails closed. Any doubt keeps the artifact.

A gc plan is unconditionally review-gated: rocky apply <plan-id> refuses it until rocky review <plan-id> --approve records a sign-off, and at apply time every eviction is re-verified against the live ledger. An entry that is no longer derivable (for example, a new reference appeared since plan time) is refused, with the failing checks reported.

A gc plan is gated as an agent whoever applies it, so a deny or require_review rule scoped to agent and gc also applies to an interactive rocky gc. This keeps an unattended gc (a cron job or a daemon with no ROCKY_PRINCIPAL) under the same rules.

Eviction is ledger-only: a durable restore tombstone is written and the ledger row retired in one transaction. No physical byte-delete follows. Reclaiming the bytes safely needs a protocol-aware VACUUM (retention windows plus TOCTOU-safe deletion against concurrent re-adds), which is future work, so [gc] physical_delete = true is a hard error rather than a silent no-op.

Treat eviction as removal with a recorded rebuild path, not as a reversible operation. The rebuild path may not work for every artifact.

rocky restore rebuilds an evicted artifact from the recipe its tombstone references. It refuses unless the recomputed content hash matches the tombstoned one.

Restore covers the same set gc evicts, or more. Restore attempts a rebuild only for a recipe that is non-partitioned, content-addressed, and reads no recorded upstream. A partitioned recipe or one with any recorded upstream is refused outright, because partitioned restore and multi-input DAG re-derivation are later phases. gc calls the same check before it marks an artifact derivable, so it does not evict such an artifact until restore can rebuild it.

Even a supported recipe can refuse. Any of these stops it:

  • a missing provenance binding,
  • canonical IR that will not deserialize,
  • an unreachable object store or table state,
  • a hash that no longer reproduces,
  • a path outside the storage prefix,
  • a lost race on ledger reinstatement.

Re-running the pipeline is not a substitute. A re-run recomputes from the current upstreams and need not reproduce the evicted bytes.

rocky gc applies to the content-addressed write path only.


Write a review-gated plan to rebuild an artifact that rocky gc evicted. Restore never writes bytes directly.

Terminal window
rocky restore <target>
rocky review <plan-id> --approve
rocky apply <plan-id>
Argument Type Default Description
target string (required) The artifact: a model name, model@<recipe-hash-prefix>, or a content-hash prefix of at least 8 hex characters. An ambiguous target is refused, with the matching candidates listed.

The plan is review-gated even for a person. At apply time, Rocky rebuilds the artifact from the recipe its tombstone references. It checks that the recomputed blake3 hash equals the tombstoned hash before any write becomes visible. Then it writes the bytes at the tombstoned path, never over mismatched bytes, and reinstates the ledger row.

Restore rebuilds only a recipe that is content-addressed, not partitioned, and reads no recorded upstream. See What restore can and cannot undo for every case that refuses.


Compose a scoped recovery plan: which models to re-run, in what order, over what partition window, at what estimated cost.

Terminal window
# Recover the window a contained (partial-failure) run left behind
rocky backfill --from-last-run
# Rebuild one model and its downstream closure over a window
rocky backfill --model fct_orders --from 2026-07-01 --to 2026-07-07
Flag Type Default Description
--model <NAME> string (repeatable) A model to rebuild. Its downstream lineage closure is included.
--from-last-run bool false Seed the backfill from the previous run’s failed models. Conflicts with --model.
--from <KEY> / --to <KEY> string Partition-window bounds applied to partitioned models. Each requires the other.
--no-downstream bool false Rebuild only the named/seed models, not their downstream closure.
--models <DIR> path models Models directory to compose the backfill against.

A backfill re-runs existing recipes over a scoped window — it never rewrites SQL. The plan is always review-gated, regardless of policy: a backfill can hide blast radius behind a routine-looking recovery, so it always requires rocky review <plan-id> --approve before rocky apply. Once approved, execution reuses the standard run path (classified retry and failure containment included).


The agent-authority policy plane. A [policy] rule in rocky.toml resolves a (principal, capability, scope) triple to one of three effects: allow, require_review, or deny. Rocky enforces the resolved effect at the mutating seams: rocky apply, promote, and the MCP propose and draft tools. With no [policy] block, behaviour is unchanged.

Terminal window
rocky policy check --principal agent --capability apply --model fct_orders
rocky policy test
rocky policy show
rocky policy freeze --principal agent --scope 'model=fct_*'
rocky policy unfreeze --principal agent --scope 'model=fct_*'
Subcommand What it does
check Explain the effect the policy plane resolves for a (principal, capability, model) triple: the verdict, the winning rule, and the reason. Read-only.
test Run the project’s [[policy.tests]] scenario assertions through the real evaluator; exits non-zero if any resolved effect differs from its expectation, so a policy edit cannot silently open a hole in CI.
show The policy plane as it stands: the rules in file order with their position as id (the matched_rule that check reports), each rule’s scope and its verify_after checks, the default agent effect, and the freezes in force that the read could see. Read-only. A source that exists but cannot be read is an error, never an empty list. freeze_sources says how each source was read. Read it before you treat the freeze list as complete: the two cases below say when it is not.
freeze The kill switch. Records a freeze decision in the decision ledger; at the enforcement seam an active freeze forces deny for the matched (principal, scope). No config file is rewritten, and freezing is always allowed. Omitting --principal freezes both principals; omitting --scope freezes every model.
unfreeze Lift a matching freeze by recording a superseding decision. Pass the same --principal / --scope used to freeze.
Subcommand Flag Type Default Description
check --principal <P> human | agent (required) The principal attempting the action.
check --capability <C> see below (required) The capability attempted.
check --model <NAME> string (required) The target model.
check --models <DIR> path models Models directory to compile for the model’s attributes.
freeze, unfreeze --principal <P> human | agent both The principal to freeze or unfreeze.
freeze, unfreeze --scope <SCOPE> string any any, layer=<n>, model=<glob>, classification=<v> or tag=<key>[=<value>]. unfreeze must match the freeze’s scope.
freeze, unfreeze --reason <TEXT> string a generated description Reason recorded on the ledger row and the durable marker.

The capabilities are read, propose, apply, promote, backfill, gc, restore, retry, quarantine, schema_change.additive, schema_change.breaking and value_change. test and show take no flags.

Policy can only tighten at runtime: freeze and the autonomy-budget degradation move effects toward require_review / deny, never toward allow.

Two things show reports rather than hides.

No [policy] block means nothing is in force. The enforcement gate answers NotConfigured before it reads a freeze source, so a freeze recorded earlier enforces nothing until a [policy] block exists. show reads no source either and reports both as not_consulted, so it never lists a freeze the engine would not honour.

On a remote [state] backend the ledger is read from the local mirror. A governed apply downloads the authoritative remote ledger before it gates. show is read-only and that download replaces the local ledger file, so show does not do it. It reports freeze_sources.ledger as local_mirror to say so: a freeze recorded by another pod can be missing from the list while an apply still denies on it. A command that downloads remote state first, such as rocky run or a governed apply, refreshes the local mirror; there is no separate command that only pulls it.

Durable freeze markers are read wherever the [state] backend has an object tier, whatever freeze_marker_writes says. That flag gates writes only, and a marker written while it was on stays enforced after it is turned off.

rocky serve answers rocky policy show --output json byte for byte at GET /api/v1/policy.


The governor’s estate digest: what happened over the window and what needs a human.

Terminal window
rocky brief # since the last digest (advances the stored cursor)
rocky brief --since 24h
rocky brief --since 7d --output json
Flag Type Default Description
--since <WINDOW> last | 24h | 7d last The window. last reads since the previous --since last digest and advances that stored cursor.

Read-only, apart from that cursor. Rocky composes the brief from typed queries over the state store and the decision ledger. The digest covers:

  • decisions awaiting review, ranked;
  • agent activity by principal. The counts are policy evaluations only. Freeze, unfreeze, and post-apply verification rows are listed with their kind but not counted, so a freeze never shows as a deny;
  • runs, drift, freshness, quality, and cost;
  • the resident scheduler’s posture: paused pipelines, consecutive-failure streaks, scheduler-spawned runs in the window, and the incident-bundle spool.

Every event line cites a run_id, a plan_id, or a decision_ref. The scheduler’s posture lines carry pipeline names and counts. Its incident line carries the newest bundle’s project-relative path.

A section whose signal is not recorded reports unavailable rather than a false all-clear. The default output is a Markdown digest ready to paste into Slack or an email. --output json is the machine surface.

rocky serve answers the same JSON at GET /api/v1/brief?since=last|24h|7d, except generated_at and since_timestamp. The route never advances the cursor, so since=last reads it as it stands, and the route’s default is 7d.


The policy-decision ledger and the custody chain behind any subject.

Terminal window
rocky audit # every recorded policy decision, oldest first
rocky audit --for fct_orders # custody chain for a table, run_id, or plan_id
rocky audit --scorecard --by principal --window 30d
rocky audit --actor alice --since 2026-10-01 # what one actor did since a date
rocky audit --actor unrecorded # rows written before principal ids existed
Flag Type Default Description
--for <SUBJECT> string Drill into the custody chain for a model/table name, a run_id, or a plan_id: who proposed, what policy decided, what the plan changed, which runs materialized it, what verification found, and the downstream blast radius.
--scorecard bool false Aggregate the ledger into acceptance / denial / escalation rates instead of listing decisions.
--by <DIM> principal | rule | scope principal Scorecard grouping dimension. Requires --scorecard.
--window <W> string all Scorecard window, e.g. 30d or 12h. Requires --scorecard.
--product <NAME> string List only the decisions about one product: the rows whose model is the product’s output model (product.output.model, default the product name), read from products/<NAME>.toml. Conflicts with --for and --scorecard.
--actor <ID> string List only the decisions one actor made: the rows whose principal id is <ID>. unrecorded lists the rows with no id. Composes with --product and --since. Conflicts with --for and --scorecard. Reads no environment variable.
--since <WHEN> string List only the decisions recorded at or after <WHEN>. Accepts YYYY-MM-DD (00:00 UTC), an RFC 3339 timestamp with an offset (Rocky converts it to UTC), or a <N>d / <N>h duration back from now. A future time lists nothing. Conflicts with --for and --scorecard.
--models <DIR> path models Models directory used to compute the downstream blast radius for --for.

Read-only. Only mutating enforcement seams record decisions — reads are never logged — so the ledger is the audit trail of governed mutations. A signal the ledger does not persist is reported as not recorded rather than inferred, and the scorecard is wired to no automatic policy change.

Each decision records two things about the actor. The class (human or agent) is what the policy gates enforce. The principal id is a name for who acted. Set it with the global --principal-id flag or ROCKY_PRINCIPAL_ID. When neither is set, the id is unnamed. The text output shows both as class(id), for example agent(mcp-worker).

Each JSON entry carries principal_id, principal_id_source (flag, env, mcp_profile or default) and principal_id_verified. The id is self-asserted, so principal_id_verified is always false. A row written before ids existed has principal_id: null, and the text output shows unrecorded. An --actor <ID> filter cannot match those rows. The output counts them in unattributed_skipped, and the text output prints a note.

rocky serve answers the same data over HTTP:

  • the ledger at GET /api/v1/audit, byte for byte. The route takes product=<name>, actor=<id> and since=<when>, which filter as the CLI flags do. A malformed actor or since is a 400.
  • the scorecard at GET /api/v1/audit/scorecard?by=<dim>&window=<w>, byte for byte except window_start for a duration window.
  • the custody chain at GET /api/v1/custody/{subject}, byte for byte.

The human sign-off that unblocks a gated plan.

Terminal window
rocky review --queue # pending escalations, ranked, each with its approve command
rocky review <plan-id> # dry-run review: diff + breaking-change findings
rocky review <plan-id> --approve # record the sign-off that unblocks rocky apply
rocky review <plan-id> --status # typed, read-only: is the sign-off marker in place?
Flag Type Default Description
--queue bool false List pending require_review escalations, ranked by blast radius, change class, and staleness.
--base <REF> string HEAD Git ref to diff the working-tree models against.
--approve bool false Record the sign-off marker. Without it, the review is a dry run.
--status bool false Report the plan’s review state without changing it: whether a well-formed marker naming the plan exists, who approved it and when, and the plan’s product binding. Conflicts with --approve and --queue.
--models <DIR> path models Models directory used to rank the queue by downstream blast radius.

rocky apply refuses an AI-authored, policy-escalated, gc, backfill, or restore plan until a review marker exists for it. Approving records who signed off, and when, into the same ledger rocky audit reads.

The marker must do more than exist. Apply parses it and checks that it names the exact plan; a truncated marker, or one copied from another plan, is refused with its own distinct error. The marker is written atomically (staged, then renamed), so a crash mid-approval leaves no marker at all. --status reads the same oracle and reports it as typed JSON — a tool that polls for approval should use it instead of probing the marker file.