3232# class: evidence
3333# since: 2026-09-12
3434#
35+ # id: alternate_research_source_is_explicit
36+ # given: a research BASE.json source commit differs from its repository's manifest-pinned libs commit
37+ # then: the workspace carries an explicit matching research_participants source identity rather than silently rebasing the libs pin
38+ # class: boundary
39+ # since: 2026-09-12
40+ #
3541# id: separated_stack_component_has_graph_identity
3642# given: a research BASE.json declares a project name distinct from its source repository name
3743# then: the project has an explicit research_participants record and the former source owner does not retain a known superseded authority claim
@@ -128,25 +134,34 @@ def check_repository_projection(manifest: dict[str, Any], human: str, findings:
128134 return index
129135
130136
131- def check_research_participants (manifest : dict [str , Any ], human : str , findings : list [str ]) -> set [tuple [str , str ]]:
132- seen : set [tuple [str , str ]] = set ()
137+ def check_research_participants (
138+ manifest : dict [str , Any ],
139+ human : str ,
140+ findings : list [str ],
141+ ) -> tuple [set [tuple [str , str ]], set [tuple [str , str , str ]]]:
142+ keys : set [tuple [str , str ]] = set ()
143+ sources : set [tuple [str , str , str ]] = set ()
133144 for entry in manifest .get ("research_participants" , []):
134- key = (entry .get ("workspace" , "" ), entry .get ("participant_id" , "" ))
135- if key in seen :
145+ workspace = str (entry .get ("workspace" , "" ))
146+ participant_id = str (entry .get ("participant_id" , "" ))
147+ repository = str (entry .get ("repository" , "" ))
148+ commit = str (entry .get ("commit" , "" ))
149+ key = (workspace , participant_id )
150+ if key in keys :
136151 error (findings , "research.duplicate" , f"duplicate research participant { key !r} " )
137- seen .add (key )
138- workspace = entry .get ("workspace" , "" )
139- commit = entry .get ("commit" , "" )
152+ keys .add (key )
153+ sources .add ((workspace , repository , commit ))
140154 if workspace and workspace not in human :
141155 error (findings , "research.human_drift" , f"STACK_MANIFEST.md does not mention workspace { workspace !r} " )
142156 if commit and commit not in human :
143157 error (findings , "research.human_drift" , f"STACK_MANIFEST.md does not mention research commit { commit !r} " )
144- return seen
158+ return keys , sources
145159
146160
147161def check_base_records (
148162 repositories : dict [str , dict [str , Any ]],
149- research_participants : set [tuple [str , str ]],
163+ research_participant_keys : set [tuple [str , str ]],
164+ research_source_identities : set [tuple [str , str , str ]],
150165 readme : str ,
151166 findings : list [str ],
152167) -> None :
@@ -165,15 +180,17 @@ def check_base_records(
165180 source_entry = repositories .get (source_repository )
166181
167182 if source_entry and source_commit != source_entry .get ("commit" ):
168- error (
169- findings ,
170- "base.source_drift" ,
171- f"{ base_path .relative_to (ROOT )} pins { source_repository } @{ source_commit } , manifest pins { source_entry .get ('commit' )} " ,
172- )
183+ explicit_source = (workspace , source_repository , source_commit )
184+ if explicit_source not in research_source_identities :
185+ error (
186+ findings ,
187+ "base.source_drift" ,
188+ f"{ base_path .relative_to (ROOT )} pins { source_repository } @{ source_commit } , manifest libs pin is { source_entry .get ('commit' )} , and no matching research_participants source identity exists" ,
189+ )
173190
174191 source_name = source_repository .rsplit ("/" , 1 )[- 1 ] if source_repository else ""
175192 if project and source_name and project != source_name :
176- if (workspace , project ) not in research_participants :
193+ if (workspace , project ) not in research_participant_keys :
177194 error (
178195 findings ,
179196 "base.separated_unrepresented" ,
@@ -185,7 +202,7 @@ def check_base_records(
185202
186203def check_english_gonol_regression (
187204 repositories : dict [str , dict [str , Any ]],
188- research_participants : set [tuple [str , str ]],
205+ research_participant_keys : set [tuple [str , str ]],
189206 human : str ,
190207 readme : str ,
191208 findings : list [str ],
@@ -204,7 +221,7 @@ def check_english_gonol_regression(
204221 error (findings , "english_gonol.stale_edcm_authority" , "EDCM still claims English/text gonol construction authority after separation" )
205222
206223 expected = ("research/english-gonol/" , "english-gonol" )
207- if expected not in research_participants :
224+ if expected not in research_participant_keys :
208225 error (findings , "english_gonol.graph_missing" , "English Gonol is missing its explicit stack research-participant identity" )
209226
210227 for surface_name , surface in (("STACK_MANIFEST.md" , human ), ("README.md" , readme )):
@@ -225,17 +242,17 @@ def main() -> int:
225242 check_manifest_shape (manifest , findings )
226243 check_digest (manifest , human , findings )
227244 repositories = check_repository_projection (manifest , human , findings )
228- research_participants = check_research_participants (manifest , human , findings )
229- check_base_records (repositories , research_participants , readme , findings )
230- check_english_gonol_regression (repositories , research_participants , human , readme , findings )
245+ research_participant_keys , research_source_identities = check_research_participants (manifest , human , findings )
246+ check_base_records (repositories , research_participant_keys , research_source_identities , readme , findings )
247+ check_english_gonol_regression (repositories , research_participant_keys , human , readme , findings )
231248
232249 if findings :
233250 for finding in findings :
234251 print (f"error: { finding } " )
235252 print (f"stack consistency: fail ({ len (findings )} error(s))" )
236253 return 1
237254
238- print (f"stack consistency: pass ({ len (repositories )} repositories, { len (research_participants )} research participant identities)" )
255+ print (f"stack consistency: pass ({ len (repositories )} repositories, { len (research_participant_keys )} research participant identities)" )
239256 return 0
240257
241258
0 commit comments