Skip to content

0.70.3: docs-only fast path redesign: one total verdict function, not hand-picked shell excuses (#4717) #4752

Closed as not planned

Description

@noahgift

Parked to 0.70.3: #4717 hit the same review finding (B3) in two rounds, so it gets this redesign instead of a third round. Owner aprender-w4472. Refs #4678, #4717.

The finding (B3, guard_mutation_below_100, both rounds)

#4717 lets a docs-only PR skip the Rust-only fat sections. To keep the required checks green,
both verdict steps (ci / gate and gate) gain an excuse: a missing sov.gate /
determinism-compare is accepted when CLASS_JOB:CLASS is success:docs
(ci.yml case "$CLASS_JOB:$CLASS:$sec" in success:docs:sov.gate | success:docs:determinism-compare)).
The guard scripts/check_ci_gate_docs_class_rule.sh runs the two steps' run: bodies against
fixture rows.

  • Round 1 (head 78551b0): 22 mutants, 17 killed, 5 survived. One was real: the ci / gate
    docs loop could drop guard-cargo and every guard stayed green.

  • Round 2 (head d7a6b3c): all 5 of round 1's survivors were killed. 5 new non-equivalent,
    in-scope mutants of the gate step survived:

    • m07: X86:sov.gate is dropped from the loop;
    • m22: the excuse is widened to success:*:sov.gate;
    • m08: sov.gate is excused regardless of CLASS_JOB;
    • m23: determinism-compare is excused regardless of CLASS_JOB;
    • m24: an empty CLASS_JOB satisfies the excuse.

    Each one lets a FAILED sov.gate merge on a class=full PR, or on a failed classifier, while
    the guard and its self-test stay green.

  • Also open, outside the mutants: 4 job-wiring mutants (m18–m21):

    • m18/m19: needs: change-class is dropped;
    • m20/m21: CLASS_JOB or X86_RESULT is hard-wired in env:.

    The guard supplies the env itself, so it cannot see these.

Why it recurs

  1. The rule is an open shell surface, not a closed table. The decision depends on
    (event × CLASS_JOB × CLASS × section × result), but it is written as case patterns, for
    lists and ifs, separately in two steps. The guard samples that space with rows chosen by
    the author.
    • Each review plants a new family of mutants and finds the cells no row visits.
    • Killing the named survivors adds rows for those cells only, so the next round finds the
      next unvisited cell. Round 2's rows covered ci / gate failure cases but never fed gate
      a failing sov.gate with a failed or empty classifier.
  2. There are two copies of the rule. ci / gate and gate each encode the excuse, so every
    fixture row must exist twice. Round 2's survivors are all in the second copy.
  3. The wiring is outside the guard's reach. The table runs only the run: bodies, so
    needs:/env: edits (m18–m21) can change what the steps read without any row moving.

Fixing more survivors, row by row, does not converge. The redesign has to make the decision
space finite and fully checked.

Option A: one verdict function plus an exhaustive truth table (keeps the excuse)

  • Move the excuse into one sourced bash library (option-neutral, no set), for example
    scripts/ci_gate_verdict.sh. It exposes verdict <event> <class_job> <class> <section> <result>,
    which prints pass, fail or excused. Both verdict steps call it. No rule text stays inline.

  • Commit the oracle as data: a TSV over the full product of the domains, including the out-of-
    domain values.

    • event: pull_request, merge_group, push.
    • CLASS_JOB: success, failure, cancelled, skipped, empty.
    • CLASS: docs, full, empty, other.
    • section: every gated section.
    • result: success, failure, cancelled, skipped, missing.

    This is a few hundred cells. The guard checks every cell, and the self-test proves that each
    planted widening or narrowing flips at least one cell. This makes the mutation score 100% by
    construction: any change to the function's behaviour changes a cell.

  • Add structural rows that read ci.yml itself:

    • both verdict jobs needs: change-class;
    • CLASS_JOB is exactly ${{ needs.change-class.result }};
    • no env: literal overrides it.

    These kill m18–m21.

  • Cost: medium. It is still a gate loosening, so it needs a new sign-off row, but the signed
    intent (row 4) is unchanged.

Option B: no excuse in the gate; the docs path produces real results (gate unchanged)

  • Leave both verdict steps exactly as on main: every gated section must report success.
  • On class=docs, the producer job runs a docs variant of each gated section instead of skipping
    it. The variant is a cheap step that re-runs the classifier on the merge commit and asserts
    the diff is still docs-only, then reports success under the same section name, with the
    class and the sha in its log.
    • A failed or absent classifier means no docs variant, so the full sections run.
    • It fails closed by construction.
  • The gate gains no new code, so there are no gate mutants. All risk moves to the classifier,
    which gets the hardening below.
  • Cost: lower gate risk and some producer plumbing in ci/sections.yml (no fat_driver growth).
    The minimum skipped work is one runner start-up per variant.
  • Risk: a success from a section that did not run its full body has to stay visibly
    distinguishable in the log, and in Σ-executed where it applies (L25: report it as
    not-triggered in the section log, never as a pass of the full body).

Recommendation: B. It deletes the surface that failed twice instead of fencing it. Fall
back to A if a docs variant cannot satisfy the required-check names.

Carried items (either option)

  • The classifier misses split macros. agy, re-measured by the primary: include_str! with
    the .md literal 4 or more lines below the macro reads class=docs. Replace the fixed context
    window with a token-level scan (or a rustc-free include_(str|bytes)!(…) multi-line
    match), plus a case-table row for 1, 3, 4 and 8 line splits.
  • There is a duplicate diff derivation. ci_change_class.sh re-implements derive_diff
    from scripts/ci_gpu_touched.sh and a second docs_only() from scripts/ci_test_tier.sh.
    Use one library.
  • The PR's own receipt defeats the fast path. A docs PR that commits its receipt under
    evidence/pr-review/<pr>/ is class=full, so the fast path never fires for it. Counting the
    receipt as docs widens the classifier: that is its own sign-off row, and nothing is built
    for it before signing.
  • The mutants rule waits on fix(ci): RESTORE mutants gate rules after 0.70 (#4646) #4648. Once fix(ci): RESTORE mutants gate rules after 0.70 (#4646) #4648 removes the exit 0 above GATE-MUTANTS-RULE,
    a docs PR reads mutants as missing. Under A it needs a table cell; under B, a docs variant
    of mutants.

Acceptance

  • A case table RED on origin/main and GREEN on the branch.
  • Under A: every cell of the oracle is checked, and the self-test kills 100% of the planted
    mutants, including m07, m08, m22, m23, m24 and m18–m21 by name.
  • Under B: the gate diff is empty, and the classifier table includes the split-macro rows.
  • Mutants reported as killed/total, receipts on the CI image, bash only (no Python, C301), and a
    quorum of two non-author models.

Activity

  1. added this to the 0.71.0 milestone on Oct 8, 2026
  2. noahgift commented on Oct 8, 2026

    @noahgift
    MemberAuthor

    Parked by APR-EPIC-001: over E1 budget (rank 28/37). Reopen = pull.

    Claim: owner:aprender-w4472. Branch heads: none. C334 item 2 (#4678's open children to E1 #3998); nothing deleted.

  3. added
    parkedClosed over epic budget (APR-EPIC-001 rule 5). Reopen = pull.
    on Oct 8, 2026
  4. modified the milestones: 0.71.0, backlog on Oct 8, 2026
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

    P2Medium priorityowner:aprender-w4472Owner sessionparkedClosed over epic budget (APR-EPIC-001 rule 5). Reopen = pull.size:MK̂ 3-4 h

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions