## 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
178 lines
6.8 KiB
Markdown
178 lines
6.8 KiB
Markdown
# 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:
|
|
|
|
- `GraphClientInterface` is the required external Graph seam.
|
|
- `UiEnforcement` and `WorkspaceUiEnforcement` centralize UI authorization behavior.
|
|
- `OperationRun` provides observable queued operations.
|
|
- `ProviderCredential` uses 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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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.
|