diff --git a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
index cb1dea3533..442cb08f66 100644
--- a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
+++ b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
@@ -1,95 +1,107 @@
 <?php
 
 final class PhabricatorPolicyRequestExceptionHandler
   extends PhabricatorRequestExceptionHandler {
 
   public function getRequestExceptionHandlerPriority() {
     return 320000;
   }
 
   public function getRequestExceptionHandlerDescription() {
     return pht(
       'Handles policy exceptions which occur when a user tries to '.
       'do something they do not have permission to do.');
   }
 
   public function canHandleRequestThrowable(
     AphrontRequest $request,
     $throwable) {
 
     if (!$this->isPhabricatorSite($request)) {
       return false;
     }
 
     return ($throwable instanceof PhabricatorPolicyException);
   }
 
   public function handleRequestThrowable(
     AphrontRequest $request,
     $throwable) {
 
     $viewer = $this->getViewer($request);
 
     if (!$viewer->isLoggedIn()) {
       // If the user isn't logged in, just give them a login form. This is
       // probably a generally more useful response than a policy dialog that
       // they have to click through to get a login form.
       //
       // Possibly we should add a header here like "you need to login to see
       // the thing you are trying to look at".
       $auth_app_class = 'PhabricatorAuthApplication';
       $auth_app = PhabricatorApplication::getByClass($auth_app_class);
 
       return id(new PhabricatorAuthStartController())
         ->setRequest($request)
         ->setCurrentApplication($auth_app)
         ->handleRequest($request);
     }
 
     $content = array(
       phutil_tag(
         'div',
         array(
           'class' => 'aphront-policy-rejection',
         ),
         $throwable->getRejection()),
     );
 
     $list = null;
     if ($throwable->getCapabilityName()) {
       $list = $throwable->getMoreInfo();
       foreach ($list as $key => $item) {
         $list[$key] = $item;
       }
 
       $content[] = phutil_tag(
         'div',
         array(
           'class' => 'aphront-capability-details',
         ),
         pht(
           'Users with the "%s" capability:',
           $throwable->getCapabilityName()));
 
     }
 
     $dialog = id(new AphrontDialogView())
       ->setTitle($throwable->getTitle())
       ->setClass('aphront-access-dialog')
       ->setUser($viewer)
       ->appendChild($content);
 
     if ($list) {
       $dialog->appendList($list);
     }
 
+    // If the install is in developer mode, include a stack trace for the
+    // exception. When debugging things, it isn't always obvious where a
+    // policy exception came from and this can make it easier to hunt down
+    // bugs or improve ambiguous/confusing messaging.
+
+    $is_developer = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
+    if ($is_developer) {
+      $dialog->appendChild(
+        id(new AphrontStackTraceView())
+          ->setTrace($throwable->getTrace()));
+    }
+
     if ($request->isAjax()) {
       $dialog->addCancelButton('/', pht('Close'));
     } else {
       $dialog->addCancelButton('/', pht('OK'));
     }
 
     return $dialog;
   }
 
 }
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
index e276e035e4..1cabeb0709 100644
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -1,666 +1,666 @@
 <?php
 
 /**
  * @task  info  Application Information
  * @task  ui    UI Integration
  * @task  uri   URI Routing
  * @task  mail  Email integration
  * @task  fact  Fact Integration
  * @task  meta  Application Management
  */
 abstract class PhabricatorApplication
   extends PhabricatorLiskDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorApplicationTransactionInterface {
 
   const GROUP_CORE            = 'core';
   const GROUP_UTILITIES       = 'util';
   const GROUP_ADMIN           = 'admin';
   const GROUP_DEVELOPER       = 'developer';
 
   final public static function getApplicationGroups() {
     return array(
       self::GROUP_CORE          => pht('Core Applications'),
       self::GROUP_UTILITIES     => pht('Utilities'),
       self::GROUP_ADMIN         => pht('Administration'),
       self::GROUP_DEVELOPER     => pht('Developer Tools'),
     );
   }
 
   final public function getApplicationName() {
     return 'application';
   }
 
   final public function getTableName() {
     return 'application_application';
   }
 
   final protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
     ) + parent::getConfiguration();
   }
 
   final public function generatePHID() {
     return $this->getPHID();
   }
 
   final public function save() {
     // When "save()" is called on applications, we just return without
     // actually writing anything to the database.
     return $this;
   }
 
 
 /* -(  Application Information  )-------------------------------------------- */
 
   abstract public function getName();
 
   public function getShortDescription() {
     return pht('%s Application', $this->getName());
   }
 
   final public function isInstalled() {
     if (!$this->canUninstall()) {
       return true;
     }
 
     $prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
     if (!$prototypes && $this->isPrototype()) {
       return false;
     }
 
     $uninstalled = PhabricatorEnv::getEnvConfig(
       'phabricator.uninstalled-applications');
 
     return empty($uninstalled[get_class($this)]);
   }
 
 
   public function isPrototype() {
     return false;
   }
 
 
   /**
    * Return `true` if this application should never appear in application lists
    * in the UI. Primarily intended for unit test applications or other
    * pseudo-applications.
    *
    * Few applications should be unlisted. For most applications, use
    * @{method:isLaunchable} to hide them from main launch views instead.
    *
    * @return bool True to remove application from UI lists.
    */
   public function isUnlisted() {
     return false;
   }
 
 
   /**
    * Return `true` if this application is a normal application with a base
    * URI and a web interface.
    *
    * Launchable applications can be pinned to the home page, and show up in the
    * "Launcher" view of the Applications application. Making an application
    * unlaunchable prevents pinning and hides it from this view.
    *
    * Usually, an application should be marked unlaunchable if:
    *
    *   - it is available on every page anyway (like search); or
    *   - it does not have a web interface (like subscriptions); or
    *   - it is still pre-release and being intentionally buried.
    *
    * To hide applications more completely, use @{method:isUnlisted}.
    *
    * @return bool True if the application is launchable.
    */
   public function isLaunchable() {
     return true;
   }
 
 
   /**
    * Return `true` if this application should be pinned by default.
    *
    * Users who have not yet set preferences see a default list of applications.
    *
    * @param PhabricatorUser User viewing the pinned application list.
    * @return bool True if this application should be pinned by default.
    */
   public function isPinnedByDefault(PhabricatorUser $viewer) {
     return false;
   }
 
 
   /**
    * Returns true if an application is first-party (developed by Phacility)
    * and false otherwise.
    *
    * @return bool True if this application is developed by Phacility.
    */
   final public function isFirstParty() {
     $where = id(new ReflectionClass($this))->getFileName();
     $root = phutil_get_library_root('phabricator');
 
     if (!Filesystem::isDescendant($where, $root)) {
       return false;
     }
 
     if (Filesystem::isDescendant($where, $root.'/extensions')) {
       return false;
     }
 
     return true;
   }
 
   public function canUninstall() {
     return true;
   }
 
   final public function getPHID() {
     return 'PHID-APPS-'.get_class($this);
   }
 
   public function getTypeaheadURI() {
     return $this->isLaunchable() ? $this->getBaseURI() : null;
   }
 
   public function getBaseURI() {
     return null;
   }
 
   final public function getApplicationURI($path = '') {
     return $this->getBaseURI().ltrim($path, '/');
   }
 
   public function getIcon() {
     return 'fa-puzzle-piece';
   }
 
   public function getApplicationOrder() {
     return PHP_INT_MAX;
   }
 
   public function getApplicationGroup() {
     return self::GROUP_CORE;
   }
 
   public function getTitleGlyph() {
     return null;
   }
 
   final public function getHelpMenuItems(PhabricatorUser $viewer) {
     $items = array();
 
     $articles = $this->getHelpDocumentationArticles($viewer);
     if ($articles) {
       foreach ($articles as $article) {
         $item = id(new PhabricatorActionView())
           ->setName($article['name'])
           ->setHref($article['href'])
           ->addSigil('help-item')
           ->setOpenInNewWindow(true);
         $items[] = $item;
       }
     }
 
     $command_specs = $this->getMailCommandObjects();
     if ($command_specs) {
       foreach ($command_specs as $key => $spec) {
         $object = $spec['object'];
 
         $class = get_class($this);
         $href = '/applications/mailcommands/'.$class.'/'.$key.'/';
         $item = id(new PhabricatorActionView())
           ->setName($spec['name'])
           ->setHref($href)
           ->addSigil('help-item')
           ->setOpenInNewWindow(true);
         $items[] = $item;
       }
     }
 
     if ($items) {
       $divider = id(new PhabricatorActionView())
         ->addSigil('help-item')
         ->setType(PhabricatorActionView::TYPE_DIVIDER);
       array_unshift($items, $divider);
     }
 
     return array_values($items);
   }
 
   public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
     return array();
   }
 
   public function getOverview() {
     return null;
   }
 
   public function getEventListeners() {
     return array();
   }
 
   public function getRemarkupRules() {
     return array();
   }
 
   public function getQuicksandURIPatternBlacklist() {
     return array();
   }
 
   public function getMailCommandObjects() {
     return array();
   }
 
 
 /* -(  URI Routing  )-------------------------------------------------------- */
 
 
   public function getRoutes() {
     return array();
   }
 
   public function getResourceRoutes() {
     return array();
   }
 
 
 /* -(  Email Integration  )-------------------------------------------------- */
 
 
   public function supportsEmailIntegration() {
     return false;
   }
 
   final protected function getInboundEmailSupportLink() {
     return PhabricatorEnv::getDoclink('Configuring Inbound Email');
   }
 
   public function getAppEmailBlurb() {
     throw new PhutilMethodNotImplementedException();
   }
 
 /* -(  Fact Integration  )--------------------------------------------------- */
 
 
   public function getFactObjectsForAnalysis() {
     return array();
   }
 
 
 /* -(  UI Integration  )----------------------------------------------------- */
 
 
   /**
    * You can provide an optional piece of flavor text for the application. This
    * is currently rendered in application launch views if the application has no
    * status elements.
    *
    * @return string|null Flavor text.
    * @task ui
    */
   public function getFlavorText() {
     return null;
   }
 
 
   /**
    * Build items for the main menu.
    *
    * @param  PhabricatorUser    The viewing user.
    * @param  AphrontController  The current controller. May be null for special
    *                            pages like 404, exception handlers, etc.
    * @return list<PHUIListItemView> List of menu items.
    * @task ui
    */
   public function buildMainMenuItems(
     PhabricatorUser $user,
     PhabricatorController $controller = null) {
     return array();
   }
 
 
 /* -(  Application Management  )--------------------------------------------- */
 
 
   final public static function getByClass($class_name) {
     $selected = null;
     $applications = self::getAllApplications();
 
     foreach ($applications as $application) {
       if (get_class($application) == $class_name) {
         $selected = $application;
         break;
       }
     }
 
     if (!$selected) {
       throw new Exception(pht("No application '%s'!", $class_name));
     }
 
     return $selected;
   }
 
   final public static function getAllApplications() {
     static $applications;
 
     if ($applications === null) {
       $apps = id(new PhutilClassMapQuery())
         ->setAncestorClass(__CLASS__)
         ->setSortMethod('getApplicationOrder')
         ->execute();
 
       // Reorder the applications into "application order". Notably, this
       // ensures their event handlers register in application order.
       $apps = mgroup($apps, 'getApplicationGroup');
 
       $group_order = array_keys(self::getApplicationGroups());
       $apps = array_select_keys($apps, $group_order) + $apps;
 
       $apps = array_mergev($apps);
 
       $applications = $apps;
     }
 
     return $applications;
   }
 
   final public static function getAllInstalledApplications() {
     $all_applications = self::getAllApplications();
     $apps = array();
     foreach ($all_applications as $app) {
       if (!$app->isInstalled()) {
         continue;
       }
 
       $apps[] = $app;
     }
 
     return $apps;
   }
 
 
   /**
    * Determine if an application is installed, by application class name.
    *
    * To check if an application is installed //and// available to a particular
    * viewer, user @{method:isClassInstalledForViewer}.
    *
    * @param string  Application class name.
    * @return bool   True if the class is installed.
    * @task meta
    */
   final public static function isClassInstalled($class) {
     return self::getByClass($class)->isInstalled();
   }
 
 
   /**
    * Determine if an application is installed and available to a viewer, by
    * application class name.
    *
    * To check if an application is installed at all, use
    * @{method:isClassInstalled}.
    *
    * @param string Application class name.
    * @param PhabricatorUser Viewing user.
    * @return bool True if the class is installed for the viewer.
    * @task meta
    */
   final public static function isClassInstalledForViewer(
     $class,
     PhabricatorUser $viewer) {
 
     if ($viewer->isOmnipotent()) {
       return true;
     }
 
     $cache = PhabricatorCaches::getRequestCache();
     $viewer_fragment = $viewer->getCacheFragment();
     $key = 'app.'.$class.'.installed.'.$viewer_fragment;
 
     $result = $cache->getKey($key);
     if ($result === null) {
       if (!self::isClassInstalled($class)) {
         $result = false;
       } else {
         $application = self::getByClass($class);
         if (!$application->canUninstall()) {
           // If the application can not be uninstalled, always allow viewers
           // to see it. In particular, this allows logged-out viewers to see
           // Settings and load global default settings even if the install
           // does not allow public viewers.
           $result = true;
         } else {
           $result = PhabricatorPolicyFilter::hasCapability(
             $viewer,
             self::getByClass($class),
             PhabricatorPolicyCapability::CAN_VIEW);
         }
       }
 
       $cache->setKey($key, $result);
     }
 
     return $result;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array_merge(
       array(
         PhabricatorPolicyCapability::CAN_VIEW,
         PhabricatorPolicyCapability::CAN_EDIT,
       ),
       array_keys($this->getCustomCapabilities()));
   }
 
   public function getPolicy($capability) {
     $default = $this->getCustomPolicySetting($capability);
     if ($default) {
       return $default;
     }
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_ADMIN;
       default:
         $spec = $this->getCustomCapabilitySpecification($capability);
         return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
 
 /* -(  Policies  )----------------------------------------------------------- */
 
   protected function getCustomCapabilities() {
     return array();
   }
 
   final private function getCustomPolicySetting($capability) {
     if (!$this->isCapabilityEditable($capability)) {
       return null;
     }
 
     $policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
     if (isset($policy_locked[$capability])) {
       return $policy_locked[$capability];
     }
 
     $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');
 
     $app = idx($config, $this->getPHID());
     if (!$app) {
       return null;
     }
 
     $policy = idx($app, 'policy');
     if (!$policy) {
       return null;
     }
 
     return idx($policy, $capability);
   }
 
 
   final private function getCustomCapabilitySpecification($capability) {
     $custom = $this->getCustomCapabilities();
     if (!isset($custom[$capability])) {
       throw new Exception(pht("Unknown capability '%s'!", $capability));
     }
     return $custom[$capability];
   }
 
   final public function getCapabilityLabel($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return pht('Can Use Application');
       case PhabricatorPolicyCapability::CAN_EDIT:
         return pht('Can Configure Application');
     }
 
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     if ($capobj) {
       return $capobj->getCapabilityName();
     }
 
     return null;
   }
 
   final public function isCapabilityEditable($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->canUninstall();
       case PhabricatorPolicyCapability::CAN_EDIT:
-        return false;
+        return true;
       default:
         $spec = $this->getCustomCapabilitySpecification($capability);
         return idx($spec, 'edit', true);
     }
   }
 
   final public function getCapabilityCaption($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if (!$this->canUninstall()) {
           return pht(
             'This application is required for Phabricator to operate, so all '.
             'users must have access to it.');
         } else {
           return null;
         }
       case PhabricatorPolicyCapability::CAN_EDIT:
         return null;
       default:
         $spec = $this->getCustomCapabilitySpecification($capability);
         return idx($spec, 'caption');
     }
   }
 
   final public function getCapabilityTemplatePHIDType($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
       case PhabricatorPolicyCapability::CAN_EDIT:
         return null;
     }
 
     $spec = $this->getCustomCapabilitySpecification($capability);
     return idx($spec, 'template');
   }
 
   final public function getDefaultObjectTypePolicyMap() {
     $map = array();
 
     foreach ($this->getCustomCapabilities() as $capability => $spec) {
       if (empty($spec['template'])) {
         continue;
       }
       if (empty($spec['capability'])) {
         continue;
       }
       $default = $this->getPolicy($capability);
       $map[$spec['template']][$spec['capability']] = $default;
     }
 
     return $map;
   }
 
   public function getApplicationSearchDocumentTypes() {
     return array();
   }
 
   protected function getEditRoutePattern($base = null) {
     return $base.'(?:'.
       '(?P<id>[0-9]\d*)/)?'.
       '(?:'.
         '(?:'.
           '(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.
           '|'.
           '(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.
         ')'.
       ')?';
   }
 
   protected function getBulkRoutePattern($base = null) {
     return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
   }
 
   protected function getQueryRoutePattern($base = null) {
     return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';
   }
 
   protected function getProfileMenuRouting($controller) {
     $edit_route = $this->getEditRoutePattern();
 
     $mode_route = '(?P<itemEditMode>global|custom)/';
 
     return array(
       '(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,
       '(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,
       '(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,
       '(?P<itemAction>configure)/' => $controller,
       '(?P<itemAction>configure)/'.$mode_route => $controller,
       '(?P<itemAction>reorder)/'.$mode_route => $controller,
       '(?P<itemAction>edit)/'.$edit_route => $controller,
       '(?P<itemAction>new)/'.$mode_route.'(?<itemKey>[^/]+)/'.$edit_route
         => $controller,
       '(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route
         => $controller,
     );
   }
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorApplicationEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorApplicationApplicationTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 }
diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php
index f776c3ec0c..deccf1ef5c 100644
--- a/src/applications/config/editor/PhabricatorConfigEditor.php
+++ b/src/applications/config/editor/PhabricatorConfigEditor.php
@@ -1,160 +1,165 @@
 <?php
 
 final class PhabricatorConfigEditor
   extends PhabricatorApplicationTransactionEditor {
 
   public function getEditorApplicationClass() {
     return 'PhabricatorConfigApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('Phabricator Configuration');
   }
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $types[] = PhabricatorConfigTransaction::TYPE_EDIT;
 
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorConfigTransaction::TYPE_EDIT:
         return array(
           'deleted' => (int)$object->getIsDeleted(),
           'value'   => $object->getValue(),
         );
     }
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorConfigTransaction::TYPE_EDIT:
         return $xaction->getNewValue();
     }
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorConfigTransaction::TYPE_EDIT:
         $v = $xaction->getNewValue();
 
         // If this is a defined configuration option (vs a straggler from an
         // old version of Phabricator or a configuration file misspelling)
         // submit it to the validation gauntlet.
         $key = $object->getConfigKey();
         $all_options = PhabricatorApplicationConfigOptions::loadAllOptions();
         $option = idx($all_options, $key);
         if ($option) {
           $option->getGroup()->validateOption(
             $option,
             $v['value']);
         }
 
         $object->setIsDeleted((int)$v['deleted']);
         $object->setValue($v['value']);
         break;
     }
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     return;
   }
 
   protected function mergeTransactions(
     PhabricatorApplicationTransaction $u,
     PhabricatorApplicationTransaction $v) {
 
     $type = $u->getTransactionType();
     switch ($type) {
       case PhabricatorConfigTransaction::TYPE_EDIT:
         return $v;
     }
 
     return parent::mergeTransactions($u, $v);
   }
 
   protected function transactionHasEffect(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $old = $xaction->getOldValue();
     $new = $xaction->getNewValue();
 
     $type = $xaction->getTransactionType();
     switch ($type) {
       case PhabricatorConfigTransaction::TYPE_EDIT:
         // If an edit deletes an already-deleted entry, no-op it.
         if (idx($old, 'deleted') && idx($new, 'deleted')) {
           return false;
         }
         break;
     }
 
     return parent::transactionHasEffect($object, $xaction);
   }
 
   protected function didApplyTransactions($object, array $xactions) {
     // Force all the setup checks to run on the next page load.
     PhabricatorSetupCheck::deleteSetupCheckCache();
 
     return $xactions;
   }
 
   public static function storeNewValue(
     PhabricatorUser $user,
     PhabricatorConfigEntry $config_entry,
     $value,
-    PhabricatorContentSource $source) {
+    PhabricatorContentSource $source,
+    $acting_as_phid = null) {
 
     $xaction = id(new PhabricatorConfigTransaction())
       ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
       ->setNewValue(
         array(
            'deleted' => false,
            'value' => $value,
         ));
 
     $editor = id(new PhabricatorConfigEditor())
       ->setActor($user)
       ->setContinueOnNoEffect(true)
       ->setContentSource($source);
 
+    if ($acting_as_phid) {
+      $editor->setActingAsPHID($acting_as_phid);
+    }
+
     $editor->applyTransactions($config_entry, array($xaction));
   }
 
   public static function deleteConfig(
     PhabricatorUser $user,
     PhabricatorConfigEntry $config_entry,
     PhabricatorContentSource $source) {
 
     $xaction = id(new PhabricatorConfigTransaction())
       ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
       ->setNewValue(
         array(
           'deleted' => true,
           'value' => null,
         ));
 
     $editor = id(new PhabricatorConfigEditor())
       ->setActor($user)
       ->setContinueOnNoEffect(true)
       ->setContentSource($source);
 
     $editor->applyTransactions($config_entry, array($xaction));
   }
 
 }
