diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json
index ce17ffe5..ab55b0b6 100644
--- a/app/config/schema/schema.json
+++ b/app/config/schema/schema.json
@@ -162,7 +162,6 @@
         "index_display": { "type": "boolean" },
         "alphanumeric": { "type": "boolean" },
         "case_sensitive": { "type": "boolean" },
-        "invalidates": { "type": "boolean" },
         "null_equivalents": { "type": "boolean" },
         "search_distance": { "type": "integer" },
         "search_exact": { "type": "boolean" },
diff --git a/app/resources/locales/en_US/default.po b/app/resources/locales/en_US/default.po
index b3a9fc8a..4893d51d 100644
--- a/app/resources/locales/en_US/default.po
+++ b/app/resources/locales/en_US/default.po
@@ -260,6 +260,9 @@ msgstr "Exact"
 msgid "match.en.SearchTypeEnum.M"
 msgstr "Mapping"
 
+msgid "match.en.SearchTypeEnum.IN"
+msgstr "Invalidate"
+
 msgid "match.en.SearchTypeEnum.X"
 msgstr "Skip"
 
@@ -492,9 +495,6 @@ msgstr "ID"
 msgid "match.fd.index_display"
 msgstr "Display Field in Matchgrid Index"
 
-msgid "match.fd.invalidates"
-msgstr "Invalidates"
-
 msgid "match.fd.label"
 msgstr "Label"
 
diff --git a/app/src/Lib/Enum/SearchTypeEnum.php b/app/src/Lib/Enum/SearchTypeEnum.php
index 5e97df6a..11f80299 100644
--- a/app/src/Lib/Enum/SearchTypeEnum.php
+++ b/app/src/Lib/Enum/SearchTypeEnum.php
@@ -30,9 +30,10 @@
 namespace App\Lib\Enum;
 
 class SearchTypeEnum extends StandardEnum {
-  const Distance  = 'D';
-  const Exact     = 'E';
-  const Mapping   = 'M';
-  const Skip      = 'X';
-  const Substring = 'S';
+  const Distance    = 'D';
+  const Exact       = 'E';
+  const Invalidate  = "IN";
+  const Mapping     = 'M';
+  const Skip        = 'X';
+  const Substring   = 'S';
 }
