Measured Behaviour

Does obfuscation break organ allocation ranking?

Every other area in this pass ends in a permission: a unit issued or held, a donor bled or turned away, a date live or expired. A match run issues no permission at all. Every candidate is eligible before it and every candidate is still eligible after it. What it produces is a SEQUENCE - and a sequence does not appear on any report that anybody reconciles.

There is no wrong answer to look for

The reason this area closes the pass is that it removes the thing every other area gives you: an outcome you can check.

A match run takes the candidates who are compatible with an organ, scores each on medical urgency, waiting time, tissue match and paediatric status, sorts by the total, and offers down the list until somebody accepts. Nobody is refused. Nobody is removed. The list at the end contains exactly the people it contained at the start, every point total is honest arithmetic on honest quantities, and the person who accepted is a real candidate who was really entitled to that organ.

The only thing that can be wrong is who was asked first, and there is no line on the report that says what the order should have been.

And the cost is not paid by the person the mistake is about. An organ has a cold ischaemic clock running from the moment it is recovered, and every offer that is made and declined spends an hour of it. Rank the wrong people first and the organ can run out of time before it reaches anybody who would have taken it. Nothing is then transplanted into the wrong patient. Nothing is transplanted at all.

What we built and what the correct run says

One kidney, group O, four hours into its cold time. Twelve candidates. Two are not ABO-compatible with it - they are on the waiting list, they are simply not on THIS list. Two are outside the two-hundred-and-fifty-mile circle, both of whom would take the organ and both of whom are hours of flying away, which is what the circle exists to price. Eight are inside it.

The correct run ranks ten, offers to three, and the organ goes to K-03: tier four, paediatric, thirty miles away, 42.84 points. K-03 is not the top of the list. Two candidates on forty-four points each are ahead, and both decline, which is entirely ordinary - a match run walks down a list. Each decline costs an hour, so the organ reaches K-03 at six cold hours with two to spare against an eight-hour limit. Both price counters read zero: no organ discarded, and the candidate the ranking exists to reach is the candidate who got it.

Losing a weight skips the person the policy was written for

The weights are the policy. Medical urgency is weighted eight because urgency is what the ranking is FOR; waiting time and tissue match adjust it; paediatric status is weighted four because a whole allocation policy was rewritten to put that thumb on the scale.

We removed the weight table. A factor the table does not carry weighs one, which is the only reasonable thing a library can do and is also, for a factor whose real weight is eight, a way of deleting it. The ordering inverted: OFFERS went from 3 to 1, and the organ went to K-04 - a tier-two adult who has waited six years, two hundred and ten miles away.

Read that outcome carefully, because the shape of it is the point. K-04 is a real candidate with a real claim. Nothing about the run looks like a failure. It looks BETTER than the correct run: one offer instead of three, four cold hours instead of six, no declines to chase at two in the morning. An operations dashboard measuring offers-per-placement and cold time would record this as the best run of the week.

Removing the urgency tier from the candidate records instead sent the organ to K-08 and left a diagnostic counter reading ten factors not scored. Both arms bypassed the candidate the ranking was written to reach; neither refused anybody.

Three different names, one nine-hundred-mile destination

The distance circle is the part of an allocation policy that most directly reflects the clock. It is expressed as three things: a radius, a flag that says whether to apply it, and the distance on each candidate record.

We removed each of the three separately. All three produced the same outcome: the organ went to K-09, a tier-four candidate with a perfect tissue match nine hundred nautical miles away, on the first offer. K-09 outscores everybody - that is why the circle exists rather than a pure points ranking, because the points do not know how long a flight takes.

Three names, one destination, is a useful thing to know when you are deciding how narrow a rename pattern needs to be. It also means the arm is easy to find IF you look at who accepted. It is invisible if you look at whether the run completed, how many candidates were ranked, or whether the points arithmetic checks out. Ten candidates were ranked in every one of these arms, and in every one of them the points were correct.

The blood group filter behaved the same way from two names. Removing the group requirement or the group field on the candidate records both took RANKED from 10 to 12 and gave a group O kidney to K-11, a group A candidate. That one at least has a downstream check that would catch it - the transfusion service's own compatibility rules, measured in the companion article, and every reason to hope they are not renamed in the same build.

The arm that reads as noise, and the arm that reads as harmless

Two results in this area are worth more than the dramatic ones, because they are the ones a review would close.

The first is the tie-break. Two of our candidates score exactly forty-four points by different routes, and the tie-break decides which of them is telephoned first. Removing it left every counter in the area IDENTICAL - same offers, same acceptor, same cold time, both price counters at zero. The only thing that moved was the order of two declines. That is a finding a reviewer closes as cosmetic in about four seconds, and on this fixture they would be right. Change one of those two candidates to somebody who accepts and it decides who gets the organ.

