@@ -185,12 +185,65 @@ def test_registry_cannot_add_unanchored_output_selectors(name, metadata, selecti
185185 smoke ['check_coverage_floor' ](registry )
186186
187187
188+ SYNTHETIC_FORK_NAME = "SYNTHETIC_RENAME_SAMPLE_MARKERS"
189+ SYNTHETIC_FORK_MODULES = (
190+ "loopx/state_projection.py" ,
191+ "loopx/control_plane/goals/active_state_metadata.py" ,
192+ )
193+
194+
195+ def _with_synthetic_fork (smoke , sources ):
196+ """Inject a synthetic multi-value fork into two in-memory modules.
197+
198+ The rename-laundering limit is a property of the inventory machinery, not
199+ of whichever real fork happens to be undeclared today. Track A retires real
200+ forks one by one (PR #4643 retired two of the three this file used to rent),
201+ so these pins carry their own sample instead: two modules, one name, two
202+ disagreeing value sets -- exactly what makes a multi-value fork.
203+ """
204+ texts = {
205+ SYNTHETIC_FORK_MODULES [0 ]: f'\n { SYNTHETIC_FORK_NAME } = ("synthetic_left", "left_two")\n ' ,
206+ SYNTHETIC_FORK_MODULES [1 ]: f'\n { SYNTHETIC_FORK_NAME } = ("synthetic_right", "right_two")\n ' ,
207+ }
208+ return [
209+ smoke ["SourceFile" ](source .path , source .suffix , source .text + texts [source .path ])
210+ if source .path in texts
211+ else source
212+ for source in sources
213+ ]
214+
215+
216+ def _rename_synthetic_side (smoke , sources , path ):
217+ return [
218+ smoke ["SourceFile" ](
219+ source .path ,
220+ source .suffix ,
221+ source .text .replace (SYNTHETIC_FORK_NAME , SYNTHETIC_FORK_NAME + "_RENAMED" , 1 ),
222+ )
223+ if source .path == path
224+ else source
225+ for source in sources
226+ ]
227+
228+
188229def test_bounded_context_scope_excludes_only_declared_multi_value_fork () -> None :
230+ """A declaration is what removes a fork from the budget; prove it by removal.
231+
232+ The undeclared count itself is debt population, not a pin -- Track A lowers
233+ it PR by PR. What must hold is the exclusion: dropping a declaration puts
234+ its fork back into the semantic budget, exactly one.
235+ """
189236 smoke = runpy .run_path (str (SMOKE ))
190237 registry = smoke ["load_registry" ]()
191238 sources = smoke ["load_sources" ](REPO_ROOT )
192239 inventory = smoke ["build_inventory" ](REPO_ROOT , sources = sources )
193- assert smoke ["check_scope_declarations" ](registry , inventory ) == 3
240+ with_declaration = smoke ["check_scope_declarations" ](registry , inventory )
241+
242+ without = copy .deepcopy (registry )
243+ del without ["scope_declarations" ]["SOURCE_SURFACES" ]
244+ assert (
245+ smoke ["check_scope_declarations" ](without , inventory ) == with_declaration + 1
246+ ), "a declared fork is excluded from the semantic budget only while declared"
194247
195248
196249def test_renaming_one_side_of_a_fork_launders_the_semantic_budget () -> None :
@@ -204,20 +257,16 @@ def test_renaming_one_side_of_a_fork_launders_the_semantic_budget() -> None:
204257 """
205258 smoke = runpy .run_path (str (SMOKE ))
206259 registry = smoke ["load_registry" ]()
207- sources = smoke ["load_sources" ](REPO_ROOT )
208- before = smoke ["build_inventory" ](REPO_ROOT , sources = sources )
209- assert smoke ["check_scope_declarations" ](registry , before ) == 3
260+ plain = smoke ["load_sources" ](REPO_ROOT )
261+ base = smoke ["check_scope_declarations" ](registry , smoke ["build_inventory" ](REPO_ROOT , sources = plain ))
210262
211- path = "loopx/state_projection.py"
212- name = "AGENT_TODO_HEADER_MARKERS"
213- renamed = [
214- smoke ["SourceFile" ](source .path , source .suffix , source .text .replace (name , name + "_RENAMED" , 1 ))
215- if source .path == path
216- else source
217- for source in sources
218- ]
263+ forked = _with_synthetic_fork (smoke , plain )
264+ before = smoke ["build_inventory" ](REPO_ROOT , sources = forked )
265+ assert smoke ["check_scope_declarations" ](registry , before ) == base + 1
266+
267+ renamed = _rename_synthetic_side (smoke , forked , SYNTHETIC_FORK_MODULES [0 ])
219268 after = smoke ["build_inventory" ](REPO_ROOT , sources = renamed )
220- assert smoke ["check_scope_declarations" ](registry , after ) == 2 , (
269+ assert smoke ["check_scope_declarations" ](registry , after ) == base , (
221270 "a rename no longer lowers the semantic budget; the RFC known-limits entry "
222271 "('Renames launder a collision') is now stale and must be revised"
223272 )
@@ -237,9 +286,14 @@ def test_divergent_value_sets_lists_the_names_a_rename_would_hide() -> None:
237286
238287 smoke = runpy .run_path (str (SMOKE ))
239288 sources = smoke ["load_sources" ](REPO_ROOT )
240- inventory = smoke ["build_inventory" ](REPO_ROOT , sources = sources )
241- listed = {row ["name" ] for row in divergent_value_sets (inventory )}
242- assert {"AGENT_TODO_HEADER_MARKERS" , "USER_TODO_HEADER_MARKERS" , "RAW_MATERIAL_KEY_HINTS" } <= listed
289+ inventory = smoke ["build_inventory" ](REPO_ROOT , sources = _with_synthetic_fork (smoke , sources ))
290+ rows = divergent_value_sets (inventory )
291+ listed = {row ["name" ] for row in rows }
292+ assert SYNTHETIC_FORK_NAME in listed
293+ assert {row ["value_sets" ] for row in rows if row ["name" ] == SYNTHETIC_FORK_NAME } == {2 }
294+ # The one real undeclared fork that survives PR #4643; when Track A retires
295+ # it, this assertion retires with it. Until then the advisory must name it.
296+ assert "RAW_MATERIAL_KEY_HINTS" in listed
243297
244298 # The advisory is not a budget input: it must not appear in the committed
245299 # inventory, which stays the single computed authority.
@@ -273,43 +327,37 @@ def test_rename_visibility_splits_into_three_cases() -> None:
273327
274328 smoke = runpy .run_path (str (SMOKE ))
275329 registry = smoke ["load_registry" ]()
276- sources = smoke ["load_sources" ](REPO_ROOT )
277- name = "AGENT_TODO_HEADER_MARKERS"
278-
279- def renamed_in (paths ):
280- out = sources
281- for path in paths :
282- out = [
283- smoke ["SourceFile" ](source .path , source .suffix , source .text .replace (name , name + "_RENAMED" , 1 ))
284- if source .path == path
285- else source
286- for source in out
287- ]
288- return out
330+ plain = smoke ["load_sources" ](REPO_ROOT )
331+ forked = _with_synthetic_fork (smoke , plain )
289332
290333 # Case 1: declared name, one side renamed -> the declaration no longer resolves.
291- declared = _restate (smoke , sources , "loopx/global_todos.py" , "SOURCE_SURFACES" , "GT_SOURCE_SURFACES" )
334+ declared = _restate (smoke , plain , "loopx/global_todos.py" , "SOURCE_SURFACES" , "GT_SOURCE_SURFACES" )
292335 with pytest .raises (smoke ["Drift" ], match = "every defining module" ):
293336 smoke ["check_scope_declarations" ](registry , smoke ["build_inventory" ](REPO_ROOT , sources = declared ))
294337
295338 # Case 2: undeclared name, one side renamed -> gone from the budget AND the advisory.
296- partial = smoke ["build_inventory" ](REPO_ROOT , sources = renamed_in (["loopx/state_projection.py" ]))
297- assert name not in {entry ["name" ] for entry in partial ["duplicate_definitions" ]["multi_value_forks" ]}
298- assert name not in {row ["name" ] for row in divergent_value_sets (partial )}
339+ partial = smoke ["build_inventory" ](
340+ REPO_ROOT , sources = _rename_synthetic_side (smoke , forked , SYNTHETIC_FORK_MODULES [0 ])
341+ )
342+ assert SYNTHETIC_FORK_NAME not in {
343+ entry ["name" ] for entry in partial ["duplicate_definitions" ]["multi_value_forks" ]
344+ }
345+ assert SYNTHETIC_FORK_NAME not in {row ["name" ] for row in divergent_value_sets (partial )}
299346
300347 # Case 3: every side renamed -> also invisible; indistinguishable from an honest rename.
301348 whole = smoke ["build_inventory" ](
302349 REPO_ROOT ,
303- sources = renamed_in ([
304- "loopx/state_projection.py" ,
305- "loopx/control_plane/goals/active_state_metadata.py" ,
306- ]),
350+ sources = _rename_synthetic_side (
351+ smoke , _rename_synthetic_side (smoke , forked , SYNTHETIC_FORK_MODULES [0 ]), SYNTHETIC_FORK_MODULES [1 ]
352+ ),
307353 )
308- assert name not in {entry ["name" ] for entry in whole ["duplicate_definitions" ]["multi_value_forks" ]}
309- assert name not in {row ["name" ] for row in divergent_value_sets (whole )}
354+ assert SYNTHETIC_FORK_NAME not in {
355+ entry ["name" ] for entry in whole ["duplicate_definitions" ]["multi_value_forks" ]
356+ }
357+ assert SYNTHETIC_FORK_NAME not in {row ["name" ] for row in divergent_value_sets (whole )}
310358
311359 # The surviving forks are what the advisory does list, by name.
312- assert "USER_TODO_HEADER_MARKERS " in {row ["name" ] for row in divergent_value_sets (partial )}
360+ assert "RAW_MATERIAL_KEY_HINTS " in {row ["name" ] for row in divergent_value_sets (partial )}
313361
314362
315363def test_bounded_context_scope_requires_every_distinct_defining_module () -> None :
0 commit comments