Skip to main content
Proposed design — not yet available. This page describes planned beta behavior for server-side policy evaluation and is published to gather feedback before the feature is built. The commands, flags, and output shown are illustrative and will change as the design firms up. Nothing here can be run against Kosli today. Share comments with the Kosli team.
Today, kosli evaluate runs client-side: the policy lives in your repository, the check runs on your CI runner, and Kosli only learns the outcome if you explicitly attest it back — a self-reported result Kosli takes on trust. Server-side policy evaluation moves the Policy Decision Point (PDP) into Kosli. You ship a versioned policy to Kosli, Kosli executes it against facts it already holds, and Kosli records the decision itself, along with the exact policy version and inputs it used.

Start here

Publish a policy, then evaluate it

Two commands, which the rest of this page fills in:
Kosli evaluates the attestations on my-release-trail and records a decision against control RCTL-043, citing the policy version, the parameters, and the exact facts it used. --flow and --trail say where the decision is recorded, as on any other Kosli command, and --context says which trail’s facts to evaluate — the same trail here, a different one when you need it. --assert fails the pipeline step if the policy denies. Everything else on this page is detail around those two commands. Adding --control is what records a decision. Omit it and Kosli still runs the policy and keeps the result, but writes no decision — an ad-hoc evaluation you can inspect without adding anything to the audit trail. That makes this one command two things: a way to check a policy against real facts, and a way to claim a control is satisfied.

What is in the beta, and what is not

In: publishing versioned policies, evaluating them against a trail or trails, running evaluations that record no decision, recording decisions against controls, validating and testing policies, and reviewing or exporting the decisions a policy has produced. Not in: evaluating anything other than trails, evaluating the contents of attachments, composing several published policies into one evaluation, scheduled evaluations, pinning an evaluation to a policy version, and advisory-only outcomes. See the full list, with reasons.

Where to go next

Author and ship policies

For governance platform engineers. Publishing and evaluating, the Rego contract, testing, and managing policies over time.

Audit the decisions

For auditors and control owners. Review, filter, sample, and export decisions — and re-run one yourself to check it.

Fix a failing evaluation

For application teams. Understand why a policy denied and what to do about it.
This is distinct from the existing client-side Evaluate trails with OPA policies tutorial, where the CLI runs the policy locally. Here, Kosli stores the policy and performs the evaluation.

Why server-side

Running evaluation inside Kosli changes who computed the decision — and where the data is processed: Because Kosli both stores the policy and runs it, a decision points at exactly what ran — the policy version and its content digest, its parameters, and the trail facts that were in scope. The record is complete enough to reconstruct the evaluation, which is what makes the decision trustworthy for audit: you can take a decision, export what it was given, and run the policy again yourself — see Re-run a decision offline. Running evaluation next to the data is also faster and scales further: Kosli doesn’t have to ship a trail’s full evidence out to your runner before evaluating, so evaluations can cover much larger volumes of facts in the cloud.

Prerequisites

These prerequisites describe the proposed beta experience — they are not runnable today.

Author, ship, and evaluate a policy

This section takes a policy from local Rego to a recorded decision. The later sections build on it — validating and managing policies, and viewing and inspecting the decisions they produce.
1

Author a policy locally

Author your policy as code in your own repository. A policy is its policy-as-code (Rego), a human-readable name, a short description of its purpose, and an input schema declaring whether it evaluates a single trail or multiple trails. You give it a stable identifier when you publish it to Kosli (next step).Your repository stays authoritative for authoring a policy — it is where the Rego is written, reviewed, and tested. Kosli is authoritative for what executed: once a version is published, that stored version is what runs and what a decision cites, so the audit record never depends on your repository still being reachable or unchanged.The result schema requires an allow rule; you can return more alongside it. Follow the same fail-safe design rules as client-side policies — see Evaluate trails with OPA policies for the reasoning behind them. The full contract a policy is written against — entry point, input, parameters, available built-ins, and result — is in The Rego authoring contract.
pr-approved.rego
Note the second violations rule. Without it, a trail that is missing the attestation entirely leaves both allow and the first violations rule undefined — a denial with an empty violations list and nothing for the team to act on. Cover the missing-fact case explicitly, so every way the policy can deny produces something to fix.Before you ship it, you can validate and test the policy against real trail data.
In the beta, a policy evaluates the facts on the trail(s) you point it at — attestations on trail / trails. Evaluating other targets (snapshots, artifacts, generic input) and composing multiple policies into one are not included (see What’s out of scope).
2

Ship a versioned policy into Kosli

Publish the policy into Kosli via the CLI (or API). The positional argument is the policy’s stable identifier (pr-approved in the example below) — this is what decisions and evaluations use to refer to the policy, so it is immutable once created; choose one that matches how your organization names its policies. --name and --description are the human-readable label and purpose, and both can be changed later without changing the identifier.Pass a single .rego file, as in the example below, or a directory when the policy is split across several files. A directory holds one policy’s bundle, not a collection of separate policies — Kosli stores the whole set as one versioned bundle with one digest, so a decision still points at one unambiguous thing. Tests — files named *_test.rego, following OPA’s convention — are stored with the bundle but never executed server-side.Kosli records the first version on publish. Every later change creates a new version and records who made it, so a decision can always point back to the exact policy that produced it — see Manage policies for updating, tagging, and archiving over time.Only org admins can publish a policy. Creating, updating, and archiving a policy all require org admin, the same as controls. Running an evaluation and recording a decision does not — an ordinary API token or service account is enough, so pipelines can invoke a policy without being able to change one. That split means the team being audited can run the control, but only an admin can decide what the control checks.
kosli create policy is the same command used for environment policies: --type rego selects an evaluation policy, while the default --type env creates an environment policy. The positional argument differs between the two — for --type env it is the policy name, while for --type rego it is the immutable identifier described above. In the beta, authoring is CLI/API only — the Kosli UI is read/tag-only.
3

Evaluate and record a decision

With kosli evaluate policy, Kosli fetches the in-scope facts and runs the policy server-side. Passing --control additionally records a decision against that control, which is what this step does; omitting it runs the evaluation on its own.Evaluation is asynchronous by default — the command returns once the evaluation is queued, naming the evaluation that will carry the outcome:
You then either fetch that evaluation (see Check an asynchronous evaluation) or, more usually in a pipeline, add --sync to wait for the outcome, as the example below does.
The flags that carry the meaning, in two groups:--policy and --context are always required — they are the evaluation. The destination group — --control, --flow, --trail, --name, and the artifact — is required together when you record a decision, and rejected when you don’t. --flow is the one exception: it may be passed on its own, because a bare trail=<trail_id> reference resolves against it.--context names the trail whose facts are read; --flow and --trail name the trail the decision is written to. They are often the same trail, as above — see the Evaluating facts from another flow example below for when they are not.Use --assert in place of --sync when the step should also fail on a denial — that is how an evaluation becomes a pipeline gate. Once recorded, a decision appears against the policy — see View the decisions for a policy.
More on --context. In the beta the only reference type is a trail: trail=<trail_id> names a trail in --flow, and trail=<flow_id>/<trail_id> names one in another flow. Repeat the flag to evaluate several trails — see Evaluating multiple trails. Kosli resolves these references into the evaluation a decision cites; they are not a cluster or account selector. The typed form leaves room for other context types — snapshots and reported workloads, for example — later, without new flags.
Evaluating facts from another flow. Usually --context and --flow / --trail name the same trail. Because each --context reference carries its own flow (trail=<flow_id>/<trail_id>), you can evaluate a trail in a different flow — for example, evaluating a build flow’s trail but recording the decision on a release flow’s trail:
The decision records which flow and trail the facts came from, so a reviewer can always see that a release decision was reached from a build flow’s evidence rather than from the release trail itself.
Any flow you can write to can supply the facts for a decision on any other flow, and the beta does not check that a given policy or source flow is acceptable for a given control. That makes it possible for evidence from a low-assurance flow — a sandbox or an experiment — to close a control that gates production. Until Kosli can constrain this, treat the set of flows that feed production controls as something to decide and review, not something to leave open.
The server captures the evidence for you. Because Kosli runs the evaluation, the decision automatically records the policy (and its version), the evaluation report, and any violations — you don’t attach them by hand with --attachments / --user-data as you do today when you attest a client-side result yourself. Likewise, --compliant is derived from the policy’s allow result rather than passed in. This is the core difference from recording a decision yourself.
The decision this records is the same kind of decision described in Working with controls — the difference is that Kosli computed it server-side rather than you attesting the outcome yourself. Enforcement is unchanged: require the control via for_control in an environment policy and gate promotion with kosli assert artifact --environment, exactly as before. Recording the decision is the PDP; asserting it is the PEP.
4

