feat(home): Discover rail — scale-safe random-id-anchor seek (bookshelf-0zpr.2) #320
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-0zpr.2"
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
WHERE id >= anchor ORDER BY id LIMIT 12) — noORDER BY RAND()ListDiscover(queryRow, query, listFormats)following the curried-fn pattern; added as 6th arg toGet()Test plan
DiscoverSQLwhitebox: noORDER BY RAND(), hasid >= ?, hasLIMIT ?, filtersdeleted = 0DiscoverIDRangeSQLwhitebox: usesMIN/MAX, filtersdeleted = 0DiscoverDateSeed: same value same day, different value next dayListDiscover: empty library (NULL min/max), normal fetch (12 books), wrap-around dedup, ErrNoRows, ID range error, query error, scan error, rows.Err, wrap-around query error, enrichAuthors error, formats error, authors populatedGetintegration: Discover rail present when non-empty, omitted when empty, error propagatedlistDiscoverarg (default stub returns empty)make testgreenmake coverage100%Closes bead bookshelf-0zpr.2 on merge.
SECURITY REVIEW — bookshelf-0zpr.2 (PR #320)
Scope: SQL injection, data exposure, unbounded queries, anchor arithmetic overflow.
1. SQL Injection — CLEAN
Both
discoverSQLanddiscoverIDRangeSQLuse only?bind parameters. The computedanchor(int64) andlimit(int) are passed toquery(ctx, discoverSQL, anchor, limit)— no string interpolation anywhere in the call path. No injection surface.2. Soft-delete filter — CLEAN
discoverSQL:WHERE b.id >= ? AND b.deleted = 0— soft-deleted books are excluded from results.discoverIDRangeSQL:WHERE deleted = 0— the range query also excludes deleted books, so the anchor is always derived from the live book population. No deleted books leak through.3. Library-wide scope (not per-user) — INTENTIONAL, NO SENSITIVE DATA
The Discover rail is explicitly library-wide (no
user_idfilter). This is a "random sample" discovery surface, analogous to a bookstore browsing table, so the design intent is correct. The query selects only:b.id,bm.title(viaCOALESCE),b.book_cover_hash, and a booleanhas_coverflag — no user data, no reading progress, no PII, no private metadata is exposed. Nothing sensitive leaks from the absence of a user filter.4. LIMIT present — CLEAN
discoverSQLhasLIMIT ?with the boundlimitparameter (set todiscoverLimit = 12). The wrap-around path passesneed = limit - len(result)as its limit argument. Both paths are bounded.5. Anchor arithmetic overflow — CLEAN
span = maxID.Int64 - minID.Int64 + 1: both aresql.NullInt64(64-bit signed). With auto-increment PKs the practical max gap is well underint64range. A library of 2^63 books is not a plausible scenario.rng.Int63n(span):rand.Int63nrequiresspan > 0. The only wayspan == 0would be ifmax == min - 1(impossible sincemin <= maxfrom MIN/MAX semantics) — so this is safe. Ifmin == max,span == 1,Int63n(1)returns 0, andanchor = minID, which is correct.anchor = minID.Int64 + rng.Int63n(span): result is in[minID, maxID]inclusive — always a valid PK seek value. No overflow risk with realistic book counts.6. Wrap-around: minID seek as fallback — CLEAN
The second seek uses
minID.Int64as anchor withneedas limit (both validated). The dedup set correctly prevents returning the same book twice. No privacy concern since all results come from the same non-deleted, non-sensitive column set.No findings.
REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review — bookshelf-0zpr.2 (PR #320)
Phase 0: DEMO Verification
No DEMO block was found in the bead comments or PR body. The PR contains a test plan checklist but no runnable command whose output can be re-executed and verified. For a UI rail feature, a valid DEMO would be a curl against the rendered home page or a browser screenshot showing the Discover rail. Per the review standard, absence of a DEMO block is a blocking finding.
Phase 1 & 2: Scale, Determinism, Correctness
Scale (PASS)
discoverSQLusesWHERE b.id >= ? ... ORDER BY b.id LIMIT ?— a pure PK forward-range seek, O(LIMIT) at any scale. Confirmed: noORDER BY RAND().discoverIDRangeSQLisSELECT MIN(id), MAX(id) FROM book WHERE deleted = 0— reads two index extremes only, constant cost regardless of table size.LIMITparameters. No unbounded query.Determinism (PASS)
discoverDateSeedcomputest.UTC().Unix() / 86400— integer UTC day number, stable for all timestamps on the same calendar day, changes at midnight UTC. Determinism is correct.time.Now()is called inside theGetreturned closure at request time (service.go:85), not at wiring time. The seed is therefore per-request but stable within a day — exactly the intended behaviour.anchor = minID.Int64 + rng.Int63n(span)wherespan = maxID - minID + 1 >= 1.Int63nrange is[0, span-1], so anchor is always in[minID, maxID]. No out-of-bounds risk.span = 1,Int63n(1) = 0,anchor = minID. Seek returns the one book. Correct.Correctness (PASS with one minor)
sql.NullInt64.Validcheck atservice.go:219returns empty slice. Correct.sql.ErrNoRowsguard atservice.go:551also returns empty slice. Correct.seenmap is built frombookIDs(first-seek IDs), thenextrarows with matching IDs are skipped. Parallelresult/bookIDsslices stay in sync through the append loop.enrichAuthorsreceives the correctly unified slices. Logic is sound.need(notlimit) as the SQL LIMIT, so it requests only the remaining gap. Correct.fetchDiscoverBooks, enrichAuthors, and listFormats are propagated with wrapped errors. No silent swallowing.wire.gowithd.Conn.QueryRowContextandd.Conn.QueryContext.Test quality
[MINOR]
internal/home/service_test.go:461—It("selects MIN and MAX of id")contains twoExpectcalls in oneItblock, violating the one-Expect-per-It convention (project-conventions.md). Split into twoItblocks:It("contains MIN(id)")andIt("contains MAX(id)").[MINOR]
internal/home/service_test.go:572—Expect(ids).NotTo(ContainElements(int64(4), int64(4)))only verifies id=4 is not duplicated. id=5 is also a potential duplicate from the second seek but is not checked. ConsiderExpect(ids).To(HaveLen(5))paired withConsistOfto be exhaustive, or at minimum add a check for id=5.REVIEW VERDICT: 1 blocker, 0 major, 2 minor
The blocker is the absent DEMO block — the Discover rail is a visible UI feature and the implementer did not provide a runnable command or screenshot showing it rendered on the home page. The two minors are test-quality nits that do not affect correctness or security.
0f0885ae471f35a45ed6