Skip to content

core: let from iterate at any step and name what it iterates over #52

Open

Description

@jbpros

from is a fixed path that leads to a list (one subject per item) or a single object (one subject). Policies that need anything else reshape the input in Rego before calling ergo. That code sits outside ergo's checks and its report, and in sdlc-policies it's where things go wrong.

Where it hurts

  • SDLC-CTRL-0007 (code review). A check on an approver can't see its pull request, so the policy copies the PR's author and last commit time onto each approver. That copy has a bug: a commit with no timestamp, or a null one, is skipped when working out the last commit time. So an approval made before that commit passes. I reproduced it: allow is true.
  • SDLC-CTRL-0008 (quality assurance). Test attestations sit in an object keyed by name, so the policy wraps each one to give it an id and writes one requirement per suite. core: let a requirement take its subjects from an object's keys #42 fixes this for a fixed list of names.
  • Any keys, not known in advance. With core: let a requirement take its subjects from an object's keys #42, a policy has to build the list itself: sort(object.keys(x)) if is_object(x) else := []. Leave out sort and $well_formed fails. Leave out the guard and the whole report goes undefined when x is missing.

Idea

Let a step in from iterate and give a name to what it iterates over. Let any and all name their item too, and let any path start from one of those names.

0008, every entry:

"from": [..., "attestations_statuses", {"each": "suite"}],

0008, fixed names. A missing name is still a subject and fails as absent, like #42:

"from": [..., "attestations_statuses", {"each": "suite", "keys": required_test_suites}],

0007, with no glue and no max. A commit without a timestamp fails its own row:

"from": [..., "pull_requests", {"each": "pr"}],
...
"peer_approval": {"op": "any", "path": ["approvers"], "as": "approver", "check": {"op": "any_of", "options": {"peer": [
	{"op": "equals", "path": ["state"], "value": "APPROVED"},
	{"op": "compare", "left": ["username"], "right": ["$pr", "author"], "cmp": "ne"},
	{"op": "all", "path": ["$pr", "commits"], "check":
		{"op": "compare_time", "left": ["$approver", "timestamp"], "right": ["timestamp"], "cmp": "gt"}},
]}}},

The syntax is only a sketch.

Open questions

  • Several iterating steps in one from. An item with an empty inner list would give no subjects and drop out of the report. Either turn that into a failing row, or allow only one iterating step and use as for anything nested. I'd start with the second.
  • Ids. What a subject's id looks like when it comes from a key, and how subjects are sorted.
  • Report. How a row shows a value read through $name.
  • Names. What happens with a $name that isn't bound, or one bound twice.
  • core: let a requirement take its subjects from an object's keys #42. Under this design, keys is part of a step in from rather than a field on the requirement. Should core: let a requirement take its subjects from an object's keys #42 move it there before it merges, or should it merge as it is, with keys replaced later?

Not covered

From the same review of sdlc-policies, these need something else:

  • checking that a value is one of a list (0004, licences)
  • the SBOM to lockfile join and the text parsing in 0004
  • reading params with defaults, which every policy repeats

Activity

  1. added
    enhancementNew feature or request
    coreRelates to the ergo library or format themselves.
    on Oct 1, 2026
  2. added
    impacts-ergo-policiesImpacts ergo policy schema. Careful design attention needed.
    impacts-ergo-reportsImpacts ergo report schema. Careful design attention needed.
    on Oct 1, 2026
  3. jbpros commented on Oct 5, 2026

    @jbpros
    ContributorAuthor

    From #113, DEV-0406: there's no way to name a subject that is a single object. each_as on an object makes each of its fields a subject, and without it the object has no name, so a check nested in all or any can only reach it through $$input:

    from: [artifact, attestations, uat]
    ...
    right: [$$input, artifact, attestations, uat, tested_by]

    Tore suggests a step that only names it:

    from: [artifact, attestations, uat, { as: uat }]
    ...
    right: [$uat, tested_by]

    as is already what names an item in all and any, so the word fits. On a list, it would name each item, the same as each_as.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreRelates to the ergo library or format themselves.enhancementNew feature or requestimpacts-ergo-policiesImpacts ergo policy schema. Careful design attention needed.impacts-ergo-reportsImpacts ergo report schema. Careful design attention needed.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions