Nothing proves a built-in carries the core version, and nothing tells an instance it is behind #272

Closed
opened 2026-09-17 22:03:14 +00:00 by tiagoagueda · 0 comments
Owner

Reported as: the built-in plugins still say 0.2.1 although 0.3.0 is released, and a built-in
should always carry the release's version.

The rule is already the code. plugins/base.py:274, shipped():

The version is Postulo's own, because that is the truth: these ship with the application
and change when it does. A built-in with a literal version number identifies nothing —
version = "1.0" meant 1.0 the day it was written and would have gone on meaning it
through every change to the parser underneath.

It sets version=__version__, and in this checkout both postulo.__version__ and
importlib.metadata.version("postulo") return 0.3.0, matching pyproject.toml and the
v0.3.0 tag. There is no literal 0.2.1 anywhere in src/.

What is actually happening. https://postulo.tiagoagueda.com/healthz answers:

{"status": "ok", "database": "ok", "version": "0.2.1"}

The instance is running a pre-0.3.0 build, which is #251 — v0.3.0 was released without a
container image. So the built-ins are reporting 0.2.1 correctly: on that instance Postulo
genuinely is 0.2.1, and a built-in claiming 0.3.0 there would be the bug.

Ragnar wants updating once #251 produces an image. That part is not this issue.

What the report does expose, and it is worth fixing

1. Nothing proves the invariant

shipped()'s own docstring is careful about its standing:

Not part of the contract a third party writes against. It is a convenience for this
repository, so that six built-ins cannot drift into claiming six different licences.

A convenience is not a guarantee. Nothing stops the next built-in being declared with a
literal version, or with Manifest(...) directly, and nothing would catch it — there are
fifteen in-tree plugins now.

This project guards exactly this kind of thing with a test: tests/test_changelog.py for a
file's shape, tests/test_stylesheet.py for a build artefact going stale. The same move
applies here and is a handful of lines:

  • every in-tree plugin's manifest version equals postulo.__version__;
  • and, while there: every in-tree manifest carries SHIPPED_AUTHOR, SHIPPED_LICENCE and
    SHIPPED_SOURCE_URL, which is the drift shipped() says it exists to prevent.

Worth extending to the official external plugins, which are a separate promise: postulo-imap,
-apprise, -dav, -paperless, -mcp and -helloworld are all at 0.3.0 today, matched
by hand, and each reads its own __version__ from package metadata. Nothing checks that they
track the core, and the first one to be forgotten will be found by somebody reading a plugins
page rather than by CI.

2. Nothing tells an instance that it is behind

There is no update check anywhere in the tree — no latest_version, no update_available,
nothing. The footer (base.html:211) prints Postulo {{ postulo_version }} and the plugins
page prints the same number beside each built-in. Both are correct, and there is no way,
from inside a running Postulo, to learn that a newer release exists.

That is what produced this report: a display that is right, looking like a bug, because the
number it would have to be compared against is not on the page.

For a self-hosted application this is more than cosmetic. An operator who does not know a
release exists does not know a security release exists, and Postulo is explicitly built to
be run by people who are not full-time administrators.

The constraint that makes this a decision rather than a feature: an update check is an
outbound request, and this application refuses to make requests on a reader's behalf —
jobs/logos.py and plugins/logos.py both exist to avoid exactly that. So the options are:

  • opt-in, server-side, off by default, asking the instance's own configured source and no
    one else — never from the browser, never on a page load;
  • or nothing at all, and the answer is a documented way for an operator to check, plus a
    release feed they subscribe to themselves.

Either is defensible. What is not defensible is the current state, where the number is shown
and cannot be interpreted. That choice belongs in this issue, before any code.

Reported as: the built-in plugins still say 0.2.1 although 0.3.0 is released, and a built-in should always carry the release's version. **The rule is already the code.** `plugins/base.py:274`, `shipped()`: > The version is Postulo's own, because that is the truth: these ship with the application > and change when it does. A built-in with a literal version number identifies nothing — > `version = "1.0"` meant 1.0 the day it was written and would have gone on meaning it > through every change to the parser underneath. It sets `version=__version__`, and in this checkout both `postulo.__version__` and `importlib.metadata.version("postulo")` return `0.3.0`, matching `pyproject.toml` and the `v0.3.0` tag. There is no literal `0.2.1` anywhere in `src/`. **What is actually happening.** `https://postulo.tiagoagueda.com/healthz` answers: ```json {"status": "ok", "database": "ok", "version": "0.2.1"} ``` The instance is running a pre-0.3.0 build, which is **#251** — v0.3.0 was released without a container image. So the built-ins are reporting 0.2.1 *correctly*: on that instance Postulo genuinely is 0.2.1, and a built-in claiming 0.3.0 there would be the bug. Ragnar wants updating once #251 produces an image. That part is not this issue. ## What the report does expose, and it is worth fixing ### 1. Nothing proves the invariant `shipped()`'s own docstring is careful about its standing: > Not part of the contract a third party writes against. It is a convenience for this > repository, so that six built-ins cannot drift into claiming six different licences. A convenience is not a guarantee. Nothing stops the next built-in being declared with a literal version, or with `Manifest(...)` directly, and nothing would catch it — there are fifteen in-tree plugins now. This project guards exactly this kind of thing with a test: `tests/test_changelog.py` for a file's shape, `tests/test_stylesheet.py` for a build artefact going stale. The same move applies here and is a handful of lines: - every in-tree plugin's manifest version equals `postulo.__version__`; - and, while there: every in-tree manifest carries `SHIPPED_AUTHOR`, `SHIPPED_LICENCE` and `SHIPPED_SOURCE_URL`, which is the drift `shipped()` says it exists to prevent. Worth extending to the official external plugins, which are a separate promise: `postulo-imap`, `-apprise`, `-dav`, `-paperless`, `-mcp` and `-helloworld` are all at `0.3.0` today, matched by hand, and each reads its own `__version__` from package metadata. Nothing checks that they track the core, and the first one to be forgotten will be found by somebody reading a plugins page rather than by CI. ### 2. Nothing tells an instance that it is behind There is no update check anywhere in the tree — no `latest_version`, no `update_available`, nothing. The footer (`base.html:211`) prints `Postulo {{ postulo_version }}` and the plugins page prints the same number beside each built-in. Both are correct, and **there is no way, from inside a running Postulo, to learn that a newer release exists.** That is what produced this report: a display that is right, looking like a bug, because the number it would have to be compared against is not on the page. For a self-hosted application this is more than cosmetic. An operator who does not know a release exists does not know a *security* release exists, and Postulo is explicitly built to be run by people who are not full-time administrators. **The constraint that makes this a decision rather than a feature:** an update check is an outbound request, and this application refuses to make requests on a reader's behalf — `jobs/logos.py` and `plugins/logos.py` both exist to avoid exactly that. So the options are: - **opt-in, server-side, off by default**, asking the instance's own configured source and no one else — never from the browser, never on a page load; - **or nothing at all**, and the answer is a documented way for an operator to check, plus a release feed they subscribe to themselves. Either is defensible. What is not defensible is the current state, where the number is shown and cannot be interpreted. That choice belongs in this issue, before any code.
tiagoagueda added this to the 0.4.0 milestone 2026-09-17 22:03:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#272
No description provided.