Skip to content

Kernel: a reviewing Run that leaves a rubric criterion uncovered has not reported #16

Closed

Description

@don-petry

Part of the epic "Epic: slice 0, foundations. The repo holds the line, its context is tested, and the platform facts are recorded".

Why. A rubric is the least a Role must check. The Kernel reads facts and run records, never a Result (§3.9), so the rule has two halves: apply rejects an uncovered Result, and the Kernel reads that rejection from the run record.

Done when

  • A pure function checks a reviewing Result against its rubric and the changed paths: every criterion that applies is met, a Finding, or not_applicable with a reason. It has its own example and counter-example; the schema alone cannot express this.
  • A Run whose Result fails that check ends as a failed Run of kind item, and the run-record field that says so is named.
  • The Kernel does not treat a required Lens as having reported on content when its Run ended that way. The boundary test is written first.
  • The simulator's reviewer can return an uncovered Result, and the invariants hold on the fixed seeds.

Needs. The story "Schemas for Task, Result, Effect and Disposition", for the Result's shape.

Design. §3.14, "A Result reports coverage"; §4.2, item 1; §3.9; I-37.

Activity

  1. changed the title [-]Kernel: a reviewing Result that leaves a rubric criterion uncovered does not count[/-] [+]Kernel: a reviewing Run that leaves a rubric criterion uncovered has not reported[/+] on Oct 11, 2026
  2. don-petry commented on Oct 17, 2026

    @don-petry
    ContributorAuthor

    Working rulings (overturnable in review)

    Solution Architect, delegated. Builds on #150 (decisions 0025, 0026), which merges first.

    1. Coverage is not a fact. The Kernel never reads a Result (§3.9). Coverage lives in the Result. The facts gain no field; the run record carries what follows from it. Why: a Result is the model's word.

    2. Coverage gets its own schema now: schemas/coverage.schema.json, coverage/1. It is a list of {criterion, verdict, reason}. verdict is met, finding or not_applicable. reason is a code matching ^[a-z_]+$, or null. Why: #19 waits on trials P-13 and P-17 for usage, not coverage.

    3. "Uncovered" is decided by one pure function: kernel/coverage.py, covered(rubric, coverage, findings, changed). It returns ("covered", []), ("uncovered", ids) or ("unreadable", []). A criterion applies when it has no applies_when, or when a changed path, or a rename's previous path (names), matches one of its patterns. An applying criterion is uncovered when:

    • it has no entry, or two;
    • its verdict is finding and no Finding cites it;
    • a Finding cites it but the verdict is not finding;
    • its verdict is not_applicable and the reason is not a code.

    So is an entry for an id the rubric lacks. An entry for a criterion that does not apply is ignored.
    Why: §3.14, I-37. The evidence is criterion ids, codes apply can post.

    4. Path syntax. applies_when is read with derive.matches, the one matcher. It gains one form: ** as a whole name stands for any run of names, none included. The hook's _glob learns it in the same change, and test_tools' agreement test widens to the rubric's patterns. Why: fits reads the rubric's **/*.py as one level deep, which under-applies PR-3.

    5. Who is at fault. uncovered ends the Run with conclusion: failure and outcome: item. Those are the run-record fields that say so, and no new field is added. unreadable (a rubric that can't be read, or changed paths not read in full) ends it with environment. Why: a broken rubric is the Factory's fault, and it must not spend the item's attempts (I-23).

    6. What the Kernel does: re-admit, bounded. A Lens has reported only through a Run on this content that ended success. After an uncovered Run the item stays verifying/awaiting_review, and the same Lens's review is admitted again. Each such Run is an attempt of review-change. After max_attempts (3) since a writer acted, attempts_exhausted is a Hold with the Run ids as evidence, needs_you. Every Run also counts toward the Run ceiling and stops under a Pause. Why: model output varies, and the existing brakes bound the retry.

    7. Fails closed.

    • complete.runs not true: facts_incomplete (exists).
    • A failed reviewing Run with a null outcome is never a report. It is not an attempt, so the Run ceiling bounds it, as with approvals today.
    • A reviewing Run on this content that is success with a non-null outcome contradicts itself. It is the new run_unreadable: defer, needs_you, evidence the Run id. It is read where awaiting_review is read, and it takes a queued item out.
    • In covered, an applies_when it can't read makes the criterion apply.

    Tests and mutants. Each test is written first.

    Boundary test Mutant it kills
    test_decide::test_a_review_run_that_failed_has_not_reported (failure, timed out and cancelled, each item; a later success reports) a failed review Run counts as a report
    test_decide::test_a_review_that_succeeded_with_a_fault_is_unreadable a success with a fault is read as a report
    test_decide::test_item_faults_exhaust_attempts (exists; its runs are review-change) the existing "attempts are unlimited"
    test_coverage::test_a_criterion_that_applies_must_be_covered a criterion left out is covered
    test_coverage::test_a_renamed_files_old_path_makes_a_criterion_apply the previous path is not read
    test_coverage::test_not_applicable_needs_a_reason_code not_applicable needs no reason
    test_coverage::test_a_finding_verdict_needs_a_finding_that_cites_it finding needs no Finding
    test_coverage::test_an_unreadable_rubric_is_unreadable_never_covered unreadable is read as covered
    test_tools:: the agreement test, with ** ** read within one name

    The example is examples/coverage.json, covered against config/rubric-pr-review.json and the paths it names. The counter-example is examples/invalid/coverage.a-criterion-that-applies-left-out.json: valid to the schema, refused by covered. tools/check_examples.py runs covered over both.

    Simulator. On fault seeds, ending() sometimes gives an uncovered review: failure/item, and no thread opens (I-5). There is no # GitHub: marker, because this is the Factory's own behaviour. Three invariants:

    • an approval or a merge has a successful review of that content by every required model Lens (I-37);
    • a thread comes only from a successful review;
    • uncovered review Runs since a writer acted are at most max_attempts.

    test_the_seeds_serve_a_review_that_left_a_criterion_uncovered must reach both the re-admission and attempts_exhausted.

    Decision record. docs/decisions/0030-a-reviewing-run-that-leaves-a-criterion-uncovered-has-not-reported.md. It replaces nothing. Its Boundaries rows name the tests above. Its waiting rows:

    Design §3.10's item row gains "a reviewing Result left a criterion uncovered".

    Follow-ups

    Taken as working rulings (the Maintainer may overturn them in review):

    • The protected edits stand: tools/hooks/protected.py (the ** path syntax) and the two simulator files. Since Kernel: a change to the Factory's own rules or a Lens's inputs is reviewed by a model Lens #135, every change to a protected path is reviewed by every model Lens.
    • kernel/coverage.py joins the protected paths.
    • No lower limit for reviews: an uncovered review keeps the existing limit of three attempts per content before a Hold. A separate limit can follow if the cost shows in practice.
  3. don-petry commented on Oct 17, 2026

    @don-petry
    ContributorAuthor

    Decision record renumbered to 0029 (#146 needs no record, so the numbers close up). Merge order follows the numbers.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions