Releases: costantinoai/alma-library-manager
Release list
v0.23.4
ALMa v0.23.4
This release makes Health a fast, truthful control surface for provider quota
and network access, and fixes a Discovery refresh failure that could leave the
production SQLite writer locked.
Added
-
Health reads durable snapshots instead of rebuilding diagnostics on every
request. Startup, periodic, mutation-triggered, and terminal-job refreshes
keep the snapshots current while the page remains a fast read. -
OpenAlex work is priced before it runs. Health and import review show the
expected credit cost, plans that exceed the remaining allowance are disabled,
and automatic work is refused when it would consume the reserved user budget. -
One network control applies to centralized outbound transports. The
stored switch andALMA_DISABLE_NETWORKare reflected consistently in
Settings, Health, and the application shell. When OpenAlex is unavailable,
Find & Add and author focus keep their viable local/alternative paths.
Fixed
-
Discovery no longer escapes the centralized SQLite writer path. Scoring
cache fills and candidate materialization now use shortwrite_section
transactions. Previously, an incidental cache upsert opened an implicit
transaction on the refresh connection and retained SQLite's write lock while
the process writer gate remained free. Correctly gated writes then queued and
timed out against the refresh itself. -
Concurrent lens refreshes no longer multiply the full local workload. A
refresh already owns four retrieval lanes plus a preference-profile worker;
lens refreshes now queue behind one local-work slot so a 30-second lane
deadline measures that lane rather than CPU and database-read oversubscription. -
An abandoned retrieval lane stays abandoned. A running Python future
cannot be cancelled. If it finishes after the parent deadline, its late result
is ignored instead of overwriting the Activity subtask from failed to
completed.
Operations
- The production incident that exposed the Discovery defect held the writer for
more than sixteen minutes and blocked extension saves, impressions, and lens
review writes. Restarting released the orphaned transaction; this release
prevents the same path from recreating it.
v0.23.3
ALMa v0.23.3
Patch release: a graph rebuild can no longer tell you it succeeded when it
didn't, and when it does fail it says what to do about it.
Fixed
-
A rebuild that failed to store its result still reported success. The
payload was computed, the write to the materialized-view table failed, the
exception was logged, and the caller saw a normal return — so the job reported
"Rebuilt 1 view(s)" while the stored map was unchanged, indistinguishable from
a real rebuild in Activity and in the logs. The shared build helper now takes
a flag: serving a cache miss still never breaks a response over a failed cache
write, but a rebuild raises, because persisting is the job. -
A rebuild where every view failed still completed. Per-view failures were
caught, logged as one line without a traceback, and the run returned a normal
summary withcount: 0— which reads as a successful no-op. Failures are now
collected and named in the job's own result, logged at ERROR with the
traceback, and a run in which nothing rebuilt and something broke fails
instead of completing. A partial run still completes, with the failed views
named. -
Two diagnostics answered "healthy" when they had simply broken. The
super-region "has this ever been built?" probe returned built on any
exception, and the pending-work count returned nothing to repair on any
exception. Both still degrade the same way — a broken probe must not drive an
endless rebuild loop — but they now log the traceback, so an unreadable
substrate and a healthy one are no longer the same answer.
Changed
- Failure messages name a cause and a next step. Every graph-rebuild error
now says which view failed, why (exception type and text), and what to do
next — check vector coverage and run AI Compute Missing before re-running for
the paper map, check author hydration for the author network, and where to
find the full traceback. The reader of an Activity row has none of the context
the traceback has; a cause with no remedy is a dead end.
v0.23.2
ALMa v0.23.2
Patch release: two Health buttons that could never do what they promised. The
"Rebuild map layouts" operation added in 0.23.1 shipped unable to run when a
human clicked it, and "Paper group integrity" sat at a permanent warning above a
repair with nothing to repair.
Fixed
-
"Rebuild map layouts" did nothing when you clicked it, and said nothing
either. Both runs on a live instance finishedcompletedin under
0.3 seconds with every map view still stale and not one line in Activity. The
pass consulted the background admission gate, and both of its policies
require the app to be idle — but clicking the button is itself a user request,
which is exactly what makes the app non-idle. A human-triggered run could
never pass its own gate. It returned before the "running" status write, so
nothing reached Activity, and the job harness then stamped the job
completed. The trigger source now travels with the run: a click skips the
background idle gate (the concurrency guards that exist for real reasons still
apply), and a click that finds everything already fresh gets an Activity line
saying so, rather than being indistinguishable from a broken button. -
The layout pass deferred to a job it had started itself. It enqueues the
super-region substrate build, then a few lines later asks "is a layout being
fitted right now?", sees that very job, and stops before the first view —
"placed 720, rebuilt 0 view(s)". The in-flight check now takes several keys
instead of one, so a caller can exclude both its own operation and the work it
just spawned. -
"Paper group integrity" warned about 84 defects no repair could clear. The
card readwarningwhile the repair underneath read0 pending, so its Run
button could never move the number. The dimension counted every defect; the
operation counted only the subset it can actually fix, and the card showed a
full card if either was non-zero. Severity now ranks on the fixable
remainder and reports the terminal slice separately — the same "N missing ·
M no fix" rule the metadata gaps already used. Every defect is still counted
and still listed; only the colour changes.The 84 in question are standalone datasets and software releases (Zenodo,
NEMAR/OpenNeuro) plus book chapters whose parent volume isn't in the corpus.
A component with no parent to attach to is a terminal state, not pending work. -
Two paper-group defects were unfixable by any code path.
- A row that is its own canonical/parent was skipped by the merge primitive
rather than repaired, so one self-pointer permanently pinned three defect
kinds. It is now cleared. - The "which orphans can we link?" counter used a narrower rule than the
detector that raises the defect, so a component typed by work type alone was
flagged as broken but excluded from the fixable count — reported terminal
while the repair could in fact link it. Both sides now share one predicate.
- A row that is its own canonical/parent was skipped by the merge primitive
-
Crossref hydration created paper-group defects on every sweep. It was the
one ingestion path that wrote component/parent pointers without settling the
group afterwards, so a paper it newly classified as a component kept its
vectors, clusters, feed rows and preference rows — regenerating defects
between every reconcile run. Feed, the importer, the OpenAlex upsert and
Library all settled already; this one now does too. -
The Map page's Rebuild button reported success for a no-op. A response of
"a rebuild is already running" fell through to the "Layout rebuilt" toast, and
the mutation had no error handler at all, so any failure was completely
invisible. Both now say what actually happened.
v0.23.1
ALMa v0.23.1
Patch release: the map/Signal-Lab substrate becomes visible and repairable from
Health, and a pressed action button now looks pressed.
Fixed
-
Signal Lab was silently switched off on a full corpus. All three games
reported "the corpus map hasn't been built yet, or no region has enough
judgeable papers" with 11k papers and every map view present. Neither clause
was true:graph:super_regions— Signal Lab's entire substrate — had simply
never been built. Its freshness owner runs inside the graph layout pass,
after the idle gate, and the gate's urgency escalation only ever covered the
four paper/author layout views, so a never-built substrate waited behind the
ordinary gate. On prod that gate never opened at all (the/healthheartbeat
counted as user activity, fixed in 0.22.0), so it waited indefinitely. It now
gets its own permissive gate — deliberately not by raisingurgent, which
would also authorise a full map refit nobody asked for. -
A pressed action button now reads as pressed. The active wash sat at the
same 10% as the hover wash, so a saved paper and a merely hovered one looked
identical and a card never showed its own state. Active now carries about
double the wash plus an inset ring — the same "physically depressed" logic the
switch and slider thumbs use. Applies everywhere the action bar does: Home's
sticky notes, Feed, Discovery, Library. -
Signal Lab's "not available" message named the wrong cause. Every empty
queue answered with "the corpus map hasn't been built yet, or no region has
enough judgeable papers" — two unrelated causes behind one sentence. On a
real instance BOTH clauses were false: the maps were built and the corpus held
11k papers, butgraph:super_regionshad never been built, so the message
sent you to check the two things that were healthy while the artifact that was
actually missing went unnamed. Each cause now answers for itself and names the
Health operation that repairs it. A diagnostic that names the wrong cause is
worse than none.
Added
-
Health → "Rebuild map layouts." Missing or drifted layouts, including the
super-region substrate, now have a pending count, an explanation and a repair
button, and sit in the maintenance sequence at order 88 — after embeddings
(a layout is fitted on the embedding set) and before cluster labels (which
label what it produces). It delegates to the same pass the scheduler runs, so
the button and the background tick cannot drift apart.This is the general lesson from the incident: an artifact that a user-facing
feature depends on needs a health surface, or its absence is invisible.
v0.23.0
ALMa v0.23.0
Truthful, comparable scores. v0.22.0 unified the ranker but rescaled weights
per paper when a signal was missing. That broke comparability and did it with a
bias. This release removes rescaling and fixes the reasons signals went missing
in the first place.
Fixed
- Scores are comparable again. Weights are now FIXED for every paper. v0.22.0
renormalised over the families it could measure, so two papers scoring 69 no
longer meant the same thing — and the bias ran one way: the families that go
missing are the ones papers score badly on (corpus means: citation 0.28,
lexical 0.27, semantic 0.51, against feedback 0.96, topic 0.74). Dropping a
weak family and handing its weight to the strong ones was a free upgrade, so a
paper rose by having less evidence. On prod, Feed rows with three families
missing averaged 68.1 against Discovery's 62.0 with all ten measured.
An unmeasured family is now imputed at its corpus prior mean: unknown neither
helps nor hurts, and the denominator is always 1.0. - Feed and Online Search papers were missing three families for no good
reason. All three are now measured:- citation — availability keyed only on
field_availability, which the
retrieval merge layer is the sole writer of. Feed papers carrying a perfectly
goodcited_by_countfrom their own ingest were still marked unmeasured.
Availability now asks whether we HAVE a citation count. - semantic — Feed never passed the candidate's own SPECTER2 vector, so
candidate_embedding_readywas false for every row and the heaviest evidence
family was unmeasurable. Feed now batch-loads vectors from
publication_embeddings, the same source Discovery uses. - retrieval — Feed has no channel fusion, but it does have monitors, and two
monitors independently surfacing one paper is the same kind of evidence as two
retrieval channels doing so. Feed now counts distinct matching monitors; Online
Search counts distinct source APIs, which the merge layer already knew and was
keeping for display only.
- citation — availability keyed only on
- Corpus map: library membership reads at a glance. Non-library dots were 0.55
opacity against 1.0 — and under the Clusters / Year / Score colour modes the hue
is overwritten per node, leaving alpha and a 0.5px radius as the only channels.
Library papers are now solid, tracked-only papers clearly translucent.
Changed
- The breakdown marks imputed families estimated, with the corpus average on
hover, and says plainly that every paper is scored on the same ten families.
Upgrading
Scores shift again, and this time toward being comparable across surfaces. Feed
and Online Search papers gain three real families, so their scores become
meaningfully different from (and no longer inflated against) Discovery's.
Existing rows rescore on their next refresh.
v0.22.0
ALMa v0.22.0
One ranker, one truthful score breakdown. Discovery had two scorers stacked
on top of each other; the paper card was drawing the wrong one. This release
deletes the dead one, puts every surface on the survivor, and rebuilds the
Why panel so what you read is what ranked the paper.
Fixed
- The score breakdown now explains the score. The card drew nine bars
decomposing a composite the ranker had already discarded, beside a final
score computed from a different feature set with different weights — the bar
and the number were never the same quantity. The panel now renders the
ranker's own decomposition, and the rows sum to the score beside them
exactly. A backend invariant (Σ family points + Σ adjustments + clipping == final_score) is enforced by tests, so the two can never drift apart again. - The same paper scored differently on different pages. Feed and Online
Search used the discarded composite as their real score while Discovery used
the family prior. All three now rank through one entry point. - Explore / Balanced / Exploit did nothing to Discovery ranking. The mode's
weight multipliers lived in the dead stage. They now reweight the ranking
families, as the control has always claimed. - Thin metadata no longer counts against a paper. A family with nothing to
measure was silently credited a neutral half-weight — so a paper with no
journal collected half the venue weight for free, and one with no embedding
was scored as though its similarity were mediocre rather than unknown.
Unmeasured families are now dropped and the rest rescaled; the panel says
which ones were not measured. - A second, stale score leaked into the UI. Discovery's provenance carried
ascore_pctcomputed before ranking, rendered beside the real one. Removed. - The Discovery table's Score column rendered a 0–100 score as
0.71under
a comment claiming the engine normalises to[0,1]. It now matches the
meters on the cards. - Illegible breakdown arithmetic. The
(86% × 0.18w)annotation rendered
atslate-300on cream — present in the DOM, invisible on screen. - Expanding a breakdown row opened the paper. The click bubbled into the
card.
Changed
- The Why panel is family-shaped. Ten rows — Semantic, Topic, Retrieval,
Author, Lexical, Recency, Citation, Feedback, Preference, Venue — each
expandable to the raw measurements underneath, with competing measurements
markedused/lostso a family's value is never unexplained. Colour is
identity (which family), never valence. usefulness_boostis deleted. Three of its four atoms were literally the
same values the model already read; the fourth,metadata_quality, measured
our own hydration completeness rather than the paper — weighting it would
have ranked pipeline artifacts.- Multi-source agreement and citation-fabric strength now count. Both were
additive bonuses bolted onto the discarded composite, so on Discovery they
moved nothing. They are now atoms inside the retrieval and citation families. - Negative preference moved inside the families. The free-standing
30-point dismissal cluster penalty is retired; rejection now enters as
bounded penalty atoms on the semantic and lexical families. - Settings → Discovery names the families it tunes, so a slider is
findable from the row it changed. One slider drives two families
(Semantic + Lexical, 70/30) — now stated rather than left to infer. - One score vocabulary. Five drifted label maps (three frontend, one
backend route, one settings card) collapsed into the ranker's own family
table; labels and descriptions travel with the payload, so a family cannot
be called three different things on three pages.
Docs
docs/reference/scoring.mdrewritten around the ten families, their
combinators, and the closure invariant.- README refreshed: the surface list matches the real navigation (Insights
retired into Library → Analytics some releases ago), a new section explains
how ranking works, and all screenshots are current.
Connector
alma-connector-0.22.0.xpiattached below (unchanged behavior; version
tracks the release).
Upgrading
Existing recommendations keep their stored scores until the next refresh, and
rows ranked by an older ranker show "no breakdown stored" instead of a wrong
one. Feed items rescore on the next Feed refresh. Absolute scores shift with
the availability rescaling — ordering is what matters, but the numbers you
have seen will move.
v0.21.1
ALMa v0.21.1
Patch release: operational polish on top of v0.21.0.
Fixed
- Health endpoint reports the release version —
/api/v1/healthreturned the HTTP contract constant (1.0.0), which read as a wrong build number on a health probe. It now reports the pyproject-derived release version (this very image says0.21.1); the contract version remains at/api/v1/version. Deploy tooling uses this to verify what's actually running.
Changed
stableimage channel — release-tag builds now also publish:stable,:stable-gpu,:stable-lite. Unlike:latest(which moves on every main push),stablemoves only on releases — point auto-updaters (e.g. watchtower) at it to track tested, released builds.- Removed the never-implemented
immediatealert schedule value — reserved vocabulary with zero implementation behind it; unknown schedule strings never fire, and that guarantee is now pinned by tests.
Tooling (repo, not runtime)
- New local release pipeline (
scripts/release.sh): preflight gates, full test gate, version bump, tag, connector signed from the tag, GitHub release from committed notes — resumable, no CI secrets, replaces the per-clone pre-push hook. - New
scripts/deploy-prod.sh: versioned container deploy (pinnedX.Y.Zor thestablechannel), env/volume-preserving, health-verified. - Docs synced across the alerts overhaul, per-channel dedup semantics, and the release/deploy flow.
Connector
alma-connector-0.21.1.xpiattached below (unchanged behavior from 0.21.0; version tracks the release).
v0.21.0
ALMa v0.21.0
Alerts — audit + overhaul
- Per-channel delivery dedup — a paper delivered on Slack stays eligible for email until email actually receives it (previously one success on any channel consumed the paper for all channels). Existing databases migrate automatically.
- Truthful rule cards — every rule shows what it watches (monitor, keywords, score threshold, author…), warns when it isn't assigned to any digest (an unassigned rule never runs), and gets a test-fire button that previews matching titles without sending anything.
- Digest cards show outcomes — a last-run status chip (worst channel outcome, so failures are never hidden) that jumps to the pre-filtered History tab, plus a "next run" line computed from the same slot math the scheduler sweep uses.
- One-click automations are atomic — suggested automations apply through a single endpoint (no orphan rules on failure), disappear once created (no duplicate factory), propose exactly the channels you have configured, and vanish entirely when no channel is configured.
- Rule validation covers all 9 types — a rule config that would silently match nothing is now rejected with a precise 422 instead of being saved.
- Author rules pick from your followed authors (custom-ID escape hatch remains).
- Fixed: edit dialog losing your typing on background refetches; edits silently resetting rule-config extras (lookback windows, lens scoping); weekly→manual leaving stale schedule badges; label-only branch rules matching nothing; history pagination erroring past the API cap.
- History retention — evaluation history is pruned past 180 days by the hourly sweep (Insights trends are unaffected).
Authors / Insights
- One attention model — Health's tracked-authors popup, the Authors page "Needs attention" section, and Insights diagnostics now compose from the same canonical builders, so counts and rows can no longer disagree.
- Failed operations are named — the last-24h failures list the actual operations instead of dead-ending on a count.
- Stale-corpus backfill — a maintenance task sweeps tracked authors whose corpus went stale and re-runs the deep refresh per author.
- Periodic scheduler jobs persist the actual error on fatal failures, so Activity shows why, not just "failed".
Connector
alma-connector-0.21.0.xpiattached below (Firefox, signed; install via about:addons → Install Add-on From File).
v0.20.1
ALMa v0.20.1
Discovery / Lenses
- Tunable relevance floor — Min Relevance Score (Settings → Discovery → Limits) drops low-scoring recommendations so the feed stops padding with weak, off-topic matches.
- Lens-scoped taste — every non-library lens computes its taste profile + scoring documents from its OWN context (collection / topic / tag), not the whole Library. Collection lenses are no longer contaminated by unrelated Library topics/authors.
- Collection-tied discovery — a collection lens surfaces Library papers from other collections (gold "in library" ribbon + a distinct "Add to collection" action).
Collections & Import
- Fixed broken bulk add-to-collection; all collection_items writes go through one helper.
- Staged imports keep their target collection (deferred membership).
- Preflight distinguishes skipped vs promoted matches; online-search save can file into a collection.
- Paper card: add to Library + one or more collections in one action.
Graphs & platform
- Corpus-scope graphs dim non-library nodes.
- App logs now reach the console under uvicorn.
Under the hood
- Repo-wide ruff lint-section config + type-hint modernization; React Fast-Refresh variant extraction.
- Build fix: track frontend/src/build chunk config (was gitignored, broke the Docker image build).
Browser connector
- ALMa Connector v0.20.1 (signed .xpi attached).
v0.17.2
ALMa v0.17.2
- Find & Add results now rank by query relevance (cross-source reciprocal-rank fusion + query-text match — search-engine semantics). Personal fit stays as the per-result "why" chips and an optional sort.
- New sort dropdown on Find & Add results: Best match (default) / Personal fit / Newest.
- Find & Add streaming lanes get a 15s deadline (was 8s) and a fail-fast Semantic Scholar path: 429s now surface as a truthful "rate-limited" chip instead of a generic timeout.
- Fix source-provenance chips dropping engines when a paper was returned by 3+ sources.
- Browser-connector save latency roughly halved: response-cache seeding across sibling identifiers (DOI ↔ OpenAlex id), DOI shape-gating to avoid guaranteed-404 lookups, no second referenced-works round-trip; popup activates on the active server's ping and Range-requests PDFs for the DOI scan.
- Schema migrations consolidated into versioned
core/migrations.py(PRAGMA user_version, 19 migrations); bootstrap DDL is current-shape only and the startup schema is guaranteed. - Includes signed Firefox connector XPI
alma-connector-0.17.2.xpi.
Verified locally:
- Full backend suite 309 passed / 1 skipped (
.venv/bin/python -m pytest) npx tsc --noEmit+ frontend vitest 38 passed- Fresh-DB schema diff vs migrated-DB clean; real dev DB migrates 0→19 idempotently
- Signed XPI built via
extension/release.sh --local --version 0.17.2