Repository navigation
core: let requirements and checks carry a description and meta - #151
Merged
Merged
Conversation
A requirement can now say what it asks for in `description`, and keep anything else, like a control id or an owner, in `meta`. Checks can have `meta` too. ergo copies both into the report and never reads `meta`. `meta` only holds strings and lists of strings, so every implementation writes the same report. Changes the report: every requirement entry and every check in it now has a `description`, `""` when none is written, and `meta` when one is. A check whose `description` isn't a string is now written wrong, where ergo used to echo it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A missing `meta` makes `meta.control` throw in JavaScript, where a missing key in `meta` just reads as undefined. So every requirement and every check in the report has a `meta`, ergo's own checks included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strings and lists of strings only was too strict: in YAML, `version: 2026` or `reviewed: yes` failed the requirement. meta now takes any JSON value. Its numbers follow the range rule numbers follow elsewhere, and a key that isn't a string or a set, which only Rego can write, is written wrong. The report writes meta's numbers in their plain form, the way expressions already do, so `1.50` and `1.5` give the same bytes, in OPA, Wasm and the JavaScript runtime alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filter can have them, and ergo checks them, but every filter shares the one `$applies` entry, which keeps ergo's own description. Also build the requirement entry as one object rather than a union. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
YAML reads an empty `description:` or `meta:` as null, and failing the requirement for a blank field punishes a gap, not a mistake. ergo only copies these two fields, so null can't change a verdict. Every other field still counts null as the wrong type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jbpros
marked this pull request as ready for review
October 7, 2026 06:38
This was referenced Oct 7, 2026
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.
Part of #114. Since #148, a requirement can't carry any wording:
descriptionfails 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 inrequirementsrather than on the rows, and type-checked. The labels half of #114 (#34) is untouched.descriptionon a requirement: what it asks for, in plain words. Checks already had it.metaon 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:
{"deployments": [ {"id": "d-1", "environment": "prod", "approved_by": "alice"}, {"id": "d-2", "environment": "prod"} ]}and this requirement:
{"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": {}):{ "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:
""and{}when not written, on every requirement and every check, ergo's own$checks included. Someta.controlnever throws in JavaScript: a missing key reads asundefined, where a missingmetawould throw.metaholds anything JSON can, so YAML likeversion: 2026orreviewed: yesjust works. Its numbers follow the range rule numbers follow everywhere else in a policy (1e400is 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.meta's numbers in their plain form, the way expressions already do:1.50,1e2and-0come out as1.5,100and0. OPA otherwise keeps a number exactly as it was written, so the samemetawould 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 ifmetais copied as written.nulldescriptionormetacounts as not written, since YAML reads an emptydescription:asnull. ergo only copies these two fields, so this can't change a verdict. Every other field still countsnullas the wrong type.descriptionthat isn't a string, ormetaof the wrong shape, fails$well_formed, on a requirement or a check (custom operators included). The report shows""or{}in its place.report.requirements[row.requirement]rather than being copied onto every row.A filter in
applies_tocan have both, and ergo checks them, but they don't reach the report, because every filter shares the one$appliesentry.The policy-level
descriptionandmetawait 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
descriptionandmeta,""and{}when not written. A check whosedescriptionisn'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.mainand 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 ametafield. I didn't run sdlc-policies' Python system tests, which may compare whole reports.🤖 Generated with Claude Code