Skip to main content
Server-side evaluation is in beta. kosli publish rego-policy and kosli evaluate policy are available in Kosli but in beta — the CLI flags, the policy input, and the result shape may still change. It is rolled out per organization, so reach out to the Kosli team to enable it for yours.
A Rego policy defines the rules Kosli evaluates trail data against. Kosli evaluates it on the server: an org admin publishes the policy once, and any pipeline evaluates it by identifier against one or more trails. Naming a control records the outcome as a decision against that control. Kosli runs the evaluation with a built-in evaluator, with no OPA installation required.
We recommend writing policies with Ergo, an open source Rego library maintained by Kosli that expresses requirements as YAML sentences. Plain Rego works too, and both follow the same contract. The older kosli evaluate trail and kosli evaluate trails commands evaluate a policy in the CLI instead. They follow a different contract and receive a different input, described in Local evaluation.

Policy contract

These rules are Kosli-specific conventions, not OPA built-ins.
boolean
required
The package that declares allow is the policy package; any package name works. Every other package in the bundle is a library the policy imports. If more than one package declares allow, mark the policy’s allow with an entrypoint: true annotation.allow must be a boolean for every input, so always give it a default. An undefined allow is an error (result_shape), not a denial. Drive it through a positive assertion, not through the absence of violations. See Safe policy design.
object
Every rule in the policy package annotated with entrypoint: true is returned in the result’s outputs, under its rule name and in its own shape. A rule that is undefined for the input is left out. A rule without the annotation is not returned, violations included.
The CLI prints outputs.violations when the policy denies. Violations are diagnostics only and must not drive the allow decision.
Policies are written in Rego v1. A .rego file in the older v0 syntax does not compile.

Bundle contents

--path takes files and directories, and Kosli publishes them together as one bundle:
  • *.rego files, except *_test.rego.
  • Data files named data.json, data.yaml, or data.yml, and *.ergo.yaml or *.ergo.yml files. Each is mounted under data at its directory path.
A bundle holds at most 100 files and 1 MiB. A data file cannot write data.params, which holds the params of each evaluation.

Writing policies with Ergo

Ergo is a single Rego file, ergo.rego, that evaluates requirements written in YAML. A policy written with Ergo is three files in one bundle:
  • ergo.rego, the library, downloaded from a pinned Ergo release, such as https://github.com/kosli-dev/ergo/releases/download/rego-v0.2.0/ergo.rego. The ergo_spec in the YAML must match the release: rego-v0.2.0 reads ergo_spec: v0.2.
  • A *.ergo.yaml file with the subjects and requirements. Kosli loads it into data.
  • A .rego file in the policy package that hands the requirements to Ergo and declares allow.
policy.ergo.yaml
policy.rego
allow follows Ergo’s overall answer, report.compliant, which is a positive assertion as Safe policy design requires. The report records every check Ergo ran, passed or failed, and is returned in outputs.report. Publish the policy directory and the library together:
A policy that imports Ergo cannot be evaluated with kosli evaluate trail, which takes a single file. See the Ergo documentation for subjects, sentences, and the report format.

Publishing a policy

kosli publish rego-policy IDENTIFIER stores a new version of the policy. Only org admins can publish, so the team whose trails are evaluated cannot write the policy that judges them. Publishing the same files and input schema as the newest version stores nothing new. --input-schema declares the input the policy reads: trail for a single trail, or trails for several. Kosli then validates the version: it compiles the policy in OPA’s strict mode against the schema of that input, so a policy that reads a field the input cannot have does not compile. A trails policy that reads input.trail fails here rather than at evaluation. To catch these errors before you publish, download the schema Kosli validates against and type-check the policy with opa check. Kosli serves one schema for each input: evaluation-input/trail/v1 for --input-schema trail, and evaluation-input/trails/v1 for --input-schema trails. Pass the capabilities file Kosli compiles against too, so the same check fails on a refused built-in function.
Below compliance_status, the input schema does not type fields, so strict mode does not check paths into attestation data.

Safe policy design

Three rules prevent a policy from allowing what it should deny.

Rule 1: use a fail-safe default

Always start with default allow := false. allow must be earned by evidence, not granted by the absence of evidence against it. Use parameter aliases at the top of the policy file rather than hardcoding threshold values. If a required param is absent, any rule that references its alias fails to evaluate, and allow correctly remains false.

Rule 2: drive allow through positive assertions

Drive the allow decision through a condition that must be true for the requirements to be met. Do not write:
When a violations rule body encounters an undefined reference, such as a missing param or an absent attestation field, OPA silently skips that rule body and adds no message to the set. The set is then empty, count(violations) == 0 evaluates to true, and allow fires even though the policy never checked its requirements. This produces a false allow. The safe pattern makes the requirement explicit:
If any field referenced inside requirements_met is undefined, the rule body fails to evaluate and allow remains false.

Rule 3: violations are diagnostics only

In a violations rule, an undefined reference causes the rule body to fail silently: no message is added. This is the safe failure mode for diagnostics. Violations explain a denial determined by the allow rule and must not determine it themselves.

Params

Pass params to an evaluation with --params, as inline JSON or as @file.json. They are available in the policy as data.params.*, and data.params is {} when none are sent. This separates policy logic from the thresholds it enforces, so one policy can cover several environments with different params.
Declare the params a policy accepts with a schemas annotation on the policy package. The schema has to be written inline; a reference to a schema elsewhere is ignored.
Kosli records the schema when the version is published, and refuses an evaluation of a published policy whose params do not match it, before anything is evaluated. Params sent with local policy files (--path) are not checked. Alias params at the top of the policy file so that missing values cause rules to fail rather than silently proceeding:

