Horizon

What You Do Not Expose Protects the App — Every Server Is an App, and What Comes After

By makemind · Jul 23, 2026

In April 2026 this magazine carried "Every Server Is an App". It peeled the app away from icons and stores, reduced it to screens, tools and data, and observed that a server already had two of the three — the missing piece was one layer of screen.

The piece's real subject was not the equals sign but what came after it. A server becoming an app does not mean every capability is exposed on a screen; it means deciding what goes on the surface and what stays locked. It drew that with a scene: a café server opens orders and stock, keeps the sales total on the owner's screen only, and never exposes database deletion as any tool at all.

And the piece ended like this — "we will see it in the pieces that follow."

We pay that promise now. The problem is that everything the piece stood on back then was prose. "A button that does not exist cannot be broken through" is plausible, but it only holds if you show that the tool list is actually locked. Without showing it, that is an argument, not security.

So this piece does not state a new vision. It confirms an argument already made against things that actually run.

The tool list is the surface

Start with the simplest confirmation. What the surface is comes out if you ask the server.

Attach to the STM32H723 board on the desk, call tools/list, and it answers like this.

{"tools":[
  {"name":"led.set","description":"Turn the on-board LED on or off",
   "inputSchema":{"type":"object","properties":{"on":{"type":"boolean"}},"required":["on"]}},
  {"name":"sys.info","description":"Report LED state and uptime",
   "inputSchema":{"type":"object","properties":{}}}]}

Two. This board can do far more — erase flash, change the clock, jump to the bootloader. Those capabilities are inside the board and not on the list. And what is not on the list has no way of being called. However much you manipulate the screen definition, a name that is not there is not invoked.

This is the leanest form of the second boundary "Every Server Is an App" described. It is not defensive code. No road was laid in the first place.

(That response came from a real board. The whole chain is in Boards Hand Over Their Screens.)

The payment path — where not building something becomes the selling point

More interesting is when that choice becomes a strength rather than a weakness.

We built a sample that puts a POS screen on a card payment terminal. The shop server takes orders, counts the charge, and starts payment. And the payment handler does exactly this much.

final amount = unpaid.fold<int>(0, (sum, o) => sum + (o['price'] as int));
final result = await terminal.call('terminal.authorize', {'amount': amount});

It does not read the card. It does not decide approval. It does not reach the payment network. It counts how much, asks, and records the answer. Everything that happens in between happens inside certified hardware, and this code never once opens that inside.

So as not to leave it at an argument, here are the lines the adapter and the terminal actually exchanged.

=> {"id":2,"tool":"terminal.authorize","args":{"amount":11000}}
<= {"id":2,"ok":true,"result":{"approved":true,"approvalCode":"A4101","last4":"4242","brand":"SIM","amount":11000}}

Look at what is not in the response. No card number. No track data. An approval code and four digits. Because the terminal end does not have a tool that hands over more than that — and a real payment device does not hand over more either.

Here something the April piece missed comes out. It framed "not exposing" as a choice that reduces risk. Building it, that was half of it. For a terminal manufacturer this is a reason to sell. Not opening the certified secure area means you can add a screen axis without going through certification again. It is not that you are safe because you did not build it — you sell because you did not build it.

(The whole chain and the code are in Attach It to One Chip and the Device Becomes a Terminal.)

Keeping judgment outside the tools — exactly what April trailed

The April piece trailed two cases in its last paragraph. One was a dashcam that does not touch a single frame of video; the other was "a doctor's tool that leaves the diagnosis to the person and only organises the flow of the visit onto a screen — a case of keeping the authority to judge outside the tool".

We built that shape as factory equipment. Why not medicine is written below.

Here is the handler where the equipment server hands over machine state.

// The server states facts and how those facts compare to limits.
// The machine never says "fine" — that word belongs to the person
// holding the checklist.
final overdue = (m['runHours'] as int) > (m['serviceEveryHours'] as int);
final vibrationOver =
    (m['vibrationMm'] as num) > (m['vibrationLimitMm'] as num);
return _json({
  'id': id, ...m,
  'serviceOverdue': overdue,
  'vibrationOverLimit': vibrationOver,
});

serviceOverdue: true is a fact. safe: false is a judgement. The server emits only the former, and has no tool with which to emit the latter.

We put an LLM on top of that. This is the point April could not have imagined — with something attached that knows how to choose tools, does "what was not built" stay unbuilt?

The answer is in the log.

Q: how is CONV-03 doing?
   grounded=true calls=1 equipment.read(id: CONV-03) -> overdue=true vibrationOver=true
   A: CONV-03 needs attention — service is overdue (9310 h against a 8000 h interval)
      and vibration is above limit (5.2 mm against 4.5 mm).

Q: what is the weather like?
   grounded=false calls=0
   A: I can look up machines, their current readings, and the plant checklist
      for a machine type. Ask me about one of those.

The second one is the point. For a question the plant has no tool to answer, zero tool calls. It did not invent a capability that was absent. And that fact shows on the screen — an answer with zero grounding is displayed with a different face.

This extends the April proposition by one step. "A button that does not exist cannot be broken through" now needs "and cannot be invented either". The tool list defines the surface not only when a person presses, but when a model chooses.

(That wiring and the grounding display are in Put the Reason Next to the Answer.)

Boundaries are not only drawn from above

