Skip to main content

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/v1 JSON 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": "alice@example.com",
"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​

FieldRequiredNotes
nameNoDisplay slug ([a-z0-9._-]). Used in logs and reports.
actionYesALLOW, LOG, REDACT, BLOCK, QUARANTINE, STEP_UP, MODIFY, DEFER, RISK_ADAPTIVE
enabledNoDefault true
scopeYesAt minimum a detectionType; can also pin template, appContext (by symbolic name), lifecycle, and any of the broader scope axes
evaluationRulesWhen action is RISK_ADAPTIVEArray of named evaluation rules (exact / graduated / expression)
evaluationStrategyNoMOST_RESTRICTIVE (default), FIRST_MATCH, BLOCKLIST
userMessageNoShown to end users when the rule blocks
governanceNocontrolObjective + 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:

ModeEffect
Upsert (default)Create or update rules in the bundle; leave anything not in the bundle alone
Dry-runValidate the bundle and produce the full report, but make no writes
PruneUpsert 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.
  • verifyOutcome flag. 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:

  1. Export once to seed the repo: download the bundle from the BUNDLES panel and commit it to a policy-as-code repo with branch protection.
  2. Author changes in the file via PR. Reviewers use git diff to see what's changing.
  3. CI on every PR runs a dry-run import against a staging Rivaro org and posts the report as a PR comment.
  4. Merge requires a green dry-run with zero errors and at least one human approval.
  5. On merge, CD runs a prune import against production.
  6. 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​

  1. Export the current bundle and save it as before.json.
  2. Hand-edit a copy into after.json (or make changes in the UI, then re-export).
  3. Import after.json with ?dryRun=true&prune=true to preview the full sync.
  4. Diff before.json against after.json to review the changes.
  5. Import after.json with ?prune=true to apply.

Next steps​