Can you spot the liar? It's the end of the year, you and your friends have just received your grades and ranks for the GOLF101 class. On a chat group, everyone sends their grade and rank to see who's got the best one.
Problem: someone is lying.
Here is an excerpt from the chat:
&lt;A&gt; I'm ranked 1 with a grade of 85.
&lt;B&gt; Ranked 3, got 50.
&lt;C&gt; Ranked 1st, prof. gave me 65/100.
&lt;D&gt; Got ranked second with 70/100

Obviously here, student C is lying (at least) on their rank; they can't be 1st with a grade of 65 since both A and D got a better grade.
There can also be cases where someone is lying but we can't know who, specifically.
Rank | Grade
-------------
  2     71
  1     80
  3     60
  2     70

Here, one of the two rank-2 students is lying (since two students can't have the same rank while having different grades) but we can't know which one.
Challenge
Create a function or program that, when given a sequence of (rank, grade) tuples which contains exactly one lie, returns the index of the lie in the list or a sentinel value if is impossible to know.
A lie is a tuple that, when removed from the list, makes the list valid (i.e. without any conflicting ranks/grades).
A valid list contains all the possible ranks starting from 1 (or 0 if you use 0-indexing), so the rank sequence 1 2 3 is valid while 2 3 4 is not. The only way to not have every possible rank is when there are equal grades, in which case you can get sequences like 1 2 2 or 1 2 3 3 3.
Multiple tuples may have the same grade, in which case they will have the same rank, and other tuples' ranks will not be affected. Two tuples having the second-best grade will lead to the sequence 1 2 2 4 ....
The input format is not fixed, you may use whatever is easier to parse for your language (a 2D int array, a list of tuples, a list of strings containing space-separated ints). The logic is more important than the parser's implementation details.
The output format is not fixed either. The &quot;index&quot; returned may be 0-based or 1-based, and the sentinel value can be anything that is clearly distinguishable from an index (if you're using 1-based indexing, 0 is a valid sentinel value).
Test Cases
Rank | Grade
-------------
  2     71
  1     80
  3     60
  2     70

Output: impossible to know
Rank | Grade
-------------
  1     85
  3     50
  1     65
  2     70

Output: third student
Rank | Grade
-------------
  1     85
  3     50
  1     65
  2     70

Output: third student
Additional cases, thanks to @tsh:

[(1, 100), (2, 60), (3, 90), (3, 90)] -&gt; invalid input because we can't remove a single item to get a valid list
[(1, 100), (1, 100), (2, 90), (3, 90)] -&gt; fourth (if we remove the third we get an invalid list with ranks [1, 1, 3])

This is code-golf, so the lowest byte count for each language wins!
EDIT: printing multiple values is a valid sentinel as long as you specify it. 0-indexing for ranks is fine too.
J, 49 47 22 bytes
[:I.1(-:\:~i.])/@|:\.]

Try it online!
-14 bytes after reading xigoi's idea of comparing the first index matches to the ranks.
Thanks to tsh for a test case showing that we needed &quot;length not equal to 1&quot; rather than &quot;length greater than 1&quot; as the failure criterion.
Returns 0-based index if exactly one liar is found, and 0 or multiple values otherwise.  Takes &quot;length&quot; by 2 matrix with 0-indexed rank list as left column, score list as right column.

1...\.] For each 1-item outfix (list with item removed), apply the verb...
(...)/@|: Transpose and put the verb in parens between the two rows, so that the rank list is its left arg, and the score list is its right arg.
-:\:~i.] Does the rank list match -: the first index of the score list i.] within the score list sorted descending \:~?  We now have a 0-1 list, where 1 indicates a liar.
[:I. Return the indexes of all ones.