TenantAtlas/app/Services/Baselines/BaselineSnapshotTruthResolver.php
ahmido 8426741068 feat: add baseline snapshot truth guards (#189)
## Summary
- add explicit BaselineSnapshot lifecycle truth with conservative backfill and a shared truth resolver
- block baseline compare from building, incomplete, or superseded snapshots and align workspace/tenant UI truth surfaces with effective snapshot state
- surface artifact truth separately from operation outcome across baseline profile, snapshot, compare, and operation run pages

## Testing
- integrated browser smoke test on the active feature surfaces
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`
- targeted baseline lifecycle and compare guard coverage added in Pest
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4 compliance preserved
- no panel provider registration changes were needed; Laravel 12 providers remain in `bootstrap/providers.php`
- global search remains disabled for the affected baseline resources by design
- destructive actions remain confirmation-gated; capture and compare actions keep their existing authorization and confirmation behavior
- no new panel assets were added; existing deploy flow for `filament:assets` is unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #189
2026-03-23 11:32:00 +00:00

176 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Baselines;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Support\Baselines\BaselineReasonCodes;
use App\Support\Baselines\BaselineSnapshotLifecycleState;
final class BaselineSnapshotTruthResolver
{
public function resolveEffectiveSnapshot(BaselineProfile $profile): ?BaselineSnapshot
{
return BaselineSnapshot::query()
->where('workspace_id', (int) $profile->workspace_id)
->where('baseline_profile_id', (int) $profile->getKey())
->where('lifecycle_state', BaselineSnapshotLifecycleState::Complete->value)
->orderByDesc('completed_at')
->orderByDesc('captured_at')
->orderByDesc('id')
->first();
}
public function resolveLatestAttemptedSnapshot(BaselineProfile $profile): ?BaselineSnapshot
{
return BaselineSnapshot::query()
->where('workspace_id', (int) $profile->workspace_id)
->where('baseline_profile_id', (int) $profile->getKey())
->orderByDesc('captured_at')
->orderByDesc('id')
->first();
}
/**
* @return array{
* ok: bool,
* snapshot: ?BaselineSnapshot,
* effective_snapshot: ?BaselineSnapshot,
* latest_attempted_snapshot: ?BaselineSnapshot,
* reason_code: ?string
* }
*/
public function resolveCompareSnapshot(BaselineProfile $profile, ?BaselineSnapshot $explicitSnapshot = null): array
{
$effectiveSnapshot = $this->resolveEffectiveSnapshot($profile);
$latestAttemptedSnapshot = $this->resolveLatestAttemptedSnapshot($profile);
if ($explicitSnapshot instanceof BaselineSnapshot) {
if ((int) $explicitSnapshot->workspace_id !== (int) $profile->workspace_id
|| (int) $explicitSnapshot->baseline_profile_id !== (int) $profile->getKey()) {
return [
'ok' => false,
'snapshot' => null,
'effective_snapshot' => $effectiveSnapshot,
'latest_attempted_snapshot' => $latestAttemptedSnapshot,
'reason_code' => BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT,
];
}
$reasonCode = $this->compareBlockedReasonForSnapshot($explicitSnapshot, $effectiveSnapshot, explicitSelection: true);
return [
'ok' => $reasonCode === null,
'snapshot' => $reasonCode === null ? $explicitSnapshot : null,
'effective_snapshot' => $effectiveSnapshot,
'latest_attempted_snapshot' => $latestAttemptedSnapshot,
'reason_code' => $reasonCode,
];
}
if ($effectiveSnapshot instanceof BaselineSnapshot) {
return [
'ok' => true,
'snapshot' => $effectiveSnapshot,
'effective_snapshot' => $effectiveSnapshot,
'latest_attempted_snapshot' => $latestAttemptedSnapshot,
'reason_code' => null,
];
}
return [
'ok' => false,
'snapshot' => null,
'effective_snapshot' => null,
'latest_attempted_snapshot' => $latestAttemptedSnapshot,
'reason_code' => $this->profileBlockedReason($latestAttemptedSnapshot),
];
}
public function isHistoricallySuperseded(BaselineSnapshot $snapshot, ?BaselineSnapshot $effectiveSnapshot = null): bool
{
$effectiveSnapshot ??= BaselineSnapshot::query()
->where('workspace_id', (int) $snapshot->workspace_id)
->where('baseline_profile_id', (int) $snapshot->baseline_profile_id)
->where('lifecycle_state', BaselineSnapshotLifecycleState::Complete->value)
->orderByDesc('completed_at')
->orderByDesc('captured_at')
->orderByDesc('id')
->first();
if (! $effectiveSnapshot instanceof BaselineSnapshot) {
return false;
}
return $snapshot->isConsumable()
&& (int) $effectiveSnapshot->getKey() !== (int) $snapshot->getKey();
}
public function artifactReasonCode(BaselineSnapshot $snapshot, ?BaselineSnapshot $effectiveSnapshot = null): ?string
{
if (! $effectiveSnapshot instanceof BaselineSnapshot) {
$snapshot->loadMissing('baselineProfile');
$profile = $snapshot->baselineProfile;
if ($profile instanceof BaselineProfile) {
$effectiveSnapshot = $this->resolveEffectiveSnapshot($profile);
}
}
if ($snapshot->isBuilding()) {
return BaselineReasonCodes::SNAPSHOT_BUILDING;
}
if ($snapshot->isIncomplete()) {
$completionMeta = is_array($snapshot->completion_meta_jsonb) ? $snapshot->completion_meta_jsonb : [];
$reasonCode = $completionMeta['finalization_reason_code'] ?? null;
return is_string($reasonCode) && trim($reasonCode) !== ''
? trim($reasonCode)
: BaselineReasonCodes::SNAPSHOT_INCOMPLETE;
}
if ($this->isHistoricallySuperseded($snapshot, $effectiveSnapshot)) {
return BaselineReasonCodes::SNAPSHOT_SUPERSEDED;
}
return null;
}
private function compareBlockedReasonForSnapshot(
BaselineSnapshot $snapshot,
?BaselineSnapshot $effectiveSnapshot,
bool $explicitSelection,
): ?string {
if ($snapshot->isBuilding()) {
return BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING;
}
if ($snapshot->isIncomplete()) {
return BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE;
}
if (! $snapshot->isConsumable()) {
return BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT;
}
if ($explicitSelection && $effectiveSnapshot instanceof BaselineSnapshot && (int) $effectiveSnapshot->getKey() !== (int) $snapshot->getKey()) {
return BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED;
}
return null;
}
private function profileBlockedReason(?BaselineSnapshot $latestAttemptedSnapshot): string
{
return match (true) {
$latestAttemptedSnapshot?->isBuilding() === true => BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING,
$latestAttemptedSnapshot?->isIncomplete() === true => BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE,
default => BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT,
};
}
}