yams/tests/scripts/test_report_config_key_usage.py
Trevon 6b5b6647bd
[04/14] Repository hygiene and reviewable configuration/retrieval contracts (#144)
* chore(repo): land the retrieval contract and drop root cruft

Lands work that had been sitting uncommitted in the working tree:

- docs/architecture/retrieval_contracts.md plus its executable form,
  tests/benchmarks/retrieval_contract.py and test_retrieval_contract.py,
  now registered in meson as retrieval_contract_unit (suite: policy).
  .gitignore keeps them tracked beneath the tests/benchmarks/*.py ignore.
- AGENTS.md: the Development Memory section, and the product default
  recorded as hybrid_assist + shadow with topology-off as the control,
  which is what search_topology_stage already does.
  docs/benchmarks/index.md gains the matching search_shadow_cost plan.
- .gitleaks.toml, and a Secret scan workflow that actually runs it on
  push and pull requests (nothing in .github invoked gitleaks before).

Deletes three root files with zero references anywhere in the tree:
bench_profile.json (ad-hoc profiling leftover), CppProperties.json
(Visual Studio config; the repo standardises on Meson), and spdlog.pc
(a hand-copied spdlog 1.12 pkg-config with prefix=/usr/local that would
mislead any PKG_CONFIG_PATH lookup; spdlog comes from Conan).

docs/plans/ (two p2p designs already shipped in 5a53cdfa and d012d95b)
was ingested into YAMS with task=desloppification source=plan, hydrated
via yams cat and byte-compared before removal, per the new AGENTS.md
rule; tests/scripts/analyze_simeon_adaptivity.py no longer points at a
plans directory.

Also reflows two stale line anchors in raw_environment_allowlist.txt
for tools/yams-cli/main.cpp (shifted by 11c39157) so the policy suite
passes again; no new environment writes.

Not included: the external/yams-repo submodule pointer in the working
tree was a stale local clone two commits behind, not an intended bump.

Signed-off-by: trvon <me@trevon.dev>

* test(policy): make the configuration-authority ratchet bidirectional

check_configuration_authority.py only reported keys and TOML readers
present in code but missing from the allowlists, so the reviewed
YAMS_* surface could grow but never shrink: production_environment_keys.txt
even said removals "may remain grandfathered". An allowlist that only
accretes stops describing the code.

The policy now also fails on allowlist entries with no remaining
occurrence in src/, include/, or tools/, naming the lines to delete.
Key removals in later commits therefore have to update the allowlist in
the same change, and the count in the success banner becomes a real
measure of the environment surface (432 today).

stale_entries() exposes the two stale sets for tooling; three unit
tests cover stale key, stale reader, and the returned names.

Signed-off-by: trvon <me@trevon.dev>

* tools(policy): report where every configuration key is referenced

Adds tests/scripts/report_config_key_usage.py: for each YAMS_* key in
production_environment_keys.txt and each dotted TOML key literally
resolved in ConfigResolver.cpp, count whole-token references per tree
bucket (src, test, bench, docs, plugin; build output and the allowlists
themselves excluded) and assign a verdict: KEEP when anything outside
product code exercises or documents the key, CANDIDATE when only
product code mentions it, NOT_IN_SRC when the allowlist is stale.

The report is evidence, not a decision. From here on a key deletion
must paste its CANDIDATE rows into the change description; the first
audit pass hand-listed keys that turned out to have bench and test
consumers, which is exactly the mistake a mechanical inventory avoids.

Today: 432 env keys (215 CANDIDATE, 217 KEEP), 117 TOML keys
(103 CANDIDATE, 14 KEEP). Registered as config_key_usage_report_unit.

The report counts only files git tracks (git ls-files) when the root is a
work tree, so an ignored local bench harness cannot turn a CANDIDATE into a
KEEP on one machine and not another; outside a work tree it falls back to
scanning the directory, which is what the synthetic-tree test exercises.

Signed-off-by: trvon <me@trevon.dev>

---------

Signed-off-by: trvon <me@trevon.dev>
2026-09-12 10:09:18 -04:00

84 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Tests for the configuration key usage report."""
from __future__ import annotations
import importlib.util
import tempfile
import unittest
from pathlib import Path
SCRIPT = Path(__file__).with_name("report_config_key_usage.py")
SPEC = importlib.util.spec_from_file_location("report_config_key_usage", SCRIPT)
assert SPEC and SPEC.loader
REPORT = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(REPORT)
class ConfigKeyUsageReportTest(unittest.TestCase):
def setUp(self) -> None:
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
for rel in ("src", "tests/scripts", "tests/benchmarks", "docs", "plugins", "build"):
(self.root / rel).mkdir(parents=True)
(self.root / "src" / "knobs.cpp").write_text(
'getenv("YAMS_ONLY_IN_SRC"); getenv("YAMS_BENCHED"); getenv("YAMS_DOCUMENTED");\n'
'resolve("search.only_in_src"); resolve("search.tested");\n',
encoding="utf-8",
)
(self.root / "tests" / "scripts" / "production_environment_keys.txt").write_text(
"# allowlist\nYAMS_ONLY_IN_SRC\nYAMS_BENCHED\nYAMS_DOCUMENTED\nYAMS_STALE\n"
"YAMS_BENCHED_LONGER\n",
encoding="utf-8",
)
(self.root / "tests" / "benchmarks" / "arms.py").write_text(
'ARMS = {"YAMS_BENCHED": "1", "YAMS_BENCHED_LONGER": "2"}\n', encoding="utf-8"
)
(self.root / "tests" / "unit_test.cpp").write_text(
'cfg = "search.tested = 3";\n', encoding="utf-8"
)
(self.root / "docs" / "config.md").write_text("Set `YAMS_DOCUMENTED`.\n",
encoding="utf-8")
# Build output must never count as a consumer.
(self.root / "build" / "generated.cpp").write_text(
'getenv("YAMS_ONLY_IN_SRC"); "search.only_in_src"\n', encoding="utf-8"
)
def tearDown(self) -> None:
self.temp.cleanup()
def rows(self) -> dict[str, dict]:
env_keys = REPORT.read_allowlist(
self.root / "tests" / "scripts" / "production_environment_keys.txt"
)
return {r["key"]: r for r in REPORT.build_report(
self.root, env_keys, ["search.only_in_src", "search.tested"])}
def test_verdicts(self) -> None:
rows = self.rows()
self.assertEqual(rows["YAMS_ONLY_IN_SRC"]["verdict"], "CANDIDATE")
self.assertEqual(rows["YAMS_BENCHED"]["verdict"], "KEEP")
self.assertEqual(rows["YAMS_DOCUMENTED"]["verdict"], "KEEP")
self.assertEqual(rows["YAMS_STALE"]["verdict"], "NOT_IN_SRC")
self.assertEqual(rows["search.only_in_src"]["verdict"], "CANDIDATE")
self.assertEqual(rows["search.tested"]["verdict"], "KEEP")
def test_counts_are_whole_token(self) -> None:
rows = self.rows()
# YAMS_BENCHED must not absorb YAMS_BENCHED_LONGER's bench reference.
self.assertEqual(rows["YAMS_BENCHED"]["bench"], 1)
self.assertEqual(rows["YAMS_BENCHED_LONGER"]["verdict"], "NOT_IN_SRC")
def test_allowlist_and_build_output_are_not_consumers(self) -> None:
rows = self.rows()
self.assertEqual(rows["YAMS_ONLY_IN_SRC"]["test"], 0)
self.assertEqual(rows["YAMS_ONLY_IN_SRC"]["src"], 1)
def test_resolver_literal_extraction(self) -> None:
resolver = self.root / "src" / "knobs.cpp"
self.assertEqual(REPORT.resolver_toml_keys(resolver),
["search.only_in_src", "search.tested"])
if __name__ == "__main__":
unittest.main()