Skip to content

Activate a Policy Set

Use this guide when validated policy data must become live. The job is complete only after simulation, activation, propagation, and a real audited exchange; creating a policy version alone changes no authorization result.

  • A validated policy version and representative allow and deny inputs.
  • A policy set in the target zone and the current active version recorded for rollback.
  • Console access or an Admin client in a trusted automation environment.

Four immutable layers separate authoring from what runs:

LayerWhat it isMutable?
PolicyA named Rego document.The name; content is versioned.
Policy versionOne immutable snapshot of policy content.No.
Policy-set versionAn immutable bundle of policy versions.No.
Active policy-set versionThe one bundle the STS evaluates for a zone.The pointer moves on activation.

You edit by creating a new version and moving the active pointer to it. Nothing already evaluated is ever rewritten, so every audit decision ties back to the exact policy-set version and manifest hash that produced it.

A zone is governed by exactly one policy set at a time. Activating a version of one set deactivates any other set in the zone in the same transaction, so which policies govern a zone is never ambiguous. Saving or activating a version also compiles the full policy bundle on the STS runtime engine, so a manifest whose policies conflict (for example, two documents defining the same data key) is rejected before it can be pinned or promoted.

flowchart LR
  Policy["Create policy"] --> Version["Create immutable policy version"]
  Version --> Set["Add policy-set version"]
  Set --> Simulate["Simulate"]
  Simulate --> Activate["Activate"]
  Activate --> Audit["Audit policy decisions"]
  1. Open the web console for your deployment.
  2. Select Policies and create or update the Rego policy.
  3. Select Policy Sets and create a set for the zone.
  4. Add the policy version to a new policy-set version.
  5. Simulate the version with a representative input.
  6. Activate the version when the simulated decision matches the intended behavior.
  7. Use Audit to follow the decision trace after the first real request.
import { AdminClient } from "@caracalai/admin";
const admin = new AdminClient({
apiUrl: process.env.CARACAL_API_URL!,
adminToken: process.env.CARACAL_ADMIN_TOKEN!,
});
const policy = await admin.policies.create(process.env.CARACAL_ZONE_ID!, {
name: "pipernet-read",
content: policySource,
});
const set = await admin.policySets.create(process.env.CARACAL_ZONE_ID!, "pipernet");
const version = await admin.policySets.addVersion(process.env.CARACAL_ZONE_ID!, set.id, [
{ policy_version_id: policy.version.id },
]);
const simulation = await admin.policySets.simulate(process.env.CARACAL_ZONE_ID!, set.id, version.id, sampleInput);
if (!simulation.would_activate) {
throw new Error(simulation.explanation.reason);
}
await admin.policySets.activate(process.env.CARACAL_ZONE_ID!, set.id, version.id);

Activation returns 202 Accepted: the active pointer moves durably, then the STS runtime reloads the bundle through a durable invalidation stream. Poll the activation status until the runtime reports the version as loaded before treating the rollout as live:

let status = await admin.policySets.activationStatus(zoneId, set.id, version.id);
while (status.propagation_status !== "loaded") {
if (status.propagation_status === "failed") {
throw new Error(status.outbox.last_error ?? "policy rollout failed");
}
await new Promise((resolve) => setTimeout(resolve, 2000));
status = await admin.policySets.activationStatus(zoneId, set.id, version.id);
}

propagation_status moves through waiting_for_outboxwaiting_for_stsloaded. With several STS replicas, the replica that receives the invalidation reloads within about a second; the rest converge through a periodic database poll (60 seconds by default), so a brief mixed-version window across replicas is expected during rollout.

CheckExpected result
Policy validationvalid: true and no blocking warnings.
SimulationRepresentative input returns the intended decision.
ActivationZone has the expected active policy-set version.
PropagationactivationStatus reports propagation_status: "loaded".
First exchangeAudit shows the new policy as a determining policy.

If activation changes expected access, keep the old policy-set version ID in the rollout notes so you can promote it again if needed.

Every denied decision links to the policy-set version that produced it, and the audit explain endpoint reconstructs the policy input for that denial. Stage the fix as a new policy-set version, then simulate the denied input against it:

const trace = await admin.audit.explain(zoneId, requestId);
const input = trace.denied[0]?.policy_input;
const candidate = await admin.policySets.addVersion(zoneId, set.id, manifest);
const check = await admin.policySets.simulate(zoneId, set.id, candidate.id, input);
if (check.result?.decision === "allow") {
await admin.policySets.activate(zoneId, set.id, candidate.id);
}

The reconstructed input is redaction-safe: actor and subject claims are never written to audit, so add any claim-dependent fields before simulating. The Iterate Policy Safely example wraps this loop as a runnable script.

FailureResponse
Simulation warns or denies the intended inputCorrect the data document and create another immutable version.
Activation is accepted but status never reaches loadedInspect the outbox identifier and STS readiness; do not assume propagation.
Real decisions still name the previous manifestTreat the replica as unconverged and keep rollout monitoring active.
Rollback is neededActivate the recorded prior version; never mutate the failed version.

Run Iterate Policy Safely for the first real denial, or Debug Authorization Decisions when live behavior differs from simulation.