from __future__ import annotations import hashlib import json import os import shutil import sys import time from pathlib import Path import pytest from helpers import FIXTURES, read_json, run_cli, write_json CANONICAL_SCRIPTS = ( Path(__file__).resolve().parents[1] / "skills" / "technical-doc-flow" / "scripts" ) sys.path.insert(0, str(CANONICAL_SCRIPTS)) import lint_document as lint_document_module # noqa: E402 from lint_document import ( # noqa: E402 bounded_change_rate, github_slug, markdown_inline_links, mask_markdown_metadata, parse_headings, protected_items, ) def lint_fixture(tmp_path: Path, name: str, *extra: object): base = FIXTURES / name output = tmp_path / f"{name}-lint.json" result = run_cli( "lint_document.py", "--document", base / "document.md", "--logic-map", base / "logic-map.json", "--term-ledger", base / "term-ledger.json", "--reader-contract", base / "reader-contract.json", "--output", output, *extra, ) return result, read_json(output) def lint_main_args(output: Path, *, document: Path | None = None) -> list[str]: base = FIXTURES / "good" return [ "--document", str(document or base / "document.md"), "--logic-map", str(base / "logic-map.json"), "--term-ledger", str(base / "term-ledger.json"), "--reader-contract", str(base / "reader-contract.json"), "--output", str(output), ] def test_good_golden_passes_without_findings(tmp_path: Path) -> None: result, report = lint_fixture(tmp_path, "good") assert result.returncode == 0, result.stderr assert report["verdict"] == "pass" assert report["findings"] == [] rules_path = CANONICAL_SCRIPTS.parent / "config" / "quality-rules.json" assert report["rules_sha256"] == hashlib.sha256( rules_path.read_bytes() ).hexdigest() def test_bad_golden_covers_all_declared_failure_modes(tmp_path: Path) -> None: result, report = lint_fixture(tmp_path, "bad") expected = set(json.loads((FIXTURES / "bad" / "expected-rule-ids.json").read_text())) actual = {finding["rule_id"] for finding in report["findings"]} assert result.returncode == 1 assert expected <= actual assert len(expected) >= 8 def test_malformed_alias_returns_input_error_report(tmp_path: Path) -> None: ledger = read_json(FIXTURES / "good" / "term-ledger.json") ledger["terms"][0]["aliases"] = [123] ledger_path = tmp_path / "ledger.json" output = tmp_path / "lint.json" write_json(ledger_path, ledger) result = run_cli( "lint_document.py", "--document", FIXTURES / "good" / "document.md", "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", ledger_path, "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 2 assert read_json(output)["verdict"] == "input_error" def test_schema_invalid_contract_cannot_receive_a_pass_report(tmp_path: Path) -> None: reader = read_json(FIXTURES / "good" / "reader-contract.json") reader["document_kind"] = "garbage" reader_path = tmp_path / "reader-contract.json" output = tmp_path / "lint.json" write_json(reader_path, reader) result = run_cli( "lint_document.py", "--document", FIXTURES / "good" / "document.md", "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", reader_path, "--output", output, ) assert result.returncode == 2 assert read_json(output)["verdict"] == "input_error" assert "schema 위반" in read_json(output)["findings"][0]["message"] def test_fence_with_trailing_text_is_not_a_closer(tmp_path: Path) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") insertion = "```text\n```anything\n# 코드 안 가짜 제목\n```\n\n" document = document.replace("## 문제", insertion + "## 문제") path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 0, read_json(output)["findings"] def test_setext_heading_is_supported_and_comment_heading_is_ignored(tmp_path: Path) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document = document.replace( "# 캐시 실패를 줄이는 설명", "캐시 실패를 줄이는 설명\n========================", ) document = document.replace("## 문제", "\n\n## 문제") path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 0, read_json(output)["findings"] def test_list_items_are_not_counted_as_one_oversized_sentence_paragraph( tmp_path: Path, ) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += "\n" + "\n".join( f"{index}. 독자가 확인할 항목입니다." for index in range(1, 9) ) path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 0 assert "DOC-P002" not in {item["rule_id"] for item in read_json(output)["findings"]} def test_fidelity_uses_typed_exact_multisets(tmp_path: Path) -> None: baseline = tmp_path / "baseline.md" baseline.write_text( '# 기준\n\n```text\nalpha\n```\n\n`foo` https://example.com/api 500ms "정확한 인용"\n', encoding="utf-8", ) document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += '\n`foobar` https://example.com/api-v2 5000ms "정확한 인용문"\n' document_path = tmp_path / "document.md" document_path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--baseline", baseline, "--output", output, ) report = read_json(output) rule_ids = {item["rule_id"] for item in report["findings"]} assert result.returncode == 1 assert {"DOC-F001", "DOC-F002", "DOC-F003", "DOC-F004", "DOC-F005"} <= rule_ids assert report["fidelity"]["missing"] == 5 def test_fidelity_binds_values_to_nearby_meaning_instead_of_only_multisets( tmp_path: Path, ) -> None: baseline = tmp_path / "baseline.md" baseline.write_text( "# 기준\n\n" "A 코드 `alpha`, B 코드 `beta`.\n" "A 링크 https://example.com/a, B 링크 https://example.com/b.\n" "A 지연 10ms, B 지연 20ms.\n" 'A 문구 "첫 번째", B 문구 "두 번째".\n', encoding="utf-8", ) document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += ( "\nA 코드 `beta`, B 코드 `alpha`.\n" "A 링크 https://example.com/b, B 링크 https://example.com/a.\n" "A 지연 20ms, B 지연 10ms.\n" 'A 문구 "두 번째", B 문구 "첫 번째".\n' ) document_path = tmp_path / "document.md" document_path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--baseline", baseline, "--output", output, ) report = read_json(output) rule_ids = {item["rule_id"] for item in report["findings"]} assert result.returncode == 1 assert {"DOC-F002", "DOC-F003", "DOC-F004", "DOC-F005"} <= rule_ids def test_number_units_are_protected_when_joined_to_korean_particles() -> None: inventory = protected_items("약10ms를 기다리고 20초동안 관찰하며 30msfoo는 식별자로 둔다.") assert inventory["number_unit"] == ["약10ms", "20초"] def test_both_endpoint_unit_ranges_are_single_ordered_protected_values() -> None: inventory = protected_items("지연 5ms-10ms, 비율 5%-10%, 비호환 5ms-10s.") assert inventory["number_range"] == ["5ms-10ms", "5%-10%"] assert inventory["number_unit"] == ["5ms", "10s"] def test_reader_visible_multiline_and_entity_quotes_exclude_metadata_and_code() -> None: inventory = protected_items( '[링크](dest "metadata") `"inline"`\n' '
\n"raw code"\n\n' '보이는 "첫 줄\n둘째 줄"과 ' '“굽은 인용”이다.\n' ) assert inventory["quote"] == ['"첫 줄\n둘째 줄"', '“굽은 인용”'] @pytest.mark.parametrize("thematic_break", ["_ _ _", "* * *"]) def test_spaced_thematic_break_terminates_lazy_blockquote_inventory( thematic_break: str, ) -> None: inventory = protected_items( f"> 첫 줄\n게으른 이어쓰기\n{thematic_break}\n분리된 본문\n" ) assert inventory["quote"] == ["첫 줄\n게으른 이어쓰기"] assert protected_items( f'"첫 줄\n{thematic_break}\n분리된 둘째 줄"' )["quote"] == [] def test_common_absolute_uri_schemes_are_protected() -> None: uris = [ "http://ApiHost.test/HttpToken", "https://ApiHost.test/HttpsToken", "ftp://FileHost.test/FtpToken", "ftps://FileHost.test/FtpsToken", "file:///tmp/FileToken", "mailto:UserToken@example.com", "ssh://GitHost.test/SshToken", "git://GitHost.test/GitToken", ] assert protected_items("\n".join(uris))["url"] == uris def test_inline_link_parser_always_progresses_and_supports_multiline_title() -> None: assert markdown_inline_links("[x](dest") == [] links = markdown_inline_links('[go](#missing "long\n title")') assert len(links) == 1 assert links[0][2:4] == (5, 13) assert mask_markdown_metadata("[x\n\ny](TODO)") == "[x\n\ny](TODO)" def test_heading_entities_require_semicolon_and_slugs_are_globally_unique() -> None: assert github_slug("A © B") == "a-copy-b" assert github_slug("A © B") == "a-b" assert [item.slug for item in parse_headings("## foo\n## foo-1\n## foo\n")] == [ "foo", "foo-1", "foo-2", ] def test_multiline_inline_code_allows_non_interrupting_ordered_marker() -> None: inventory = protected_items("text `a\n2. TODO\nc` tail") assert inventory["inline_code"] == ["a 2. TODO c"] def test_finalizer_change_rate_detects_nonlocal_reorder_and_is_linear_enough() -> None: paragraphs = [f"문단 {index}에는 고유한 설명과 값 {index}가 있다." for index in range(5000)] before = "\n\n".join(paragraphs) after = "\n\n".join(reversed(paragraphs)) started = time.monotonic() rate = bounded_change_rate(before, after) elapsed = time.monotonic() - started assert rate > 0.15 assert elapsed < 5.0 def test_finalizer_change_rate_allows_one_local_word_edit() -> None: before = "이 문서는 캐시 장애의 원인과 검증 범위를 독자에게 차례로 설명한다." after = "이 문서는 캐시 장애의 원인과 검증 한계를 독자에게 차례로 설명한다." assert bounded_change_rate(before, after) <= 0.15 def test_finalizer_change_rate_detects_reorder_even_when_every_paragraph_is_edited() -> None: paragraphs = [ f"문단 {index}은 캐시 동작 {index}와 고유한 검증 범위를 설명한다." for index in range(120) ] before = "\n\n".join(paragraphs) after = "\n\n".join(f"{item} 보충" for item in reversed(paragraphs)) assert bounded_change_rate(before, after) > 0.15 def test_draft_baseline_hash_and_change_rate_are_reported(tmp_path: Path) -> None: draft = tmp_path / "draft.md" draft.write_text("# 완전히 다른 초안\n\n" + "다른 문장. " * 100, encoding="utf-8") result, report = lint_fixture(tmp_path, "good", "--draft-baseline", draft) assert result.returncode == 1 assert "FNL-001" in {item["rule_id"] for item in report["findings"]} assert report["fidelity"]["draft_baseline"]["sha256"] assert report["fidelity"]["finalization_change_rate"] > 0.15 def test_unregistered_candidate_is_a_default_hard_gate(tmp_path: Path) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += "\n등록하지 않은 NewCacheMode 후보가 있다.\n" path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" common = ( "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert run_cli("lint_document.py", *common).returncode == 1 assert "DOC-T007" in { item["rule_id"] for item in read_json(output)["findings"] } def test_fail_on_warning_changes_exit_code(tmp_path: Path) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += "\n\n" + ("긴" * 901) + "\n" path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" common = ( "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert run_cli("lint_document.py", *common).returncode == 0 assert "DOC-P001" in { item["rule_id"] for item in read_json(output)["findings"] } assert run_cli("lint_document.py", *common, "--fail-on", "warning").returncode == 1 def test_definition_window_threshold_is_actually_enforced(tmp_path: Path) -> None: rules_path = ( Path(__file__).resolve().parents[1] / "skills" / "technical-doc-flow" / "config" / "quality-rules.json" ) rules = read_json(rules_path) rules["thresholds"]["term"]["definition_window_chars"] = 10 strict_rules = tmp_path / "quality-rules.json" write_json(strict_rules, rules) result, report = lint_fixture(tmp_path, "good", "--rules", strict_rules) assert result.returncode == 1 assert "DOC-T001" in {item["rule_id"] for item in report["findings"]} def test_definition_window_also_accepts_plain_definition_after_first_name( tmp_path: Path, ) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document = document.replace( "같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같게 되는 성질을 멱등성이라고 한다.", "멱등성이라는 이름을 쓴다. 같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같게 되는 성질을 뜻한다.", ) ledger = read_json(FIXTURES / "good" / "term-ledger.json") ledger["terms"][0]["first_use"] = "멱등성이라는 이름을 쓴다." document_path = tmp_path / "document.md" ledger_path = tmp_path / "term-ledger.json" output = tmp_path / "lint.json" document_path.write_text(document, encoding="utf-8") write_json(ledger_path, ledger) result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", ledger_path, "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 0, read_json(output)["findings"] def test_hidden_html_comment_cannot_satisfy_term_first_use(tmp_path: Path) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") first_use = ( "같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같게 되는 성질을 " "멱등성이라고 한다." ) document = document.replace( first_use, f"\n\n멱등성을 사용한다.", ) path = tmp_path / "document.md" path.write_text(document, encoding="utf-8") output = tmp_path / "lint.json" result = run_cli( "lint_document.py", "--document", path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 1 assert "DOC-T001" in { item["rule_id"] for item in read_json(output)["findings"] } def test_ledger_term_must_actually_appear_and_first_use_must_name_it( tmp_path: Path, ) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") original = ( "같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같게 되는 성질을 " "멱등성이라고 한다." ) generic = "같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같게 되는 성질을 설명한다." document = document.replace(original, generic).replace("멱등성", "같은 결과 성질") ledger = read_json(FIXTURES / "good" / "term-ledger.json") ledger["terms"][0]["first_use"] = generic document_path = tmp_path / "document.md" ledger_path = tmp_path / "term-ledger.json" output = tmp_path / "lint.json" document_path.write_text(document, encoding="utf-8") write_json(ledger_path, ledger) result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", ledger_path, "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 1 messages = [ item["message"] for item in read_json(output)["findings"] if item["rule_id"] == "DOC-T001" ] assert any("실제로 등장하지" in message for message in messages) assert any("first_use에 정식 용어" in message for message in messages) @pytest.mark.parametrize( "protected_name", ["document.md", "logic-map.json", "term-ledger.json", "reader-contract.json"], ) def test_lint_output_cannot_overwrite_inputs(tmp_path: Path, protected_name: str) -> None: base = FIXTURES / "good" protected = tmp_path / protected_name shutil.copyfile(base / protected_name, protected) before = protected.read_bytes() paths = { "document.md": protected if protected_name == "document.md" else base / "document.md", "logic-map.json": protected if protected_name == "logic-map.json" else base / "logic-map.json", "term-ledger.json": protected if protected_name == "term-ledger.json" else base / "term-ledger.json", "reader-contract.json": protected if protected_name == "reader-contract.json" else base / "reader-contract.json", } result = run_cli( "lint_document.py", "--document", paths["document.md"], "--logic-map", paths["logic-map.json"], "--term-ledger", paths["term-ledger.json"], "--reader-contract", paths["reader-contract.json"], "--output", protected, ) assert result.returncode == 2 assert protected.read_bytes() == before def test_lint_existing_output_requires_same_tool_ownership(tmp_path: Path) -> None: output = tmp_path / "report.json" output.write_text('{"tool":"verify_run","sentinel":true}\n', encoding="utf-8") before = output.read_bytes() result = run_cli( "lint_document.py", "--document", FIXTURES / "good" / "document.md", "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 2 assert output.read_bytes() == before def test_lint_malformed_same_tool_output_is_not_overwritten(tmp_path: Path) -> None: output = tmp_path / "report.json" output.write_text('{"tool":"lint_document","sentinel":true}\n', encoding="utf-8") before = output.read_bytes() result = run_cli( "lint_document.py", "--document", FIXTURES / "good" / "document.md", "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--output", output, ) assert result.returncode == 2 assert output.read_bytes() == before @pytest.mark.parametrize("raced_entry", ["file", "symlink", "hardlink"]) def test_lint_publish_rejects_entry_created_after_preflight( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, raced_entry: str ) -> None: output = tmp_path / "report.json" protected = tmp_path / "document.md" shutil.copyfile(FIXTURES / "good" / "document.md", protected) protected_before = protected.read_bytes() original_publish = lint_document_module.publish_report_json def publish_after_race(prepared, value): if raced_entry == "file": prepared.path.write_bytes(b"unrelated sentinel\n") elif raced_entry == "symlink": prepared.path.symlink_to(protected) else: os.link(protected, prepared.path) return original_publish(prepared, value) monkeypatch.setattr( lint_document_module, "publish_report_json", publish_after_race, ) result = lint_document_module.main(lint_main_args(output, document=protected)) assert result == 2 assert protected.read_bytes() == protected_before if raced_entry == "file": assert output.read_bytes() == b"unrelated sentinel\n" elif raced_entry == "symlink": assert output.is_symlink() assert output.resolve() == protected.resolve() else: assert not output.is_symlink() assert os.path.samefile(output, protected) def test_lint_publish_rejects_owned_report_changed_after_preflight( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: output = tmp_path / "report.json" assert lint_document_module.main(lint_main_args(output)) == 0 original_publish = lint_document_module.publish_report_json changed = output.read_bytes() + b"\n" original_inode = output.stat().st_ino def publish_after_race(prepared, value): prepared.path.write_bytes(changed) assert prepared.path.stat().st_ino == original_inode return original_publish(prepared, value) monkeypatch.setattr( lint_document_module, "publish_report_json", publish_after_race, ) result = lint_document_module.main(lint_main_args(output)) assert result == 2 assert output.stat().st_ino == original_inode assert output.read_bytes() == changed def test_lint_can_conditionally_refresh_owned_report(tmp_path: Path) -> None: output = tmp_path / "report.json" assert lint_document_module.main(lint_main_args(output)) == 0 assert lint_document_module.main(lint_main_args(output)) == 0 assert read_json(output)["tool"] == "lint_document" def test_fidelity_does_not_count_values_hidden_in_html_comments( tmp_path: Path, ) -> None: baseline = tmp_path / "baseline.md" baseline.write_text( '# 기준\n\n`SecretToken987` https://example.com/unique-cache 731ms "고유 인용문"\n', encoding="utf-8", ) document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += ( '\n\n' ) document_path = tmp_path / "document.md" output = tmp_path / "lint.json" document_path.write_text(document, encoding="utf-8") result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", FIXTURES / "good" / "term-ledger.json", "--reader-contract", FIXTURES / "good" / "reader-contract.json", "--baseline", baseline, "--output", output, ) report = read_json(output) assert result.returncode == 1 assert {"DOC-F002", "DOC-F003", "DOC-F004", "DOC-F005"} <= { item["rule_id"] for item in report["findings"] } assert report["fidelity"]["missing"] == 4 def test_registered_multiword_term_suppresses_its_internal_candidate_only( tmp_path: Path, ) -> None: document = (FIXTURES / "good" / "document.md").read_text(encoding="utf-8") document += "\n중복 실행을 묶는 single-flight coordinator를 사용한다.\n" ledger = read_json(FIXTURES / "good" / "term-ledger.json") reader = read_json(FIXTURES / "good" / "reader-contract.json") ledger["assumed_known"].append("single-flight coordinator") reader["assumed_known"].append("single-flight coordinator") document_path = tmp_path / "document.md" ledger_path = tmp_path / "term-ledger.json" reader_path = tmp_path / "reader-contract.json" output = tmp_path / "lint.json" document_path.write_text(document, encoding="utf-8") write_json(ledger_path, ledger) write_json(reader_path, reader) result = run_cli( "lint_document.py", "--document", document_path, "--logic-map", FIXTURES / "good" / "logic-map.json", "--term-ledger", ledger_path, "--reader-contract", reader_path, "--output", output, ) candidates = { item["context"] for item in read_json(output)["findings"] if item["rule_id"] == "DOC-T007" } assert result.returncode == 0 assert "single-flight" not in candidates def test_registered_prefix_does_not_hide_longer_unregistered_term(tmp_path: Path) -> None: result, report = lint_fixture(tmp_path, "bad") assert result.returncode == 1 candidates = { item["context"] for item in report["findings"] if item["rule_id"] == "DOC-T007" } assert "HTTPX" in candidates