Skip to main content
This feature is in beta. Controls, decisions, the controls CLI commands (kosli create control and related), server-side evaluation (kosli publish rego-policy and kosli evaluate policy), and the for_control policy requirement are all available in Kosli but in beta — the UI, CLI flags, and policy YAML schema may still change. It is rolled out per organization, so reach out to the Kosli team to enable it for yours, or with any questions and comments.
Controls in Kosli represent the named, identifiable governance requirements that your organization enforces across software delivery — things like “source code review”, “no hard-coded credentials”, or “vulnerability scan passed”. They are the things auditors ask about, the things compliance teams track, and the things governance platform engineers build automation around. Without controls as first-class entities, Kosli can tell you that an attestation was made, but not which governance requirement it satisfies. Controls close that gap: they connect the evidence you collect in pipelines to the specific requirements that auditors, control owners, and regulators care about. This tutorial covers how to:
  • Define a control library in Kosli that mirrors your existing controls catalog
  • Write, test, and publish a policy that decides a control
  • Record decisions against controls by evaluating published policies from your pipelines
  • Require controls in environment policies and gate deployments on them
  • Review the decisions and version history of each control
  • Manage your catalog: list and archive controls

Prerequisites

Setup

Understanding controls

Before creating controls, it helps to understand how they fit into the Kosli data model. Controls are the named governance requirements your organization enforces. They have a stable identity (the control identifier), a human-readable name, and an optional description and links pointing back to your GRC system or policy document.
Kosli holds a mirror to your existing control definitions — it does not replace your GRC system or ServiceNow instance. The control catalog in Kosli is a lightweight copy that enables querying and reporting.
Raw fact attestations are the evidence you collect in pipelines — test results, vulnerability scans, pull request approvals. These are facts about what happened. Policies are the rules that judge whether the evidence satisfies a control. A policy is written as YAML requirements with Ergo, or in plain Rego, published to Kosli by an org admin, and evaluated by Kosli against the trails you name. Decisions are the recorded outcomes of a Policy Decision Point (PDP) — the step in your process where a judgment is reached about a specific control: “control RCTL-043 is satisfied for this artifact.” In Kosli, the PDP is a server-side evaluation: Kosli evaluates a published policy and records the outcome as a decision against the control. A decision is recorded at the point where the judgment is made — typically during a release or promotion step. A decision must be complete: one decision closes a control, and there is no partial decision. When a control depends on several checks — for example unit, integration, and system tests — the policy that records the control’s decision has to cover all of them.
Multi-level evaluations are not supported yet: a policy cannot read the outcome of other evaluations or decisions, so several evaluations cannot be combined into one control’s decision. Until then, write one policy per control that checks everything the control requires.
This separation matters: raw facts exist independently of controls. A JUnit test report is a fact. Whether that test report satisfies a “test coverage” control is a decision. The decision references the fact; the fact doesn’t need to know about the control. Environment policies state what an artifact must satisfy to run in an environment, and can require a passing decision for named controls. A Policy Enforcement Point (PEP) is where that requirement is acted upon: kosli assert artifact --environment checks an artifact against the environment’s policy and exits non-zero if it doesn’t comply. Place it at the point in your pipeline where you want to gate progress on control compliance.

Creating a control

A control has the following fields: Only org admins can create or edit controls. Create one in the Kosli app, with the CLI, or through the API.
Navigate to Controls in the Kosli app sidebar, select New control, and fill in the fields.
Each change to a control’s name, description, or links creates a new version. Make one with kosli update control, or from the control’s detail page in the app:
Decisions always reference the version of the control that was current when they were recorded, so the audit trail stays precise as controls evolve. Tags are added separately — from a control’s detail page, or with kosli tag control CONTROL-IDENTIFIER --set key=value. Tagging doesn’t create a new version.
Control identifiers are the stable identity that decisions and environment policies reference. Choose identifiers that match how your organization already refers to controls — for example, the identifiers in your ServiceNow or GRC system. If you use RCTL-043 today, use exactly that.

Writing a policy for the control

