diff --git a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
index b2475cdee5..9e7a9d11a9 100644
--- a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
+++ b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php
@@ -1,435 +1,466 @@
 <?php
 
 final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
 
   /**
    * Verify that any user can view an object with POLICY_PUBLIC.
    */
   public function testPublicPolicyEnabled() {
     $env = PhabricatorEnv::beginScopedEnv();
     $env->overrideEnvConfig('policy.allow-public', true);
 
     $this->expectVisibility(
       $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
       array(
         'public'  => true,
         'user'    => true,
         'admin'   => true,
       ),
       pht('Public Policy (Enabled in Config)'));
   }
 
 
   /**
    * Verify that POLICY_PUBLIC is interpreted as POLICY_USER when public
    * policies are disallowed.
    */
   public function testPublicPolicyDisabled() {
     $env = PhabricatorEnv::beginScopedEnv();
     $env->overrideEnvConfig('policy.allow-public', false);
 
     $this->expectVisibility(
       $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
       array(
         'public'  => false,
         'user'    => true,
         'admin'   => true,
       ),
       pht('Public Policy (Disabled in Config)'));
   }
 
 
   /**
    * Verify that any logged-in user can view an object with POLICY_USER, but
    * logged-out users can not.
    */
   public function testUsersPolicy() {
     $this->expectVisibility(
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       array(
         'public'  => false,
         'user'    => true,
         'admin'   => true,
       ),
       pht('User Policy'));
   }
 
 
   /**
    * Verify that only administrators can view an object with POLICY_ADMIN.
    */
   public function testAdminPolicy() {
     $this->expectVisibility(
       $this->buildObject(PhabricatorPolicies::POLICY_ADMIN),
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => true,
       ),
       pht('Admin Policy'));
   }
 
 
   /**
    * Verify that no one can view an object with POLICY_NOONE.
    */
   public function testNoOnePolicy() {
     $this->expectVisibility(
       $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => false,
       ),
       pht('No One Policy'));
   }
 
 
   /**
    * Test offset-based filtering.
    */
   public function testOffsets() {
     $results = array(
       $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
       $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
       $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
     );
 
     $query = new PhabricatorPolicyAwareTestQuery();
     $query->setResults($results);
     $query->setViewer($this->buildUser('user'));
 
     $this->assertEqual(
       3,
       count($query->setLimit(3)->setOffset(0)->execute()),
       pht('Invisible objects are ignored.'));
 
     $this->assertEqual(
       0,
       count($query->setLimit(3)->setOffset(3)->execute()),
       pht('Offset pages through visible objects only.'));
 
     $this->assertEqual(
       2,
       count($query->setLimit(3)->setOffset(1)->execute()),
       pht('Offsets work correctly.'));
 
     $this->assertEqual(
       2,
       count($query->setLimit(0)->setOffset(1)->execute()),
       pht('Offset with no limit works.'));
   }
 
 
   /**
    * Test limits.
    */
   public function testLimits() {
     $results = array(
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
       $this->buildObject(PhabricatorPolicies::POLICY_USER),
     );
 
     $query = new PhabricatorPolicyAwareTestQuery();
     $query->setResults($results);
     $query->setViewer($this->buildUser('user'));
 
     $this->assertEqual(
       3,
       count($query->setLimit(3)->setOffset(0)->execute()),
       pht('Limits work.'));
 
     $this->assertEqual(
       2,
       count($query->setLimit(3)->setOffset(4)->execute()),
       pht('Limit + offset work.'));
   }
 
 
   /**
    * Test that omnipotent users bypass policies.
    */
   public function testOmnipotence() {
     $results = array(
       $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
     );
 
     $query = new PhabricatorPolicyAwareTestQuery();
     $query->setResults($results);
     $query->setViewer(PhabricatorUser::getOmnipotentUser());
 
     $this->assertEqual(
       1,
       count($query->execute()));
   }
 
 
   /**
    * Test that invalid policies reject viewers of all types.
    */
   public function testRejectInvalidPolicy() {
     $invalid_policy = 'the duck goes quack';
     $object = $this->buildObject($invalid_policy);
 
     $this->expectVisibility(
       $object = $this->buildObject($invalid_policy),
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => false,
       ),
       pht('Invalid Policy'));
   }
 
 
   /**
    * Test that extended policies work.
    */
   public function testExtendedPolicies() {
     $object = $this->buildObject(PhabricatorPolicies::POLICY_USER)
       ->setPHID('PHID-TEST-1');
 
     $this->expectVisibility(
       $object,
       array(
         'public'  => false,
         'user'    => true,
         'admin'   => true,
       ),
       pht('No Extended Policy'));
 
     // Add a restrictive extended policy.
     $extended = $this->buildObject(PhabricatorPolicies::POLICY_ADMIN)
       ->setPHID('PHID-TEST-2');
     $object->setExtendedPolicies(
       array(
         PhabricatorPolicyCapability::CAN_VIEW => array(
           array($extended, PhabricatorPolicyCapability::CAN_VIEW),
         ),
       ));
 
     $this->expectVisibility(
       $object,
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => true,
       ),
       pht('With Extended Policy'));
 
     // Depend on a different capability.
     $object->setExtendedPolicies(
       array(
         PhabricatorPolicyCapability::CAN_VIEW => array(
           array($extended, PhabricatorPolicyCapability::CAN_EDIT),
         ),
       ));
 
     $extended->setCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT));
     $extended->setPolicies(
       array(
         PhabricatorPolicyCapability::CAN_EDIT =>
           PhabricatorPolicies::POLICY_NOONE,
       ));
 
     $this->expectVisibility(
       $object,
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => false,
       ),
       pht('With Extended Policy + Edit'));
   }
 
 
   /**
    * Test that cyclic extended policies are arrested properly.
    */
   public function testExtendedPolicyCycles() {
     $object = $this->buildObject(PhabricatorPolicies::POLICY_USER)
       ->setPHID('PHID-TEST-1');
 
     $this->expectVisibility(
       $object,
       array(
         'public'  => false,
         'user'    => true,
         'admin'   => true,
       ),
       pht('No Extended Policy'));
 
     // Set a self-referential extended policy on the object. This should
     // make it fail all policy checks.
     $object->setExtendedPolicies(
       array(
         PhabricatorPolicyCapability::CAN_VIEW => array(
           array($object, PhabricatorPolicyCapability::CAN_VIEW),
         ),
       ));
 
     $this->expectVisibility(
       $object,
       array(
         'public'  => false,
         'user'    => false,
         'admin'   => false,
       ),
       pht('Extended Policy with Cycle'));
   }
 
   /**
    * An omnipotent user should be able to see even objects with invalid
    * policies.
    */
   public function testInvalidPolicyVisibleByOmnipotentUser() {
     $invalid_policy = 'the cow goes moo';
     $object = $this->buildObject($invalid_policy);
 
     $results = array(
       $object,
     );
 
     $query = new PhabricatorPolicyAwareTestQuery();
     $query->setResults($results);
     $query->setViewer(PhabricatorUser::getOmnipotentUser());
 
     $this->assertEqual(
       1,
       count($query->execute()));
   }
 
   public function testAllQueriesBelongToActualApplications() {
     $queries = id(new PhutilSymbolLoader())
       ->setAncestorClass('PhabricatorPolicyAwareQuery')
       ->loadObjects();
 
     foreach ($queries as $qclass => $query) {
       $class = $query->getQueryApplicationClass();
       if (!$class) {
         continue;
       }
       $this->assertTrue(
         (bool)PhabricatorApplication::getByClass($class),
         pht(
           "Application class '%s' for query '%s'.",
           $class,
           $qclass));
     }
   }
 
   public function testMultipleCapabilities() {
     $object = new PhabricatorPolicyTestObject();
     $object->setCapabilities(
       array(
         PhabricatorPolicyCapability::CAN_VIEW,
         PhabricatorPolicyCapability::CAN_EDIT,
       ));
     $object->setPolicies(
       array(
         PhabricatorPolicyCapability::CAN_VIEW
           => PhabricatorPolicies::POLICY_USER,
         PhabricatorPolicyCapability::CAN_EDIT
           => PhabricatorPolicies::POLICY_NOONE,
       ));
 
     $filter = new PhabricatorPolicyFilter();
     $filter->requireCapabilities(
       array(
         PhabricatorPolicyCapability::CAN_VIEW,
         PhabricatorPolicyCapability::CAN_EDIT,
       ));
     $filter->setViewer($this->buildUser('user'));
 
     $result = $filter->apply(array($object));
 
     $this->assertEqual(array(), $result);
   }
 