View and trace the decision

As soon as the evaluation completes, the decision appears in Kosli against the control and the policy that produced it. Open it in the Kosli app to see the outcome, and the evaluation it was reached from:A decision carries two timestamps. The requested time is when the evaluation was enqueued; the recorded time is when the decision was written, once the evaluation ran. Kosli fixes the trail moment — which attestations are in scope — at the requested time. The trail data itself is only queried and serialized once the evaluation executes, but always as of that fixed timestamp, not as of whatever the trail contains by then — so a queued evaluation is deterministic no matter how long it waits. Kosli never backdates the record.For example, evaluation ev_7f3a91c2 is requested at 09:14:22 and executes at 09:14:25. If a new pull-request approval landed on the trail at 09:14:24 — after the request, before execution — it is still not part of this decision: the query at 09:14:25 reads the trail as it stood at 09:14:22, not as it stands when the evaluation happens to run. If that new fact changes the answer, it takes a new evaluation to say so.Because the policy version, parameters, and trail moments are all pinned to that requested time, the decision states exactly what was evaluated and when it applied — enough to re-run it offline. To review and filter every decision a policy has produced, see View the decisions for a policy.

Check an asynchronous evaluation

Without --sync, the outcome arrives on the evaluation the command named. An evaluation exists from the moment it is queued, so you can ask about it before there is any outcome — or any decision — to look at:
STATUS is queued until the evaluation runs, then completed or failed — see When evaluation itself fails. Branch on STATUS, not on whether a field is present: the response carries the same keys at every status, with the ones that aren’t known yet returned empty, so a client written against a completed evaluation still parses a queued one. Once it is completed, the evaluation names the decision it produced, if any, so a queued evaluation and the decision it becomes are one hop apart in both directions. An evaluation reaches a terminal status once. Its result is written a single time and never revised, so completed and failed are final — re-evaluating creates a new evaluation rather than changing this one. Add --wait to block until the evaluation is terminal instead of polling for it:
The same command returns what the evaluation was given, not just what it concluded: --show-input behaves as it does on the client-side kosli evaluate trail, so jq '.input' extracts the policy input and kosli evaluate input consumes it unchanged.

Evaluate without recording a decision

Leave --control off and Kosli runs the policy exactly as it would otherwise — same facts, same pinning, same recorded result — but writes no decision:
The evaluation is still a full record: it has an ID, it pins the policy version and parameters it used, and it can be replayed like any other. What it doesn’t do is claim that a control is satisfied. Use it to check a policy against real facts — while you are iterating on one, or to answer a one-off question — without adding to the set of decisions an auditor samples. Because the destination flags describe the decision, they are rejected here: --trail, --name, --fingerprint, --description, and --annotate all require a decision to be recorded. --flow is accepted on its own, since a bare trail=<trail_id> reference resolves against it.
An empty --control is an error, not an omission. Otherwise an unset $CONTROL_ID would quietly turn a step that records a decision into one that records nothing, and the step would still pass. If a control must be closed on every run, require it in the trail’s flow template as a type: decision slot as well — a missing decision then surfaces as a non-compliant trail rather than as silence.
--assert works here too, and gates the pipeline on the outcome without recording a decision. That is a reasonable way to try a policy against real pipelines before you let it close a control — but it is not a control: the evaluation records that the check ran, and nothing records that the control was satisfied.

Evaluating multiple trails

A policy published with --input-schema trails evaluates several trails at once and receives them as input.trails. Repeat --context to name each one:
The input schema and the number of references have to agree: passing several references to a trail-schema policy is rejected before the evaluation is queued, as is passing one reference to a trails-schema policy. Several trails are read, but the decision is still recorded on exactly one trail — the one named by --flow and --trail.

