From edf817172dcd847059077e7e8d80ee682ed6361a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Gr=C3=B8ndahl?= Date: Mon, 7 Sep 2026 10:07:50 +0200 Subject: [PATCH 1/2] docs: document missing-value semantics and override handling in environment policies Two behaviors were described only in the changelog, so a policy author looking them up on the reference page found nothing: - Comparison operators and matches() evaluate to false when either operand is missing, and missing values are the common case (no provenance, absent tag). Adds a "Missing values" section under Functions, including the negation pitfall and the exists() workaround. - Attestation rules evaluate the newest attestation including overrides. Raised in review on #376. --- policy-reference/environment_policy.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/policy-reference/environment_policy.mdx b/policy-reference/environment_policy.mdx index 3b2baedf..b014cb57 100644 --- a/policy-reference/environment_policy.mdx +++ b/policy-reference/environment_policy.mdx @@ -73,6 +73,8 @@ An environment policy is a YAML file that declares compliance requirements for a A [policy expression](#policy-expressions). When present, this attestation is only required when the expression evaluates to `true`. + + A rule is satisfied by the newest matching attestation, including overrides. Overriding a non-compliant attestation can therefore bring an environment back to compliant, and overriding a compliant one down to non-compliant makes the environment non-compliant. @@ -152,6 +154,21 @@ Parentheses control precedence: `${{ flow.name == 'prod' and (flow.tags.team == | `exists(arg)` | Returns `true` if `arg` is not null. | `${{ exists(flow) }}` | | `matches(input, regex)` | Returns `true` if `input` matches the regular expression. | `${{ matches(artifact.name, "^datadog:.*") }}` | +### Missing values + +Every operator and function evaluates to `false` when either operand is missing. This holds for `==`, `!=`, `in`, `exists()`, `matches()`, and the ordering comparisons `<`, `>`, `<=`, and `>=`. + +Missing values are common rather than exceptional: + +- `flow` is empty for any artifact reported without provenance. +- `flow.tags.` is empty for a tag the flow does not have. + +A missing value never matches, so negating a comparison does not make it match either: for an artifact with no provenance, both `${{ matches(flow.name, "^snyk-.*") }}` and `${{ not matches(flow.name, "^snyk-.*") }}` evaluate to `false`. To branch on presence, test it explicitly with `exists()`: + +```yaml +if: ${{ not exists(flow) or not matches(flow.name, "^snyk-.*") }} +``` + ## Constraints - `_schema` is the only required field. All other fields are optional and use server defaults when omitted. From ba9464b6d1b217bfcd8034f8035974f5bdf3e425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Gr=C3=B8ndahl?= Date: Mon, 7 Sep 2026 11:19:56 +0200 Subject: [PATCH 2/2] docs: correct missing-value semantics and pin the override rule Review found the negation claim was an extrapolation, not something the source supports: from 'matches() is false when an operand is missing', a reader computes 'not matches(...)' as true, so the page needed a stronger propagation rule it never stated. Removed rather than replaced -- neither direction is verified, and the exists() workaround that depended on it went with it. - Scope the rule to the comparison, membership, and function forms; drop the 'every operator and function' over-claim. - Unify on 'missing' instead of mixing missing/empty/not null. - Pin the attestation match to name and type, and define what an override does, since no page on the site defines the term. - Move that prose above , matching every other ParamField. - Link the new anchor from the Exceptions section of getting_started/policies.md. --- getting_started/policies.md | 2 +- policy-reference/environment_policy.mdx | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/getting_started/policies.md b/getting_started/policies.md index 926504ff..fd648912 100644 --- a/getting_started/policies.md +++ b/getting_started/policies.md @@ -89,7 +89,7 @@ artifacts: ### Exceptions -You can add exceptions to policy rules using [policy expressions](/policy-reference/environment_policy#policy-expressions). +You can add exceptions to policy rules using [policy expressions](/policy-reference/environment_policy#policy-expressions). Note how expressions behave when a value is [missing](/policy-reference/environment_policy#missing-values), which is common for artifacts reported without provenance. ```yaml _schema: https://docs.kosli.com/schemas/policy/v1 diff --git a/policy-reference/environment_policy.mdx b/policy-reference/environment_policy.mdx index b014cb57..4c5cd2f0 100644 --- a/policy-reference/environment_policy.mdx +++ b/policy-reference/environment_policy.mdx @@ -60,6 +60,8 @@ An environment policy is a YAML file that declares compliance requirements for a List of attestations every artifact must have. Each element is a required-attestation rule. + A rule is evaluated against the newest attestation whose `name` and `type` match the rule, including override attestations — an override supersedes the status of the attestation it overrides. Overriding a non-compliant attestation can therefore bring an environment back to compliant, and overriding a compliant one down to non-compliant makes the environment non-compliant. + The [attestation type](#attestation-types) to require. Cannot be `*` when `name` is also `*`. @@ -73,8 +75,6 @@ An environment policy is a YAML file that declares compliance requirements for a A [policy expression](#policy-expressions). When present, this attestation is only required when the expression evaluates to `true`. - - A rule is satisfied by the newest matching attestation, including overrides. Overriding a non-compliant attestation can therefore bring an environment back to compliant, and overriding a compliant one down to non-compliant makes the environment non-compliant. @@ -156,18 +156,14 @@ Parentheses control precedence: `${{ flow.name == 'prod' and (flow.tags.team == ### Missing values -Every operator and function evaluates to `false` when either operand is missing. This holds for `==`, `!=`, `in`, `exists()`, `matches()`, and the ordering comparisons `<`, `>`, `<=`, and `>=`. - -Missing values are common rather than exceptional: +A value is missing when the context does not supply it. This is common rather than exceptional: -- `flow` is empty for any artifact reported without provenance. -- `flow.tags.` is empty for a tag the flow does not have. +- `flow` is missing for any artifact reported without provenance. +- `flow.tags.` is missing for a tag the flow does not have. -A missing value never matches, so negating a comparison does not make it match either: for an artifact with no provenance, both `${{ matches(flow.name, "^snyk-.*") }}` and `${{ not matches(flow.name, "^snyk-.*") }}` evaluate to `false`. To branch on presence, test it explicitly with `exists()`: +The comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`), the membership operator `in`, and the functions `exists()` and `matches()` all evaluate to `false` when either operand is missing. A missing operand is not an error — the comparison is simply not satisfied. -```yaml -if: ${{ not exists(flow) or not matches(flow.name, "^snyk-.*") }} -``` +To branch on whether a value is present at all, test it with `exists()` rather than relying on a comparison against it. ## Constraints