@@ -654,12 +654,50 @@ def evidence_provenance(base_url: str, compose_project: str) -> dict[str, Any]:
654654 "runner_name" : os .environ .get ("RUNNER_NAME" , "" ),
655655 "runner_os" : os .environ .get ("RUNNER_OS" , "" ),
656656 "runner_arch" : os .environ .get ("RUNNER_ARCH" , "" ),
657+ "runner_environment" : os .environ .get ("RUNNER_ENVIRONMENT" , "" ),
657658 "compose_project" : compose_project ,
658659 "base_url" : base_url ,
659660 "bounded_growth_policy_sha256" : file_sha256 (policy_path ),
660661 }
661662
662663
664+ def evidence_trust_profile (
665+ * ,
666+ duration_seconds : int ,
667+ compose_project : str ,
668+ runner_environment : str ,
669+ periodic_sample_count : int ,
670+ minimum_trusted_samples : int ,
671+ sampling_health : dict [str , Any ],
672+ failures : list [str ],
673+ ) -> dict [str , Any ]:
674+ minimum_duration_seconds = 3600
675+ reasons = []
676+
677+ if duration_seconds < minimum_duration_seconds :
678+ reasons .append (f"duration below trusted long-soak minimum { minimum_duration_seconds } s" )
679+ if not compose_project :
680+ reasons .append ("compose-backed resource sampling was not configured" )
681+ if runner_environment and runner_environment != "self-hosted" :
682+ reasons .append (f"runner environment is { runner_environment } , not self-hosted" )
683+ if periodic_sample_count < minimum_trusted_samples :
684+ reasons .append ("periodic sample coverage below trusted minimum" )
685+ if int (sampling_health .get ("unhealthy_samples" ) or 0 ) > 0 :
686+ reasons .append ("compose-backed resource sampling has unhealthy samples" )
687+ if failures :
688+ reasons .append ("bounded-growth assertions failed" )
689+
690+ return {
691+ "profile" : "trusted_long_soak_v1" ,
692+ "eligible" : len (reasons ) == 0 ,
693+ "minimum_duration_seconds" : minimum_duration_seconds ,
694+ "runner_environment" : runner_environment ,
695+ "requires_self_hosted_runner" : True ,
696+ "requires_compose_resource_sampling" : True ,
697+ "reasons" : reasons ,
698+ }
699+
700+
663701def main () -> int :
664702 args = parse_args ()
665703
@@ -758,6 +796,8 @@ def main() -> int:
758796 observed_sample_coverage = periodic_sample_count / expected_samples
759797 sampling_health = sample_health (samples , args .compose_project )
760798
799+ provenance = evidence_provenance (base_url , args .compose_project )
800+
761801 summary = {
762802 "duration_seconds" : args .duration_seconds ,
763803 "elapsed_seconds" : round (elapsed_seconds , 2 ),
@@ -799,7 +839,7 @@ def main() -> int:
799839 "evidence" : {
800840 "started_at" : started_at .isoformat ().replace ("+00:00" , "Z" ),
801841 "finished_at" : finished_at .isoformat ().replace ("+00:00" , "Z" ),
802- "provenance" : evidence_provenance ( base_url , args . compose_project ) ,
842+ "provenance" : provenance ,
803843 },
804844 }
805845
@@ -867,6 +907,16 @@ def main() -> int:
867907 metrics .mark_assertion_failed ()
868908 summary ["failures" ] = failures
869909
910+ summary ["evidence" ]["trust" ] = evidence_trust_profile (
911+ duration_seconds = args .duration_seconds ,
912+ compose_project = args .compose_project ,
913+ runner_environment = str (provenance .get ("runner_environment" ) or "" ),
914+ periodic_sample_count = periodic_sample_count ,
915+ minimum_trusted_samples = min_samples ,
916+ sampling_health = sampling_health ,
917+ failures = failures ,
918+ )
919+
870920 metrics_path .write_text (metrics .prometheus (), encoding = "utf-8" )
871921 summary_path .write_text (json .dumps (summary , indent = 2 , sort_keys = True ) + "\n " , encoding = "utf-8" )
872922 print (json .dumps (summary , indent = 2 , sort_keys = True ))
0 commit comments