Skip to content

ergo.rego 0.3.0

Choose a tag to compare

@github-actions github-actions released this 30 Oct 21:46
· 69 commits to main since this release
rego-v0.3.0
9151aa7

Changed

  • This version follows spec 0.3, so a policy says ergo_spec: v0.3. One that says v0.2 is written wrong, with unsupported version v0.2, expected v0.3, and still works with 0.2.0. The report's ergo changes with it, from "ergo": "v0.2" to "ergo": "v0.3".
  • min_subjects is a whole number from 0 to 1,000,000. A requirement with a larger one, like 1000001 or 1e308, is written wrong, with its $well_formed row failing and no other row, where before ergo.rego read any whole number up to what a 64-bit float holds, and rejected some big ones like 1e308 by accident. The message for every wrong min_subjects, -1, 1.5, "2" and 1000001 too, is now not a whole number from 0 to 1000000, where it was not a whole number of 0 or more. A number past a float, like 1e400, still says number out of range. Reports change for such policies: the $well_formed row shows the new message.
  • A subject with an identified_by whose from line uses named by each of is written wrong, with not allowed with named by each of: the key is the id under identified_by in the $well_formed row, and no other row for its requirements. Before, ergo.rego ignored the identified_by and used the key as the id.

Fixed

  • A sum of or a count of with a path doesn't read the path of an item its whose can't decide, so the side fails with the whose's own cause, as tally.whose says. It read the path and gave the cause of what it found. With sum of items.x whose k is "a" is 1 on {"things": [{"id": "t", "items": [{"x": "ab", "k": null}]}]}, the check now fails as null, where it failed as unusable. With k missing it now gives absent, and a field that is missing with k null gives null, where it gave absent. The same goes for a path that goes on, like items.x.y, and for count of items.x whose k is "a": on [{"k": null}] it went from absent to null, and count of items.x.y whose k is "a" is 1 on [{"x": "s", "k": null}] from unusable to null. Only the cause changes.
  • A for line whose item path goes on into a list item that isn't an object, null included, fails that item as not_an_object, as every and some do. It failed as unusable, or as absent for a null item. With t set to ["a"] or [null], for: every t.k as x now fails item 1 of t as not_an_object. count of and sum of fail a null item as not_an_object too, where they said absent. The same checks fail as before, and only the cause changes, in the row, in failed_items and in the requirement's causes.
  • A for item that picks with brackets, like t[0] as i, is written wrong, as the spec says. Before, ergo.rego read the brackets as a position, so t[0] as i meant first of t as i. It fails $well_formed with for: an item starts with "every", "some" or a position, like "last of", and the requirement gets no other row. The same goes for an item after the first, like every t as x, t[0] as i, which used to be read. first of t as i and brackets after every or some, like every t[0].u as i, are read as before. Reports change for a policy that has such an item: with {"things": [{"id": "a", "t": [{"u": [1, 2]}]}]}, for: t[0] as i with assert: i exists used to be satisfied, with the input item 1 of t, and is now not met.
  • A child subject under a parent that isn't an object fails $min_subjects as not_an_object, as the spec now says (subjects.chain.not_an_object). Before, a parent that was a string, a number, a list or a boolean gave unusable, and a null parent gave absent. Reports change for such inputs, like {"trail": {"artifacts": ["x"]}} with a pull request subject from artifact.pull_requests. The verdict doesn't change, and a parent that is an object without the field still gives absent.
  • A from line that is another subject's name and then named by each of, like artifact named by each of $params.keys, reads the keys inside each parent subject and puts the parent under in, as the spec says. Before, ergo.rego read a top-level input field with the parent's name, so a check could pass on data the policy never pointed at. A parent that isn't an object fails $min_subjects as not_an_object. A first key with a dot, like .artifact named by each of $params.keys or .artifact.p, reads the input field and not the parent, and a position, like artifact[0] named by each of $params.keys, first of artifact named by each of $params.keys or artifact.x[0] named by each of $params.keys, is no longer dropped: it reads the parent as a list and fails $min_subjects as unusable. The report prints .artifact.p and .artifact named by each of $params.keys with their dot, in subjects.<name>.from and in the $min_subjects and $unique_ids expressions, where it printed artifact.p, which reads the parent. A policy with such a from line could pass on data it never pointed at. Reports change for such a policy: with {"artifacts": [{"id": "a1"}], "artifact": {"x": {"ok": true}}} and keys ["x"], ok is true used to pass on /artifact/x/ok and now fails as absent for x in a1.
  • A sum of or a plus whose numbers can't all be held as whole numbers below 10^15 at one scale fails as unusable, with its entry null. The scale is the most decimal places any number added has, counted on the value, and the result counts too. More than 15 decimal places fails too. Before, such a sum fell back to the runtime's own addition, which gave different answers in different runtimes. Reports change for such inputs: x plus y is at least 0 on {"x": 0.123456789012345, "y": 100} was satisfied and now fails as unusable, and so does 1e20 plus 1. Sums of ordinary numbers, and numbers with trailing zeros like 1.500000000000000000, are added exactly as before.
  • A within check works out the gap exactly on the decimals, where it used OPA's arithmetic, which isn't exact. That's promised for numbers with at most 15 significant digits, the ones the spec covers. Reports change for such inputs: x equals y within 0.2 on {"x": 0.3, "y": 0.1} failed as value and is now satisfied, and so is 0.1 and 0.3, while within 0.19999 still fails as value. A check that passed before still passes, except one whose two sides and within number have exponents more than 4096 apart, like 0 and 1e-5000 within 1, which passed and now fails as unusable.
  • A from line with named by each of fails $min_subjects when the object it reads the keys from is missing, null or not an object, as absent, null or unusable, so the requirement isn't met. Each listed key is still a subject and gets its rows, as before. A key that isn't a string makes no subjects and fails $min_subjects as unusable. Before, from: scans named by each of $params.required on an input without scans gave one subject per key, so a check like x does not exist passed for each and the policy was compliant. The same holds when a ref step before it can't be used, like scans named by $params.env named by each of $params.required with env set to 5, which used to be met too. A child subject of such a parent gets one $min_subjects row with no in, failing with the parent's cause, and one $unique_ids row with no in, instead of a row per key.
  • When the input is a list, from: $input, from: every $input and an every walk that starts at the input, like from: every commits, make each item a subject. They failed $min_subjects as unusable. Each item gets its own rows, with locations like /0/x, and an empty list fails $min_subjects as value. A position on a list input, like from: $input[0] or from: first of $input, reads the item, and ends absent past the end or on [], where it failed unusable. The same goes for a child subject whose from starts with a position on a parent that is a list, like artifact[0].c: it reads the item of each parent, and a parent that is [] or null fails $min_subjects as absent. A field, like $input.commits, stays unusable on a list. The $min_subjects and $unique_ids expressions of from: every $input and from: every $input whose ... now keep every, like count(matching(every $input)) >= 1, on any input and not only a list. A report for a policy whose subjects come from a list input changes, and so does the expression text of every policy with from: every $input.
  • subjects at the top of a report is written the way the spec says for a policy written wrong. A chain of more than three subjects lists every subject in it, not only the last. A from line that reads but has a problem, like values of mixed types, shows as <invalid from> instead of the line. A subject whose from starts at another subject lists that subject even when the line is written wrong, as with every parent.items, and a subject that narrows another has that subject's id then, even with an identified_by of its own. A from that doesn't parse, or isn't a string, lists no other subject, as before.
  • A check written wrong with values of mixed types and nothing else is printed as written, like every other check written wrong, and its $well_formed input is named after the check. Before, its expression was printed from the parse and its input was named by the place inside the check. Reports change for such checks: x is one of 1.0, 'a' had the expression x is one of 1, "a" and now has x is one of 1.0, 'a', and its input, which was named by the place inside the check, is now named after the check: checks.f.checks.0 under a for line is now checks.f, checks.g.checks.1 in a list of assertions is now checks.g, with line 2: in front of the problem, and checks.c.whose.0 or checks.c.left.whose.0, for mixed values in the whose of every xs whose … or count of xs whose …, is now checks.c. A problem in the whose of a for item was checks.i.items.0.whose.0 and reads for: values of mixed types under checks.i. Every problem with the for line now starts with for: , so a form its whose can't take, like "starts with" can't take a count of, a sum of or a plus, an unknown alias in a for item, and a walk over more than ten steps on the for line, which had no prefix, now have one. A check now lists values of mixed types with every other problem it has: on another line, like line 2: doesn't parse, on the same line, like y is compared with itself, after a for line written wrong, like for: 5 with invalid for, or after one that doesn't parse, like for: every xs. Before, the other problem hid it.
  • A whose check that compares two fields names the one it couldn't read in failed_items, or the left side when neither could be read or both were. Before, ergo.rego named the side that isn't a count of, or the left side when there was no count, so with every items.status whose lo is at least hi is "ok" and an item {"lo": 1} it named items[0].lo with 1, and now names items[0].hi with null. A field that's null counts as not read too, and so does the list of a count of or sum of that isn't a list, so whose count of kids is at least min on {"kids": 5, "min": 1} names kids with 5. A plus side is named when it's the one not read, like hi for whose lo is at least hi plus 1 on {"lo": 1}. A side that reads outside the item is named by its own path with its own location: a $params side that isn't given is $params.min, at $params:, where it was named items[0]."$$params".min, a part is its alias, like w, and a parent's field is like art.q. Reports change for such items: only the path and value of an undecided item in failed_items. Which checks pass, their causes and the statuses don't change.
  • A subject name with a $ is quoted where Ergo writes it into a report, as the spec says (name.keys). With a subject $x and a child subject read from "$x".items, the $min_subjects and $unique_ids expressions say count(matching("$x".items)) >= 1 and count(repeated(ids("$x".items))) == 0, where they said $x.items, which can't be pasted back into a sentence. The from of a subject named $x that has no from is "\"$x\"" in report.subjects, where it was "$x". Which checks pass doesn't change.
  • A for line whose second item has a position, like every xs as p, first of ys as q, lists a failed list once in failed_items, as one without a position does. With ys set to "s", missing or null, the row failed as unusable, absent or null with "failed_items": []. It now has {"item": "ys", "path": "ys", "cause": "unusable", "value": "s", "failed_assertions": [], "locations": ["/things/0/ys"]}. The same goes for some xs as p as the first item and for last of ys sorted by n as q. An empty ys still fails as absent with no entry. Only failed_items changes.
  • A from line with every and a first key with a dot that is named like a subject, like every .artifact.p or every .artifact, reads the top-level input field, as a dot means. It was written wrong, with every can't go with a from line that starts at another subject yet, so $well_formed failed and the requirement got no other row. Now it makes one subject per value the walk ends at, and the report prints it with its dot. A from: .artifact with no every no longer narrows the subject artifact: it reads the top-level field artifact, where it used to take the subject's items and their id. Without the dot, every artifact.p is still written wrong. Reports change for a policy with such a from line.
  • Inside a whose, a part's alias on the right side was read as a field of the item when it was a term of a plus or the path of an elapsed time, so a check could pass on the item's own data. With the part pa set to meta, every items.status whose lo is at least pa.w plus hi is "ok" on {"kids": [{"id": "K", "meta": {}, "items": [{"lo": 5, "hi": 1, "pa": {"w": 1}, "status": "ok"}]}]} was satisfied, because it read the item's own pa.w, and every items.status whose lo is at least pa.w is "ok" failed as absent. Both now read the part, so both fail as absent, and with meta set to {"w": 1} and no pa on the item, the first now passes where it failed. The same went for some, a for line's item and days elapsed from pa.t to hi, with or without plus. A parent's name did the same in the whose of a count of, like count of items whose lo is at least days elapsed from fam.t to hi is 1. Reports change for such checks.
  • A second for item that picks, like first of ys whose k is p.k as q, can read the first item's alias in its whose. ergo.rego worked out the pick before the first alias was bound, so such a check always failed as absent. It now picks for each item of the first list, so the check passes or fails on the data. The same checks without the first alias in the whose are read as before. Reports change for such policies: with {"s": [{"xs": [{"k": 1}], "ys": [{"k": 1, "n": 1}]}]}, for: every xs as p, first of ys whose k is p.k as q with assert: q.n is 1 used to fail as absent and is now satisfied, and its inputs list the picked item 1 of ys instead of the list. A first item that picks nothing, because ys is empty or no item has its k, fails the check as absent and isn't listed in failed_items. A ys that is missing, null or not a list is listed once in failed_items, not under each first item, and an empty first list still shows ys[] in inputs.
  • The inputs of a failed sentence on a for line follow for.failure_inputs when a walk reads a name that sorts before $, like a whose that reads $input or a parent, or a walked path like "#rows": the entries that start at an alias first, sorted by name, then the rest. Before, what the whose read came first. With for: every xs as x and every rows.n whose z is at least $input.q is at least x.v, the names were $input.q, x.v, rows[].n and are now x.v, $input.q, rows[].n, in failed_assertions and in items. Whether a check passes and its cause don't change.
  • On a for line whose first item has a whose and whose second item starts at the first item's alias, the row's inputs showed the second list's values under every first item, including the ones whose left out or couldn't decide. They're shown only under the first items that whose kept now, as for.inputs says, and the first item's own entry still lists every item. Reports change for such checks. The verdict, the cause and failed_items don't.
  • ergo.rego reads the grammar of a sentence, a for line or a from line before it looks at its names, so an unknown $<word> no longer hides the rest of the line. A line with an unknown name and a syntax error, like parent.items whose zz is $foo foo bar, says doesn't parse instead of unknown name $foo, and one that reads, like parent.items whose zz is $foo, says unknown name $foo next to its other problems, a comparison of a path with itself included, as x is x and y is $foo does. A subject whose from has an unknown name lists the subject it starts at in report.subjects, and one that narrows another has that subject's id and parts.
  • A child subject whose from is another subject's name followed only by a position, like p[1], p[0], first of p or item 2 of p, reads that position inside each parent, as subjects.chain.not_an_object says. Before, ergo.rego dropped the position and narrowed the parent, so a requirement could be met when the item wasn't there. With ps set to [{"x": 1}], k with from: p[1] now fails $min_subjects as unusable, and it used to be met. A parent that is a list gives the item at that position, or fails as absent when the list is too short, which ergo.rego couldn't read before. Reports change for such policies.
  • A subject whose from has every and a named by that reads a missing, null or wrong-typed param no longer takes its requirement out of the report. $min_subjects fails as absent, null or unusable, as it does without every. With from: every x named by $params.k and params {}, requirements and results used to be empty and ergo.violations gave nothing. Reports change for such policies.
  • last of p after a parent's name says last of isn't supported yet here again and still lists p in subjects.
  • A count of or a sum of input carries a part's part tag only when the list it counts or sums starts at the part, as parts.tag says. ergo.rego also tagged one whose whose only read the part. With a part pa: "pa", the check count of items whose w is pa.w is 1 and {"things": [{"id": "a", "items": [{"w": 1}, {"w": 2}], "pa": {"w": 2}}]}, the entry {"name": "count of items whose w equals pa.w", "value": 1, ...} had "part": {"id": null, "name": "pa"} and now has none. The pa.w entry keeps its tag, and count of pa.items whose w is pa.w keeps it too. No verdict or cause changes.
  • On the right of a whose, a count of or sum of that starts at a part's alias counts the part, not the item's field of the same name, as parts.where says. With a part x: "p", the check every xs.v whose n is count of x.z is 1 and {"things": [{"id": "a", "p": {"z": [1]}, "xs": [{"n": 1, "v": 1, "x": {"z": [1, 1, 1]}}, {"n": 3, "v": 5, "x": {"z": [1, 1, 1]}}]}]}, it counted each item's x.z (3), kept the item with n 3 and failed as value. It now counts the part's z (1), keeps the item with n 1 and is satisfied, and the row lists x.z with the part's tag. The same goes for some, count of … whose, sum of … whose and a for item's whose. The left side of a whose and a name with a dot, .x.z, still read the item's field. The ten-step limit now counts such a part's own path, so a part of ten names there is written wrong, as it is after every.
  • A parent subject's name on the right of a whose inside a count of or sum of, like fam.z in count of items whose n is count of fam.z is 1, read each item's field of that name. It now reads the parent, as it does in every, some and a for item's whose. The left side and .fam.z still read the item's field, and a parent's part counts its path toward the ten-step limit there.
  • A walk over ten steps on a for line, in its whose too, gives for: every and some over more than ten steps aren't supported yet, with the for: that sentence.for.line puts on every problem with the for line. It had no for: before.
  • A from line with every on a null input, like every commits or every $input[0], fails $min_subjects as null, as its failed_items already said, where it failed as absent. Reports change for such policies.