+  public function testPolicyStrength() {
+    $public = PhabricatorPolicyQuery::getGlobalPolicy(
+      PhabricatorPolicies::POLICY_PUBLIC);
+    $user = PhabricatorPolicyQuery::getGlobalPolicy(
+      PhabricatorPolicies::POLICY_USER);
+    $admin = PhabricatorPolicyQuery::getGlobalPolicy(
+      PhabricatorPolicies::POLICY_ADMIN);
+    $noone = PhabricatorPolicyQuery::getGlobalPolicy(
+      PhabricatorPolicies::POLICY_NOONE);
+
+    $this->assertFalse($public->isStrongerThan($public));
+    $this->assertFalse($public->isStrongerThan($user));
+    $this->assertFalse($public->isStrongerThan($admin));
+    $this->assertFalse($public->isStrongerThan($noone));
+
+    $this->assertTrue($user->isStrongerThan($public));
+    $this->assertFalse($user->isStrongerThan($user));
+    $this->assertFalse($user->isStrongerThan($admin));
+    $this->assertFalse($user->isStrongerThan($noone));
+
+    $this->assertTrue($admin->isStrongerThan($public));
+    $this->assertTrue($admin->isStrongerThan($user));
+    $this->assertFalse($admin->isStrongerThan($admin));
+    $this->assertFalse($admin->isStrongerThan($noone));
+
+    $this->assertTrue($noone->isStrongerThan($public));
+    $this->assertTrue($noone->isStrongerThan($user));
+    $this->assertTrue($noone->isStrongerThan($admin));
+    $this->assertFalse($admin->isStrongerThan($noone));
+  }
+
 
   /**
    * Test an object for visibility across multiple user specifications.
    */
   private function expectVisibility(
     PhabricatorPolicyTestObject $object,
     array $map,
     $description) {
 
     foreach ($map as $spec => $expect) {
       $viewer = $this->buildUser($spec);
 
       $query = new PhabricatorPolicyAwareTestQuery();
       $query->setResults(array($object));
       $query->setViewer($viewer);
 
       $caught = null;
       $result = null;
       try {
         $result = $query->executeOne();
       } catch (PhabricatorPolicyException $ex) {
         $caught = $ex;
       }
 
       if ($expect) {
         $this->assertEqual(
           $object,
           $result,
           pht('%s with user %s should succeed.', $description, $spec));
       } else {
         $this->assertTrue(
           $caught instanceof PhabricatorPolicyException,
           pht('%s with user %s should fail.', $description, $spec));
       }
     }
   }
 
 
   /**
    * Build a test object to spec.
    */
   private function buildObject($policy) {
     $object = new PhabricatorPolicyTestObject();
     $object->setCapabilities(
       array(
         PhabricatorPolicyCapability::CAN_VIEW,
       ));
     $object->setPolicies(
       array(
         PhabricatorPolicyCapability::CAN_VIEW => $policy,
       ));
 
     return $object;
   }
 
 
   /**
    * Build a test user to spec.
    */
   private function buildUser($spec) {
     $user = new PhabricatorUser();
 
     switch ($spec) {
       case 'public':
         break;
       case 'user':
         $user->setPHID(1);
         break;
       case 'admin':
         $user->setPHID(1);
         $user->setIsAdmin(true);
         break;
       default:
         throw new Exception(pht("Unknown user spec '%s'.", $spec));
     }
 
     return $user;
   }
 
 }
diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php
index 4224319d9c..46dfad3f57 100644
--- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php
+++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php
@@ -1,112 +1,183 @@
 <?php
 
 final class PhabricatorPolicyExplainController
   extends PhabricatorPolicyController {
 
   private $phid;
   private $capability;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function willProcessRequest(array $data) {
     $this->phid = $data['phid'];
     $this->capability = $data['capability'];
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $phid = $this->phid;
     $capability = $this->capability;
 
     $object = id(new PhabricatorObjectQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($phid))
       ->executeOne();
     if (!$object) {
       return new Aphront404Response();
     }
 
     $policies = PhabricatorPolicyQuery::loadPolicies(
       $viewer,
       $object);
 
     $policy = idx($policies, $capability);
     if (!$policy) {
       return new Aphront404Response();
     }
 
     $handle = id(new PhabricatorHandleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($phid))
       ->executeOne();
     $object_uri = nonempty($handle->getURI(), '/');
 
     $explanation = PhabricatorPolicy::getPolicyExplanation(
       $viewer,
       $policy->getPHID());
 
     $auto_info = (array)$object->describeAutomaticCapability($capability);
 
     $auto_info = array_merge(
       array($explanation),
       $auto_info);
     $auto_info = array_filter($auto_info);
 
     foreach ($auto_info as $key => $info) {
       $auto_info[$key] = phutil_tag('li', array(), $info);
     }
     if ($auto_info) {
       $auto_info = phutil_tag('ul', array(), $auto_info);
     }
 
     $capability_name = $capability;
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     if ($capobj) {
       $capability_name = $capobj->getCapabilityName();
     }
 
-    $space_info = null;
-    if ($object instanceof PhabricatorSpacesInterface) {
-      if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
-        $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
-          $object);
-
-        $handles = $viewer->loadHandles(array($space_phid));
+    $dialog = id(new AphrontDialogView())
+      ->setUser($viewer)
+      ->setClass('aphront-access-dialog');
 
-        $space_info = array(
-          pht(
-            'This object is in %s, and can only be seen by users with '.
-            'access to that space.',
-            $handles[$space_phid]->renderLink()),
-          phutil_tag('br'),
-          phutil_tag('br'),
-        );
-      }
-    }
+    $this->appendSpaceInformation($dialog, $object, $policy, $capability);
 
