feat(issues): the honest processing-issues ledger — row-less import failures, self-healing resolution, family lens (faceted plan Phase 11)
processing_issues only recorded cross-library move rejections; files that failed to import vanished into an error counter and stdout. The ledger now records every failure class and heals itself: Schema (in place, pre-release boundary): - media_item_id NULLABLE + file_path TEXT (deliberately unbounded — an over-long path is itself a failure mode; a VARCHAR(500) ledger column died on the exact 22001 it existed to report — caught by the new test). Row-less failures key on (library, file_path); item-backed on (item, type). Widened dedup = COALESCE-expression unique index; the old table UNIQUE stays (NULLs pairwise-distinct for row-less). - Conflict-target upsert: repeat failures UPDATE the same row and REOPEN resolved ones — never duplicates. Wirings: - CreateMediaItem failure (single-file + multifile createAudioBookItem) → row-less import_failed (error). - Extraction hard-fail with fallback import → item-backed metadata_extraction_failed (warning). - Change-path re-extraction failure (the ev/sidecar guard) → item-backed metadata_extraction_failed; LogProcessingIssue keeps its signature, a row-less helper joins it. Auto-resolution: - Successful import → ResolveOpenProcessingIssuesForPath (library, file_path). - Successful (re)extraction → ResolveOpenProcessingIssuesForItem (single-file change path, multifile editorial apply). - Unchanged-skip scans do NOT resolve: extraction never re-ran, so the degraded metadata is still degraded — the page heals exactly when the book does. Family lens on ALL issues surfaces (list, stats, resolve, delete, SSR page): explicit out-of-set library id → 403, never silent. This tightens one pre-lens behavior — a nonexistent library id returned 200-empty, now 403 (pinned, changelog entry in docs/contract_pins.md). List is LEFT-JOINed so row-less rows appear (media_item_id "", title falls back to the path). Tests: full lifecycle state machine through real scans (fail → appears, repeat → no dups, success-at-path → resolved, LEFT-JOIN list shape); family lens 403s with a fresh non-shared admin; all 7 pre-existing issue suites green (two expectations widened to the lens, changelogged); adjacent suites (CrossLibraryMove, Audiobook, Calibre, ExtractorVersion, SchemaIdempotency) green; unit suites green.
This commit is contained in:
1 parent
19b9c7c9b8
commit
9b5defcb31
50 files changed
+1244
-681
No files matched your search
@@ -4208,9 +4208,14 @@ SELECT * FROM media_bookmarks WHERE id = $1;
|
||||
-- ============================================================================
|
||||
|
||||
-- name: CreateProcessingIssue :one
|
||||
INSERT INTO processing_issues (media_item_id, library_id, issue_type, issue_description, severity)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (media_item_id, issue_type)
|
||||
-- Widened upsert (faceted plan Phase 11): media_item_id and file_path
|
||||
-- are nullable — item-backed rows key on (item, library, type, ''),
|
||||
-- row-less import failures on (zero-uuid, library, type, path). The
|
||||
-- conflict target matches idx_processing_issues_dedup's expressions.
|
||||
-- A repeat failure REOPENS a resolved row (resolved=false).
|
||||
INSERT INTO processing_issues (media_item_id, library_id, issue_type, issue_description, severity, file_path)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (COALESCE(media_item_id, '00000000-0000-0000-0000-000000000000'::uuid), library_id, issue_type, COALESCE(file_path, ''))
|
||||
DO UPDATE SET issue_description = EXCLUDED.issue_description,
|
||||
severity = EXCLUDED.severity,
|
||||
resolved = false,
|
||||
@@ -4218,6 +4223,10 @@ DO UPDATE SET issue_description = EXCLUDED.issue_description,
|
||||
RETURNING *;
|
||||
|
||||
-- name: ListProcessingIssuesByLibrary :many
|
||||
-- LEFT JOIN (Phase 11): row-less import failures have NO media_items
|
||||
-- row — an inner join would silently drop the ledger's newest kind.
|
||||
-- Item-side projections COALESCE to keep them non-nullable strings;
|
||||
-- the display file path falls back to the issue's own for row-less rows.
|
||||
SELECT
|
||||
pi.id,
|
||||
pi.media_item_id,
|
||||
@@ -4227,12 +4236,12 @@ SELECT
|
||||
pi.resolved,
|
||||
pi.resolved_at,
|
||||
pi.created_at,
|
||||
mi.title,
|
||||
mi.file_path,
|
||||
mi.format_group,
|
||||
COALESCE(mi.title, '') AS title,
|
||||
COALESCE(mi.file_path, pi.file_path, '') AS file_path,
|
||||
COALESCE(mi.format_group, '') AS format_group,
|
||||
lt.name as library_type_name
|
||||
FROM processing_issues pi
|
||||
JOIN media_items mi ON pi.media_item_id = mi.id
|
||||
LEFT JOIN media_items mi ON pi.media_item_id = mi.id
|
||||
JOIN libraries l ON pi.library_id = l.id
|
||||
JOIN library_types lt ON l.library_type_id = lt.id
|
||||
WHERE pi.library_id = $1
|
||||
@@ -4245,6 +4254,30 @@ ORDER BY
|
||||
END,
|
||||
pi.created_at DESC;
|
||||
|
||||
-- name: ResolveOpenProcessingIssuesForItem :execrows
|
||||
-- Ledger self-heal (faceted plan Phase 11): a scan that processes the
|
||||
-- item successfully closes its open issues (extraction failures heal).
|
||||
UPDATE processing_issues
|
||||
SET resolved = true,
|
||||
resolved_at = NOW()
|
||||
WHERE media_item_id = $1
|
||||
AND resolved = false;
|
||||
|
||||
-- name: ResolveOpenProcessingIssuesForPath :execrows
|
||||
-- Row-less issues resolve by (library, file_path) when the file finally
|
||||
-- imports — the page heals itself without admin action.
|
||||
UPDATE processing_issues
|
||||
SET resolved = true,
|
||||
resolved_at = NOW()
|
||||
WHERE media_item_id IS NULL
|
||||
AND library_id = $1
|
||||
AND file_path = $2
|
||||
AND resolved = false;
|
||||
|
||||
-- name: GetProcessingIssue :one
|
||||
-- Single-issue read for the resolve/delete endpoints' family-lens gate.
|
||||
SELECT * FROM processing_issues WHERE id = $1;
|
||||
|
||||
-- name: GetProcessingIssueStats :one
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE severity = 'error' AND resolved = false) as error_count,
|
||||
|
||||
Reference in new issue
Block a user