status)) { Finding::STATUS_RISK_ACCEPTED => 'accepted_risk', Finding::STATUS_RESOLVED, Finding::STATUS_CLOSED => 'historical', default => 'active', }; } public function resolveExceptionStatus(FindingException $exception, ?CarbonImmutable $now = null): string { $now ??= CarbonImmutable::instance(now()); $status = (string) $exception->status; if (in_array($status, [ FindingException::STATUS_REJECTED, FindingException::STATUS_REVOKED, FindingException::STATUS_SUPERSEDED, ], true)) { return $status; } if ($status === FindingException::STATUS_PENDING) { return FindingException::STATUS_PENDING; } $expiresAt = $exception->expires_at instanceof Carbon ? CarbonImmutable::instance($exception->expires_at) : null; if ($expiresAt instanceof CarbonImmutable && $expiresAt->lessThanOrEqualTo($now)) { return FindingException::STATUS_EXPIRED; } if ($this->isExpiring($exception, $now)) { return FindingException::STATUS_EXPIRING; } return FindingException::STATUS_ACTIVE; } public function resolveValidityState(FindingException $exception, ?CarbonImmutable $now = null): string { if ($exception->isPendingRenewal()) { return $this->resolveApprovedValidityState($exception, $now); } return match ($this->resolveExceptionStatus($exception, $now)) { FindingException::STATUS_ACTIVE => FindingException::VALIDITY_VALID, FindingException::STATUS_EXPIRING => FindingException::VALIDITY_EXPIRING, FindingException::STATUS_EXPIRED => FindingException::VALIDITY_EXPIRED, FindingException::STATUS_REVOKED => FindingException::VALIDITY_REVOKED, FindingException::STATUS_REJECTED => FindingException::VALIDITY_REJECTED, default => FindingException::VALIDITY_MISSING_SUPPORT, }; } public function resolveFindingState(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string { $exception ??= $finding->relationLoaded('findingException') ? $finding->findingException : $finding->findingException()->first(); $findingIsRiskAccepted = $finding->isRiskAccepted(); if (! $exception instanceof FindingException) { return $findingIsRiskAccepted ? 'risk_accepted_without_valid_exception' : 'ungoverned'; } if (! $findingIsRiskAccepted) { return $exception->isPending() ? 'pending_exception' : 'ungoverned'; } if ($exception->isPendingRenewal()) { return match ($this->resolveApprovedValidityState($exception, $now)) { FindingException::VALIDITY_VALID => 'valid_exception', FindingException::VALIDITY_EXPIRING => 'expiring_exception', FindingException::VALIDITY_EXPIRED => 'expired_exception', default => 'pending_exception', }; } return match ($this->resolveExceptionStatus($exception, $now)) { FindingException::STATUS_PENDING => 'pending_exception', FindingException::STATUS_ACTIVE => 'valid_exception', FindingException::STATUS_EXPIRING => 'expiring_exception', FindingException::STATUS_EXPIRED => 'expired_exception', FindingException::STATUS_REVOKED => 'revoked_exception', FindingException::STATUS_REJECTED => 'rejected_exception', default => $findingIsRiskAccepted ? 'risk_accepted_without_valid_exception' : 'ungoverned', }; } public function isValidGovernedAcceptedRisk(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): bool { return in_array($this->resolveFindingState($finding, $exception, $now), [ 'valid_exception', 'expiring_exception', ], true); } public function resolveGovernanceValidity(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string { $exception ??= $finding->relationLoaded('findingException') ? $finding->findingException : $finding->findingException()->first(); if ($exception instanceof FindingException) { return $this->resolveValidityState($exception, $now); } return $finding->isRiskAccepted() ? FindingException::VALIDITY_MISSING_SUPPORT : null; } public function resolveGovernanceAttention(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string { if (! $finding->isRiskAccepted()) { return 'not_applicable'; } return match ($this->resolveGovernanceValidity($finding, $exception, $now)) { FindingException::VALIDITY_VALID => 'healthy', FindingException::VALIDITY_EXPIRING, FindingException::VALIDITY_EXPIRED, FindingException::VALIDITY_REVOKED, FindingException::VALIDITY_REJECTED, FindingException::VALIDITY_MISSING_SUPPORT => 'attention_needed', default => 'attention_needed', }; } public function requiresGovernanceAttention(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): bool { return $this->resolveGovernanceAttention($finding, $exception, $now) === 'attention_needed'; } public function resolveWarningMessage(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string { $exception ??= $finding->relationLoaded('findingException') ? $finding->findingException : $finding->findingException()->first(); if (! $exception instanceof FindingException) { return $finding->isRiskAccepted() ? 'This finding is marked as accepted risk without a valid exception record.' : null; } $exceptionStatus = $exception->isPendingRenewal() ? match ($this->resolveApprovedValidityState($exception, $now)) { FindingException::VALIDITY_EXPIRED => FindingException::STATUS_EXPIRED, FindingException::VALIDITY_EXPIRING => FindingException::STATUS_EXPIRING, FindingException::VALIDITY_VALID => FindingException::STATUS_ACTIVE, default => FindingException::STATUS_PENDING, } : $this->resolveExceptionStatus($exception, $now); if ($finding->isRiskAccepted()) { return match ($this->resolveFindingState($finding, $exception, $now)) { 'risk_accepted_without_valid_exception' => 'This finding is marked as accepted risk without a valid exception record.', 'expiring_exception' => 'The linked exception is still valid, but it is nearing expiry and needs review.', 'expired_exception' => 'The linked exception has expired and no longer governs accepted risk.', 'revoked_exception' => 'The linked exception was revoked and no longer governs accepted risk.', 'rejected_exception' => 'The linked exception was rejected and does not govern accepted risk.', default => null, }; } if ($exception->requiresFreshDecisionForFinding($finding)) { return 'This finding changed after the earlier exception decision; a fresh decision is required.'; } return match ($exceptionStatus) { FindingException::STATUS_EXPIRING => 'The linked exception is nearing expiry and needs review.', FindingException::STATUS_EXPIRED => 'The linked exception has expired and no longer governs accepted risk.', FindingException::STATUS_REVOKED => 'The linked exception was revoked and no longer governs accepted risk.', FindingException::STATUS_REJECTED => 'The linked exception was rejected and does not govern accepted risk.', default => null, }; } public function resolveHistoricalContext(Finding $finding): ?string { return match ((string) $finding->status) { Finding::STATUS_RESOLVED => $this->resolvedHistoricalContext($finding), Finding::STATUS_CLOSED => $this->closedHistoricalContext($finding), default => null, }; } public function resolvePrimaryNarrative(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string { return match ($this->resolveWorkflowFamily($finding)) { 'accepted_risk' => $this->resolveGovernanceAttention($finding, $exception, $now) === 'healthy' ? 'Accepted risk remains visible because current governance is still valid.' : 'Accepted risk is still on record, but governance follow-up is needed before it can be treated as safe to ignore.', 'historical' => match ((string) $finding->status) { Finding::STATUS_RESOLVED => 'Resolved is a historical workflow state. It does not prove the issue is permanently gone.', Finding::STATUS_CLOSED => 'Closed is a historical workflow state. It does not prove the issue is permanently gone.', default => 'This finding is historical workflow context.', }, default => 'This finding is still active workflow work and should be reviewed until it is resolved, closed, or formally governed.', }; } public function resolvePrimaryNextAction(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string { if ($finding->hasOpenStatus() && $finding->due_at?->isPast() === true) { return 'Review the overdue finding and update ownership or next workflow step.'; } if ($this->resolveWorkflowFamily($finding) === 'accepted_risk') { return match ($this->resolveGovernanceValidity($finding, $exception, $now)) { FindingException::VALIDITY_VALID => 'Keep the exception under review until remediation is possible.', FindingException::VALIDITY_EXPIRING => 'Renew or review the exception before the current governance window lapses.', FindingException::VALIDITY_EXPIRED, FindingException::VALIDITY_REVOKED, FindingException::VALIDITY_REJECTED, FindingException::VALIDITY_MISSING_SUPPORT => 'Restore valid governance or move the finding back into active remediation.', default => 'Review the current governance state before treating this accepted risk as stable.', }; } if ((string) $finding->status === Finding::STATUS_RESOLVED || (string) $finding->status === Finding::STATUS_CLOSED) { return 'Review the closure context and reopen the finding if the issue has returned or governance has lapsed.'; } if ($finding->assignee_user_id === null || $finding->owner_user_id === null) { return 'Assign an owner and next workflow step so follow-up does not stall.'; } return 'Review the current workflow state and decide whether to progress, resolve, close, or request governance coverage.'; } public function syncExceptionState(FindingException $exception, ?CarbonImmutable $now = null): FindingException { $resolvedStatus = $this->resolveExceptionStatus($exception, $now); $resolvedValidityState = $this->resolveValidityState($exception, $now); if ((string) $exception->status === $resolvedStatus && (string) $exception->current_validity_state === $resolvedValidityState) { return $exception; } $exception->forceFill([ 'status' => $resolvedStatus, 'current_validity_state' => $resolvedValidityState, ])->save(); return $exception->refresh(); } private function resolveApprovedValidityState(FindingException $exception, ?CarbonImmutable $now = null): string { $now ??= CarbonImmutable::instance(now()); $expiresAt = $this->renewalAwareDate( $exception, 'previous_expires_at', $exception->expires_at, ); if ($expiresAt instanceof CarbonImmutable && $expiresAt->lessThanOrEqualTo($now)) { return FindingException::VALIDITY_EXPIRED; } if ($this->isExpiring($exception, $now, renewalAware: true)) { return FindingException::VALIDITY_EXPIRING; } return FindingException::VALIDITY_VALID; } private function isExpiring(FindingException $exception, CarbonImmutable $now, bool $renewalAware = false): bool { $reviewDueAt = $renewalAware ? $this->renewalAwareDate($exception, 'previous_review_due_at', $exception->review_due_at) : ($exception->review_due_at instanceof Carbon ? CarbonImmutable::instance($exception->review_due_at) : null); if ($reviewDueAt instanceof CarbonImmutable && $reviewDueAt->lessThanOrEqualTo($now)) { return true; } $expiresAt = $renewalAware ? $this->renewalAwareDate($exception, 'previous_expires_at', $exception->expires_at) : ($exception->expires_at instanceof Carbon ? CarbonImmutable::instance($exception->expires_at) : null); if (! $expiresAt instanceof CarbonImmutable) { return false; } return $expiresAt->lessThanOrEqualTo($now->addDays(7)); } private function renewalAwareDate(FindingException $exception, string $metadataKey, mixed $fallback): ?CarbonImmutable { $currentDecision = $exception->relationLoaded('currentDecision') ? $exception->currentDecision : $exception->currentDecision()->first(); if ($currentDecision instanceof FindingExceptionDecision && is_string($currentDecision->metadata[$metadataKey] ?? null)) { return CarbonImmutable::parse((string) $currentDecision->metadata[$metadataKey]); } return $fallback instanceof Carbon ? CarbonImmutable::instance($fallback) : null; } private function resolvedHistoricalContext(Finding $finding): ?string { $reason = (string) ($finding->resolved_reason ?? ''); return match ($reason) { 'no_longer_drifting' => 'The latest compare did not reproduce the earlier drift, but treat this as the last observed workflow outcome rather than a permanent guarantee.', 'permission_granted', 'permission_removed_from_registry', 'role_assignment_removed', 'ga_count_within_threshold' => 'The last observed workflow reason suggests the triggering condition was no longer present, but this remains historical context.', default => 'Resolved records the workflow outcome. Review the reason and latest evidence before treating it as technical proof.', }; } private function closedHistoricalContext(Finding $finding): ?string { return match ((string) ($finding->closed_reason ?? '')) { 'accepted_risk' => 'Closed reflects workflow handling. Governance validity still determines whether accepted risk remains safe to rely on.', default => 'Closed records the workflow outcome. Review the recorded reason before treating it as technical proof.', }; } }