database = $database; $this->registry = $registry; $this->collector = $collector ?? new ServerObservationCollector(); } /** * @return array{run_uuid:string,profile:string,status:string,findings:list>} */ public function run( string $profile, string $language = 'en', int $actor_id = 0 ): array { $observations = $this->collector->collect( $profile ); return $this->run_observations( $observations, $language, $actor_id ); } /** * Persist a previously validated local standalone observation set. * * @return array{run_uuid:string,profile:string,status:string,findings:list>} */ public function run_observations( ServerObservationSet $observations, string $language = 'en', int $actor_id = 0 ): array { if ( ! in_array( $language, array( 'en', 'ru' ), true ) ) { throw new \InvalidArgumentException( 'invalid_server_report_language' ); } $factory = new FindingFactory( $this->registry, new FindingContract() ); $server = array(); foreach ( ServerObservationSet::check_ids() as $check_id ) { $server[] = new ServerFindingChecker( $check_id, $factory, $observations ); } $runs = new RunRepository( $this->database, new RunStateMachine() ); $tasks = new TaskRepository( $this->database ); $findings = new FindingRepository( $this->database, new FindingContract() ); $coordinator = new RunCoordinator( $runs, $tasks ); $checkers = new CheckerRegistry( $this->registry, $server ); // The registry's broad server profile also describes WordPress-visible and // manual checks used by other surfaces. This CLI service is deliberately a // closed nine-check plan and must never absorb those implementations. $check_ids = $this->registry->local_implemented_for_profile( 'server', ServerObservationSet::check_ids() ); if ( ServerObservationSet::check_ids() !== $check_ids ) { throw new StorageException( 'server_check_plan_mismatch' ); } $runner = new ChunkRunner( $runs, $tasks, $findings, $checkers, $this->registry, $coordinator, new TransactionManager( $this->database ) ); $run = $coordinator->create( 'server', $language, $this->registry->registry_version(), $this->registry->plan_budget( $check_ids ), $check_ids, $actor_id ); $run = $coordinator->start( $run->uuid(), $actor_id ); $worker_limit = count( $check_ids ) + 1; for ( $index = 0; $index < $worker_limit; ++$index ) { $result = $runner->run_one( $run->uuid() ); if ( RunStatus::COMPLETED === $result['run_status'] || RunStatus::FAILED === $result['run_status'] ) { break; } } $summary = $runs->summary( $run->uuid() ); if ( null === $summary || RunStatus::COMPLETED !== $summary['status'] ) { throw new StorageException( 'server_scan_failed' ); } return array( 'run_uuid' => $run->uuid(), 'profile' => (string) $observations->section( 'server.runtime_services' )['values']['profile'], 'status' => $summary['status'], 'findings' => $findings->list_for_run( $run->id(), 20 ), ); } }