Skip to content

Releases: VirInvictus/cquarry

## v1.28.1 (2026-10-23)

Choose a tag to compare

@github-actions github-actions released this 23 Oct 04:54

v1.28.1 (2026-10-23)

The Calibre 9.17 watch audit: NBSP preservation and an atomic extra-file write

The Friday parity-watch automation's second schema-search trip (v9.17.0,
tagged 2026-10-23) turned out to be upstream fixing a regression in the
exact adapter cquarry mirrored in 1.26.2. The full read-only audit against
this tree found two fixes to make and verified the rest of the release is
either out of cquarry's scope or already safe by construction; the per-file
verdicts live in the roadmap's Calibre 9.17 watch audit section.

  • The whitespace collapse now preserves the three non-breaking spaces
    (NO-BREAK SPACE, FIGURE SPACE, NARROW NO-BREAK SPACE), exactly like
    upstream 9.17's normalize_whitespace. 9.16's plain ' '.join(x.split())
    -- mirrored by 1.26.2 -- silently turned them into regular spaces because
    Python's str.split() treats them as whitespace, and upstream reverted
    that as a regression in 9.17 after users hit it in text and comments
    columns. All four _write_pattern_a branches and
    add_custom_column_values normalize through the same helper; ends still
    strip, breaking runs still collapse, and a trailing NBSP still strips away
    with the ends (upstream's composite behavior).
  • add_data_file writes through a temp file + atomic replace (upstream
    9.17's add_extra_file fix): opening the destination directly truncated
    an existing file whenever the source read or the write failed part way.
    A failed copy now leaves the old file intact and no temp orphan behind.
  • Audited, no action: restore_original_format cannot destroy the
    current format on failure (cquarry reads the ORIGINAL bytes into memory
    first and places them atomically after the rows commit -- upstream's
    empty-placeholder bug never existed here); the trash move is a
    same-filesystem atomic rename (.caltrash lives inside the library), so
    the partial-copy data-loss class upstream fixed does not apply;
    set_format's file swap stays the caller's atomic-replace job by
    contract; the remaining 9.17 work is notes-sidecar, in-memory cache, and
    GUI-facing robustness that cquarry does not mirror.
  • 707 tests pass (four new: NBSP preservation for single-valued text, the
    enum adapt-then-validate order with NBSP spellings, NBSP preservation in
    multi-valued items and appends, and the failed-copy intact-destination
    pin).

## v1.28.0 (2026-10-11)

Choose a tag to compare

@github-actions github-actions released this 12 Oct 03:53

v1.28.0 (2026-10-11)

The entity case-change wave (issue #6)

Third live occurrence of the same trap in the library (ABRAMS/Abrams on
2026-10-04, then Daw Books/DAW Books and ROC/Roc on 2026-10-11, the ROC
row carrying 40 accumulated books): a case-variant target spelling
resolved NOCASE to the existing row and the setter reported already-so,
silently no-oping the consolidation. Issue #6 offered two shapes; the fix
takes the parity one, because the spec's documented rationale turned out
to rest on a misreading of upstream.

  • The spec premise was wrong, and said so plainly. §5 deviation 12
    (2026-09-12) claimed upstream's write default reuses a case-different
    entity row as-is (allow_case_change=False), citing get_db_id. The
    flag flows from many_one/many_many, whose callers default it True
    (Writer.set_books db/write.py:558, set_field cache.py:1970 -- the
    path Calibre's GUI metadata edits ride); only the retired legacy
    db.py API wrapped False, and change_case then rewrites the row's
    name and dirties every linked book. The rewritten deviation records
    the correction in place, in the same class as the two upstream-factual
    fixes the v1.25.0 audit landed.
  • set_publisher consolidates case variants (the issue's headline).
    set_publisher(id, "DAW Books") on a Daw Books row now rewrites the
    row's name to the requested spelling and touches + queues every book
    linked to it, returning True. set_series is the same shape, indexes
    untouched by an in-place re-case.
  • Authors get the full upstream treatment. set_authors (and every
    path that funnels through it: add_book, the trash restore) compares
    and stores the display form and updates each linked book's
    author_sort only when it matched the string recomputed from the
    stored sorts modulo case -- upstream's strcmp rule, so a hand-tuned
    sort on an unrelated book survives a spelling fix made from another
    book. The row's own sort is deliberately NOT rewritten: upstream's
    change_case writes only the name column (its asort_map recompute is
    session memory that reverts on reopen), so the stored sort -- and any
    hand-tuning in it -- survives, with set_author_sort_name remaining
    the re-keying verb. Paths re-lay through the usual deferred machinery.
  • The tag surface stops fragmenting. add_tag used to resolve the
    exact spelling only and INSERT a duplicate row for a case variant --
    the only cquarry path that could create case-duplicate rows. It now
    resolves NOCASE and re-cases the shared row (every linked book
    touched), and remove_tag gained the NOCASE fallback so removing
    roc reaches a ROC row instead of honestly failing against the
    case.
  • Custom-column text family included, exclusions named. Pattern-A
    text/series/multi-text value rows re-case like the builtin entities
    (upstream writes them through the same many_one/many_many), and
    multi-valued items dedupe case-insensitively keeping the first
    spelling (upstream's uniq(vals, kmap)), so ["ya", "YA"] stores one
    row. Deliberately excluded: custom enumerations (upstream's enum
    writer hardcodes case changes off), languages (upstream keys them
    exactly; odd-case rows reuse as-is), ratings (numeric), and the
    rename/removal verbs, whose exact-spelling-first resolution is the
    ratified 1.19 contract -- rename_entity remains the consolidation
    tool, including for pre-existing case-duplicate rows cquarry
    deliberately does not heal (fix_case_duplicates stays Calibre's).
  • Eleven new regression tests in TestCaseChangeParity (the DAW
    scenario, series indexes, the author strcmp rule, tag
    re-case/already-linked/removal, custom-column set/append/dedupe,
    language reuse, batch rollback), plus the three tests that pinned the
    old behavior re-pinned to the new contract.

## v1.27.0 (2026-10-11)

Choose a tag to compare

@github-actions github-actions released this 11 Oct 22:31

v1.27.0 (2026-10-11)

The author display-form parity wave (the three issue #5 follow-ups)

The 1.26.3 collision fix (issue #5, earlier today) made set_authors
reuse an existing piped author row; its review verified three adjacent
surfaces still resolving stored spellings only, each logged as a
deliberate non-goal. Brandon chose to ship all three as one wave the
same day. Together they complete the author display-form parity story:
upstream keys EVERY author lookup by the display form (AuthorsTable
unserializes stored pipes back to commas when it loads the id map,
tables.py -- an authors-only flip), and its authors write adapter flips
incoming pipes to commas before anything else.

  • Lane 1, resolution (write surface). set_author_sort_name,
    rename_entity (old-name lookup AND merge detection),
    set_link_map(kind="authors"), and remove_entity_everywhere now
    resolve author names through _resolve_author_id: exact raw
    spelling first, then that spelling NOCASE (lowest id, the ratified
    1.19 contract untouched), then the piped display form NOCASE. A
    comma name reaches a piped row instead of raising, and renaming to a
    comma spelling whose piped form already exists MERGES into it, the
    survivor keeping its stored name -- exactly upstream's replace path.
  • Lane 2, layout. _first_author_name (the single input every
    path builder shares) now shows the display form, so directories and
    format stems read Leckie, Ann/... for piped-stored authors where
    1.26 laid out Leckie_ Ann/ (the pipe hit _ascii_filename's
    illegal-character map). This is convergence, not a new divergence:
    Calibre's own layout inputs are unserialized display forms.
    Pre-1.27 underscore directories linger untouched until each book is
    next written, then move -- the same shape as any layout correction;
    no bulk migration, and find_book_by_path/format_path_index match
    stored paths, so read-side matching is unaffected.
  • Lane 3, inputs. set_authors and add_book display-normalize
    incoming author names before any resolution, dedupe, or path
    construction (pipes become commas, upstream's authors-only write
    adapter), so a pipe-bearing input resolves and stores exactly like
    its comma form instead of matching only its own spelling and
    INSERTing a duplicate. The other name-taking verbs deliberately do
    NOT normalize: their raw-first resolution is what lets a caller
    target a piped row by its stored spelling when both spellings exist.
  • Nine new regression tests across the three lanes (resolution
    reaching piped rows through every verb, the display-form merge,
    layout assertions for directories and format stems, the
    underscore-to-display re-lay, and the piped-input round trip: input
    pipes -> normalized -> stored piped -> layout display). Suite 683 to
    692; spec.md, CLAUDE.md, API.md, and the roadmap's issue #5 section
    document the completed surface.

## v1.26.3 (2026-10-11)

Choose a tag to compare

@github-actions github-actions released this 11 Oct 22:05

v1.26.3 (2026-10-11)

set_authors resolves piped author rows instead of colliding with them (issue #5)

Live failure on 2026-10-11 (big-wave library phase 3, book 10083): the
author row existed as the pipe-import artifact John William Strutt| Baron Rayleigh and the caller called set_authors(10083, ['John William Strutt, Baron Rayleigh']). The lookup tried the raw comma
spelling only, missed, and the create path's INSERT
(name.replace(",", "|")) died on UNIQUE(authors.name) against the
piped row it was about to duplicate. The batch machinery rolled the
143-book pass back cleanly, so no data was hurt, but the crash fires
exactly when the comma form is the repair for an existing piped form,
which is the author-byline-repair case the setter exists for.

  • Author resolution now tries both spellings. set_authors and
    add_book resolve incoming display names through
    _author_row_for_name: the raw spelling first (so rename_entity's
    verbatim comma rows keep winning), then the piped form the create
    path would store, and only a double miss reaches the INSERT. This is
    upstream's observable behavior: Calibre keys its author lookups by
    the display form (AuthorsTable unserializes stored pipes back to
    commas on load), so a comma-name lookup there already reuses piped
    rows; cquarry's direct SQL needed both spellings tried explicitly.
  • Reuse semantics are unchanged: an existing row keeps its
    hand-tuned sort, the stored spelling is never rewritten, a raw
    comma match outranks a piped variant, and the orphaned piped
    duplicate then falls to the setter's standard prune. To fix a reused
    piped row's sort afterward, pass set_author_sort_name the STORED
    piped spelling (the display comma name raises there; that verb still
    resolves stored spellings only -- see the logged follow-ups).
  • The add_book dry-run plan stays truthful for the reuse case:
    _author_sort_keys shares the same resolution, so a plan over a
    comma name with an existing piped row reports new: False and the
    row's stored sort instead of predicting a phantom new author.
  • Logged follow-ups (adjacent surfaces this fix deliberately does not
    touch
    , each recorded in the roadmap's issue #5 section with
    upstream citations): the on-disk layout for piped-named authors lays
    out with the pipe mapped to an underscore (Leckie_ Ann/) where
    upstream uses the display form -- pre-existing since the 1.26
    creation shape, identical before and after this fix, and now on
    record; the reverse resolution direction (a pipe-bearing input
    against comma-spelled rows) still creates a second row where
    upstream's write adapter would reuse; and set_author_sort_name/
    rename_entity/set_link_map still resolve stored spellings only,
    so display names raise where reuse now silently works.
  • No schema, read-path, or search changes; nothing else in the write
    module resolved-and-inserted different spellings of the same name.
    Six regression tests cover set_authors reuse, hand-tuned sorts,
    NOCASE, comma-row precedence, add_book over a piped row, and the
    dry-run plan.

## v1.26.2 (2026-10-09)

Choose a tag to compare

@github-actions github-actions released this 09 Oct 19:39

v1.26.2 (2026-10-09)

Mirroring Calibre 9.16: the first parity-watch audit lane

Calibre 9.16 shipped on 2026-10-09 and tripped the new parity-watch
automation's schema-search tier (seven files under src/calibre/db/
moved, the most since 9.12). The full read-only audit against this tree
found one real divergence and ratified everything else; the per-file
verdicts live in the roadmap's Calibre 9.16 watch audit section.

  • Custom-column writes now collapse whitespace exactly like upstream's
    text adapters
    : single-valued text, series, and enumeration values go
    through the single_text mirror (' '.join(x.split()) over the
    stripped string; a whitespace-only value clears via its empty-to-None
    rule), and each item of a multi-valued text write collapses like
    upstream's multiple_text adapter has always done -- in both write
    verbs, set_custom_column and add_custom_column_values. Enumeration
    values are adapted BEFORE validation, so a double-spaced spelling of a
    listed value now normalizes and passes instead of raising. cquarry
    used to strip ends only and store the interior verbatim, so rows
    written here and rows written by the 9.16 GUI disagreed for values
    carrying double spaces or line breaks. Comments columns are
    unaffected: upstream deliberately moved them to the verbatim
    long_text adapter, so their behavior is unchanged.
  • Schema v27 ratified. The book_storage table cquarry mirrored
    from master in 1.25 shipped upstream in 9.16 with the same DDL and
    write semantics; the audit confirmed the support gate (`user_version

    27, fresh libraries born at 28, upgrades land there too) and cquarry's _require_table` ValueError on pre-v27 schemas. No code
    change was needed; CLAUDE.md's blob-writer bullet now records the
    shipped fact.

  • The roadmap records the audit: the 9.16 verdict per file (the
    search fast path and the categories cache need no action; the
    read-path reworks are internal mechanics), the stale gap row for
    get_book_storage (shipped in af41e52) removed, and the
    snapshot-retry hardening candidate updated to 9.16's 60-second
    busy-time budget + can_use_sqlite_in probe + /tmp fallback
    reference shape.
  • Nothing is owed downstream: no caller in CalibreQuarry, bindery-cli,
    Hermitage, or Carrel-calibre-web writes custom columns through
    cquarry yet.

## v1.26.1 (2026-10-02)

Choose a tag to compare

@github-actions github-actions released this 02 Oct 20:28

v1.26.1 (2026-10-02)

The closing sweep: documentation truth after the audit wave

A verification sweep over the 1.26 wave's documentation found the prose
lagging the shipped code and the recorded facts. No behavior changes; the
only Python delta is one docstring. Every fix was re-verified against the
current tree and upstream's reference clone before landing.

  • CLAUDE.md's blob-writer bullets teach the 1.26 storage shapes. The
    bullets still described the 1.25 passthrough (str verbatim / raw
    json.dumps / wrapped entry) and instructed "do not 'complete' that" --
    which, after 1.26 shipped the conversion-options frame, read as a
    mandate to revert a shipped fix. They now match spec §3.6 and API.md:
    json.dumps on every plugin-data payload, upstream's deterministic
    protocol-2 BINSTRING frame, the bare str->str map in the data column.
  • The author-sort bullet carries the 1.26 refinements (the honest
    no-op on an equal stored sort and update_books=False's row-only
    write), and the four consumer-wave spots in CLAUDE.md and API.md speak
    in the recorded past: Carrel 0.6.43 and Hermitage 1.8.6 retired their
    helper copies on 2026-10-02.
  • set_plugin_data's docstring matches its body: every payload goes
    through json.dumps(val, default=str) since 1.26; the docstring's
    first half still claimed a str stores verbatim.
  • Spec §5 carries the search-audit divergences it promised. New
    entries 14-17 record the enum-multi count-operator scope, the
    relative-date vocabulary pair (negative Ndaysago upstream, singular
    1dayago here), loc:="X"'s contains-vs-exact result-set difference,
    and the search-behavior preferences -- each verified against upstream's
    reference clone, each dependency- or GUI-bound. The roadmap audit block
    had claimed "§5 carries them" while §5 carried none of them.
  • The audit's seventh search divergence is retracted. Unregistered
    @name: does not sweep as an all: text search upstream:
    get_user_category_matches returns the empty set for an unknown name,
    exactly like cquarry (db/search.py:601-602, :719-721, :907-929 in the
    reference clone). There is no divergence to record; CLAUDE.md's
    match-nothing claim was right all along, and the roadmap now says so.
  • Erratum, tag v1.25.0 vs patchnotes.md. Commit c03d7dc backticked
    the @Name token in the v1.25.0 entry after the tag was cut (GitHub
    autolinked the bare token as a mention), so the tag message and this
    file differ by that one formatting pair. Tags are immutable and stay
    untouched; the delta is recorded here rather than rewritten.

## v1.26.0 (2026-10-02)

Choose a tag to compare

@github-actions github-actions released this 02 Oct 19:03

v1.26.0 (2026-10-02)

The parity audit's fix wave: every confirmed bug and divergence lands

The 2026-10-02 eight-agent parity audit (four reviewers, two bug hunters,
one compat-merge review, one claim verification -- 8/8 top claims confirmed)
found three shipped-code bugs, eleven confirmed divergences, and a gaps
list. This release fixes the bugs and the divergences users plausibly hit,
and records the rest in the roadmap's audit block.

  • set_book_storage stores upstream's bare data map (was the wrapped
    {'timestamp', 'data'} entry, which Calibre's validating reader dropped
    on every cquarry-written row), with upstream's guards reproduced
    (str-only keys/values, the 1 MiB UTF-16 cap, newest-timestamp-wins) and
    the missing read half shipping as get_book_storage.
  • remove_book cleans books_pages_link and book_storage -- the two
    FK-cascade tables neither books_delete_trg nor cquarry's
    foreign-keys-off connection covered; the stranded pages row (its book
    column is the PRIMARY KEY) broke move_book_from_trash with
    IntegrityError on any library with page counts.
  • get_categories follows upstream's is_category = normalized rule
    (direct-storage customs out -- their raw values made engine-unparseable
    expressions), gains the identifiers category (one node per identifier
    key), and escapes expression values for quotes/backslashes.
  • Search parity, claim-verified: bare-term sweeps match identifier
    KEYS like upstream (the 1.18-1.25 spec sentence said otherwise and was
    wrong about upstream); all:false is upstream's sequential loop
    (missing AT LEAST ONE swept field, not missing every); cover: is
    numeric with exactly true/false special-cased (cover:1 works,
    cover:yes raises).
  • New author rows follow upstream's creation path: the name stores
    with commas as the legacy pipe and the sort is
    helpers.author_to_author_sort's flip under the default tweaks -- the
    old verbatim defaults rested on a spec sentence that was factually
    wrong about upstream.
  • Writer fidelity: remove_format queues the pages rescan;
    set_plugin_data json.dumps's every payload (the str-verbatim
    shortcut stored strings upstream's reader cannot decode);
    set_conversion_options wraps payloads in upstream's deterministic
    protocol-2 frame (fresh rows were unreadable by Calibre's unpickling
    reader); set_custom_series_index writes the link row's extra float
    and fresh series assignments seed it at 1.0;
    set_author_sort_name gains update_books=False and its long-promised
    honest no-op; copy_book_from_library carries a 0.0 series index
    verbatim and its author-sort postprocess no longer rewrites existing
    destination books.
  • Recorded, not adopted (roadmap audit block): the enum count-operator
    gap, negative Ndaysago, loc:="X"'s contains-vs-exact degradation,
    translated date vocabulary, the search-behavior prefs, upstream's
    localized language node names, ICU sort keys, and the popularity/
    first-letter sort parameters -- each dependency-bound or GUI-state-
    bound, now named in spec §5 or the roadmap.
  • Housekeeping: the compat branch carries the merge-review erratum
    (restored its own 1.18.0+py313.1 entry; suite count corrected to 653).
  • Suite: 672 tests (flat -- every fix reshaped existing pins rather than adding volume; the new pins replaced the wrong-shape ones one-for-one).

## v1.25.0 (2026-10-02)

Choose a tag to compare

@github-actions github-actions released this 02 Oct 15:55

v1.25.0 (2026-10-02)

The parity program's second release: Phases 16, 17, and 18 land

The 2026-09-29 reopen's remaining three phases ship in one release: the
read-surface wave with the four shared-helper promotions (Phase 16), the
write-side extras (Phase 17), and the restricted tag browser (Phase 18). The
Cross-Repo Rule consumers affected this round are Carrel-calibre-web and
Hermitage; the four promotions carry the standing 2026-10-01 waiver (the
helpers land here, the consumers' private copies retire in a future consumer
wave), and both consumer roadmaps carry the adoption rows.

  • Phase 16 reads. get_cover_bytes/get_cover_last_modified (the
    web-frontend pair: raw bytes for serving, an aware-UTC mtime for
    conditional GET, riding get_cover_path's resolution);
    virtual_libraries_for_books and user_categories_for_books (the
    inverse maps: every book -> the wings / @Name members it holds, resolved
    through the same engine paths as the search locations so the answers
    agree with vl:/@Name: queries; wings that fail to evaluate are
    skipped with a warning where upstream splices an error string into the
    name tuples); format_hash/format_metadata (the SHA-256 file-changed
    detector the FTS sidecar's hash columns compare against, plus the
    on-disk {path, size, mtime} facts); books_by_year/books_by_month
    (upstream's date bucketing generalized to any date field, builtin or
    date-typed custom column, restriction-shaped; the 0101/0100 sentinels
    bucket nowhere, exactly as the search engine treats them);
    get_next_series_num_for (the preference-aware next number, builtin or
    series-typed custom column, with current_indices=True; one boundary
    named rather than faked: upstream reads series_index_auto_increment
    from its tweaks files, which metadata.db never carries, so the setting
    comes from the library's preferences table with upstream's shipped
    default "next"); get_annotations_filtered plus
    get_annotation_users/get_annotation_types/get_annotation_styles
    (the annotation conveniences: the decoded view plus user/kind/style
    filters, a limit, and Calibre's removed-skeleton tombstones hidden by
    default -- upstream's ignore_removed inverted to the renderer-facing
    default; styles cover only what the library holds, since the viewer's
    builtin catalog is GUI constants, not data); read_backup (Calibre's
    stored sidecar OPF as bytes, for diffing its last write against the
    rows; reads what Calibre wrote, the OPF-generation decline untouched);
    and the XS tail: get_size_stats (notes always 0 per the recorded
    decline), is_fts_enabled (the preference; the in-process pool state
    is not database-visible), get_all_link_maps_for_book (the four
    builtin fields plus link-carrying custom columns), and
    get_last_read_positions fmt/user/order_by/limit filters. The tail
    rode one real fix: load_custom_column built every normalized column's
    link map but stashed it series-only, so custom_column_links answered
    empty for text/enumeration/rating despite documenting all four.
  • Phase 16 promotions (the 2026-10-01 waiver applies to all four):
    ordered_virtual_library_names (Calibre's sidebar order: stored tab
    position first, unknown names alphabetical, unparseable positions
    ranking with the unknowns), helpers.unpipe_author (the
    pipe-flattening display helper, render-identical with the consumer
    copies), CalibreDB.tag_rollup_ids (the id-set sibling of the
    counts-only rollup, the engine's anchored-subtree rule), and
    helpers.IDENTIFIER_LINKS + identifier_link() (Hermitage's canonical
    identifier-to-link table; ISBN resolves to openlibrary.org per
    Brandon's 2026-09-29 call, Carrel's WorldCat mapping switches in its
    consumer wave).
  • Phase 17 writes. set_author_sort_name (the row-level author sort
    writer; book-level author_sort recomputed " & "-joined in link
    order) and set_link_map (the generic per-value link writer over
    authors/series/publishers/tags plus normalized custom columns by
    #label; one named deviation: unknown values raise where upstream
    skips silently, because a link aimed at a misspelling should fail
    loudly); set_pages (the page-count row writer clearing needs_scan:
    a real value is what the pending rescan waited for); the data/
    directory verbs (list_data_files/get_data_file reads,
    add_data_file/rename_data_file/remove_data_files writes -- pure
    filesystem like the trash verbs, one shared traversal guard, upstream's
    merge-conflict auto-rename layout); copy_book_from_library (the
    blessed cross-library copy composed from sanctioned reads and writes,
    one batch() on the destination, format files through add_book's
    seed path, per-author sorts then the book-level author_sort verbatim;
    annotations, data/ extras, custom columns, and the uuid each stay out
    for a recorded reason, and duplicate policy stays with the frontend);
    and the blob passthrough writers set_plugin_data/
    set_conversion_options/set_book_storage (opaque payloads, none of
    them OPF-queued, matching upstream; upstream's in-process recipe
    pickling is deliberately the caller's boundary). The copy primitive
    carries conversion options; plugin data it does not, exactly like
    upstream's copy.
  • Phase 18: get_categories(book_ids=None), the restricted tag
    browser. Nodes over the builtin browse fields plus storage-backed
    custom columns carry {id, name, sort, count, avg_rating, id_set, search_expression}, and searching a node's expression returns that
    node's id set (swept per-node in the tests). Restriction narrows to
    values the restricted books actually hold; counts and entity-view
    average ratings agree with get_tag_browser_counts where they
    overlap. Named in the ship notes: the views' ratings avg_rating
    column is an upstream cross-join artifact (every row identical,
    verified against the live library), rating nodes surface stars per the
    cquarry convention rather than the views' internal 0-10 text, and
    composite columns stay gated by the section 7 GPM boundary.
    User categories, search, and news are upstream GUI synthesizations
    outside the portable subset. Verified against the live library: 12
    categories over ~8k books with per-node roundtrips clean.
  • Housekeeping: the pre-tag slop pass returned zero kill findings and
    three fix-grade notes, all applied (a repeated guarantee clause,
    a recurring triple negation, and a stale __version__ example).
  • Suite: 556 -> 653 tests (pytest and CI's unittest discover agree).

## v1.24.0 (2026-10-02)

Choose a tag to compare

@github-actions github-actions released this 02 Oct 13:55

v1.24.0 (2026-10-02)

The parity program's first release: Phases 14 and 15 land

The 2026-09-29 reopen (roadmap.md) committed five parity phases; this release ships the first two, the completion wave and the maintenance ring, plus the Dependabot bookkeeping. The Cross-Repo Rule consumers affected this round are CalibreQuarry (adopts get_dirtied_formats() in its Phase 20 lane) and Carrel-calibre-web (the residue trio; adoption waived this round per the decision record).

  • Restore-from-trash verbs (cquarry.write): the trash stops being a dead end. copy_format_from_trash/copy_book_from_trash rescue entries out of .caltrash (pure filesystem), move_format_from_trash undeletes one format into its live book, delete_trash_entry removes one entry, and move_book_from_trash rebuilds the row from the entry's sidecar metadata.opf (Calibre's own backup, read with stdlib ElementTree and never generated, keeping the OPF-generation decline intact): core metadata restored, every extension-bearing file re-registered in data, the cover flag from the file, OPF resync and FTS extraction queued, and the directory moved back only after the rows commit. Custom columns, annotations, and plugin data are not restored (the OPF carries none of them).
  • get_dirtied_formats() (read side): the FTS sidecar's extraction queue as sorted (book_id, format) pairs, the formats sibling of get_dirtied_books(). Retires CalibreQuarry's raw sidecar read when its lane adopts it.
  • set_custom_column_metadata (write side): name/editable/display edits on an existing column, upstream backend.py:1407's shape. The headline use: display={"enum_values": [...]} populates an enumeration without Calibre open. Honest no-op on equal values; a change sets update_all_last_mod_dates_on_start like upstream's wrapper; datatype and label changes are refused by omission (type is baked into the storage layout; the label is the #label search token).
  • The Carrel residue trio (read side): get_book_by_uuid (the Calibre-Companion lookup; case-insensitive, None on miss), get_entity_book_ids(kind, name) (the id-set half of get_entities; tags resolve the engine's anchored-subtree rule so a browse node's set covers its children), and get_all_formats() (a cached {book_id: [FMT]} map).
  • facet_counts / facet_counts_for_ids (read side): per-value counts over a restricted result set (the builtin browse fields plus custom columns by #label; ratings surfaced as stars exactly as field() yields them). The _for_ids half is the restriction plumbing Phase 18's restricted tag browser reuses.
  • WritableCalibreDB.maintain(): vacuum, analyze, and integrity_check over the library DB and the attached FTS sidecar in one standalone pass (upstream backend.py vacuum extended with the two statements calibredb users run by hand; the notes DB stays out of scope). Refuses batch() and mid-transaction states; reports the check rows (["ok"] on a healthy file).
  • check_library_disk() (cquarry.integrity): the extra-side disk checks of upstream's check_library in one tree walk: extra titles/authors, malformed paths and formats, extra formats, unknown files, extra covers, failed folders. The missing side is deliberately not re-answered (find_missing_format_files/find_missing_cover_files own it). The format-vs-unknown-file classifier is structural (a token extension minus the image/OPF/junk set), not upstream's curated BOOK_EXTENSIONS; POSIX case sensitivity assumed.
  • FTS queue verbs (cquarry.write): fts_reindex_book, fts_reindex_all, and fts_queue_clear are the queue half of upstream's fts_unindex family as pure sidecar SQL. One boundary named rather than faked: upstream's index-row deletion runs books_text delete triggers that tokenize through Calibre's custom FTS5 tokenizer, which does not exist outside Calibre, so removing indexed text stays Calibre's job (an O-lane calibredb fts_unindex wrap if a consumer asks).
  • Typed preference writes (cquarry.write): set_preference(key, value) over a five-key whitelist (saved_searches, virtual_libraries, user_categories, grouped_search_terms, fts_enabled), each payload validated by key before anything lands and stored as one JSON row in Calibre's shape; saved_search_add/saved_search_delete/saved_search_rename are the calibredb saved_searches parity item (rename resolves case-insensitively to the stored spelling and refuses to overwrite an existing name where upstream silently overwrites).
  • Housekeeping: the Dependabot gate ticked (both major-bump PRs merged 2026-09-30), the import skills swept per the skill-sync rule, and an API.md table row the maintain() insert had split was repaired in the same window.
  • Suite: 500 -> 556 tests (pytest; 550 collected by CI's unittest discover).

## v1.23.2 (2026-09-16)

Choose a tag to compare

@github-actions github-actions released this 16 Sep 21:19

v1.23.2 (2026-09-16)

The version guard runs again; every carrier agrees

The v1.23.1 hotfix shipped with four stale version carriers (src/cquarry/__init__.py, src/cquarry/config.py, spec.md, API.md all still reading 1.23.0), so the published wheel reported cquarry.__version__ == "1.23.0" and every consumer reading the constant saw the wrong release. The guard that should have caught it, tests/test_version_sync.py, was the one file in the suite written as plain pytest-style functions -- and CI runs unittest discover, which silently collected zero tests from it. The file is now a unittest.TestCase (469 -> 471 collected tests), every carrier agrees at 1.23.2, and the discover run that gates each push executes the guard.

Found by the post-blitz verification day's deep pass on the foundation repos.