`uv sync --frozen` resolved ruff 0.15.22 and the tree read clean. A loose install resolves 0.16.6, under which the SAME untouched code reports 148 findings -- 4 more than round 9 counted, because this round added four files. All of them are new rules rather than new defects: 0.16 widened the default rule set to whole families (YTT, ASYNC, PL, ISC, C4, UP, B, SIM, FURB, ...). (`[skip-docs]` is for CLAUDE.md, which a lint-configuration change does not reach. README's developer section IS updated in this commit.) THE DEFECT IS NOT THE 148, IT IS THAT NOBODY CHOSE THEM. `[tool.ruff]` set only `line-length` and `target-version`, so the acceptance was ruff's default, and the tree stayed green only as long as the lockfile froze an old ruff. `select` is now written down: `E4`, `E7`, `E9`, `F` (the historical default), `I` because this tree already keeps imports sorted, and `RUF100` so a `noqa` that has stopped meaning anything is caught rather than left as decoration. Pin `ruff>=0.9` -> `ruff>=0.16.6,<0.17`. Per rule, before -> after: RUF100 50 -> 0, I001 20 -> 0, ISC004 19, PLW1510 8, C408 8, EXE001 6, RUF007 5, PLE2515 4, UP031 3, B017 3, and fourteen more with 2 or fewer -- the families out of the declared set are 0 by selection, and 148 is the number to start from if they are adopted, which is a separate decision and not one to take inside a version-pin commit. 57 were auto-fixed; one E402 was reintroduced by the import-sorting fix merging a block away from its `noqa`, and got the directive back rather than a bare one. `S` IS MEASURED OUT, NOT ASSUMED OUT: it reports 2657 `S101` on a suite whose every assertion is an `assert`, and `S603` flags 19 subprocess calls of which one was ever marked -- selecting it buys 18 suppressions and no defect. Two `noqa` directives naming non-selected rules were dropped with that reason recorded in the configuration instead. THE TWO FILES 0.16 WOULD REFORMAT ARE MARKDOWN, NOT PYTHON: `README.md` and `docs/2026-09-08-blindsone-below-k-k2.md`. 0.16 formats fenced Python inside markdown, and both blocks are RECORDS -- the second is a quotation of `COST_VOCABULARY` as it stood when that measurement was taken. Reformatting a quotation makes it stop being one, so markdown is excluded from the formatter and `ruff format --check .` stays in the acceptance over `.py`. `tools/okf_consume_measure.py` is fenced by the order as run-not-edited, so its three findings are exempted by path with the reason and the debt named, and its bytes are untouched. THE LOCKFILE TRAP IS CLOSED, NOT AVOIDED. `uv.lock` predated the `[ocr]` extra, so any unlocked resolve wrote that extra's transitive tree back into it -- 681 insertions over 4 deletions, twice now, and round 9 recorded the cause as `uv run` OUTSIDE the project when it is `uv run` without `--frozen` INSIDE it. The relock is complete for every declared extra (703 insertions, 26 deletions), and measured after it, an unfrozen `uv run` leaves the file alone. `ruff check src tests tools`, `ruff format --check .` (0.16.6), `mypy src` over 21 files and 1535 tests, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
222 lines
8.3 KiB
Python
222 lines
8.3 KiB
Python
"""The upstream OKF watch: it must be able to prove it can find.
|
|
|
|
This watch reports a NEGATIVE result almost every time it runs. That is the
|
|
whole hazard: a run that never reached the network, or whose query silently
|
|
matched nothing, produces the same "no change" as a run that genuinely found
|
|
nothing. Trust in an absence that was never measured is worse than no watch,
|
|
so every test here is about keeping those two outcomes distinguishable.
|
|
|
|
The git-backed tests build their own repository in tmp_path. Skipping when
|
|
`~/repos/_okf-canonical` is missing would preserve nothing on the machine
|
|
where it exists, and pointing them at the real mirror would make the suite
|
|
depend on upstream's history.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
|
|
|
import okf_watch
|
|
|
|
# --- the exit-status / empty-result seam -----------------------------------
|
|
|
|
|
|
def _git(repo: Path, *args: str) -> str:
|
|
return subprocess.run(
|
|
["git", *args], cwd=repo, capture_output=True, text=True, check=True
|
|
).stdout
|
|
|
|
|
|
@pytest.fixture
|
|
def repo(tmp_path: Path) -> Path:
|
|
"""A repository with one quiet commit and one normative commit."""
|
|
r = tmp_path / "upstream"
|
|
r.mkdir()
|
|
_git(r, "init", "-q", "-b", "main")
|
|
_git(r, "config", "user.email", "t@example.invalid")
|
|
_git(r, "config", "user.name", "t")
|
|
(r / "SPEC.md").write_text("Version 0.2\n", encoding="utf-8")
|
|
(r / "CONTRIBUTING.md").write_text("hello\n", encoding="utf-8")
|
|
_git(r, "add", "-A")
|
|
_git(r, "commit", "-q", "-m", "base")
|
|
(r / "CONTRIBUTING.md").write_text("hello again\n", encoding="utf-8")
|
|
_git(r, "add", "-A")
|
|
_git(r, "commit", "-q", "-m", "docs: contributing")
|
|
(r / "SPEC.md").write_text("Version 0.2 tightened\n", encoding="utf-8")
|
|
_git(r, "add", "-A")
|
|
_git(r, "commit", "-q", "-m", "spec: tighten timestamps")
|
|
return r
|
|
|
|
|
|
def test_a_failing_git_call_raises_instead_of_returning_empty(repo: Path) -> None:
|
|
"""The defect this watch exists to avoid, at its smallest.
|
|
|
|
`git log <nonexistent>` writes to stderr and exits non-zero. A helper that
|
|
returned its empty stdout would hand the caller "no commits" — a negative
|
|
manufactured by a broken query.
|
|
"""
|
|
with pytest.raises(okf_watch.WatchError) as exc:
|
|
okf_watch.git(repo, "log", "--format=%H", "no-such-ref..HEAD")
|
|
assert "exit" in str(exc.value)
|
|
|
|
|
|
def test_an_empty_result_from_a_successful_call_is_not_an_error(repo: Path) -> None:
|
|
"""The other half of the seam: a real zero must still be readable as zero."""
|
|
head = okf_watch.git(repo, "rev-parse", "HEAD")
|
|
assert okf_watch.git(repo, "log", "--format=%H", f"{head}..{head}") == ""
|
|
|
|
|
|
# --- classification --------------------------------------------------------
|
|
|
|
|
|
def test_a_spec_change_is_normative() -> None:
|
|
normative, quiet = okf_watch.classify(["SPEC.md", "CONTRIBUTING.md"])
|
|
assert normative == ["SPEC.md"]
|
|
assert quiet == ["CONTRIBUTING.md"]
|
|
|
|
|
|
def test_the_reference_reader_is_normative_because_is_stale_reversed_there() -> None:
|
|
normative, _ = okf_watch.classify(["src/reference_agent/bundle/document.py"])
|
|
assert normative == ["src/reference_agent/bundle/document.py"]
|
|
|
|
|
|
def test_the_viewer_is_quiet_but_the_readme_is_not() -> None:
|
|
"""README carries the relocation notice. That is how the move was announced."""
|
|
normative, quiet = okf_watch.classify(
|
|
["src/reference_agent/viewer/static/viz.css", "bundles/acme_retail/viz.html", "README.md"]
|
|
)
|
|
assert normative == ["README.md"]
|
|
assert quiet == ["src/reference_agent/viewer/static/viz.css", "bundles/acme_retail/viz.html"]
|
|
|
|
|
|
def test_an_unknown_path_defaults_to_normative() -> None:
|
|
"""Quiet is an enumerated list; signal is not.
|
|
|
|
A watch that enumerates what matters cannot see a change upstream has not
|
|
invented yet, and fails in the silent direction. Over-firing is visible;
|
|
under-firing is not.
|
|
"""
|
|
normative, quiet = okf_watch.classify(["some/new/thing-upstream-adds-later.md"])
|
|
assert normative == ["some/new/thing-upstream-adds-later.md"]
|
|
assert quiet == []
|
|
|
|
|
|
# --- proving it can find ---------------------------------------------------
|
|
|
|
|
|
def test_the_self_check_passes_on_a_range_that_really_changed_the_spec(repo: Path) -> None:
|
|
base = okf_watch.git(repo, "rev-parse", "HEAD~1")
|
|
head = okf_watch.git(repo, "rev-parse", "HEAD")
|
|
okf_watch.prove_can_find(repo, base, head) # must not raise
|
|
|
|
|
|
def test_the_self_check_fails_loudly_when_the_query_finds_nothing(repo: Path) -> None:
|
|
"""An empty known-positive means the query is broken, not that upstream is quiet."""
|
|
head = okf_watch.git(repo, "rev-parse", "HEAD")
|
|
with pytest.raises(okf_watch.WatchError) as exc:
|
|
okf_watch.prove_can_find(repo, head, head)
|
|
assert "known-positive" in str(exc.value)
|
|
|
|
|
|
def test_the_self_check_fails_loudly_when_the_range_is_unreachable(repo: Path) -> None:
|
|
with pytest.raises(okf_watch.WatchError):
|
|
okf_watch.prove_can_find(repo, "deadbeef", "HEAD")
|
|
|
|
|
|
# --- idempotence: report on change, not on state ---------------------------
|
|
|
|
|
|
def test_a_first_normative_change_is_reported(tmp_path: Path) -> None:
|
|
state = okf_watch.State.load(tmp_path / "s.json", pin="PIN")
|
|
assert okf_watch.decide(state, ["aaa", "bbb"]) == ["aaa", "bbb"]
|
|
|
|
|
|
def test_the_same_upstream_state_is_not_reported_twice(tmp_path: Path) -> None:
|
|
path = tmp_path / "s.json"
|
|
state = okf_watch.State.load(path, pin="PIN")
|
|
okf_watch.decide(state, ["aaa"])
|
|
state.record(["aaa"])
|
|
state.save(path)
|
|
again = okf_watch.State.load(path, pin="PIN")
|
|
assert okf_watch.decide(again, ["aaa"]) == []
|
|
|
|
|
|
def test_only_the_new_commits_are_reported(tmp_path: Path) -> None:
|
|
"""Upstream advancing must not re-announce what was already announced."""
|
|
path = tmp_path / "s.json"
|
|
state = okf_watch.State.load(path, pin="PIN")
|
|
state.record(["aaa"])
|
|
state.save(path)
|
|
later = okf_watch.State.load(path, pin="PIN")
|
|
assert okf_watch.decide(later, ["aaa", "bbb"]) == ["bbb"]
|
|
|
|
|
|
def test_moving_our_pin_resets_what_counts_as_already_reported(tmp_path: Path) -> None:
|
|
"""A pin move means we absorbed everything behind it. Nothing before it is news."""
|
|
path = tmp_path / "s.json"
|
|
state = okf_watch.State.load(path, pin="OLDPIN")
|
|
state.record(["aaa"])
|
|
state.save(path)
|
|
assert json.loads(path.read_text(encoding="utf-8"))["pin"] == "OLDPIN"
|
|
after = okf_watch.State.load(path, pin="NEWPIN")
|
|
assert after.reported == []
|
|
|
|
|
|
# --- what gets sent --------------------------------------------------------
|
|
|
|
|
|
def test_the_coord_body_is_ascii_because_coord_send_requires_it() -> None:
|
|
body = okf_watch.render_body(
|
|
pin="ad30107",
|
|
head="beef123",
|
|
commits=[("beef123", "spec: tighten timestamps")],
|
|
normative=["SPEC.md"],
|
|
quiet=["CONTRIBUTING.md"],
|
|
)
|
|
body.encode("ascii") # raises UnicodeEncodeError if not
|
|
|
|
|
|
def test_the_body_names_the_repository_it_watched() -> None:
|
|
""" "No change" is only meaningful with the object named. So is a hit."""
|
|
body = okf_watch.render_body(
|
|
pin="ad30107", head="beef123", commits=[("beef123", "x")], normative=["SPEC.md"], quiet=[]
|
|
)
|
|
assert okf_watch.CANONICAL_REPO in body
|
|
assert "ad30107" in body and "beef123" in body
|
|
|
|
|
|
def test_the_body_carries_the_denominator_not_just_the_hits() -> None:
|
|
body = okf_watch.render_body(
|
|
pin="p",
|
|
head="h",
|
|
commits=[("a", "one"), ("b", "two")],
|
|
normative=["SPEC.md"],
|
|
quiet=["CONTRIBUTING.md", "LICENSE.md"],
|
|
)
|
|
assert "1 of 3" in body
|
|
|
|
|
|
# --- the override that fired a false alarm ---------------------------------
|
|
|
|
|
|
def test_an_overridden_pin_cannot_send_a_real_message(tmp_path: Path, repo: Path) -> None:
|
|
"""Measured 2026-08-23: this exact combination delivered two false alarms.
|
|
|
|
`--pin` exists so the reporting branch can be exercised on demand against
|
|
real upstream history. Exercising it must never be indistinguishable from
|
|
upstream actually moving, so the override forces dry-run rather than
|
|
trusting the operator to pair two flags correctly.
|
|
"""
|
|
args = okf_watch.parse_args(["--pin", "deadbeef", "--state", str(tmp_path / "s.json")])
|
|
assert args.dry_run is True
|
|
|
|
|
|
def test_the_real_pin_leaves_dry_run_alone() -> None:
|
|
assert okf_watch.parse_args([]).dry_run is False
|