-    $content = array(
-      $space_info,
-      pht('Users with the "%s" capability:', $capability_name),
-      $auto_info,
-    );
+    $intro = pht(
+      'Users with the "%s" capability for this object:',
+      $capability_name);
 
     $object_name = pht(
       '%s %s',
       $handle->getTypeName(),
       $handle->getObjectName());
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
-      ->setClass('aphront-access-dialog')
+    return $dialog
       ->setTitle(pht('Policy Details: %s', $object_name))
-      ->appendChild($content)
+      ->appendParagraph($intro)
+      ->appendChild($auto_info)
       ->addCancelButton($object_uri, pht('Done'));
+  }
+
+  private function appendSpaceInformation(
+    AphrontDialogView $dialog,
+    PhabricatorPolicyInterface $object,
+    PhabricatorPolicy $policy,
+    $capability) {
+    $viewer = $this->getViewer();
+
+    if (!($object instanceof PhabricatorSpacesInterface)) {
+      return;
+    }
+
+    if (!PhabricatorSpacesNamespaceQuery::getSpacesExist($viewer)) {
+      return;
+    }
+
+    // NOTE: We're intentionally letting users through here, even if they only
+    // have access to one space. The intent is to help users in "space jail"
+    // understand who objects they create are visible to:
+
+    $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
+      $object);
+
+    $handles = $viewer->loadHandles(array($space_phid));
+    $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide');
+
+    $dialog->appendParagraph(
+      array(
+        pht(
+          'This object is in %s, and can only be seen or edited by users with '.
+          'access to view objects in the space.',
+          $handles[$space_phid]->renderLink()),
+        ' ',
+        phutil_tag(
+          'strong',
+          array(),
+          phutil_tag(
+            'a',
+            array(
+              'href' => $doc_href,
+              'target' => '_blank',
+            ),
+            pht('Learn More'))),
+      ));
+
+    $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
+    $space = idx($spaces, $space_phid);
+    if (!$space) {
+      return;
+    }
+
+    $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space);
+    $space_policy = idx($space_policies, PhabricatorPolicyCapability::CAN_VIEW);
+    if (!$space_policy) {
+      return;
+    }
+
+    $space_explanation = PhabricatorPolicy::getPolicyExplanation(
+      $viewer,
+      $space_policy->getPHID());
+    $items = array();
+    $items[] = $space_explanation;
+
+    foreach ($items as $key => $item) {
+      $items[$key] = phutil_tag('li', array(), $item);
+    }
+
+    $dialog->appendParagraph(pht('Users who can see objects in this space:'));
+    $dialog->appendChild(phutil_tag('ul', array(), $items));
+
+    $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
+    if ($capability == $view_capability) {
+      $stronger = $space_policy->isStrongerThan($policy);
+      if ($stronger) {
+        $dialog->appendParagraph(
+          pht(
+            'The space this object is in has a more restrictive view '.
+            'policy ("%s") than the object does ("%s"), so the space\'s '.
+            'view policy is shown as a hint instead of the object policy.',
+            $space_policy->getShortName(),
+            $policy->getShortName()));
+      }
+    }
 
-    return id(new AphrontDialogResponse())->setDialog($dialog);
+    $dialog->appendParagraph(
+      pht(
+        'After a user passes space policy checks, they must still pass '.
+        'object policy checks.'));
   }
 
 }
diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php
index e01e0dd416..c23858b0a8 100644
--- a/src/applications/policy/storage/PhabricatorPolicy.php
+++ b/src/applications/policy/storage/PhabricatorPolicy.php
@@ -1,400 +1,454 @@
 <?php
 
 final class PhabricatorPolicy
   extends PhabricatorPolicyDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorDestructibleInterface {
 
   const ACTION_ALLOW = 'allow';
   const ACTION_DENY = 'deny';
 
   private $name;
   private $shortName;
   private $type;
   private $href;
   private $workflow;
   private $icon;
 
   protected $rules = array();
   protected $defaultAction = self::ACTION_DENY;
 
   private $ruleObjects = self::ATTACHABLE;
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'rules' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'defaultAction' => 'text32',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPolicyPHIDTypePolicy::TYPECONST);
   }
 
   public static function newFromPolicyAndHandle(
     $policy_identifier,
     PhabricatorObjectHandle $handle = null) {
 
     $is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
     if ($is_global) {
       return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
     }
 
     $policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
     if ($policy) {
       return $policy;
     }
 
     if (!$handle) {
       throw new Exception(
         pht(
           "Policy identifier is an object PHID ('%s'), but no object handle ".
           "was provided. A handle must be provided for object policies.",
           $policy_identifier));
     }
 
     $handle_phid = $handle->getPHID();
     if ($policy_identifier != $handle_phid) {
       throw new Exception(
         pht(
           "Policy identifier is an object PHID ('%s'), but the provided ".
           "handle has a different PHID ('%s'). The handle must correspond ".
           "to the policy identifier.",
           $policy_identifier,
           $handle_phid));
     }
 
     $policy = id(new PhabricatorPolicy())
       ->setPHID($policy_identifier)
       ->setHref($handle->getURI());
 
     $phid_type = phid_get_type($policy_identifier);
     switch ($phid_type) {
       case PhabricatorProjectProjectPHIDType::TYPECONST:
         $policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
         $policy->setName($handle->getName());
         break;
       case PhabricatorPeopleUserPHIDType::TYPECONST:
         $policy->setType(PhabricatorPolicyType::TYPE_USER);
         $policy->setName($handle->getFullName());
         break;
       case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
         // TODO: This creates a weird handle-based version of a rule policy.
         // It behaves correctly, but can't be applied since it doesn't have
         // any rules. It is used to render transactions, and might need some
         // cleanup.
         break;
       default:
         $policy->setType(PhabricatorPolicyType::TYPE_MASKED);
         $policy->setName($handle->getFullName());
         break;
     }
 
     $policy->makeEphemeral();
 
     return $policy;
   }
 
   public function setType($type) {
     $this->type = $type;
     return $this;
   }
 
   public function getType() {
     if (!$this->type) {
       return PhabricatorPolicyType::TYPE_CUSTOM;
     }
     return $this->type;
   }
 
   public function setName($name) {
     $this->name = $name;
     return $this;
   }
 
   public function getName() {
     if (!$this->name) {
       return pht('Custom Policy');
     }
     return $this->name;
   }
 
   public function setShortName($short_name) {
     $this->shortName = $short_name;
     return $this;
   }
 
   public function getShortName() {
     if ($this->shortName) {
       return $this->shortName;
     }
     return $this->getName();
   }
 
   public function setHref($href) {
     $this->href = $href;
     return $this;
   }
 
   public function getHref() {
     return $this->href;
   }
 
   public function setWorkflow($workflow) {
     $this->workflow = $workflow;
     return $this;
   }
 
   public function getWorkflow() {
     return $this->workflow;
   }
 
   public function setIcon($icon) {
     $this->icon = $icon;
     return $this;
   }
 
   public function getIcon() {
     if ($this->icon) {
       return $this->icon;
     }
 
     switch ($this->getType()) {
       case PhabricatorPolicyType::TYPE_GLOBAL:
         static $map = array(
           PhabricatorPolicies::POLICY_PUBLIC  => 'fa-globe',
           PhabricatorPolicies::POLICY_USER    => 'fa-users',
           PhabricatorPolicies::POLICY_ADMIN   => 'fa-eye',
           PhabricatorPolicies::POLICY_NOONE   => 'fa-ban',
         );
         return idx($map, $this->getPHID(), 'fa-question-circle');
       case PhabricatorPolicyType::TYPE_USER:
         return 'fa-user';
       case PhabricatorPolicyType::TYPE_PROJECT:
         return 'fa-briefcase';
       case PhabricatorPolicyType::TYPE_CUSTOM:
       case PhabricatorPolicyType::TYPE_MASKED:
         return 'fa-certificate';
       default:
         return 'fa-question-circle';
     }
   }
 
   public function getSortKey() {
     return sprintf(
       '%02d%s',
       PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
       $this->getSortName());
   }
 
   private function getSortName() {
     if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
       static $map = array(
         PhabricatorPolicies::POLICY_PUBLIC  => 0,
         PhabricatorPolicies::POLICY_USER    => 1,
         PhabricatorPolicies::POLICY_ADMIN   => 2,
         PhabricatorPolicies::POLICY_NOONE   => 3,
       );
       return idx($map, $this->getPHID());
     }
     return $this->getName();
   }
 
   public static function getPolicyExplanation(
     PhabricatorUser $viewer,
     $policy) {
 
     $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
     if ($rule) {
       return $rule->getPolicyExplanation();
     }
 
     switch ($policy) {
       case PhabricatorPolicies::POLICY_PUBLIC:
         return pht('This object is public.');
       case PhabricatorPolicies::POLICY_USER:
         return pht('Logged in users can take this action.');
       case PhabricatorPolicies::POLICY_ADMIN:
         return pht('Administrators can take this action.');
       case PhabricatorPolicies::POLICY_NOONE:
         return pht('By default, no one can take this action.');
       default:
         $handle = id(new PhabricatorHandleQuery())
           ->setViewer($viewer)
           ->withPHIDs(array($policy))
           ->executeOne();
 
         $type = phid_get_type($policy);
         if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
           return pht(
             'Members of the project "%s" can take this action.',
             $handle->getFullName());
         } else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
           return pht(
             '%s can take this action.',
             $handle->getFullName());
         } else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
           return pht(
             'This object has a custom policy controlling who can take this '.
             'action.');
         } else {
           return pht(
             'This object has an unknown or invalid policy setting ("%s").',
             $policy);
         }
     }
   }
 
   public function getFullName() {
     switch ($this->getType()) {
       case PhabricatorPolicyType::TYPE_PROJECT:
         return pht('Project: %s', $this->getName());
       case PhabricatorPolicyType::TYPE_MASKED:
         return pht('Other: %s', $this->getName());
       default:
         return $this->getName();
     }
   }
 
   public function renderDescription($icon = false) {
     $img = null;
     if ($icon) {
       $img = id(new PHUIIconView())
         ->setIconFont($this->getIcon());
     }
 
     if ($this->getHref()) {
       $desc = javelin_tag(
         'a',
         array(
           'href' => $this->getHref(),
           'class' => 'policy-link',
           'sigil' => $this->getWorkflow() ? 'workflow' : null,
         ),
         array(
           $img,
           $this->getName(),
         ));
     } else {
       if ($img) {
         $desc = array($img, $this->getName());
       } else {
         $desc = $this->getName();
       }
     }
 
     switch ($this->getType()) {
       case PhabricatorPolicyType::TYPE_PROJECT:
         return pht('%s (Project)', $desc);
       case PhabricatorPolicyType::TYPE_CUSTOM:
         return $desc;
       case PhabricatorPolicyType::TYPE_MASKED:
         return pht(
           '%s (You do not have permission to view policy details.)',
           $desc);
       default:
         return $desc;
     }
   }
 
   /**
    * Return a list of custom rule classes (concrete subclasses of
    * @{class:PhabricatorPolicyRule}) this policy uses.
    *
    * @return list<string> List of class names.
    */
   public function getCustomRuleClasses() {
     $classes = array();
 
     foreach ($this->getRules() as $rule) {
       $class = idx($rule, 'rule');
       try {
         if (class_exists($class)) {
           $classes[$class] = $class;
         }
       } catch (Exception $ex) {
         continue;
       }
     }
 
     return array_keys($classes);
   }
 
   /**
    * Return a list of all values used by a given rule class to implement this
    * policy. This is used to bulk load data (like project memberships) in order
    * to apply policy filters efficiently.
    *
    * @param string Policy rule classname.
    * @return list<wild> List of values used in this policy.
    */
   public function getCustomRuleValues($rule_class) {
     $values = array();
     foreach ($this->getRules() as $rule) {
       if ($rule['rule'] == $rule_class) {
         $values[] = $rule['value'];
       }
     }
     return $values;
   }
 
   public function attachRuleObjects(array $objects) {
     $this->ruleObjects = $objects;
     return $this;
   }
 
   public function getRuleObjects() {
     return $this->assertAttached($this->ruleObjects);
   }
 
 
+  /**
+   * Return `true` if this policy is stronger (more restrictive) than some
+   * other policy.
+   *
+   * Because policies are complicated, determining which policies are
+   * "stronger" is not trivial. This method uses a very coarse working
+   * definition of policy strength which is cheap to compute, unambiguous,
+   * and intuitive in the common cases.
+   *
+   * This method returns `true` if the //class// of this policy is stronger
+   * than the other policy, even if the policies are (or might be) the same in
+   * practice. For example, "Members of Project X" is considered a stronger
+   * policy than "All Users", even though "Project X" might (in some rare
+   * cases) contain every user.
+   *
+   * Generally, the ordering here is:
+   *
+   *   - Public
+   *   - All Users
+   *   - (Everything Else)
+   *   - No One
+   *
+   * In the "everything else" bucket, we can't make any broad claims about
+   * which policy is stronger (and we especially can't make those claims
+   * cheaply).
+   *
+   * Even if we fully evaluated each policy, the two policies might be
+   * "Members of X" and "Members of Y", each of which permits access to some
+   * set of unique users. In this case, neither is strictly stronger than
+   * the other.
+   *
+   * @param PhabricatorPolicy Other policy.
+   * @return bool `true` if this policy is more restrictive than the other
+   *  policy.
+   */
+  public function isStrongerThan(PhabricatorPolicy $other) {
+    $this_policy = $this->getPHID();
+    $other_policy = $other->getPHID();
+
+    $strengths = array(
+      PhabricatorPolicies::POLICY_PUBLIC => -2,
+      PhabricatorPolicies::POLICY_USER => -1,
+      // (Default policies have strength 0.)
+      PhabricatorPolicies::POLICY_NOONE => 1,
+    );
+
+    $this_strength = idx($strengths, $this->getPHID(), 0);
+    $other_strength = idx($strengths, $other->getPHID(), 0);
+
+    return ($this_strength > $other_strength);
+  }
+
+
+
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
     );
   }
 
   public function getPolicy($capability) {
     // NOTE: We implement policies only so we can comply with the interface.
     // The actual query skips them, as enforcing policies on policies seems
     // perilous and isn't currently required by the application.
     return PhabricatorPolicies::POLICY_PUBLIC;
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->delete();
   }
 
 
 }
diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php
index 29aa2d07df..100ee93d4b 100644
--- a/src/view/phui/PHUIHeaderView.php
+++ b/src/view/phui/PHUIHeaderView.php
@@ -1,305 +1,331 @@
 <?php
 
 final class PHUIHeaderView extends AphrontTagView {
 
   const PROPERTY_STATUS = 1;
 
   private $objectName;
   private $header;
   private $tags = array();
   private $image;
   private $imageURL = null;
   private $subheader;
   private $headerColor;
   private $noBackground;
   private $bleedHeader;
   private $properties = array();
   private $actionLinks = array();
   private $buttonBar = null;
   private $policyObject;
   private $epoch;
 
   public function setHeader($header) {
     $this->header = $header;
     return $this;
   }
 
   public function setObjectName($object_name) {
     $this->objectName = $object_name;
     return $this;
   }
 
   public function setNoBackground($nada) {
     $this->noBackground = $nada;
     return $this;
   }
 
   public function addTag(PHUITagView $tag) {
     $this->tags[] = $tag;
     return $this;
   }
 
   public function setImage($uri) {
     $this->image = $uri;
     return $this;
   }
 
   public function setImageURL($url) {
     $this->imageURL = $url;
     return $this;
   }
 
   public function setSubheader($subheader) {
     $this->subheader = $subheader;
     return $this;
   }
 
   public function setBleedHeader($bleed) {
     $this->bleedHeader = $bleed;
     return $this;
   }
 
   public function setHeaderColor($color) {
     $this->headerColor = $color;
     return $this;
   }
 
   public function setPolicyObject(PhabricatorPolicyInterface $object) {
     $this->policyObject = $object;
     return $this;
   }
 
   public function addProperty($property, $value) {
     $this->properties[$property] = $value;
     return $this;
   }
 
   public function addActionLink(PHUIButtonView $button) {
     $this->actionLinks[] = $button;
     return $this;
   }
 
   public function setButtonBar(PHUIButtonBarView $bb) {
     $this->buttonBar = $bb;
     return $this;
   }
 
   public function setStatus($icon, $color, $name) {
     $header_class = 'phui-header-status';
 
     if ($color) {
       $icon = $icon.' '.$color;
       $header_class = $header_class.'-'.$color;
     }
 
     $img = id(new PHUIIconView())
       ->setIconFont($icon);
 
     $tag = phutil_tag(
       'span',
       array(
         'class' => "{$header_class} plr",
       ),
       array(
         $img,
         $name,
       ));
 
     return $this->addProperty(self::PROPERTY_STATUS, $tag);
   }
 
   public function setEpoch($epoch) {
     $age = time() - $epoch;
     $age = floor($age / (60 * 60 * 24));
     if ($age < 1) {
       $when = pht('Today');
     } else if ($age == 1) {
       $when = pht('Yesterday');
     } else {
       $when = pht('%d Days Ago', $age);
     }
 
     $this->setStatus('fa-clock-o bluegrey', null, pht('Updated %s', $when));
     return $this;
   }
 
   protected function getTagName() {
     return 'div';
   }
 
   protected function getTagAttributes() {
     require_celerity_resource('phui-header-view-css');
 
     $classes = array();
     $classes[] = 'phui-header-shell';
 
     if ($this->noBackground) {
       $classes[] = 'phui-header-no-backgound';
     }
 
     if ($this->bleedHeader) {
       $classes[] = 'phui-bleed-header';
     }
 
     if ($this->headerColor) {
       $classes[] = 'sprite-gradient';
       $classes[] = 'gradient-'.$this->headerColor.'-header';
     }
 
     if ($this->properties || $this->policyObject || $this->subheader) {
       $classes[] = 'phui-header-tall';
     }
 
     if ($this->image) {
       $classes[] = 'phui-header-has-image';
     }
 
     return array(
       'class' => $classes,
     );
   }
 
   protected function getTagContent() {
     $image = null;
     if ($this->image) {
       $image = phutil_tag(
         ($this->imageURL ? 'a' : 'span'),
         array(
           'href' => $this->imageURL,
           'class' => 'phui-header-image',
           'style' => 'background-image: url('.$this->image.')',
         ),
         ' ');
     }
 
     $viewer = $this->getUser();
 
     $header = array();
     if ($viewer) {
       $header[] = id(new PHUISpacesNamespaceContextView())
         ->setUser($viewer)
         ->setObject($this->policyObject);
     }
 
     if ($this->objectName) {
       $header[] = array(
         phutil_tag(
           'a',
           array(
             'href' => '/'.$this->objectName,
           ),
           $this->objectName),
         ' ',
       );
     }
 
     if ($this->actionLinks) {
       $actions = array();
       foreach ($this->actionLinks as $button) {
         $button->setColor(PHUIButtonView::SIMPLE);
         $button->addClass(PHUI::MARGIN_SMALL_LEFT);
         $button->addClass('phui-header-action-link');
         $actions[] = $button;
       }
       $header[] = phutil_tag(
         'div',
         array(
           'class' => 'phui-header-action-links',
         ),
         $actions);
     }
 
     if ($this->buttonBar) {
       $header[] = phutil_tag(
         'div',
         array(
           'class' => 'phui-header-action-links',
         ),
         $this->buttonBar);
     }
     $header[] = $this->header;
 
     if ($this->tags) {
       $header[] = ' ';
       $header[] = phutil_tag(
         'span',
         array(
           'class' => 'phui-header-tags',
         ),
         array_interleave(' ', $this->tags));
     }
 
     if ($this->subheader) {
       $header[] = phutil_tag(
         'div',
         array(
           'class' => 'phui-header-subheader',
         ),
         $this->subheader);
     }
 
     if ($this->properties || $this->policyObject) {
       $property_list = array();
       foreach ($this->properties as $type => $property) {
         switch ($type) {
           case self::PROPERTY_STATUS:
             $property_list[] = $property;
           break;
           default:
             throw new Exception(pht('Incorrect Property Passed'));
           break;
         }
       }
 
       if ($this->policyObject) {
         $property_list[] = $this->renderPolicyProperty($this->policyObject);
       }
 
       $header[] = phutil_tag(
         'div',
         array(
           'class' => 'phui-header-subheader',
         ),
         $property_list);
     }
 
     return array(
       $image,
       phutil_tag(
         'h1',
         array(
           'class' => 'phui-header-view grouped',
         ),
         $header),
       );
   }
 
   private function renderPolicyProperty(PhabricatorPolicyInterface $object) {
     $viewer = $this->getUser();
 
     $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object);
 
     $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
     $policy = idx($policies, $view_capability);
     if (!$policy) {
       return null;
     }
 
+    // If an object is in a Space with a strictly stronger (more restrictive)
+    // policy, we show the more restrictive policy. This better aligns the
+    // UI hint with the actual behavior.
+
+    // NOTE: We'll do this even if the viewer has access to only one space, and
+    // show them information about the existence of spaces if they click
+    // through.
+    if ($object instanceof PhabricatorSpacesInterface) {
+      $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
+        $object);
+
+      $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
+      $space = idx($spaces, $space_phid);
+      if ($space) {
+        $space_policies = PhabricatorPolicyQuery::loadPolicies(
+          $viewer,
+          $space);
+        $space_policy = idx($space_policies, $view_capability);
+        if ($space_policy) {
+          if ($space_policy->isStrongerThan($policy)) {
+            $policy = $space_policy;
+          }
+        }
+      }
+    }
+
     $phid = $object->getPHID();
 
     $icon = id(new PHUIIconView())
       ->setIconFont($policy->getIcon().' bluegrey');
 
     $link = javelin_tag(
       'a',
       array(
         'class' => 'policy-link',
         'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/',
         'sigil' => 'workflow',
       ),
       $policy->getShortName());
 
     return array($icon, $link);
   }
 
 }