Input data

The input names the instant it was evaluated at, and every trail in the context, all resolved at that instant.
string
The time of the evaluation request, in RFC 3339 format in UTC. It is the policy’s only clock: Kosli refuses time.now_ns(). See Restrictions.
array
Every trail in the context, each a trail moment. Name each trail with its own --context trail=<flow>/<trail>, for example --context trail=my-app/build-123 --context trail=my-release/release-456. Always present, even for one trail. The order carries no meaning, so select a trail by its flow_name and trail_name.
object
The trail moment, present only when the context names exactly one trail. A policy published with --input-schema trail reads it, and Kosli refuses to evaluate such a policy against more than one trail.

Trail moment

A trail moment is the state of a trail at one instant.
string
The flow the trail belongs to.
string
The trail name (git commit SHA or custom name).
integer
The number of the moment within the trail’s history.
number
When the moment was recorded, as a Unix timestamp.
string
The flow template the trail was evaluated against.
object
Compliance data for the trail.

Seeing the input

Read a trail moment from the API to see what a policy will receive:
The last path segment selects the moment: latest, a moment number, or @{2026-10-26T06:40:05} for the newest moment at or before that UTC instant. GET .../moments lists a trail’s moments. The response is one trail moment; wrap it as shown in Input data to get the full input.

Replaying an evaluation

To see what an evaluation was given, use kosli get evaluation with the evaluation’s ID. kosli evaluate policy --output json prints the ID as id, and the error for an evaluation still pending after 30 seconds names it too. A pending evaluation can be read back as well, as its input does not depend on its outcome. --show-input adds the document the policy read as input under .input, with each trail as it stood when the evaluation was requested. It needs --output json:
To check a result independently, write what the evaluation was given to a directory with --fixture and --dir, then replay it with OPA. Kosli writes input.json, the params as data.json, and the files of the policy that ran under policy/. The directory must be empty or not exist yet.
The query assumes the policy package is named policy. Replace policy in data.policy.allow with your policy’s package name.

Restrictions

The server evaluates a policy the same way every time it is run against the same input:
  • Built-in functions whose result can change between runs are refused, including time.now_ns, rand.intn, uuid.rfc4122, opa.runtime, http.send and net.lookup_ip_addr. A policy that uses one does not compile. Read input.evaluation.time for the current time. The capabilities file lists every built-in function Kosli allows.
  • A policy reads nothing from the network or the server’s files. The $ref of a schema annotation must point within that schema, starting with #, or the policy does not compile. opa check with the capabilities file refuses a $ref to a URL, but accepts one to a file:// path, which fails only when you publish.
  • An evaluation covers at most 100 trails, and a trail named twice is evaluated once.
  • An evaluation that runs longer than 50 seconds fails with evaluate.

Results and exit codes

kosli evaluate policy waits up to 30 seconds for the result, then prints RESULT: ALLOWED or RESULT: DENIED, the violations, the policy version, and the decision, if one was recorded. --output json prints the evaluation as the server returns it: a completed evaluation carries result with allow and outputs, and a failed one carries error with a kind and a message. A denial is a completed evaluation with allow: false, never an error. Use --assert to fail a pipeline step on a denial.

Error kinds

Examples

Require a compliant pull request on every trail

Allows only when every trail in input.trails has a compliant pull request attestation. The attestation name is read from params. Publish it with --input-schema trails and evaluate it with one --context per trail.
Use the policy.rego from Writing policies with Ergo unchanged, with this YAML beside it:
policy.ergo.yaml

Local evaluation

kosli evaluate trail and kosli evaluate trails fetch trail data from Kosli and evaluate a local policy file in the CLI, passed with --policy. A local evaluation records nothing: it cannot record a decision against a control. To try a policy on the server’s input before publishing it, use kosli evaluate policy --path instead. To evaluate a policy file on the Kosli server instead, add --server-side (beta) to either command. Kosli builds the input from the trails you name as trail moments, so the CLI reads no trail data. A policy written for local input may need its field paths changed first. See Local input data. The flag can’t be combined with --attestations, --show-input or --output-rule, and a server-side evaluation takes at most 100 trails. A local evaluation differs from a server-side one:
  • The policy is a single .rego file that declares package policy. Kosli reads data.policy.allow and data.policy.violations, with no annotations needed.
  • No built-in functions are refused, so a policy that passes locally can still fail to compile on the server. Check it against the capabilities file first, as in Publishing a policy.
  • The input is the trail as the trails API returns it, not a trail moment.

Local input data

kosli evaluate trail passes the trail as input.trail, and kosli evaluate trails passes an array of trails as input.trails. There is no input.evaluation. Each trail carries the same compliance_status maps as a trail moment, with attestation fields enriched via --attestations, so paths such as input.trail.compliance_status.attestations_statuses[name] read the same in both. The trail-level fields differ: A local trail with two attestations of the same name keeps only the last one. Use --show-input with --output json to print the full input for a given trail. Pipe through jq to explore specific fields:

Local testing

To test a policy before publishing it, run kosli evaluate policy --path against a real trail: Kosli evaluates the local files exactly as it will evaluate the published policy, and records nothing. This works for Ergo policies and for policies split across several files.
For the local contract, use kosli evaluate input to test a policy against captured trail data without making live Kosli API calls:

Local exit codes

Exit code 1 is used for both denial and failure. To distinguish between them in CI, use --output json and read the allow field directly from the output rather than relying on the exit code.

Further reading

Last modified on November 4, 2026