# How a plug-in runs

Each minute the Server asks which plug-ins are due, downloads what their manifests declare, runs each fetcher with no network, and writes the run whole.

Kanboombo's Server runs your folder on your schedule, in your time zone, for as
long as a person's approval holds. A plug-in is built to be unable to do harm,
not trusted not to: a reviewer reads every host it may reach, nothing can leave
its room, and the same files always give the same records. The cost: no signing
in to an API or paging through one; a source is a file at an address you can
write down ahead.

## When a run is due

Nothing runs until a person approves one exact version of your code, its
[digest](/developers/plugins/publish/updates/#digest); before every run the
Server hashes your files again, and runs nothing that is not approved. Each
minute it asks which plug-ins are due: a time your `schedule` allows has passed
since the last attempt, on your zone's clock (Harbour Almanac's `15 6,18 * * *`
is shown as "Every day at 06:15 and 18:15, Auckland"). Missed times run once.
Approve, Resume and the first run start at once. A failed run is tried again
thirty minutes later, then waits for its next time. Runs go one at a time, never
beside a deploy, so a due run may wait its turn.

## The downloads {#downloader}

Your code never touches the network. Before it starts, Kanboombo downloads
each declared source in order, with `{today}`, `{year}` and `{month}` filled:

- **https only**, no name or password in the address, to a host your sources
  name (`links.hosts` is for item links, not downloads);
- **public addresses only**: every address the name resolves to is checked,
  loopback, private, link-local, shared, multicast and reserved ones are
  refused, and the connection goes to the checked address and nowhere else;
- **at most five redirects**, each to your sources' hosts, checked again;
- **a `200` of at most `maxBytes`** (never over 20 MB) within 60 seconds, kept
  only when whole.

Each request says `Kanboombo plug-ins (+https://kanboombo.com)`. One failed
download fails the run (`Could not download tides: …`), and nothing is written.

## The locked room {#locked-room}

Your fetcher runs as `node fetch.mjs` in a room the operating system builds for
one run: a throwaway user owning nothing; a network of its own that reaches
nothing, with no internet socket; an empty root holding, read only, your
approved files at `/plugin`, the downloads at `/sources/<name>` and Node, plus a
private temporary folder. Only files the digest covers are copied in (with
`node_modules/` when a `package-lock.json` made it), so fixtures and READMEs
cannot be imported. There is no database, key or secret: the environment is
`LANG=C.UTF-8` and what the system always sets (`PATH`, `USER`).

It stops at 120 seconds (or your lower `limits.seconds`) and 256 MB (a lower
`limits.megabytes` is not yet enforced here), with at most 32 tasks and 64 MB of
output, and only the system calls a service needs. Node's permission model
reads only your code and sources, writes only the temporary folder, and allows
no child process, worker, native addon or WASI. When the run ends, its code,
downloads, output and context are deleted.

It sees only the [run context](/developers/plugins/build/fetcher/#run-context):
never who installed or reads it, nor how many, nothing of any account or
anyone's days, no key, token or Server address. It runs once for all readers.

## The write

Every line is checked against the approved manifest, and one bad line refuses
the batch ([Records](/developers/plugins/concepts/records/#rules)); a batch that
passes is written in one transaction. Each run is kept with its version, digest,
Node version, times, counts and sources; the host shows the last five.

## When a run fails {#when-a-run-fails}

A download fails, your fetcher exits non-zero, runs past its time or memory,
prints over 64 MB, or the batch breaks a rule. Nothing is written and stored
records stay. The reason, at most 300 characters, is kept: your last stderr line
(or the error, never its stack), a sentence such as `It ran past its 30 s limit
and was stopped.`, or the broken rule and line. The plug-in shows Failing at the
host, readers see `Couldn't refresh` after your newest record, and the next
success clears it. So when your source is not what you expect, exit `2` with one
plain line: a failed run beats wrong records.

> [!WARNING]
> Exit `0` with nothing printed only when your source truly has nothing. An
> empty batch is a success, and for a series with a snapshot window it says
> every stored record in the window has gone.

## No personal data {#personal-data}

A record is shown to every reader and kept for years, so it holds no personal
data: no private person's name, email, telephone or identity number, nothing
that identifies someone who has not chosen to be public. Leave such columns out
even when your source publishes them; your organization states this for every
version.

## Kanboombo's own sections

Sections marked **Official** in the store are Kanboombo's own code, run outside
the locked room with the network and keys they need, each listing saying what
it reaches; none of this is relaxed for yours. DOP Economy is held to all of it.
