From 85d517b5ffd75a7bcf97205fa1ca51f60f9aad80 Mon Sep 17 00:00:00 2001 From: Marcus Lee Date: Wed, 3 Jun 2026 17:45:01 +1000 Subject: [PATCH 1/2] Prove entrypoint freshness sweep labels and clean state --- doc_steward/cli.py | 28 ++++++++- doc_steward/entrypoint_freshness_sweep.py | 59 ++++++++++++++++++ tests_doc/test_entrypoint_freshness_sweep.py | 63 ++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/doc_steward/cli.py b/doc_steward/cli.py index cf2d5132..27f40b11 100644 --- a/doc_steward/cli.py +++ b/doc_steward/cli.py @@ -23,7 +23,15 @@ from .artefact_index_validator import check_artefact_index from .dap_validator import check_dap_compliance from .doc_authority_manifest import check_doc_authority_manifest -from .entrypoint_freshness_sweep import run as run_entrypoint_freshness_sweep +from .entrypoint_freshness_sweep import ( + DEFAULT_LABELS as ENTRYPOINT_FRESHNESS_LABELS, +) +from .entrypoint_freshness_sweep import ( + run as run_entrypoint_freshness_sweep, +) +from .entrypoint_freshness_sweep import ( + validate_repo_labels as validate_entrypoint_freshness_labels, +) from .freshness_validator import check_freshness, get_freshness_mode from .global_archive_link_ban_validator import check_global_archive_link_ban from .index_checker import check_index @@ -161,6 +169,19 @@ def cmd_freshness_check(args: argparse.Namespace) -> int: def cmd_entrypoint_freshness_sweep(args: argparse.Namespace) -> int: """Run entrypoint freshness issue-creator adapter.""" + if args.validate_labels: + payload = validate_entrypoint_freshness_labels(args.repo, list(ENTRYPOINT_FRESHNESS_LABELS)) + if args.json: + import json + + print(json.dumps(payload, indent=2, sort_keys=True)) + else: + print( + "[PASSED] entrypoint freshness labels valid" + if payload["valid"] + else "[FAILED] entrypoint freshness labels missing" + ) + return 0 if payload["valid"] else 1 run_entrypoint_freshness_sweep( Path(args.repo_root).resolve(), repo=args.repo, @@ -359,6 +380,11 @@ def main() -> int: help="write a sweep_lib run receipt; rejected with --dry-run", ) p_entrypoint_sweep.add_argument("--json", action="store_true") + p_entrypoint_sweep.add_argument( + "--validate-labels", + action="store_true", + help="read-only preflight that configured GitHub labels exist", + ) p_entrypoint_sweep.set_defaults(func=cmd_entrypoint_freshness_sweep) # protocols-structure-check diff --git a/doc_steward/entrypoint_freshness_sweep.py b/doc_steward/entrypoint_freshness_sweep.py index 414f7e7c..53170875 100644 --- a/doc_steward/entrypoint_freshness_sweep.py +++ b/doc_steward/entrypoint_freshness_sweep.py @@ -105,6 +105,41 @@ def _as_int(value: object) -> int: return value if isinstance(value, int) else 0 +def gh_list_labels(repo: str) -> set[str]: + """Read live GitHub labels for explicit preflight validation.""" + proc = subprocess.run( + ["gh", "label", "list", "-R", repo, "--limit", "200", "--json", "name"], + text=True, + capture_output=True, + timeout=90, + ) + if proc.returncode != 0: + raise RuntimeError(proc.stderr.strip() or proc.stdout.strip()) + raw = json.loads(proc.stdout or "[]") + return {str(item.get("name")) for item in raw if item.get("name")} + + +def validate_repo_labels( + repo: str, + labels: list[str], + *, + existing_labels: set[str] | None = None, +) -> dict[str, object]: + """Validate configured issue labels before an issue-creating run. + + This is read-only. It prevents a late `gh issue create` failure and gives the + sweep a deterministic label preflight receipt. + """ + available = existing_labels if existing_labels is not None else gh_list_labels(repo) + missing = [label for label in labels if label not in available] + return { + "repo": repo, + "labels": labels, + "missing_labels": missing, + "valid": not missing, + } + + def gh_create_issue(repo: str, title: str, body: str, labels: list[str]) -> int: with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as handle: handle.write(body) @@ -171,6 +206,13 @@ def process_findings( libs = sweep_lib or _load_sweep_lib() fingerprint = libs["make_fingerprint"](SWEEP_ID, TARGET, CHECK_ID, normalized, "warning") libs["validate_issue_payload"](title, body, labels) + if create_issue: + label_result = validate_repo_labels(repo, labels) + if not label_result["valid"]: + raise RuntimeError( + "missing GitHub labels for doc-entrypoint freshness issue: " + + ", ".join(str(label) for label in label_result["missing_labels"]) + ) row = { "fingerprint": fingerprint, @@ -282,9 +324,26 @@ def main(argv: list[str] | None = None) -> int: help="write a sweep_lib run receipt; rejected with --dry-run", ) parser.add_argument("--json", action="store_true") + parser.add_argument( + "--validate-labels", + action="store_true", + help="read-only preflight that configured GitHub labels exist", + ) args = parser.parse_args(argv) if args.dry_run and args.record_run: parser.error("--record-run mutates sweep receipts and cannot be combined with --dry-run") + if args.validate_labels: + payload = validate_repo_labels(args.repo, list(DEFAULT_LABELS)) + if args.json: + print(json.dumps(payload, indent=2, sort_keys=True)) + else: + message = ( + "[PASSED] entrypoint freshness labels valid" + if payload["valid"] + else "[FAILED] entrypoint freshness labels missing" + ) + print(message) + return 0 if payload["valid"] else 1 run( args.repo_root, repo=args.repo, diff --git a/tests_doc/test_entrypoint_freshness_sweep.py b/tests_doc/test_entrypoint_freshness_sweep.py index c79305a8..c13c2d93 100644 --- a/tests_doc/test_entrypoint_freshness_sweep.py +++ b/tests_doc/test_entrypoint_freshness_sweep.py @@ -12,6 +12,7 @@ normalize_error, process_findings, run, + validate_repo_labels, ) @@ -97,6 +98,29 @@ def test_process_findings_created_records_one_finding_without_issue_creation(): assert db.closed is True +def test_process_findings_create_issue_validates_labels_before_upsert(monkeypatch): + db = FakeDB(action="created") + import doc_steward.entrypoint_freshness_sweep as sweep + + monkeypatch.setattr( + sweep, + "validate_repo_labels", + lambda repo, labels: { + "repo": repo, + "labels": labels, + "missing_labels": ["severity:warning"], + "valid": False, + }, + ) + + import pytest + + with pytest.raises(RuntimeError, match="missing GitHub labels"): + process_findings([finding()], dry_run=False, create_issue=True, sweep_lib=fake_lib(db)) + + assert db.calls == [] + + def test_process_findings_updated_skips_duplicate_creation(): db = FakeDB(action="updated") @@ -109,6 +133,32 @@ def test_process_findings_updated_skips_duplicate_creation(): assert db.closed is True +def test_validate_repo_labels_reports_missing_without_mutation(): + result = validate_repo_labels( + "marcusglee11/lifeos-operational-bus", + ["sweep:inventory-hygiene", "severity:warning"], + existing_labels={"sweep:inventory-hygiene"}, + ) + + assert result == { + "repo": "marcusglee11/lifeos-operational-bus", + "labels": ["sweep:inventory-hygiene", "severity:warning"], + "missing_labels": ["severity:warning"], + "valid": False, + } + + +def test_validate_repo_labels_accepts_configured_labels(): + result = validate_repo_labels( + "marcusglee11/lifeos-operational-bus", + ["sweep:inventory-hygiene", "severity:warning"], + existing_labels={"sweep:inventory-hygiene", "severity:warning"}, + ) + + assert result["valid"] is True + assert result["missing_labels"] == [] + + def test_process_findings_clean_state_has_no_rows(): result = process_findings([], dry_run=False, sweep_lib=fake_lib(FakeDB())) @@ -142,6 +192,19 @@ def test_run_dirty_dry_run_does_not_load_sweep_lib(monkeypatch, tmp_path): assert rows[0]["action"] == "dry-run" +def test_run_clean_dry_run_is_quiet_and_does_not_record_receipt(monkeypatch, tmp_path, capsys): + import doc_steward.entrypoint_freshness_sweep as sweep + + monkeypatch.setattr(sweep, "check_entrypoint_freshness", lambda repo_root: []) + result = run(tmp_path, dry_run=True, json_output=False, record_run=False) + captured = capsys.readouterr() + + assert captured.out.strip() == "[SILENT]" + assert result["findings"] == 0 + assert result["rows"] == [] + assert result["receipt"] is None + + def test_cli_rejects_dry_run_record_run(tmp_path): import pytest From 663064308362592d2787a8703fd018c833817b8b Mon Sep 17 00:00:00 2001 From: Marcus Lee Date: Wed, 3 Jun 2026 18:04:26 +1000 Subject: [PATCH 2/2] Avoid label preflight on duplicate freshness findings --- doc_steward/entrypoint_freshness_sweep.py | 13 ++++++------- tests_doc/test_entrypoint_freshness_sweep.py | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc_steward/entrypoint_freshness_sweep.py b/doc_steward/entrypoint_freshness_sweep.py index 53170875..038a034d 100644 --- a/doc_steward/entrypoint_freshness_sweep.py +++ b/doc_steward/entrypoint_freshness_sweep.py @@ -206,13 +206,6 @@ def process_findings( libs = sweep_lib or _load_sweep_lib() fingerprint = libs["make_fingerprint"](SWEEP_ID, TARGET, CHECK_ID, normalized, "warning") libs["validate_issue_payload"](title, body, labels) - if create_issue: - label_result = validate_repo_labels(repo, labels) - if not label_result["valid"]: - raise RuntimeError( - "missing GitHub labels for doc-entrypoint freshness issue: " - + ", ".join(str(label) for label in label_result["missing_labels"]) - ) row = { "fingerprint": fingerprint, @@ -230,6 +223,12 @@ def process_findings( updated = 0 if action == "created": if create_issue: + label_result = validate_repo_labels(repo, labels) + if not label_result["valid"]: + raise RuntimeError( + "missing GitHub labels for doc-entrypoint freshness issue: " + + ", ".join(str(label) for label in label_result["missing_labels"]) + ) issue_num = gh_create_issue(repo, title, body, labels) db.upsert_finding( fingerprint, diff --git a/tests_doc/test_entrypoint_freshness_sweep.py b/tests_doc/test_entrypoint_freshness_sweep.py index c13c2d93..adb8a14f 100644 --- a/tests_doc/test_entrypoint_freshness_sweep.py +++ b/tests_doc/test_entrypoint_freshness_sweep.py @@ -98,7 +98,7 @@ def test_process_findings_created_records_one_finding_without_issue_creation(): assert db.closed is True -def test_process_findings_create_issue_validates_labels_before_upsert(monkeypatch): +def test_process_findings_create_issue_validates_labels_before_issue_creation(monkeypatch): db = FakeDB(action="created") import doc_steward.entrypoint_freshness_sweep as sweep @@ -118,7 +118,8 @@ def test_process_findings_create_issue_validates_labels_before_upsert(monkeypatc with pytest.raises(RuntimeError, match="missing GitHub labels"): process_findings([finding()], dry_run=False, create_issue=True, sweep_lib=fake_lib(db)) - assert db.calls == [] + assert len(db.calls) == 1 + assert db.calls[0][0][0].startswith("fp:inventory-hygiene-sweep:lifeos-doc-entrypoint") def test_process_findings_updated_skips_duplicate_creation():