TenantAtlas/app/Support/Baselines/BaselineReasonCodes.php
ahmido 1f0cc5de56 feat: implement operator explanation layer (#191)
## Summary
- add the shared operator explanation layer with explanation families, trustworthiness semantics, count descriptors, and centralized badge mappings
- adopt explanation-first rendering across baseline compare, governance operation run detail, baseline snapshot presentation, tenant review detail, and review register rows
- extend reason translation, artifact-truth presentation, fallback ops UX messaging, and focused regression coverage for operator explanation semantics

## Testing
- vendor/bin/sail bin pint --dirty --format agent
- vendor/bin/sail artisan test --compact tests/Feature/Monitoring/OperationsTenantScopeTest.php tests/Feature/Operations/OperationRunBlockedExecutionPresentationTest.php
- vendor/bin/sail artisan test --compact

## Notes
- Livewire v4 compatible
- panel provider registration remains in bootstrap/providers.php
- no destructive Filament actions were added or changed in this PR
- no new global-search behavior was introduced in this slice

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #191
2026-03-24 11:24:33 +00:00

143 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Baselines;
/**
* Stable reason codes for 422 precondition failures.
*
* These codes are returned in the response body when a baseline operation
* cannot start due to unmet preconditions. No OperationRun is created.
*/
final class BaselineReasonCodes
{
public const string CAPTURE_MISSING_SOURCE_TENANT = 'baseline.capture.missing_source_tenant';
public const string CAPTURE_PROFILE_NOT_ACTIVE = 'baseline.capture.profile_not_active';
public const string CAPTURE_ROLLOUT_DISABLED = 'baseline.capture.rollout_disabled';
public const string SNAPSHOT_BUILDING = 'baseline.snapshot.building';
public const string SNAPSHOT_INCOMPLETE = 'baseline.snapshot.incomplete';
public const string SNAPSHOT_SUPERSEDED = 'baseline.snapshot.superseded';
public const string SNAPSHOT_CAPTURE_FAILED = 'baseline.snapshot.capture_failed';
public const string SNAPSHOT_COMPLETION_PROOF_FAILED = 'baseline.snapshot.completion_proof_failed';
public const string SNAPSHOT_LEGACY_NO_PROOF = 'baseline.snapshot.legacy_no_proof';
public const string SNAPSHOT_LEGACY_CONTRADICTORY = 'baseline.snapshot.legacy_contradictory';
public const string COMPARE_NO_ASSIGNMENT = 'baseline.compare.no_assignment';
public const string COMPARE_PROFILE_NOT_ACTIVE = 'baseline.compare.profile_not_active';
public const string COMPARE_NO_ACTIVE_SNAPSHOT = 'baseline.compare.no_active_snapshot';
public const string COMPARE_NO_CONSUMABLE_SNAPSHOT = 'baseline.compare.no_consumable_snapshot';
public const string COMPARE_NO_ELIGIBLE_TARGET = 'baseline.compare.no_eligible_target';
public const string COMPARE_INVALID_SNAPSHOT = 'baseline.compare.invalid_snapshot';
public const string COMPARE_ROLLOUT_DISABLED = 'baseline.compare.rollout_disabled';
public const string COMPARE_SNAPSHOT_BUILDING = 'baseline.compare.snapshot_building';
public const string COMPARE_SNAPSHOT_INCOMPLETE = 'baseline.compare.snapshot_incomplete';
public const string COMPARE_SNAPSHOT_SUPERSEDED = 'baseline.compare.snapshot_superseded';
/**
* @return array<int, string>
*/
public static function all(): array
{
return [
self::CAPTURE_MISSING_SOURCE_TENANT,
self::CAPTURE_PROFILE_NOT_ACTIVE,
self::CAPTURE_ROLLOUT_DISABLED,
self::SNAPSHOT_BUILDING,
self::SNAPSHOT_INCOMPLETE,
self::SNAPSHOT_SUPERSEDED,
self::SNAPSHOT_CAPTURE_FAILED,
self::SNAPSHOT_COMPLETION_PROOF_FAILED,
self::SNAPSHOT_LEGACY_NO_PROOF,
self::SNAPSHOT_LEGACY_CONTRADICTORY,
self::COMPARE_NO_ASSIGNMENT,
self::COMPARE_PROFILE_NOT_ACTIVE,
self::COMPARE_NO_ACTIVE_SNAPSHOT,
self::COMPARE_NO_CONSUMABLE_SNAPSHOT,
self::COMPARE_NO_ELIGIBLE_TARGET,
self::COMPARE_INVALID_SNAPSHOT,
self::COMPARE_ROLLOUT_DISABLED,
self::COMPARE_SNAPSHOT_BUILDING,
self::COMPARE_SNAPSHOT_INCOMPLETE,
self::COMPARE_SNAPSHOT_SUPERSEDED,
];
}
public static function isKnown(?string $reasonCode): bool
{
return is_string($reasonCode) && in_array(trim($reasonCode), self::all(), true);
}
public static function trustImpact(?string $reasonCode): ?string
{
return match (trim((string) $reasonCode)) {
self::SNAPSHOT_CAPTURE_FAILED => 'limited_confidence',
self::COMPARE_ROLLOUT_DISABLED,
self::CAPTURE_ROLLOUT_DISABLED,
self::SNAPSHOT_BUILDING,
self::SNAPSHOT_INCOMPLETE,
self::SNAPSHOT_SUPERSEDED,
self::SNAPSHOT_COMPLETION_PROOF_FAILED,
self::SNAPSHOT_LEGACY_NO_PROOF,
self::SNAPSHOT_LEGACY_CONTRADICTORY,
self::COMPARE_NO_ASSIGNMENT,
self::COMPARE_PROFILE_NOT_ACTIVE,
self::COMPARE_NO_ACTIVE_SNAPSHOT,
self::COMPARE_NO_CONSUMABLE_SNAPSHOT,
self::COMPARE_NO_ELIGIBLE_TARGET,
self::COMPARE_INVALID_SNAPSHOT,
self::COMPARE_SNAPSHOT_BUILDING,
self::COMPARE_SNAPSHOT_INCOMPLETE,
self::COMPARE_SNAPSHOT_SUPERSEDED,
self::CAPTURE_MISSING_SOURCE_TENANT,
self::CAPTURE_PROFILE_NOT_ACTIVE => 'unusable',
default => null,
};
}
public static function absencePattern(?string $reasonCode): ?string
{
return match (trim((string) $reasonCode)) {
self::SNAPSHOT_BUILDING,
self::SNAPSHOT_INCOMPLETE,
self::SNAPSHOT_COMPLETION_PROOF_FAILED,
self::SNAPSHOT_LEGACY_NO_PROOF,
self::SNAPSHOT_LEGACY_CONTRADICTORY,
self::COMPARE_NO_ACTIVE_SNAPSHOT,
self::COMPARE_NO_CONSUMABLE_SNAPSHOT,
self::COMPARE_SNAPSHOT_BUILDING,
self::COMPARE_SNAPSHOT_INCOMPLETE => 'missing_input',
self::CAPTURE_MISSING_SOURCE_TENANT,
self::CAPTURE_PROFILE_NOT_ACTIVE,
self::CAPTURE_ROLLOUT_DISABLED,
self::COMPARE_NO_ASSIGNMENT,
self::COMPARE_PROFILE_NOT_ACTIVE,
self::COMPARE_NO_ELIGIBLE_TARGET,
self::COMPARE_INVALID_SNAPSHOT,
self::COMPARE_ROLLOUT_DISABLED,
self::SNAPSHOT_SUPERSEDED,
self::COMPARE_SNAPSHOT_SUPERSEDED => 'blocked_prerequisite',
self::SNAPSHOT_CAPTURE_FAILED => 'unavailable',
default => null,
};
}
}