diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
index 044cb8beda..c4ee026374 100644
--- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
+++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
@@ -1,190 +1,190 @@
 <?php
 
 final class PhabricatorOwnersPackageEditEngine
   extends PhabricatorEditEngine {
 
   const ENGINECONST = 'owners.package';
 
   public function getEngineName() {
     return pht('Owners Packages');
   }
 
   public function getSummaryHeader() {
     return pht('Configure Owners Package Forms');
   }
 
   public function getSummaryText() {
     return pht('Configure forms for creating and editing packages in Owners.');
   }
 
   public function getEngineApplicationClass() {
     return 'PhabricatorOwnersApplication';
   }
 
   protected function newEditableObject() {
     return PhabricatorOwnersPackage::initializeNewPackage($this->getViewer());
   }
 
   protected function newObjectQuery() {
     return id(new PhabricatorOwnersPackageQuery())
       ->needPaths(true);
   }
 
   protected function getObjectCreateTitleText($object) {
     return pht('Create New Package');
   }
 
   protected function getObjectEditTitleText($object) {
     return pht('Edit Package: %s', $object->getName());
   }
 
   protected function getObjectEditShortText($object) {
     return pht('Package %d', $object->getID());
   }
 
   protected function getObjectCreateShortText() {
     return pht('Create Package');
   }
 
   protected function getObjectName() {
     return pht('Package');
   }
 
   protected function getObjectViewURI($object) {
     return $object->getURI();
   }
 
   protected function buildCustomEditFields($object) {
 
     $paths_help = pht(<<<EOTEXT
 When updating the paths for a package, pass a list of dictionaries like
 this as the `value` for the transaction:
 
 ```lang=json, name="Example Paths Value"
 [
   {
     "repositoryPHID": "PHID-REPO-1234",
     "path": "/path/to/directory/",
     "excluded": false
   },
   {
     "repositoryPHID": "PHID-REPO-1234",
     "path": "/another/example/path/",
     "excluded": false
   }
 ]
 ```
 
 This transaction will set the paths to the list you provide, overwriting any
 previous paths.
 
 Generally, you will call `owners.search` first to get a list of current paths
 (which are provided in the same format), make changes, then update them by
 applying a transaction of this type.
 EOTEXT
       );
 
     $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
     $autoreview_map = ipull($autoreview_map, 'name');
 
     $dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
     $dominion_map = ipull($dominion_map, 'name');
 
     return array(
       id(new PhabricatorTextEditField())
         ->setKey('name')
         ->setLabel(pht('Name'))
         ->setDescription(pht('Name of the package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageNameTransaction::TRANSACTIONTYPE)
         ->setIsRequired(true)
         ->setValue($object->getName()),
       id(new PhabricatorDatasourceEditField())
         ->setKey('owners')
         ->setLabel(pht('Owners'))
         ->setDescription(pht('Users and projects which own the package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageOwnersTransaction::TRANSACTIONTYPE)
         ->setDatasource(new PhabricatorProjectOrUserDatasource())
         ->setIsCopyable(true)
         ->setValue($object->getOwnerPHIDs()),
       id(new PhabricatorSelectEditField())
         ->setKey('dominion')
         ->setLabel(pht('Dominion'))
         ->setDescription(
           pht('Change package dominion rules.'))
         ->setTransactionType(
           PhabricatorOwnersPackageDominionTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setValue($object->getDominion())
         ->setOptions($dominion_map),
       id(new PhabricatorSelectEditField())
         ->setKey('autoReview')
         ->setLabel(pht('Auto Review'))
         ->setDescription(
           pht(
             'Automatically trigger reviews for commits affecting files in '.
             'this package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageAutoreviewTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setValue($object->getAutoReview())
         ->setOptions($autoreview_map),
       id(new PhabricatorSelectEditField())
         ->setKey('auditing')
         ->setLabel(pht('Auditing'))
         ->setDescription(
           pht(
             'Automatically trigger audits for commits affecting files in '.
             'this package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageAuditingTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
-        ->setValue($object->getAuditingEnabled())
+        ->setValue($object->getAuditingState())
         ->setOptions(
           array(
-            '' => pht('Disabled'),
-            '1' => pht('Enabled'),
+            PhabricatorOwnersPackage::AUDITING_NONE => pht('No Auditing'),
+            PhabricatorOwnersPackage::AUDITING_AUDIT => pht('Audit Commits'),
           )),
       id(new PhabricatorRemarkupEditField())
         ->setKey('description')
         ->setLabel(pht('Description'))
         ->setDescription(pht('Human-readable description of the package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageDescriptionTransaction::TRANSACTIONTYPE)
         ->setValue($object->getDescription()),
       id(new PhabricatorSelectEditField())
         ->setKey('status')
         ->setLabel(pht('Status'))
         ->setDescription(pht('Archive or enable the package.'))
         ->setTransactionType(
           PhabricatorOwnersPackageStatusTransaction::TRANSACTIONTYPE)
         ->setIsFormField(false)
         ->setValue($object->getStatus())
         ->setOptions($object->getStatusNameMap()),
       id(new PhabricatorCheckboxesEditField())
         ->setKey('ignored')
         ->setLabel(pht('Ignored Attributes'))
         ->setDescription(pht('Ignore paths with any of these attributes.'))
         ->setTransactionType(
           PhabricatorOwnersPackageIgnoredTransaction::TRANSACTIONTYPE)
         ->setValue(array_keys($object->getIgnoredPathAttributes()))
         ->setOptions(
           array(
             'generated' => pht('Ignore generated files (review only).'),
           )),
       id(new PhabricatorConduitEditField())
         ->setKey('paths.set')
         ->setLabel(pht('Paths'))
         ->setIsFormField(false)
         ->setTransactionType(
           PhabricatorOwnersPackagePathsTransaction::TRANSACTIONTYPE)
         ->setConduitDescription(
           pht('Overwrite existing package paths with new paths.'))
         ->setConduitTypeDescription(
           pht('List of dictionaries, each describing a path.'))
         ->setConduitDocumentation($paths_help),
     );
   }
 
 }
diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php
index 564fc8a28b..207e0cb809 100644
--- a/src/applications/owners/storage/PhabricatorOwnersPackage.php
+++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php
@@ -1,805 +1,815 @@
 <?php
 
 final class PhabricatorOwnersPackage
   extends PhabricatorOwnersDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorCustomFieldInterface,
     PhabricatorDestructibleInterface,
     PhabricatorConduitResultInterface,
     PhabricatorFulltextInterface,
     PhabricatorFerretInterface,
     PhabricatorNgramsInterface {
 
   protected $name;
   protected $auditingEnabled;
   protected $autoReview;
   protected $description;
   protected $status;
   protected $viewPolicy;
   protected $editPolicy;
   protected $dominion;
   protected $properties = array();
 
   private $paths = self::ATTACHABLE;
   private $owners = self::ATTACHABLE;
   private $customFields = self::ATTACHABLE;
   private $pathRepositoryMap = array();
 
   const STATUS_ACTIVE = 'active';
   const STATUS_ARCHIVED = 'archived';
 
   const AUTOREVIEW_NONE = 'none';
   const AUTOREVIEW_SUBSCRIBE = 'subscribe';
   const AUTOREVIEW_SUBSCRIBE_ALWAYS = 'subscribe-always';
   const AUTOREVIEW_REVIEW = 'review';
   const AUTOREVIEW_REVIEW_ALWAYS = 'review-always';
   const AUTOREVIEW_BLOCK = 'block';
   const AUTOREVIEW_BLOCK_ALWAYS = 'block-always';
 
+  const AUDITING_NONE = 'none';
+  const AUDITING_AUDIT = 'audit';
+
   const DOMINION_STRONG = 'strong';
   const DOMINION_WEAK = 'weak';
 
   const PROPERTY_IGNORED = 'ignored';
 
   public static function initializeNewPackage(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorOwnersApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(
       PhabricatorOwnersDefaultViewCapability::CAPABILITY);
     $edit_policy = $app->getPolicy(
       PhabricatorOwnersDefaultEditCapability::CAPABILITY);
 
     return id(new PhabricatorOwnersPackage())
       ->setAuditingEnabled(0)
       ->setAutoReview(self::AUTOREVIEW_NONE)
       ->setDominion(self::DOMINION_STRONG)
       ->setViewPolicy($view_policy)
       ->setEditPolicy($edit_policy)
       ->attachPaths(array())
       ->setStatus(self::STATUS_ACTIVE)
       ->attachOwners(array())
       ->setDescription('');
   }
 
   public static function getStatusNameMap() {
     return array(
       self::STATUS_ACTIVE => pht('Active'),
       self::STATUS_ARCHIVED => pht('Archived'),
     );
   }
 
   public static function getAutoreviewOptionsMap() {
     return array(
       self::AUTOREVIEW_NONE => array(
         'name' => pht('No Autoreview'),
       ),
       self::AUTOREVIEW_REVIEW => array(
         'name' => pht('Review Changes With Non-Owner Author'),
         'authority' => true,
       ),
       self::AUTOREVIEW_BLOCK => array(
         'name' => pht('Review Changes With Non-Owner Author (Blocking)'),
         'authority' => true,
       ),
       self::AUTOREVIEW_SUBSCRIBE => array(
         'name' => pht('Subscribe to Changes With Non-Owner Author'),
         'authority' => true,
       ),
       self::AUTOREVIEW_REVIEW_ALWAYS => array(
         'name' => pht('Review All Changes'),
       ),
       self::AUTOREVIEW_BLOCK_ALWAYS => array(
         'name' => pht('Review All Changes (Blocking)'),
       ),
       self::AUTOREVIEW_SUBSCRIBE_ALWAYS => array(
         'name' => pht('Subscribe to All Changes'),
       ),
     );
   }
 
   public static function getDominionOptionsMap() {
     return array(
       self::DOMINION_STRONG => array(
         'name' => pht('Strong (Control All Paths)'),
         'short' => pht('Strong'),
       ),
       self::DOMINION_WEAK => array(
         'name' => pht('Weak (Control Unowned Paths)'),
         'short' => pht('Weak'),
       ),
     );
   }
 
   protected function getConfiguration() {
     return array(
       // This information is better available from the history table.
       self::CONFIG_TIMESTAMPS => false,
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'sort',
         'description' => 'text',
         'auditingEnabled' => 'bool',
         'status' => 'text32',
         'autoReview' => 'text32',
         'dominion' => 'text32',
       ),
     ) + parent::getConfiguration();
   }
 
   public function getPHIDType() {
     return PhabricatorOwnersPackagePHIDType::TYPECONST;
   }
 
   public function isArchived() {
     return ($this->getStatus() == self::STATUS_ARCHIVED);
   }
 
   public function getMustMatchUngeneratedPaths() {
     $ignore_attributes = $this->getIgnoredPathAttributes();
     return !empty($ignore_attributes['generated']);
   }
 
   public function getPackageProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function setPackageProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getIgnoredPathAttributes() {
     return $this->getPackageProperty(self::PROPERTY_IGNORED, array());
   }
 
   public function setIgnoredPathAttributes(array $attributes) {
     return $this->setPackageProperty(self::PROPERTY_IGNORED, $attributes);
   }
 
   public function loadOwners() {
     if (!$this->getID()) {
       return array();
     }
     return id(new PhabricatorOwnersOwner())->loadAllWhere(
       'packageID = %d',
       $this->getID());
   }
 
   public function loadPaths() {
     if (!$this->getID()) {
       return array();
     }
     return id(new PhabricatorOwnersPath())->loadAllWhere(
       'packageID = %d',
       $this->getID());
   }
 
   public static function loadAffectedPackages(
     PhabricatorRepository $repository,
     array $paths) {
 
     if (!$paths) {
       return array();
     }
 
     return self::loadPackagesForPaths($repository, $paths);
   }
 
   public static function loadAffectedPackagesForChangesets(
     PhabricatorRepository $repository,
     DifferentialDiff $diff,
     array $changesets) {
     assert_instances_of($changesets, 'DifferentialChangeset');
 
     $paths_all = array();
     $paths_ungenerated = array();
 
     foreach ($changesets as $changeset) {
       $path = $changeset->getAbsoluteRepositoryPath($repository, $diff);
 
       $paths_all[] = $path;
 
       if (!$changeset->isGeneratedChangeset()) {
         $paths_ungenerated[] = $path;
       }
     }
 
     if (!$paths_all) {
       return array();
     }
 
     $packages_all = self::loadAffectedPackages(
       $repository,
       $paths_all);
 
     // If there are no generated changesets, we can't possibly need to throw
     // away any packages for matching only generated paths. Just return the
     // full set of packages.
     if ($paths_ungenerated === $paths_all) {
       return $packages_all;
     }
 
     $must_match_ungenerated = array();
     foreach ($packages_all as $package) {
       if ($package->getMustMatchUngeneratedPaths()) {
         $must_match_ungenerated[] = $package;
       }
     }
 
     // If no affected packages have the "Ignore Generated Paths" flag set, we
     // can't possibly need to throw any away.
     if (!$must_match_ungenerated) {
       return $packages_all;
     }
 
     if ($paths_ungenerated) {
       $packages_ungenerated = self::loadAffectedPackages(
         $repository,
         $paths_ungenerated);
     } else {
       $packages_ungenerated = array();
     }
 
     // We have some generated paths, and some packages that ignore generated
     // paths. Take all the packages which:
     //
     //   - ignore generated paths; and
     //   - didn't match any ungenerated paths
     //
     // ...and remove them from the list.
 
     $must_match_ungenerated = mpull($must_match_ungenerated, null, 'getID');
     $packages_ungenerated = mpull($packages_ungenerated, null, 'getID');
     $packages_all = mpull($packages_all, null, 'getID');
 
     foreach ($must_match_ungenerated as $package_id => $package) {
       if (!isset($packages_ungenerated[$package_id])) {
         unset($packages_all[$package_id]);
       }
     }
 
     return $packages_all;
   }
 
   public static function loadOwningPackages($repository, $path) {
     if (empty($path)) {
       return array();
     }
 
     return self::loadPackagesForPaths($repository, array($path), 1);
   }
 
   private static function loadPackagesForPaths(
     PhabricatorRepository $repository,
     array $paths,
     $limit = 0) {
 
     $fragments = array();
     foreach ($paths as $path) {
       foreach (self::splitPath($path) as $fragment) {
         $fragments[$fragment][$path] = true;
       }
     }
 
     $package = new PhabricatorOwnersPackage();
     $path = new PhabricatorOwnersPath();
     $conn = $package->establishConnection('r');
 
     $repository_clause = qsprintf(
       $conn,
       'AND p.repositoryPHID = %s',
       $repository->getPHID());
 
     // NOTE: The list of $paths may be very large if we're coming from
     // the OwnersWorker and processing, e.g., an SVN commit which created a new
     // branch. Break it apart so that it will fit within 'max_allowed_packet',
     // and then merge results in PHP.
 
     $rows = array();
     foreach (array_chunk(array_keys($fragments), 1024) as $chunk) {
       $indexes = array();
       foreach ($chunk as $fragment) {
         $indexes[] = PhabricatorHash::digestForIndex($fragment);
       }
 
       $rows[] = queryfx_all(
         $conn,
         'SELECT pkg.id, pkg.dominion, p.excluded, p.path
           FROM %T pkg JOIN %T p ON p.packageID = pkg.id
           WHERE p.pathIndex IN (%Ls) AND pkg.status IN (%Ls) %Q',
         $package->getTableName(),
         $path->getTableName(),
         $indexes,
         array(
           self::STATUS_ACTIVE,
         ),
         $repository_clause);
     }
     $rows = array_mergev($rows);
 
     $ids = self::findLongestPathsPerPackage($rows, $fragments);
 
     if (!$ids) {
       return array();
     }
 
     arsort($ids);
     if ($limit) {
       $ids = array_slice($ids, 0, $limit, $preserve_keys = true);
     }
     $ids = array_keys($ids);
 
     $packages = $package->loadAllWhere('id in (%Ld)', $ids);
     $packages = array_select_keys($packages, $ids);
 
     return $packages;
   }
 
   public static function loadPackagesForRepository($repository) {
     $package = new PhabricatorOwnersPackage();
     $ids = ipull(
       queryfx_all(
         $package->establishConnection('r'),
         'SELECT DISTINCT packageID FROM %T WHERE repositoryPHID = %s',
         id(new PhabricatorOwnersPath())->getTableName(),
         $repository->getPHID()),
       'packageID');
 
     return $package->loadAllWhere('id in (%Ld)', $ids);
   }
 
   public static function findLongestPathsPerPackage(array $rows, array $paths) {
 
     // Build a map from each path to all the package paths which match it.
     $path_hits = array();
     $weak = array();
     foreach ($rows as $row) {
       $id = $row['id'];
       $path = $row['path'];
       $length = strlen($path);
       $excluded = $row['excluded'];
 
       if ($row['dominion'] === self::DOMINION_WEAK) {
         $weak[$id] = true;
       }
 
       $matches = $paths[$path];
       foreach ($matches as $match => $ignored) {
         $path_hits[$match][] = array(
           'id' => $id,
           'excluded' => $excluded,
           'length' => $length,
         );
       }
     }
 
     // For each path, process the matching package paths to figure out which
     // packages actually own it.
     $path_packages = array();
     foreach ($path_hits as $match => $hits) {
       $hits = isort($hits, 'length');
 
       $packages = array();
       foreach ($hits as $hit) {
         $package_id = $hit['id'];
         if ($hit['excluded']) {
           unset($packages[$package_id]);
         } else {
           $packages[$package_id] = $hit;
         }
       }
 
       $path_packages[$match] = $packages;
     }
 
     // Remove packages with weak dominion rules that should cede control to
     // a more specific package.
     if ($weak) {
       foreach ($path_packages as $match => $packages) {
 
         // Group packages by length.
         $length_map = array();
         foreach ($packages as $package_id => $package) {
           $length_map[$package['length']][$package_id] = $package;
         }
 
         // For each path length, remove all weak packages if there are any
         // strong packages of the same length. This makes sure that if there
         // are one or more strong claims on a particular path, only those
         // claims stand.
         foreach ($length_map as $package_list) {
           $any_strong = false;
           foreach ($package_list as $package_id => $package) {
             if (!isset($weak[$package_id])) {
               $any_strong = true;
               break;
             }
           }
 
           if ($any_strong) {
             foreach ($package_list as $package_id => $package) {
               if (isset($weak[$package_id])) {
                 unset($packages[$package_id]);
               }
             }
           }
         }
 
         $packages = isort($packages, 'length');
         $packages = array_reverse($packages, true);
 
         $best_length = null;
         foreach ($packages as $package_id => $package) {
           // If this is the first package we've encountered, note its length.
           // We're iterating over the packages from longest to shortest match,
           // so packages of this length always have the best claim on the path.
           if ($best_length === null) {
             $best_length = $package['length'];
           }
 
           // If this package has the same length as the best length, its claim
           // stands.
           if ($package['length'] === $best_length) {
             continue;
           }
 
           // If this is a weak package and does not have the best length,
           // cede its claim to the stronger package.
           if (isset($weak[$package_id])) {
             unset($packages[$package_id]);
           }
         }
 
         $path_packages[$match] = $packages;
       }
     }
 
     // For each package that owns at least one path, identify the longest
     // path it owns.
     $package_lengths = array();
     foreach ($path_packages as $match => $hits) {
       foreach ($hits as $hit) {
         $length = $hit['length'];
         $id = $hit['id'];
         if (empty($package_lengths[$id])) {
           $package_lengths[$id] = $length;
         } else {
           $package_lengths[$id] = max($package_lengths[$id], $length);
         }
       }
     }
 
     return $package_lengths;
   }
 
   public static function splitPath($path) {
     $result = array(
       '/',
     );
 
     $parts = explode('/', $path);
     $buffer = '/';
     foreach ($parts as $part) {
       if (!strlen($part)) {
         continue;
       }
 
       $buffer = $buffer.$part.'/';
       $result[] = $buffer;
     }
 
     return $result;
   }
 
   public function attachPaths(array $paths) {
     assert_instances_of($paths, 'PhabricatorOwnersPath');
     $this->paths = $paths;
 
     // Drop this cache if we're attaching new paths.
     $this->pathRepositoryMap = array();
 
     return $this;
   }
 
   public function getPaths() {
     return $this->assertAttached($this->paths);
   }
 
   public function getPathsForRepository($repository_phid) {
     if (isset($this->pathRepositoryMap[$repository_phid])) {
       return $this->pathRepositoryMap[$repository_phid];
     }
 
     $map = array();
     foreach ($this->getPaths() as $path) {
       if ($path->getRepositoryPHID() == $repository_phid) {
         $map[] = $path;
       }
     }
 
     $this->pathRepositoryMap[$repository_phid] = $map;
 
     return $this->pathRepositoryMap[$repository_phid];
   }
 
   public function attachOwners(array $owners) {
     assert_instances_of($owners, 'PhabricatorOwnersOwner');
     $this->owners = $owners;
     return $this;
   }
 
   public function getOwners() {
     return $this->assertAttached($this->owners);
   }
 
   public function getOwnerPHIDs() {
     return mpull($this->getOwners(), 'getUserPHID');
   }
 
   public function isOwnerPHID($phid) {
     if (!$phid) {
       return false;
     }
 
     $owner_phids = $this->getOwnerPHIDs();
     $owner_phids = array_fuse($owner_phids);
 
     return isset($owner_phids[$phid]);
   }
 
   public function getMonogram() {
     return 'O'.$this->getID();
   }
 
   public function getURI() {
     // TODO: Move these to "/O123" for consistency.
     return '/owners/package/'.$this->getID().'/';
   }
 
+  public function getAuditingState() {
+    if ($this->getAuditingEnabled()) {
+      return self::AUDITING_AUDIT;
+    } else {
+      return self::AUDITING_NONE;
+    }
+  }
+
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if ($this->isOwnerPHID($viewer->getPHID())) {
           return true;
         }
         break;
     }
 
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('Owners of a package may always view it.');
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorOwnersPackageTransactionEditor();
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorOwnersPackageTransaction();
   }
 
 
 /* -(  PhabricatorCustomFieldInterface  )------------------------------------ */
 
 
   public function getCustomFieldSpecificationForRole($role) {
     return PhabricatorEnv::getEnvConfig('owners.fields');
   }
 
   public function getCustomFieldBaseClass() {
     return 'PhabricatorOwnersCustomField';
   }
 
   public function getCustomFields() {
     return $this->assertAttached($this->customFields);
   }
 
   public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
     $this->customFields = $fields;
     return $this;
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $conn_w = $this->establishConnection('w');
 
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE packageID = %d',
         id(new PhabricatorOwnersPath())->getTableName(),
         $this->getID());
 
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE packageID = %d',
         id(new PhabricatorOwnersOwner())->getTableName(),
         $this->getID());
 
       $this->delete();
     $this->saveTransaction();
   }
 
 
 /* -(  PhabricatorConduitResultInterface  )---------------------------------- */
 
 
   public function getFieldSpecificationsForConduit() {
     return array(
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('name')
         ->setType('string')
         ->setDescription(pht('The name of the package.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('description')
         ->setType('string')
         ->setDescription(pht('The package description.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('status')
         ->setType('string')
         ->setDescription(pht('Active or archived status of the package.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('owners')
         ->setType('list<map<string, wild>>')
         ->setDescription(pht('List of package owners.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('review')
         ->setType('map<string, wild>')
         ->setDescription(pht('Auto review information.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('audit')
         ->setType('map<string, wild>')
         ->setDescription(pht('Auto audit information.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('dominion')
         ->setType('map<string, wild>')
         ->setDescription(pht('Dominion setting information.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('ignored')
         ->setType('map<string, wild>')
         ->setDescription(pht('Ignored attribute information.')),
     );
   }
 
   public function getFieldValuesForConduit() {
     $owner_list = array();
     foreach ($this->getOwners() as $owner) {
       $owner_list[] = array(
         'ownerPHID' => $owner->getUserPHID(),
       );
     }
 
     $review_map = self::getAutoreviewOptionsMap();
     $review_value = $this->getAutoReview();
     if (isset($review_map[$review_value])) {
       $review_label = $review_map[$review_value]['name'];
     } else {
       $review_label = pht('Unknown ("%s")', $review_value);
     }
 
     $review = array(
       'value' => $review_value,
       'label' => $review_label,
     );
 
+    $audit_value = $this->getAuditingState();
     if ($this->getAuditingEnabled()) {
-      $audit_value = 'audit';
       $audit_label = pht('Auditing Enabled');
     } else {
-      $audit_value = 'none';
       $audit_label = pht('No Auditing');
     }
 
     $audit = array(
       'value' => $audit_value,
       'label' => $audit_label,
     );
 
     $dominion_value = $this->getDominion();
     $dominion_map = self::getDominionOptionsMap();
     if (isset($dominion_map[$dominion_value])) {
       $dominion_label = $dominion_map[$dominion_value]['name'];
       $dominion_short = $dominion_map[$dominion_value]['short'];
     } else {
       $dominion_label = pht('Unknown ("%s")', $dominion_value);
       $dominion_short = pht('Unknown ("%s")', $dominion_value);
     }
 
     $dominion = array(
       'value' => $dominion_value,
       'label' => $dominion_label,
       'short' => $dominion_short,
     );
 
     // Force this to always emit as a JSON object even if empty, never as
     // a JSON list.
     $ignored = $this->getIgnoredPathAttributes();
     if (!$ignored) {
       $ignored = (object)array();
     }
 
     return array(
       'name' => $this->getName(),
       'description' => $this->getDescription(),
       'status' => $this->getStatus(),
       'owners' => $owner_list,
       'review' => $review,
       'audit' => $audit,
       'dominion' => $dominion,
       'ignored' => $ignored,
     );
   }
 
   public function getConduitSearchAttachments() {
     return array(
       id(new PhabricatorOwnersPathsSearchEngineAttachment())
         ->setAttachmentKey('paths'),
     );
   }
 
 
 /* -(  PhabricatorFulltextInterface  )--------------------------------------- */
 
 
   public function newFulltextEngine() {
     return new PhabricatorOwnersPackageFulltextEngine();
   }
 
 
 /* -(  PhabricatorFerretInterface  )----------------------------------------- */
 
 
   public function newFerretEngine() {
     return new PhabricatorOwnersPackageFerretEngine();
   }
 
 
 /* -(  PhabricatorNgramsInterface  )----------------------------------------- */
 
 
   public function newNgrams() {
     return array(
       id(new PhabricatorOwnersPackageNameNgrams())
         ->setValue($this->getName()),
     );
   }
 
 }
diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php
index df4f0feb01..d7ea7093f9 100644
--- a/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php
+++ b/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php
@@ -1,32 +1,83 @@
 <?php
 
 final class PhabricatorOwnersPackageAuditingTransaction
   extends PhabricatorOwnersPackageTransactionType {
 
   const TRANSACTIONTYPE = 'owners.auditing';
 
   public function generateOldValue($object) {
     return (int)$object->getAuditingEnabled();
   }
 
   public function generateNewValue($object, $value) {
-    return (int)$value;
+    switch ($value) {
+      case PhabricatorOwnersPackage::AUDITING_AUDIT:
+        return 1;
+      case '1':
+        // TODO: Remove, deprecated.
+        return 1;
+      default:
+        return 0;
+    }
   }
 
   public function applyInternalEffects($object, $value) {
     $object->setAuditingEnabled($value);
   }
 
   public function getTitle() {
     if ($this->getNewValue()) {
       return pht(
         '%s enabled auditing for this package.',
         $this->renderAuthor());
     } else {
       return pht(
         '%s disabled auditing for this package.',
         $this->renderAuthor());
     }
   }
 
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    // See PHI1047. This transaction type accepted some weird stuff. Continue
+    // supporting it for now, but move toward sensible consistency.
+
+    $modern_options = array(
+      PhabricatorOwnersPackage::AUDITING_NONE =>
+        sprintf('"%s"', PhabricatorOwnersPackage::AUDITING_NONE),
+      PhabricatorOwnersPackage::AUDITING_AUDIT =>
+        sprintf('"%s"', PhabricatorOwnersPackage::AUDITING_AUDIT),
+    );
+
+    $deprecated_options = array(
+      '0' => '"0"',
+      '1' => '"1"',
+      '' => pht('"" (empty string)'),
+    );
+
+    foreach ($xactions as $xaction) {
+      $new_value = $xaction->getNewValue();
+
+      if (isset($modern_options[$new_value])) {
+        continue;
+      }
+
+      if (isset($deprecated_options[$new_value])) {
+        continue;
+      }
+
+      $errors[] = $this->newInvalidError(
+        pht(
+          'Package auditing value "%s" is not supported. Supported options '.
+          'are: %s. Deprecated options are: %s.',
+          $new_value,
+          implode(', ', $modern_options),
+          implode(', ', $deprecated_options)),
+        $xaction);
+    }
+
+    return $errors;
+  }
+
 }