A decision comes from a policy: Kosli evaluates a policy, written with Ergo or in plain Rego, against your trails and records the outcome against the control. Write the policy, test it against a real trail, then publish it. The example below decides RCTL-043 with the simplest possible check: the trail has a compliant pull request attestation. The attestation name is read from params — values passed to each evaluation with --params — so the same policy works whatever your pipelines call the attestation.
Ergo is an open source Rego library, maintained by Kosli, that lets you write a policy’s requirements as YAML sentences instead of Rego rules. Its report records every check it ran, passed or failed, which makes a denial easy to read.Download ergo.rego from a pinned release, so a new Ergo version can’t change your policy unexpectedly, and create a directory for the policy:
Write the requirements in rctl-043/policy.ergo.yaml. Kosli loads *.ergo.yaml files into data, so the policy can read them:
rctl-043/policy.ergo.yaml
Then hand the requirements to Ergo in rctl-043/policy.rego. allow is Ergo’s overall answer, and violations lists the checks that failed:
rctl-043/policy.rego
Pass both the policy directory and the library wherever a command takes --path:
See the Ergo documentation for subjects, sentences, and the report format.

Test the policy

Before publishing, evaluate the local files against a trail that already has the attestation. --path sends the files with the evaluation, Kosli runs them exactly as it will run the published policy, and nothing is recorded:
The command prints RESULT: ALLOWED or RESULT: DENIED, with the violations. Try it against a trail without the attestation to see the denial. See Seeing the input to inspect the data the policy receives.

Publish the policy

Publish the policy under an identifier. Each publish stores a new version, and evaluations run the newest valid one. --input-schema trail gives the policy a single trail moment as input.trail:
Only org admins can publish, and only a published policy can record a decision. This keeps the judgment out of the hands of the team being judged: Kosli runs the evaluation and records the decision itself, so a pipeline cannot swap in its own policy or alter the result on its way into Kosli. A policy and its control are versioned separately. The control states the requirement and changes rarely; the policy implements the check and changes whenever you refine it, for example to read a new attestation. Publishing a new policy version doesn’t change the control, and editing the control doesn’t change the policy. Each decision cites both versions.

Recording a decision against a control

Evaluate the published policy and name the control it decides. Use --fingerprint to identify the artifact the decision applies to — this is what allows kosli assert artifact --environment to later check that all required controls have passing decisions for that artifact specifically.
Kosli evaluates the policy where the trails are stored and records the decision in the same request, so the outcome never passes through your pipeline. The decision carries its evidence: the policy version that ran, the params, the result, and the policy files as an attachment. You can record more than one decision for the same control; the most recent one is used when evaluating compliance.

Pinning a policy version

By default, --identifier runs the policy’s current version, so publishing a new version changes what every later decision runs. To keep a control’s decisions on a version you have reviewed, pin it by appending @v<n> to the identifier:
The pinned version must exist and have passed validation, or Kosli refuses the evaluation. The decision cites the version that ran, and --params must match that version’s params schema. Move the pin when you are ready to adopt a newer version.

Evaluating several trails

A control often depends on evidence spread across trails, such as a build trail with the pull request and a release trail with the approval. Name each trail with its own --context. Kosli reads them all at the same instant and passes them to the policy as the input.trails array:
A policy that reads several trails must be published with --input-schema trails, and reads input.trails instead of input.trail. The order of the array carries no meaning, so select a trail by its flow_name and trail_name. The decision is still recorded in the one trail named by --flow and --trail. See Input data and the multi-trail example.

Requiring controls in environment policies

Environment policies define the requirements an artifact must satisfy before it can run in a given environment. You can require that specific named controls have a passing decision recorded, making individual controls explicit policy gates.
prod-policy.yaml
Each attestations rule with type: decision and a for_control identifier requires a compliant decision recorded for that control. The control must already exist in your organization — unlike recording a decision, a policy that references an unknown control identifier is rejected. With this policy in place, an artifact deployed to this environment will be marked non-compliant if a passing decision has not been recorded for that artifact for each listed control. The artifact can still be deployed — Kosli records compliance state but does not block deployments by default. To gate deployments on compliance, use kosli assert in your pipeline. This abstracts the policy from the specific tooling your pipelines use: instead of “has an attestation of type snyk with zero criticals”, the policy expresses “control RCTL-1866 has been satisfied” — and the decision attestation carries the evidence of how that judgment was reached. For the complete policy syntax, including attestation requirements and exceptions, see the Environment Policies page.

