, * diagnostic_label?: string|null, * notes: string * }>> */ private const ENTRIES = [ 'governance_artifact_existence' => [ 'not_created' => [ 'axis' => 'artifact_existence', 'label' => 'Not created yet', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['No artifact'], 'notes' => 'The intended artifact has not been produced yet.', ], 'historical_only' => [ 'axis' => 'artifact_existence', 'label' => 'Historical artifact', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Historical only'], 'notes' => 'The artifact remains readable for history but is no longer the current working artifact.', ], 'created' => [ 'axis' => 'artifact_existence', 'label' => 'Artifact available', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Created'], 'notes' => 'The intended artifact exists and can be inspected.', ], 'created_but_not_usable' => [ 'axis' => 'artifact_existence', 'label' => 'Artifact not usable', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Created but not usable'], 'notes' => 'The artifact record exists, but the operator cannot safely rely on it for the primary task.', ], ], 'governance_artifact_content' => [ 'trusted' => [ 'axis' => 'data_coverage', 'label' => 'Trustworthy artifact', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Trusted'], 'notes' => 'The artifact content is fit for the primary operator workflow.', ], 'partial' => [ 'axis' => 'data_coverage', 'label' => 'Partial', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Partially complete'], 'notes' => 'The artifact exists but key content is incomplete.', ], 'missing_input' => [ 'axis' => 'data_coverage', 'label' => 'Missing input', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Missing'], 'notes' => 'The artifact is blocked by missing upstream inputs.', ], 'metadata_only' => [ 'axis' => 'evidence_depth', 'label' => 'Metadata only', 'color' => 'info', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Metadata-only'], 'notes' => 'Only metadata is available. This is diagnostic context and should not replace the primary truth state.', ], 'reference_only' => [ 'axis' => 'evidence_depth', 'label' => 'Reference only', 'color' => 'info', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Reference-only'], 'notes' => 'Only reference placeholders are available. This is diagnostic context and should not replace the primary truth state.', ], 'empty' => [ 'axis' => 'data_coverage', 'label' => 'Empty snapshot', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Empty'], 'notes' => 'The artifact exists but captured no usable content.', ], 'unsupported' => [ 'axis' => 'product_support_maturity', 'label' => 'Support limited', 'color' => 'gray', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Unsupported'], 'notes' => 'The product is representing the source with limited fidelity. This remains diagnostic unless a stronger truth dimension applies.', ], ], 'governance_artifact_freshness' => [ 'current' => [ 'axis' => 'data_freshness', 'label' => 'Current', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Fresh'], 'notes' => 'The available artifact is current enough for the primary task.', ], 'stale' => [ 'axis' => 'data_freshness', 'label' => 'Stale', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Refresh recommended'], 'notes' => 'The artifact exists but should be refreshed before relying on it.', ], 'unknown' => [ 'axis' => 'data_freshness', 'label' => 'Freshness unknown', 'color' => 'gray', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Unknown'], 'notes' => 'The system cannot determine freshness from the available payload.', ], ], 'governance_artifact_publication_readiness' => [ 'not_applicable' => [ 'axis' => 'publication_readiness', 'label' => 'Not applicable', 'color' => 'gray', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['N/A'], 'notes' => 'Publication readiness does not apply to this artifact family.', ], 'internal_only' => [ 'axis' => 'publication_readiness', 'label' => 'Internal only', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Draft'], 'notes' => 'The artifact is useful internally but not ready for stakeholder delivery.', ], 'publishable' => [ 'axis' => 'publication_readiness', 'label' => 'Publishable', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Ready'], 'notes' => 'The artifact is ready for stakeholder publication or export.', ], 'blocked' => [ 'axis' => 'publication_readiness', 'label' => 'Blocked', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Not publishable'], 'notes' => 'The artifact exists but is blocked from publication or export.', ], ], 'governance_artifact_actionability' => [ 'none' => [ 'axis' => 'operator_actionability', 'label' => 'No action needed', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['No follow-up'], 'notes' => 'The current non-green state is informational only and does not require action.', ], 'optional' => [ 'axis' => 'operator_actionability', 'label' => 'Review recommended', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Optional follow-up'], 'notes' => 'The artifact can be used, but the operator should review the follow-up guidance.', ], 'required' => [ 'axis' => 'operator_actionability', 'label' => 'Action required', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Required follow-up'], 'notes' => 'The artifact cannot be trusted for the primary task until an operator addresses the issue.', ], ], 'baseline_snapshot_lifecycle' => [ 'building' => [ 'axis' => 'execution_lifecycle', 'label' => 'Building', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['In progress'], 'notes' => 'The snapshot row exists, but completion proof has not finished yet.', ], 'complete' => [ 'axis' => 'data_coverage', 'label' => 'Complete', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Ready'], 'notes' => 'The snapshot passed completion proof and is eligible for compare.', ], 'incomplete' => [ 'axis' => 'data_coverage', 'label' => 'Incomplete', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Partial'], 'notes' => 'The snapshot exists but did not finish cleanly and is not usable for compare.', ], 'superseded' => [ 'axis' => 'data_freshness', 'label' => 'Superseded', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Historical'], 'notes' => 'A newer complete snapshot is the effective current baseline truth.', ], ], 'operation_run_status' => [ 'queued' => [ 'axis' => 'execution_lifecycle', 'label' => 'Queued for execution', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Queued'], 'notes' => 'Execution is waiting for a worker to start the run.', ], 'running' => [ 'axis' => 'execution_lifecycle', 'label' => 'In progress', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Running'], 'notes' => 'Execution is currently running.', ], 'completed' => [ 'axis' => 'execution_lifecycle', 'label' => 'Run finished', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Completed'], 'notes' => 'Execution has reached a terminal state and the outcome badge carries the primary meaning.', ], ], 'operation_run_outcome' => [ 'pending' => [ 'axis' => 'execution_outcome', 'label' => 'Awaiting result', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Pending'], 'notes' => 'Execution has not produced a terminal outcome yet.', ], 'succeeded' => [ 'axis' => 'execution_outcome', 'label' => 'Completed successfully', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Succeeded'], 'notes' => 'The run finished without operator follow-up.', ], 'partially_succeeded' => [ 'axis' => 'execution_outcome', 'label' => 'Completed with follow-up', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Partially succeeded', 'Partial'], 'notes' => 'The run finished but needs operator review or cleanup.', ], 'blocked' => [ 'axis' => 'execution_outcome', 'label' => 'Blocked by prerequisite', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Blocked'], 'notes' => 'Execution could not start or continue until a prerequisite is fixed.', ], 'failed' => [ 'axis' => 'execution_outcome', 'label' => 'Execution failed', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Failed'], 'notes' => 'Execution ended unsuccessfully and needs operator attention.', ], 'cancelled' => [ 'axis' => 'execution_outcome', 'label' => 'Cancelled', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Cancelled'], 'notes' => 'Execution was intentionally stopped.', ], ], 'evidence_completeness' => [ 'complete' => [ 'axis' => 'data_coverage', 'label' => 'Coverage ready', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Complete'], 'notes' => 'Required evidence is present.', ], 'partial' => [ 'axis' => 'data_coverage', 'label' => 'Coverage incomplete', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Partial'], 'notes' => 'Some required evidence dimensions are still missing.', ], 'missing' => [ 'axis' => 'data_coverage', 'label' => 'Not collected yet', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Missing'], 'notes' => 'No evidence has been captured for this slice yet. This is not a failure by itself.', ], 'stale' => [ 'axis' => 'data_freshness', 'label' => 'Refresh recommended', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Stale'], 'notes' => 'Evidence exists but is old enough that the operator should refresh it before relying on it.', ], ], 'tenant_review_completeness' => [ 'complete' => [ 'axis' => 'data_coverage', 'label' => 'Review inputs ready', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Complete'], 'notes' => 'The review has the evidence inputs it needs.', ], 'partial' => [ 'axis' => 'data_coverage', 'label' => 'Review inputs incomplete', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Partial'], 'notes' => 'Some review sections still need inputs.', ], 'missing' => [ 'axis' => 'data_coverage', 'label' => 'Review input pending', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Missing'], 'notes' => 'The review has not been anchored to usable evidence yet.', ], 'stale' => [ 'axis' => 'data_freshness', 'label' => 'Refresh review inputs', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Stale'], 'notes' => 'The review input exists but should be refreshed before stakeholder use.', ], ], 'baseline_snapshot_fidelity' => [ 'full' => [ 'axis' => 'evidence_depth', 'label' => 'Detailed evidence', 'color' => 'success', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Full'], 'notes' => 'Full structured evidence detail is available.', ], 'partial' => [ 'axis' => 'evidence_depth', 'label' => 'Mixed evidence detail', 'color' => 'info', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Partial'], 'notes' => 'Some items have full detail while others are metadata-only.', ], 'reference_only' => [ 'axis' => 'evidence_depth', 'label' => 'Metadata only', 'color' => 'info', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Reference only'], 'notes' => 'Only reference metadata is available for this capture.', ], 'unsupported' => [ 'axis' => 'product_support_maturity', 'label' => 'Support limited', 'color' => 'gray', 'classification' => 'diagnostic', 'next_action_policy' => 'none', 'legacy_aliases' => ['Unsupported'], 'diagnostic_label' => 'Fallback renderer', 'notes' => 'The renderer fell back to a lower-fidelity representation. This is diagnostic context, not a governance gap.', ], ], 'baseline_snapshot_gap_status' => [ 'clear' => [ 'axis' => 'data_coverage', 'label' => 'No follow-up needed', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['No gaps'], 'notes' => 'The captured group does not contain unresolved coverage gaps.', ], 'gaps_present' => [ 'axis' => 'data_coverage', 'label' => 'Coverage gaps need review', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Gaps present'], 'notes' => 'The captured group has unresolved gaps that should be reviewed.', ], ], 'restore_run_status' => [ 'draft' => [ 'axis' => 'execution_lifecycle', 'label' => 'Draft', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Draft'], 'notes' => 'The restore run has not been prepared yet.', ], 'scoped' => [ 'axis' => 'execution_lifecycle', 'label' => 'Scope selected', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Scoped'], 'notes' => 'Items were selected for restore.', ], 'checked' => [ 'axis' => 'execution_lifecycle', 'label' => 'Checks complete', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Checked'], 'notes' => 'Safety checks were completed for this run.', ], 'previewed' => [ 'axis' => 'execution_lifecycle', 'label' => 'Preview ready', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Previewed'], 'notes' => 'A dry-run preview is available for review.', ], 'pending' => [ 'axis' => 'execution_lifecycle', 'label' => 'Pending execution', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Pending'], 'notes' => 'Execution has not been queued yet.', ], 'queued' => [ 'axis' => 'execution_lifecycle', 'label' => 'Queued for execution', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Queued'], 'notes' => 'Execution is queued and waiting for a worker.', ], 'running' => [ 'axis' => 'execution_lifecycle', 'label' => 'Applying restore', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Running'], 'notes' => 'Execution is currently applying restore work.', ], 'completed' => [ 'axis' => 'execution_outcome', 'label' => 'Applied successfully', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Completed'], 'notes' => 'The restore run finished successfully.', ], 'partial' => [ 'axis' => 'execution_outcome', 'label' => 'Applied with follow-up', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Partial'], 'notes' => 'The restore run finished but needs follow-up on a subset of items.', ], 'failed' => [ 'axis' => 'execution_outcome', 'label' => 'Restore failed', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Failed'], 'notes' => 'The restore run did not complete successfully.', ], 'cancelled' => [ 'axis' => 'execution_outcome', 'label' => 'Cancelled', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Cancelled'], 'notes' => 'Execution was intentionally cancelled.', ], 'aborted' => [ 'axis' => 'execution_outcome', 'label' => 'Stopped early', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Aborted'], 'notes' => 'Execution stopped before the normal terminal path completed.', ], 'completed_with_errors' => [ 'axis' => 'execution_outcome', 'label' => 'Applied with follow-up', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Completed with errors'], 'notes' => 'Execution completed but still needs follow-up on failed items.', ], ], 'restore_result_status' => [ 'applied' => [ 'axis' => 'item_result', 'label' => 'Applied', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Applied'], 'notes' => 'The item was applied successfully.', ], 'dry_run' => [ 'axis' => 'item_result', 'label' => 'Preview only', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Dry run'], 'notes' => 'The item was only simulated and not applied.', ], 'mapped' => [ 'axis' => 'item_result', 'label' => 'Mapped to existing item', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Mapped'], 'notes' => 'The source item mapped to an existing target.', ], 'skipped' => [ 'axis' => 'item_result', 'label' => 'Not applied', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Skipped'], 'notes' => 'The item was intentionally not applied.', ], 'partial' => [ 'axis' => 'item_result', 'label' => 'Partially applied', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Partial'], 'notes' => 'The item only applied in part and needs review.', ], 'manual_required' => [ 'axis' => 'operator_actionability', 'label' => 'Manual follow-up needed', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Manual required'], 'notes' => 'The operator must handle this item manually.', ], 'failed' => [ 'axis' => 'item_result', 'label' => 'Apply failed', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Failed'], 'notes' => 'The item failed to apply.', ], ], 'restore_preview_decision' => [ 'created' => [ 'axis' => 'item_result', 'label' => 'Will create', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Created'], 'notes' => 'The preview plans to create a new target item.', ], 'created_copy' => [ 'axis' => 'item_result', 'label' => 'Will create copy', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Created copy'], 'notes' => 'The preview plans to create a copy and should be reviewed before execution.', ], 'mapped_existing' => [ 'axis' => 'item_result', 'label' => 'Will map existing', 'color' => 'info', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Mapped existing'], 'notes' => 'The preview plans to map this item to an existing target.', ], 'skipped' => [ 'axis' => 'item_result', 'label' => 'Will skip', 'color' => 'gray', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Skipped'], 'notes' => 'The preview plans to skip this item.', ], 'failed' => [ 'axis' => 'item_result', 'label' => 'Cannot apply', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Failed'], 'notes' => 'The preview could not produce a viable action for this item.', ], ], 'restore_check_severity' => [ 'blocking' => [ 'axis' => 'operator_actionability', 'label' => 'Fix before running', 'color' => 'danger', 'classification' => 'primary', 'next_action_policy' => 'required', 'legacy_aliases' => ['Blocking'], 'notes' => 'Execution should not proceed until this check is fixed.', ], 'warning' => [ 'axis' => 'operator_actionability', 'label' => 'Review before running', 'color' => 'warning', 'classification' => 'primary', 'next_action_policy' => 'optional', 'legacy_aliases' => ['Warning'], 'notes' => 'Execution may proceed, but the operator should review the warning first.', ], 'safe' => [ 'axis' => 'operator_actionability', 'label' => 'Ready to continue', 'color' => 'success', 'classification' => 'primary', 'next_action_policy' => 'none', 'legacy_aliases' => ['Safe'], 'notes' => 'No blocking issue was found for this check.', ], ], ]; /** * @return array{ * axis: OperatorSemanticAxis, * label: string, * color: string, * classification: OperatorStateClassification, * next_action_policy: OperatorNextActionPolicy, * legacy_aliases: list, * diagnostic_label: ?string, * notes: string * }|null */ public static function entry(BadgeDomain $domain, mixed $value): ?array { $state = BadgeCatalog::normalizeState($value); if ($state === null) { return null; } if ($domain === BadgeDomain::OperationRunOutcome && $state === 'operation.blocked') { $state = 'blocked'; } $entry = self::ENTRIES[$domain->value][$state] ?? null; if (! is_array($entry)) { return null; } return [ 'axis' => self::axisFrom($entry['axis']), 'label' => $entry['label'], 'color' => $entry['color'], 'classification' => self::classificationFrom($entry['classification']), 'next_action_policy' => self::nextActionPolicyFrom($entry['next_action_policy']), 'legacy_aliases' => $entry['legacy_aliases'], 'diagnostic_label' => $entry['diagnostic_label'] ?? null, 'notes' => $entry['notes'], ]; } /** * @return array, * diagnostic_label: ?string, * notes: string * }>> */ public static function all(): array { $entries = []; foreach (self::ENTRIES as $domain => $mappings) { foreach ($mappings as $state => $entry) { $entries[$domain][$state] = [ 'axis' => self::axisFrom($entry['axis']), 'label' => $entry['label'], 'color' => $entry['color'], 'classification' => self::classificationFrom($entry['classification']), 'next_action_policy' => self::nextActionPolicyFrom($entry['next_action_policy']), 'legacy_aliases' => $entry['legacy_aliases'], 'diagnostic_label' => $entry['diagnostic_label'] ?? null, 'notes' => $entry['notes'], ]; } } return $entries; } /** * @return list */ public static function curatedExamples(): array { return [ ['name' => 'Operation blocked by missing prerequisite', 'domain' => BadgeDomain::OperationRunOutcome, 'raw_value' => 'blocked'], ['name' => 'Artifact exists but is not usable', 'domain' => BadgeDomain::GovernanceArtifactExistence, 'raw_value' => 'created_but_not_usable'], ['name' => 'Artifact is trustworthy', 'domain' => BadgeDomain::GovernanceArtifactContent, 'raw_value' => 'trusted'], ['name' => 'Artifact is stale', 'domain' => BadgeDomain::GovernanceArtifactFreshness, 'raw_value' => 'stale'], ['name' => 'Artifact is publishable', 'domain' => BadgeDomain::GovernanceArtifactPublicationReadiness, 'raw_value' => 'publishable'], ['name' => 'Artifact requires action', 'domain' => BadgeDomain::GovernanceArtifactActionability, 'raw_value' => 'required'], ['name' => 'Operation completed with follow-up', 'domain' => BadgeDomain::OperationRunOutcome, 'raw_value' => 'partially_succeeded'], ['name' => 'Operation completed successfully', 'domain' => BadgeDomain::OperationRunOutcome, 'raw_value' => 'succeeded'], ['name' => 'Evidence not collected yet', 'domain' => BadgeDomain::EvidenceCompleteness, 'raw_value' => 'missing'], ['name' => 'Evidence refresh recommended', 'domain' => BadgeDomain::EvidenceCompleteness, 'raw_value' => 'stale'], ['name' => 'Review input pending', 'domain' => BadgeDomain::TenantReviewCompleteness, 'raw_value' => 'missing'], ['name' => 'Mixed evidence detail stays diagnostic', 'domain' => BadgeDomain::BaselineSnapshotFidelity, 'raw_value' => 'partial'], ['name' => 'Support limited stays diagnostic', 'domain' => BadgeDomain::BaselineSnapshotFidelity, 'raw_value' => 'unsupported'], ['name' => 'Coverage gaps need review', 'domain' => BadgeDomain::BaselineSnapshotGapStatus, 'raw_value' => 'gaps_present'], ['name' => 'Restore preview blocked by a check', 'domain' => BadgeDomain::RestoreCheckSeverity, 'raw_value' => 'blocking'], ['name' => 'Restore run applied with follow-up', 'domain' => BadgeDomain::RestoreRunStatus, 'raw_value' => 'completed_with_errors'], ['name' => 'Restore item requires manual follow-up', 'domain' => BadgeDomain::RestoreResultStatus, 'raw_value' => 'manual_required'], ]; } public static function spec( BadgeDomain $domain, mixed $value, ?string $icon = null, ?string $iconColor = null, ): ?BadgeSpec { $entry = self::entry($domain, $value); if ($entry === null) { return null; } return new BadgeSpec( label: $entry['label'], color: $entry['color'], icon: $icon, iconColor: $iconColor, semanticAxis: $entry['axis'], classification: $entry['classification'], nextActionPolicy: $entry['next_action_policy'], diagnosticLabel: $entry['diagnostic_label'], legacyAliases: $entry['legacy_aliases'], notes: $entry['notes'], ); } private static function axisFrom(string $value): OperatorSemanticAxis { return OperatorSemanticAxis::tryFrom($value) ?? throw new InvalidArgumentException("Unknown operator semantic axis [{$value}]."); } private static function classificationFrom(string $value): OperatorStateClassification { return OperatorStateClassification::tryFrom($value) ?? throw new InvalidArgumentException("Unknown operator state classification [{$value}]."); } private static function nextActionPolicyFrom(string $value): OperatorNextActionPolicy { return OperatorNextActionPolicy::tryFrom($value) ?? throw new InvalidArgumentException("Unknown operator next-action policy [{$value}]."); } }