@@ -136,6 +136,70 @@ def _machine_fence(kind: str, text: str, where: str) -> None:
136136 )
137137
138138
139+ _POSTURE_KEYS = {"ai_bots" , "healthz" , "runtime" }
140+ _POSTURE_ENUMS = {"healthz" : {"minimal" , "full" }, "runtime" : {"docker" , "python" }}
141+
142+
143+ def _posture_fence (text : str , where : str ) -> dict :
144+ """The ```yaml posture block in DIVERGENCES.md (1.6.30, F4).
145+
146+ Declared postures used to live in the hub's own table — a copy of a
147+ measurement somebody took once, aging in a repo that cannot see the
148+ host. The fence homes each posture in the repo that serves it. SHAPE
149+ is all this validates: no test can tell a stale 200 from a fresh one,
150+ so the grammar is kept narrow enough that a wrong value is visibly
151+ wrong. Empty is valid and means "the template defaults".
152+ """
153+ fences = re .findall (
154+ r"^```yaml posture[ \t]*\n(.*?)^```[ \t]*$" , text , re .M | re .S
155+ )
156+ assert len (fences ) == 1 , (
157+ f"{ where } : expected exactly one ```yaml posture fence, "
158+ f"found { len (fences )} "
159+ )
160+ declared : dict = {}
161+ for raw in fences [0 ].splitlines ():
162+ stripped = raw .strip ()
163+ if not stripped or stripped .startswith ("#" ):
164+ continue
165+ key , sep , value = stripped .partition (":" )
166+ key , value = key .strip (), value .strip ()
167+ assert sep , f"{ where } posture: { raw !r} is not a `key: value` line"
168+ assert key in _POSTURE_KEYS , (
169+ f"{ where } posture: unknown key { key !r} — the hub reads "
170+ f"{ sorted (_POSTURE_KEYS )} and would ignore this one silently"
171+ )
172+ assert key not in declared , f"{ where } posture: { key !r} declared twice"
173+ if key in _POSTURE_ENUMS :
174+ assert value in _POSTURE_ENUMS [key ], (
175+ f"{ where } posture: { key } : { value !r} — expected one of "
176+ f"{ sorted (_POSTURE_ENUMS [key ])} "
177+ )
178+ declared [key ] = value
179+ continue
180+ try :
181+ statuses = json .loads (value )
182+ except ValueError as exc :
183+ raise AssertionError (
184+ f"{ where } posture: ai_bots must be a JSON object like "
185+ f'{{"/": 403, "/llms.txt": 200}} — { exc } '
186+ ) from None
187+ assert isinstance (statuses , dict ) and statuses , (
188+ f"{ where } posture: ai_bots is { statuses !r} — a non-empty JSON "
189+ "object of path -> status, or omit the key entirely"
190+ )
191+ for path , status in statuses .items ():
192+ assert path .startswith ("/" ), (
193+ f"{ where } posture: ai_bots key { path !r} is not a path"
194+ )
195+ assert isinstance (status , int ) and 100 <= status <= 599 , (
196+ f"{ where } posture: ai_bots[{ path !r} ] is { status !r} — an "
197+ "HTTP status, measured with a real vendor UA"
198+ )
199+ declared [key ] = statuses
200+ return declared
201+
202+
139203def test_kit_files_exist_and_are_not_ignored ():
140204 """The blanket `.claude/` ignore kept the contract local-only for the
141205 template's whole life — every fork inherited nothing. The allow-list
@@ -278,3 +342,39 @@ def test_divergences_carry_the_byte_owned_block():
278342 "mention heuristic"
279343 )
280344 _machine_fence ("byte-owned" , text , "DIVERGENCES.md" )
345+
346+
347+ def test_divergences_posture_fence_is_wellformed ():
348+ """The declared posture (1.6.30, F4): shape only, plus the one value
349+ the repo can contradict by itself.
350+
351+ ABSENCE SKIPS, like the byte-owned fence and for the same reason — a
352+ fork that has not ported the item yet keeps its CI green and gets the
353+ contract item, not a red on arrival. What is declared is held: an
354+ unknown key would be read by nobody, and a `runtime:` disagreeing with
355+ render.yaml is the posture lying about something in its own tree.
356+ """
357+ import pytest
358+
359+ div = REPO / "DIVERGENCES.md"
360+ if not div .is_file ():
361+ pytest .skip ("no DIVERGENCES.md — nothing to declare a posture in" )
362+ text = div .read_text ()
363+ if not re .search (r"^```yaml posture[ \t]*$" , text , re .M ):
364+ pytest .skip (
365+ "DIVERGENCES.md has no posture fence — port the 1.6.30 item; "
366+ "until then the hub reads its own seeded table"
367+ )
368+ declared = _posture_fence (text , "DIVERGENCES.md" )
369+
370+ render = REPO / "render.yaml"
371+ if "runtime" in declared and render .is_file ():
372+ for line in render .read_text ().splitlines ():
373+ m = re .match (r"\s*runtime:\s*(\S+)" , line )
374+ if m :
375+ assert declared ["runtime" ] == m .group (1 ), (
376+ f"posture declares runtime { declared ['runtime' ]!r} , "
377+ f"render.yaml says { m .group (1 )!r} — the posture is "
378+ "wrong about this repo's own tree"
379+ )
380+ break
0 commit comments