diff --git a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
index 91b5c73249..59dfc09486 100644
--- a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
+++ b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
@@ -1,197 +1,206 @@
 <?php
 
 final class PhabricatorApplicationPolicyChangeTransaction
   extends PhabricatorApplicationTransactionType {
 
   const TRANSACTIONTYPE = 'application.policy';
   const METADATA_ATTRIBUTE = 'capability.name';
 
   private $policies;
 
   public function generateOldValue($object) {
     $application = $object;
     $capability = $this->getCapabilityName();
     return $application->getPolicy($capability);
   }
 
   public function applyExternalEffects($object, $value) {
     $application = $object;
     $user = $this->getActor();
 
     $key = 'phabricator.application-settings';
     $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
     $current_value = $config_entry->getValue();
 
     $phid = $application->getPHID();
     if (empty($current_value[$phid])) {
       $current_value[$application->getPHID()] = array();
     }
     if (empty($current_value[$phid]['policy'])) {
       $current_value[$phid]['policy'] = array();
     }
 
     $new = array($this->getCapabilityName() => $value);
     $current_value[$phid]['policy'] = $new + $current_value[$phid]['policy'];
 
     $editor = $this->getEditor();
     $content_source = $editor->getContentSource();
+
+    // NOTE: We allow applications to have custom edit policies, but they are
+    // currently stored in the Config application. The ability to edit Config
+    // values is always restricted to administrators, today. Empower this
+    // particular edit to punch through possible stricter policies, so normal
+    // users can change application configuration if the application allows
+    // them to do so.
+
     PhabricatorConfigEditor::storeNewValue(
-      $user,
+      PhabricatorUser::getOmnipotentUser(),
       $config_entry,
       $current_value,
-      $content_source);
+      $content_source,
+      $user->getPHID());
   }
 
   public function getTitle() {
     $old = $this->renderPolicy($this->getOldValue());
     $new = $this->renderPolicy($this->getNewValue());
 
     return pht(
       '%s changed the "%s" policy from "%s" to "%s".',
       $this->renderAuthor(),
       $this->renderCapability(),
       $old,
       $new);
   }
 
   public function getTitleForFeed() {
     $old = $this->renderPolicy($this->getOldValue());
     $new = $this->renderPolicy($this->getNewValue());
 
     return pht(
       '%s changed the "%s" policy for application %s from "%s" to "%s".',
       $this->renderAuthor(),
       $this->renderCapability(),
       $this->renderObject(),
       $old,
       $new);
   }
 
   public function validateTransactions($object, array $xactions) {
     $user = $this->getActor();
     $application = $object;
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($user)
       ->setObject($application)
       ->execute();
 
     $errors = array();
     foreach ($xactions as $xaction) {
       $new = $xaction->getNewValue();
       $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE);
 
       if (empty($policies[$new])) {
         // Not a standard policy, check for a custom policy.
         $policy = id(new PhabricatorPolicyQuery())
           ->setViewer($user)
           ->withPHIDs(array($new))
           ->executeOne();
         if (!$policy) {
           $errors[] = $this->newInvalidError(
             pht('Policy does not exist.'));
           continue;
         }
       } else {
         $policy = idx($policies, $new);
       }
 
       if (!$policy->isValidPolicyForEdit()) {
         $errors[] = $this->newInvalidError(
             pht('Can\'t set the policy to a policy you can\'t view!'));
           continue;
       }
 
       if ($new == PhabricatorPolicies::POLICY_PUBLIC) {
         $capobj = PhabricatorPolicyCapability::getCapabilityByKey(
           $capability);
         if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
           $errors[] = $this->newInvalidError(
             pht('Can\'t set non-public policies to public.'));
           continue;
         }
       }
 
       if (!$application->isCapabilityEditable($capability)) {
         $errors[] = $this->newInvalidError(
           pht('Capability "%s" is not editable for this application.',
             $capability));
         continue;
       }
     }
 
     // If we're changing these policies, the viewer needs to still be able to
     // view or edit the application under the new policy.
     $validate_map = array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
     $validate_map = array_fill_keys($validate_map, array());
 
     foreach ($xactions as $xaction) {
       $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE);
       if (!isset($validate_map[$capability])) {
         continue;
       }
 
       $validate_map[$capability][] = $xaction;
     }
 
     foreach ($validate_map as $capability => $cap_xactions) {
       if (!$cap_xactions) {
         continue;
       }
 
       $editor = $this->getEditor();
       $policy_errors = $editor->validatePolicyTransaction(
         $object,
         $cap_xactions,
         self::TRANSACTIONTYPE,
         $capability);
 
       foreach ($policy_errors as $error) {
         $errors[] = $error;
       }
     }
 
     return $errors;
   }
 
   private function renderPolicy($name) {
     $policies = $this->getAllPolicies();
     if (empty($policies[$name])) {
       // Not a standard policy, check for a custom policy.
       $policy = id(new PhabricatorPolicyQuery())
         ->setViewer($this->getViewer())
         ->withPHIDs(array($name))
         ->executeOne();
       $policies[$name] = $policy;
     }
 
     $policy = idx($policies, $name);
     return $this->renderValue($policy->getFullName());
   }
 
   private function getAllPolicies() {
     if (!$this->policies) {
       $viewer = $this->getViewer();
       $application = $this->getObject();
       $this->policies = id(new PhabricatorPolicyQuery())
         ->setViewer($viewer)
         ->setObject($application)
         ->execute();
     }
 
     return $this->policies;
   }
 
   private function renderCapability() {
     $application = $this->getObject();
     $capability = $this->getCapabilityName();
     return $application->getCapabilityLabel($capability);
   }
 
   private function getCapabilityName() {
     return $this->getMetadataValue(self::METADATA_ATTRIBUTE);
   }
 
 }
