Heatrail

Chapter 08

Reasons you can read

A score is a number, and a number is not an explanation. The part of a Heatrail response worth reading is the list beside it: the reasons that fired, in words, in the same JSON, on the same round trip. This article is about why those sentences exist, why nobody at Heatrail can change the rules behind them without shipping code, and why the last step is a person rather than a rule.

The property

Every score comes from a rule that can be named, and the response names it. There is no model in the scoring path. The rules cannot be changed through the API. A person in the shop decides what to do.

Why it holds

What actually arrives at the shop

An order comes in. The shop's own server calls the check endpoint while the basket is still on screen, and what comes back is a small object: a score, a short list of reasons, an opaque handle for the matched trail. Nothing else happens. Heatrail does not touch the order or the payment and has no way to (see advisory by construction). What follows is the shop's decision, taken in the shop's own code.

In practice the response lands in a queue. The shop's rule sends this order somewhere a human will look at it, and the human reads the reasons. That is where the design pressure comes from: if a person is going to read the output, the useful artefact is the sentence rather than the number. A score of 72 tells a fulfilment clerk nothing they can argue with. A line saying that the same customer identifier matched a network confirmed fraud trail, with how many shops it touched and how recently, is something a person can weigh against the order in front of them, agree with, or reject.

So the reasons are not a debugging aid bolted onto a scoring service. The number is a convenience for routing. The sentences are what gets a decision made, and what gets it defended later if anyone asks how it was reached.

Figure 1

A check response carries a score and reasons in words. It goes to a review queue, and never to the payment provider.A card at the top left shows a check response with a score field and a reasons list holding two sentences in a monospaced typeface. A solid arrow leaves the bottom of the card and enters a wide box below labelled review queue in the shop, which contains a drawing of one person. To the right of that box sits a second box labelled payment provider. No arrow reaches it. A dashed line leaves the right edge of the response card, turns downwards towards the payment provider box and stops short, crossed out, under the words nothing is sent this way.Check response, to the shop that asked"score": 72"reasons": [Customer identifier matched a network-confirmedfraud trail, 2 shop(s) affected, 3 d agoSame identifier seen in 3 shops within 24 h]nothing is sent this waythe shop routes itReview queue in the shopthe reasons are read, then a person decides whathappens to this orderor a rule the shop wrote itself routes it onwardone personPayment providerauthorises the chargeand never hears fromHeatrail at all
The response goes to a person, or to a rule the shop wrote itself. Nothing goes to the payment.

The rules ship as code, not as settings

Scoring configuration lives versioned in the repository. It changes when someone writes a commit and deploys it, and every such change writes an event into the append only ledger, so what the rules were on any given day is evidence rather than a memory. There is no settings screen for it, no toggle in the operator console, no field an administrator can nudge before lunch.

The rules endpoint follows from that. It exists so an operator can read the configuration and the log of its changes, and it is read only by construction: there is no write route to reach. Claims like that are usually made about intentions rather than about shape, and this one is about shape. A complete takeover of an operator account, full credentials, full session, every step up passed, still cannot change how anyone is scored. The attacker in that scenario is looking for a write path that was never built. Changing scoring means a commit, a deploy, and a ledger entry naming the moment.

What a reason is allowed to say

A reason is a category about the order in front of this shop. It says that something matched, roughly how widely and roughly how recently. It never carries another shop's evidence, never names another merchant, never returns an identifier or a hash, and never carries enough detail to let somebody map the shared signal by sending checks at it and reading the answers back. That last constraint is why the response is categorical in the first place, and it is worked through properly in why the network cannot be enumerated.

A reason that explained more would help the person in the queue, and it would help somebody probing the network just as much. The response resolves that one way: the shop is told what is relevant to its own order, in categories, and no further.

Rules, and what they are not called

Scoring is rule based. The thing that decides a score is a rule, and a shop can be told which one fired. That is the whole claim, and the vocabulary matches it: the phrase machine learning does a great deal of unearned work in this industry, and a rule engine described in that vocabulary would be a lie that happens to sell better.

The commitment about vocabulary is as firm as the one about code. If a model ever enters the scoring path, this page says so on the day it does, and says what it changed about the reasons a shop receives.

Rules are legible and deliberately simple. Simple rules work here because the signal comes from shops labelling harm they confirmed, not from cleverness at the scoring step. A denser network improves the answer more than a better model would.

Verify it

The reasons a check can return are published, so start by reading them. They sit in the API reference beside the two hundred response for POST /v1/check, with the rule about what they may and may not contain, and the enumerated reason_code values a shop sends back when it labels an order.

curl -s https://heatrail.eu/docs/api/ | grep -i reasons

Then check the second half of the property, that the rules cannot be moved through the API. Reading the rules view needs an operator session. The half that needs no session is about which routes exist at all:

curl -si -X POST https://api.heatrail.eu/dashboard/rules | head -1
# HTTP/1.1 405 Method Not Allowed

That answer arrives before any credential is looked at: it is the router saying no such route was ever defined, not an authorisation layer saying no. There is no version of that request, with any key, at any privilege level, that becomes a two hundred. The read route answers with the configuration and the ledger entries recording every time it changed.

Last, and this is the one a merchant can check on their own orders: a shop can always be told which rule fired, because the response already says so. The same score and reasons for the most recent check on an order can be fetched again through the explain endpoint, for a details view in your admin. You do not have to ask us. You have it.

Heatrail

This site is not public yet

Heatrail is in a private pre-launch period. Enter the access password to continue.

Enquiries: hello@heatrail.eu