Policy, approval and retention commands
Part of the NovaFabric CLI reference. Both nova and novafabric run the same binary.
Policy and governance (v0.8)
nova policy test
Run the Rego unit test suite on the built-in policy bundle (requires opa installed).
nova policy test
nova policy test --bundle /path/to/custom-bundleOptions:
--bundle PATH— override the default policy bundle path (also:NOVAFABRIC_POLICY_BUNDLE_PATHenv var)
Returns exit code 0 if all tests pass, 1 if any test fails or opa is not found.
The built-in bundle ships reference gates including promote_gate.rego,
regression_gate.rego (v0.9), and budget_gate.rego — the cost/energy budget
gate (ADR-0136, experimental).
Budget gate (budget_gate.rego, experimental). Denies promotion when the
capsule's already-recorded cost/energy/token rollup exceeds declared ceilings —
a promotion gate over sealed evidence, never a live spend alert. The rollup is
assembled with novafabric.policy.budget_block_from_capsule(capsule_dir) (reads
nova.cost from model-calls.jsonl, measured_joules from
energy-receipts.jsonl, and gen_ai.usage.* token counts) and passed as
input.resource.budget; ceilings go in input.context.budget_ceilings
(total_cost / cost_per_run / energy_kwh / tokens). Missing evidence is
never treated as zero: absent ceilings or absent data pass with an explicit
"no data" reason (skip_unmeasured, the default), while
input.context.missing_evidence: "require_measured" fail-closes on a declared
ceiling with no recorded evidence. Spec: design/spec/budget-gate-v0.md (private).
The nova policy budget set|list|show authoring commands and the budget-gate
verdict record are future design (ADR-0136 P2/P3) — not yet implemented.
Environment-conditioned gates (experimental, ADR-0126 P3). The evidence-export
gate's input carries the capsule's recorded deployment environment as
input.resource.deployment_environment (the value of nova capture --environment /
NOVAFABRIC_ENVIRONMENT, verbatim; null when none was recorded — never inferred), so a
custom policy can apply production-only requirements:
allow if {
input.resource.deployment_environment == "production"
input.resource.redaction_proof_present == true
input.resource.unsafe_skips == 0
}Asset-scoped gates (promotion, protected labels) carry null. The mutating-replay gate
does not yet populate the field (planned); no built-in gate conditions on it.
nova policy explain <decision-id>
Replay a past policy decision from the audit log, showing the event type, actor, resource, and details.
nova policy explain 3f8a1b2c-...The dashboard Policy Explain panel auto-completes decision IDs via
GET /api/policy/recent-decisions?limit=50 (serve REST API), which returns up to 50
recent IDs from ~/.novafabric/dashboard-audit.jsonl in most-recent-first order.
nova policy list
List Rego policy files in the built-in bundle and any signed promotion policies stored in the PolicyStore.
nova policy list
nova policy list --namespace staging
nova policy list --bundle /path/to/custom-bundle
nova policy list --db ~/.local/share/novafabric/merkle.dbOptions:
--bundle PATH— Rego bundle directory to scan (default: built-in bundle; also:NOVAFABRIC_POLICY_BUNDLE_PATH)--db PATH— PolicyStore SQLite DB path (default:NOVAFABRIC_HOME/promote/policy.db, falling back to~/.local/share/novafabric/merkle.db)--namespace / -n TEXT— filter signed policies to this namespace (default: show all namespaces)
Output has two sections:
- Rego Bundle — table of
.regofiles with file name (relative to bundle root) and size. - Signed Promotion Policies — table of stored policy versions with version number, namespace, and creation timestamp. If no DB exists yet, a note is printed and the command exits cleanly.
nova approve <name@version>
Approve an asset that is in the pending_approval lifecycle state, recording the approver and note in the audit log.
nova approve my-model@v2
nova approve my-model@v2 --approver alice --note "Reviewed eval results"Options:
--approver TEXT— name or ID of the approver (default: current OS user)--note TEXT— optional approval note recorded in the audit log
Exits with code 1 if the asset is not found or not in pending_approval state.
nova hold create <registry>
Place a legal hold on a registry, suspending all capsule deletion (ADR-0031).
nova hold create my-registry --reason "SEC examination 2026-Q2"
nova hold create my-registry --reason "SEC examination 2026-Q2" --duration-days 365Options:
--reason TEXT— required; reason for the hold (recorded in audit log)--duration-days N— hold duration in days; omit for indefinite hold
Holds are stored in .novafabric/registries/<registry>/holds.jsonl (append-only).
nova hold list <registry>
List active (unreleased) legal holds on a registry.
nova hold list my-registrynova hold release <hold-id>
Release a legal hold by ID. Records the release in the audit log.
nova hold release hold-a1b2c3d4Exits with code 1 if the hold ID is not found or already released.
nova capsule delete <capsule-id>
Delete a capsule record, subject to retention policy and legal holds.
nova capsule delete cap-01JXXXXX --registry my-registry
nova capsule delete cap-01JXXXXX --registry my-registry --age-days 1826
nova capsule delete cap-01JXXXXX --registry my-registry --forceOptions:
--registry TEXT— required; the registry name owning this capsule--age-days N— age of the capsule in days (used for retention window check; default: 0)--force— skip retention and hold checks (dangerous; use only for emergency recovery)
Deletion is blocked when:
- One or more active legal holds exist for the registry (even without a
retention-policy.yaml) - A
retention-policy.yamlexists and the capsule is within the retention window deletion_mode: prohibitedis set in the policy
On success, a signed capsule.delete entry is appended to the audit log.
Retention scheduler (ADR-0134)
Applies the ADR-0031 retention
windows over time: a WORM-aware, crypto-shred-integrated, audited sweep.
Bindings live in a new optional bindings: block inside the existing
.novafabric/registries/<registry>/retention-policy.yaml
(schema: schemas/retention-binding.schema.json); a registry with no bindings
is swept for nothing. NovaFabric embeds no daemon — run the sweep manually
or wire nova retention apply --yes into cron/systemd.
# retention-policy.yaml (ADR-0031 fields unchanged; bindings are additive)
bindings:
- id: mifid-trade-5y
match: {tag: trade-record, deployment_environment: production}
window: P1825D # ISO-8601 duration from capsule created_at, or YYYY-MM-DD
action: purge # expire-metadata | purge | crypto-shred
- id: gdpr-subject-pii
match: {tag: contains-pii}
window: P180D
action: crypto-shred # dispatches to the ADR-0069 DEK destructionMatching reads capsule manifests under the capsule directory:
metadata.tags (comma-separated), metadata.deployment_environment,
metadata.pii_subject_id (crypto-shred subject), and alias (matched by the
asset predicate, glob allowed).
Safety invariants (fixed, not configurable): a WORM/legal hold always wins
— a WORM-retained capsule is never purged or shredded before its locked_until
(recorded skipped: worm_hold); purge is refused under
deletion_mode: prohibited; a crypto-shred inside the Art.17(3)(b) window is
deferred. Every decision — including every skip — appends one hash-chained
retention.action audit entry (schemas/retention-action-record.schema.json):
deletion is itself evidence.
nova retention plan
Dry-run (the default posture): list what WOULD be affected. Touches nothing,
writes no audit records, and uses the identical due-computation code path as
apply.
nova retention plan --registry my-registry
nova retention plan --registry my-registry --jsonnova retention apply
Apply due retention actions. Confirm-gated: prompts unless --yes (or
--dry-run). Idempotent and fail-safe — re-runs are no-ops; a per-item
failure is recorded error and the sweep continues.
nova retention apply --registry my-registry --dry-run # preview
nova retention apply --registry my-registry --yes # cron/CI
nova retention apply -r my-registry --action crypto-shred --limit 100 --yesOptions:
--dry-run— preview only (identical planner, no action, no audit entries)--yes / -y— skip the confirmation prompt (for cron/CI)--capsule-dir PATH— capsule directory to sweep (default:$NOVAFABRIC_HOME/capsules)--worm-db PATH— local WORM adapter DB consulted forlocked_until(default:.novafabric/registries/<registry>/worm.db)--action NAME— only consider bindings with this action--limit N— bound the number of applied actions per pass (the rest carries over)--principal TEXT— acting identity recorded in evidence entries (default:cli-user)--retention-months N— Art.17(3)(b) minimum window for crypto-shred (default: 6)--json— machine-readable output
nova retention status
Read-only: per binding, how many items are due now, how many are held (WORM/legal), and the next due date.
nova retention status --registry my-registry --jsonnova retention explain <capsule-id>
Read-only: which bindings match this capsule, its computed due date, and its current hold state.
nova retention explain cap-01JXXXXX --registry my-registry