# Part 13 — catching a dishonest relay

The relay cannot read a record, alter one, or forge one. What it could always
do — until now — is decline to hand one over, or hand over the version that
was replaced. A device asking for a graph it has never seen has no way to know
what it was not given.

This is on by default and needs no configuration. What follows is what it
does, what it cannot do, and what your app should show.

## The two checks

```mermaid
flowchart TD
  P[pull] --> L[read the append-only log]
  L --> C1{every entry served,<br/>at that revision or newer?}
  C1 -- no --> A1[alarm: omitted / rolled-back]
  C1 -- yes --> F[fold the log into a chain head]
  F --> C2{does a peer's signed head<br/>match at the same boundary?}
  C2 -- no --> A2[alarm: diverged]
  C2 -- yes --> OK[state: ok, agreed + 1]
```

**Against itself.** Every write the relay accepts goes in a log it cannot
rewrite without contradiction. If the log says record `r7` was written at
revision 900 and this device holds revision 400, the relay served the version
that was replaced. If it holds nothing at all, the record was withheld.
This check needs neither a peer nor a key.

**Against other devices.** The chain head depends on the log alone, so any two
devices that read the same range compute the same value regardless of when
they joined. Each publishes its head as an ordinary record — sealed, signed,
verified against the roster like anything else — and a relay keeping a private
world per member fails here even though it passes the first check perfectly.

## What your app should do

```ts
const log = engine.status.graphs['g:acme']?.log;
// { seq: 1204, state: 'ok' | 'unverified' | 'off' | 'alarm', agreed: 2, from: 0 }

engine.on('log.alarm', (e) => {
  // e.reason: 'omitted' | 'rolled-back' | 'diverged'
  incident(e.graphId, e.reason, e.detail);
});
```

| state        | means                                                    | show                                 |
| ------------ | -------------------------------------------------------- | ------------------------------------ |
| `ok`         | everything the log admits to was served here             | nothing, or a quiet tick             |
| `agreed > 0` | another device signed the same history                   | "verified against N other devices"   |
| `unverified` | could not check — old relay, offline, or a pruned log    | "not verified", never "fine"         |
| `off`        | this subscription is filtered, so nothing can be checked | only if your app relies on the check |
| `alarm`      | the relay contradicted its log, or two devices disagree  | **an incident**                      |
| `from > 0`   | the relay's log no longer goes back to the start         | "verified from here on"              |

**`alarm` latches on purpose.** A relay caught omitting one record is not
cleared by behaving for the next thousand, and a warning that clears itself is
a warning nobody sees. The honest response is to stop trusting that
deployment — not to clear the flag.

**`from` is the anchor, and a non-zero one is permanent.** A device that first
folded a log which had already been pruned starts its chain in the middle of
history. It still catches an omission or a rollback after that point — that is
the relay's own account of what it accepted, and it is still checkable — but
the span before it is gone, and its head is a different quantity from the one
a device that folded the whole log holds at the same boundary. So such a
device reports `unverified` rather than `ok`, corroborates nobody, and
publishes no checkpoint of its own. Comparing the two would turn an operator
reclaiming disk into a permanent alarm naming a colleague, which is the worst
failure mode this check has: an accusation aimed at a person, raised by
somebody running a `DELETE`.

## What it cannot do

- **A relay that shows every member a wholly separate world**, their peers'
  checkpoints included, is invisible in-band. Catching that needs two people
  comparing a head out of band. Nothing here does it for you.
- **A filtered subscription cannot verify.** A device that asked for a subset
  cannot tell that from being shown one, so the check reports `off`. If you
  use partial sync and want this protection, keep one device somewhere doing
  an unfiltered pull.
- **A device that never syncs learns nothing.** Detection is comparative and
  eventual. The first sync on a brand-new device is exactly when you are least
  protected and most exposed.

## What it costs

One extra request per sync that has new history, and none at all when a graph
is idle. On the relay, about sixty bytes per write, kept forever.

The storage cost is a real trade-off worth considering before you ship:
the relay now **retains** the shape of a record's edit history, where it used
to overwrite it. It never learns more about the contents — it saw each write
as it happened either way — but "this document was edited eleven times last
night" is now something it remembers.

```ts
// Verifying, but not publishing a head of your own:
await ArgalaEngine.open({ ..., checkpointEveryMs: -1 });
// Off entirely — say why in a comment, because the default is on:
await ArgalaEngine.open({ ..., verifyLog: false });
```

---
