Skip to content

Ergo spec 0.3

Choose a tag to compare

@github-actions github-actions released this 30 Oct 21:45
· 68 commits to main since this release
spec-v0.3
9151aa7

Changed

  • A policy says ergo_spec: v0.3, and the report's ergo changes from "ergo": "v0.2" to "ergo": "v0.3". A policy that says v0.2 is written wrong, with unsupported version v0.2, expected v0.3. The schemas are under https://ergo.kosli.com/spec/0.3/.
  • An implementation may fail a check as unusable when it can't read or compare a number exactly, like one whose exponent is too long to hold (Values). It used to have to compare every number it was given. ergo.rego compares such numbers as before, so its reports don't change.
  • Whitespace of any kind at either end of a sentence, a for line or a from line is dropped before it's read, not only spaces: every character with Unicode's White_Space property, like a tab, a newline or U+3000 (sentence.ends). So a YAML block like assert: |, which ends in a newline, reads as the sentence it holds. ergo.rego already read these lines, so no report changes. A byte order mark or a zero-width space at either end still makes a line not read. The same goes for the path of an identified_by or a part, which ergo.rego also trimmed already.
  • min_subjects is now a whole number from 0 to 1,000,000. It used to say a whole number of 0 or more, so engines disagreed on big values. A requirement with a larger min_subjects is written wrong, and the message for every wrong min_subjects is not a whole number from 0 to 1000000, where it was not a whole number of 0 or more. A number a 64-bit float can't hold still says number out of range. The policy schema has "maximum": 1000000 on min_subjects. Reports change for such policies: a requirement with a min_subjects over 1,000,000 fails $well_formed and gets no other row, and the $well_formed row of any wrong min_subjects shows the new message.
  • subjects.keyed.id says that a subject listed by named by each of takes its key as its id, so an identified_by on it is written wrong, with not allowed with named by each of: the key is the id. Before, subjects.id said identified_by gave the id, with no exception, and ergo.rego ignored it and used the key. A report changes only for a policy that has both.
  • subjects.sentence says a position after another subject's name, like p[1] or first of p, isn't narrowing: it is a child subject that reads the position inside each parent, and subjects.from says a from line with every whose named by can't be read keeps its requirement and fails $min_subjects like one without every. New cases cover both. Reports change for a policy that relied on ergo.rego dropping the position or losing the requirement.

