Policy Bundles
Ship policy through pull requests. Export your organization's full policy state as a single canonical JSON bundle, store it in version control, review changes in PR, and import it back to apply.
This is policy-as-code for Rivaro — built into the platform, no separate CI tool required.
Where to find it in the app
Dashboard → Policies & Authority → BUNDLES.
The BUNDLES stage tab is your bundle workbench. From here you can:
- Export — download the current organization's full policy state as a single
rivaro.policy/v1JSON file with a SHA-256 checksum. - Import — upload a bundle and apply it. The panel shows a diff (what would be created / updated / deleted) before you confirm.
- Dry-run — toggle the dry-run switch to see exactly what an import would do without applying anything.
- Prune — toggle the prune switch so your live policy is made to match the file exactly. Anything missing from the bundle is deleted.
- Download the JSON Schema — for editor autocomplete when authoring bundles by hand.
When the import panel runs against a bundle, it shows a per-rule status (CREATED, UPDATED, UNCHANGED, DELETED, SKIPPED, ERROR) plus aggregate counts. The full report can be downloaded as JSON for audit retention.
Why bundles
Clicking around the UI is fine when you have ten rules. It stops being fine when you have hundreds across multiple environments, multiple AppContexts, multiple regulatory templates, and a team that needs four-eyes review before any change ships.
A bundle gives you:
- One file — the entire org's enabled policy state, deterministically serialized.
- A checksum — SHA-256 over the canonical form. Detect drift in CI.
- Round-trip fidelity — export → import is lossless for everything in scope.
- Dry-run — see exactly what would change before anything is written.
- Prune — make your live policy match your file. Anything missing from the bundle is deleted.
- A JSON Schema — get editor autocomplete and validation against
rivaro.policy/v1.
Bundle format (rivaro.policy/v1)
The exported file looks like this. The full schema is downloadable from the BUNDLES panel.
{
"apiVersion": "rivaro.policy/v1",
"kind": "PolicyBundle",
"metadata": {
"organizationId": "org_abc123...",
"exportedAt": "2026-05-23T18:00:00Z",
"exportedBy": "[email protected]",
"checksum": "sha256:<64-hex>"
},
"rules": [
{
"name": "block-ssn-egress",
"action": "BLOCK",
"enabled": true,
"scope": {
"template": "DEFAULT",
"lifecycle": "EGRESS",
"detectionType": "PII_SSN"
},
"userMessage": "SSNs cannot be returned to clients.",
"governance": {
"controlObjective": "DATA_MINIMIZATION",
"controlRationale": "PII at egress violates customer contract"
}
},
{
"name": "graduated-refund-amount",
"action": "RISK_ADAPTIVE",
"scope": {
"appContext": "billing-agent",
"lifecycle": "EGRESS",
"detectionType": "AGENT_TOOL_FINANCIAL_PAYOUT"
},
"evaluationStrategy": "MOST_RESTRICTIVE",
"evaluationRules": [
{
"name": "graduated_refund_amount",
"mode": "graduated",
"metric": "transaction_amount",
"ranges": [
{ "gte": 0, "lt": 100, "action": "ALLOW" },
{ "gte": 100, "lt": 1000, "action": "LOG" },
{ "gte": 1000, "lt": 10000, "action": "STEP_UP" },
{ "gte": 10000, "action": "BLOCK" }
]
}
]
}
],
"outputChecks": [
{
"name": "invoice-schema",
"documentSchema": { "...": "..." },
"rulePack": { "...": "..." },
"evidenceManifest": { "...": "..." }
}
]
}
What's in a rule
| Field | Required | Notes |
|---|---|---|
name | No | Display slug ([a-z0-9._-]). Used in logs and reports. |
action | Yes | ALLOW, LOG, REDACT, BLOCK, QUARANTINE, STEP_UP, MODIFY, DEFER, RISK_ADAPTIVE |
enabled | No | Default true |
scope | Yes | At minimum a detectionType; can also pin template, appContext (by symbolic name), lifecycle, and any of the broader scope axes |
evaluationRules | When action is RISK_ADAPTIVE | Array of named evaluation rules (exact / graduated / expression) |
evaluationStrategy | No | MOST_RESTRICTIVE (default), FIRST_MATCH, BLOCKLIST |
userMessage | No | Shown to end users when the rule blocks |
governance | No | controlObjective + controlRationale — compliance metadata persisted with the rule |
App-contexts are referenced by symbolic name in bundles, not UUID, so the same bundle is portable across environments where an app-context has different identifiers.
Determinism guarantees
Two exports run back-to-back produce byte-identical output — rules are serialized in a stable order regardless of how or when they were created. You can git diff two exports and the only changes are real changes.
Import behavior
When you import a bundle from the BUNDLES panel, three behaviors are available:
| Mode | Effect |
|---|---|
| Upsert (default) | Create or update rules in the bundle; leave anything not in the bundle alone |
| Dry-run | Validate the bundle and produce the full report, but make no writes |
| Prune | Upsert AND delete any live rule not present in the bundle (your policy becomes a faithful mirror of the file) |
Prune only deletes if the bundle has zero validation errors. If anything fails, the prune step is skipped — you'll get a partial upsert (or none, on dry-run).
You can combine dry-run + prune to preview what a full sync would do without writing.
How the importer matches an existing rule
The importer matches by scope dimensions plus prompt scope — not by name. The name is for humans; the scope is the actual identity. This means you can rename a rule and the import still updates the right row.
Matching considers:
template,appContext,lifecycle,detectionType- Plus any advanced scope axes the rule sets
If multiple rows match (variants), the importer claims them one-by-one so variants are preserved.
What's not in bundles
A few things are intentionally outside the bundle scope:
- Disabled rules. Only enabled rules are exported. If you want to disable via bundle, omit the rule entirely or set
enabled: false. - TRAINING-stage connector rules. Connector rules live under the connector, not in the org policy bundle. Manage them separately under Training-Data Connectors.
- Notification channel bindings. A rule's notification channel is environment-specific and not bundled. Re-attach notification channels per environment.
verifyOutcomeflag. Per-rule outcome-verification opt-in is not in the bundle.- Governance enforcement bands. These live on the org's governance policy, not on individual rules. Configure them in Policies & Authority → GOVERNANCE.
- Template defaults. Templates are code-defined and ship with Rivaro. Bundles carry your overrides, not the baseline.
GitOps workflow
Recommended pattern for teams that want full policy-as-code:
- Export once to seed the repo: download the bundle from the BUNDLES panel and commit it to a
policy-as-coderepo with branch protection. - Author changes in the file via PR. Reviewers use
git diffto see what's changing. - CI on every PR runs a dry-run import against a staging Rivaro org and posts the report as a PR comment.
- Merge requires a green dry-run with zero errors and at least one human approval.
- On merge, CD runs a prune import against production.
- The post-import response is uploaded as a build artifact for the compliance audit trail.
This is the same workflow Kubernetes manifests use. The bundle is your kubectl apply -f for governance.
For developers: automating bundle import/export
The BUNDLES panel covers the day-to-day workflow. If you need to wire bundles into a CI/CD pipeline, the same operations are available via REST.
Export
Send a GET request to $RIVARO_BASE/api/policy/bundle with your bearer token and save the response as policy.json.
Returns the canonical bundle for the authenticated organization — all enabled rules, deterministically ordered, with a SHA-256 checksum over the body.
Import
POST the bundle JSON back to $RIVARO_BASE/api/policy/bundle with Content-Type: application/json.
Append ?dryRun=true for a dry-run, ?prune=true for prune mode, or both (?dryRun=true&prune=true) to preview a full sync.
The response is a per-rule status report (CREATED, UPDATED, UNCHANGED, DELETED, SKIPPED, ERROR) plus aggregate counts — the same content the BUNDLES panel displays after an import.
JSON Schema
GET /api/policy/bundle/schema
Wire the schema into your editor for autocomplete and validation while you author bundles by hand:
// .vscode/settings.json
{
"json.schemas": [
{
"fileMatch": ["**/policy/*.json", "**/policy.json"],
"url": "https://your-rivaro.example.com/api/policy/bundle/schema"
}
]
}
Round-trip example
- Export the current bundle and save it as
before.json. - Hand-edit a copy into
after.json(or make changes in the UI, then re-export). - Import
after.jsonwith?dryRun=true&prune=trueto preview the full sync. - Diff
before.jsonagainstafter.jsonto review the changes. - Import
after.jsonwith?prune=trueto apply.
Next steps
- Policy Templates — Templates ship a baseline you override with bundles
- Policy Scoping — All ten scoping dimensions, how they resolve
- Output Checks — Output checks ride in the same bundle
- Compliance Reporting — Bundle history feeds the change-management evidence stream