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

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:

  • 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

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.