fix(review-minors): batch of 8 review-minor follow-ups (bookshelf-ifwpa) #1397
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-ifwpa"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Umbrella PR folding 8 small review-minor follow-up beads into one reviewable diff, per bookshelf-ifwpa. Each sub-bead was investigated against current origin/main first; some findings were already moot (code refactored since the original review) and are noted below.
Addressed
ListOnDeck's next-unreadNOT EXISTSanti-join gets ab2.id < b.idtiebreaker so two books sharing aseries_numbercan't both surface as "next" (data-quality footgun). Anon-user test sentinel switched from anint64zero-value (BeZeropasses even if stub is called with uid=0) to a falsifiableboolflag.buildEnrichDeps(194 lines) split intobuildEnrichBulkWaitFor/buildEnrichProviders/buildEnrichRatingSetters/buildEnrichLoadRefreshOpts/buildEnrichRefetchRawhelpers, bringing it under the funlen gate — the.golangci.ymlgrandfather exclusion for it is removed.wfengine/engine.go'sNew()now usese.loggerconsistently (was mixing the localloggerparam ande.loggeraftereis constructed).UNIQUE KEY (book_id, creator_id, role)oncomic_metadata_creator_mappingvia migration 0051 (with a dedup pre-step for any pre-existing duplicate rows) soINSERT IGNOREinpersist.gois a real DB-side guard instead of a silent no-op (the table only had a PK on auto-incrementid).persist_test.go's 5 duplicate-name regressionDescribeblocks now call the SUT inJustBeforeEachinstead ofBeforeEach.pdf_page_images_streaming_test.go's decode-loop collapsed to a singleHaveEach(WithTransform(...))Expect.llm_scan_workflow_test.go's 3 vacuousif errors.As(err, &wfErr) { Expect(wfErr.Permanent).To(BeFalse()) }guards (never execute the inner Expect on the happy/expected path) replaced with unconditionalExpect(errors.As(err, &wfErr)).To(BeFalse())— verified againstScanBook's actual error-wrapping logic that these paths never produce a*gowf.Error.ContainElements(int64(4), int64(4))(only proved id=4 wasn't duplicated — missed id=5) replaced withConsistOf(1,2,3,4,5)asserting the exact expected id set.bookdrop.IngestFilenow takes a cheap indexedexists(ctx, path) (bool, error)dep and skipsextractMeta(a full EPUB ZIP parse) whenever the file is already known tobookdrop_file—InsertBookdropFileIfNew'sON DUPLICATE KEY UPDATEclause never touchesoriginal_metadatafor existing rows, so that extraction work was pure waste on every rescan pass over an unchanged library. Added a filename-fallback (non-EPUB) coverage test that was previously missing.scan_run.go's Phase-3 doc comment now explicitly states the authors/series post-TX gap is NOT auto-healed on a later scan (by design — see bookshelf-obdm).batch_seed_extra_test.go's mismatch-skip test now assertsexecCallCount==0to actually prove the defensive!okskip fired.persist_test.go's TX-args assertion uses a panic-safeContainElement(ContainElement(...))match instead of indexingtxExecArgs[0]directly.scan_extra.godoc comments no longer reference the no-longer-existing singularInsertBookAndFileIfAbsentfunction name (the singular path was already removed in a prior refactor — batching is now the only insert path).comicvine/title_test.go's tautologicalExpect(result, err).NotTo(BeNil())(trivially false on a non-nilable value struct) replaced withExpect(result.Title, err).NotTo(BeEmpty()).Left out (reported, not folded in — stay their own follow-up items)
Gomega Eventually): the file's multi-Expect-per-Itpattern is already permitted under the project's e2e multi-Expect relaxation policy (CLAUDE.md), and the manualtime.Now()+Sleeppoll idiom is used consistently across that entire journey file. Converting only this one spec would be an inconsistent partial refactor.INSERT IGNORE→ON DUPLICATE KEY UPDATE): those mapping tables already have real PK-based uniqueness(book_id, entity_id), soINSERT IGNOREis meaningful there today. The suggestion is speculative future-hardening against a delete path for reference tables that doesn't exist yet.internal/library/scan/meta.goas a non-io.ReaderAt(effectively test-only) fallback path; no code change needed.slog.Default()incover_extract.go): fixing requires threading a logger throughExtractCoverFromFileand its whole call chain (findOPFPath/findCoverPath/parseOPF/decodeXMLEntry) across every caller — disproportionate diff growth for a cosmetic pre-existing nit the original review explicitly flagged as "not introduced by #354".ExpectIt): no longer exists — theDescribeblock was refactored since PR #320 and no test matching that description remains onorigin/main.Docs: N/A — internal test/code hygiene + one query-level SQL fix (adds an index/constraint, no schema column change), no user-facing surface changes.
Closes bookshelf-ifwpa. Also closes: bookshelf-cco6, bookshelf-dhf4, bookshelf-drcg, bookshelf-ebti, bookshelf-k5pd, bookshelf-q1lq, bookshelf-iunx, bookshelf-8uhoy.
Test plan
make test— all packages greenmake lint— 0 issues, all policy checks (e2e/test/screenshot/controller/csrf/cursor) passmake integration—internal/db(incl. schema-compat allowlist guard),internal/dbtest,internal/wfengine,cmd/pergamumall green, confirming migration 0051 applies cleanly🤖 Generated with Claude Code
Addresses cco6/dhf4/drcg/ebti/k5pd/q1lq/iunx/8uhoy in one reviewable PR: - cco6: ListOnDeck's next-unread NOT EXISTS anti-join gets a b2.id tiebreaker so two books sharing a series_number can't both surface as "next"; test sentinel switched from an int64 zero-value to a falsifiable bool flag. - dhf4: buildEnrichDeps split into buildEnrichBulkWaitFor/buildEnrichProviders/ buildEnrichRatingSetters/buildEnrichLoadRefreshOpts/buildEnrichRefetchRaw helpers, dropping it under the funlen gate (grandfather exclusion removed); wfengine/engine.go's New() now uses e.logger consistently instead of the local logger param after e is constructed. - drcg: added a UNIQUE KEY on comic_metadata_creator_mapping(book_id, creator_id, role) (migration 0051, with a dedup pre-step) so INSERT IGNORE is a real DB-side guard instead of a no-op; persist_test.go's 5 duplicate-* Describe blocks now call the SUT in JustBeforeEach instead of BeforeEach. - ebti: pdf_page_images_streaming_test.go's decode-loop collapsed to a single HaveEach/WithTransform Expect; llm_scan_workflow_test.go's 3 vacuous `if errors.As(...) { Expect(...) }` guards replaced with unconditional Expect(errors.As(...)).To(BeFalse()) assertions that actually run. - k5pd: ContainElements dup-check now asserts the full expected id set via ConsistOf (was only checking id=4 wasn't duplicated, missing id=5). - q1lq: IngestFile now takes a cheap indexed `exists` dep and skips extractMeta (EPUB ZIP parse) whenever the file is already known to bookdrop_file — the ON DUPLICATE KEY branch never consults original_metadata for existing rows, so extraction for those was pure waste on every rescan pass. Added a filename-fallback (non-EPUB) coverage test. - iunx: scan_run.go's Phase-3 doc comment now states the authors/series post-TX gap is NOT auto-healed; batch_seed_extra_test.go's mismatch-skip test now asserts execCallCount==0; persist_test.go's TX-args It uses a panic-safe ContainElement(ContainElement(...)) match instead of indexing txExecArgs[0] directly; scan_extra.go doc comments no longer reference the no-longer-existing singular InsertBookAndFileIfAbsent function name. - 8uhoy: comicvine/title_test.go's tautological BeNil() on a value struct replaced with a meaningful Expect(result.Title, err).NotTo(BeEmpty()). Left out (reported, not folded in): - dhf4 item 3 (e2e/api journey_6 manual poll -> Gomega Eventually): the file's multi-Expect-per-It pattern is already permitted under the project's e2e multi-Expect relaxation policy, and the manual time.Now()+Sleep poll idiom is used consistently throughout that journey file — converting only this one spec would be an inconsistent partial refactor. Stays its own item. - drcg item 2 (char/team/location INSERT IGNORE -> ON DUPLICATE KEY UPDATE): those tables already have real PK-based uniqueness (book_id, entity_id), so INSERT IGNORE is meaningful there today; the suggestion is speculative future-hardening for a delete path that doesn't exist yet. - q1lq item 3 (256MB buffered EPUB fallback): already documented in internal/library/scan/meta.go as a non-ReaderAt-only (effectively test-only) fallback path; no code change needed. - q1lq item 4 (slog.Default() in cover_extract.go): fixing this requires threading a logger through ExtractCoverFromFile and its whole call chain (findOPFPath/findCoverPath/parseOPF/decodeXMLEntry) across every caller — disproportionate diff growth for a cosmetic pre-existing nit explicitly flagged as "not introduced by #354". - k5pd item 1 (ListDiscover MIN/MAX multi-Expect It): no longer exists — the Describe was refactored since PR #320 and no test matching that description remains. Docs: N/A — internal test/code hygiene + one query-level SQL fix, no user-facing surface changes. Closes bookshelf-ifwpa; addresses cco6/dhf4/drcg/ebti/k5pd/q1lq/iunx/8uhoy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tRKybTpfjQ4SxmNdVFLHiSecurity Review — PR #1397 (bookshelf-ifwpa)
(Correcting the previous comment on this PR, which was posted with stale content from an unrelated PR due to a local file-write error on my end — apologies for the noise.)
Adversarial security pass over the diff (8 review-minor cleanups folded into one PR: migration 0051, bookdrop exists-check optimization, app.go/build_enrich_deps.go refactors, test hardening).
No findings. Summary of what was checked:
internal/db/migrations/0051_comic_creator_mapping_unique.{up,down}.sql): theDELETE ... INNER JOINdedup step matches on the exact composite key(book_id, creator_id, role)before adding theUNIQUE KEY, keeping the lowestid— no cross-entity data loss, no authz-relevant mapping affected (comic creator/character/team/location mappings are global metadata, not per-user). No user input is interpolated into this migration.BookdropFileExistsByPathquery (internal/db/queries/bookdrop.sql/ generatedinternal/db/sqlc/bookdrop.sql.go) uses a?placeholder bound viaQueryRowContext(ctx, query, filePath)— properly parameterized, no string concatenation.INSERT IGNOREininternal/comic/persist.go(unchanged by this PR) already uses the same three columns as the new UNIQUE constraint, so the dedup key is consistent between the Go-side guard and the new DB-side constraint.bookdrop.IngestFileexists-check (internal/bookdrop/service.go): the newexistsdependency only skips extraction, never skips theinsertcall — the ON DUPLICATE KEY UPDATE path is still hit unconditionally, so proposal-reactivation/status semantics are unchanged. On anexistscheck error it fails open (extracts anyway) and logs aWarnwith path + err only — no secrets/PII, matches logging-standard. Confirmed viainternal/bookdrop/service_test.gonew cases (exists=true skips extract but still inserts; exists error still extracts).bookdrop,home/service.go,library/scan/scan_run.go,wfengine/engine.go,app.go,build_enrich_deps.go) remove auser_idscope, ownership check, or CSRF token.build_enrich_deps.go's 192-line diff is a pure decomposition intoenrichBulkWaitFor/buildEnrichRatingSetters/buildEnrichProviders/buildEnrichLoadRefreshOpts/buildEnrichRefetchRawhelpers (funlen gate) — same params threaded through unchanged, confirmed againstorigin/main.home/service.go's SQL change only adds ab2.id < b.idtiebreaker to the existing next-in-series anti-join (flake-prevention determinism fix per review-standard), not a scoping change.q1lq"dead buffer"/duplicate-ingest test changes:internal/bookdrop/meta_test.goadds coverage for an existing filename-fallback path; no validation logic was removed, only test assertions tightened (e.g.internal/metadata/comicvine/title_test.go,internal/wfengine/llm_scan_workflow_test.go,internal/library/scan/persist_test.goall moved to stricter matchers, not looser ones).internal/wfengine/engine.gofixes two spots to usee.loggerinstead of the pre-constructionloggerparam (cosmetic/gofmt-adjacent, no new log fields, nothing sensitive).style=surface touched.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review — PR #1397 (bookshelf-ifwpa)
Reviewed the diff against
origin/mainand cross-checked each of the 8 folded sub-beads plus the PR body's "Left out" justifications. CI is green; did not re-run tests.Linter-baseline integrity (.golangci.yml)
Verified: exactly one
exclude-rulesentry removed (build_enrich_deps.go/buildEnrichDepsfunlen), zero added. TracedbuildEnrichDepsend-to-end — it's a genuine burn-down: the 194-line function is split intoenrichBulkWaitFor,enrichRatingSetters/buildEnrichRatingSetters,enrichProviders/buildEnrichProviders,buildEnrichLoadRefreshOpts,buildEnrichRefetchRaw, leavingbuildEnrichDepsitself at ~48 lines / well under the 60-line/40-statement funlen gate. Legit.Migration 0051 (comic_creator_mapping UNIQUE)
up.sqladds a dedupDELETE ... INNER JOIN ... ON ... AND t1.id > t2.idpre-step (keeps lowest id) beforeALTER TABLE ... ADD UNIQUE KEY (book_id, creator_id, role)— correctly handles pre-existing duplicate rows on a real DB, won't fail the ALTER.down.sqlis the correct inverse (DROP KEY). Index-only, no new column — compliant with the Grimmory-compat column-freeze rule. Confirmedinternal/comic/persist.go'sINSERT IGNORE(unchanged) is now a real DB-side guard instead of a no-op, per the PR's stated intent — the PR does not convert it toON DUPLICATE KEY UPDATE; that'sdrcgitem 2, explicitly and correctly left out in the PR body (char/team/location tables already have real PK-based uniqueness, so the speculative-hardening rationale is sound).q1lq (slog.Default / bookdrop skip-reparse)
bookdrop.IngestFilenow takes an injectedexists(ctx, path) (bool, error)and skipsextractMetawhen the file is already known; on anexistserror it fails open (extracts anyway) — matches the doc comment. NewBookdropFileExistsByPathquery is an indexedEXISTS(SELECT 1 ... WHERE file_path = ?)(unique index) — cheap, no scale concern. Both callers (app.go'sstartBookdropGoroutinesandbuild_bookdrop_deps.go'sbuildBookdropScanDeps) updated consistently.slog.Default()incover_extract.go(q1lq item 4) is explicitly left out with a reasonable disproportionate-diff justification.Test hygiene / conventions
*_test.gofiles remainpackage <pkg>_test(black-box) — spot-checked all 9.exists-check tests, which correctly fold err into the valueExpect).home/service.go's On-Deck tiebreaker (b2.id < b.id) and the anon-user sentinel fix (boolflag replacing aBeZero-passes-triviallyint64) both check out against the described footgun.scan_run.goPhase-3 doc comment,batch_seed_extra_test.go'sexecCallCount==0assertion, andpersist_test.go's panic-safeContainElement(ContainElement(...))all verified againstiunx.llm_scan_workflow_test.go's vacuousif errors.As(...) { Expect(...) }guards correctly replaced with unconditionalExpect(errors.As(err, &wfErr)).To(BeFalse())(ebti).title_test.go's tautologicalNotTo(BeNil())on a non-nilable struct replaced withresult.Title ... NotTo(BeEmpty())(8uhoy).Findings
[MINOR] internal/bookdrop/service.go:153-159 —
alreadyKnown/existsErrdeclared via:=mid-function instead of hoisted into the function's topvar (...)blockBoth vars are referenced across the next two statements (cross-block meaning per project-conventions.md "Variable declarations: prefer top-of-block"), unlike the genuinely scope-local vars the convention exempts. Minor stylistic inconsistency with the rest of the same function, which otherwise strictly hoists (
absPath,info,result,ext,meta,errare all in the var block). Fix: addalreadyKnown boolandexistsErr errorto the existingvar (...)block.[MINOR] PR body — dhf4 item 2 (shared rate-limiter across HTTP + worker enrich paths) is neither listed under "Addressed" nor "Left out"
Investigated independently: it appears already moot — both
books.Wire(HTTP path, viabuild_extended_deps.go) andbuildEnrichDeps(worker path) key their token buckets identically ("token_bucket.<id>.rate"/"token_bucket.<id>") against the same DB-backedratelimiter.NewDBTokenBucketwith atomicClaimTokenBucketSlot/ClaimTokenBucketSlotWithReserveclaims — so the two paths already share real cross-process rate-limiting state, not separate in-memory limiter instances, and the dhf4 concern doesn't apply to the current code. Recommend a one-line addition to the PR body's "Left out" section (or an "Already moot" note) so future readers don't have to re-derive this the way this review did. No code change needed.No blockers, no majors. Every sub-bead's claimed "addressed" change was verified against the actual diff; every "left out" claim was independently checked against origin/main and found accurate.
REVIEW VERDICT: 0 blocker, 0 major, 2 minor
fb7e7e5f58b3f1ed8a41