diff --git a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
index 12fbc8ebd4..b76e63b5c0 100644
--- a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
+++ b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php
@@ -1,79 +1,83 @@
 <?php
 
 final class PhabricatorApplicationUninstallTransaction
   extends PhabricatorApplicationTransactionType {
 
   const TRANSACTIONTYPE = 'application.uninstall';
 
   public function generateOldValue($object) {
     $key = 'phabricator.uninstalled-applications';
     $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
     $list = $config_entry->getValue();
     $uninstalled = PhabricatorEnv::getEnvConfig($key);
 
     if (isset($uninstalled[get_class($object)])) {
       return 'uninstalled';
     } else {
       return 'installed';
     }
   }
 
   public function generateNewValue($object, $value) {
     if ($value === 'uninstall') {
       return 'uninstalled';
     } else {
       return 'installed';
     }
   }
 
   public function applyExternalEffects($object, $value) {
     $application = $object;
     $user = $this->getActor();
 
     $key = 'phabricator.uninstalled-applications';
     $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
     $list = $config_entry->getValue();
     $uninstalled = PhabricatorEnv::getEnvConfig($key);
 
     if (isset($uninstalled[get_class($application)])) {
       unset($list[get_class($application)]);
     } else {
       $list[get_class($application)] = true;
     }
 
     $editor = $this->getEditor();
     $content_source = $editor->getContentSource();
+
+    // Today, changing config requires "Administrator", but "Can Edit" on
+    // applications to let you uninstall them may be granted to any user.
     PhabricatorConfigEditor::storeNewValue(
-      $user,
+      PhabricatorUser::getOmnipotentUser(),
       $config_entry,
       $list,
-      $content_source);
+      $content_source,
+      $user->getPHID());
   }
 
   public function getTitle() {
     if ($this->getNewValue() === 'uninstalled') {
       return pht(
         '%s uninstalled this application.',
         $this->renderAuthor());
     } else {
       return pht(
         '%s installed this application.',
         $this->renderAuthor());
     }
   }
 
   public function getTitleForFeed() {
     if ($this->getNewValue() === 'uninstalled') {
       return pht(
         '%s uninstalled %s.',
         $this->renderAuthor(),
         $this->renderObject());
     } else {
       return pht(
         '%s installed %s.',
         $this->renderAuthor(),
         $this->renderObject());
     }
   }
 
 }