The second is the cold clock. We removed the field carrying how long the organ has been out. COLD-AT-END went from 6.0 to NaN - visibly broken, on the report, in the run line. The acceptor did not change. Nothing was discarded. Both price counters stayed at zero, because K-03 accepts within three offers and three offers fit inside any limit.

So the arm that destroys the safety clock outright measures HARMLESS on our fixture, and it is the arm most likely to end an organ. It is harmless here only because this run is short. A run with seven declines against an eight-hour limit is the run that matters, and the limit is exactly what stopped working.

This is the general warning about reading a matrix and it is worth stating in its own sentence: an arm that measures inert is no more evidence of safety than a control that measures inert. It is evidence about the fixture.

Why the ordinary checks do not reach this

The service's own consistency rule in our fixture asks whether candidates were ranked and whether an offer was made. It passed in every arm above, truthfully. Ten candidates were ranked. An offer was made. Somebody accepted.

A reconciliation between the waiting list before the run and the waiting list after it also passes, and it passes BY CONSTRUCTION: a match run does not remove anybody. Earlier work in this series called this shape a reconciliation that is closed by construction, and the conclusion there applies here without modification - such a check is an invariant of the code, not evidence about the result.

A plausibility bound on the points does not reach it either. Every total the run produced is a number that candidate could legitimately have scored under a different weighting, and one of the arms produced a set of totals that were correct in every term and merely weighted one to one.

What does reach it is comparing WHO ACCEPTED against an independently held expectation. That is the only counter in this area that moved on the arms that mattered, and it works because it is held outside the bundle under a flat string key that no transformation can move.

What this means if you protect allocation software

Protect the bundle - the argument for doing so is not weakened by anything here. Then keep the rename pattern away from the names an installed ranking library reads and from the fields on the candidate and organ records you hand it. Scope the member pattern, or supply an explicit member mapping instead of a broad regular expression.

The thing this area adds to that advice is about how you VERIFY. Every test that asks whether the run finished, whether the totals add up, whether the list is intact, or whether anything looks implausible will pass on a build where the ordering has silently inverted. The test that catches it is a fixed scenario with a known correct acceptor, asserted by name.

There is also a design point that costs nothing and pays regardless of obfuscation, because a comparator handed a key it cannot read returns NaN whether the cause is a rename, a schema change or a partially populated import. Make a comparator throw on an unreadable ordering key rather than returning zero. A sort told nothing leaves the pair where it found them, and where it found them is whatever order the database happened to return - which is registration order, which is nobody's policy.

And build the fixture with declines in it. Our negative control is the same twelve people on a run where the first-ranked candidate accepts. One offer, no declines, no cold time spent, and no part of the ordering past the top entry ever consulted. It is the run that goes smoothly, it is what most test data looks like, and an ordering only starts to matter from the second entry down.

Frequently asked questions

Can obfuscation change who receives an organ?

It can change the order candidates are offered in, if a renaming pattern reaches the weight table or the fields on the candidate records. In our measurement, removing the weight table sent the organ to a different candidate on the first offer, with every point total still honest arithmetic and every candidate still on the list.

Why is a ranking harder to test than a permission?

Because there is no refusal to look for. Nobody is removed and nobody is rejected, so the run produces a complete, plausible report whichever order it used. The only thing that moved is the sequence, and no line of the report states what the sequence should have been.

What happens to the distance circle?

In our fixture the radius, the flag that applies it and the distance field on the candidate records all produced the same outcome - the organ went to a candidate nine hundred nautical miles away on the first offer, because that candidate outscores everybody and the points do not know how long a flight takes.

Does losing the cold ischaemic clock show up in testing?

On our fixture it did not change the outcome at all, because the organ was accepted within three offers. The clock value printed as NaN and nothing else moved. It is the arm most likely to end an organ on a run with many declines, and the one most likely to be closed as harmless on a run with few.

Which finding is a reviewer most likely to dismiss?

The tie-break. Removing it left every counter identical and moved only the order of two declines, which reads as cosmetic. It is cosmetic on that fixture and decides who receives the organ on a fixture where either of those two candidates accepts.

Do the usual sanity checks catch an ordering change?

No. A consistency rule asking whether candidates were ranked and an offer was made passes truthfully in every arm. A reconciliation between the list before and after passes by construction, because a match run removes nobody. A plausibility bound on the points passes because every total is a total somebody could legitimately have scored.

What should we assert in a test instead?

A fixed scenario with a known correct acceptor, asserted by name against an expectation held outside the code under test. That was the only counter in our fixture that moved on the arms that mattered. It is also worth making a comparator throw on an unreadable ordering key rather than returning zero, since a sort told nothing preserves arrival order.

Related reading