diff --git a/src/applications/herald/engine/engine/HeraldEngine.php b/src/applications/herald/engine/engine/HeraldEngine.php
index 9532a4862d..9547a4e486 100644
--- a/src/applications/herald/engine/engine/HeraldEngine.php
+++ b/src/applications/herald/engine/engine/HeraldEngine.php
@@ -1,520 +1,518 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 class HeraldEngine {
 
   protected $rules = array();
   protected $results = array();
   protected $stack = array();
   protected $activeRule = null;
 
   protected $fieldCache = array();
   protected $object = null;
 
   public static function loadAndApplyRules(HeraldObjectAdapter $object) {
     $content_type = $object->getHeraldTypeName();
     $rules = HeraldRule::loadAllByContentTypeWithFullData(
       $content_type,
       $object->getPHID());
 
     $engine = new HeraldEngine();
     $effects = $engine->applyRules($rules, $object);
     $engine->applyEffects($effects, $object, $rules);
 
     return $engine->getTranscript();
   }
 
   public function applyRules(array $rules, HeraldObjectAdapter $object) {
     $t_start = microtime(true);
 
     $rules = mpull($rules, null, 'getID');
 
     $this->transcript = new HeraldTranscript();
     $this->transcript->setObjectPHID((string)$object->getPHID());
     $this->fieldCache = array();
     $this->results = array();
     $this->rules   = $rules;
     $this->object  = $object;
 
     $effects = array();
     foreach ($rules as $id => $rule) {
       $this->stack = array();
       try {
         if (($rule->getRepetitionPolicy() ==
              HeraldRepetitionPolicyConfig::FIRST) &&
             $rule->getRuleApplied($object->getPHID())) {
           // This rule is only supposed to be applied a single time, and it's
           // aleady been applied, so this is an automatic failure.
           $xscript = id(new HeraldRuleTranscript())
             ->setRuleID($id)
             ->setResult(false)
             ->setRuleName($rule->getName())
             ->setRuleOwner($rule->getAuthorPHID())
             ->setReason(
               "This rule is only supposed to be repeated a single time, ".
               "and it has already been applied."
             );
           $this->transcript->addRuleTranscript($xscript);
           $rule_matches = false;
         } else {
           $rule_matches = $this->doesRuleMatch($rule, $object);
         }
       } catch (HeraldRecursiveConditionsException $ex) {
         $names = array();
         foreach ($this->stack as $rule_id => $ignored) {
           $names[] = '"'.$rules[$rule_id]->getName().'"';
         }
         $names = implode(', ', $names);
         foreach ($this->stack as $rule_id => $ignored) {
           $xscript = new HeraldRuleTranscript();
           $xscript->setRuleID($rule_id);
           $xscript->setResult(false);
           $xscript->setReason(
             "Rules {$names} are recursively dependent upon one another! ".
             "Don't do this! You have formed an unresolvable cycle in the ".
             "dependency graph!");
           $xscript->setRuleName($rules[$rule_id]->getName());
           $xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID());
           $this->transcript->addRuleTranscript($xscript);
         }
         $rule_matches = false;
       }
       $this->results[$id] = $rule_matches;
 
       if ($rule_matches) {
         foreach ($this->getRuleEffects($rule, $object) as $effect) {
           $effects[] = $effect;
         }
       }
     }
 
     $object_transcript = new HeraldObjectTranscript();
     $object_transcript->setPHID($object->getPHID());
     $object_transcript->setName($object->getHeraldName());
     $object_transcript->setType($object->getHeraldTypeName());
     $object_transcript->setFields($this->fieldCache);
 
     $this->transcript->setObjectTranscript($object_transcript);
 
     $t_end = microtime(true);
 
     $this->transcript->setDuration($t_end - $t_start);
 
     return $effects;
   }
 
   public function applyEffects(
     array $effects,
     HeraldObjectAdapter $object,
     array $rules) {
 
     $this->transcript->setDryRun($object instanceof HeraldDryRunAdapter);
 
     $xscripts = $object->applyHeraldEffects($effects);
     foreach ($xscripts as $apply_xscript) {
       if (!($apply_xscript instanceof HeraldApplyTranscript)) {
         throw new Exception(
           "Heraldable must return HeraldApplyTranscripts from ".
           "applyHeraldEffect().");
       }
       $this->transcript->addApplyTranscript($apply_xscript);
     }
 
     if (!$this->transcript->getDryRun()) {
 
       $rules = mpull($rules, null, 'getID');
       $applied_ids = array();
       $first_policy = HeraldRepetitionPolicyConfig::toInt(
         HeraldRepetitionPolicyConfig::FIRST);
 
       // Mark all the rules that have had their effects applied as having been
       // executed for the current object.
       $rule_ids = mpull($xscripts, 'getRuleID');
 
       foreach ($rule_ids as $rule_id) {
         if (!$rule_id) {
           // Some apply transcripts are purely informational and not associated
           // with a rule, e.g. carryover emails from earlier revisions.
           continue;
         }
 
         $rule = idx($rules, $rule_id);
         if (!$rule) {
           continue;
         }
 
         if ($rule->getRepetitionPolicy() == $first_policy) {
           $applied_ids[] = $rule_id;
         }
       }
 
       if ($applied_ids) {
         $conn_w = id(new HeraldRule())->establishConnection('w');
         $sql = array();
         foreach ($applied_ids as $id) {
           $sql[] = qsprintf(
             $conn_w,
             '(%s, %d)',
             $object->getPHID(),
             $id);
         }
         queryfx(
           $conn_w,
           'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
           HeraldRule::TABLE_RULE_APPLIED,
           implode(', ', $sql));
       }
     }
   }
 
   public function getTranscript() {
     $this->transcript->save();
     return $this->transcript;
   }
 
   protected function doesRuleMatch(
     HeraldRule $rule,
     HeraldObjectAdapter $object) {
 
     $id = $rule->getID();
 
     if (isset($this->results[$id])) {
       // If we've already evaluated this rule because another rule depends
       // on it, we don't need to reevaluate it.
       return $this->results[$id];
     }
 
     if (isset($this->stack[$id])) {
       // We've recursed, fail all of the rules on the stack. This happens when
       // there's a dependency cycle with "Rule conditions match for rule ..."
       // conditions.
       foreach ($this->stack as $rule_id => $ignored) {
         $this->results[$rule_id] = false;
       }
       throw new HeraldRecursiveConditionsException();
     }
 
     $this->stack[$id] = true;
 
     $all = $rule->getMustMatchAll();
 
     $conditions = $rule->getConditions();
 
     $result = null;
 
     $local_version = id(new HeraldRule())->getConfigVersion();
     if ($rule->getConfigVersion() > $local_version) {
       $reason = "Rule could not be processed, it was created with a newer ".
                 "version of Herald.";
       $result = false;
     } else if (!$conditions) {
       $reason = "Rule failed automatically because it has no conditions.";
       $result = false;
-/* TOOD: Restore this in some form?
-    } else if (!is_fb_employee($rule->getAuthorPHID())) {
-      $reason = "Rule failed automatically because its owner is not an ".
-                "active employee.";
+    } else if ($rule->hasInvalidOwner()) {
+      $reason = "Rule failed automatically because its owner is invalid ".
+                "or disabled.";
       $result = false;
-*/
     } else {
       foreach ($conditions as $condition) {
         $match = $this->doesConditionMatch($rule, $condition, $object);
 
         if (!$all && $match) {
           $reason = "Any condition matched.";
           $result = true;
           break;
         }
 
         if ($all && !$match) {
           $reason = "Not all conditions matched.";
           $result = false;
           break;
         }
       }
 
       if ($result === null) {
         if ($all) {
           $reason = "All conditions matched.";
           $result = true;
         } else {
           $reason = "No conditions matched.";
           $result = false;
         }
       }
     }
 
     $rule_transcript = new HeraldRuleTranscript();
     $rule_transcript->setRuleID($rule->getID());
     $rule_transcript->setResult($result);
     $rule_transcript->setReason($reason);
     $rule_transcript->setRuleName($rule->getName());
     $rule_transcript->setRuleOwner($rule->getAuthorPHID());
 
     $this->transcript->addRuleTranscript($rule_transcript);
 
     return $result;
   }
 
   protected function doesConditionMatch(
     HeraldRule $rule,
     HeraldCondition $condition,
     HeraldObjectAdapter $object) {
 
     $object_value = $this->getConditionObjectValue($condition, $object);
     $test_value   = $condition->getValue();
 
     $cond = $condition->getFieldCondition();
 
     $transcript = new HeraldConditionTranscript();
     $transcript->setRuleID($rule->getID());
     $transcript->setConditionID($condition->getID());
     $transcript->setFieldName($condition->getFieldName());
     $transcript->setCondition($cond);
     $transcript->setTestValue($test_value);
 
     $result = null;
     switch ($cond) {
       case HeraldConditionConfig::CONDITION_CONTAINS:
         // "Contains" can take an array of strings, as in "Any changed
         // filename" for diffs.
         foreach ((array)$object_value as $value) {
           $result = (stripos($value, $test_value) !== false);
           if ($result) {
             break;
           }
         }
         break;
       case HeraldConditionConfig::CONDITION_NOT_CONTAINS:
         $result = (stripos($object_value, $test_value) === false);
         break;
       case HeraldConditionConfig::CONDITION_IS:
         $result = ($object_value == $test_value);
         break;
       case HeraldConditionConfig::CONDITION_IS_NOT:
         $result = ($object_value != $test_value);
         break;
       case HeraldConditionConfig::CONDITION_IS_ME:
         $result = ($object_value == $rule->getAuthorPHID());
         break;
       case HeraldConditionConfig::CONDITION_IS_NOT_ME:
         $result = ($object_value != $rule->getAuthorPHID());
         break;
       case HeraldConditionConfig::CONDITION_IS_ANY:
         $test_value = array_flip($test_value);
         $result = isset($test_value[$object_value]);
         break;
       case HeraldConditionConfig::CONDITION_IS_NOT_ANY:
         $test_value = array_flip($test_value);
         $result = !isset($test_value[$object_value]);
         break;
       case HeraldConditionConfig::CONDITION_INCLUDE_ALL:
         if (!is_array($object_value)) {
           $transcript->setNote('Object produced bad value!');
           $result = false;
         } else {
           $have = array_select_keys(array_flip($object_value),
                                     $test_value);
           $result = (count($have) == count($test_value));
         }
         break;
       case HeraldConditionConfig::CONDITION_INCLUDE_ANY:
         $result = (bool)array_select_keys(array_flip($object_value),
                                           $test_value);
         break;
       case HeraldConditionConfig::CONDITION_INCLUDE_NONE:
         $result = !array_select_keys(array_flip($object_value),
                                      $test_value);
         break;
       case HeraldConditionConfig::CONDITION_EXISTS:
         $result = (bool)$object_value;
         break;
       case HeraldConditionConfig::CONDITION_NOT_EXISTS:
         $result = !$object_value;
         break;
       case HeraldConditionConfig::CONDITION_REGEXP:
         foreach ((array)$object_value as $value) {
           $result = @preg_match($test_value, $value);
           if ($result === false) {
             $transcript->setNote(
               "Regular expression is not valid!");
             break;
           }
           if ($result) {
             break;
           }
         }
         $result = (bool)$result;
         break;
       case HeraldConditionConfig::CONDITION_REGEXP_PAIR:
         // Match a JSON-encoded pair of regular expressions against a
         // dictionary. The first regexp must match the dictionary key, and the
         // second regexp must match the dictionary value. If any key/value pair
         // in the dictionary matches both regexps, the condition is satisfied.
         $regexp_pair = json_decode($test_value, true);
         if (!is_array($regexp_pair)) {
           $result = false;
           $transcript->setNote("Regular expression pair is not valid JSON!");
           break;
         }
         if (count($regexp_pair) != 2) {
           $result = false;
           $transcript->setNote("Regular expression pair is not a pair!");
           break;
         }
 
         $key_regexp   = array_shift($regexp_pair);
         $value_regexp = array_shift($regexp_pair);
 
         foreach ((array)$object_value as $key => $value) {
           $key_matches = @preg_match($key_regexp, $key);
           if ($key_matches === false) {
             $result = false;
             $transcript->setNote("First regular expression is invalid!");
             break 2;
           }
           if ($key_matches) {
             $value_matches = @preg_match($value_regexp, $value);
             if ($value_matches === false) {
               $result = false;
               $transcript->setNote("Second regular expression is invalid!");
               break 2;
             }
             if ($value_matches) {
               $result = true;
               break 2;
             }
           }
         }
         $result = false;
         break;
       case HeraldConditionConfig::CONDITION_RULE:
       case HeraldConditionConfig::CONDITION_NOT_RULE:
 
         $rule = idx($this->rules, $test_value);
         if (!$rule) {
           $transcript->setNote(
             "Condition references a rule which does not exist!");
           $result = false;
         } else {
           $is_not = ($cond == HeraldConditionConfig::CONDITION_NOT_RULE);
           $result = $this->doesRuleMatch($rule, $object);
           if ($is_not) {
             $result = !$result;
           }
         }
         break;
       default:
         throw new HeraldInvalidConditionException(
           "Unknown condition '{$cond}'.");
     }
 
     $transcript->setResult($result);
 
     $this->transcript->addConditionTranscript($transcript);
 
     return $result;
   }
 
   protected function getConditionObjectValue(
     HeraldCondition $condition,
     HeraldObjectAdapter $object) {
 
     $field = $condition->getFieldName();
 
     return $this->getObjectFieldValue($field);
   }
 
   public function getObjectFieldValue($field) {
     if (isset($this->fieldCache[$field])) {
       return $this->fieldCache[$field];
     }
 
     $result = null;
     switch ($field) {
       case HeraldFieldConfig::FIELD_RULE:
         $result = null;
         break;
       case HeraldFieldConfig::FIELD_TITLE:
       case HeraldFieldConfig::FIELD_BODY:
       case HeraldFieldConfig::FIELD_DIFF_FILE:
       case HeraldFieldConfig::FIELD_DIFF_CONTENT:
         // TODO: Type should be string.
         $result = $this->object->getHeraldField($field);
         break;
       case HeraldFieldConfig::FIELD_AUTHOR:
       case HeraldFieldConfig::FIELD_REPOSITORY:
       case HeraldFieldConfig::FIELD_MERGE_REQUESTER:
         // TODO: Type should be PHID.
         $result = $this->object->getHeraldField($field);
         break;
       case HeraldFieldConfig::FIELD_TAGS:
       case HeraldFieldConfig::FIELD_REVIEWER:
       case HeraldFieldConfig::FIELD_REVIEWERS:
       case HeraldFieldConfig::FIELD_CC:
       case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVIEWERS:
       case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
         // TODO: Type should be list.
         $result = $this->object->getHeraldField($field);
         break;
       case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
       case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
       case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
         $result = $this->object->getHeraldField($field);
         if (!is_array($result)) {
           throw new HeraldInvalidFieldException(
             "Value of field type {$field} is not an array!");
         }
         break;
       case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVISION:
         // TODO: Type should be boolean I guess.
         $result = $this->object->getHeraldField($field);
         break;
       default:
         throw new HeraldInvalidConditionException(
           "Unknown field type '{$field}'!");
     }
 
     $this->fieldCache[$field] = $result;
     return $result;
   }
 
   protected function getRuleEffects(
     HeraldRule $rule,
     HeraldObjectAdapter $object) {
 
     $effects = array();
     foreach ($rule->getActions() as $action) {
       $effect = new HeraldEffect();
       $effect->setObjectPHID($object->getPHID());
       $effect->setAction($action->getAction());
       $effect->setTarget($action->getTarget());
 
       $effect->setRuleID($rule->getID());
 
       $name = $rule->getName();
       $id   = $rule->getID();
       $effect->setReason(
         'Conditions were met for Herald rule "'.$name.'" (#'.$id.').');
 
       $effects[] = $effect;
     }
     return $effects;
   }
 
 }