The Rego authoring contract

Kosli evaluates your policy with OPA, so the tooling you already use locally — opa fmt, opa check, opa test — works on these policies unchanged. What Kosli fixes is the contract around the policy: the entry point it evaluates, the shape of input, the parameters and built-ins a policy may use, and the result Kosli reads back.
Everything in this section is proposed and open for comment. If a constraint here would stop you shipping a control you need, that is the most useful feedback you can give us.

Entry point

A policy declares package policy, and Kosli evaluates data.policy.allow. Keeping the package fixed means the CLI, the server, and your own opa test cases all agree on where the entry point is, and it is what lets your tests import the policy under test as policy.allow.

What the policy sees in input

A policy evaluates the attestations in the slots on the trail — nothing else. Trail events (trail started, trail updated) are not attestations and are not in input, and neither are attestations from earlier moments of the same trail. Slots are matched by name, not by attestation type. Attachments are not opened. A policy sees an attestation’s own fields, not the bytes of the files attached to it, so it cannot parse a SARIF file, an SBOM, or a PDF report to reason about individual findings. To evaluate that content, summarize it into JSON at attestation time — the counts, severities, or package identifiers the policy needs — using whichever field the attestation type gives you for structured data:
  • For a custom attestation type you define, pass the summary as --attestation-data, validated against your type’s schema. This is the better fit when the summary is a recurring shape worth its own schema.
  • For a built-in type (generic, snyk, sonar, and others) with no schema field for your summary, pass it as --user-data instead.
Keep the original file on the same attestation with --attachments. The summary is what the policy reads; the attachment is what lets an auditor go back to the file the summary was derived from and check that it is faithful. The --input-schema you declare when you publish the policy selects the shape of input: Those schemas are published as JSON Schema so you can type-check a policy against input before you ship it. Associate one with your rules using an OPA metadata annotation:
pr-approved.rego
Then check the policy against it. opa check resolves schema["trail-v1"] to trail-v1.json in the directory you pass to --schema:
This turns a misspelled field into a compile error naming the fields that do exist, rather than an undefined reference that silently leaves allow at its false default. --strict additionally rejects unused imports and unused local assignments.
Schemas give you type checking while authoring; they are not how Kosli validates a policy on publish. Kosli performs its own validation — see Validate and test a policy.

Parameters

Parameters are the only input to a policy from outside the trail. They arrive as data.params, and every evaluation records the parameters it used, so a decision always shows what the policy was run with.
Alias parameters at the top of the policy, as above, so a missing parameter leaves the alias undefined and any rule referencing it fails to evaluate — rather than the policy proceeding with a silently absent value. You don’t declare the parameters a policy takes. Kosli derives them from the policy itself, by reading which data.params paths the Rego depends on, and validates the parameters you supply at evaluation time — an unknown parameter name or a missing one is rejected before the evaluation is queued, by the CLI where it can be caught locally and by the server as the actual gate. This closes off a failure that is otherwise very hard to spot. Supplying maxHigh to a policy that reads max_high leaves the alias undefined, so allow falls back to false and the violations rules that reference the same alias don’t fire either — the result is a recorded non-compliant decision with no violations explaining it, and a failed pipeline with nothing to fix. Rejecting the parameters up front turns that into an immediate, obvious error.
Derivation reads parameter paths statically, so a policy that computes parameter names at evaluation time — data.params[key] for some derived key — cannot be checked this way, and Kosli falls back to accepting whatever you supply. Prefer literal parameter names.
Parameters are recorded on every decision and are visible to anyone who can read that decision. Pass thresholds, names, and flags — never secrets or personal data.

Result

Give allow a default allow := false and drive it through positive assertions, so an evaluation that goes wrong denies rather than allows.

Available built-ins

Because Kosli executes the policy, evaluation runs with no network access and must produce the same decision every time it is re-evaluated. Both constraints are expressed as an OPA capabilities file, which you can check a policy against locally before publishing:

Evaluation time

