Skip to content

Releases: fabiodalez-dev/Pinakes

Pinakes v0.7.71-rc.2

Pinakes v0.7.71-rc.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Aug 22:50
Immutable release. Only release title and notes can be modified.

Fixes the LiteSpeed edge cache so the admin toggle actually caches, and adds an on-demand purge control. No schema change.

Performance

  • The edge-cache privacy block shipped in 0.7.70/0.7.71 without CacheLookup on, so LiteSpeed ignored the application's cache headers and never served a page from edge cache — the Settings → Advanced toggle enabled the response headers but stored nothing. The generated, fresh-install and example .htaccess now enable LSCache request lookup, and an upgraded install heals its existing block in place (no manual re-save) so an admin who had LiteSpeed enabled starts caching immediately.
  • LiteSpeedCache::enabled() now additionally requires the lookup directive to be present, so the admin diagnostics and the front-end pending-availability fallbacks only treat caching as live when the server will actually honour it. The Apache-based official Docker image continues to disable LiteSpeed entirely.

Administration

  • Settings → Advanced gains a "Clear edge cache" button that purges every Pinakes-tagged LiteSpeed entry on demand, for when an admin wants an immediate refresh instead of waiting for content-change invalidation. Translated in every bundled locale.

Internal

  • New behavioral coverage verifies that a fresh install enables lookup, that a legacy privacy block missing CacheLookup on self-heals in place exactly once and idempotently, and that the purge control is wired end to end.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.71-rc.1

Pinakes v0.7.71-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Aug 16:51
Immutable release. Only release title and notes can be modified.

First release candidate for catalog denormalization. Existing installs receive an idempotent migration; Redis remains deliberately deferred.

Performance

  • Bounded catalog counts and facet payloads are materialized in MySQL with the same generation token and short safety TTL used by QueryCache. APCu remains the fastest same-host layer, while CLI/file-cache SAPIs that share the installation generation reuse the aggregate instead of repeating six catalog scans. Cross-server coherence remains deferred to the Redis phase.
  • Public catalog rows read a write-maintained principal-author projection instead of executing three correlated libri_autori subqueries per book. Principal/co-author priority, explicit credit order, pseudonym display and author sorting remain deterministic.

Compatibility and consistency

  • Book, author, import and enrichment write paths rebuild the author projection through the existing SearchIndexBuilder maintenance funnel. During a rolling upgrade, catalog queries retain the legacy subquery fallback until all projection columns exist.
  • Materialized aggregates fail open to live SQL if the table, generation counter or MySQL named lock is unavailable. Content and availability invalidation still use ContentCache; no second availability source is introduced.
  • Projection degradation tracking now coordinates readers and writers through a stable sibling lock and fails closed on partial or corrupt state. Profile review-cache invalidation compares against the locked database before-value instead of a potentially stale session.
  • The Apache-based official Docker image permanently disables LiteSpeed/LSCache at runtime and in Settings, even when an imported database or forged request asks to enable it; file/APCu query caching remains available normally.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.69-rc.6

Pinakes v0.7.69-rc.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Aug 01:19
Immutable release. Only release title and notes can be modified.

Sixth release candidate for the issue #387 caching overhaul. No schema change and no new required configuration.

Fixed

  • The circular prerelease-check exception now recognizes only an actively pending or failed Verified Release run. A cancelled, skipped or neutral self-check can no longer satisfy the BLOCKED safeguard; regression tests cover both terminal buckets.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.69-rc.1

Pinakes v0.7.69-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 27 Aug 20:51
Immutable release. Only release title and notes can be modified.

Release candidate for a performance/caching overhaul (issue #387). No schema change, no new required configuration — every install upgrades with identical behaviour on a cold cache. Bundles three reviewed pieces:

Performance

  • Single-backend query cache with O(1) invalidation. QueryCache now uses one backend per request (APCu when available, else file) instead of writing to both on every set, and invalidates the content namespaces (catalog, home, genre-tree, book-detail, reviews) through monotonic generation counters — a bump makes every prior entry unreachable without scanning or deleting. The counters are file-backed and written atomically (temp + rename()), so a failed write can never wipe one and re-expose invalidated data; a scheduled non-blocking GC reclaims orphaned files. Per-request hit/miss instrumentation (QueryCache::stats()) is available for a later diagnostics surface.
  • Hot public datasets are cached, availability stays live. The book-detail static DTO (metadata/authors/publishers/series/related), the reviews block and the bounded catalog listing pages are cached per locale, while copie_disponibili/copie_totali/stato are stripped before storage and re-read live on every request — a stale availability number can never be served. Every write-path that changes a cached column (book edit, cover download, series mutation, reviewer name, author enrichment) invalidates the right namespace.
  • Sessionless anonymous request path + lazy CSRF. Anonymous read-only requests with no auth cookie no longer open a PHP session or embed a per-visitor CSRF token in the HTML (the token is minted on demand via GET /csrf-token), clearing the two blockers to a future shared edge cache. CSRF validation is unchanged — every state-changing request still opens a session and is validated exactly as before; private-mode gating and the login/remember-me flows are untouched. Only the genuinely public /uploads subtrees (covers, author photos, branding) are treated as sessionless; the private ones stay behind the session.

Internal

  • OPcache guidance in php.ini.recommended corrected (JIT explicitly disabled, fast_shutdown removed, sizing driven by opcache_get_status()). Extensive new behavioural coverage for the cache backend, generation invalidation, atomicity, the availability split and the sessionless/CSRF predicate.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.68

Choose a tag to compare

@github-actions github-actions released this 27 Aug 06:01
Immutable release. Only release title and notes can be modified.
0e9db33

Circulation fixes. A request for a book that is currently out no longer fails on approval — it becomes a real waitlist reservation — and copy allocation is made consistent everywhere a copy is chosen. Also restores the "waiting for pickup" cancel action (#381), the terminal pickup email for archived books, and author photos supplied as a URL (#382).

Fixed

  • A request for an already-loaned single-copy book now routes to the waitlist instead of failing on approval (#384). The request used to be recorded as a pending loan, and approving it returned HTTP 500 because no copy could be allocated for the overlapping window. The reservation-vs-loan decision is now unified in one gate: when no copy is assignable through the requested window but physical copies exist, a real prenotazioni waitlist reservation is created; when a copy is free the loan proceeds (auto-approving where enabled). Legacy copy-less titles keep the pending-approval fallback.
  • Copy allocation is consistent across every site that picks a copy (#384). The request gate, the approval step and the FIFO waitlist promotion now share one rule: a copy carrying a preceding commitment — one whose availability would depend on an earlier borrower returning on time — is never bound, and among otherwise-free copies the one with no later scheduled loan is preferred. A future scheduled loan therefore never comes to depend on a new borrower returning early, and an idle sibling is used instead of the copy a later loan already needs.
  • A "waiting for pickup" loan can be cancelled again (#381). The cancel action resolves to annullato — or scaduto when the pickup deadline has already passed — restores the copy to availability, and never interferes with other loans or later reservations on the same or another copy.
  • The terminal pickup email reaches the borrower even for a soft-deleted book. The cancel/expire notification query filtered out soft-deleted books, so a borrower whose book had since been archived silently lost the expired/cancelled email even though the loan was closed; the notice now fires regardless.
  • Author photos supplied as a URL now display (#382). A photo entered as a remote URL is downloaded into uploads/autori on save instead of being stored as a bare link the page could not render.

Internal

  • The reservation-vs-loan decision now lives in a single shared LoanRequestGate that every public request entry point routes through (the book-detail modal, POST /user/loan, and the NCIP RequestItem service), so the #384 contract can never diverge between paths. NCIP RequestItem creates a real FIFO reservation when no copy is assignable instead of a bare pending request; ncip_transactions gains a prenotazione_id audit link (added to existing installs by the plugin schema self-heal), and CancelRequestItem cancels the linked reservation, reorders the queue and runs any promotions. Bundled ncip-server plugin bumped to 1.0.4.
  • The companion Mobile API now preserves that same outcome at the JSON boundary: when the canonical loan path routes an apparently-immediate request to the FIFO waitlist it returns HTTP 201 with data.type=reservation, never a false 500 after committing the reservation. Mobile API 1.4.4 also exposes DELETE /loans/{id} and a cancellable loan hint so native clients can safely cancel pending, scheduled and da_ritirare loans (#381) without relying on the overlapping numeric id spaces of loans and reservations.
  • New behavioural coverage for the reservation-routing gate, the three copy-selection sites (I1 preceding-dependency and I3 future-commitment ordering), the soft-deleted-book pickup notification and the NCIP request/cancel lifecycle, each driving the real controllers against a seeded database and failing on the pre-fix code by design.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.67

Choose a tag to compare

@github-actions github-actions released this 24 Aug 17:45
Immutable release. Only release title and notes can be modified.
66922a7

Fixes a silent upgrade gap discovered on a live install: a foreign key or column that a bundled plugin adds in a release was not applied when an already-active plugin was upgraded through the admin UI, even though the plugin version was bumped.

Fixed

  • Plugin schema self-heal now covers foreign keys and columns, not just tables. An admin-UI upgrade loads a plugin's old class into memory before the new files are swapped in, so PHP keeps running the old onActivate/ensureSchema during the upgrade even as the version is stamped forward — any foreign key or column added in that release never runs at upgrade time. The boot-time self-heal is the only path that applies it, and it previously probed missing tables only. It now also probes declared columns (expectedColumns()) and foreign keys (expectedForeignKeys()) and re-runs onActivate when any declared element is missing. Concrete effect: ncip_transactions' two foreign keys and libri.file_url/libri.audio_url are now restored automatically on the next page load after upgrading. No action is required; already-upgraded installs heal themselves.

Internal

  • ncip-server and digital-library declare their foreign keys / columns from a single source shared with the code that creates them, so the self-heal probe and the DDL cannot drift apart. New behavioural coverage (tests/plugin-schema-selfheal-fkcol.unit.php) reproduces the missing-FK / missing-column state against the real PluginManager and asserts it self-heals; it fails on the pre-fix code by design and is wired into the mandatory schema gate.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.66

Choose a tag to compare

@github-actions github-actions released this 24 Aug 13:58
Immutable release. Only release title and notes can be modified.
2811a76

A full audit of all 20 bundled plugins — security, correctness and schema fixes — plus four previously-orphaned plugin features wired into the UI.

Security

  • Centralised SSRF guard for caller-configurable endpoints: HttpClient gains
    an ssrf_guard option that resolves the target host, rejects hosts resolving to
    private/reserved address space, pins the connection to the vetted public IP, and
    rejects cross-host/port/scheme redirects. Enabled for the book-club AI endpoint
    and the z39 SRU client, both of which also moved off raw curl onto the guarded
    client.
  • book-club AI settings are admin-only: the global API key and outbound
    endpoint were reachable by staff (AdminAuthMiddleware admits both roles); an
    inline admin re-check now gates both read and save.
  • resource-sync Basic Auth is rate-limited; dewey-editor destructive
    endpoints require admin inline; digital-library file-preview links get
    rel="noopener".

Features

  • api-book-scraper settings page is wired (hasSettingsPage/
    getSettingsViewPath) — previously a 404.
  • z39-server UNIMARC import completes the advertised round-trip: a pasted or
    uploaded UNIMARC record pre-fills the book form (like the SBN import), never a
    direct insert, so validation and soft-delete rules are preserved.
  • viaf-authority surfaces a VIAF/ISNI panel on the author edit page via the
    existing author.form.fields hook.
  • frbr-lrm wires Book↔Work linking and full Expression CRUD into the admin UI
    via book.form.fields; the manifest drops the unbuilt dedup/relator claims.

Fixed

  • api-book-scraper no longer wipes its stored API key on a settings save that
    leaves the key field blank (blank means "keep the existing key").
  • archives soft-delete frees the UNIQUE reference_code/ark_identifier for
    reuse by appending a per-id token (truncated to fit VARCHAR(64)), keeping the
    original code as a readable prefix.
  • dewey-editor keys its data file off the full locale (en_USen_GB) with
    a legacy fallback; readers and writers share one resolver.
  • Plugin hook registration (api-book-scraper) is now transactional and rethrows on
    failure; removed dead activate() code in discogs/open-library; aligned
    plugin.json versions and dropped stale max_app_version fields.

Upgrade

  • The five plugins with schema or hook changes (ncip-server, digital-library,
    frbr-lrm, viaf-authority, z39-server) get a plugin.json version bump so the
    Updater re-runs their ensureSchema()/hook registration on existing installs —
    the ncip ncip_transactions FOREIGN KEYs, digital-library's file_url/
    audio_url columns and the new frbr/viaf hooks are applied on upgrade, not only
    on fresh install. No core SQL migration.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.65

Choose a tag to compare

@github-actions github-actions released this 22 Aug 13:58
Immutable release. Only release title and notes can be modified.
7161371

Add-to-calendar links in loan emails, the #366 legacy ready-pickup backfill, and a public-facing SEO pass.

Features

  • Add-to-calendar links in loan emails: the loan-approved and pickup-ready
    emails carry a calendar block with a Google Calendar link and a tokenized
    per-loan .ics download, so borrowers can save the due date in any calendar
    app (multi-calendar, no account linking). Rendered by NotificationService
    via LoanCalendarLinks; upgraded installs get the {{sezione_calendario}}
    placeholder appended to already-seeded templates by an idempotent migration,
    fresh installs from the template defaults. Strings translated in all five locales.

Fixed

  • #366 residual repair: legacy da_ritirare rows with a NULL
    pickup_deadline could stay "ready for pickup" forever — the expiry sweep
    skips NULL deadlines and the 0.7.63 repair intentionally preserves a pickup
    whose copy is free. PickupDeadlineBackfill now runs on every upgrade
    (admin updater and Docker both drive Updater::runMigrations()), after the
    canonical triggers are re-applied, assigning today + pickup_expiry_days
    capped at the loan's data_scadenza; rows whose window already closed get a
    past deadline and are culled by the next sweep through the normal expiry
    path. Covered by tests/migration-0.7.65-rc.1.unit.php.

SEO

  • Canonical URLs no longer inherit tracking query strings (layout fallback and
    book detail), so ?utm_* variants stop becoming indexable duplicates.
  • hreflang alternates and locale-prefixed sitemap URLs (/en/…) pointed to
    non-routable 404s: hreflang is no longer emitted while the language is
    session-based, and the sitemap lists only default-locale URLs.
  • The sitemap drops the auth pages (blocked by robots.txt anyway), skips
    authors/publishers with no visible books, and logs when the explicit
    book/author caps truncate; robots.txt now disallows every active locale's
    login/register variant.
  • Author, publisher and genre archives share one localized SEO block:
    canonical on the name-based URL (the id route collapses into it),
    translated titles/descriptions, CollectionPage (+Person) and BreadcrumbList
    JSON-LD. Book pages brand their title and Library schema with the
    configured app.name.
  • Internal search results and the standalone auth pages carry
    noindex,follow; empty og:image/twitter:image tags are omitted.
  • Catalog pagination is server-rendered with real ?page=N links (crawlable
    without JavaScript); grid covers load lazily and the book cover — the LCP —
    gets fetchpriority="high". Book meta descriptions are cut multibyte-safe.
  • Public events emit Event + BreadcrumbList JSON-LD.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.64

Choose a tag to compare

@github-actions github-actions released this 22 Aug 09:52
Immutable release. Only release title and notes can be modified.
76afab7

Optional multiple physical copies of the same title per borrower (#238).

Features

  • Multiple copies per borrower (opt-in, Settings → Loans → "Più copie dello
    stesso titolo", off by default): staff can lend more than one distinct
    physical copy of the same title to the same borrower. Every loan stays bound
    to its own copy, so no two open loans for a borrower can share a copy.
    Pending requests, reservations and legacy copyless rows keep the historical
    borrower/title uniqueness, and per-copy overlap guards, eligibility, capacity
    and the max-active-loans limit are all unchanged (each copy counts as one
    active loan). The desk "Salva e registra un'altra copia" flow keeps the
    selected title and focuses the next copy scan for fast batch check-outs.
    Existing installations are unaffected without a migration — the setting
    resolves to off until it is enabled, and fresh installs seed it off in all
    five shipped locales.

Fixed

  • Circulation can no longer hand out a copy that is physically out (#366
    residual): creation, approval, rescheduling, renewal, reassignment and the DB
    triggers all treat an in_corso loan whose due date has passed but that the
    maintenance sweep has not yet flipped to in_ritardo as an open-ended
    commitment (same rule as CapacityService). The pre-assigned copy is
    validated against the loan's book, and a request whose whole window is already
    past is rejected instead of becoming an unpickable da_ritirare.
    confirmPickup re-checks the loan rows holding the copy (not just
    copie.stato) before issuing. Approval and the date-aware allocator still
    accept a copy that is physically out as long as the requested window does not
    overlap its open loan — the per-copy overlap check is the authority — so a
    future-dated request is not needlessly blocked when the only copy is on loan.
  • Backdating a loan's due date reports a clear error: actively moving an
    active loan's due date into the past used to reach the per-copy overlap
    trigger and surface an opaque loan_update_failed; the edit flow now rejects
    it up front with the same expired_window message the create/approve flows
    use, while still allowing edits to an already-overdue loan whose due date is
    left unchanged.
  • Queue promotion re-checks borrower eligibility: a user suspended (or
    with an expired card) while waiting is skipped — their reservation stays
    active in the queue — instead of being promoted to a pending loan that
    approval then refuses, burning their position and pinning the copy. The scan
    is not capped at the first 25 rows, so an eligible reader farther down the
    FIFO can still receive a free copy.
  • Reservations on books without copy rows are refused up front: the queue
    can only convert physical copie rows, so a legacy book with none produced
    reservations that silently never converted until they expired.
  • Reassigning a promoted loan keeps queue semantics (multiplicity ON): a
    row with origine='prenotazione' still in prenotato/da_ritirare is
    treated as a queue commitment when reassigned to another borrower, so a user
    can never end up with a physical loan plus a promoted queue position for the
    same title.
  • The desk create form is double-submit safe: submit buttons disable on
    first submit and every rendered form carries a one-time server token. A
    replayed POST therefore cannot register a second real loan on another copy,
    including retries caused by latency or double-clicks.
  • A freed copy now serves every compatible hold: reassignOnReturn keeps
    assigning the returned copy until no blocked FIFO candidate can take it
    (holds with disjoint windows previously waited for the next maintenance
    sweep), and holds whose window is entirely past are no longer eligible.
  • Pickup-ready emails are claim-and-retry: a new
    prestiti.pickup_notification_sent flag (seeded in the schema and added by
    an idempotent upgrade migration) makes the "ready for pickup" email
    idempotent. Token-owned claims recover after a worker crash, failures are
    retried fairly by the hourly notification cron, and existing rows are
    initialized as already handled without firing legacy circulation triggers.
    Every future transition to ready resets the flag; reassigning a ready loan
    resets the recipient-specific claim as well.
  • Borrowers without an email no longer wedge the notification crons:
    expiry warnings and overdue notices for them keep their claim (no more
    endless SMTP retry churn) and still produce the in-app/admin notifications,
    which previously never fired for email-less borrowers.
  • Cron process locks no longer unlink after unlock (the race
    scripts/maintenance.php already documented), both cron entrypoints refuse
    non-CLI execution, and runAll() refreshes the cross-session cooldown
    marker so an admin login right after the cron no longer re-runs the whole
    maintenance synchronously.
  • NCIP: a recognized active FromAgencyId is attributed to transaction
    logs without silently turning the historically informational partner table
    into a new authorization gate. CheckInItem and RenewItem reject a
    malformed UserId, use a valid optional UserId to select the correct loan,
    and refuse an ambiguous title-only mutation when several NCIP loans are open;
    check-out/check-in/renew are logged to ncip_transactions; renewals claim
    the window from the day after the current due date (#336 parity with web).
  • Return flow: a deadlock/lock-timeout now reports a dedicated
    "another operation was updating this book, retry" error instead of the
    generic failure; reservation availability emails format dates in the
    recipient's language like the rest of the #360 pipeline; cancelling a
    promoted pending loan releases and reassigns its copy immediately.
  • Admin reservation edits: an out-of-whitelist status is rejected instead
    of silently coerced to attiva, and a completed (promoted) reservation
    cannot be flipped while its converted loan is still open.
  • Localized gender on the admin user-details page (#371): the stored
    sesso enum (M/F/Altro) now renders through the same translated labels
    as the edit form instead of the raw code, with a safe fallback for unexpected
    values.
  • Notifications dropdown no longer clips on phones: the panel was anchored
    to the right-hand bell button, so a near-full-width dropdown overflowed the
    right screen edge and cut off "Segna tutte come lette" and the notification
    bodies. Below the md breakpoint it is now pinned to the viewport and
    centred; the desktop panel is unchanged.

Changed

  • Release artifacts now have one canonical producer: the tag-triggered
    Verified Release workflow builds twice, verifies the package, uploads a
    draft, checks every server-side digest and only then publishes it. The local
    release script performs preflight, pushes the annotated tag and monitors that
    workflow instead of racing it with a second upload path.
  • reassignOnNewCopy now walks the whole blocked-hold FIFO and assigns the
    first compatible candidate (skipping an incompatible head) instead of
    stopping at the first blocked hold; a same-borrower conflict on the target
    copy blocks regardless of dates. Observable also with the multiplicity
    setting off: a younger reservation can receive a returned copy while the
    head stays blocked, without losing its priority for future copies.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.

Pinakes v0.7.63

Choose a tag to compare

@github-actions github-actions released this 20 Aug 20:22
5b35f2e

Fixed

  • #366 upgrade recovery: migrate_0.7.63-rc.1.sql immediately demotes
    impossible legacy da_ritirare rows back to prenotato without cancelling
    the scheduled loan, clears their stale pickup deadline and releases the bad
    copy assignment; maintenance repeats the same repair as a runtime safety net.
    The loan-integrity trigger permits
    corrective edits to an already-existing overdue overlap (including moving
    the start date to tomorrow) while still rejecting newly introduced overlaps.
    Scheduled loans without a pinned copy are assigned a real free copy before
    being announced ready, so confirmPickup() cannot inherit a copy-less row.

The ZIP was built twice with an identical SHA-256, audited as the
exact distributable, accompanied by an SPDX SBOM, and signed with
a GitHub artifact provenance attestation.