registry = $registry; $this->contract = $contract; } /** * @param array $evidence Masked structured evidence. * @param list $affected_objects Safe object references. * @return array */ public function build( string $check_id, string $status, array $evidence, int $observed, int $total, string $coverage_state, string $timestamp, array $affected_objects = array() ): array { $definition = $this->registry->definition( $check_id ); $statuses = $definition['statuses'] ?? null; if ( ! is_array( $statuses ) || ! in_array( $status, $statuses, true ) ) { throw new InvalidArgumentException( 'checker_status_not_declared' ); } $this->validate_evidence( $definition, $evidence ); $severity_policy = $this->registry->policy( 'severity_policies', (string) $definition['severity_logic'] ); $severity = $severity_policy[ $status ] ?? null; $confidence = $this->confidence( $definition, $status, $coverage_state ); if ( ! is_string( $severity ) ) { throw new InvalidArgumentException( 'checker_severity_missing' ); } $i18n = (string) $definition['i18n']; $finding = array( 'check_id' => $check_id, 'check_version' => (string) $definition['version'], 'rule_pack_version' => $this->registry->registry_version(), 'status' => $status, 'severity' => $severity, 'confidence' => $confidence, 'category' => (string) $definition['category'], 'subcategory' => (string) $definition['category'], 'affected_objects' => $affected_objects, 'title_key' => $i18n . '.title', 'summary_key' => $i18n . '.summary.' . $status, 'why_it_matters_key' => $i18n . '.why_it_matters', 'evidence' => $evidence, 'evidence_summary_key' => $i18n . '.evidence.' . $status, 'next_step_key' => $i18n . '.next_step.' . $status, 'owner_key' => 'owner.site_administrator', 'effort_band' => 'unknown', 'prerequisites' => array_values( $definition['prerequisites'] ), 'coverage' => array( 'unit' => (string) $definition['coverage_unit'], 'observed' => $observed, 'total' => $total, 'state' => $coverage_state, ), 'limitation_key' => $i18n . '.limitation.' . $status, 'method' => (string) $definition['adapter'], 'source_refs' => array_values( $definition['sources'] ), 'first_seen' => $timestamp, 'last_seen' => $timestamp, 'comparison_state' => 'new', 'acknowledgement' => null, ); $this->contract->validate( $finding ); return $finding; } /** * @param array $definition Check definition. * @param array $evidence Evidence object. */ private function validate_evidence( array $definition, array $evidence ): void { $schema = $this->registry->policy( 'evidence_schemas', (string) $definition['evidence_schema'] ); $keys = array_keys( $evidence ); foreach ( $schema['required'] as $required ) { if ( ! in_array( $required, $keys, true ) ) { throw new InvalidArgumentException( 'checker_evidence_required' ); } } foreach ( $keys as $key ) { if ( ! in_array( $key, $schema['allowed'], true ) ) { throw new InvalidArgumentException( 'checker_evidence_unknown' ); } } } /** * @param array $definition Check definition. */ private function confidence( array $definition, string $status, string $coverage_state ): string { $policy = $this->registry->policy( 'confidence_policies', (string) $definition['confidence'] ); if ( 'not_tested' === $status ) { $key = 'not_tested'; } elseif ( 'error' === $status || 'error' === $coverage_state ) { $key = 'error'; } elseif ( 'partial' === $coverage_state ) { $key = 'partial'; } else { $key = 'base'; } $value = $policy[ $key ] ?? null; return is_string( $value ) ? $value : 'low'; } }