11import importlib .util
2+ import io
23import re
4+ import tempfile
35import unittest
46from pathlib import Path
7+ from unittest import mock
58
69
710ROOT = Path (__file__ ).resolve ().parents [1 ]
@@ -18,8 +21,8 @@ class DocsContractTests(unittest.TestCase):
1821 def setUpClass (cls ):
1922 cls .english_path = ROOT / "AGENTS.md"
2023 cls .chinese_path = ROOT / "AGENTS.zh-CN.md"
21- cls .english = cls .english_path .read_text ( encoding = "utf-8" )
22- cls .chinese = cls .chinese_path .read_text ( encoding = "utf-8" )
24+ cls .english = cls .english_path .read_bytes (). decode ( "utf-8" )
25+ cls .chinese = cls .chinese_path .read_bytes (). decode ( "utf-8" )
2326
2427 def assert_single_mutation_rejected (
2528 self ,
@@ -59,6 +62,102 @@ def test_documents_fit_context_budget(self):
5962 f"{ path .name } exceeds { validate_docs .MAX_BYTES } bytes" ,
6063 )
6164
65+ def test_protocol_content_digests_match_release_lock (self ):
66+ documents = {
67+ "AGENTS.md" : self .english ,
68+ "AGENTS.zh-CN.md" : self .chinese ,
69+ }
70+ self .assertEqual (
71+ {
72+ name : validate_docs .content_digest (text )
73+ for name , text in documents .items ()
74+ },
75+ validate_docs .PROTOCOL_CONTENT_DIGESTS ,
76+ )
77+ self .assertEqual (
78+ {validate_docs .extract_version (text ) for text in documents .values ()},
79+ {validate_docs .RELEASE_LOCK_VERSION },
80+ )
81+
82+ def test_content_lock_rejects_known_markdown_context_bypasses (self ):
83+ authority = next (
84+ line
85+ for line in self .english .splitlines ()
86+ if line .startswith ("Only project-tree `AGENTS.md`" )
87+ )
88+ directory = validate_docs .extract_heading_section (
89+ self .english ,
90+ 3 ,
91+ "Directory Layout" ,
92+ )
93+ tree_match = re .search (
94+ r"^```[ \t]*\n.*?^```[ \t]*$" ,
95+ directory ,
96+ flags = re .MULTILINE | re .DOTALL ,
97+ )
98+ self .assertIsNotNone (tree_match )
99+ tree = tree_match .group (0 )
100+ mutations = (
101+ (
102+ authority ,
103+ f'[visible](<foo)bar> "\n { authority } \n ")' ,
104+ ),
105+ (
106+ authority ,
107+ f"> <div>\n > raw\n <x-agentgo>\n { authority } \n </x-agentgo>" ,
108+ ),
109+ (
110+ tree ,
111+ f"<x-agentgo>\n { tree } \n </x-agentgo>" ,
112+ ),
113+ )
114+ for old , new in mutations :
115+ with self .subTest (replacement = new .splitlines ()[0 ]):
116+ mutated = self .english .replace (old , new , 1 )
117+ errors = validate_docs .validate_texts (
118+ mutated ,
119+ self .chinese ,
120+ max_bytes = 100_000 ,
121+ )
122+ self .assertIn (
123+ "AGENTS.md content differs from the v1.12.1 release lock" ,
124+ errors ,
125+ )
126+
127+ def test_oversized_documents_skip_deep_parsing (self ):
128+ with mock .patch .object (
129+ validate_docs ,
130+ "mask_fenced_code_blocks" ,
131+ side_effect = AssertionError ("deep parser must not run" ),
132+ ):
133+ errors = validate_docs .validate_texts (
134+ self .english + "<" * 200_000 ,
135+ self .chinese + "<" * 200_000 ,
136+ )
137+ self .assertEqual (len (errors ), 2 )
138+ self .assertTrue (all ("limit is" in error for error in errors ))
139+
140+ def test_main_rejects_symlinked_protocol_artifacts (self ):
141+ with tempfile .TemporaryDirectory () as temporary_directory :
142+ root = Path (temporary_directory )
143+ target = root / "canonical-english.md"
144+ target .write_bytes (self .english .encode ("utf-8" ))
145+ (root / "AGENTS.md" ).symlink_to (target .name )
146+ (root / "AGENTS.zh-CN.md" ).write_bytes (
147+ self .chinese .encode ("utf-8" )
148+ )
149+ stderr = io .StringIO ()
150+ stdout = io .StringIO ()
151+ with (
152+ mock .patch .object (validate_docs , "ROOT" , root ),
153+ mock .patch ("sys.stderr" , stderr ),
154+ mock .patch ("sys.stdout" , stdout ),
155+ ):
156+ result = validate_docs .main ()
157+ self .assertEqual (result , 1 )
158+ self .assertIn ("AGENTS.md must be a regular file" , stderr .getvalue ())
159+ self .assertEqual (stdout .getvalue (), "" )
160+
62161 def test_first_line_versions_match_semver (self ):
63162 english_version = validate_docs .extract_version (self .english )
64163 chinese_version = validate_docs .extract_version (self .chinese )
@@ -165,7 +264,7 @@ def test_validator_rejects_extra_h3_outside_h2_sections(self):
165264 ),
166265 )
167266
168- def test_validator_ignores_h3_examples_inside_fenced_code (self ):
267+ def test_content_lock_rejects_fenced_h3_example_drift (self ):
169268 old = "## Startup Instructions\n "
170269 self .assertEqual (self .english .count (old ), 1 )
171270 english = self .english .replace (
@@ -179,7 +278,10 @@ def test_validator_ignores_h3_examples_inside_fenced_code(self):
179278 self .chinese ,
180279 max_bytes = 100_000 ,
181280 )
182- self .assertEqual (errors , [])
281+ self .assertEqual (
282+ errors ,
283+ ["AGENTS.md content differs from the v1.12.1 release lock" ],
284+ )
183285
184286 def test_validator_rejects_marker_relocated_into_fenced_code (self ):
185287 marker = (
@@ -801,7 +903,7 @@ def test_validator_rejects_changed_extended_changelog_format(self):
801903 ),
802904 )
803905
804- def test_validator_does_not_require_equal_physical_line_counts (self ):
906+ def test_content_lock_rejects_physical_line_drift (self ):
805907 chinese_with_extra_blank_line = self .chinese + "\n "
806908 self .assertNotEqual (
807909 len (self .english .splitlines ()),
@@ -813,7 +915,10 @@ def test_validator_does_not_require_equal_physical_line_counts(self):
813915 chinese_with_extra_blank_line ,
814916 max_bytes = 100_000 ,
815917 )
816- self .assertEqual (errors , [])
918+ self .assertEqual (
919+ errors ,
920+ ["AGENTS.zh-CN.md content differs from the v1.12.1 release lock" ],
921+ )
817922
818923 def test_readme_stable_examples_match_protocol_version (self ):
819924 version = validate_docs .extract_version (self .english )
0 commit comments