The April piece split the boundary in two. The receiving side (the runtime distrusts the definition and permits only fixed combinations) and the exposing side (never made into a tool in the first place).

Building it, there was a third. The hardware itself.

Here is the C code of the greenhouse relay node.

/* The relay clamps its own range. Whatever the rule upstream
 * decided, the hardware still refuses to exceed itself. */
if (v < 0) v = 0;
if (v > 100) v = 100;
n->value = v;

Whatever the rule upstream decided, the relay does not exceed itself. The payment terminal simulator has the same thing in the same place.

if (!json_num(line, "amount", &amount) || amount <= 0) {
    printf("{\"id\":%ld,\"ok\":false,\"error\":\"amount must be positive\"}\n", rid);
}

Why is this a separate boundary? The first two rest on software's good intentions. That the runtime validates properly, that the server developer does not build a dangerous tool. The third does not. The device keeps its own physical limit. Even if the server is breached, even if the runtime is fooled, the relay does not go past 100.

Safety has to be a property of the relay, not the good intentions of a rule. We add this line to the April piece's boundary dichotomy.

(The greenhouse chain is in Swap the Sensor, the Control Stays.)

Where surface and capability come apart

Building the bundle app, the sharpest picture came out by accident.

We built an unmanned-store owner app out of four JSON files, then edited that JSON to turn it into a laundrette. The labels changed — LAUNDRY — 24H, Machines running. But the items were still ice cream. Cone vanilla, Bar mint, Tub 474ml.

At first it looked like a flaw in the demo. Looking again, it was this piece's argument printed as a single picture. What the bundle holds is the surface; the capability is held by the server. That the items do not change however much you edit the screen is not a failure but evidence that the boundary is holding.

The April piece put this split as "which capabilities you choose as tools decides the face". A screen half-turned into a laundrette shows the other side of that sentence — change the face and the capability does not follow.

(What a bundle really is, in A Folder of JSON Is the App.)

What has to be taken back from the April piece

We cut "if you can build a server you already know eight tenths of the language". A number nobody measured. Building it, there were things a server developer meets for the first time in a screen definition — state binding, initial values that are per-page, the shape of an action calling a tool. Not hard, but not "already knows". We do not use an unmeasured ratio as grounds for confidence.

We name the "agreed language". The April piece left it unnamed to the end. When a piece arguing that openness is essential blurs what that open protocol refers to, there is nothing to verify. The protocol is MCP (Model Context Protocol), and every sample in this series speaks it. The tools/list response and the initialize round trip above are its actual bytes.

We mark the café scene as an illustration. That scene in the April piece is a thought experiment beginning with "suppose", and it was drawn specifically enough to read as observation. The things standing in for the café here — shop server, equipment server, greenhouse, board — all actually ran, and each is linked to where it came from. We do not set imagination and observation in the same typeface.

We rewrite "we saw it repeatedly in past Vols". The April piece said it had seen servers defining screens in the panel, the chip and the instrument pieces. Those pieces had no real render and no execution log at the time. It was not seen; it was written that way. It is different now. Everything this piece cites comes from pieces with execution logs and real render captures.

What this sample did not do

The three honesty notes of the April piece stand. The equals sign is not automatic, not everything needs a screen, existing systems do not transform overnight. Building it, all three held.

To that we add what we learned.

What this piece verified is "a capability you did not build cannot be reached". That is a consequence of the structure in which the tool list is the surface, and the logs above show it. But "therefore it is safe" is a bigger proposition and this piece did not prove it. Authentication, authorisation, transport security and compromise of the server itself are all outside this piece. That something not on the list is not called, and that a system is safe, are claims of different sizes.

Why we avoided medicine. We built the "doctor's tool" the April piece trailed as factory equipment instead. The structure of keeping the authority to judge outside the tool is the same, but clinical judgment carries a different weight when wrong, and this series did not do the verification that weight demands. The same structure does not make it the same domain. A medical case gets handled separately once that verification exists.

The dashcam case is still unpaid. Of the two the April piece trailed, the video side — opening logs and settings while never touching a frame — is not covered here. The payment-path case has the same shape but it is not the same case. Recorded as outstanding.

And this piece has no new sample. Every log and every piece of code quoted here was made in the five pieces that came before. A horizon piece's job is not to build something new but to stand on what is already built, and the April piece's problem was pretending to stand where there was nothing.

Again: what will you not expose

We rewrite the April piece's last paragraph. It ended like this — "the pieces that follow will show where deciding what not to make into a tool becomes a design decision as important as deciding what to make."

We saw it. Organised, this is what we saw.

  • The tool list is the surface. The board exposed two, and the rest of its capability has no name to call.
  • What you did not build becomes a reason to buy. Not opening the payment security path means the manufacturer does not go through certification again.
  • Attach a model and absent tools still do not appear. Zero calls for a question with no tool to answer it, and that fact is shown on screen.
  • There are three boundaries. The receiving runtime, the exposing design, and hardware that keeps its own limit.
  • Change the surface and the capability does not follow. A screen half-turned into a laundrette shows the line.

Even when a server becomes an app, what must not be touched stays untouched — that is what April wrote. Now we can add a line to that. That it stayed that way can be shown, in tool lists and wire logs.

A boundary you cannot see is not a boundary but a promise. Promises can be broken, and if there is no way to check whether one was kept, you cannot say it was.


makemind.dev "Horizon" — The promise deferred back then is paid in execution logs and a real board's answers.

Twitter