$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)); } /** * @return array */ public function rows(): array { $rows = []; foreach ($this->policyTypes as $type => $meta) { $rows[$type] = array_merge($meta, [ 'segment' => 'policy', 'type' => $type, ]); } foreach ($this->foundationTypes as $type => $meta) { $rows[$type] = array_merge($meta, [ 'segment' => 'foundation', 'type' => $type, ]); } ksort($rows); return $rows; } /** * @return array{ * segment: 'policy'|'foundation', * type: string, * status: string, * item_count?: int, * error_code?: string|null * }|null */ public function row(string $type): ?array { return $this->rows()[$type] ?? null; } /** * 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; } $row = self::normalizeBuildRow($status); if ($row === null) { continue; } if (array_key_exists($type, $foundationLookup)) { $foundations[$type] = $row; continue; } $policy[$type] = $row; } ksort($policy); ksort($foundations); return [ 'policy_types' => $policy, 'foundation_types' => $foundations, ]; } /** * @return array{status: string, item_count?: int, error_code?: string|null}|null */ private static function normalizeBuildRow(mixed $value): ?array { if (is_string($value)) { $status = self::normalizeStatus($value); return $status === null ? null : ['status' => $status]; } if (! is_array($value)) { return null; } $status = self::normalizeStatus($value['status'] ?? null); if ($status === null) { return null; } $row = ['status' => $status]; if (array_key_exists('item_count', $value) && is_int($value['item_count'])) { $row['item_count'] = $value['item_count']; } if (array_key_exists('error_code', $value) && (is_string($value['error_code']) || $value['error_code'] === null)) { $row['error_code'] = $value['error_code']; } return $row; } 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; } }