diff --git a/src/applications/herald/storage/rule/HeraldRule.php b/src/applications/herald/storage/rule/HeraldRule.php
index 97e564f1f1..6d507d0ea2 100644
--- a/src/applications/herald/storage/rule/HeraldRule.php
+++ b/src/applications/herald/storage/rule/HeraldRule.php
@@ -1,210 +1,241 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 class HeraldRule extends HeraldDAO {
 
   const TABLE_RULE_APPLIED = 'herald_ruleapplied';
 
   protected $name;
   protected $authorPHID;
 
   protected $contentType;
   protected $mustMatchAll;
   protected $repetitionPolicy;
   protected $ruleType;
 
   protected $configVersion = 8;
 
   private $ruleApplied = array(); // phids for which this rule has been applied
+  private $invalidOwner = false;
 
   public static function loadAllByContentTypeWithFullData(
     $content_type,
     $object_phid) {
 
     $rules = id(new HeraldRule())->loadAllWhere(
       'contentType = %s',
       $content_type);
 
     if (!$rules) {
       return array();
     }
 
+    self::flagDisabledUserRules($rules);
+
     $rule_ids = mpull($rules, 'getID');
 
     $conditions = id(new HeraldCondition())->loadAllWhere(
       'ruleID in (%Ld)',
       $rule_ids);
 
     $actions = id(new HeraldAction())->loadAllWhere(
       'ruleID in (%Ld)',
       $rule_ids);
 
     $applied = queryfx_all(
       id(new HeraldRule())->establishConnection('r'),
       'SELECT * FROM %T WHERE phid = %s',
       self::TABLE_RULE_APPLIED,
       $object_phid);
     $applied = ipull($applied, null, 'ruleID');
 
     $conditions = mgroup($conditions, 'getRuleID');
     $actions = mgroup($actions, 'getRuleID');
     $applied = igroup($applied, 'ruleID');
 
     foreach ($rules as $rule) {
       $rule->setRuleApplied($object_phid, isset($applied[$rule->getID()]));
 
       $rule->attachConditions(idx($conditions, $rule->getID(), array()));
       $rule->attachActions(idx($actions, $rule->getID(), array()));
     }
 
     return $rules;
   }
 
+  private static function flagDisabledUserRules(array $rules) {
+
+    $users = array();
+    foreach ($rules as $rule) {
+      if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
+        continue;
+      }
+      $users[$rule->getAuthorPHID()] = true;
+    }
+
+    $handles = id(new PhabricatorObjectHandleData(array_keys($users)))
+      ->loadHandles();
+
+    foreach ($rules as $key => $rule) {
+      if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
+        continue;
+      }
+      $handle = $handles[$rule->getAuthorPHID()];
+      if (!$handle->isComplete() || $handle->isDisabled()) {
+        $rule->invalidOwner = true;
+      }
+    }
+  }
+
   public function getRuleApplied($phid) {
     if (idx($this->ruleApplied, $phid) === null) {
       throw new Exception("Call setRuleApplied() before getRuleApplied()!");
     }
     return $this->ruleApplied[$phid];
   }
 
   public function setRuleApplied($phid, $applied) {
     $this->ruleApplied[$phid] = $applied;
     return $this;
   }
 
   public function loadConditions() {
     if (!$this->getID()) {
       return array();
     }
     return id(new HeraldCondition())->loadAllWhere(
       'ruleID = %d',
       $this->getID());
   }
 
   public function attachConditions(array $conditions) {
     $this->conditions = $conditions;
     return $this;
   }
 
   public function getConditions() {
     // TODO: validate conditions have been attached.
     return $this->conditions;
   }
 
   public function loadActions() {
     if (!$this->getID()) {
       return array();
     }
     return id(new HeraldAction())->loadAllWhere(
       'ruleID = %d',
       $this->getID());
   }
 
   public function attachActions(array $actions) {
     // TODO: validate actions have been attached.
     $this->actions = $actions;
     return $this;
   }
 
   public function getActions() {
     return $this->actions;
   }
 
   public function loadEdits() {
     if (!$this->getID()) {
       return array();
     }
     $edits = id(new HeraldRuleEdit())->loadAllWhere(
       'ruleID = %d ORDER BY dateCreated DESC',
       $this->getID());
 
     return $edits;
   }
 
   public function attachEdits(array $edits) {
     $this->edits = $edits;
     return $this;
   }
 
   public function getEdits() {
     if ($this->edits === null) {
       throw new Exception("Attach edits before accessing them!");
     }
     return $this->edits;
   }
 
   public function saveEdit($editor_phid) {
     $edit = new HeraldRuleEdit();
     $edit->setRuleID($this->getID());
     $edit->setEditorPHID($editor_phid);
     $edit->save();
   }
 
   public function saveConditions(array $conditions) {
     return $this->saveChildren(
       id(new HeraldCondition())->getTableName(),
       $conditions);
   }
 
   public function saveActions(array $actions) {
     return $this->saveChildren(
       id(new HeraldAction())->getTableName(),
       $actions);
   }
 
   protected function saveChildren($table_name, array $children) {
     if (!$this->getID()) {
       throw new Exception("Save rule before saving children.");
     }
 
     foreach ($children as $child) {
       $child->setRuleID($this->getID());
     }
 
 // TODO:
 //    $this->openTransaction();
       queryfx(
         $this->establishConnection('w'),
         'DELETE FROM %T WHERE ruleID = %d',
         $table_name,
         $this->getID());
       foreach ($children as $child) {
         $child->save();
       }
 //    $this->saveTransaction();
   }
 
   public function delete() {
 
 // TODO:
 //    $this->openTransaction();
       queryfx(
         $this->establishConnection('w'),
         'DELETE FROM %T WHERE ruleID = %d',
         id(new HeraldCondition())->getTableName(),
         $this->getID());
       queryfx(
         $this->establishConnection('w'),
         'DELETE FROM %T WHERE ruleID = %d',
         id(new HeraldAction())->getTableName(),
         $this->getID());
       parent::delete();
 //    $this->saveTransaction();
   }
 
+  public function hasInvalidOwner() {
+    return $this->invalidOwner;
+  }
+
 }
diff --git a/src/applications/herald/storage/rule/__init__.php b/src/applications/herald/storage/rule/__init__.php
index 7078b9f324..d9227c43d1 100644
--- a/src/applications/herald/storage/rule/__init__.php
+++ b/src/applications/herald/storage/rule/__init__.php
@@ -1,18 +1,20 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
+phutil_require_module('phabricator', 'applications/herald/config/ruletype');
 phutil_require_module('phabricator', 'applications/herald/storage/action');
 phutil_require_module('phabricator', 'applications/herald/storage/base');
 phutil_require_module('phabricator', 'applications/herald/storage/condition');
 phutil_require_module('phabricator', 'applications/herald/storage/edithistory');
+phutil_require_module('phabricator', 'applications/phid/handle/data');
 phutil_require_module('phabricator', 'storage/queryfx');
 
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('HeraldRule.php');