"""Regression tests for manually reviewed confidence and relationship linting.""" from __future__ import annotations import json import os import subprocess import sys from pathlib import Path from obsidian_wiki.lint import lint_vault from obsidian_wiki.trust import ( build_trust_ledger, check_trust_ledger, iter_trust_pages, page_fingerprint, update_trust_ledger, write_trust_ledger, ) def _page( vault: Path, relpath: str, *, confidence: float = 0.80, updated: str = "2026-07-12T17:38:39+07:00", body: str = "Reviewed material claim.", relationships: list[tuple[str, str]] | None = None, ) -> Path: path = vault / relpath path.parent.mkdir(parents=True, exist_ok=True) lines = [ "---", f"title: {path.stem}", f"category: {path.parts[-2] if len(path.parts) > 1 else 'concepts'}", "tags: [test]", "sources:", " - github.com/example/repo@0123456789abcdef0123456789abcdef01234567", "created: 2026-07-01", f"updated: {updated}", f"base_confidence: {confidence:.2f}", "lifecycle: reviewed", ] if relationships: lines.append("relationships:") for relation_type, target in relationships: lines.extend([f" - type: {relation_type}", f' target: "[[{target}]]"']) lines.extend(["---", f"# {path.stem}", "", body, ""]) path.write_text("\n".join(lines), encoding="utf-8") return path def _write_ledger(vault: Path) -> Path: ledger = build_trust_ledger(vault, reviewed_at="2026-07-12T17:38:39+07:00") path = vault / "_meta" / "trust-ledger.json" write_trust_ledger(path, ledger) return path def _run_cli(home: Path, *args: str) -> subprocess.CompletedProcess[str]: env = os.environ.copy() env["HOME"] = str(home) return subprocess.run( [sys.executable, "-m", "obsidian_wiki.cli", *args], capture_output=True, text=True, env=env, ) def test_reviewed_ledger_is_authoritative_instead_of_reclassifying_sources(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md", confidence=0.53) _page(vault, "skills/beta.md", confidence=0.80) ledger_path = _write_ledger(vault) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "pass" assert report["counts"] == { "reviewed": 2, "not_applicable": 0, "stale": 0, "unreviewed": 0, "score_mismatches": 0, "missing_pages": 0, "errors": 0, } assert {item["page"] for item in report["reviewed"]} == { "concepts/alpha.md", "skills/beta.md", } def test_claim_change_invalidates_review_but_updated_timestamp_does_not(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md", confidence=0.53) ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("updated: 2026-07-12T17:38:39+07:00", "updated: 2026-07-13T09:00:00+07:00")) assert check_trust_ledger(vault, ledger_path)["counts"]["reviewed"] == 1 page.write_text(page.read_text().replace("Reviewed material claim.", "Changed material claim.")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "warn" assert report["stale"] == [{"page": "concepts/alpha.md", "reason": "material_fingerprint_changed"}] def test_material_change_marks_review_stale(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md", confidence=0.53) ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("Reviewed material claim.", "Changed material claim.")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "warn" assert report["stale"] == [ {"page": "concepts/alpha.md", "reason": "material_fingerprint_changed"} ] def test_duplicate_confidence_field_fails_instead_of_bypassing_fingerprint(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md", confidence=0.53) ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("lifecycle: reviewed", "base_confidence: 0.99\nlifecycle: reviewed")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "duplicate top-level field: base_confidence"} ] def test_volatile_field_with_nested_content_fails_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("updated: 2026-07-12T17:38:39+07:00", "updated: 2026-07-12T17:38:39+07:00\n hidden: material")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "updated must be a scalar"} ] def test_invalid_confidence_is_checked_before_stale_short_circuit(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) changed = page.read_text().replace("Reviewed material claim.", "Changed material claim.") page.write_text(changed.replace("base_confidence: 0.80", "base_confidence: nan")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["stale"] == [] assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "base_confidence is not finite"} ] def test_invalid_lifecycle_fails_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("lifecycle: reviewed", "lifecycle: totally-invalid")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "invalid lifecycle: totally-invalid"} ] def test_owner_trust_schema_accepts_lifecycle_and_optional_fields(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("base_confidence: 0.80\n", "").replace("lifecycle: reviewed", "lifecycle: active")) report = check_trust_ledger( vault, ledger_path, allowed_lifecycles={"active", "confirmed", "stub"}, required_trust_keys=("updated",), schema_source="wiki/AGENTS.md", ) assert report["status"] == "warn" assert report["errors"] == [] assert report["not_applicable"] == [ { "page": "concepts/alpha.md", "reason": "base_confidence_absent_by_owner_schema", } ] assert report["stale"] == [ { "page": "concepts/alpha.md", "reason": "confidence_not_applicable_but_ledger_entry_exists", } ] assert report["schema"] == { "source": "wiki/AGENTS.md", "allowed_lifecycles": ["active", "confirmed", "stub"], "required_trust_fields": ["updated"], } def test_owner_schema_build_excludes_no_confidence_page_as_not_applicable(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text(page.read_text().replace("base_confidence: 0.80\n", "")) ledger = build_trust_ledger( vault, reviewed_at="2026-08-05T12:00:00+09:00", required_trust_keys=("updated",), ) assert ledger["pages"] == {} assert ledger["not_applicable"] == ["concepts/alpha.md"] assert page_fingerprint(page, required_trust_keys=("updated",)).startswith("sha256:") def test_owner_schema_update_removes_stale_no_confidence_entry(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("base_confidence: 0.80\n", "")) before = check_trust_ledger( vault, ledger_path, required_trust_keys=("updated",), ) assert before["status"] == "warn" assert before["stale"] == [ { "page": "concepts/alpha.md", "reason": "confidence_not_applicable_but_ledger_entry_exists", } ] ledger = update_trust_ledger( vault, ledger_path, reviewed_at="2026-08-05T13:00:00+09:00", page_paths=["concepts/alpha.md"], required_trust_keys=("updated",), ) assert ledger["pages"] == {} assert ledger["not_applicable"] == ["concepts/alpha.md"] assert ledger["removed_not_applicable"] == ["concepts/alpha.md"] write_trust_ledger(ledger_path, ledger, vault=vault) after = check_trust_ledger( vault, ledger_path, required_trust_keys=("updated",), ) assert after["status"] == "pass" assert after["counts"]["not_applicable"] == 1 assert after["stale"] == [] def test_owner_trust_schema_rejects_unknown_lifecycle_typo(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("lifecycle: reviewed", "lifecycle: activ")) report = check_trust_ledger( vault, ledger_path, allowed_lifecycles={"active", "confirmed", "stub"}, required_trust_keys=("updated",), schema_source="wiki/AGENTS.md", ) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "invalid lifecycle: activ"} ] def test_owner_lifecycle_propagates_through_record_update_and_check(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text(page.read_text().replace("lifecycle: reviewed", "lifecycle: active")) schema = { "allowed_lifecycles": {"active", "confirmed", "stub"}, "required_trust_keys": ("base_confidence", "lifecycle", "updated"), } ledger = build_trust_ledger( vault, reviewed_at="2026-08-05T12:00:00+09:00", **schema, ) ledger_path = vault / "_meta" / "trust-ledger.json" write_trust_ledger(ledger_path, ledger, vault=vault) assert check_trust_ledger(vault, ledger_path, schema_source="wiki/AGENTS.md", **schema)["status"] == "pass" page.write_text(page.read_text().replace("Reviewed material claim.", "Reviewed updated claim.")) updated = update_trust_ledger( vault, ledger_path, reviewed_at="2026-08-05T13:00:00+09:00", page_paths=["concepts/alpha.md"], **schema, ) write_trust_ledger(ledger_path, updated, vault=vault) assert check_trust_ledger(vault, ledger_path, schema_source="wiki/AGENTS.md", **schema)["status"] == "pass" def test_mismatched_scalar_quotes_fail_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("lifecycle: reviewed", 'lifecycle: "reviewed\'')) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": 'invalid lifecycle: "reviewed\''} ] def test_invalid_updated_timestamp_fails_before_fingerprint_match(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) page.write_text( page.read_text().replace( "updated: 2026-07-12T17:38:39+07:00", "updated: definitely-not-a-timestamp", ) ) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "updated is not an ISO-8601 date or timestamp"} ] def test_inline_comments_on_scalar_trust_fields_remain_valid(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text( page.read_text() .replace("base_confidence: 0.80", "base_confidence: 0.80 # reviewed score") .replace("lifecycle: reviewed", "lifecycle: reviewed # human-reviewed") ) ledger_path = _write_ledger(vault) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "pass" assert report["counts"]["reviewed"] == 1 def test_score_only_change_is_a_failing_mismatch_not_stale(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md", confidence=0.53) ledger_path = _write_ledger(vault) page.write_text(page.read_text().replace("base_confidence: 0.53", "base_confidence: 0.88")) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["stale"] == [] assert report["score_mismatches"] == [ {"page": "concepts/alpha.md", "stored": 0.88, "reviewed": 0.53} ] def test_new_page_requires_manual_review_instead_of_formula_guess(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) _page(vault, "concepts/new.md", confidence=0.95) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "warn" assert report["unreviewed"] == [{"page": "concepts/new.md", "reason": "not_in_manual_ledger"}] def test_standalone_trust_check_fails_for_page_missing_trust_fields(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text( "\n".join( line for line in page.read_text().splitlines() if not line.startswith(("base_confidence:", "lifecycle:")) ) + "\n" ) ledger_path = vault / "_meta" / "trust-ledger.json" write_trust_ledger( ledger_path, { "schema_version": 1, "method": "manual-lineage-and-claim-coverage-v1", "reviewed_at": "2026-07-12T17:38:39+07:00", "pages": {}, }, vault=vault, ) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [ {"page": "concepts/alpha.md", "issue": "missing base_confidence"} ] def test_lint_vault_requires_trust_ledger_by_default(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") report = lint_vault(vault) # Staged migration (#28, #146): a missing ledger only warns until the # vault owner opts into strict_trust. assert report["status"] == "warn" assert report["findings"]["confidence_ledger_errors"] == [ {"issue": "ledger_missing", "path": str(vault / "_meta" / "trust-ledger.json")} ] strict_report = lint_vault(vault, strict_trust=True) assert strict_report["status"] == "fail" def test_non_object_ledger_fails_cleanly(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = vault / "_meta" / "trust-ledger.json" ledger_path.parent.mkdir(parents=True) ledger_path.write_text("[]\n") report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [{"issue": "ledger_must_be_an_object"}] def test_duplicate_json_keys_are_rejected(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) text = ledger_path.read_text() ledger_path.write_text(text.replace('"schema_version": 1', '"schema_version": 1, "schema_version": 1')) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"][0]["issue"] == "ledger_unreadable" assert "duplicate JSON key" in report["errors"][0]["detail"] def test_boolean_schema_version_fails_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["schema_version"] = True ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"][0]["issue"] == "unsupported_schema_version" def test_invalid_utf8_ledger_returns_structured_failure(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = vault / "_meta" / "trust-ledger.json" ledger_path.parent.mkdir(parents=True) ledger_path.write_bytes(b"\xff\xfe") report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"][0]["issue"] == "ledger_unreadable" def test_invalid_ledger_review_timestamp_fails_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["reviewed_at"] = "yesterday" ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [{"issue": "invalid_reviewed_at"}] def test_trust_writer_rejects_symlinked_meta_directory(tmp_path: Path) -> None: vault = tmp_path / "vault" outside = tmp_path / "outside" vault.mkdir() outside.mkdir() (vault / "_meta").symlink_to(outside, target_is_directory=True) ledger = {"schema_version": 1, "method": "manual-lineage-and-claim-coverage-v1", "pages": {}} try: write_trust_ledger(vault / "_meta" / "trust-ledger.json", ledger, vault=vault) except RuntimeError as exc: assert "outside the resolved vault" in str(exc) or "symlink" in str(exc) else: raise AssertionError("symlinked _meta directory was accepted") assert not (outside / "trust-ledger.json").exists() def test_trust_writer_never_follows_predictable_temp_symlink(tmp_path: Path) -> None: vault = tmp_path / "vault" meta = vault / "_meta" meta.mkdir(parents=True) outside = tmp_path / "outside.txt" outside.write_text("sentinel\n") (meta / "trust-ledger.json.tmp").symlink_to(outside) ledger = {"schema_version": 1, "method": "manual-lineage-and-claim-coverage-v1", "pages": {}} write_trust_ledger(meta / "trust-ledger.json", ledger, vault=vault) assert outside.read_text() == "sentinel\n" assert json.loads((meta / "trust-ledger.json").read_text())["schema_version"] == 1 def test_trust_writer_rejects_symlinked_ledger_destination(tmp_path: Path) -> None: vault = tmp_path / "vault" meta = vault / "_meta" meta.mkdir(parents=True) outside = tmp_path / "outside.txt" outside.write_text("sentinel\n") destination = meta / "trust-ledger.json" destination.symlink_to(outside) ledger = {"schema_version": 1, "method": "manual-lineage-and-claim-coverage-v1", "pages": {}} try: write_trust_ledger(destination, ledger, vault=vault) except RuntimeError as exc: assert "outside the resolved vault" in str(exc) or "symlink" in str(exc) else: raise AssertionError("symlinked ledger destination was accepted") assert outside.read_text() == "sentinel\n" def test_out_of_range_reviewed_confidence_is_invalid_ledger_data(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["pages"]["concepts/alpha.md"]["reviewed_confidence"] = 2.0 ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [{"page": "concepts/alpha.md", "issue": "invalid_ledger_entry"}] def test_boolean_reviewed_confidence_is_invalid_ledger_data(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["pages"]["concepts/alpha.md"]["reviewed_confidence"] = True ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [{"page": "concepts/alpha.md", "issue": "invalid_ledger_entry"}] def test_non_standard_json_number_is_unreadable_not_approved(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["pages"]["concepts/alpha.md"]["reviewed_confidence"] = float("nan") ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"][0]["issue"] == "ledger_unreadable" def test_malformed_fingerprint_and_entry_timestamp_fail_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) entry = ledger["pages"]["concepts/alpha.md"] entry["material_fingerprint"] = "garbage" entry["reviewed_at"] = "yesterday" ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["errors"] == [{"page": "concepts/alpha.md", "issue": "invalid_ledger_entry"}] def test_non_object_page_entry_is_invalid_not_unreviewed(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") ledger_path = _write_ledger(vault) ledger = json.loads(ledger_path.read_text()) ledger["pages"]["concepts/alpha.md"] = [] ledger_path.write_text(json.dumps(ledger)) report = check_trust_ledger(vault, ledger_path) assert report["status"] == "fail" assert report["unreviewed"] == [] assert report["errors"] == [{"page": "concepts/alpha.md", "issue": "invalid_ledger_entry"}] def test_lint_vault_surfaces_manual_trust_status_without_formula_drift(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md", confidence=0.53, relationships=[("related_to", "skills/beta")]) _page(vault, "skills/beta.md", confidence=0.80, relationships=[("related_to", "concepts/alpha")]) _write_ledger(vault) report = lint_vault(vault) assert report["findings"]["confidence_review_stale"] == [] assert report["findings"]["confidence_unreviewed"] == [] assert report["findings"]["confidence_mismatches"] == [] assert report["findings"]["typed_relationship_issues"] == [] assert report["stats"]["trust"]["reviewed"] == 2 def test_typed_relationship_resolver_uses_exact_vault_paths(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "projects/demo/alpha.md", relationships=[("implements", "projects/demo/beta")]) _page(vault, "projects/demo/beta.md", relationships=[("related_to", "projects/demo/alpha")]) _page(vault, "concepts/broken.md", relationships=[("related_to", "skills/missing")]) report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/broken.md", "index": 0, "issue": "missing_target", "target": "skills/missing", } ] def test_target_first_relationship_order_is_parsed_and_validated(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") _page(vault, "skills/beta.md") page.write_text( page.read_text().replace( "lifecycle: reviewed", 'lifecycle: reviewed\nrelationships:\n - target: "[[skills/missing]]"\n type: related_to', ) ) report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/alpha.md", "index": 0, "issue": "missing_target", "target": "skills/missing", } ] def test_dash_only_relationship_item_is_parsed_and_validated(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text( page.read_text().replace( "lifecycle: reviewed", 'lifecycle: reviewed\nrelationships:\n -\n target: "[[skills/missing]]"\n type: related_to', ) ) report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/alpha.md", "index": 0, "issue": "missing_target", "target": "skills/missing", } ] def test_unknown_relationship_field_fails_closed(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") _page(vault, "skills/beta.md") page.write_text( page.read_text().replace( "lifecycle: reviewed", 'lifecycle: reviewed\nrelationships:\n - target: "[[skills/beta]]"\n type: related_to\n weight: 5', ) ) report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/alpha.md", "index": 0, "issue": "malformed_relationship_entry", } ] def test_basename_only_relationship_is_rejected_when_ambiguous(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md", relationships=[("related_to", "beta")]) _page(vault, "skills/beta.md") _page(vault, "projects/demo/beta.md") report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/alpha.md", "index": 0, "issue": "ambiguous_target", "target": "beta", } ] def test_qualified_relationship_is_ambiguous_when_normalized_paths_collide(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md", relationships=[("related_to", "skills/foo-bar")]) _page(vault, "skills/Foo Bar.md") _page(vault, "skills/foo-bar.md") report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [ { "page": "concepts/alpha.md", "index": 0, "issue": "ambiguous_target", "target": "skills/foo-bar", } ] def test_empty_inline_relationship_list_is_valid(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text(page.read_text().replace("lifecycle: reviewed", "lifecycle: reviewed\nrelationships: []")) report = lint_vault(vault, require_trust_ledger=False) assert report["findings"]["typed_relationship_issues"] == [] def test_lint_excludes_bootstrap_scaffolding_from_page_health(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md", relationships=[("related_to", "concepts/beta")]) _page(vault, "concepts/beta.md", relationships=[("related_to", "concepts/alpha")]) bootstrap = vault / "_bootstrap" / "README.md" bootstrap.parent.mkdir(parents=True) bootstrap.write_text("# Setup instructions\n") report = lint_vault(vault) serialized = json.dumps(report) assert "_bootstrap/README.md" not in serialized def test_iter_trust_pages_skips_tool_owned_directories(tmp_path: Path) -> None: """A `.venv` inside a project-vault must not enter the ledger's page set — the ledger would otherwise record reviews for dependency READMEs, and each dependency upgrade would churn its entries.""" vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") junk = ( ".venv/lib/python3.12/site-packages/somepkg/README.md", ".trash/draft.md", "node_modules/pkg/README.md", "venv/readme.md", "__pycache__/x.md", ) for relative in junk: path = vault / relative path.parent.mkdir(parents=True, exist_ok=True) path.write_text("# dependency readme\nno frontmatter\n", encoding="utf-8") assert [page.relative_to(vault).as_posix() for page in iter_trust_pages(vault)] == [ "concepts/alpha.md" ] def test_iter_trust_pages_honors_okignore(tmp_path: Path) -> None: """A vault-configured quarantine dir (`_excluded/`) stays out of the ledger.""" vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") (vault / ".okignore").write_text("_excluded/\n", encoding="utf-8") excluded = vault / "_excluded" / "pii.md" excluded.parent.mkdir(parents=True, exist_ok=True) excluded.write_text("plain text, no frontmatter\n", encoding="utf-8") assert [page.relative_to(vault).as_posix() for page in iter_trust_pages(vault)] == [ "concepts/alpha.md" ] def test_lint_flags_content_page_missing_trust_schema(tmp_path: Path) -> None: vault = tmp_path / "vault" page = _page(vault, "concepts/alpha.md") page.write_text( "\n".join( line for line in page.read_text().splitlines() if not line.startswith(("base_confidence:", "lifecycle:")) ) + "\n" ) report = lint_vault(vault) # Staged migration (#28, #146): legacy pages predating the trust schema # warn instead of failing lint until the owner opts into strict_trust. assert report["status"] == "warn" assert report["findings"]["missing_frontmatter"] == [] assert report["findings"]["confidence_missing_fields"] == [ {"page": "concepts/alpha.md", "missing": ["base_confidence", "lifecycle"]} ] strict_report = lint_vault(vault, strict_trust=True) assert strict_report["status"] == "fail" def test_required_trust_ledger_fails_closed_when_missing(tmp_path: Path) -> None: vault = tmp_path / "vault" _page(vault, "concepts/alpha.md") report = lint_vault(vault, require_trust_ledger=True) # require_trust_ledger controls whether the ledger check runs at all (and # is thus reported); strict_trust controls whether its findings fail lint. assert report["status"] == "warn" assert report["findings"]["confidence_ledger_errors"] == [ {"issue": "ledger_missing", "path": str(vault / "_meta" / "trust-ledger.json")} ] strict_report = lint_vault(vault, require_trust_ledger=True, strict_trust=True) assert strict_report["status"] == "fail"