decisionFor($tenant); expect($decision->lifecycle)->toBe($expectedLifecycle) ->and($decision->canViewTenantSurface)->toBeTrue() ->and($decision->canSelectAsContext)->toBe($canSelectAsContext) ->and($decision->canOperate)->toBe($canOperate) ->and($decision->canArchive)->toBe($canArchive) ->and($decision->canRestore)->toBe($canRestore) ->and($decision->canResumeOnboarding)->toBe($canResumeOnboarding) ->and($decision->canReferenceInWorkspaceMonitoring)->toBeTrue(); })->with([ 'draft' => [ fn (): Tenant => Tenant::factory()->draft()->create(), TenantLifecycle::Draft, false, false, false, false, true, ], 'onboarding' => [ fn (): Tenant => Tenant::factory()->onboarding()->create(), TenantLifecycle::Onboarding, false, false, false, false, true, ], 'active' => [ fn (): Tenant => Tenant::factory()->active()->create(), TenantLifecycle::Active, true, true, true, false, false, ], 'archived' => [ fn (): Tenant => Tenant::factory()->archived()->create(), TenantLifecycle::Archived, false, false, false, true, false, ], ]); it('returns structured selector outcomes for active and non-active tenants', function ( \Closure $tenantFactory, bool $expectedAllowed, ?TenantOperabilityReasonCode $expectedReason, ): void { $tenant = $tenantFactory(); $outcome = app(TenantOperabilityService::class)->outcomeFor( tenant: $tenant, question: TenantOperabilityQuestion::SelectorEligibility, lane: TenantInteractionLane::StandardActiveOperating, ); expect($outcome->allowed)->toBe($expectedAllowed) ->and($outcome->reasonCode)->toBe($expectedReason); })->with([ 'active-selector-eligible' => [fn (): Tenant => Tenant::factory()->active()->create(), true, null], 'draft-selector-ineligible' => [fn (): Tenant => Tenant::factory()->draft()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle], 'onboarding-selector-ineligible' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle], 'archived-selector-ineligible' => [fn (): Tenant => Tenant::factory()->archived()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle], ]); it('keeps tenant-bound and canonical lanes viewable across all tenant lifecycles', function ( \Closure $tenantFactory, TenantInteractionLane $lane, ): void { $tenant = $tenantFactory(); $question = $lane === TenantInteractionLane::AdministrativeManagement ? TenantOperabilityQuestion::TenantBoundViewability : TenantOperabilityQuestion::CanonicalLinkedRecordViewability; $outcome = app(TenantOperabilityService::class)->outcomeFor( tenant: $tenant, question: $question, lane: $lane, ); expect($outcome->allowed)->toBeTrue() ->and($outcome->discoverable)->toBeTrue(); })->with([ 'draft-admin' => [fn (): Tenant => Tenant::factory()->draft()->create(), TenantInteractionLane::AdministrativeManagement], 'onboarding-admin' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), TenantInteractionLane::AdministrativeManagement], 'archived-admin' => [fn (): Tenant => Tenant::factory()->archived()->create(), TenantInteractionLane::AdministrativeManagement], 'onboarding-canonical' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), TenantInteractionLane::CanonicalWorkspaceRecord], 'archived-canonical' => [fn (): Tenant => Tenant::factory()->archived()->create(), TenantInteractionLane::CanonicalWorkspaceRecord], ]); it('returns wrong-lane reasons for questions asked in the wrong lane', function (): void { $tenant = Tenant::factory()->active()->create(); $outcome = app(TenantOperabilityService::class)->outcomeFor( tenant: $tenant, question: TenantOperabilityQuestion::ArchiveEligibility, lane: TenantInteractionLane::CanonicalWorkspaceRecord, ); expect($outcome->allowed)->toBeFalse() ->and($outcome->reasonCode)->toBe(TenantOperabilityReasonCode::WrongLane); }); it('returns lifecycle-safe primary management action keys', function ( \Closure $tenantFactory, ?string $expectedActionKey, ): void { $tenant = $tenantFactory(); expect(app(TenantOperabilityService::class)->primaryManagementActionKey($tenant)) ->toBe($expectedActionKey); })->with([ 'draft-primary' => [fn (): Tenant => Tenant::factory()->draft()->create(), null], 'onboarding-primary' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), null], 'active-primary' => [fn (): Tenant => Tenant::factory()->active()->create(), 'archive'], 'archived-primary' => [fn (): Tenant => Tenant::factory()->archived()->create(), 'restore'], ]); it('can prefer onboarding as the primary management action for draft-like tenants', function ( \Closure $tenantFactory, ): void { $tenant = $tenantFactory(); expect(app(TenantOperabilityService::class)->primaryManagementActionKey($tenant, preferOnboarding: true)) ->toBe('resume_onboarding'); })->with([ 'draft-prefers-onboarding' => [fn (): Tenant => Tenant::factory()->draft()->create()], 'onboarding-prefers-onboarding' => [fn (): Tenant => Tenant::factory()->onboarding()->create()], ]);