Fixed

  • for.flaws now says that an item of a for line that isn't an object, null included, fails as not_an_object when the item's path goes on into it, with some and a position too, as every does. It used to say so only for a second item that reads into the first, and the spec didn't say what the other items fail as. for.walk says it too. Reports change for such inputs: with t set to ["a"], for: every t.k as x failed item 1 of t as unusable and now fails it as not_an_object. ergo.rego also failed null items as absent, and count of and sum of on a null item as absent where tally.walk says not_an_object. Only the cause changes, not whether a check fails.
  • tally.whose now says that the path of an item the whose can't decide isn't read, so only its cause counts: null for a null k and absent for a missing one. Cases pin it for sum of and count of. ergo.rego read the path anyway, so such a side failed as unusable when the value wasn't a number, and as absent where k was null and the path missing. Reports change for such inputs, in the cause only.
  • sentence.for.pick now says that an index in brackets doesn't pick, so a for item like t[0] as i is written wrong, as the grammar already had it. ergo.rego read it as a position, so t[0] as i meant first of t as i, and a second item was read the same way. Reports change for such a policy: the check fails $well_formed and the requirement gets no other row.
  • A child subject whose parent isn't an object, null included, fails $min_subjects as not_an_object, as subjects.chain.not_an_object now says. The spec didn't say, and ergo.rego gave unusable for a string, number, list or boolean parent and absent for a null one. Reports change for such inputs. A parent that is an object without the field still gives absent.
  • A from line that is a subject's name and then named by each of, like artifact named by each of $params.keys, makes a child subject, as subjects.chain now says. It reads the keys inside each parent, with in, and a parent that isn't an object fails $min_subjects as not_an_object. A first key with a dot is the input field and never the parent, and the report prints it with its dot when it is named like another subject (sentence.print.canonical), and a position after the parent's name is read as a list and not dropped, so $min_subjects fails as unusable. ergo.rego read a top-level input field of the parent's name, and dropped such a position. Reports change for such a policy.
  • The spec now says these things that ergo.rego already did, so no report changes:
  • tally.sum now says when a sum_of or an add fails as unusable: when a number added, or the result, isn't a whole number below 10^15 once scaled by 10^p, where p is the most decimal places any of them has, counted on the value, or when p is above 15. It used to say such sums are added as the runtime adds them, which gave different answers in different runtimes. Reports change for such inputs, like 0.123456789012345 plus 100, which now fails as unusable with a null entry.
  • sentence.phrase.within now says the gap is worked out exactly on the decimals, for the numbers Values covers, and that an implementation that can't compare the numbers exactly fails the check as unusable, as Values says. ergo.rego and the Rust engine do when the exponents of the two sides and the within number are more than 4096 apart. It used to say nothing, and ergo.rego used OPA's arithmetic, which isn't exact. Reports may change for such inputs: x equals y within 0.2 on 0.3 and 0.1 failed as value and now passes.
  • A from line with named by each of fails $min_subjects when the path before it doesn't lead to an object, as subjects.sentence now says: as absent or null when the object is missing or null, and as unusable when it's a list, a string or a number. Each listed key is still a subject, as before, so its checks still show what's missing, but the requirement is never met. A list of keys that holds anything but strings makes no subjects and fails $min_subjects as unusable. The spec didn't say, and ergo.rego met the requirement whenever every check passed, so with from: scans named by each of $params.required and no scans in the input, x does not exist passed for every key and the policy was compliant. The same holds when a ref step before named by each of can't be used, like named by $params.env with env set to 5, which was met too. Reports change for such inputs: $min_subjects fails and the requirement is not_met, a key list with a non-string key gives no subject rows, and a child subject of such a parent gets one $min_subjects row and one $unique_ids row with no in, as subjects.chain.count says, instead of a row per key.
  • subjects.from says that the whole input may be a list, so from: $input and from: every $input make each item a subject, and so does an every walk that starts at a list input. A case said the opposite and from: $input on a list failed $min_subjects as unusable. An empty list fails as value. A from whose steps start with a position, like $input[0] or first of $input, reads a list input as a list, and ends absent past the end of it. A child subject's from that starts with a position, like artifact[0].c, reads a parent that is a list the same way, as path.not_an_object says, where ergo.rego failed it as unusable. A field step still ends unusable on a list. The $min_subjects and $unique_ids expressions of from: every $input, and of every $input whose ..., keep every, on any input. Reports change for policies whose input is a list, and the expressions change for every policy with such a from.
  • report.subjects now says what it lists for a subject written wrong. A from that reads but has a problem, like values of mixed types, is <invalid from>, and so is the from of a subject with applies_to or of, because those fields said how it was found. A subject whose from starts at another subject lists that subject even when the line is written wrong, 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. Cases with report.subjects check each of these. ergo.rego gave the line itself for a from with a problem, listed only the last subject of a chain of more than three, and left out the subject a from with every starts at, so its reports change for such policies.
  • A sentence written wrong with values of mixed types is printed as written (sentence.print.written_wrong), and its $well_formed input is named checks.<name> (written_wrong.row), with the line's number in front of the problem for a list of two or more (sentence.check), like any other problem. The spec already said so and listed this case as open, because ergo.rego printed it from its parse and named the input by the place inside the check. sentence.for.line now also says that a problem with the for line, its whose included, starts with for: , like for: values of mixed types, which ergo.rego did for every for line problem but three: a form a whose on the for line can't take, an unknown alias in a for item, and a walk over more than ten steps, which now have the prefix too. A sentence lists values of mixed types with every other problem it has, like y is compared with itself on the same line. Reports change for such checks: x is one of 1.0, 'a' gave the expression x is one of 1, "a" in ergo.rego and now gives x is one of 1.0, 'a', the input checks.f.checks.0 is now checks.f, and a problem that hid values of mixed types no longer does.
  • walk.failed_items.side now says which side of a two-sided whose check an undecided item names in failed_items: the side that wasn't read, or the left side when neither was read or both were. The spec only said the field of the first check that couldn't decide, and ergo.rego named the side that isn't a count of, or the left side when there was none. Reports change for items where a whose finds one of its two fields: whose lo is at least hi on {"lo": 1} now names hi with null, where it named lo with 1. A side counts as read only when its path ends found, and a count of or sum of side only when its list is a list, so a null field or {"kids": 5} is the side named. A plus side follows the same rule, and a side that reads outside the item, a $params or $input path, a part or a parent's field, is named by its own path, like $params.min, w or art.q, with its own location. Verdicts, causes and statuses don't change.
  • sentence.subject.every now says that every can go with a path that starts with a dot and another subject's name, like every .artifact.p, because the dot reads the input field and not the subject. ergo.rego wrote it wrong. Without the dot it is still written wrong. A from: .artifact also no longer narrows the subject artifact, as sentence.path.start already said, because ergo.rego read it as narrowing. Reports change for such a policy.
  • parts.where now says that every term of a plus on the right side of a whose, and each path of an elapsed time there, reads a part or a parent's name, as a bare path on that side does. ergo.rego read them as fields of the item, so a check could pass on the item's own data: with the part pa set to meta, whose lo is at least pa.w plus hi passed on an item's own pa.w where meta had no w. Reports change for such checks.
  • sentence.for.pick now says that a picked second item can read the first item's alias in its whose, and is picked for each item of the first list, as a second every or some item is walked. Reports change for such policies: every xs as p, first of ys whose k is p.k as q gave absent whatever the data in ergo.rego, and now passes or fails on it. for.pick and for.failed_items now say that a pick that finds nothing lists no failed item, and for.shared_failures that a second list that can't be walked is listed once also then. The cases are in spec/cases/for.
  • for.failure_inputs now says that the order of a failed every or some on a for line holds when a walk reads a name that sorts before an alias, like a whose that reads $input or a parent, or a walked path like "#rows", and it's no longer listed as open. ergo.rego listed what such a whose read first. Reports change for such a policy: the order of inputs in failed_assertions and 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.
  • sentence.path.start now says that an unknown $<word> is read by the grammar like $params, and only makes the line written wrong with unknown name, next to any other problem the line has. A line that doesn't parse says why. Cases pin what report.subjects lists for a from with an unknown name, and the messages of a sentence and a for line with an unknown name and a syntax error. sentence.written_wrong.self is listed next to an unknown name too.
  • sentences.abnf now reads $<name> as a start, after named by and in brackets, where it read only $params and $input, so a line with an unknown name is one the grammar reads. A bare $params or $input after named by or in brackets is still not accepted.
  • parts.tag says a count of or sum of entry has a part's tag only when the list it counts or sums starts at the part, and parts.where says a count of or sum of on the right of a whose reads a part's alias, and on the left the item's field. Both were already how the rules read for other sides, and the cases now pin them. With a part pa: "pa", count of items whose w is pa.w is 1 on {"things": [{"id": "a", "items": [{"w": 1}, {"w": 2}], "pa": {"w": 2}}]} gives the number entry no part, and with a part x: "p", every xs.v whose n is count of x.z is 1 on {"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]}}]}]} counts the part's z and is satisfied. Reports change for an engine that did otherwise.
  • Cases pin that a walk over ten steps on a for line, in its whose too, is listed with for: in front, as sentence.for.line says.