From 53f3ace4c47e4d42c3426aaaf585272c4ecdb3d0 Mon Sep 17 00:00:00 2001 From: stacknil Date: Tue, 14 Jul 2026 14:26:26 +0800 Subject: [PATCH 1/3] fix(patterns): reject future review dates --- scripts/check_pattern_library.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/check_pattern_library.py b/scripts/check_pattern_library.py index e16d172..ed721de 100644 --- a/scripts/check_pattern_library.py +++ b/scripts/check_pattern_library.py @@ -62,6 +62,9 @@ def parse_card( except (TypeError, ValueError): errors.append(f"{relative(path)}: last_reviewed must use YYYY-MM-DD") return None + if reviewed > date.today(): + errors.append(f"{relative(path)}: last_reviewed must not be in the future") + return None body = text[match.end() :] headings = list(HEADING_RE.finditer(body)) From 2adc5780b88a1efd2b68cc484fd60788b8d0c7be Mon Sep 17 00:00:00 2001 From: stacknil Date: Tue, 14 Jul 2026 14:27:28 +0800 Subject: [PATCH 2/3] test(validators): pin decision-bearing failures --- .github/workflows/markdown-lint.yml | 5 + .pre-commit-config.yaml | 9 + tests/test_validation_contracts.py | 249 ++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 tests/test_validation_contracts.py diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index abf9afa..bb73d23 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -18,6 +18,7 @@ name: Markdown Validation - "scripts/render_tags_doc.py" - "scripts/generate_markdownlint_debt.py" - "scripts/run_markdownlint.py" + - "tests/**/*.py" - "requirements-lint.txt" - ".markdownlint-cli2.jsonc" - ".markdownlint-cli2-debt.jsonc" @@ -40,6 +41,7 @@ name: Markdown Validation - "scripts/render_tags_doc.py" - "scripts/generate_markdownlint_debt.py" - "scripts/run_markdownlint.py" + - "tests/**/*.py" - "requirements-lint.txt" - ".markdownlint-cli2.jsonc" - ".markdownlint-cli2-debt.jsonc" @@ -79,6 +81,9 @@ jobs: - name: Validate canonical public placeholders run: python scripts/check_placeholders.py + - name: Run validator contract tests + run: python -m unittest discover -s tests -p 'test_*.py' + - name: Run Markdown checks run: python scripts/check_markdown.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0ecbbc..3f25f4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,15 @@ repos: files: >- ^(README\.md|docs/.+\.md|patterns/.+\.md|schemas/pattern-library\.json| scripts/check_pattern_library\.py|notes/.+\.md)$ + - id: test-validation-contracts + name: test-validation-contracts + entry: python -m unittest discover -s tests -p test_*.py + language: python + additional_dependencies: + - PyYAML>=6.0,<7.0 + - jsonschema>=4.23,<5.0 + pass_filenames: false + always_run: true - id: validate-frontmatter name: validate-frontmatter entry: python scripts/check_markdown.py diff --git a/tests/test_validation_contracts.py b/tests/test_validation_contracts.py new file mode 100644 index 0000000..e56bd90 --- /dev/null +++ b/tests/test_validation_contracts.py @@ -0,0 +1,249 @@ +from __future__ import annotations + +import json +import sys +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +import check_markdown # noqa: E402 +import check_pattern_library # noqa: E402 +import check_placeholders # noqa: E402 + + +REQUIRED_PATTERN_SECTIONS = [ + "Signal", + "Why it matters", + "False-positive contexts", + "Evidence limits", + "Defensive next step", + "Related implementation", + "Supporting notes", +] + + +def pattern_card_text( + *, + title: str, + maturity: str = "stable", + last_reviewed: str = "2000-01-01", + implementation_url: str = "https://github.com/stacknil/core-project", +) -> str: + sections: list[str] = [] + for section in REQUIRED_PATTERN_SECTIONS: + if section == "Related implementation": + content = f"[Core project]({implementation_url})" + elif section == "Supporting notes": + content = "[Source note](../notes/source.md)" + else: + content = "Decision-bearing evidence." + sections.append(f"## {section}\n\n{content}") + + return ( + "---\n" + f"maturity: {maturity}\n" + f"last_reviewed: {last_reviewed}\n" + "---\n" + f"# {title}\n\n" + + "\n\n".join(sections) + + "\n" + ) + + +class PatternLibraryContractTests(unittest.TestCase): + def test_parse_card_future_review_date_is_rejected(self) -> None: + with TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + card_path = temp_root / "patterns" / "future-review.md" + card_path.parent.mkdir(parents=True) + card_path.write_text( + pattern_card_text( + title="Future review", + last_reviewed="2999-01-01", + ), + encoding="utf-8", + ) + errors: list[str] = [] + + with patch.object(check_pattern_library, "ROOT", temp_root): + card = check_pattern_library.parse_card( + card_path, + REQUIRED_PATTERN_SECTIONS, + {"draft", "reviewed", "stable"}, + errors, + ) + + self.assertIsNone(card) + self.assertEqual( + errors, + [ + "patterns/future-review.md: " + "last_reviewed must not be in the future" + ], + ) + + def test_validate_stable_card_without_core_project_is_rejected(self) -> None: + with TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + self._write_pattern_library( + temp_root, + implementation_overrides={0: "https://example.com/other-project"}, + ) + + errors, card_count, stable_count, supporting_count = self._validate( + temp_root + ) + + self.assertEqual((card_count, stable_count, supporting_count), (6, 6, 1)) + self.assertEqual( + errors, + [ + "patterns/card-0.md: " + "stable card must link to a core project" + ], + ) + + def test_validate_featured_reviewed_card_is_rejected(self) -> None: + with TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + self._write_pattern_library( + temp_root, + maturity_overrides={0: "reviewed"}, + ) + + errors, card_count, stable_count, supporting_count = self._validate( + temp_root + ) + + self.assertEqual((card_count, stable_count, supporting_count), (6, 5, 1)) + self.assertEqual( + errors, + ["featured pattern is not stable: patterns/card-0.md"], + ) + + def _write_pattern_library( + self, + root: Path, + *, + implementation_overrides: dict[int, str] | None = None, + maturity_overrides: dict[int, str] | None = None, + ) -> None: + implementation_overrides = implementation_overrides or {} + maturity_overrides = maturity_overrides or {} + patterns = root / "patterns" + schemas = root / "schemas" + notes = root / "notes" + patterns.mkdir(parents=True) + schemas.mkdir(parents=True) + notes.mkdir(parents=True) + (notes / "source.md").write_text("# Source\n", encoding="utf-8") + + featured: list[str] = [] + for index in range(6): + relative_path = f"patterns/card-{index}.md" + featured.append(relative_path) + (root / relative_path).write_text( + pattern_card_text( + title=f"Card {index}", + maturity=maturity_overrides.get(index, "stable"), + implementation_url=implementation_overrides.get( + index, + "https://github.com/stacknil/core-project", + ), + ), + encoding="utf-8", + ) + + config = { + "version": 1, + "maturity_values": ["draft", "reviewed", "stable"], + "required_sections": REQUIRED_PATTERN_SECTIONS, + "core_projects": [ + { + "name": "Core project", + "url_prefix": "https://github.com/stacknil/core-project", + } + ], + "featured_patterns": featured, + "flagship_case_studies": [], + "metric_surfaces": [], + } + (schemas / "pattern-library.json").write_text( + json.dumps(config), + encoding="utf-8", + ) + + def _validate( + self, + root: Path, + ) -> tuple[list[str], int, int, int]: + with ( + patch.object(check_pattern_library, "ROOT", root), + patch.object( + check_pattern_library, + "CONFIG_PATH", + root / "schemas" / "pattern-library.json", + ), + ): + return check_pattern_library.validate() + + +class FrontmatterContractTests(unittest.TestCase): + def test_validate_structure_path_mismatch_is_reported(self) -> None: + issues = check_markdown.validate_structure( + "notes/10-web/example.md", + "# Example\n\n## Summary\n\nSafe summary.\n", + { + "path": "notes/10-web/wrong.md", + "topic": "10-web", + }, + ) + + self.assertEqual( + [(issue.path, issue.message) for issue in issues], + [ + ( + "notes/10-web/example.md", + "path field mismatch: expected 'notes/10-web/example.md', " + "found 'notes/10-web/wrong.md'", + ) + ], + ) + + +class PlaceholderContractTests(unittest.TestCase): + def test_noncanonical_placeholder_does_not_flag_literal_identifier(self) -> None: + with TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + note = temp_root / "notes" / "10-web" / "example.md" + note.parent.mkdir(parents=True) + note.write_text( + "Use NONCANONICAL_TARGET, but preserve LD_PRELOAD.\n", + encoding="utf-8", + ) + literal_policy = check_placeholders.LiteralPolicy( + exact_tokens=frozenset(), + angle_tokens=frozenset(), + ) + + with patch.object(check_placeholders, "ROOT", temp_root): + issues = check_placeholders.collect_issues( + note, + canonical={"TARGET_IP"}, + replacements={}, + literal_policy=literal_policy, + ) + + self.assertEqual( + [(issue.token, issue.category) for issue in issues], + [("NONCANONICAL_TARGET", "noncanonical-placeholder")], + ) + + +if __name__ == "__main__": + unittest.main() From 357e64a5745bfaef64ba2271081a4a6fefa7f50e Mon Sep 17 00:00:00 2001 From: stacknil Date: Tue, 14 Jul 2026 14:28:41 +0800 Subject: [PATCH 3/3] docs(validation): document contract test gate --- AGENTS.md | 1 + README.md | 1 + patterns/README.md | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 28c208a..b01e8c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,7 @@ Normalize and maintain this repository as a local-first canonical knowledge base Run: - python scripts/render_tags_doc.py --check +- python -m unittest discover -s tests -p 'test_*.py' - python scripts/check_placeholders.py `` - python scripts/check_markdown.py - python -m pre_commit run --files `` diff --git a/README.md b/README.md index b7bcce7..fb09d7c 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ Run these before publishing or merging materially edited public notes: ```text python scripts/render_tags_doc.py --check python scripts/render_readme_snapshot.py --check +python -m unittest discover -s tests -p 'test_*.py' python scripts/check_placeholders.py python scripts/check_markdown.py python -m pre_commit run --files diff --git a/patterns/README.md b/patterns/README.md index f559c74..2c00155 100644 --- a/patterns/README.md +++ b/patterns/README.md @@ -38,7 +38,9 @@ These cards remain useful but are not part of the featured stable set: | `reviewed` | Structurally reviewed and supported, but not yet promoted as a stable library entry. | | `stable` | Evidence-bounded, linked to a core implementation, and supported by at least one source note. | -Every card records `maturity` and `last_reviewed` in front matter. +Every card records `maturity` and `last_reviewed` in front matter. The review +date must be a valid current or historical date; future provenance claims fail +validation. ## Card Contract