@@ -60,6 +60,8 @@ func (s *Service) DefineValidation(ctx context.Context, investigationID string,
6060 Timeout : opts .Timeout ,
6161 MaxOutputBytes : opts .MaxOutputBytes ,
6262 Observation : observationContractToEvidence (opts .Observation ),
63+ Protocol : evidence .ValidationProtocol (opts .Protocol ),
64+ ReadinessTimeout : opts .ReadinessTimeout ,
6365 }
6466
6567 evSvc := evidence .NewService (c , evidence .NewExecRunner ())
@@ -223,6 +225,29 @@ func bindValidationWorkspace(ctx context.Context, service *Service, c *corpus.Co
223225 return nil
224226}
225227
228+ // RunValidationGroup executes a bounded repeat/stress validation group.
229+ func (s * Service ) RunValidationGroup (ctx context.Context , id string , opts cli.RepeatValidationOptions ) (* cli.ValidationRunGroupResult , error ) {
230+ if ! opts .Execute {
231+ return nil , evidence .ErrExecutionNotAuthorized
232+ }
233+ kinds := make ([]evidence.RunKind , len (opts .Kinds ))
234+ for index , kind := range opts .Kinds {
235+ kinds [index ] = evidence .RunKind (kind )
236+ }
237+ c , err := s .openCorpus (ctx )
238+ if err != nil {
239+ return nil , err
240+ }
241+ group , err := evidence .NewService (c , evidence .NewExecRunner ()).RunValidationGroup (ctx , id , evidence.RepeatValidationOptions {
242+ Kinds : kinds , RunCount : opts .RunCount , Concurrency : opts .Concurrency ,
243+ PerRunTimeout : opts .PerRunTimeout , OverallTimeout : opts .OverallTimeout , SampleInterval : opts .SampleInterval ,
244+ })
245+ if err != nil {
246+ return nil , mapEvidenceError (err )
247+ }
248+ return validationRunGroupResult (group ), nil
249+ }
250+
226251// CompareValidation compares a base validation run with a candidate validation run.
227252func (s * Service ) CompareValidation (ctx context.Context , baseRunID , candidateRunID string ) (* cli.ValidationComparisonResult , error ) {
228253 c , err := s .openReadOnlyCorpus (ctx )
@@ -363,6 +388,10 @@ func validationResult(def *evidence.ValidationDefinition) *cli.ValidationResult
363388 if def .Timeout > 0 {
364389 timeout = def .Timeout .String ()
365390 }
391+ readinessTimeout := ""
392+ if def .ReadinessTimeout > 0 {
393+ readinessTimeout = def .ReadinessTimeout .String ()
394+ }
366395 return & cli.ValidationResult {
367396 ID : def .ID ,
368397 InvestigationID : def .InvestigationID ,
@@ -378,6 +407,8 @@ func validationResult(def *evidence.ValidationDefinition) *cli.ValidationResult
378407 Timeout : timeout ,
379408 MaxOutputBytes : def .MaxOutputBytes ,
380409 Observation : observationContractToCLI (def .Observation ),
410+ Protocol : string (def .Protocol ),
411+ ReadinessTimeout : readinessTimeout ,
381412 CreatedAt : formatTime (def .CreatedAt ),
382413 }
383414}
@@ -402,7 +433,77 @@ func validationRunResult(run *evidence.ValidationRun) *cli.ValidationRunResult {
402433 WorkspaceSnapshotAfter : run .WorkspaceSnapshotAfter ,
403434 WorkspaceBindingStatus : run .WorkspaceBindingStatus ,
404435 WorkspaceBindingReason : run .WorkspaceBindingReason ,
436+ Process : validationProcessIdentity (run .Process ),
437+ Phases : validationPhases (run .Phases ),
438+ TimeoutPhase : run .TimeoutPhase ,
439+ FailurePhase : run .FailurePhase ,
440+ Resources : validationResources (run .Resources ),
441+ Cleanup : validationCleanup (run .Cleanup ),
442+ }
443+ }
444+
445+ func validationRunGroupResult (group * evidence.ValidationRunGroup ) * cli.ValidationRunGroupResult {
446+ result := & cli.ValidationRunGroupResult {
447+ ID : group .ID , DefinitionID : group .DefinitionID , InvestigationID : group .InvestigationID ,
448+ ConfigurationSHA256 : group .ConfigurationSHA256 , RequestedRuns : group .RequestedRuns , CompletedRuns : group .CompletedRuns ,
449+ Concurrency : group .Concurrency , PerRunTimeout : group .PerRunTimeout .String (), OverallTimeout : group .OverallTimeout .String (),
450+ SampleInterval : group .SampleInterval .String (), Classification : string (group .Classification ),
451+ StartedAt : formatTime (group .StartedAt ), CompletedAt : formatTime (group .CompletedAt ),
452+ }
453+ for _ , attempt := range group .Attempts {
454+ result .Attempts = append (result .Attempts , cli.ValidationAttemptResult {
455+ Index : attempt .Index , Kind : string (attempt .Kind ), RunID : attempt .RunID ,
456+ StartedAt : formatTime (attempt .StartedAt ), CompletedAt : formatTime (attempt .CompletedAt ), ExitCode : attempt .ExitCode ,
457+ Classification : string (attempt .Classification ), ObservationStatus : string (attempt .ObservationStatus ),
458+ TimeoutPhase : attempt .TimeoutPhase , FailurePhase : attempt .FailurePhase ,
459+ Error : attempt .Error , Process : validationProcessIdentity (attempt .Process ),
460+ Phases : validationPhases (attempt .Phases ),
461+ Resources : validationResources (attempt .Resources ), Cleanup : validationCleanup (attempt .Cleanup ),
462+ })
463+ }
464+ for _ , aggregate := range group .Aggregates {
465+ result .Aggregates = append (result .Aggregates , cli.ValidationAggregateResult {
466+ Kind : string (aggregate .Kind ), Requested : aggregate .Requested , Completed : aggregate .Completed ,
467+ Passing : aggregate .Passing , Failing : aggregate .Failing , Inconclusive : aggregate .Inconclusive ,
468+ Cancelled : aggregate .Cancelled , Classification : string (aggregate .Classification ),
469+ ResourceClassification : aggregate .ResourceClassification ,
470+ })
471+ }
472+ if group .Comparison != nil {
473+ result .Comparison = & cli.ValidationGroupComparisonResult {Classification : string (group .Comparison .Classification ), Explanation : group .Comparison .Explanation }
474+ }
475+ return result
476+ }
477+
478+ func validationPhases (value evidence.RunPhases ) cli.ValidationRunPhases {
479+ return cli.ValidationRunPhases {
480+ SpawnStartedAt : formatTime (value .SpawnStartedAt ), ProcessStartedAt : formatTime (value .ProcessStartedAt ),
481+ InitializedAt : formatTime (value .InitializedAt ), ToolsListedAt : formatTime (value .ToolsListedAt ),
482+ FirstResponseAt : formatTime (value .FirstResponseAt ), ExecutionEndedAt : formatTime (value .ExecutionEndedAt ),
483+ ShutdownStartedAt : formatTime (value .ShutdownStartedAt ), ShutdownCheckedAt : formatTime (value .ShutdownCheckedAt ),
484+ }
485+ }
486+
487+ func validationProcessIdentity (value evidence.ProcessIdentity ) cli.ValidationProcessIdentity {
488+ return cli.ValidationProcessIdentity {PID : value .PID , CreateTimeUnixMilli : value .CreateTimeUnixMilli }
489+ }
490+
491+ func validationResources (value evidence.ResourceTelemetry ) cli.ValidationResourceTelemetry {
492+ return cli.ValidationResourceTelemetry {
493+ Provider : value .Provider , Platform : value .Platform , SampleInterval : value .SampleInterval .String (), SampleCount : value .SampleCount ,
494+ CPUTimeMillis : cli.ValidationInt64Metric {Value : value .CPUTimeMillis .Value , UnavailableReason : value .CPUTimeMillis .UnavailableReason },
495+ PeakRSSBytes : cli.ValidationUint64Metric {Value : value .PeakRSSBytes .Value , UnavailableReason : value .PeakRSSBytes .UnavailableReason },
496+ PeakChildCount : cli.ValidationInt64Metric {Value : value .PeakChildCount .Value , UnavailableReason : value .PeakChildCount .UnavailableReason },
497+ SamplerOverheadNanoseconds : value .SamplerOverheadNanoseconds ,
498+ }
499+ }
500+
501+ func validationCleanup (value evidence.CleanupResult ) cli.ValidationCleanupResult {
502+ result := cli.ValidationCleanupResult {Status : value .Status , Reason : value .Reason , CheckedAt : formatTime (value .CheckedAt )}
503+ for _ , survivor := range value .Survivors {
504+ result .Survivors = append (result .Survivors , validationProcessIdentity (survivor ))
405505 }
506+ return result
406507}
407508
408509func observationContractToEvidence (contract * cli.ValidationObservationContract ) * evidence.ObservationContract {
0 commit comments