@@ -78,13 +78,18 @@ class ReleasePromotionContractBuilder:
7878 package_version : str | None = None
7979 validation : ValidationReport | None = None
8080 diagnostics : Sequence [DiagnosticRef ] = ()
81+ published_artifact_index : ArtifactRef | None = None
8182 metadata : Mapping [str , Any ] = field (default_factory = dict )
8283
8384 def __post_init__ (self ) -> None :
8485 if not isinstance (self .candidate_bundle , ReleaseCandidateInputBundle ):
8586 raise ValueError ("candidate_bundle must be ReleaseCandidateInputBundle" )
8687 if not isinstance (self .promotion_result , FullPromotionResult ):
8788 raise ValueError ("promotion_result must be FullPromotionResult" )
89+ if self .published_artifact_index is not None and not isinstance (
90+ self .published_artifact_index , ArtifactRef
91+ ):
92+ raise ValueError ("published_artifact_index must be ArtifactRef" )
8893 object .__setattr__ (
8994 self ,
9095 "diagnostics" ,
@@ -100,10 +105,15 @@ def build(self) -> StageContract:
100105
101106 context = self .candidate_bundle .context
102107 inputs = _contract_inputs (self .candidate_bundle )
103- outputs = _contract_outputs (context , self .promotion_result )
108+ outputs = _contract_outputs (
109+ context ,
110+ self .promotion_result ,
111+ published_artifact_index = self .published_artifact_index ,
112+ )
104113 parameters = _contract_parameters (
105114 self .candidate_bundle ,
106115 self .promotion_result ,
116+ published_artifact_index = self .published_artifact_index ,
107117 )
108118 return StageContract (
109119 contract_type = RELEASE_PROMOTION_CONTRACT_TYPE ,
@@ -122,6 +132,11 @@ def build(self) -> StageContract:
122132 "context" : context .to_dict (),
123133 "candidate_bundle" : self .candidate_bundle .to_dict (),
124134 "promotion_result" : self .promotion_result .to_dict (),
135+ "published_artifact_index" : (
136+ self .published_artifact_index .to_dict ()
137+ if self .published_artifact_index is not None
138+ else None
139+ ),
125140 "outputs" : [output .to_dict () for output in outputs ],
126141 }
127142 ),
@@ -152,6 +167,7 @@ def build_release_promotion_contract(
152167 package_version : str | None = None ,
153168 validation : ValidationReport | None = None ,
154169 diagnostics : Sequence [DiagnosticRef ] = (),
170+ published_artifact_index : ArtifactRef | None = None ,
155171 metadata : Mapping [str , Any ] | None = None ,
156172) -> StageContract :
157173 """Build the Stage 5 release promotion contract."""
@@ -164,6 +180,7 @@ def build_release_promotion_contract(
164180 package_version = package_version ,
165181 validation = validation ,
166182 diagnostics = diagnostics ,
183+ published_artifact_index = published_artifact_index ,
167184 metadata = metadata or {},
168185 ).build ()
169186
@@ -178,6 +195,7 @@ def write_release_promotion_contract(
178195 package_version : str | None = None ,
179196 validation : ValidationReport | None = None ,
180197 diagnostics : Sequence [DiagnosticRef ] = (),
198+ published_artifact_index : ArtifactRef | None = None ,
181199 metadata : Mapping [str , Any ] | None = None ,
182200) -> StageContract :
183201 """Build, write, and return the Stage 5 release promotion contract."""
@@ -190,6 +208,7 @@ def write_release_promotion_contract(
190208 package_version = package_version ,
191209 validation = validation ,
192210 diagnostics = diagnostics ,
211+ published_artifact_index = published_artifact_index ,
193212 metadata = metadata ,
194213 )
195214 write_contract (contract , contract_path )
@@ -266,13 +285,15 @@ def _contract_inputs(
266285def _contract_outputs (
267286 context : ReleasePromotionContext ,
268287 result : FullPromotionResult ,
288+ * ,
289+ published_artifact_index : ArtifactRef | None = None ,
269290) -> tuple [ArtifactRef , ...]:
270291 hf_base = f"hf://{ context .hf_repo_name } "
271292 completion_marker_path = (
272293 result .completion_marker .marker_path
273294 or f"releases/{ context .release_version } /release-complete.json"
274295 )
275- return (
296+ outputs = (
276297 ArtifactRef (
277298 logical_name = "huggingface_release_artifacts" ,
278299 uri = f"{ hf_base } /" ,
@@ -339,11 +360,16 @@ def _contract_outputs(
339360 metadata = {"artifact_family" : "release_completion_marker" },
340361 ),
341362 )
363+ if published_artifact_index is not None :
364+ outputs = (* outputs , published_artifact_index )
365+ return outputs
342366
343367
344368def _contract_parameters (
345369 candidate_bundle : ReleaseCandidateInputBundle ,
346370 result : FullPromotionResult ,
371+ * ,
372+ published_artifact_index : ArtifactRef | None = None ,
347373) -> dict [str , Any ]:
348374 context = candidate_bundle .context
349375 return {
@@ -363,6 +389,9 @@ def _contract_parameters(
363389 "source_output_contract_path" : candidate_bundle .source_output_contract_path ,
364390 "validation_report_paths" : list (candidate_bundle .validation_report_paths ),
365391 "diagnostics_manifest_path" : candidate_bundle .diagnostics_manifest_path ,
392+ "published_artifact_index_path" : _artifact_relative_path (
393+ published_artifact_index
394+ ),
366395 }
367396
368397
@@ -374,6 +403,7 @@ def _contract_metadata(
374403 outputs : Sequence [ArtifactRef ],
375404 extra : Mapping [str , Any ],
376405) -> dict [str , Any ]:
406+ outputs_by_name = {output .logical_name : output for output in outputs }
377407 return {
378408 ** dict (extra ),
379409 "contract_file" : RELEASE_PROMOTION_CONTRACT_FILENAME ,
@@ -383,10 +413,22 @@ def _contract_metadata(
383413 "cleanup" : promotion_result .cleanup .to_dict (),
384414 "already_finalized" : promotion_result .already_finalized ,
385415 "promotion_result" : promotion_result .to_dict (),
416+ "published_artifact_index" : (
417+ outputs_by_name ["published_artifact_index" ].to_dict ()
418+ if "published_artifact_index" in outputs_by_name
419+ else None
420+ ),
386421 "public_refs" : {output .logical_name : output .uri for output in outputs },
387422 }
388423
389424
425+ def _artifact_relative_path (artifact : ArtifactRef | None ) -> str | None :
426+ if artifact is None :
427+ return None
428+ relative_path = artifact .metadata .get ("relative_path" )
429+ return relative_path if isinstance (relative_path , str ) and relative_path else None
430+
431+
390432def _execution_record (result : FullPromotionResult ) -> ExecutionRecord :
391433 return ExecutionRecord (
392434 status = "completed" ,
@@ -411,6 +453,16 @@ def _substage_records(
411453 promotion_result : FullPromotionResult ,
412454) -> tuple [SubstageRecord , ...]:
413455 outputs_by_name = {artifact .logical_name : artifact for artifact in public_outputs }
456+ finalization_outputs = [
457+ outputs_by_name ["release_manifest" ],
458+ outputs_by_name ["versioned_release_manifest" ],
459+ outputs_by_name ["trace_tro" ],
460+ outputs_by_name ["versioned_trace_tro" ],
461+ outputs_by_name ["version_manifest" ],
462+ outputs_by_name ["release_completion_marker" ],
463+ ]
464+ if "published_artifact_index" in outputs_by_name :
465+ finalization_outputs .append (outputs_by_name ["published_artifact_index" ])
414466 return (
415467 SubstageRecord (
416468 substage_id = "5a_validate_outputs" ,
@@ -442,14 +494,7 @@ def _substage_records(
442494 SubstageRecord (
443495 substage_id = "5d_write_version_manifest" ,
444496 status = "completed" ,
445- outputs = (
446- outputs_by_name ["release_manifest" ],
447- outputs_by_name ["versioned_release_manifest" ],
448- outputs_by_name ["trace_tro" ],
449- outputs_by_name ["versioned_trace_tro" ],
450- outputs_by_name ["version_manifest" ],
451- outputs_by_name ["release_completion_marker" ],
452- ),
497+ outputs = tuple (finalization_outputs ),
453498 reuse_mode = "handoff" ,
454499 metadata = {
455500 "version_manifest_updated" : promotion_result .version_manifest .updated ,
0 commit comments