A policy cannot read the clock, because a decision that depends on when it happened to run is not replicable. Instead, Kosli injects the evaluation’s request timestamp — the moment the decision is valid as of — as data.kosli.evaluation_time, in nanoseconds since the Unix epoch, so it can be compared directly against a parsed attestation timestamp:
Here approval_timestamp is whichever timestamp the attestation you are evaluating carries — the field depends on the attestation type. Time windows work as normal, they are anchored to the moment the decision applies to, and re-evaluating the stored context produces the same answer.

Engine and Rego version

Kosli pins the OPA version it evaluates with and records it on every decision alongside the policy version, so a decision states exactly what ran. Author against that version locally and check with opa check --v1-compatible to avoid a policy that compiles on your machine but not in Kosli.

When evaluation itself fails

A policy that times out, exhausts its memory budget, or hits a runtime error is a broken policy, not a non-compliant artifact, and Kosli reports the two differently — a failed evaluation records a typed error rather than a --compliant=false decision, and --assert exits with a distinct code so a pipeline can tell “this change is not compliant” from “this policy is broken”. The typed error lands on the evaluation, so a failure is visible without --sync or --assert — the distinction holds in the asynchronous path too:
An evaluation that is never picked up — a worker lost, a queue drained — also terminates rather than sitting in queued indefinitely: Kosli writes a typed abandoned error against it after a timeout, so every evaluation reaches a terminal status. A built-in that errors aborts the evaluation rather than evaluating to undefined — an undefined built-in would deny with no violations, which is the outcome this design avoids everywhere else. print and trace output is discarded rather than captured on the report; express diagnostics through violations or a custom result field instead.

Validate and test a policy

Before you ship a policy — or wire it into a pipeline gate — check that it holds up.
A policy is code, so treat it like code: keep Rego tests alongside each policy that assert it allows and denies the cases you expect. Rego’s built-in test framework runs these locally and in CI, catching regressions before you publish a new version.

Validate

kosli validate policy checks that a policy is well-formed without recording anything. It confirms that:
  • the Rego compiles — a bundle is validated as a whole, not file by file;
  • it matches its declared input schema (trail / trails);
  • it satisfies the result schema — an allow rule is mandatory.
Kosli runs the same validation when you publish, so a policy that fails these checks never becomes a version.
--policy takes a single .rego file, as here, or a directory holding the policy’s bundle — the same forms kosli create policy and kosli evaluate policy accept, so you can validate exactly what you are about to publish.

Evaluate a local policy

You don’t have to publish a policy before it can evaluate anything. Point --policy at a local, unpublished .rego file — or at a directory holding the policy’s bundle — and Kosli evaluates it server-side just as it would a stored policy. This is how you iterate on a policy you haven’t shipped, against the facts it will really run on:
Because there is no stored version to cite, the evaluation records the exact policy source that ran, so the result is still replayable. A local policy cannot record a decision — a local --policy file and --control are mutually exclusive. Publishing is what puts a policy through the org-admin gate, so a decision from unpublished Rego would let anyone who can record a decision supply the code behind it. Publish a version — a no-op if nothing changed — and the same evaluation records a decision.

Manage policies

Update a policy

Publishing a change is the same command as the initial publish: re-run kosli create policy with the updated Rego, name, or description. Each publish creates a new version and records who made it — add --comment to note what changed — so a decision always points back to the exact version that produced it. The identifier never changes.
An evaluation that references a policy by identifier uses its current version. Decisions recorded earlier keep pointing at the version that was current when they were made. Publishing an unchanged policy is a no-op. If what you publish is identical to the current version, Kosli creates no new version, says so, and exits 0:
Publishing is idempotent, so it is safe to run on every merge, or from terraform apply: a pipeline that republishes an untouched policy leaves the history alone instead of filling it with identical versions. What counts as a change: Comparison is against the current version, not the whole history. So republishing the bundle from an earlier version is a real change and does create a new version — that is how you roll back, and the history records that the rollback happened rather than hiding it.

Review a policy change

