Skip to content

resmon v2.2.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 20:03
· 33 commits to main since this release
8f966d7

Read this before you upgrade, if anything outside the app talks to resmon

resmon's local API is now locked to this app, and a v2.1.0 MCP server will stop working.

Before 2.2 the backend answered anything that could reach 127.0.0.1: every origin,
Access-Control-Allow-Private-Network: true on every response, no Host check and no
credential. A web page open in any browser on the machine could drive the whole API.

From 2.2.0 every request on every route passes one guard that runs before CORS and before
any request body is read:

  • Host must be this backend's own loopback address and port, or 403 host_refused.
  • Origin, when present, must be this app's renderer origin exactly, or
    403 origin_refused, with no CORS header on the answer.
  • Authorization: Bearer <token> must carry this backend's per-instance secret — 32
    bytes from the OS CSPRNG, compared in constant time — or 401 token_missing /
    401 token_invalid.

There are no exempt routes. /api/health needs the token too, and there is no "auth
off" switch in shipped code or in tests. The token is written owner-only to
api-token-<port> in the state directory and removed on clean shutdown; a file left by a
crash admits nothing, because every start mints a fresh token and accepts only the one it
holds.

If you run resmon's MCP server from a separate checkout, update it in the same sitting as
the app.
A 2.1.0 MCP server sends no Authorization header, so a 2.2 backend answers it
401 token_missing on every call. Symmetrically, a 2.2 app or MCP server will not attach to
a backend that published no token file — the app starts its own and leaves the old daemon
alone. Nothing needs configuring for the installed app; a development instance with its own
state directory needs RESMON_STATE_DIR set for the MCP server too.

What this deliberately does not defend: processes running as your own OS user, which can
read the token file, the backend's environment and the database itself; Windows file
permissions, where 0600 is largely ignored; anything that can run code inside the app's
renderer; and loopback traffic itself, which is plain HTTP. Full model:
docs/local-api-security.md.

Your corpus walks five schema steps on first launch

This release moves an existing corpus from schema 13 to 18 the first time it opens:
reading queue (14), assistant composer choices (15), Library (16), Evidence (17),
selected-evidence answers (18). Every step is additive and backfills nothing.

Each step had a green test, but each started from the step before it and none from a
database a released resmon had written. This release adds that proof, and it runs in the
ordinary hermetic suite on every pull request: a corpus written by tag v2.1.0's own code
in a disposable worktree — 21 tables plus the full-text index, 116 rows, every value of
every CHECK-constrained column that version has, non-Latin text, non-NFC combining
characters, a NULL in every column it allows one, an execution with a failed source and one
skipped for a missing credential, an AI lane left running — replayed through today's
migrations and compared column by column.

The denominators the run prints itself: 56 of 56 authored sqlite_master objects equal to a
fresh install's; 40 of 40 objects v2.1.0 owned still present; 31 of 31 tables' PRAGMAs equal
to fresh; 116 rows across 21 of 21 schema-13 tables compared column by column; 166 schema-13
columns still present with the same type and nullability; 8 of 8 AUTOINCREMENT high-water
marks preserved; 23 enumerated CHECK constraints exercised on the upgraded database, 73
allowed values accepted and 23 sentinels rejected; PRAGMA integrity_check ok and
foreign_key_check empty. Four deliberate mutations to the migration code were each
confirmed to turn it red.

Everything in that fixture is synthetic. No row comes from anybody's corpus.

What is new

Library and Evidence — keeping the papers, not just the records. A Library vault you
create explicitly retains selected PDF, TXT and Markdown originals; items are findable past
the first page, can be associated with existing paper IDs, and export as a complete JSON
inventory. On top of it, Evidence projects hold passages anchored to an exact file, version,
canonical text hash and codepoint range — a passage whose evidence is missing or changed
stays explicitly unresolved rather than silently re-anchored. You can ask a question or
request a structured briefing over excerpts you selected, inspecting the exact hash-bound
disclosure preview before pressing Send, and export a saved answer as a portable ZIP or as
one self-contained offline HTML file capped at 4 MiB. Resetting the corpus keeps your
Library; deleting a linked paper removes only the association.

A reading queue. Every run gains a Papers tab listing what it stored, 50 to a page
with each paper's corpus-local document id and a Save to read button, and a Reading queue page holds them in To read / Read / All with the existing evidence panel on
every row and tick-boxes that feed the existing BibTeX / RIS / CSV export. Saving is
idempotent, a request that changes nothing writes nothing, and identity is the stored id,
never a title or DOI. An upgraded corpus starts the queue empty, on purpose.

Coverage before conclusions. Opening a run shows source coverage above its report, so a
quiet run does not read as "nothing was published". Counts use the saved selected-source set
when that is established; otherwise they say plainly that the full selection is unknown.
Report ZIPs gain a generated search-record JSON/Markdown pair per execution, with the
original report and log bytes unchanged.

Chats, and a conversation that keeps its connection. Saved conversations were capped at
the latest 50; Chats adds title filtering, stable newest-created paging, a literal persisted
transcript and "Continue in Ask". Connection, model and effort are now fixed at conversation
creation rather than following a changed global default, and continuing a historical
conversation asks once and discloses exactly what would be replayed. Markdown and JSON
exports refuse output above 8 MiB rather than truncating it.

Sources that stop, and say why. HAL, Zenodo, bioRxiv/medRxiv, ERIC, DBLP and OAPEN share
one 45-second cooperative budget across pagination, limiter admission, response reads and
one bounded retry. A later page that fails no longer discards rows earlier pages returned —
the partial outcome reaches saved coverage, the activity messages, the report and the
exported search record. A failed search keeps the order of how it failed rather than only
the last attempt. The weekly live suite can quarantine one provider's outage without hiding
anything else: a quarantined case still runs and still asserts, and a pass is reported as a
recovery.

Full write-up: Update 25 — Keep the paper, and lock the door, on the resmon blog.

What this release does not do

  • It does not keep a 2.1.0 MCP server working against a 2.2 backend. That is the point of
    the lock. Update both halves together.
  • It does not fix OAPEN's intermittent HTTP 500s. The longer timeout makes them visible as
    an HTTP 500 instead of a timeout; no client setting changes what a server sends.
  • The timing bounds on source requests are cooperative. They do not preempt DNS shutdown, OS
    scheduling or CPU-bound work.
  • An evidence answer is not a verification. Matching a quotation identifies the text it came
    from; it does not establish that the quotation supports a claim.
  • The upgrade proof walks 13 → 18 from a v2.1.0 corpus. It establishes nothing about a
    database written by any other version, about sqlite-vec, or about the renderer.
  • The token keeps out other principals, not you. Anything running as your own user account
    can read the token file — and the database.
  • No Windows Library support, no OCR, no background full-text processing, no inferred paper
    or version association, no silent re-anchor.
  • macOS builds are unsigned, so they cannot self-update and no latest-mac.yml is
    published.

What's Changed

Full Changelog: v2.1.0...v2.2.0