TenantAtlas/docs/architecture-guidelines.md
ahmido bf43dad3d1 fix: enforce workspace surface scope for customer review workspace (#366)
## 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
2026-05-15 20:52:37 +00:00

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.