A policy is the code that decides whether a control passes, so a change to it is a change to your control environment. Reviewing that change is part of the lifecycle — author, test, version, review, ship — and in the beta the review happens in your policy repository, not in Kosli. The practice we recommend, and the one Kosli’s records are designed to corroborate:
1

Keep policies in a protected repository

Require pull requests, and require review from the people accountable for the control — a CODEOWNERS entry per policy is the simplest way to make sure the right control owner sees a change to their control. Restrict who can push directly.
2

Make the control owner's question answerable at review time

The question a control owner has to answer is not “is this valid Rego?” but “does this policy actually meet the control objective?” Reviewing the change alongside its tests is what makes that answerable — the tests state, in cases, what the policy is claiming to enforce.
3

Publish only from CI, after review

Because only org admins can publish, publish from a CI job running with an admin credential on merge, rather than by hand from a laptop. The reviewed commit then becomes the published version, and the two cannot drift.You can publish every policy on every merge without thinking about which ones changed — publishing an unchanged policy is a no-op, so only the policies you actually edited get a new version.
4

Point the version back at its review

Use --comment to reference the change record, so Kosli’s version history and your review history join up:
Kosli records who published each version, when, what they said changed, and the digest of the source that was published. Combined with the review in your repository, that is a complete change-control trail for the policy itself — which is what an auditor asking “who decided this is what the control means?” is looking for.
There is no review or approval step inside Kosli in the beta: a policy version is live as soon as it is published, and there is no draft state or second-approver requirement. If your control environment needs approval to be enforced by Kosli rather than by your repository, tell us — it affects how much of this belongs in the product.

Tag a policy

Tags are versionless labels for organizing and filtering policies — grouping by team, framework, or control area. Tagging does not create a new version.

Archive and unarchive

When a policy is retired, archive it rather than deleting it. Archiving is light-touch: the policy is removed from the main list in the UI but remains evaluable, and its versions and past decisions are preserved. Archiving records who archived it and when, and does not create a new version. Unarchive it if it comes back into use.

List and filter policies

Browse your policies in the Kosli app — select Policies in the sidebar to see the full catalog. Search by name or identifier, and filter by tag to narrow the list. Each policy shows its identifier, current version, and tags.
You can also list policies from the CLI. Filter to evaluation policies with --type rego, and by name or tag — each entry shows the identifier and current version:

View a policy

Select a policy to open it. The Policy tab is the default view: it shows the policy-as-code that Kosli actually runs, read-only, for the version you choose — not a name and description of it.
A policy can be a single .rego file or several, so the tab lists the files in the version and shows the one you select. Alongside them it shows what an auditor needs to check the record: the bundle digest a decision cites, the bundle’s roots (the data paths it owns), the entrypoint that Kosli evaluates, the declared input schema, and the parameters the policy reads. Tests shipped with the policy are listed and marked not executed — Kosli stores them as evidence the policy was tested, but only the entrypoint runs.
When you open a policy from a decision, the tab shows the version that decision used, not the current one — so the source in front of you is the source that produced the outcome you are looking at.
Policies are read-only in the Kosli UI. Edit them in your repository and publish a new version with kosli create policy.

View the decisions for a policy

This is where auditors and control owners review what a policy has decided. Every decision a policy produces is listed against that policy, across all versions of the policy. In the Kosli app, select Policies, choose a policy, and open its Decisions tab. Each row shows the timestamp, the trail and artifact the decision applies to, the policy version that produced it, and the outcome (compliant or non-compliant). Filter by outcome, version, or time range, and page through the results to sample them.
Select a decision to open its detail, showing the trail and artifact it applies to, the policy version and parameters that were used, the evaluation report, and any violations.
For an audit or an offline review, export the filtered set as CSV — from the Decisions view in the UI, or from the CLI:
--compliant=false surfaces every non-compliant evaluation for follow-up; combine it with --since or a version filter to scope the export. Each row shows the requested and recorded timestamps, so an export is enough to sample decisions by when they applied rather than only by when they were written.
The same filtered sampling and CSV export are proposed for a control’s Decisions tab, so an auditor can sample by control as well as by policy. That is not part of the controls feature today.
Evaluations that record no decision do not appear here, since this view lists decisions — in the beta you reach an ad-hoc evaluation by its ID.

