## Summary - keep `/admin/reviews/workspace` workspace-scoped in shell and sidebar context - treat `tenant` query hints on the customer review workspace as page-level filters only - update the customer review workspace tests and Spec 311 navigation contract to match the workspace-hub IA ## Testing - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `git diff --check` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #366
6.8 KiB
6.8 KiB
TenantPilot Architecture Guidelines
Status: 2026-05-15 Applies to: Laravel 12.52, PHP 8.4, Filament 5.2+, Livewire 4, PostgreSQL 16.
Target Architecture
TenantPilot should remain a Laravel monolith with explicit bounded modules, not a speculative framework. The architecture target is:
- Filament owns admin UI composition only.
- Domain/application services own Intune, backup, restore, audit, evidence, and permission behavior.
- Jobs own long-running or remote Graph work.
- Policies and gates own authorization.
- Models own persistence relationships, casts, scopes, and small invariants only.
- Migrations own data integrity through foreign keys, unique constraints, partial indexes, and JSONB where queryable.
This aligns with the constitution: heavy architecture is allowed for tenant isolation, RBAC, auditability, immutable history, queue correctness, credential safety, and compliance evidence; speculative generic layers are not.
Current Architecture Signals
Strong patterns already present:
GraphClientInterfaceis the required external Graph seam.UiEnforcementandWorkspaceUiEnforcementcentralize UI authorization behavior.OperationRunprovides observable queued operations.ProviderCredentialuses encrypted casts for credential payloads.- Workspace/tenant isolation migrations add non-null workspace ownership and composite constraints.
- Pest lanes and architecture/governance tests already exist.
High-risk drift:
- Large Filament classes concentrate UI, authorization, table configuration, modal logic, dispatching, notifications, and domain workflow glue in one place.
- Some resources use static
can*()methods instead of dedicated policies, making authorization harder to audit globally. - Historic JSON columns remain mixed with newer JSONB design.
Rules
- Business logic must not live directly in Filament table/header actions except trivial UI orchestration.
- Every action that creates, mutates, deletes, restores, retries, syncs, dispatches, or exports must call a service/action class or queued job.
- Every new resource-backed model needs a policy, or a documented exception in the feature spec.
- Every tenant-owned query must scope by workspace and managed environment before rendering or mutation.
- Graph calls must never happen during UI render. They must happen in services/jobs through
GraphClientInterface. - New abstractions require the constitution proportionality check unless they are security, audit, queue, or isolation-critical.
- Do not add generic provider frameworks until at least two real providers require the variation.
- Prefer extracted builders only when they reduce real review burden. Do not extract one-off schema fragments into a new layer just for style.
Refactoring Backlog
| Target | Problem | Recommendation | Priority | Effort | Risk if ignored |
|---|---|---|---|---|---|
ManagedEnvironmentOnboardingWizard |
5,748 LOC workflow page | Split into step schema builders, onboarding draft mutation service, and page-only orchestration. | P1 | L | High regression risk in onboarding and RBAC. |
ManagedEnvironmentResource |
3,785 LOC resource | Extract table columns/filters/actions and tenant-scoped domain actions. | P1 | L | Difficult safe review of destructive environment actions. |
RestoreRunResource |
2,779 LOC resource | Extract restore action builders and write-gate composition. | P1 | M | Restore safety logic becomes hard to audit. |
FindingResource |
2,503 LOC resource | Extract bulk exception/assignment workflows. | P2 | M | Slower feature work and fragile tests. |
BackupScheduleResource |
repeated run/retry/bulk closures | Extract StartBackupScheduleRunAction service. |
P1 | M | Duplicate authorization/audit behavior can drift. |
Preferred Code Patterns
Thin Filament Resource
use App\Filament\Resources\BackupScheduleResource\Actions\BackupScheduleActions;
use App\Filament\Resources\BackupScheduleResource\Schemas\BackupScheduleForm;
use App\Filament\Resources\BackupScheduleResource\Tables\BackupScheduleTable;
use App\Models\BackupSchedule;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
final class BackupScheduleResource extends Resource
{
protected static ?string $model = BackupSchedule::class;
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return BackupScheduleForm::configure($schema);
}
public static function table(Table $table): Table
{
return BackupScheduleTable::configure($table);
}
public static function makeRunNowAction(): Action
{
return BackupScheduleActions::runNow();
}
}
Service Action for Business Logic
namespace App\Actions\BackupSchedules;
use App\Jobs\RunBackupScheduleJob;
use App\Models\BackupSchedule;
use App\Models\User;
use App\Services\OperationRunService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
final class StartBackupScheduleRun
{
public function __construct(
private readonly OperationRunService $operationRuns,
) {}
public function handle(User $actor, BackupSchedule $schedule): int
{
Gate::forUser($actor)->authorize('run', $schedule);
return DB::transaction(function () use ($schedule, $actor): int {
$run = $this->operationRuns->startBackupScheduleRun($schedule, $actor);
RunBackupScheduleJob::dispatch($schedule->getKey(), $run->getKey())
->onQueue('graph');
return (int) $run->getKey();
});
}
}
Idempotent Job Skeleton
use App\Models\OperationRun;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
final class SyncManagedEnvironmentPoliciesJob implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public int $timeout = 300;
public function __construct(
private readonly int $operationRunId,
) {}
public function handle(): void
{
$run = DB::transaction(function (): OperationRun {
$run = OperationRun::query()->lockForUpdate()->findOrFail($this->operationRunId);
if ($run->isTerminal()) {
return $run;
}
$run->markRunning();
return $run;
});
if ($run->isTerminal()) {
return;
}
// Graph work happens here through GraphClientInterface-backed services.
}
}
Acceptance Standard for New Features
- Spec/plan/tasks exist when code changes runtime behavior.
- Resource/page logic remains UI-focused.
- Mutations have policy authorization, transaction boundaries where needed, audit logging, and tests.
- Remote work is queued and observable.
- Tenant/workspace isolation is proven by tests.
- PostgreSQL-specific behavior is covered in the PostgreSQL lane.