Read this before you upgrade
Your corpus crosses three schema steps on the first launch: 18 → 21. One init_db at
startup does the whole walk. executions is rebuilt with a wider status vocabulary and six
new columns; routine_missed_fires, routine_delivery_targets and deliveries are
created; executions.request_id gains a partial unique index. Every row you already have,
every key and every AUTOINCREMENT high-water mark comes out the other side unchanged.
That is not a hope. It is proven against a database v2.2.0's own code wrote — the
corpus fixture that release committed, regenerated out of process from its own tag and
diffed, then walked to 21 and compared column by column. The class of mistake this catches
is the one that shipped once before: a migration reasoned out against a hand-written idea of
what an older database looks like.
Back up first anyway, and 2.3.0 is the release that gives you a way to. Settings → Storage →
Back up now writes the database and every retained Library byte as one bundle, with a manifest
naming every file and its hash.
Your MCP server needs no change this time. The contract is still 2.3 and the inventory
is still twenty-five tools. A 2.3 MCP server pointed at a 2.2 backend simply gets no
delivery summary back — nothing breaks, one field is absent.
2.3.0 is the classic checkpoint
This is the last feature release of resmon's current interface. From here, 2.x continues on
a classic branch for security fixes only, and the next major version rebuilds the
interface on the same engine — the same backend, the same database, the same corpus — and
is measured against 2.3.0 journey by journey rather than against an idea of what the app
does. Nothing is being retired, and no date is being promised.
What is new
A run says what actually happened to it. executions.status had four values and none of
them was true of a run whose backend was killed; the graceful shutdown path wrote failed,
which was an overclaim, because resmon never observed a failure. There is now a fifth word,
interrupted, with ownership columns behind it — which process claimed the row, when it
was last seen working, and which of two ways resmon found out — so the judgement on the next
start is established rather than assumed. A pid that has been reused, or one owned by
another user, reads as alive: resmon would rather leave a row waiting than tell you a live
run has stopped. Restart re-runs an interrupted execution and links the new run back to
the one it came from; the original is never edited.
One run per routine, one run per submission. A scheduled fire can no longer start a
second copy of a routine that is already running, and a resubmitted request can no longer
produce a second run — both enforced by the database rather than by a check the application
hoped it won because it looked first. Missed fires — scheduled runs whose time had
already passed while resmon was not running — are now recorded. They are recorded, not
caught up: whether resmon should silently run a week of missed sweeps on the morning a
laptop is opened is your decision, not a default chosen by a migration.
A routine's report has somewhere to go. Four destinations ship — email, a folder,
a webhook and an Atom feed — and each destination gets its own record per run: the
attempts made, the reason it has not arrived, and when the next try is. A destination can be
set to review, and then its delivery waits until you approve it, however long that is;
nothing promotes it automatically. Sending has moved off the execution worker's thread, so a
mail server that accepts a connection and then sits there can no longer hold a run's slot
open. Failures back off 1, 5 and 25 minutes and then stop with the reason on the row.
Webhook envelopes are signed with a per-destination secret from your keyring, HTTPS only
except for a receiver on 127.0.0.1, and the bundle link the envelope carries proves itself
rather than handing a receiver a credential that opens every route.
Backup and restore, as one pair. The database is snapshotted through SQLite's own backup
API — consistent under WAL, no sidecars — every retained vault byte is re-hashed against the
catalog on the way out, and the manifest names the keyring entries a restore cannot bring
back by name, never by value. The restore is staged: a request thread cannot safely
replace the database its own process has open, so the next start acts on it before anything
opens the database, and the current database is moved aside rather than deleted.
Smaller things. Nine registered sources that the acknowledgments had never credited are
credited. Tutorials link the YouTube walk-throughs instead of embedding them, and the two
YouTube origins have left the renderer's Content-Security-Policy with a test pinning the
exact source set per directive. An unused tailwindcss dependency is gone — the production
build is byte-for-byte identical without it — and APScheduler's [sqlalchemy] extra, which
was being relied on, is now declared.
What this release does not do
- It does not run your missed sweeps for you. A missed fire is recorded, not caught up.
- A delivery record is a record of what resmon did, not proof of what arrived. A folder
delivery says the bundle was written; it cannot tell you your sync client uploaded it. A
webhook delivery says the receiver answered; it cannot tell you a person read it. - A row left mid-delivery may already have been sent. resmon re-queues it on the next
start, and only where its owner can be established to be gone. It would rather you see one
report twice than never see it. interruptedis what resmon can establish, not everything that is true. A row whose
owner cannot be shown to be gone staysrunninguntil something can say otherwise.- A restore cannot bring back your keyring. The manifest names the entries so you know
what to re-enter; the values were never in the bundle. - The upgrade proof walks 13 → 21 and 18 → 21, from corpora v2.1.0 and v2.2.0 wrote. It
establishes nothing about a database written by any other version, about the semantic
search index, or about the renderer. - macOS builds are still unsigned. They cannot self-update, which is why no
latest-mac.ymlis published for them.
What's Changed
- Tutorials: link the YouTube walk-throughs instead of embedding them by @ryanjosephkamp in #134
- docs: verify the published v2.2.0 installer on arm64 by @ryanjosephkamp in #136
- The v2.2.0 fixture: a corpus v2.2.0's own code wrote, for the next release to walk from by @ryanjosephkamp in #135
- Live: a strict source that answers nothing fails by asserting, so a quarantine can reach it by @ryanjosephkamp in #137
- Trim: drop the unused tailwindcss dependency and move to Node 22 by @ryanjosephkamp in #138
- README: credit the nine registered sources the acknowledgments were missing by @ryanjosephkamp in #140
- Interrupted executions: honest states on restart, ownership, and Restart (schema 19) by @ryanjosephkamp in #139
- One run per routine, one run per submission, and a record of missed fires (schema 20) by @ryanjosephkamp in #141
- Skip on the first-run card waits for the dismissal to be recorded by @ryanjosephkamp in #142
- Release the admission slot on every exit of the execution worker by @ryanjosephkamp in #143
- Trim: drop the YouTube origins from the renderer CSP; declare APScheduler's [sqlalchemy] extra by @ryanjosephkamp in #144
- One execution-status vocabulary, and a HAL budget test that does not race by @ryanjosephkamp in #145
- Record where a routine's report goes, and whether it got there by @ryanjosephkamp in #146
- Prove the Interrupted row and the renderer CSP in a real window by @ryanjosephkamp in #147
- Deliver a routine's report to a webhook and to a feed by @ryanjosephkamp in #148
- One pid-liveness rule, and a guard for the execution worker's release order by @ryanjosephkamp in #149
- e2e: name the backend's interpreter, and fail loudly instead of exiting 0 on a run that did not run by @ryanjosephkamp in #150
- Counts against merged main at cc26a2a by @ryanjosephkamp in #151
- CI: refuse committed symlinks and maintainer-machine paths by @ryanjosephkamp in #154
- Three leftovers: case-insensitive address scrub, a no-pid startup pin, a dead e2e helper by @ryanjosephkamp in #152
- CI: move the tag-fetch rationale comment back above its step by @ryanjosephkamp in #155
- Backup and restore: the database and the Library vault as a pair, with a restore drill by @ryanjosephkamp in #153
- v2.3.0 — the classic checkpoint: the fixture schema 21 owes, and the docs in step by @ryanjosephkamp in #156
Full Changelog: v2.2.0...v2.3.0