Re-run a decision offline

Sampling a decision usually raises the question of whether it was right. Everything the evaluation was given is held on the evaluation itself, which the decision names — so you can export it and evaluate the policy again yourself, with stock OPA, on your own machine:
Every decision shows the evaluation it came from, in the UI and in kosli get decision, so the evaluation ID is one hop from the decision you are looking at. The export contains what the evaluation was given, as ordinary OPA inputs:
Because an evaluation exists from the moment it is queued, this export also works when there is no decision to look at — an evaluation still in the queue, one that failed, or one run without --control. That is usually the case you most want to inspect.
Evaluate it with OPA directly. --bundle loads the whole policy directory, so the same command works whether the policy was one file or many:
Because the evaluation sandbox excludes anything non-deterministic — no clock, no network, no randomness, with time supplied as data — this returns the outcome the decision recorded. If it doesn’t, that is a finding in itself. The export preserves the bundle’s file layout because the digest is over the whole set — so an auditor can verify the digest on the decision as well as re-run the policy, rather than taking on trust that the exported source is what ran.
An auditor can reach the same conclusion independently, with a tool Kosli doesn’t control. The same export doubles as a regression fixture: commit input.json and data.json beside your policy and assert against them with opa test, and a real past decision becomes a permanent test case.
Re-running a stored decision through a different policy or version — to ask “what would the new policy have said?” — is out of scope for the beta. Exporting the fixture and running it locally is the way to answer that question today.

Inspect a failing evaluation

When a policy denies, an application team needs to know why — and the decision records it through its violations. Whether the policy’s rules weren’t met (for example, a pull request with no approver) or an expected attestation was missing from the trail, both surface as violations on the decision. A policy can return more than allow and violations — any additional fields in its result are captured on the decision, so you can surface richer, human-readable context beyond a bare pass/fail. When you gate a pipeline with --assert, Kosli prints the violations as markdown so failures are readable directly in CI logs:
In the Kosli UI, the violations are parsed out of the decision and rendered on the decision detail (shown above under View the decisions for a policy), so an application team can see exactly what to fix.

Out of scope for the beta

The following are explicitly not included in the beta and may be addressed later. They are listed so you can tell what has been deferred from what has not been considered — if something here blocks you, that is worth telling us.
  • Evidence-attachment content evaluation — evaluating the contents of attached files. Summarize the attachment into JSON at attestation time — --attestation-data for a custom attestation type, --user-data otherwise — and a policy can evaluate that instead — see What the policy sees in input.
  • Composable policies — combining several separately published policies, each with its own identifier and version, into one evaluation. This is distinct from a bundle, where one policy spans several Rego files under a single identifier, version, and digest. Bundles are supported.
  • Non-trail evaluate targets — snapshots, artifacts, and generic input.
  • Scheduled evaluations and risk measurement.
  • Associating policies to controls — a stored policy-to-control link that lets Kosli infer which policy closes which control. In the beta you name the control per evaluation with --control; there is no stored mapping.
  • Pinning an evaluation to a policy version — an evaluation always uses the policy’s current version. There is no way to hold a pipeline on version 3 while version 4 is rolled out.
  • Re-running an evaluation in Kosli — you cannot replay a stored evaluation, either through a different policy or version to compare outcomes, or as-is to check that it still reaches the same answer. Export the fixture and run it locally.
  • Advisory outcomes — a decision is compliant or non-compliant. There is no advisory or warn-only mode that records a concern without failing the control.
  • Verbose evaluation tracing — beyond violations and any custom result fields the policy returns, there is no rule-by-rule trace of how a policy reached its answer.
  • Filtering decisions by branch or environment — decisions can be filtered by outcome, policy version, and time, but not by where the change came from or where it was heading.

Next steps

Feedback

This is a design preview. If you are reviewing the proposed beta flow, the most useful feedback is on the command surface (kosli evaluate policy and how policies are published), the Rego authoring contract — particularly any constraint there that would stop you shipping a control you need — the decision and audit model, and anything in What’s out of scope that you would expect in the beta. Share comments with the Kosli team.
Last modified on August 3, 2026