create([ 'tenant_id' => null, 'external_id' => 'platform', 'name' => 'Platform', ]); config()->set('tenantpilot.break_glass.enabled', true); config()->set('tenantpilot.break_glass.ttl_minutes', 15); config()->set('tenantpilot.support_access.max_ttl_minutes', 120); }); function spec276_resolver_platform_user(): PlatformUser { return PlatformUser::factory()->create([ 'capabilities' => [ PlatformCapabilities::ACCESS_SYSTEM_PANEL, PlatformCapabilities::DIRECTORY_VIEW, PlatformCapabilities::SUPPORT_ACCESS_MANAGE, PlatformCapabilities::USE_BREAK_GLASS, ], ]); } it('auto activates audit-view support access and dedupes overlapping requests from the same platform actor', function (): void { $workspace = Workspace::factory()->create(); $platformUser = spec276_resolver_platform_user(); $grant = app(SupportAccessGrantManager::class)->request( workspace: $workspace, actor: $platformUser, scope: SupportAccessGrant::SCOPE_AUDIT_VIEW, reason: 'Review support case audit history', ttlMinutes: 45, ); $duplicate = app(SupportAccessGrantManager::class)->request( workspace: $workspace, actor: $platformUser, scope: SupportAccessGrant::SCOPE_AUDIT_VIEW, reason: 'Review support case audit history again', ttlMinutes: 60, ); expect($grant->status)->toBe(SupportAccessGrant::STATUS_ACTIVE) ->and($grant->approval_mode)->toBe(SupportAccessGrant::APPROVAL_MODE_AUTO) ->and($grant->starts_at)->not->toBeNull() ->and($grant->expires_at)->not->toBeNull() ->and($duplicate->getKey())->toBe($grant->getKey()) ->and(SupportAccessGrant::query()->count())->toBe(1); expect(AuditLog::query() ->where('workspace_id', (int) $workspace->getKey()) ->whereIn('action', [ AuditActionId::SupportAccessRequested->value, AuditActionId::SupportAccessActivated->value, ]) ->count())->toBe(2); }); it('keeps workspace-recovery support access pending until a workspace owner approves it', function (): void { $workspace = Workspace::factory()->create(); $platformUser = spec276_resolver_platform_user(); $owner = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $owner->getKey(), 'role' => WorkspaceRole::Owner->value, ]); $grant = app(SupportAccessGrantManager::class)->request( workspace: $workspace, actor: $platformUser, scope: SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY, reason: 'Recover workspace ownership safely', ttlMinutes: 30, ); expect($grant->status)->toBe(SupportAccessGrant::STATUS_REQUESTED) ->and($grant->approval_mode)->toBe(SupportAccessGrant::APPROVAL_MODE_OWNER_REQUIRED) ->and($grant->starts_at)->toBeNull() ->and($grant->expires_at)->toBeNull(); $approved = app(SupportAccessGrantManager::class)->approve($grant, $owner); expect($approved->status)->toBe(SupportAccessGrant::STATUS_ACTIVE) ->and($approved->approved_by_user_id)->toBe((int) $owner->getKey()) ->and($approved->expires_at)->not->toBeNull() ->and(app(SupportAccessGrantResolver::class)->activeRecoveryGrantFor($workspace)?->getKey())->toBe($grant->getKey()); }); it('expires stale active grants before returning active support access', function (): void { $workspace = Workspace::factory()->create(); $grant = SupportAccessGrant::factory()->expired()->create([ 'workspace_id' => (int) $workspace->getKey(), 'scope' => SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY, ]); expect(app(SupportAccessGrantResolver::class)->activeRecoveryGrantFor($workspace))->toBeNull() ->and($grant->fresh()->status)->toBe(SupportAccessGrant::STATUS_EXPIRED); expect(AuditLog::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('action', AuditActionId::SupportAccessExpired->value) ->exists())->toBeTrue(); }); it('requires break-glass and a waiver reason for ownerless workspace recovery', function (): void { $workspace = Workspace::factory()->create(); $platformUser = spec276_resolver_platform_user(); expect(fn () => app(SupportAccessGrantManager::class)->request( workspace: $workspace, actor: $platformUser, scope: SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY, reason: 'Recover ownerless workspace', ttlMinutes: 30, waiverReason: 'No owners exist', ))->toThrow(ValidationException::class); $this->actingAs($platformUser, 'platform'); app(BreakGlassSession::class)->start($platformUser, 'Recover ownerless workspace'); $grant = app(SupportAccessGrantManager::class)->request( workspace: $workspace, actor: $platformUser, scope: SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY, reason: 'Recover ownerless workspace', ttlMinutes: 30, waiverReason: 'Validated no workspace owners remain', ); expect($grant->status)->toBe(SupportAccessGrant::STATUS_ACTIVE) ->and($grant->approval_mode)->toBe(SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER) ->and($grant->waiver_reason)->toBe('Validated no workspace owners remain'); expect(AuditLog::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('action', AuditActionId::SupportAccessOwnerlessWaiverUsed->value) ->whereJsonContains('metadata->waiver_reason', 'Validated no workspace owners remain') ->exists())->toBeTrue(); });