Skip to content

MC/DC gate: the report-side absolute floors are layout-coupled — a 14-line guard anywhere moves unrelated functions' proved counts both directions (witness#208) #1100

Description

@avrabe

Found on PR #1096 (RQ-61-MVPANIC, #1093), diagnosed per scripts/mcdc_gate.py's own instruction (manifest diff before touching any number). Evidence runs: main 33120546295 (b2abf951) vs branch 33126354714 (a3b01739).

The measurement

Instrument side (the gate's stable surface): per-function branch population identical for all 20 scored functions except the intended compile_function_with_opts 9 → 10 (the #1093 guard); per-branch (kind, instr_index) signatures identical; totals 175 → 176.

Report side, same rows (59, incl. the 3 new rv_param_block_gate rows):

              dec   full  cond  prov  gap  dead
main           32     9   141    60    33   48
branch         27     9   132    56    26   50   -> FAIL proved 56 < floor 57

The proved 60 → 56 decomposes ENTIRELY onto functions this PR does not touch and whose instrument populations did not move: sp_slot_store −2 (its full_mcdc decision vanished from the report), validate_final_allocation_rv32 −4 (13 decisions regrouped into 12 with different branch membership), build_options −1, validate_reloc_resolutions −1; offset by validate_served_image +2 (scored 0 conditions on main, 4 on branch), runtime_image +1, spanned +1.

Two facts that close the alternative hypotheses:

  1. Not a missing-independence-pair problem (hypothesis "add more rows"): the new guard branch records hits: 7 in run.json and its driver provably reaches both outcomes (return value flips 0/1, asserted in the harness host test) — yet the report contains no decision holding that branch. A condition witness never scores cannot be proved by any row.
  2. Not a property of the row driver's shape: the FIRST red on this PR (before any harness change — the 14-line backend guard alone) already moved dec 32 → 29 and dead 48 → 51, in different functions than the second red. Any code addition anywhere reshuffles the reconstruction; chasing a driver shape that happens to reconstruct identically is layout dice-rolling.

What this means for the gate

BRANCH_POPULATION (exact, instrument-side) does its job — it is stable and deletion-sensitive. The report-side absolute floors (FLOOR_DECISIONS/CONDITIONS/PROVED, CEILING_DEAD) are coupled to binary layout through witness's DWARF-based decision reconstruction (upstream: pulseengine/witness#208, fresh instance commented there). The gate's own #990 block already documents the protocol: a floor miss WITHOUT a population mismatch is reconstruction moving under you; re-statement WITH the manifest evidence, never a floor lowered to go green. This issue exists so that protocol is a tracked property of the gate rather than folklore, and so the re-statement events are auditable:

  • every re-statement must cite the manifest diff showing the population unmoved (or moved only by the PR's own stated conditions);
  • the long-term fix is upstream (witness#208: reconstruction stable under layout change, or decision identity keyed to stable DWARF decision ids rather than grouping);
  • until then, expect one re-statement per condition-adding PR, and treat a floor miss WITH a population mismatch as the real alarm.

Instance data (manifest/report diff scripts + both evidence dirs) reproduced in PR #1096's thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions