Skip to content

release: beta.135 → beta.147 (includes critical data-safety fix #209)#212

Open
deucebucket wants to merge 20 commits intomainfrom
develop
Open

release: beta.135 → beta.147 (includes critical data-safety fix #209)#212
deucebucket wants to merge 20 commits intomainfrom
develop

Conversation

@deucebucket
Copy link
Copy Markdown
Owner

Release: 0.9.0-beta.147

Promotes 18 commits from `develop` to `main`. Ships as `:latest` Docker tag on merge.

⚠️ Critical fix included

#209 — Hard link failure silently copies and deletes originals. Reported by @kyleviloria on beta.134 (the current `:latest`). When `Use hard links` was enabled and the watch folder / library lived on different filesystems, `os.link()` raised `EXDEV` and LM silently fell back to `shutil.copy2()` + deleting each original file — destroying torrent seeds, doubling disk use, and silently violating the user's explicit preference. Fixed in #210 with an `st_dev` pre-check that fails fast with a clear message and leaves source files untouched. Live-tested against the maintainer's 1193-book library across 3 scenarios (cross-fs, same-fs, no-hardlinks) — all passed.

Features shipped

Fixes shipped

Full commit list

```
7864dcf Fix #209: Hard link failure no longer copies+deletes originals (#210)
d47e916 Add ecosystem cross-repo sync workflow
1bd11bf Feat #110: Add folder triage UI - dashboard stats, library badges, settings toggle (#206)
a06840c docs: Plugin system documentation and discoverability (#203) (#204)
b6f6a9d fix: Resolve SAFETY BLOCK for files in library root (#201) (#202)
2986cdb feat: Professional UI overhaul - design system, CSS/JS extraction, settings reorg (#198) (#199)
3c973c1 feat: Add drop-in Python plugin system (#188) (#197)
c246f37 feat: Add Plugin Health Dashboard with auto-disable circuit breaker (#189) (#196)
3a63024 feat: Add Custom Layer Builder wizard UI (#186) (#195)
19a4d1d feat: Expand hooks with event system and body templates (#187) (#194)
6398849 feat: Template HTTP layers - no-code custom API sources (#185) (#191)
d0e07b3 feat: Add standalone layer execution API and UI (#66) (#184)
6182293 feat: Add pipeline configuration UI in settings (#66) (#183)
a09343b Addresses #66: Add PipelineOrchestrator with layer adapters and feature flag (#182)
04d625e Addresses #66: Add pipeline_order to default config (#181)
8072c18 Addresses #66: Add LayerRegistry and LayerInfo for modular pipeline (#180)
d51dac5 feat: Integrate file validation into scan pipeline (#110) (#179)
3699f9c Fix #169: Expand clean_search_title regex to handle special chars in author names (#178)
```

Test plan

Merge strategy

Use `Create a merge commit` (not squash). Release PRs to main preserve full history per project convention — individual fix/feature commits stay attributable.

deucebucket and others added 18 commits March 10, 2026 16:36
…author names (#178)

The "by Author" stripping regex now handles periods (F. Scott Fitzgerald),
apostrophes (O'Brien), hyphens (Mary-Jane), and trailing parenthetical
content like (Audiobook)(Nonfiction).

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
* Fix #110: Integrate file validation into scan pipeline

Wire up the existing file_validation.py module into deep_scan_library()
so invalid audio files (corrupt, truncated, too short) are caught before
they enter the processing queue.

Changes:
- database.py: Add validation_status and validation_reason columns to books table
- config.py: Add enable_file_validation, min_audio_duration_seconds, min_audio_file_size_mb settings
- file_validation.py: Accept configurable min_duration and min_size overrides
- app.py: Import and call validate_audio_file at all 4 queue insertion points
  (loose files, flat books, series books, regular books). Invalid files get
  status='validation_failed' and are excluded from the queue. ffprobe
  unavailability is non-blocking (validation skipped gracefully).
- app.py: Add validation_failed counts to dashboard, /api/stats, and /api/library
- database.py: Add validation_failed to should_requeue_book skip list
- dashboard.html: Show validation failure alert when count > 0
- settings.html: Add File Validation toggle with configurable thresholds

* chore: Bump version to beta.135 for file validation integration (#110)

---------

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
…180)

Introduces the foundational registry that describes all processing layers,
their ordering, enable/disable config keys, and circuit breaker dependencies.

- LayerInfo: frozen dataclass with layer metadata (id, name, description,
  config key, default order, circuit breaker info)
- LayerRegistry: lookup by id, ordered listing, enable/disable checks,
  custom pipeline_order support, order validation
- default_registry: pre-populated with all 5 current layers matching the
  execution order in worker.process_all_queue

Purely additive -- nothing uses the registry yet. No behavior changes.

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
…re flag (#182)

Introduces a configurable PipelineOrchestrator that can replace the
hardcoded process_all_queue sequence. Uses adapter pattern to wrap
existing battle-tested layer functions with a uniform interface.

- Add adapters.py: AudioIdAdapter, ApiLookupAdapter, AudioCreditsAdapter,
  AiVerifyAdapter, SlRequeueAdapter wrapping existing layer functions
- Add orchestrator.py: PipelineOrchestrator handling layer ordering,
  circuit breaker waits, batch loops, rate limiting, stuck item recovery
- Add use_modular_pipeline feature flag (default: False) for safe rollout
- Update pipeline __init__.py with new exports

When use_modular_pipeline is False (default), existing behavior is
completely unchanged. When True, the orchestrator runs layers in the
order defined by pipeline_order config using the registry and adapters.

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
* Addresses #66: Add pipeline configuration UI in settings

* fix: Wire up pipeline layer toggles and add version bump/changelog

- Add name attribute to pipeline layer toggle checkboxes so they submit with form
- Bump version to beta.136
- Add CHANGELOG entry for pipeline configuration UI

* fix: Use existing json import instead of inline alias

---------

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
* Addresses #66: Add standalone layer execution API and UI

Adds POST /api/pipeline/run-layer/<layer_id> endpoint that runs a single
pipeline layer on demand (synchronous). Adds a play button next to each
layer in the settings pipeline section with spinner feedback and result
badges showing processed/resolved counts.

* chore: Bump version to beta.137 for standalone layer execution (#66)

---------

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
Adds CustomApiLayer backend for user-defined HTTP API processing layers. Includes JSONPath extractor, URL template substitution, auth support (bearer/api_key/basic), per-layer circuit breakers, and registration into LayerRegistry. Foundation for #66 Phase 5.

Closes #185
Expands hook system from fixed-only to 8 event types with run_on filtering, body_template for custom webhook payloads, standardized event envelope, and emit_event() helper. Fully backward compatible.

Closes #187
Adds Plugins tab to Settings with 4-step wizard for no-code custom API layers. Includes plugins Blueprint with CRUD + test endpoints, wizard modal (info, API config, response mapping, test & save), and full integration with existing patterns.

Closes #186
…189) (#196)

Adds plugin health dashboard with metrics tracking, auto-disable circuit breaker (5 consecutive failures), health API endpoints, and UI with status cards and error logs.

Closes #189
Adds Python drop-in plugin system. Users drop plugin directories into /data/plugins/ with manifest.json and a BasePlugin subclass. Features: auto-discovery via importlib, PluginAdapter for pipeline compatibility, timeout enforcement, deep copy isolation, exception handling, metrics recording with auto-disable, and an example plugin template.

Closes #188
…ttings reorg (#198) (#199)

Professional UI overhaul: extracted 730 lines CSS to static/css/style.css with design tokens, consolidated duplicate JS to static/js/common.js, reorganized settings from 7 tabs to 4 (Library, Engine, Pipeline, Integrations), fixed color palette, added mobile responsive breakpoints, replaced all hardcoded colors with CSS variables.

Closes #198
* Fix #201: Resolve path normalization mismatch causing SAFETY BLOCK for library root files

Windows mapped drives (e.g. R:\) resolve to UNC paths (\\server\share) but
library path comparisons in the processing layers used raw config paths
without .resolve(), causing the match to fail. The fallback then used
parent.parent which went above the library root for loose files.

Fixed all 4 locations to resolve both sides before comparison, and added
smart fallback that checks if the path is a file before deciding depth.

* Bump APP_VERSION to beta.144, update README badge and CHANGELOG

* Fix same path resolution bug in layer_content.py

Missed 5th location with identical unresolved relative_to() and
parent.parent fallback — caught by vibe-check review.

---------

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
* Docs #203: Plugin system documentation and discoverability

Add in-app docs for Python drop-in plugins, secrets management, and
real-world API examples (Google Books, Open Library) to the Plugins
settings tab. Ship example-logger plugin to examples/plugins/ with
comprehensive README.

* Fix extra brace in Google Books URL template example

---------

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
…ttings toggle (#206)

Backend folder triage (clean/messy/garbage classification) was already
implemented but invisible in the UI. Now surfaces triage data:
- Dashboard info banner showing messy/garbage folder counts
- Library view badges on affected books (Messy/Garbage indicators)
- Settings toggle to enable/disable folder triage
- Triage data in "all" library API responses

Split push corrections to #205 (blocked on Skaldleita backend).

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
When an issue gets the 'ecosystem' label and references another
deucebucket repo (e.g. deucebucket/skaldleita#127), automatically
comments on the partner issue with a cross-reference link.
Both repos have the same workflow and share an ECOSYSTEM_PAT secret.
When 'Use hard links' was enabled and the watch folder and library were on
different filesystems, os.link() raised EXDEV and the code silently fell back
to shutil.copy2() then deleted the originals. That broke torrent seeding,
doubled disk use, and silently violated the user's explicit preference.

- Add filesystem-compatibility pre-check in move_to_output_folder when
  use_hard_links is True. Returns a clear, actionable error if source and
  library are on different st_dev, without touching any source files.
- Remove the EXDEV copy+delete fallback from both the single-file and
  directory-loop branches. Remaining OSErrors (permission, ENOSPC, etc.)
  propagate to the outer handler with source intact; the watch worker records
  the failure as watch_folder_error visible in the UI.
- Bump APP_VERSION to 0.9.0-beta.147 and update README badge + CHANGELOG.

Reported by @kyleviloria.

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
…tice (#213)

Problem 1: watch_folder_processed was an in-memory set() that got wiped on
every restart. Any file that couldn't be processed (unknown author, ambiguous
match, move failure, mtime churn) got re-submitted every scan cycle after a
restart, forever. One LM instance generated ~48% of all Skaldleita /match
traffic for days on the same filename.

Problem 2: Skaldleita PR #129 added a server_notice field to /match
responses. When the server detects a retry loop it now sends
{severity, code, message, action: abort_task, upgrade_url}. LM ignored it.

Fixes:

- New watch_folder_processed SQLite table (path PK, processed_at, outcome,
  error_message). outcome in {moved, move_failed, aborted_by_server}.
- watch_folder_is_processed() / watch_folder_mark_processed() helpers in
  library_manager/database.py. process_watch_folder swapped from set ops
  to these helpers. Restart no longer resets dedup.
- bookdb.py logs every server_notice (with upgrade_url). On
  action=abort_task it stashes the notice in a threading.local() slot so
  scope is per-thread (watch worker, API endpoint, pipeline layer don't
  cross-contaminate).
- process_watch_folder reads the abort slot after each identify attempt;
  if set, marks the item as aborted_by_server and skips the pipeline.

Bumps APP_VERSION to 0.9.0-beta.148.

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
Copy link
Copy Markdown

@bucket-agent bucket-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Vibe Check Review

Context

Release PR aggregating beta.135 → beta.148 from develop into main. The headline is a critical data-safety fix (#209: stop copy+delete when hardlinking across filesystems) plus a watch-folder retry-loop fix (#208). Most other changes (modular pipeline, plugin system, custom layer builder, UI overhaul) have already been individually reviewed and merged to develop.

Codebase Patterns I Verified

  • library_manager/database.py:153 uses ALTER TABLE ... ADD COLUMN inside try/except: pass for idempotent schema migrations — the new validation_status/validation_reason columns match this pattern.
  • move_to_output_folder at app.py:6500 uses (success, new_path, error) tuple return — new st_dev mismatch error path conforms.
  • _abort_state = threading.local() in bookdb.py:40 — consumer at app.py:6836-6842 clears after every identify attempt, so no leak across watch items.
  • use_modular_pipeline flag defaults False in config.py:121, gated in worker.py:168 — zero behavior change on merge.
  • CHANGELOG.md (Keep-a-Changelog) updated through beta.148; README version badge bumped 134→148.

✅ Good

  • Critical fix is correct: app.py:6527-6538 pre-checks st_dev match before any file operation; the dangerous used_copy_fallback / files_to_delete codepath is fully removed — sources are now guaranteed untouched on cross-filesystem hardlink failure.
  • Empty-folder cleanup guard is right: app.py:6650 now only runs when not use_hard_links — no more accidental source removal when originals were meant to stay.
  • Error message is actionable: tells the user exactly how to fix it ("Move your library to the same volume... or disable 'Use hard links' in Settings"), confirms source files were not modified.
  • #208 retry-loop fix is thorough: SQLite-backed dedup (watch_folder_processed) survives restarts, and the server_notice / abort_task integration gives Skaldleita a clean kill switch without layering more timeout heuristics in LM.

🚨 Issues Found

No issues found.

📋 Scope Verification

Issue Problem Addressed? Notes
#209 Hardlink failure silently copies+deletes originals app.py:6524-6538 pre-check + fallback removed at 6624-6627, 6644-6645
#208 (implicit via beta.148) In-memory watch dedup wiped on restart; Skaldleita retry-loop hammer database.py:182 table + helpers; bookdb.py:40 notice handler; app.py:6461/6836/6841/6896/6931 integration
#66, #110, #169, #178#199, #201#206 Prior develop merges All already reviewed and merged to develop; this PR only aggregates them for release

Scope Status: SCOPE_OK

📝 Documentation Check

  • CHANGELOG.md: ✅ Updated (beta.147 + beta.148 entries with issue refs and fix details)
  • README.md: ✅ Updated (version badge 134→148, "Recent Changes" section extended through beta.148)

🎯 Verdict

APPROVE — The critical data-safety fix is implemented correctly (pre-check + full removal of the destructive fallback), the #208 persistence fix is sound, and every other change in the bundle has already been individually reviewed on its way into develop. Ship it.

… column (#214)

Three INSERT statements in process_watch_folder targeted a column 'added_at'
on the books table. The real column is 'created_at' — 'added_at' only exists
on the queue table. Every watch-folder INSERT raised sqlite3.OperationalError
which two separate except blocks caught with logger.debug(), hiding the error
at default log levels.

Effects:
- Successful watch-folder moves never produced a 'pending' or 'needs_attention'
  row in the books table.
- Failed watch-folder moves never produced a 'watch_folder_error' row — the
  UI had no surface for the failure; only the log showed it.

Fixes:
- Renamed 'added_at' to 'created_at' in the three books-table INSERTs at
  app.py:6906, 6914, 6944.
- Raised both swallow-except blocks from logger.debug to logger.warning with
  exc_info=True so future failures don't rot silently.

Surfaced during live testing of the #209 fix. Bug has existed since the
watch-folder feature was introduced.

Bumps APP_VERSION to 0.9.0-beta.149.

Co-authored-by: deucebucket <deucebucket@users.noreply.github.com>
Copy link
Copy Markdown

@bucket-agent bucket-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Vibe Check Review

Context

Release PR rolling up beta.135 → beta.149 from develop → main. Includes the modular pipeline architecture (#66 family), plugin system + health dashboard, UI overhaul, folder triage UI, and three data-safety bug fixes (#209 hardlink EXDEV, #208 watch-folder retry storm, #211 phantom added_at column).

Codebase Patterns I Verified

  • Hardlink fix (#209) is intact: app.py:6524-6538 has the st_dev pre-check; grepping confirmed no shutil.copy2 remains as an EXDEV fallback in move_to_output_folder — the only remaining shutil.copy2 in the file is at line 12077 (unrelated backup code).
  • CHANGELOG ordering: new entries added at top in Keep-a-Changelog style matching existing format.
  • Sub-PRs were individually reviewed and approved into develop (commits 7864dcf, 6cd29e3, 24f0888, etc. visible on develop); this PR is the merge cut, not net-new code.

✅ Good

  • Critical data-safety fix (#209) verified end-to-end: pre-check present, fallback removed, source files untouched on EXDEV.
  • Two bonus data-safety fixes landed beyond what the title advertises (#208, #211), both making previously-silent failures loud.
  • CHANGELOG + README both updated; release notes describe behavior-changing fixes accurately.

🚨 Issues Found

No issues found.

📋 Scope Verification

Release rollup — all linked issues were addressed in their individual sub-PRs prior to this merge. Spot-checked #209 (hardlink) against current app.py and confirmed the fix shipped.

Scope Status: SCOPE_OK

Minor note (non-blocking): PR title says "beta.135 → beta.147" but the actual diff includes beta.148 and beta.149 (commits 6cd29e3 and 24f0888 landed on develop after the PR was opened). Consider updating the title to "beta.135 → beta.149" before merge for accurate release tagging. Not a blocker — content is all additive data-safety work.

📝 Documentation Check

  • CHANGELOG.md: ✅ Updated (new entries for 147/148/149)
  • README.md: ✅ Updated

🎯 Verdict

APPROVE

Ship it. All sub-PRs were already reviewed; the critical data-safety fix is verified present in the merged state; CHANGELOG/README are current. Only suggestion is cosmetic: update the PR title to reflect beta.149 as the final version before cutting the tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant