@@ -177,6 +177,45 @@ def counted_hash(*args):
177177 assert api .uploads [0 ][0 ][1 ] == f'sha256/{ artifact .sha256 } /image.sif'
178178
179179
180+ def test_source_publish_survives_another_attempt_staging (configured , monkeypatch , tmp_path , capability ):
181+ import subprocess
182+ from services .remote_execution import bundle
183+
184+ repo = tmp_path / 'repo'
185+ repo .mkdir ()
186+ subprocess .run (['git' , 'init' , '-q' , str (repo )], check = True )
187+ (repo / 'source.txt' ).write_text ('immutable committed source' )
188+ subprocess .run (['git' , '-C' , str (repo ), 'add' , '.' ], check = True )
189+ subprocess .run (['git' , '-C' , str (repo ), '-c' , 'user.name=Fixture' ,
190+ '-c' , 'user.email=fixture@example.invalid' , 'commit' , '-qm' , 'fixture' ], check = True )
191+ revision = subprocess .check_output (['git' , '-C' , str (repo ), 'rev-parse' , 'HEAD' ], text = True ).strip ()
192+ data = tmp_path / 'data'
193+ first = tmp_path / 'first'
194+ digest = bundle ._staged_source_archive (repo , data , revision , first , extract = False )
195+ source = first / '.bms-source.tar.gz'
196+ entry = SimpleNamespace (source = source , role = 'source' , sha256 = digest , size_bytes = source .stat ().st_size )
197+ monkeypatch .setattr (worker , 'MIN_BYTES' , 1 )
198+ monkeypatch .setenv ('BMS_HF_ASSET_ALLOW_PUBLISH' , '1' )
199+ api = Api (entry , False )
200+ original_upload = api .batch_bucket_files
201+ generations = []
202+
203+ def upload (bucket , * , add ):
204+ original_upload (bucket , add = add )
205+ before = worker ._identity (source .stat ())
206+ second = tmp_path / 'second'
207+ assert bundle ._staged_source_archive (repo , data , revision , second , extract = False ) == digest
208+ # A sibling launch and its cleanup must not change the publisher's generation.
209+ (second / '.bms-source.tar.gz' ).unlink ()
210+ assert hashlib .sha256 (source .read_bytes ()).hexdigest () == digest
211+ generations .append ((before , worker ._identity (source .stat ())))
212+
213+ monkeypatch .setattr (api , 'batch_bucket_files' , upload )
214+ assert worker ._prepare (request (entry ), broker .configuration (), api , Http (capability ), 'token' ) == capability
215+ assert len (api .uploads ) == 1
216+ assert len (generations ) == 1 and generations [0 ][0 ] == generations [0 ][1 ]
217+
218+
180219@pytest .mark .parametrize ('failure' , ['missing' , 'public' , 'owner' , 'size' , 'quota' , 'hash' , 'symlink' , 'changed' , 'uncertain' ])
181220def test_fail_closed_no_capability (configured , monkeypatch , artifact , capability , failure ):
182221 api , http = Api (artifact ), Http (capability )
0 commit comments