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.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 The CLI prints
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.outputs.violations when the policy denies. Violations are diagnostics only and must not drive the allow decision..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:
*.regofiles, except*_test.rego.- Data files named
data.json,data.yaml, ordata.yml, and*.ergo.yamlor*.ergo.ymlfiles. Each is mounted underdataat its directory path.
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 ashttps://github.com/kosli-dev/ergo/releases/download/rego-v0.2.0/ergo.rego. Theergo_specin the YAML must match the release:rego-v0.2.0readsergo_spec: v0.2.- A
*.ergo.yamlfile with the subjects and requirements. Kosli loads it intodata. - A
.regofile in the policy package that hands the requirements to Ergo and declaresallow.
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:
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 withdefault 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:
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:
requirements_met is undefined, the rule body fails to evaluate and allow remains false.
Rule 3: violations are diagnostics only
In aviolations 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.
schemas annotation on the policy package. The schema has to be written inline; a reference to a schema elsewhere is ignored.
--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: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, usekosli 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:
--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.
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.sendandnet.lookup_ip_addr. A policy that uses one does not compile. Readinput.evaluation.timefor 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
$refof a schema annotation must point within that schema, starting with#, or the policy does not compile.opa checkwith the capabilities file refuses a$refto a URL, but accepts one to afile://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 ininput.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.
- Ergo
- Rego
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
.regofile that declarespackage policy. Kosli readsdata.policy.allowanddata.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, runkosli 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.
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
- Rego Style Guide: naming, rule structure, and test conventions
- OPA Annotations: including
entrypoint: trueandschemas - Tutorial: Working with controls
- Tutorial: Evaluate trails with OPA policies