TenantAtlas/apps/platform/app/Models/BaselineProfile.php
ahmido be314c577f Spec 400: rebuild Tenantial homepage visuals (#387)
## Summary
- rebuild the public Tenantial homepage around an evidence-first Microsoft tenant governance narrative
- replace the old hero visual with a new static dashboard preview and add dedicated Trust Bar and Feature Pillars sections
- update the shared public shell, navigation, footer, dark design tokens, assets, and homepage content to match the new brand direction
- align website smoke coverage and Spec 400 artifacts with the rebuilt homepage

## Testing
- not run in this pass
- updated website smoke specs under apps/website/tests/smoke

## Note
- `website-dev` was pushed to `origin` so the requested PR base exists remotely
- the remote `website-dev` branch is an ancestor of `origin/dev`, so this PR may also show upstream `dev` history relative to that base

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #387
2026-05-18 14:38:11 +00:00

191 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Baselines\BaselineProfileStatus;
use App\Support\Baselines\BaselineScope;
use App\Support\Baselines\BaselineSnapshotLifecycleState;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use InvalidArgumentException;
use JsonException;
class BaselineProfile extends Model
{
use HasFactory;
/** @var list<string> */
protected $fillable = [
'workspace_id',
'name',
'description',
'version_label',
'status',
'capture_mode',
'scope_jsonb',
'active_snapshot_id',
'created_by_user_id',
];
/**
* @return array<string, mixed>
*/
protected function casts(): array
{
return [
'status' => BaselineProfileStatus::class,
'capture_mode' => BaselineCaptureMode::class,
];
}
protected function scopeJsonb(): Attribute
{
return Attribute::make(
get: function (mixed $value): array {
try {
return $this->normalizedScopeFrom($value)->toJsonb();
} catch (InvalidArgumentException) {
return $this->decodeScopeJsonb($value) ?? ['policy_types' => [], 'foundation_types' => []];
}
},
set: function (mixed $value): string {
$scope = BaselineScope::fromJsonb(is_array($value) ? $value : null)->toStoredJsonb();
try {
return json_encode($scope, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return '{"version":2,"entries":[]}';
}
},
);
}
public function normalizedScope(): BaselineScope
{
return $this->normalizedScopeFrom($this->getRawOriginal('scope_jsonb'));
}
/**
* @return array<string, mixed>|null
*/
public function rawScopeJsonb(): ?array
{
return $this->decodeScopeJsonb($this->getRawOriginal('scope_jsonb'));
}
/**
* @return array{version: 2, entries: list<array{domain_key: string, subject_class: string, subject_type_keys: list<string>, filters: array<string, mixed>}>}
*/
public function canonicalScopeJsonb(): array
{
return $this->normalizedScope()->toStoredJsonb();
}
public function requiresScopeSaveForward(): bool
{
return (bool) $this->normalizedScope()->normalizationLineage()['save_forward_required'];
}
public function rewriteScopeToCanonicalV2(): bool
{
$this->scope_jsonb = $this->canonicalScopeJsonb();
if (! $this->isDirty('scope_jsonb')) {
return false;
}
return $this->save();
}
/**
* Decode raw scope_jsonb value from the database.
*
* @return array<string, mixed>|null
*/
private function decodeScopeJsonb(mixed $value): ?array
{
if (is_array($value)) {
return $value;
}
if (is_string($value) && $value !== '') {
try {
$decoded = json_decode($value, true, flags: JSON_THROW_ON_ERROR);
return is_array($decoded) ? $decoded : null;
} catch (JsonException) {
return null;
}
}
return null;
}
private function normalizedScopeFrom(mixed $value): BaselineScope
{
return BaselineScope::fromJsonb($this->decodeScopeJsonb($value));
}
public function workspace(): BelongsTo
{
return $this->belongsTo(Workspace::class);
}
public function createdByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by_user_id');
}
public function activeSnapshot(): BelongsTo
{
return $this->belongsTo(BaselineSnapshot::class, 'active_snapshot_id');
}
public function snapshots(): HasMany
{
return $this->hasMany(BaselineSnapshot::class);
}
public function resolveCurrentConsumableSnapshot(): ?BaselineSnapshot
{
$activeSnapshot = $this->relationLoaded('activeSnapshot')
? $this->getRelation('activeSnapshot')
: $this->activeSnapshot()->first();
if ($activeSnapshot instanceof BaselineSnapshot && $activeSnapshot->isConsumable()) {
return $activeSnapshot;
}
return $this->snapshots()
->where('lifecycle_state', BaselineSnapshotLifecycleState::Complete->value)
->orderByDesc('completed_at')
->orderByDesc('captured_at')
->orderByDesc('id')
->first();
}
public function resolveLatestAttemptedSnapshot(): ?BaselineSnapshot
{
return $this->snapshots()
->orderByDesc('captured_at')
->orderByDesc('id')
->first();
}
public function hasConsumableSnapshot(): bool
{
return $this->resolveCurrentConsumableSnapshot() instanceof BaselineSnapshot;
}
public function tenantAssignments(): HasMany
{
return $this->hasMany(BaselineTenantAssignment::class);
}
}