Asserting artifact compliance

kosli assert artifact --environment is the Policy Enforcement Point (PEP). It checks that an artifact satisfies all requirements in the environment’s attached policy — including any controls required via for_control rules — and exits non-zero if it doesn’t. Use it as a pipeline gate before promoting an artifact to an environment.
If the artifact satisfies all policy requirements — including passing decisions for every listed control — the command exits 0. If not, the command exits non-zero and prints which requirements were not met, failing the pipeline step.
kosli evaluate policy --control is the PDP: it records a judgment about a control. kosli assert artifact --environment is the PEP: it enforces that judgment as part of the environment policy. Keep them in separate pipeline steps — the PDP step is where evidence is evaluated and the outcome is recorded; the PEP step is where the pipeline gates on that recorded outcome.

End-to-end pipeline flow

The sequence below shows a single artifact moving through a build trail and a release trail, with the compliance decision made at release time and enforced by the release controller checking the environment policy.

Viewing a control

Navigate to Controls in the Kosli app and select a control to see its detail view. The header shows the identifier, current version, name, any links, description, and tags. Below it are two tabs: Decisions and Versions.

Decisions

The Decisions tab is the default view. It lists every decision recorded against this control across all flows and trails, with the timestamp, the trail it was recorded on, the artifact fingerprint, and the outcome (compliant or non-compliant). Filter by outcome and flow, and sort by timestamp, to narrow the list.
Decisions tab for a control showing decisions with timestamp, compliant/non-compliant outcome, trail, and artifact fingerprint
Select a decision to open its detail, showing the trail it is recorded on, the trails it was evaluated against and when, the policy and version that judged it, the artifact it applies to, and the evaluated policy bundle (policy.tar.gz) attached as evidence.
Decision detail tray showing the trail, the trail it was evaluated against, the policy and its version, the artifact, and the policy.tar.gz evidence file for a decision

Versions

The Versions tab lists every version of the control definition — the name, description, and links as they were at each version, with who made the change and when. Because every decision references the control version that was current when it was recorded, this history lets auditors see exactly what the control required at the time of each decision. Changes to the policy that decides the control don’t appear here: policy versions are tracked separately, and each decision names the policy version that judged it.

Managing your controls catalog

As your catalog grows, list and search it, and archive controls you retire.

Listing controls

Navigate to Controls in the Kosli app sidebar to browse your full controls catalog. Search by name or identifier, and filter by tag to narrow the list. Each control shows its identifier, description, tags, name, and current version.
Controls catalog listing controls with their identifier, version, name, description, and tags, with a search box and tag-filter and sort controls
You can also list controls from the CLI with kosli list controls, fetch a single control with kosli get control CONTROL-IDENTIFIER, or use the controls API (GET /api/v2/controls/{org}).

Archiving a control

When a control is retired, archive it rather than deleting it. Archiving moves the control out of the main catalog into its Archived view but preserves its full history — past decisions and every version of the definition remain intact for audit — and an archived control can be unarchived later if it comes back into use. Archive or unarchive a control from its detail page (org admins only), from the CLI with kosli archive control CONTROL-IDENTIFIER and kosli unarchive control CONTROL-IDENTIFIER, or through the controls API (POST /api/v2/controls/{org}/{identifier}/archive and .../unarchive). Both actions appear as events in the control’s Versions tab, alongside Created and Edited events, so the full lifecycle is auditable.

What you’ve accomplished

You have learned how to define controls in Kosli, write, test, and publish a policy that decides a control, record decisions by evaluating that policy with kosli evaluate policy --control as the PDP, require controls in environment policies with for_control, assert artifact compliance using kosli assert artifact --environment as the PEP, and review the decisions and version history of each control. Your controls catalog is now the bridge between the evidence Kosli collects and the requirements your auditors, control owners, and regulators care about. For each production change, you can now answer: “which of our controls have a compliant decision recorded, and which don’t?” From here you can:
Last modified on November 3, 2026