Skip to content

core: let a check read a subject that is one object by its name #244

Open

Description

@jbpros

Steps

Policies on Kosli trails repeat long paths. Every attestation sits at compliance_status.attestations_statuses.<name>.attestation_data, and many checks compare fields from two attestations. pr-reviewer's review-controls policy, moved to sentences in kosli-dev/pr-reviewer#491, builds these paths with Rego helpers. Written as YAML, a check looks like this:

every_hunk_acknowledged: compliance_status.attestations_statuses.coverage-verification.attestation_data.hunks_acknowledged equals compliance_status.attestations_statuses.coverage-verification.attestation_data.hunks_total

Idea

A subject can stand for one object, like one attestation, and a check reads it by its name. With this input:

{"trail": {"name": "pr-491-r1", "compliance_status": {"attestations_statuses": {
  "coverage-verification": {"attestation_data": {"hunks_total": 25, "hunks_acknowledged": 25}},
  "findings": {"attestation_data": {"truncated": false}}
}}}}

the policy could say:

subjects:
  round:
    from: trail
    identified_by: name
  coverage:
    from: round.compliance_status.attestations_statuses.coverage-verification.attestation_data
  findings:
    from: round.compliance_status.attestations_statuses.findings.attestation_data
requirements:
  review_round:
    subject: round
    checks:
      every_hunk_acknowledged: coverage.hunks_acknowledged equals coverage.hunks_total
      findings_complete: findings.truncated is false

and a row would name its inputs coverage.hunks_acknowledged and coverage.hunks_total.

ergo half-does this already: a chained subject's checks read their parent by name, like artifact.name (subjects.chain). This would let a check read any subject that's one object, not only its parent, so the long path is written once, in that subject's from.

To decide

  • Which subjects can be read this way. Only one that's a single object? One whose from leads to a list would have several values, which a plain path can't stand for.
  • Missing, null or not an object. A check that reads coverage.x when the coverage attestation isn't there should fail as absent, null or unusable, like any path, and never pass.
  • Where it's read from. In the example, coverage starts from round, so each round reads its own coverage. A subject that starts from the input would be the same for every row.
  • Names. A subject's name and a field of the subject can clash. Chains already say the subject wins, and this would follow the same rule.
  • The report. Inputs named coverage.hunks_total read well, but a reader also needs to know where coverage comes from. The subject's from is in the policy, and the report could repeat it.

Two other ways were considered:

  • a separate section of named path prefixes: it does nearly the same thing as subjects, as a second concept
  • words specific to Kosli, like attestation "coverage-verification": it would tie ergo's grammar to one product

Activity

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

    @jbpros
    ContributorAuthor

    Decided (2026-10-17): the first idea, letting a check read another subject by name, runs into #210 whenever that subject doesn't come from the one being checked. Instead, a subject declares parts, paths inside it, under has::

    subjects:
      round:
        from: trail
        identified_by: name
        has:
          coverage: compliance_status.attestations_statuses.coverage-verification.attestation_data
          findings: compliance_status.attestations_statuses.findings.attestation_data
    requirements:
      review_round:
        subject: round
        checks:
          every_hunk_acknowledged: coverage.hunks_acknowledged equals coverage.hunks_total

    coverage.hunks_total means that path inside the round being checked. With this input:

    {"trail": {"name": "r1", "compliance_status": {"attestations_statuses": {
      "coverage-verification": {"attestation_data": {"hunks_total": 25, "hunks_acknowledged": 25}}}}}}

    the check passes, exactly as it would with the full paths written out.

    1. Words. has: in the policy, part for what it declares, in the report and the docs. Alias for a word that stands for something longer, everywhere: in the docs, the spec, the ABNF and the messages. That covers the word after as on a for line, a part's key, and a chained subject's parent. The ABNF rule name becomes alias, and name given twice and unknown name <word> become alias given twice and unknown alias <word>.
    2. Clashes. An alias shadows a field with the same name. Two aliases with the same name are written wrong. The dot in front, like .empty, still only marks a field named like a grammar word. It doesn't get around an alias: rename the alias instead.
    3. Narrowing. A subject that narrows another, like from: round whose …, keeps its parts and can add its own. A kept part's alias given again is written wrong.
    4. Chains. A subject's alias gives access to everything it has, fields and parts, like artifact.pr_attestation.approvers, and a from line can start from a parent's part. A part is only reached through its subject: pr_attestation alone means nothing outside the subject that has it. With core: descend from a subject that has several subjects #210, this works per parent.
    5. The swap rule. A part's alias can be replaced by its path and every row stays the same: the same passed, cause and values. Only the names in expressions and inputs differ. So a missing, null or wrong-typed part gives whatever the full path gives today. A test swaps every part's alias for its path in the spec cases' policies and compares the rows. This doesn't apply to for aliases, which stand for each item in turn.
    6. The report gets a top-level subjects section, with one entry for each subject a requirement uses, directly or as one it narrows or descends from. Each entry has description, from and identified_by printed as sentences, and every part with its path, {} when there are none. Each requirement entry says which subject it checks with "subject". This changes every report.
  3. jbpros commented on Oct 17, 2026

    @jbpros
    ContributorAuthor

    Decided (2026-10-17): a part can have an id, so a row says which attestation it read. Peter asked for this, and it's what #34 did with labels on selectors.

    A part is written in either of two ways, as a subject is. It's one line when it has no id, and it has from and an optional identified_by under it when it has one:

    round:
      from: trail
      identified_by: name
      has:
        findings: compliance_status.attestations_statuses.findings.attestation_data
        coverage:
          from: compliance_status.attestations_statuses.coverage-verification
          identified_by: attestation_id

    With this input:

    {"trail": {"name": "pr-491-r1", "compliance_status": {"attestations_statuses": {
      "coverage-verification": {"attestation_id": "att-7", "attestation_data": {"hunks_total": 25, "hunks_acknowledged": 25}},
      "findings": {"attestation_data": {"truncated": false}}}}}}

    an input read through coverage carries the part's alias and id:

    {"name": "coverage.attestation_data.hunks_total", "value": 25, "part": {"name": "coverage", "id": "att-7"}}

    A row that compares two parts names both attestations, which a subject's id can't do. The id never changes whether a check passes or its cause, and a missing id is null. The swap rule still holds, because swapping the alias for its path gives the same passed, cause and values, and only names and the part tag differ.

    The plan gets a fourth step: #245 lists the subjects in the report, then the alias rename, then has: with both forms printed in the subjects section, then the part tag on inputs.

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.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions