TenantAtlas/app/Support/Baselines/BaselineCompareSummaryAssessment.php
ahmido 02e75e1cda feat: harden baseline compare summary trust surfaces (#196)
## Summary
- add a shared baseline compare summary assessment and assessor for compact trust propagation
- harden dashboard, landing, and banner baseline compare surfaces against false all-clear claims
- add focused Pest coverage for dashboard, landing, banner, reason translation, and canonical detail parity

## Validation
- vendor/bin/sail bin pint --dirty --format agent
- vendor/bin/sail artisan test --compact tests/Feature/Baselines/BaselineCompareSummaryAssessmentTest.php tests/Feature/Baselines/BaselineCompareExplanationFallbackTest.php tests/Feature/Filament/BaselineCompareNowWidgetTest.php tests/Feature/Filament/NeedsAttentionWidgetTest.php tests/Feature/Filament/BaselineCompareExplanationSurfaceTest.php tests/Feature/Filament/BaselineCompareLandingWhyNoFindingsTest.php tests/Feature/Filament/BaselineCompareCoverageBannerTest.php tests/Feature/Filament/BaselineCompareSummaryConsistencyTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php tests/Feature/ReasonTranslation/ReasonTranslationExplanationTest.php

## Notes
- Livewire compliance: Filament v5 / Livewire v4 stack unchanged
- Provider registration: unchanged, Laravel 12 providers remain in bootstrap/providers.php
- Global search: no searchable resource behavior changed
- Destructive actions: none introduced by this change
- Assets: no new assets registered; existing deploy process remains unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #196
2026-03-27 00:19:53 +00:00

151 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Baselines;
use InvalidArgumentException;
final readonly class BaselineCompareSummaryAssessment
{
public const string STATE_POSITIVE = 'positive';
public const string STATE_CAUTION = 'caution';
public const string STATE_STALE = 'stale';
public const string STATE_ACTION_REQUIRED = 'action_required';
public const string STATE_UNAVAILABLE = 'unavailable';
public const string STATE_IN_PROGRESS = 'in_progress';
public const string EVIDENCE_NONE = 'none';
public const string EVIDENCE_COVERAGE_WARNING = 'coverage_warning';
public const string EVIDENCE_EVIDENCE_GAP = 'evidence_gap';
public const string EVIDENCE_STALE_RESULT = 'stale_result';
public const string EVIDENCE_SUPPRESSED_OUTPUT = 'suppressed_output';
public const string EVIDENCE_UNAVAILABLE = 'unavailable';
public const string NEXT_TARGET_LANDING = 'landing';
public const string NEXT_TARGET_FINDINGS = 'findings';
public const string NEXT_TARGET_RUN = 'run';
public const string NEXT_TARGET_NONE = 'none';
/**
* @param array{label: string, target: string} $nextAction
*/
public function __construct(
public string $stateFamily,
public string $headline,
public ?string $supportingMessage,
public string $tone,
public bool $positiveClaimAllowed,
public string $trustworthinessLevel,
public string $evaluationResult,
public string $evidenceImpact,
public int $findingsVisibleCount,
public int $highSeverityCount,
public array $nextAction,
public ?string $lastComparedLabel = null,
public ?string $reasonCode = null,
) {
if (! in_array($this->stateFamily, [
self::STATE_POSITIVE,
self::STATE_CAUTION,
self::STATE_STALE,
self::STATE_ACTION_REQUIRED,
self::STATE_UNAVAILABLE,
self::STATE_IN_PROGRESS,
], true)) {
throw new InvalidArgumentException('Unsupported baseline summary state family: '.$this->stateFamily);
}
if (trim($this->headline) === '') {
throw new InvalidArgumentException('Baseline summary assessments require a headline.');
}
if (! in_array($this->evidenceImpact, [
self::EVIDENCE_NONE,
self::EVIDENCE_COVERAGE_WARNING,
self::EVIDENCE_EVIDENCE_GAP,
self::EVIDENCE_STALE_RESULT,
self::EVIDENCE_SUPPRESSED_OUTPUT,
self::EVIDENCE_UNAVAILABLE,
], true)) {
throw new InvalidArgumentException('Unsupported baseline summary evidence impact: '.$this->evidenceImpact);
}
if (! in_array($this->nextAction['target'] ?? null, [
self::NEXT_TARGET_LANDING,
self::NEXT_TARGET_FINDINGS,
self::NEXT_TARGET_RUN,
self::NEXT_TARGET_NONE,
], true)) {
throw new InvalidArgumentException('Unsupported baseline summary next-action target.');
}
if (trim((string) ($this->nextAction['label'] ?? '')) === '') {
throw new InvalidArgumentException('Baseline summary assessments require a next-action label.');
}
if ($this->positiveClaimAllowed && $this->stateFamily !== self::STATE_POSITIVE) {
throw new InvalidArgumentException('Positive claim eligibility must resolve to the positive summary state.');
}
}
public function nextActionLabel(): string
{
return $this->nextAction['label'];
}
public function nextActionTarget(): string
{
return $this->nextAction['target'];
}
/**
* @return array{
* stateFamily: string,
* headline: string,
* supportingMessage: ?string,
* tone: string,
* positiveClaimAllowed: bool,
* trustworthinessLevel: string,
* evaluationResult: string,
* evidenceImpact: string,
* findingsVisibleCount: int,
* highSeverityCount: int,
* nextAction: array{label: string, target: string},
* lastComparedLabel: ?string,
* reasonCode: ?string
* }
*/
public function toArray(): array
{
return [
'stateFamily' => $this->stateFamily,
'headline' => $this->headline,
'supportingMessage' => $this->supportingMessage,
'tone' => $this->tone,
'positiveClaimAllowed' => $this->positiveClaimAllowed,
'trustworthinessLevel' => $this->trustworthinessLevel,
'evaluationResult' => $this->evaluationResult,
'evidenceImpact' => $this->evidenceImpact,
'findingsVisibleCount' => $this->findingsVisibleCount,
'highSeverityCount' => $this->highSeverityCount,
'nextAction' => $this->nextAction,
'lastComparedLabel' => $this->lastComparedLabel,
'reasonCode' => $this->reasonCode,
];
}
}