TenantAtlas/app/Support/Baselines/BaselineCompareSummaryAssessor.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

286 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Baselines;
use App\Support\Ui\OperatorExplanation\OperatorExplanationPattern;
use App\Support\Ui\OperatorExplanation\TrustworthinessLevel;
use Carbon\CarbonImmutable;
final class BaselineCompareSummaryAssessor
{
private const int STALE_AFTER_DAYS = 7;
public function assess(BaselineCompareStats $stats): BaselineCompareSummaryAssessment
{
$explanation = $stats->operatorExplanation();
$findingsVisibleCount = (int) ($stats->findingsCount ?? 0);
$highSeverityCount = (int) ($stats->severityCounts['high'] ?? 0);
$reasonCode = is_string($stats->reasonCode) ? BaselineCompareReasonCode::tryFrom($stats->reasonCode) : null;
$evaluationResult = $stats->state === 'failed'
? 'failed_result'
: $explanation->evaluationResult;
$positiveClaimAllowed = $this->positiveClaimAllowed($stats, $explanation, $reasonCode, $evaluationResult);
$isStale = $this->hasStaleResult($stats, $evaluationResult);
$stateFamily = $this->stateFamily($stats, $findingsVisibleCount, $positiveClaimAllowed, $isStale);
return new BaselineCompareSummaryAssessment(
stateFamily: $stateFamily,
headline: $this->headline($stats, $stateFamily, $findingsVisibleCount, $highSeverityCount, $evaluationResult),
supportingMessage: $this->supportingMessage($stats, $stateFamily, $findingsVisibleCount, $evaluationResult),
tone: $this->tone($stats, $stateFamily),
positiveClaimAllowed: $positiveClaimAllowed,
trustworthinessLevel: $explanation->trustworthinessLevel->value,
evaluationResult: $evaluationResult,
evidenceImpact: $this->evidenceImpact($stats, $evaluationResult, $isStale),
findingsVisibleCount: $findingsVisibleCount,
highSeverityCount: $highSeverityCount,
nextAction: $this->nextAction($stats, $stateFamily, $findingsVisibleCount, $evaluationResult),
lastComparedLabel: $stats->lastComparedHuman,
reasonCode: $stats->reasonCode,
);
}
private function positiveClaimAllowed(
BaselineCompareStats $stats,
OperatorExplanationPattern $explanation,
?BaselineCompareReasonCode $reasonCode,
string $evaluationResult,
): bool {
if ($stats->state !== 'ready') {
return false;
}
if ((int) ($stats->findingsCount ?? 0) > 0) {
return false;
}
if ($evaluationResult !== 'no_result') {
return false;
}
if ($explanation->trustworthinessLevel !== TrustworthinessLevel::Trustworthy) {
return false;
}
if (in_array($stats->coverageStatus, ['warning', 'unproven'], true)) {
return false;
}
if ((int) ($stats->evidenceGapsCount ?? 0) > 0) {
return false;
}
if ($this->hasStaleResult($stats, $evaluationResult)) {
return false;
}
if ($stats->reasonCode === null) {
return true;
}
return $reasonCode?->supportsPositiveClaim() ?? false;
}
private function stateFamily(
BaselineCompareStats $stats,
int $findingsVisibleCount,
bool $positiveClaimAllowed,
bool $isStale,
): string {
return match (true) {
$stats->state === 'comparing' => BaselineCompareSummaryAssessment::STATE_IN_PROGRESS,
$stats->state === 'failed',
$findingsVisibleCount > 0 => BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED,
in_array($stats->state, ['no_tenant', 'no_assignment', 'no_snapshot', 'idle'], true) => BaselineCompareSummaryAssessment::STATE_UNAVAILABLE,
$isStale => BaselineCompareSummaryAssessment::STATE_STALE,
$positiveClaimAllowed => BaselineCompareSummaryAssessment::STATE_POSITIVE,
default => BaselineCompareSummaryAssessment::STATE_CAUTION,
};
}
private function evidenceImpact(BaselineCompareStats $stats, string $evaluationResult, bool $isStale): string
{
if (in_array($stats->state, ['no_tenant', 'no_assignment', 'no_snapshot', 'idle', 'failed'], true)) {
return BaselineCompareSummaryAssessment::EVIDENCE_UNAVAILABLE;
}
if ($isStale) {
return BaselineCompareSummaryAssessment::EVIDENCE_STALE_RESULT;
}
if ($evaluationResult === 'suppressed_result') {
return BaselineCompareSummaryAssessment::EVIDENCE_SUPPRESSED_OUTPUT;
}
if ((int) ($stats->evidenceGapsCount ?? 0) > 0) {
return BaselineCompareSummaryAssessment::EVIDENCE_EVIDENCE_GAP;
}
if (in_array($stats->coverageStatus, ['warning', 'unproven'], true)) {
return BaselineCompareSummaryAssessment::EVIDENCE_COVERAGE_WARNING;
}
return BaselineCompareSummaryAssessment::EVIDENCE_NONE;
}
private function headline(
BaselineCompareStats $stats,
string $stateFamily,
int $findingsVisibleCount,
int $highSeverityCount,
string $evaluationResult,
): string {
return match ($stateFamily) {
BaselineCompareSummaryAssessment::STATE_POSITIVE => 'No confirmed drift in the latest baseline compare.',
BaselineCompareSummaryAssessment::STATE_CAUTION => match (true) {
$evaluationResult === 'suppressed_result' => 'The last compare finished, but normal result output was suppressed.',
(int) ($stats->evidenceGapsCount ?? 0) > 0 => 'No confirmed drift is visible, but evidence gaps still limit this result.',
in_array($stats->coverageStatus, ['warning', 'unproven'], true) => 'No confirmed drift is visible, but coverage limits this compare.',
default => 'The latest compare result needs caution before you treat it as an all-clear.',
},
BaselineCompareSummaryAssessment::STATE_STALE => 'The latest baseline compare result is stale.',
BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => match (true) {
$stats->state === 'failed' || $evaluationResult === 'failed_result' => 'The latest baseline compare failed before it produced a usable result.',
$highSeverityCount > 0 => sprintf('%d high-severity drift finding%s need review.', $highSeverityCount, $highSeverityCount === 1 ? '' : 's'),
default => sprintf('%d open drift finding%s need review.', $findingsVisibleCount, $findingsVisibleCount === 1 ? '' : 's'),
},
BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'Baseline compare is in progress.',
default => match ($stats->state) {
'no_assignment' => 'This tenant does not have an assigned baseline yet.',
'no_snapshot' => 'The current baseline snapshot is not available for compare.',
'idle' => 'A current baseline compare result is not available yet.',
default => 'A usable baseline compare result is not currently available.',
},
};
}
private function supportingMessage(
BaselineCompareStats $stats,
string $stateFamily,
int $findingsVisibleCount,
string $evaluationResult,
): ?string {
return match ($stateFamily) {
BaselineCompareSummaryAssessment::STATE_POSITIVE => $stats->lastComparedHuman !== null
? 'Last compared '.$stats->lastComparedHuman.'.'
: 'The latest compare result is trustworthy enough to treat zero findings as current.',
BaselineCompareSummaryAssessment::STATE_CAUTION => match (true) {
$evaluationResult === 'suppressed_result' => 'Review the run detail before treating zero visible findings as complete.',
(int) ($stats->evidenceGapsCount ?? 0) > 0 => 'Review the compare detail to see which evidence gaps still limit trust.',
in_array($stats->coverageStatus, ['warning', 'unproven'], true) => 'Coverage warnings mean zero visible findings are not an all-clear on their own.',
default => $stats->reasonMessage ?? $stats->message,
},
BaselineCompareSummaryAssessment::STATE_STALE => $stats->lastComparedHuman !== null
? 'Last compared '.$stats->lastComparedHuman.'. Refresh compare before relying on this posture.'
: 'Refresh compare before relying on this posture.',
BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => match (true) {
$stats->state === 'failed' => $stats->failureReason,
$findingsVisibleCount > 0 => 'Open findings remain on this tenant and need review.',
default => $stats->message,
},
BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'Current counts are diagnostic only until the compare run finishes.',
default => $stats->message,
};
}
private function tone(BaselineCompareStats $stats, string $stateFamily): string
{
return match ($stateFamily) {
BaselineCompareSummaryAssessment::STATE_POSITIVE => 'success',
BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => 'danger',
BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'info',
BaselineCompareSummaryAssessment::STATE_UNAVAILABLE => $stats->state === 'no_snapshot' ? 'warning' : 'gray',
default => 'warning',
};
}
/**
* @return array{label: string, target: string}
*/
private function nextAction(
BaselineCompareStats $stats,
string $stateFamily,
int $findingsVisibleCount,
string $evaluationResult,
): array {
if ($findingsVisibleCount > 0) {
return [
'label' => 'Open findings',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS,
];
}
return match ($stateFamily) {
BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => [
'label' => $evaluationResult === 'failed_result' ? 'Review the failed run' : 'Review compare detail',
'target' => $stats->operationRunId !== null
? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
: BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
BaselineCompareSummaryAssessment::STATE_CAUTION => [
'label' => 'Review compare detail',
'target' => $stats->operationRunId !== null
? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
: BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
BaselineCompareSummaryAssessment::STATE_STALE => [
'label' => 'Open Baseline Compare',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => [
'label' => $stats->operationRunId !== null ? 'View run' : 'Open Baseline Compare',
'target' => $stats->operationRunId !== null
? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
: BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
BaselineCompareSummaryAssessment::STATE_UNAVAILABLE => match ($stats->state) {
'no_assignment' => [
'label' => 'Assign a baseline first',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
],
'no_snapshot' => [
'label' => 'Review baseline prerequisites',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
'idle' => [
'label' => 'Open Baseline Compare',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
],
default => [
'label' => 'Review compare availability',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
],
},
default => [
'label' => 'No action needed',
'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
],
};
}
private function hasStaleResult(BaselineCompareStats $stats, string $evaluationResult): bool
{
if ($stats->state !== 'ready') {
return false;
}
if ($stats->lastComparedIso === null) {
return false;
}
if (! in_array($evaluationResult, ['full_result', 'no_result', 'incomplete_result', 'suppressed_result'], true)) {
return false;
}
try {
$lastComparedAt = CarbonImmutable::parse($stats->lastComparedIso);
} catch (\Throwable) {
return false;
}
return $lastComparedAt->lt(now()->subDays(self::STALE_AFTER_DAYS));
}
}