diff --git a/.github/workflows/quality-advisory.yml b/.github/workflows/quality-advisory.yml index 3cc828f8..dedbf02c 100644 --- a/.github/workflows/quality-advisory.yml +++ b/.github/workflows/quality-advisory.yml @@ -122,6 +122,16 @@ jobs: # leave a reason in the log, since nobody watches a green advisory job for absence. if ! MERGE_BASE="$(git merge-base HEAD "origin/$BASE_REF" 2>/dev/null)"; then echo "::notice title=Complexity delta skipped::could not resolve a merge base against origin/$BASE_REF" + # Record the bail so the liveness receipt can SEE it. Without this the step exits 0, the + # receipt still reports "measured" off the whole-repo triage, and the PR-caused delta -- + # the entire point of this step on a PR -- goes quiet behind a green check. That is the + # failure mode this workflow's liveness job exists to catch, sitting inside it. + # QUOTE the reason: the receipt SOURCES this file, and an unquoted value containing + # spaces parses as a command, silently leaving the variable unset -- which reported + # "no reason recorded" and threw away the one useful diagnostic. Caught by executing + # the receipt shell, not by reading it. + echo "C901_DELTA=skipped" > c901-delta.env + echo "C901_DELTA_REASON='could not resolve a merge base against origin/$BASE_REF'" >> c901-delta.env exit 0 fi echo "merge base: $MERGE_BASE" @@ -138,9 +148,23 @@ jobs: --head c901-head.json \ --repo-root . \ --summary-file "$GITHUB_STEP_SUMMARY" + # Written LAST, so it exists only if every step above actually completed. Its absence is + # therefore proof the delta died somewhere, which the receipt reports as a dead gate. + BASE_N="$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))))' c901-base.json)" + HEAD_N="$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))))' c901-head.json)" + { + echo "C901_DELTA=ok" + echo "C901_DELTA_MERGE_BASE=$MERGE_BASE" + echo "C901_DELTA_BASE_N=$BASE_N" + echo "C901_DELTA_HEAD_N=$HEAD_N" + } > c901-delta.env - name: Record gate liveness id: receipt if: always() + # Routed through env, not interpolated into the shell body (zizmor: template injection). + env: + IS_PR: ${{ github.event_name == 'pull_request' }} + EVENT_NAME: ${{ github.event_name }} continue-on-error: true run: | # Units count what was EXAMINED, never what was found: a repo with zero functions over the @@ -149,11 +173,37 @@ jobs: FILES="$(ruff check --select C901 --show-files messagefoundry 2>/dev/null | wc -l)" FINDINGS="$(ruff check --select C901 --output-format=json --exit-zero messagefoundry \ | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))')" + # THE DELTA IS THE POINT ON A PR, and until now this receipt could not see it. `--show-files` + # proves ruff enumerated files, which is true whether or not the delta step ran -- so a delta + # that bailed on an unresolvable merge base left a green "measured" receipt and vanished. + # That is this workflow's own failure mode reproduced inside its liveness control. + # + # On a non-PR event the delta legitimately does not run and the whole-repo triage IS the + # measurement. On a PR its silence is a dead gate, not an absence of news. + if [ "$IS_PR" != "true" ]; then + python3 scripts/quality/liveness.py record \ + --signal complexity --status measured \ + --units "$FILES" --unit-name "files scanned" \ + --evidence "ruff C901 scanned $FILES files; $FINDINGS over the threshold (whole-repo triage; the PR delta does not apply on $EVENT_NAME)" \ + --extra "{\"findings\":$FINDINGS}" + exit 0 + fi + if [ ! -s c901-delta.env ]; then + python3 scripts/quality/liveness.py record --signal complexity --status failed \ + --reason "the PR complexity delta produced no outcome marker; it did not complete (see the job log)" + exit 0 + fi + . ./c901-delta.env + if [ "$C901_DELTA" != "ok" ]; then + python3 scripts/quality/liveness.py record --signal complexity --status failed \ + --reason "the PR complexity delta did not run: ${C901_DELTA_REASON:-no reason recorded}" + exit 0 + fi python3 scripts/quality/liveness.py record \ --signal complexity --status measured \ --units "$FILES" --unit-name "files scanned" \ - --evidence "ruff C901 scanned $FILES files; $FINDINGS over the threshold" \ - --extra "{\"findings\":$FINDINGS}" + --evidence "ruff C901 scanned $FILES files ($FINDINGS over the threshold); delta compared ${C901_DELTA_BASE_N} base vs ${C901_DELTA_HEAD_N} head findings against ${C901_DELTA_MERGE_BASE}" \ + --extra "{\"findings\":$FINDINGS,\"delta_base_n\":${C901_DELTA_BASE_N},\"delta_head_n\":${C901_DELTA_HEAD_N}}" clone: # Signal 9 - duplication / clone detection. Flags copy-pasted blocks (the "copy-instead-of-abstract" diff --git a/tests/test_quality_advisory_invariants.py b/tests/test_quality_advisory_invariants.py index 6e721e60..7573fba3 100644 --- a/tests/test_quality_advisory_invariants.py +++ b/tests/test_quality_advisory_invariants.py @@ -326,6 +326,56 @@ def test_the_mutation_other_count_is_not_a_remainder(code: str) -> None: ) +def test_the_complexity_delta_reports_its_own_outcome(workflow: dict) -> None: + """The delta is the POINT of the complexity job on a PR, and the receipt could not see it. + + `--show-files` proves ruff enumerated files, which is true whether or not the delta ran — so a + delta that bailed on an unresolvable merge base left a green `measured` receipt and vanished. + That is this workflow's own failure mode reproduced inside its liveness control, so the delta + now writes an outcome marker and the receipt rules on it. + """ + steps = {s.get("id"): s for s in workflow["jobs"]["complexity"]["steps"]} + delta = next( + s + for s in workflow["jobs"]["complexity"]["steps"] + if "c901_delta.py" in (s.get("run") or "") + ) + + # Split at the delta invocation: everything before it is the bail path, everything after is the + # success path. BOTH must write the marker, or its absence is ambiguous. Counting occurrences is + # NOT enough — the bail path writes twice on its own, so a count check passes even with the + # success-path marker deleted. (Found by negative-probing this very assertion.) + bail, _, success = delta["run"].partition("c901_delta.py") + assert "c901-delta.env" in bail, "the merge-base bail path must record that it bailed" + assert "c901-delta.env" in success, ( + "the success path must record completion — written last, so its absence proves the delta died" + ) + + receipt = steps["receipt"]["run"] + assert "c901-delta.env" in receipt, "the receipt must consult the delta's outcome" + assert "--status failed" in receipt, "a vanished delta must be reported as a dead gate on a PR" + + +def test_the_delta_marker_reason_is_quoted(code: str) -> None: + """The receipt SOURCES the marker file. An unquoted value containing spaces parses as a command + and leaves the variable unset — which silently reported "no reason recorded" and threw away the + only useful diagnostic. Found by executing the receipt shell, not by reading it.""" + assert re.search(r"C901_DELTA_REASON='[^']+'", code), ( + "the reason must be single-quoted so `source` yields the whole string" + ) + + +def test_the_non_pr_path_is_not_treated_as_a_dead_delta(workflow: dict) -> None: + """On cron/dispatch the delta legitimately does not run and the whole-repo triage IS the + measurement. Failing there would fire on good news, which gets a check muted.""" + steps = {s.get("id"): s for s in workflow["jobs"]["complexity"]["steps"]} + env = steps["receipt"].get("env") or {} + assert any("event_name" in str(v) for v in env.values()), ( + "the receipt must know whether this is a PR before ruling the delta missing" + ) + assert "does not apply" in steps["receipt"]["run"] + + def test_the_killed_count_is_derived_not_grepped(code: str) -> None: """`mutmut results` lists ONLY the mutants worth looking at (survived / no tests / timeout / suspicious). Killed mutants are never listed, so counting `': killed'` returns 0 on a perfectly