Repository navigation
Conversation
A selector can define labels, a map of names to paths inside the item it picked. Every input read through that selector gets a labels field with those values, so a row shows which item each value came from, such as the id of the attestation a check read. Labels go on each input rather than the row, because one row can read through several selectors. They never change whether a check passes, its cause or its expression. Inputs read through a selector without labels are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jbpros
added a commit
that referenced
this pull request
Oct 2, 2026
…are clear (#98) Closes #27. Eight rules keep their names, because the docs tell people to call or define them: `report`, `report_with_params`, `violations`, `operators`, `op_passed`, `value_at`, `arg` and `leaf_passed`. The other 227 now start with `_`. This breaks any policy that calls a helper directly, like `ergo.row_cause`. ## How the rename was done OPA lets a local variable or parameter share a rule's name, so a plain find-and-replace wasn't safe. A script renamed identifiers outside strings and field reads (`.x`), and would have skipped any rule body that declared a local with the same name. It found none. Calls from other packages were renamed only where written as `ergo.x`. ## Other changes - Four tests in `ergo_test.rego` call `_is_violation`, `_definition_field` and `_rfc3339_shaped` directly. Two pin that `violations` reads only the requirements, and two test the RFC 3339 gate on its own (`compare_time` accepts numbers through another branch), so neither can be checked through the report. They stay where they are: Regal's `leaked-internal-reference` doesn't apply to `_test.rego` files, and moving them into `package ergo` would put test rules into `data.ergo`. - REFERENCE.md now names exactly the rules a custom operator may call, says what each does, and says the rest can change. It also drops `check_passed` from the list of rules that cause a loop, since it's private now, and fixes "Two rules:" over a list of three. ## Checks - `opa check --strict . --ignore .github`, `opa fmt --list .` (prints nothing), `opa test . --ignore .github` (642/642, the same count as main), and the `--strict-builtin-errors` run prints `true`. - The coverage command lists no unreached lines. - Only `ergo_test.rego` calls a `_` rule from outside `package ergo`. - The baking example's report is byte-identical to main's. - `regal lint --disable-all --enable leaked-internal-reference --ignore-files '.github/**' .` finds nothing. - 1,000 random policies and inputs give byte-identical reports and violations on main and this branch. #34 is the only open PR touching `ergo.rego`, so it will need its helper calls renamed when rebased. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 4, 2026
jbpros
added a commit
that referenced
this pull request
Oct 7, 2026
Part of #114. Since #148, a requirement can't carry any wording: `description` fails as an unknown field. This adds two fields, and answers #114's questions about notes in the policy: one agreed field, `meta`, on checks and requirements, kept in the definitions in `requirements` rather than on the rows, and type-checked. The labels half of #114 (#34) is untouched. - **`description`** on a requirement: what it asks for, in plain words. Checks already had it. - **`meta`** on a requirement and on any check: anything else to keep with it, like a control id, an owner or a framework. ergo copies it into the report and never reads it. With this input: ```json {"deployments": [ {"id": "d-1", "environment": "prod", "approved_by": "alice"}, {"id": "d-2", "environment": "prod"} ]} ``` and this requirement: ```json {"prod_deploy": { "description": "Every production deployment is approved", "meta": {"control": "SDLC-CTRL-0007", "frameworks": ["SOC 2"]}, "subject_type": "deployment", "from": ["deployments"], "id": ["id"], "checks": {"approved": { "description": "Someone approved the deployment", "meta": {"severity": "high"}, "op": "non_empty_string", "path": ["approved_by"] }} }} ``` the requirement's entry in the report starts like this (`$` checks left out, each of them has `"meta": {}`): ```json { "checks": { "approved": { "description": "Someone approved the deployment", "expression": "approved_by is a non-empty string", "meta": {"severity": "high"}, "op": "non_empty_string", "path": ["approved_by"] } }, "description": "Every production deployment is approved", "meta": {"control": "SDLC-CTRL-0007", "frameworks": ["SOC 2"]}, "require": "every", "status": "not_met", ... } ``` The rules: - Both are optional, but the report always has them, `""` and `{}` when not written, on every requirement and every check, ergo's own `$` checks included. So `meta.control` never throws in JavaScript: a missing key reads as `undefined`, where a missing `meta` would throw. - `meta` holds anything JSON can, so YAML like `version: 2026` or `reviewed: yes` just works. Its numbers follow the range rule numbers follow everywhere else in a policy (`1e400` is written wrong), and a key that isn't a string or a set, which only a Rego-written policy can hold, is written wrong too. - The report writes `meta`'s numbers in their plain form, the way expressions already do: `1.50`, `1e2` and `-0` come out as `1.5`, `100` and `0`. OPA otherwise keeps a number exactly as it was written, so the same `meta` would give different bytes depending on how it was typed, and differ from a JavaScript implementation. A test compares the marshalled reports of two policies that write the same numbers differently, and it passes under OPA, Wasm and the JS runtime. It fails if `meta` is copied as written. - A `null` `description` or `meta` counts as not written, since YAML reads an empty `description:` as `null`. ergo only copies these two fields, so this can't change a verdict. Every other field still counts `null` as the wrong type. - Any other `description` that isn't a string, or `meta` of the wrong shape, fails `$well_formed`, on a requirement or a check (custom operators included). The report shows `""` or `{}` in its place. - Violations are unchanged. The requirement's description stays in `report.requirements[row.requirement]` rather than being copied onto every row. A filter in `applies_to` can have both, and ergo checks them, but they don't reach the report, because every filter shares the one `$applies` entry. The policy-level `description` and `meta` wait for the shape of a policy to be settled in #113, since today a policy is a bare map of requirements and has nowhere to put them. Changes the report: every requirement entry and every check in it gets `description` and `meta`, `""` and `{}` when not written. A check whose `description` isn't a string used to be echoed and is now written wrong. ### Checked - `opa check --strict`, `opa fmt --list`, `opa test` (859/859), `--strict-builtin-errors`, Regal and the comment check, all clean. Full coverage. - OPA 1.20.2 in Docker (859/859), Wasm with OPA 1.19.0 (859/859), and the JS Wasm runtime script (859/859). - The README example run as Rego and as YAML gives the same report, and the reference's report sample is from that run. - Downstream, against `main` and this branch: sdlc-policies (95/95 across four policies), server npm-bump (6/6) and its 11 demo policies compile, pr-reviewer review-controls (97/97). None uses a non-string description or a `meta` field. I didn't run sdlc-policies' Python system tests, which may compare whole reports. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A selector can now define
labels, a map of names to paths inside the item it picked. Every value read through that selector is tagged with those fields, so a report shows where each value came from. Kosli wants this so a row can name the attestation a check read.gives this input on the row:
{ "name": "attestations.[type==pull_request].state", "value": "MERGED", "labels": { "evidence_id": "a1b2c3", "url": "https://example.com/prs/42" } }How it behaves:
compare, the options of anany_of, a substitute. A row-level tag would need a rule for which value wins when two selectors use the same name.null, and so is a label whose path leads nowhere. Iflabelsisn't an object, the input'slabelsisnull.labelsare unchanged, so existing reports are byte-identical.allandany, labels come from the selector in the check'spath. A selector in the element check adds none, and a path still holds only one selector.Known gap: if two
any_ofoptions read the same path with the samewherebut differentlabels, OPA errors instead of writing a failing row. The same error already happens today when two different paths render to the same name, so I've left both alone.kosli-dev/sdlc-policies#19 tries this in SDLC-CTRL-0007, with the pinned ergo plus this change.
Ran
opa check --strict . --ignore .github,opa fmt --list .andopa test . --ignore .github(336/336), and the coverage command lists no unreached lines.🤖 Generated with Claude Code