*/ private const ALLOWED_COLORS = [ 'gray', 'info', 'success', 'warning', 'danger', 'primary', ]; public function __construct( public readonly string $label, public readonly string $color, public readonly ?string $icon = null, public readonly ?string $iconColor = null, public readonly ?OperatorSemanticAxis $semanticAxis = null, public readonly ?OperatorStateClassification $classification = null, public readonly ?OperatorNextActionPolicy $nextActionPolicy = null, public readonly ?string $diagnosticLabel = null, /** * @var list */ public readonly array $legacyAliases = [], public readonly ?string $notes = null, ) { if (trim($this->label) === '') { throw new InvalidArgumentException('BadgeSpec label must be a non-empty string.'); } if (! in_array($this->color, self::ALLOWED_COLORS, true)) { throw new InvalidArgumentException('BadgeSpec color must be one of: '.implode(', ', self::ALLOWED_COLORS)); } if ($this->icon !== null && trim($this->icon) === '') { throw new InvalidArgumentException('BadgeSpec icon must be null or a non-empty string.'); } if ($this->iconColor !== null && ! in_array($this->iconColor, self::ALLOWED_COLORS, true)) { throw new InvalidArgumentException('BadgeSpec iconColor must be null or one of: '.implode(', ', self::ALLOWED_COLORS)); } $hasTaxonomyMetadata = $this->semanticAxis !== null || $this->classification !== null || $this->nextActionPolicy !== null || $this->diagnosticLabel !== null || $this->legacyAliases !== [] || $this->notes !== null; if ($hasTaxonomyMetadata && ($this->semanticAxis === null || $this->classification === null || $this->nextActionPolicy === null)) { throw new InvalidArgumentException('BadgeSpec taxonomy metadata requires semanticAxis, classification, and nextActionPolicy together.'); } if ($this->diagnosticLabel !== null && trim($this->diagnosticLabel) === '') { throw new InvalidArgumentException('BadgeSpec diagnosticLabel must be null or a non-empty string.'); } foreach ($this->legacyAliases as $legacyAlias) { if (! is_string($legacyAlias) || trim($legacyAlias) === '') { throw new InvalidArgumentException('BadgeSpec legacyAliases must contain only non-empty strings.'); } } if ($this->notes !== null && trim($this->notes) === '') { throw new InvalidArgumentException('BadgeSpec notes must be null or a non-empty string.'); } if ($this->classification === OperatorStateClassification::Diagnostic && in_array($this->color, ['warning', 'danger'], true)) { throw new InvalidArgumentException('Diagnostic badge specs cannot use warning or danger colors.'); } if ($this->classification === OperatorStateClassification::Primary && in_array($this->color, ['warning', 'danger'], true) && $this->nextActionPolicy === OperatorNextActionPolicy::None) { throw new InvalidArgumentException('Primary warning or danger badge specs must declare an operator next-action policy.'); } } /** * @return array */ public static function allowedColors(): array { return self::ALLOWED_COLORS; } public static function unknown(): self { return new self( label: 'Unknown', color: 'gray', icon: 'heroicon-m-question-mark-circle', iconColor: 'gray', ); } }