$policyTypes * @param array $foundationTypes */ public function __construct( public array $policyTypes, public array $foundationTypes, ) {} public static function fromContext(mixed $context): ?self { if (! is_array($context)) { return null; } $inventory = $context['inventory'] ?? null; if (! is_array($inventory)) { return null; } $coverage = $inventory['coverage'] ?? null; if (! is_array($coverage)) { return null; } $policyTypes = self::normalizeCoverageMap($coverage['policy_types'] ?? null); $foundationTypes = self::normalizeCoverageMap($coverage['foundation_types'] ?? null); if ($policyTypes === [] && $foundationTypes === []) { return null; } return new self( policyTypes: $policyTypes, foundationTypes: $foundationTypes, ); } /** * @return list */ public function coveredTypes(): array { $covered = []; foreach (array_merge($this->policyTypes, $this->foundationTypes) as $type => $meta) { if (($meta['status'] ?? null) === self::StatusSucceeded) { $covered[] = $type; } } sort($covered, SORT_STRING); return array_values(array_unique($covered)); } /** * Build the canonical `inventory.coverage.*` payload for OperationRun.context. * * @param array $statusByType * @param list $foundationTypes * @return array{policy_types: array, foundation_types: array} */ public static function buildPayload(array $statusByType, array $foundationTypes): array { $foundationTypes = array_values(array_unique(array_filter($foundationTypes, fn (mixed $type): bool => is_string($type) && $type !== ''))); $foundationLookup = array_fill_keys($foundationTypes, true); $policy = []; $foundations = []; foreach ($statusByType as $type => $status) { if (! is_string($type) || $type === '') { continue; } $normalizedStatus = self::normalizeStatus($status); if ($normalizedStatus === null) { continue; } $row = ['status' => $normalizedStatus]; if (array_key_exists($type, $foundationLookup)) { $foundations[$type] = $row; continue; } $policy[$type] = $row; } ksort($policy); ksort($foundations); return [ 'policy_types' => $policy, 'foundation_types' => $foundations, ]; } private static function normalizeStatus(mixed $status): ?string { if (! is_string($status)) { return null; } return match ($status) { self::StatusSucceeded, self::StatusFailed, self::StatusSkipped => $status, default => null, }; } /** * @return array */ private static function normalizeCoverageMap(mixed $value): array { if (! is_array($value)) { return []; } $normalized = []; foreach ($value as $type => $meta) { if (! is_string($type) || $type === '') { continue; } if (! is_array($meta)) { continue; } $status = self::normalizeStatus($meta['status'] ?? null); if ($status === null) { continue; } $row = ['status' => $status]; if (array_key_exists('item_count', $meta) && is_int($meta['item_count'])) { $row['item_count'] = $meta['item_count']; } if (array_key_exists('error_code', $meta) && (is_string($meta['error_code']) || $meta['error_code'] === null)) { $row['error_code'] = $meta['error_code']; } $normalized[$type] = $row; } ksort($normalized); return $normalized; } }