Skip to content

Releases: costantinoai/alma-library-manager

v0.23.4

Choose a tag to compare

@costantinoai costantinoai released this 29 Jul 12:36

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 and ALMA_DISABLE_NETWORK are 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 short write_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

Choose a tag to compare

@costantinoai costantinoai released this 28 Jul 12:51

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 with count: 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

Choose a tag to compare

@costantinoai costantinoai released this 28 Jul 12:11

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 finished completed in 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 read warning while the repair underneath read 0 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.
  • 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

Choose a tag to compare

@costantinoai costantinoai released this 28 Jul 09:47

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 /health heartbeat
    counted as user activity, fixed in 0.22.0), so it waited indefinitely. It now
    gets its own permissive gate — deliberately not by raising urgent, 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, but graph:super_regions had 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

Choose a tag to compare

@costantinoai costantinoai released this 28 Jul 08:41

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
      good cited_by_count from 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_ready was 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.
  • 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

Choose a tag to compare

@costantinoai costantinoai released this 28 Jul 00:36

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
    a score_pct computed before ranking, rendered beside the real one. Removed.
  • The Discovery table's Score column rendered a 0–100 score as 0.71 under
    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
    at slate-300 on 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
    marked used / lost so a family's value is never unexplained. Colour is
    identity (which family), never valence.
  • usefulness_boost is 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.md rewritten 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.xpi attached 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

Choose a tag to compare

@costantinoai costantinoai released this 19 Jul 14:01

ALMa v0.21.1

Patch release: operational polish on top of v0.21.0.

Fixed

  • Health endpoint reports the release version/api/v1/health returned 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 says 0.21.1); the contract version remains at /api/v1/version. Deploy tooling uses this to verify what's actually running.

Changed

  • stable image channel — release-tag builds now also publish :stable, :stable-gpu, :stable-lite. Unlike :latest (which moves on every main push), stable moves only on releases — point auto-updaters (e.g. watchtower) at it to track tested, released builds.
  • Removed the never-implemented immediate alert 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 (pinned X.Y.Z or the stable channel), 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.xpi attached below (unchanged behavior from 0.21.0; version tracks the release).

v0.21.0

Choose a tag to compare

@costantinoai costantinoai released this 18 Jul 15:39

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.xpi attached below (Firefox, signed; install via about:addons → Install Add-on From File).

v0.20.1

Choose a tag to compare

@costantinoai costantinoai released this 16 Jul 16:20

ALMa v0.20.1

Discovery / Lenses

  • Tunable relevance floorMin 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

Choose a tag to compare

@costantinoai costantinoai released this 04 Jun 18:23

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