## 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
176 lines
6.5 KiB
PHP
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,
|
|
};
|
|
}
|
|
}
|