Staff saw the record in the list, then got not found
A branch check compared two different kinds of value and denied everyone. Fixing it led to 37 actions a branch user could have taken on other branches' records.
A member of staff limited to one branch clicks a record that belongs to their own branch, and the system says “not found”. A moment earlier the same record was in their list. The system is telling them that it exists and that it does not, depending on the screen.
That sounds like a small fault, and the support reply usually sounds sensible too: “I can’t see my own record” looks like a setting gone wrong, and the quick answer is to give the person wider access. That hides the bug for good and widens their permissions for real. We went looking for the cause instead, and while fixing it we found thirty-seven places where a branch user could have acted on another branch’s orders and invoices.
What was actually going on
Which branches a user may see is stored as a list of text values. The application read the record’s branch through a layer that hands it back as a different kind of value, an identifier object rather than text. The check was comparing one to the other, and the two are never equal in any language that tells them apart, even when they spell the same thing. So every branch-limited user was refused every record, including their own.
Three things hid it. The list screen worked, because it asked the database to do the comparison, and the database treats the two as the same. The same check was written twice, in two places with different rules, and only one was wrong. Second, it failed closed: it denied instead of allowing, so nothing leaked and nothing raised an alarm, and nobody watches for denials that should have been allowed. Third, a review had described one set of screens with this check as stricter than their parent screens. They were stricter. They were refusing everything. Stricter and broken look the same from outside.
The same hand-written comparison later turned up in a completely different part of the product, with the same silence and the same result.
What we changed
We deleted both hand-written copies and sent everything through one shared branch check, including the synchronisation path, so the routes cannot drift apart later. Anywhere the same permission rule is written in two places is a future disagreement with a date on it.
Then we audited every screen and action that takes a record’s id. The list of known gaps named three. The audit found thirty-seven: ten ways of reading, and twenty-seven ways of changing, including confirming and cancelling orders, packing and delivering dispatches, issuing and voiding invoices, allocating payments, and renaming and deleting branches. Whoever compiled the original list was not careless. They had listed the ones they had run into, and these gaps are missed one action at a time, so the only reliable way to find them is to count every action.
The shared check answers “not found” rather than “forbidden”, because “forbidden” confirms the record exists. Someone guessing ids learns nothing from “not found”. One action needed the other answer: setting a stock reorder level takes the target branch from what was sent, not from the address, so a branch operator could have set it for any depot in the organisation. There the record in the address is legitimately visible, and what is refused is the branch named in the request.
What it did not fix
A check that wrongly refuses produces no alert and looks like user error, and as far as this write-up records, nothing in our monitoring watches for it. What catches it is a test that proves the right person is let in.
The pattern, for anyone who limits staff by branch
For every permission rule, ask for two tests: one proving the wrong person is refused, and one proving the right person is admitted. Almost everyone writes the first. The second is what catches a check that has quietly become “always no”.
Ask too whether the list and the detail screen always agree about whether a record exists. And when staff say “I can’t see my own record”, find out why before you widen their access.
Where this ends up
Branches, orders, dispatches and invoices that each belong to a branch are the ordinary case in Sazinga Field, which is why the rule that a record in the list and the record on its own screen must agree is one its checks are written against.