FifeRouter

1 September 2026 · routing constraints

Failing open without failing unconstrained

An unclassifiable request must not error. It must also not become a request with no rules. Those two sentences are one careless word apart.

The classifier will sometimes not know. Somebody sends something that fits no concept, or fits three equally, or is four words long. The system has to do something, and there are two obvious answers.

Fail closed. Refuse what you cannot categorise. Safe, defensible, and it means a typo in a prompt becomes an outage for whoever sent it.

Fail open. Route it somewhere sensible and carry on.

We fail open. CON-fail-open-on-classification says an unclassifiable request goes to the default concept and is logged for taxonomy review. That is the easy half of the decision.

The word that does the damage

"Fail open" has two readings, and only one of them is right.

The first: unclassified. We do not know which concept this is, so treat it as the default one — with the default concept's constraints, capabilities and policy row, all of which apply exactly as they would to anything else.

The second: unconstrained. We do not know what this is, so let it through.

The second reading is how a fail-open policy turns into a hole. Nobody writes if unknown: skip_filters(). What happens is subtler: the unclassified path becomes a special case, the special case is written by someone thinking about availability rather than about residency, and a request nobody could categorise ends up reaching a model that a categorised request could not.

Which means "I could not tell what this was" becomes a way to get past the constraints — and an attacker does not need to guess that; they only need to send something ambiguous.

What holds it

general.assist is a concept like any other. It has a policy row, a candidate list and an objective. Falling back to it is not falling out of the system, it is landing in a normal part of it.

And the filters run afterwards, on that concept's constraints. If the request also carried x-fife-critical, residency still pins to in_infra and the vendor models are still eliminated — because criticality is a property of the request, not of the classification, and it did not become less true when the classifier shrugged.

The test is named for the distinction:

tests/router/test_router.py::TestFailOpen
    ::test_failing_open_does_not_mean_failing_unconstrained

It exists because the two meanings are one careless sentence apart, and the sentence is in the constraint document, which is exactly where somebody implementing this would read it and take the wrong half.

The record, and why it matters more than the routing

Every fall-through is logged for taxonomy review. Not as an error — nothing failed — but as the signal that a category is missing.

That log is the input to the only mechanism that fixes this properly. A steady trickle of unclassifiable requests that all look alike is a concept nobody has written yet, and the fix is a file with a description and four exemplars, not a better classifier.

Without the log you get a system that is quietly correct: it routes the ambiguous traffic somewhere reasonable, forever, and nobody ever discovers that 15% of requests were a category the taxonomy should have had.

The general shape

This is the same trade as a 404 that leaks whether a resource exists, or a retry that turns a transient failure into a duplicate charge. The behaviour everyone agrees on — do not error, degrade gracefully — has a second half that nobody states, and the second half is where the bug lives.

Degrade gracefully into what. If the answer is "into a path with fewer rules", the graceful degradation is a bypass with better manners.


← All posts