# In-admin updates

2026-08-06

Convoro can now publish a catalogue and an install can now fetch a package from
it. The half that is missing is the install knowing there is anything to fetch.

This note is the check: how a site finds out that something it has is out of
date, and — more importantly — how that check fails.

## The one rule this has to obey

🚨 **The check never happens inside a visitor's request.**

A queued job that makes an outbound call from inside a page view has already
caused a real outage on a live site: the remote end got slow, every request that
happened to run the queue got slow with it, and the forum went down without
anything in it being wrong. That is the standing rule this feature is most
likely to break, because "check for updates" is exactly the kind of thing that
looks harmless to do on page load.

So:

- The fetch happens in `convoro update:check`, run from cron.
- The admin screen **reads what that stored**, and never fetches.
- A site whose cron is not set up sees "last checked: never", which is honest
  and is a state worth showing rather than hiding behind a live call.
- Every outbound call is capped: 1s to connect, 3s in total, one attempt. A
  catalogue that is slow is a catalogue that is unreachable, and the difference
  does not matter to us.

The webhook sender already works this way and its timeouts are the precedent.

## What it compares

The catalogue is keyed by module key; so is the extension registry. That is the
whole join.

For each installed extension with a key the catalogue knows, compare the
installed version against the catalogue's using `Version::compareStrings`, which
already exists and already handles 1.10.0 sorting after 1.9.0.

Three outcomes, and the third is the one people forget:

- **behind** — the catalogue has a newer version
- **current** — the same, or the install is ahead
- **not offered** — the catalogue has never heard of this key

"Not offered" is normal: a site's own private extension is not in anybody's
catalogue. It is reported as nothing at all rather than as a problem, because a
screen that lists every local extension under a warning heading trains people to
ignore the screen.

## What it does not do

**It does not install anything.** Not in this pass, and the restraint is
deliberate: an update check that can also apply updates is one bug away from
replacing a working extension with a broken one on a live forum, unattended. The
screen tells you what is available and links to it. Applying it is the existing
`ext:install` path, run by somebody who is watching.

**It does not phone home.** The only thing sent is a GET for a JSON file. No
site URL, no member count, no list of what is installed — the catalogue is a
public document and reading one should not be an act of registration. A check
that quietly reported the shape of somebody's forum back to us would be a thing
we would have to disclose, and not doing it is simpler than disclosing it.

## Convoro itself

The same mechanism, one entry. A catalogue may carry a `convoro` block naming
the newest core version; if it does, the core is compared the same way as an
extension. If it does not, the core is reported as "not offered" and nothing
pretends otherwise.

## Where it shows

Two places, and neither is a badge that never goes away:

- **Admin → Extensions**, against each extension, because that is where somebody
  already is when they think about versions.
- **The dashboard**, in the "what is waiting" strip, and only when something is
  actually behind. A permanent "everything is up to date" row is furniture.

## Failing

A check that has never run, a catalogue that is unreachable, a catalogue that
returns HTML because a proxy ate it — all three are the same state to a person:
*we do not know*. They are shown as that, with when it was last tried and what
went wrong, rather than as "up to date", which is the one answer we must never
give without evidence.

---

## Addendum, 2026-08-10: it installs now

The note above says, twice and firmly, that this must never apply an update —
that an update check which can also install is one bug from replacing a working
extension on a live forum unattended, and that applying it is `ext:install`, run
by somebody who is watching.

**That was right about the risk and wrong about the audience.**

Convoro's standing requirement is that it installs by uploading a zip to shared
hosting and running a web installer, like Invision and phpBB rather than Flarum.
The installer honours that. But `CoreUpdater` and `Patch` were only ever reached
from `core:update` on the command line — so somebody on shared hosting could
install Convoro perfectly and then never take a single update.

That is worse than not installing at all. They find out later, on the release
with a security fix in it, and their options are "find a shell" or "stay
vulnerable". A restraint that leaves people stranded on an old version is not
caution, it is the more dangerous choice wearing caution's clothes.

So the rule narrows rather than breaks:

- **Rendering a page still never fetches, and never installs.** That half is
  absolute. An admin screen whose load time is somebody else's uptime is the
  bug that took a live site down.
- **A person pressing a button may.** Fetching, applying a patch, and now
  installing a release are all things somebody chose, in a request they started,
  while looking at the result. That is the same exception checkout and the
  patch applier already use.

What keeps it honest is the same as on the CLI: `CoreUpdater::canInstall()`
gates it, the files are backed up before anything is written, and the screen
says plainly that the database is not backed up and a migration cannot be
undone. The web path runs exactly the same code as `core:update`, because two
implementations of "replace the application" is one more than anybody should
maintain.