\ No newline at end of file
diff --git a/app/src/Lib/Match/MatchService.php b/app/src/Lib/Match/MatchService.php
index 30ef9772..5928c32f 100644
--- a/app/src/Lib/Match/MatchService.php
+++ b/app/src/Lib/Match/MatchService.php
@@ -625,7 +625,9 @@ protected function search(string $mode,
       ];
       
       foreach($rule->rule_attributes as $ruleattr) {
-        if($ruleattr->search_type == SearchTypeEnum::Skip) {
+        if($ruleattr->search_type == SearchTypeEnum::Skip
+           // We skip invalidate rules as well, these are handled in searchReferenceId()
+           || $ruleattr->search_type == SearchTypeEnum::Invalidate) {
           continue;
         }
         
@@ -849,20 +851,80 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
       skipSor: $trustMode == TrustModeEnum::Trust
     );
 
+    // Was a canonical match downgraded?
+    $downgraded = false;
+
     switch($canonicalMatches->count()) {
       case 1:
+        // Before returning this canonical match, we need to perform some checks that
+        // might demote the result to potential. Note that $results may have more than
+        // one row even for a canonical match, since there may be multiple matchgrid
+        // entries that point to the same Reference ID.
+
+        $results = $canonicalMatches->getRawResults();
+
+        // First, check if any Attribute Rules in "Invalidate" mode will demote this result.
+
+        foreach($results as $rowId => $attrs) {
+          // Find the rule that generated this match.
+
+          $rulename = $canonicalMatches->getRuleForResult($rowId);
+
+          foreach($this->mgConfig->canonical_rules as $rule) {
+            if($rule->name == $rulename) {
+              // This is the correct rule, check the Rule Attributes. Note there could
+              // be more than one Rule Attribute with Search Type "Invalidate".
+
+              foreach($rule->rule_attributes as $ruleAttribute) {
+                if($ruleAttribute->search_type == SearchTypeEnum::Invalidate) {
+                  // The name of the attribute for this Rule Attribute, eg "dob".
+                  // This is the column name, not the API name.
+                  $ruleAttributeName = $ruleAttribute->attribute->name;
+
+                  // The value in the search request
+                  $searchValue = $attributes->getValueByAttribute($ruleAttribute->attribute);
+                  // The value in the matched row
+                  $rowValue = $results[$rowId][$ruleAttributeName];
+
+                  // We only perform the validation check if both values are not empty.
+                  if(!empty($searchValue) 
+                     && !empty($rowValue)
+                     && ($searchValue !== $rowValue)) {
+                    // The returned value does not match, invalidate the request and drop
+                    // the result to potential
+
+                    Log::write('debug', $sor . "/" . $sorid . " Invalidate mode for $ruleAttributeName ($rulename) downgrading result to Potential");
+
+                    $downgraded = [
+                      'rule' => "_invalidate",
+                      'attrs' => $attrs
+                    ];
+
+                    // No need to continue with any loop
+                    break 3;
+                  }
+                }
+              }
+
+              // We found the rule we're looking for, no need to continue with the loop
+              break;
+            }
+          }
+
+          // If we have multiple rows, we should have the same rule for each row,
+          // so we can stop after the first.
+          break;
+        }
+
+        if(!$downgraded) {
+          // Next we need to handle Potential Trust Mode, check to see if any result row
+          // matched the SOR.
+
         if($trustMode != TrustModeEnum::Potential) {
           // Exact match, return
           return $canonicalMatches;
         }
 
-        // Handling Potential mode is a bit more complicated. First, get the
-        // raw results. Even though count() was 1, $results may have more than
-        // one entry since we're getting the per-row matches. Check to see if
-        // _any_ row matched the SOR.
-
-        $results = $canonicalMatches->getRawResults();
-
         foreach($results as $rowId => $attrs) {
           if($attrs['sor'] == $sor) {
             $sorMatch = $attrs;
@@ -876,8 +938,12 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
           return $canonicalMatches;
         }
 
+          $downgraded = [
+            'rule' => "_trustmode",
+            'attrs' => $sorMatch
+          ];
         Log::write('debug', $sor . "/" . $sorid . " Trust Mode downgrading result to Potential");
-
+        }
         break;
       case 0:
         // Fall through and try potential matches
@@ -900,11 +966,11 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
       skipSor: $trustMode == TrustModeEnum::Trust
     );
 
-    // Add in the potential match from above, if configured
+    // Add in the downgraded potential match from above, if configured
 
-    if($sorMatch) {
+    if($downgraded !== false) {
       // We use a psuedo-rule name
-      $potentialMatches->add($sorMatch, "_trustmode");
+      $potentialMatches->add($downgraded['attrs'], $downgraded['rule']);
     }
     
     // The calling code generally checks to see if any rules successfully ran,
diff --git a/app/src/Lib/Match/ResultManager.php b/app/src/Lib/Match/ResultManager.php
index 00a70979..313e15cc 100644
--- a/app/src/Lib/Match/ResultManager.php
+++ b/app/src/Lib/Match/ResultManager.php
@@ -39,12 +39,16 @@ class ResultManager {
   // for the same referenceId). Attributes are stored in the same format as AttributeManager
   // to faciliate wire representation.
   protected $results = [];
+  // Track which rule created which results. This will only be populated for rules
+  // that generated results.
+  protected $resultRules = [];
   // Keep the raw results as well, to facilitate UI representation.
   protected $rawResults = [];
   // "canonical" or "potential"
   protected $confidenceMode = null;
   protected $attrconfig = [];
-  // We track which rules ran successfully as a way of validating the inbound request
+  // We track which rules ran successfully as a way of validating the inbound request.
+  // This includes rules that did not generate any results.
   protected $successfulRules = [];
   
   /**
@@ -97,6 +101,8 @@ public function add(array $attributes, string $rule=null) {
     }
     
     $this->results[$referenceId][$rowId] = $parsed;
+
+    $this->resultRules[$rowId] = $rule;
     
     $this->rawResults[$rowId] = $attributes;
   }
@@ -274,6 +280,18 @@ public function getResultsForJson($mode="search") {
     return $ret;
   }
   
+  /**
+   * Get the name of the rule that generated the result for the specified row ID.
+   * 
+   * @since  COmanage Match v1.2.0
+   * @param  int    $rowId  Row to return rule for
+   * @return string         Rule name (if found) or false
+   */
+
+  public function getRuleForResult(int $rowId) {
+    return $this->resultRules[$rowId] ?? false;
+  }
+
   /**
    * Obtain an array of successfully executed rules as reported to the ResultManager.
    * Note that successful rules may have generated no matches.
diff --git a/app/src/Model/Table/RuleAttributesTable.php b/app/src/Model/Table/RuleAttributesTable.php
index 03c6f828..9f671f44 100644
--- a/app/src/Model/Table/RuleAttributesTable.php
+++ b/app/src/Model/Table/RuleAttributesTable.php
@@ -122,6 +122,7 @@ public function validationDefault(Validator $validator): Validator {
       [ 'rule' => [ 'inList', [
         SearchTypeEnum::Distance,
         SearchTypeEnum::Exact,
+        SearchTypeEnum::Invalidate,
         SearchTypeEnum::Mapping,
         SearchTypeEnum::Skip,
         SearchTypeEnum::Substring
diff --git a/app/templates/Attributes/fields.inc b/app/templates/Attributes/fields.inc
index e412c5c6..9fb5c8f3 100644
--- a/app/templates/Attributes/fields.inc
+++ b/app/templates/Attributes/fields.inc
@@ -37,8 +37,6 @@ if($action == 'add' || $action == 'edit') {
   
   print $this->Field->control('alphanumeric', [], false);
   print $this->Field->control('case_sensitive', [], false);
-// CO-1762
-//  print $this->Field->control('invalidates', [], false);
   print $this->Field->control('null_equivalents', [], false);
   
   print $this->Field->control('search_distance', [], false);