'array', 'metadata' => 'array', 'assignments' => 'array', 'captured_at' => 'datetime', ]; public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } public function backupSet(): BelongsTo { return $this->belongsTo(BackupSet::class); } public function policy(): BelongsTo { return $this->belongsTo(Policy::class); } public function policyVersion(): BelongsTo { return $this->belongsTo(PolicyVersion::class); } // Assignment helpers public function getAssignmentCountAttribute(): int { return count($this->assignments ?? []); } public function hasAssignments(): bool { return ! empty($this->assignments); } public function getGroupIdsAttribute(): array { return collect($this->assignments ?? []) ->pluck('target.groupId') ->filter() ->unique() ->values() ->toArray(); } public function getScopeTagIdsAttribute(): array { return $this->metadata['scope_tag_ids'] ?? ['0']; } public function getScopeTagNamesAttribute(): array { return $this->metadata['scope_tag_names'] ?? ['Default']; } public function hasOrphanedAssignments(): bool { return $this->metadata['has_orphaned_assignments'] ?? false; } public function assignmentsFetchFailed(): bool { return $this->metadata['assignments_fetch_failed'] ?? false; } public function assignmentCaptureReason(): ?string { $reason = $this->metadata['assignment_capture_reason'] ?? null; return is_string($reason) && trim($reason) !== '' ? trim($reason) : null; } public function snapshotSource(): ?string { $source = $this->metadata['snapshot_source'] ?? $this->metadata['source'] ?? null; return is_string($source) && trim($source) !== '' ? trim($source) : null; } /** * @return list */ public function warningMessages(): array { $warnings = $this->metadata['warnings'] ?? []; if (! is_array($warnings)) { return []; } return collect($warnings) ->filter(fn (mixed $warning): bool => is_string($warning) && trim($warning) !== '') ->map(fn (string $warning): string => trim($warning)) ->values() ->all(); } public function integrityWarning(): ?string { $warning = $this->metadata['integrity_warning'] ?? null; return is_string($warning) && trim($warning) !== '' ? trim($warning) : null; } public function protectedPathsCount(): int { return max(0, (int) ($this->metadata['protected_paths_count'] ?? 0)); } public function hasCapturedPayload(): bool { return is_array($this->payload) && $this->payload !== []; } public function isFoundation(): bool { $types = array_column(config('tenantpilot.foundation_types', []), 'type'); return in_array($this->policy_type, $types, true); } public function resolvedDisplayName(): string { $metadata = $this->metadata ?? []; $payload = is_array($this->payload) ? $this->payload : []; $versionSnapshot = is_array($this->policyVersion?->snapshot) ? $this->policyVersion->snapshot : []; $name = $metadata['displayName'] ?? $metadata['display_name'] ?? $payload['displayName'] ?? $payload['name'] ?? $versionSnapshot['displayName'] ?? $versionSnapshot['name'] ?? $this->policy?->display_name ?? null; if (is_string($name) && $name !== '') { return $name; } return $this->policy_identifier; } // Scopes public function scopeWithAssignments($query) { return $query->whereNotNull('assignments') ->whereRaw('json_array_length(assignments) > 0'); } }