feat(bulk-scan): cost guardrails (budget + dry-run estimate) + progress/metrics (bookshelf-cs2zl.4) #1475
Open
zombor
wants to merge 2 commits from
bd-bookshelf-cs2zl.4 into main
pull from: bd-bookshelf-cs2zl.4
merge into: zombor:main
zombor:main
zombor:bd-bookshelf-fg10w
zombor:bd-bookshelf-psnb4
zombor:bd-bookshelf-4jjjj
zombor:bd-bookshelf-z25zw.4
zombor:bd-bookshelf-z25zw.2
zombor:bd-bookshelf-fmz2w
zombor:bd-bookshelf-uepg0
zombor:bd-bookshelf-4pndq
zombor:bd-bookshelf-z25zw.3
zombor:bd-bookshelf-z25zw.1
zombor:bd-bookshelf-lyhus
zombor:bd-bookshelf-wivx.3
zombor:bd-bookshelf-9snmz.2.4
zombor:bd-bookshelf-75buj
zombor:bd-bookshelf-00vvk.2
zombor:bd-bookshelf-k7yqm
zombor:bd-bookshelf-2ld0f
zombor:bd-bookshelf-4avzd
zombor:bd-bookshelf-nwo0v.4
zombor:bd-bookshelf-q3d2g
zombor:bd-bookshelf-00vvk.1
zombor:bd-bookshelf-mqavx
zombor:bd-bookshelf-t7cmm
zombor:bd-bookshelf-8l09k
zombor:bd-bookshelf-duy4m
zombor:bd-bookshelf-v8o1o
zombor:bd-bookshelf-lwfh6
zombor:bd-bookshelf-egkeh
zombor:bd-bookshelf-yh6wk
zombor:bd-bookshelf-f8gf8
zombor:bd-bookshelf-v1byv
zombor:bd-bookshelf-289dd
zombor:bd-bookshelf-ar5x7
zombor:bd-bookshelf-h4oh2.8
zombor:bd-cikiq
zombor:bd-nnb9-seeder-llm
zombor:bd-cleanup-batch
zombor:bd-bookshelf-l3dv0
zombor:bd-bookshelf-nnb9.30
zombor:bd-bookshelf-4dlia
zombor:bd-bookshelf-v2gkq
zombor:bd-bookshelf-4un29.7
zombor:bd-bookshelf-1v17h
zombor:bd-bookshelf-nnb9.29
zombor:bd-bookshelf-nnb9.13
zombor:bd-bookshelf-88rd5
zombor:bd-bookshelf-nnb9.27
zombor:bd-bookshelf-7vv09
zombor:bd-bookshelf-syyzy
zombor:bd-bookshelf-adzpx
zombor:bd-bookshelf-v3bi0
zombor:bd-bookshelf-j8pvj
zombor:bd-bookshelf-fqvh.5
zombor:bd-bookshelf-h4oh2.7
zombor:bd-bookshelf-ttfqc
zombor:bd-bookshelf-hva6t
zombor:bd-bookshelf-di2zy
zombor:bd-bookshelf-fqvh.4
zombor:bd-bookshelf-8dnq
zombor:bd-bookshelf-dbw4q
zombor:bd-bookshelf-1eg75
zombor:bd-bookshelf-jjcgz
zombor:bd-bookshelf-k4wji
zombor:bd-bookshelf-abwx.1
zombor:bd-bookshelf-qi21k.1
zombor:bd-bookshelf-dhlj4.2
zombor:bd-bookshelf-dhlj4.4
zombor:bd-bookshelf-dhlj4.1
zombor:bd-bookshelf-dhlj4.3
zombor:bd-bookshelf-s957
zombor:bd-bookshelf-mfli8
zombor:bd-bookshelf-h4oh2.2
zombor:bd-bookshelf-007p6
zombor:bd-bookshelf-ox4u6
zombor:bd-bookshelf-dfknw
zombor:bd-bookshelf-fvug6
zombor:bd-bookshelf-2ddlb
zombor:bd-bookshelf-nnb9.6
zombor:bd-bookshelf-5pyab
zombor:bd-bookshelf-qapga
zombor:bd-bookshelf-2dcn3
zombor:bd-bookshelf-64dbp
zombor:bd-bookshelf-r5dix
zombor:bd-bookshelf-nnb9.11
zombor:bd-bookshelf-nnb9.7
zombor:bd-bookshelf-s6k3o
zombor:bd-bookshelf-p8trd
zombor:bd-bookshelf-81hq
zombor:bd-bookshelf-tm38.9
zombor:bd-bookshelf-tm38.15
zombor:bd-bookshelf-tm38.11
zombor:bd-bookshelf-bxtat
zombor:bd-bookshelf-ifwpa
zombor:bd-bookshelf-bz643.7
zombor:bd-bookshelf-fip0
zombor:bd-bookshelf-0u50.2
zombor:bd-bookshelf-m35t
zombor:bd-bookshelf-bz643.6
zombor:bd-bookshelf-cd2u
zombor:bd-bookshelf-xyfk
zombor:bd-bookshelf-4un29.6
zombor:bd-bookshelf-alw8k
zombor:bd-bookshelf-dgs62
zombor:bd-bookshelf-bz643.5
zombor:bd-bookshelf-ztnqw
zombor:bd-bookshelf-ot43
zombor:bd-bookshelf-14k7f
zombor:bd-bookshelf-n1k3c
zombor:bd-bookshelf-67rri
zombor:bd-bookshelf-bz643.4
zombor:bd-bookshelf-ezivi
zombor:bd-bookshelf-eseay.13
zombor:bd-bookshelf-0u50.3
zombor:bd-bookshelf-eseay.27
zombor:bd-bookshelf-eseay.16
zombor:bd-bookshelf-eseay.7
zombor:bd-bookshelf-eseay.12
zombor:bd-bookshelf-eseay.18
zombor:bd-bookshelf-eseay.9
zombor:bd-bookshelf-eseay.22
zombor:bd-bookshelf-eseay.8
zombor:bd-bookshelf-eseay.6
zombor:bd-bookshelf-bz643.3
zombor:bd-bookshelf-v8qc
zombor:bd-bookshelf-2puu
zombor:bd-bookshelf-0u50.1
zombor:bd-bookshelf-eseay.1
zombor:bd-bookshelf-eseay.3
zombor:bd-bookshelf-y2r7
zombor:bd-bookshelf-guzv
zombor:bd-bookshelf-uiz4
zombor:bd-bookshelf-4un29.5
zombor:bd-bookshelf-d7zq
zombor:bd-bookshelf-eseay.5
zombor:bd-bookshelf-eseay.2
zombor:bd-bookshelf-eseay.4
zombor:bd-bookshelf-b96py
zombor:bd-bookshelf-4un29.4
zombor:bd-bookshelf-i34sv
zombor:bd-bookshelf-2a8v2
zombor:bd-bookshelf-bz643.1
zombor:bd-bookshelf-3brr8
zombor:bd-bookshelf-ora3c
zombor:bd-bookshelf-k56js
zombor:bd-bookshelf-me2xs
zombor:bd-bookshelf-tc4u
zombor:bd-bookshelf-4un29.3
zombor:bd-bookshelf-7w1mn
zombor:bd-bookshelf-bz643.2
zombor:bd-bookshelf-t9b1v
zombor:bd-bookshelf-7pobp
zombor:bd-bookshelf-kedq3
zombor:bd-bookshelf-sgow8
zombor:bd-bookshelf-ydjo9
zombor:bd-bookshelf-sdtvq
zombor:bd-bookshelf-wro8e
zombor:bd-bookshelf-xif3r
zombor:bd-bookshelf-0xjkz
zombor:bd-bookshelf-t4ftz
zombor:bd-bookshelf-d6a0j
zombor:bd-bookshelf-eu18m
zombor:bd-bookshelf-or2yq
zombor:bd-bookshelf-y7wy8
zombor:bd-bookshelf-49zk3
zombor:bd-bookshelf-5nrvm
zombor:bd-bookshelf-z0j58
zombor:bd-bookshelf-b8p9v
zombor:bd-bookshelf-7vrur
zombor:bd-bookshelf-w442p
zombor:bd-bookshelf-4vb3n
zombor:bd-bookshelf-nwo0v.2
zombor:bd-bookshelf-zwfeq
zombor:bd-bookshelf-r0ua
zombor:bd-bookshelf-skp0b.2
zombor:bd-bookshelf-t408q
zombor:bd-bookshelf-ubhc1
zombor:bd-bookshelf-cs2zl.3
zombor:bd-bookshelf-9snmz.3.2
zombor:bd-bookshelf-6tq2k.3
zombor:bd-bookshelf-bg8c5
zombor:bd-bookshelf-t3z2w.3
zombor:bd-bookshelf-4un29.2
zombor:bd-bookshelf-stwfu
zombor:bd-bookshelf-skp0b.1
zombor:bd-bookshelf-6tq2k.2
zombor:bd-bookshelf-b1mbr.6
zombor:bd-bookshelf-ti0zf
zombor:bd-bookshelf-dc11r
zombor:bd-bookshelf-bar26
zombor:bd-bookshelf-lwj7a
zombor:bd-bookshelf-gsebk
zombor:bd-bookshelf-ojr0m
zombor:bd-bookshelf-4un29.1
zombor:bd-bookshelf-hq7ty
zombor:bd-bookshelf-9snmz.2.1
zombor:bd-bookshelf-b9x27
zombor:bd-bookshelf-nwo0v.1
zombor:bd-bookshelf-t582g.3.3
zombor:bd-bookshelf-wf21j
zombor:bd-bookshelf-pcskn
zombor:bd-bookshelf-lk0f7
zombor:bd-bookshelf-9snmz.4.1
zombor:bd-bookshelf-9snmz.3.1
zombor:bd-bookshelf-w5s4b
zombor:bd-bookshelf-t582g.1.2
zombor:bd-bookshelf-2kb7e
zombor:bd-bookshelf-t582g.5.1
zombor:bd-bookshelf-t582g.1.1
zombor:bd-bookshelf-x7hym
zombor:bd-bookshelf-qgd90.11
zombor:bd-bookshelf-t582g.5.2
zombor:bd-bookshelf-l11xh
zombor:bd-bookshelf-qgd90.12
zombor:bd-bookshelf-qgd90.13
zombor:bd-bookshelf-qgd90.10
zombor:bd-bookshelf-t582g.3.1
zombor:bd-bookshelf-t582g.6.3
zombor:bd-bookshelf-kyk09
zombor:bd-bookshelf-qgd90.8
zombor:bd-bookshelf-qgd90.9
zombor:bd-bookshelf-7wnz1
zombor:bd-bookshelf-t582g.2.2
zombor:bd-bookshelf-t582g.6.2
zombor:bd-bookshelf-t582g.4.2
zombor:bd-bookshelf-t582g.6.1
zombor:bd-bookshelf-t582g.13
zombor:bd-bookshelf-t582g.12
zombor:bd-bookshelf-t582g.5.3
zombor:bd-bookshelf-t582g.3.2
zombor:bd-bookshelf-t582g.11
zombor:bd-bookshelf-9snmz.1.2
zombor:bd-bookshelf-9snmz.1.3
zombor:bd-bookshelf-9snmz.1.1
zombor:bd-bookshelf-t3z2w.6
zombor:bd-bookshelf-t582g.9
zombor:bd-bookshelf-t582g.7
zombor:bd-bookshelf-t582g.2.1
zombor:bd-bookshelf-9snmz.1.4
zombor:bd-bookshelf-t582g.10
zombor:bd-bookshelf-t582g.4.3
zombor:bd-bookshelf-t582g.14
zombor:bd-bookshelf-t582g.4.1
zombor:bd-bookshelf-zedtf
zombor:bd-bookshelf-t3z2w.4
zombor:bd-bookshelf-lems6
zombor:bd-bookshelf-lrlp6
zombor:bd-bookshelf-qgd90.5
zombor:bd-bookshelf-cs2zl.2
zombor:bd-bookshelf-wtsp.7
zombor:bd-bookshelf-qgd90.3
zombor:bd-bookshelf-sgv1d
zombor:bd-bookshelf-xrfax
zombor:bd-bookshelf-np8wh
zombor:bd-bookshelf-nosq3
zombor:bd-bookshelf-6d4km
zombor:bd-bookshelf-qgd90.2
zombor:bd-bookshelf-uwob4
zombor:bd-bookshelf-qgd90.1
zombor:bd-bookshelf-x05zh
zombor:bd-bookshelf-8br47
zombor:bd-bookshelf-t3z2w.2
zombor:bd-bookshelf-t8ovd
zombor:bd-bookshelf-t3z2w.1
zombor:bd-bookshelf-rhvsi
zombor:bd-bookshelf-o4xwc
zombor:bd-bookshelf-66xxp
zombor:bd-bookshelf-3gnqi
zombor:bd-bookshelf-b0hoe
zombor:bd-bookshelf-wtsp.1
zombor:bd-bookshelf-yyft
zombor:bd-bookshelf-cs2zl.1
zombor:bd-bookshelf-onx3v
zombor:bd-bookshelf-17i2p
zombor:bd-bookshelf-k4jrb
zombor:bd-bookshelf-c75cx
zombor:bd-bookshelf-33oo
zombor:bd-bookshelf-nnb9.4
zombor:bd-bookshelf-uoz1
zombor:bd-bookshelf-pwkx.2
zombor:bd-bookshelf-w2mii
zombor:bd-bookshelf-tm38.10
zombor:bd-bookshelf-j58r
zombor:bd-bookshelf-frlqv
zombor:bd-bookshelf-nymf0
zombor:bd-bookshelf-74rm
zombor:bd-bookshelf-6tq2k.1
zombor:bd-bookshelf-r24bc.3
zombor:bd-bookshelf-tm38.2
zombor:bd-bookshelf-b1mbr.3
zombor:bd-bookshelf-v1ap
zombor:bd-bookshelf-zgje6
zombor:bd-bookshelf-6bt0f
zombor:bd-bookshelf-rbaj
zombor:bd-bookshelf-b1mbr.4
zombor:bd-bookshelf-du8hq
zombor:bd-bookshelf-tm38.5
zombor:bd-bookshelf-qnswo
zombor:bd-bookshelf-qo6ly.2
zombor:bd-bookshelf-qo6ly.5
zombor:bd-bookshelf-qo6ly.7
zombor:bd-bookshelf-qo6ly.6
zombor:bd-bookshelf-aatkt
zombor:bd-bookshelf-7w29g
zombor:bd-bookshelf-l5a46
zombor:bd-bookshelf-npcxc
zombor:bd-bookshelf-qo6ly.4
zombor:bd-bookshelf-qo6ly.3
zombor:bd-bookshelf-1ka7f
zombor:bd-bookshelf-dzrgz
zombor:bd-bookshelf-qo6ly.1
zombor:bd-bookshelf-xb1xn
zombor:bd-bookshelf-qo6ly.8
zombor:bd-bookshelf-m3yzn
zombor:bd-bookshelf-2yn0c-p2
zombor:bd-bookshelf-2yn0c
zombor:bd-bookshelf-lbmtp
zombor:bd-bookshelf-7c8d5
zombor:bd-bookshelf-vsf6.2
zombor:bd-bookshelf-85a3a
zombor:bd-bookshelf-rval6
zombor:bd-bookshelf-bpb9.2
zombor:bd-bookshelf-fqvh.2
zombor:bd-bookshelf-8i4p
zombor:bd-bookshelf-fr9s2
zombor:bd-bookshelf-wl6by
zombor:bd-bookshelf-80j52
zombor:bd-bookshelf-r24bc.2
zombor:bd-bookshelf-0v44o.3
zombor:bd-bookshelf-bu5v
zombor:bd-bookshelf-b1mbr.5
zombor:bd-bookshelf-tm38.7
zombor:bd-bookshelf-kgebl
zombor:bd-bookshelf-7wgxa
zombor:bd-bookshelf-hqemh
zombor:bd-bookshelf-36hv1
zombor:bd-bookshelf-86v1x
zombor:bd-bookshelf-b1mbr.2
zombor:bd-bookshelf-a6qot
zombor:bd-reviewer-demo-gate
zombor:bd-bookshelf-wb7f2
zombor:bd-bookshelf-me0ix
zombor:bd-bookshelf-10th0
zombor:bd-bookshelf-7ocsw
zombor:bd-bookshelf-hflt3
zombor:bd-bookshelf-m16mq
zombor:bd-bookshelf-30pry
zombor:bd-bookshelf-mcqt8
zombor:bd-bookshelf-3q9hy
zombor:bd-bookshelf-lj69z.4
zombor:bd-bookshelf-dydjz
zombor:bd-bookshelf-7obo7
zombor:bd-bookshelf-lj69z.6
zombor:bd-bookshelf-1txhu
zombor:bd-bookshelf-zvlod
zombor:bd-bookshelf-d1866
zombor:bd-bookshelf-lj69z.3
zombor:bd-bookshelf-lj69z.2
zombor:bd-bookshelf-36j4z
zombor:bd-bookshelf-63dw.17
zombor:bd-bookshelf-m7n5c
zombor:bd-bookshelf-4jilj
zombor:bd-bookshelf-nngwa
zombor:bd-bookshelf-63dw.16
zombor:bd-bookshelf-rvfcj
zombor:bd-bookshelf-hkgs0
zombor:bd-bookshelf-kentd
zombor:bd-bookshelf-q9n8e
zombor:bd-bookshelf-j46t4
zombor:bd-bookshelf-4lli2
zombor:bd-bookshelf-lj69z.5
zombor:bd-bookshelf-hlalw
zombor:bd-bookshelf-jcb79
zombor:bd-bookshelf-gcy0e
zombor:bd-bookshelf-9ij4c
zombor:bd-bookshelf-lj69z.1
zombor:bd-bookshelf-wxxk7
zombor:bd-bookshelf-2q3jo
zombor:bd-bookshelf-5qv5.3
zombor:bd-bookshelf-ng7e5
zombor:bd-bookshelf-02gem
zombor:bd-bookshelf-zvcf
zombor:bd-bookshelf-5qv5.2
zombor:bd-bookshelf-pwkx.4
zombor:bd-bookshelf-yhfqd
zombor:bd-bookshelf-0okxi
zombor:bd-bookshelf-pwkx.3
zombor:bd-bookshelf-vhqg5
zombor:bd-bookshelf-tm38.13
zombor:bd-bookshelf-vsf6.4
zombor:bd-bookshelf-epa9z
zombor:bd-bookshelf-ju77u
zombor:bd-bookshelf-wivx.2
zombor:bd-bookshelf-5qv5.1
zombor:bd-bookshelf-vgbmo.2
zombor:bd-bookshelf-0v44o.2
zombor:bd-bookshelf-7ahb0
zombor:bd-bookshelf-g81y
zombor:bd-bookshelf-ebbx
zombor:bd-bookshelf-22pag
zombor:bd-bookshelf-c0nvx
zombor:bd-bookshelf-c2b5p
zombor:bd-bookshelf-6ze9n
zombor:bd-bookshelf-px2u9
zombor:bd-bookshelf-jqxvt
zombor:bd-bookshelf-it6qc
zombor:bd-bookshelf-f9po3
zombor:bd-bookshelf-vgbmo.1
zombor:bd-bookshelf-j49zv
zombor:bd-bookshelf-8k02l
zombor:bd-bookshelf-6nrjb
zombor:bd-bookshelf-mzvjv
zombor:bd-bookshelf-yco0j
zombor:bd-bookshelf-r24bc.1
zombor:bd-bookshelf-oi1l2
zombor:bd-bookshelf-0v44o.1
zombor:bd-bookshelf-q75pc.1
zombor:bd-bookshelf-2tqlm
zombor:bd-bookshelf-q2e6j
zombor:bd-bookshelf-tvx5
zombor:bd-bookshelf-q3urm
zombor:bd-bookshelf-af1ug
zombor:bd-bookshelf-gr8c
zombor:bd-bookshelf-w9n1
zombor:bd-bookshelf-7cj1
zombor:bd-bookshelf-x1qd
zombor:bd-bookshelf-tm5g1
zombor:bd-bookshelf-h7yv
zombor:bd-bookshelf-osnt
zombor:bd-bookshelf-63dw.15
zombor:bd-bookshelf-63dw.14
zombor:bd-bookshelf-63dw.13
zombor:bd-bookshelf-63dw.12
zombor:bd-bookshelf-63dw.11
zombor:bd-bookshelf-jnsce
zombor:bd-bookshelf-0zbt
zombor:bd-bookshelf-z9s8.3
zombor:bd-bookshelf-59k1
zombor:bd-bookshelf-sq9g
zombor:bd-bookshelf-mg8b
zombor:bd-bookshelf-nnmp
zombor:bd-bookshelf-t1j0
zombor:bd-bookshelf-km3w
zombor:bd-bookshelf-50yv
zombor:bd-bookshelf-trj6
zombor:bd-bookshelf-vguw.15
zombor:bd-bookshelf-ndik
zombor:bd-bookshelf-z9s8.2
zombor:bd-bookshelf-ldxk
zombor:bd-bookshelf-z9s8.1
zombor:bd-bookshelf-9il8
zombor:bd-bookshelf-53zz
zombor:bd-bookshelf-tm38.8
zombor:bd-bookshelf-tm38.1
zombor:bd-bookshelf-r89h
zombor:bd-bookshelf-wdow
zombor:bd-bookshelf-o6np
zombor:bd-bookshelf-qowx
zombor:bd-bookshelf-ed3d
zombor:bd-bookshelf-nrup
zombor:bd-bookshelf-ke3w
zombor:bd-bookshelf-5c2h
zombor:bd-bookshelf-9a2z
zombor:bd-bookshelf-8oj1
zombor:bd-bookshelf-gbaz
zombor:bd-bookshelf-dy0a
zombor:bd-bookshelf-p4nm
zombor:bd-bookshelf-ef27
zombor:bd-bookshelf-4hu8
zombor:bd-bookshelf-mrjp
zombor:bd-bookshelf-23v3
zombor:bd-bookshelf-1hdb
zombor:bd-bookshelf-v33b
zombor:bd-bookshelf-wfx9
zombor:bd-bookshelf-tm38.6
zombor:bd-bookshelf-i6yc
zombor:bd-bookshelf-wkwd
zombor:bd-bookshelf-qwvg
zombor:bd-bookshelf-3hdz
zombor:bd-bookshelf-rwvk
zombor:bd-bookshelf-6pit
zombor:bd-bookshelf-ihze
zombor:bd-bookshelf-hkdy
zombor:bd-bookshelf-l1nn
zombor:bd-bookshelf-4jla
zombor:bd-bookshelf-0115
zombor:bd-bookshelf-gs7j
zombor:bd-bookshelf-nnb9.17
zombor:bd-bookshelf-nnb9.28
zombor:bd-bookshelf-nfkm
zombor:bd-bookshelf-8u2q
zombor:bd-bookshelf-l6cz
zombor:bd-bookshelf-fwpb
zombor:bd-bookshelf-tpnl
zombor:bd-bookshelf-cwz9
zombor:bd-bookshelf-4el7
zombor:bd-bookshelf-drq3
zombor:bd-bookshelf-u6a5
zombor:bd-bookshelf-pwkx.1
zombor:bd-bookshelf-rnvl
zombor:bd-bookshelf-3493
zombor:bd-bookshelf-xqk5
zombor:bd-bookshelf-b8oi
zombor:bd-bookshelf-ybzp
zombor:bd-bookshelf-xvdi
zombor:bd-bookshelf-hd0y.2
zombor:bd-bookshelf-c51j
zombor:bd-bookshelf-hd0y.1
zombor:bd-bookshelf-t19a
zombor:bd-bookshelf-9yl4
zombor:bd-bookshelf-mwz5
zombor:bd-bookshelf-ic7s
zombor:bd-bookshelf-di1h
zombor:bd-bookshelf-l2ln
zombor:bd-bookshelf-uamr
zombor:bd-bookshelf-2qfp
zombor:bd-bookshelf-d3x0
zombor:bd-bookshelf-nskf
zombor:bd-bookshelf-vzc6
zombor:bd-bookshelf-76yy
zombor:bd-bookshelf-i16w
zombor:bd-bookshelf-32if
zombor:bd-bookshelf-ond2
zombor:bd-bookshelf-nbfw
zombor:bd-bookshelf-92us
zombor:bd-bookshelf-4xxe
zombor:bd-bookshelf-wivx.1
zombor:bd-bookshelf-njg4.1
zombor:bd-bookshelf-1725
zombor:bd-bookshelf-ivtz.3
zombor:bd-bookshelf-ivtz.2
zombor:bd-bookshelf-5i5b
zombor:bd-bookshelf-2v3k
zombor:bd-bookshelf-qmqd
zombor:bd-bookshelf-9by3
zombor:bd-bookshelf-qsvf
zombor:bd-bookshelf-bn7r
zombor:bd-bookshelf-6e4w
zombor:bd-bookshelf-bsbn
zombor:bd-bookshelf-ivtz.1
zombor:bd-bookshelf-f7at
zombor:bd-bookshelf-660k
zombor:bd-bookshelf-9gbq
zombor:bd-bookshelf-tdvx
zombor:bd-bookshelf-uj5h.2
zombor:bd-bookshelf-3rny
zombor:bd-bookshelf-uj5h.4
zombor:bd-bookshelf-vbs7
zombor:bd-bookshelf-kpbh
zombor:bd-bookshelf-srv2
zombor:bd-bookshelf-6dxk
zombor:bd-bookshelf-h1yj
zombor:bd-bookshelf-b5xs
zombor:bd-bookshelf-z7gq
zombor:bd-bookshelf-pls2
zombor:bd-bookshelf-hxwe
zombor:bd-bookshelf-nuun
zombor:bd-bookshelf-bbsd.8
zombor:bd-bookshelf-bbsd.1
zombor:bd-bookshelf-bbsd.6
zombor:bd-bookshelf-xjbg
zombor:bd-bookshelf-uj5h.1
zombor:bd-bookshelf-bbsd.7
zombor:bd-bookshelf-bbsd.3
zombor:bd-bookshelf-vavz
zombor:bd-bookshelf-vumd
zombor:bd-bookshelf-8dry
zombor:bd-bookshelf-fqvh.1
zombor:bd-bookshelf-bbsd.5
zombor:bd-bookshelf-bbsd.2
zombor:bd-bookshelf-bbsd.4
zombor:bd-bookshelf-v3ks
zombor:bd-bookshelf-28s0
zombor:bd-bookshelf-rppy
zombor:bd-bookshelf-s7kr
zombor:bd-bookshelf-6krh
zombor:bd-bookshelf-fq9r
zombor:bd-bookshelf-n51o
zombor:bd-bookshelf-4rvz
zombor:bd-bookshelf-cehm
zombor:bd-bookshelf-scev
zombor:bd-bookshelf-bkzy
zombor:bd-bookshelf-amwv
zombor:bd-bookshelf-9u56
zombor:bd-bookshelf-t5xp
zombor:bd-bookshelf-63dw.10
zombor:bd-bookshelf-0szi
zombor:bd-bookshelf-5kje
zombor:bd-bookshelf-fx7k
zombor:bd-bookshelf-y6z7
zombor:bd-bookshelf-zii7
zombor:bd-bookshelf-s8jh
zombor:bd-bookshelf-iso0
zombor:bd-bookshelf-ug3n
zombor:bd-bookshelf-rpr4
zombor:bd-bookshelf-b6je
zombor:bd-bookshelf-u8fd
zombor:bd-bookshelf-slof
zombor:bd-bookshelf-u5q4
zombor:bd-bookshelf-nu1i
zombor:bd-bookshelf-81x8
zombor:bd-bookshelf-vnu3
zombor:bd-bookshelf-ry66
zombor:bd-bookshelf-7mzg
zombor:bd-bookshelf-lj5p
zombor:bd-bookshelf-81gk
zombor:bd-bookshelf-poza
zombor:bd-bookshelf-v7r0
zombor:bd-bookshelf-3u1y
zombor:bd-bookshelf-y1nh
zombor:bd-bookshelf-94sd
zombor:bd-bookshelf-ssbz
zombor:bd-bookshelf-d4yf.2
zombor:bd-bookshelf-tal7
zombor:bd-bookshelf-h411
zombor:bd-bookshelf-mpfv
zombor:bd-bookshelf-o1qm
zombor:bd-bookshelf-0nyl
zombor:bd-bookshelf-63dw.9
zombor:bd-bookshelf-lwh6
zombor:bd-bookshelf-cp6i
zombor:bd-bookshelf-qdlw
zombor:bd-bookshelf-vbtl
zombor:bd-bookshelf-nqp0.3
zombor:bd-bookshelf-nqp0.1
zombor:bd-bookshelf-jxqf
zombor:bd-bookshelf-63dw.8
zombor:bd-bookshelf-d4yf.1
zombor:bd-bookshelf-pmd7
zombor:bd-bookshelf-jjj3
zombor:bd-bookshelf-5xcs.2
zombor:bd-bookshelf-nnb9.9
zombor:bd-bookshelf-wbf2
zombor:bd-bookshelf-7lsm
zombor:bd-bookshelf-5xcs.1
zombor:bd-bookshelf-ocjj
zombor:bd-bookshelf-htpc.2
zombor:bd-bookshelf-nnb9.12
zombor:bd-bookshelf-nnb9.10
zombor:bd-bookshelf-qnmg
zombor:bd-bookshelf-0ypn
zombor:bd-bookshelf-nnb9.26
zombor:bd-bookshelf-nnb9.19
zombor:bd-bookshelf-nnb9.22
zombor:bd-bookshelf-bpb9.1
zombor:bd-bookshelf-nnb9.16
zombor:bd-bookshelf-htpc.3
zombor:bd-bookshelf-zc7j
zombor:bd-bookshelf-nnb9.18
zombor:bd-bookshelf-uydp
zombor:bd-bookshelf-abwx
zombor:bd-bookshelf-nnb9.21
zombor:bd-bookshelf-3j83
zombor:bd-bookshelf-htpc.1
zombor:bd-bookshelf-nnb9.23
zombor:bd-bookshelf-9yut
zombor:bd-bookshelf-dziq
zombor:bd-bookshelf-1st
zombor:bd-bookshelf-8dp6
zombor:bd-bookshelf-ziz3
zombor:bd-bookshelf-fgkh
zombor:bd-bookshelf-nnb9.2
zombor:bd-bookshelf-nnb9.24
zombor:bd-bookshelf-1gkx
zombor:bd-bookshelf-e47e.1
zombor:bd-bookshelf-hh9z
zombor:bd-bookshelf-v1to
zombor:bd-bookshelf-m0g7
zombor:bd-bookshelf-fjkw
zombor:bd-bookshelf-vyya.3
zombor:bd-bookshelf-vqy9.20
zombor:bd-bookshelf-tdtj
zombor:bd-bookshelf-4ywp
zombor:bd-bookshelf-lw6t
zombor:bd-bookshelf-qy9b
zombor:bd-bookshelf-4op.3.5
zombor:bd-bookshelf-av3c
zombor:bd-bookshelf-y0h8
zombor:bd-bookshelf-nrl3
zombor:bd-bookshelf-04kb
zombor:bd-bookshelf-8czw
zombor:bd-bookshelf-rcgw
zombor:bd-bookshelf-skgs
zombor:bd-bookshelf-vyya.2
zombor:bd-bookshelf-vf83
zombor:bd-bookshelf-bimx
zombor:bd-bookshelf-lkra
zombor:bd-bookshelf-74qg
zombor:bd-bookshelf-061u
zombor:bd-bookshelf-qmhj
zombor:bd-bookshelf-63dw.7
zombor:bd-bookshelf-djaf
zombor:bd-bookshelf-2sjm
zombor:bd-bookshelf-3f0r.10
zombor:bd-bookshelf-9au8
zombor:bd-bookshelf-2hoy
zombor:bd-bookshelf-hynl
zombor:bd-bookshelf-qknn
zombor:bd-bookshelf-vqy9.3
zombor:bd-bookshelf-xn31
zombor:bd-bookshelf-vyya.1
zombor:bd-bookshelf-yaoi
zombor:bd-bookshelf-t1is
zombor:bd-bookshelf-f1lh
zombor:bd-bookshelf-bvmy
zombor:bd-bookshelf-vqy9-batch
zombor:bd-bookshelf-3f0r.5
zombor:bd-bookshelf-3f0r.11
zombor:bd-bookshelf-63dw.6
zombor:bd-bookshelf-o0m9
zombor:bd-bookshelf-63dw.5
zombor:bd-bookshelf-4op.11.3
zombor:bd-bookshelf-l7kx
zombor:bd-bookshelf-eccn.2
zombor:bd-bookshelf-63dw.4
zombor:bd-bookshelf-vqy9.9
zombor:bd-bookshelf-z9xy
zombor:bd-bookshelf-u7vb.4
zombor:bd-bookshelf-qzgu
zombor:bd-bookshelf-vqy9.8
zombor:bd-bookshelf-8uas
zombor:bd-bookshelf-3f0r.3
zombor:bd-bookshelf-etkw.13
zombor:bd-bookshelf-etkw.14
zombor:bd-bookshelf-4op.11.2
zombor:bd-bookshelf-uk9f
zombor:bd-bookshelf-etkw.12
zombor:bd-bookshelf-r5w2
zombor:bd-bookshelf-9gsf
zombor:bd-bookshelf-wh79
zombor:bd-bookshelf-jsng
zombor:bd-bookshelf-rqun
zombor:bd-bookshelf-hvk9
zombor:bd-bookshelf-nvnq
zombor:bd-bookshelf-xrpd
zombor:bd-bookshelf-9yjy
zombor:bd-bookshelf-0b1o
zombor:bd-bookshelf-tosp
zombor:bd-bookshelf-ezqn
zombor:bd-bookshelf-xwi0
zombor:bd-bookshelf-tvgm
zombor:bd-bookshelf-3f0r.9
zombor:bd-bookshelf-etkw.11
zombor:bd-bookshelf-vqy9.19
zombor:bd-bookshelf-etkw.10
zombor:bd-bookshelf-65fk
zombor:bd-bookshelf-u7vb.3
zombor:bd-bookshelf-quq6
zombor:bd-bookshelf-4np8
zombor:bd-bookshelf-4gae
zombor:bd-bookshelf-4fjs.2
zombor:bd-bookshelf-vqy9.12
zombor:bd-bookshelf-vqy9.15
zombor:bd-bookshelf-vqy9.13
zombor:bd-bookshelf-vqy9.2
zombor:bd-bookshelf-etkw.9
zombor:bd-bookshelf-etkw.8
zombor:bd-bookshelf-63dw.3
zombor:bd-bookshelf-3v97
zombor:bd-bookshelf-63dw.2
zombor:bd-bookshelf-c15a.6
zombor:bd-bookshelf-aulh
zombor:bd-bookshelf-etkw.7
zombor:bd-bookshelf-0ai8
zombor:bd-bookshelf-63dw.1
zombor:bd-bookshelf-etkw.6
zombor:bd-bookshelf-rzlf
zombor:bd-bookshelf-e4mq
zombor:bd-bookshelf-4ztq.2
zombor:bd-bookshelf-evno
zombor:bd-bookshelf-etkw.5
zombor:bd-bookshelf-h2af
zombor:bd-bookshelf-3f0r.7
zombor:bd-bookshelf-6edv
zombor:bd-bookshelf-pytb
zombor:bd-bookshelf-0q61
zombor:bd-bookshelf-3hcm
zombor:bd-bookshelf-hei6
zombor:bd-bookshelf-1257.1
zombor:bd-bookshelf-gxiq
zombor:bd-bookshelf-etkw.4
zombor:bd-bookshelf-y59n.5
zombor:bd-bookshelf-etkw.3
zombor:bd-bookshelf-rfru
zombor:bd-bookshelf-55dd.2
zombor:bd-bookshelf-d61z
zombor:bd-bookshelf-vqy9.7
zombor:bd-bookshelf-w3e2
zombor:bd-cleanup-sweep
zombor:bd-bookshelf-1x1l
zombor:bd-bookshelf-ts96
zombor:bd-bookshelf-vqy9.5
zombor:bd-bookshelf-s885
zombor:bd-bookshelf-4ztq.1
zombor:bd-bookshelf-wzqw
zombor:bd-bookshelf-3f0r.8
zombor:bd-bookshelf-vqy9.6
zombor:bd-bookshelf-23na
zombor:bd-bookshelf-0ynf
zombor:bd-bookshelf-55dd.1
zombor:bd-bookshelf-n89u.2
zombor:bd-bookshelf-n89u.3
zombor:bd-bookshelf-xcck
zombor:bd-bookshelf-f3yy
zombor:bd-bookshelf-nre9
zombor:bd-bookshelf-c15a.5
zombor:bd-bookshelf-06b7
zombor:bd-bookshelf-3jrv
zombor:bd-bookshelf-3jrv-rebased
zombor:bd-bookshelf-luo3
zombor:bd-bookshelf-n89u.1
zombor:bd-bookshelf-lv9s
zombor:bd-bookshelf-aq0a
zombor:bd-bookshelf-fi2u
zombor:bd-bookshelf-d7es
zombor:bd-bookshelf-irrt
zombor:bd-bookshelf-u7vb.2
zombor:bd-bookshelf-3f0r.6
zombor:bd-bookshelf-vqy9.4
zombor:bd-bookshelf-jh5d
zombor:bd-bookshelf-83i1
zombor:bd-bookshelf-yc8k
zombor:bd-bookshelf-u7vb.1
zombor:bd-bookshelf-43bh
zombor:bd-bookshelf-e0t0
zombor:bd-bookshelf-etkw.2
zombor:bd-bookshelf-5rj3
zombor:bd-bookshelf-zurw
zombor:bd-bookshelf-88oe
zombor:bd-bookshelf-etkw.1
zombor:bd-bookshelf-gf14
zombor:bd-bookshelf-tswu
zombor:bd-bookshelf-d17j
zombor:bd-bookshelf-2xfi
zombor:bd-bookshelf-q14o
zombor:bd-bookshelf-qfjk
zombor:bd-bookshelf-d1x3.3
zombor:bd-bookshelf-aox5
zombor:bd-bookshelf-2p2m
zombor:bd-bookshelf-uuew
zombor:bd-bookshelf-sjy4
zombor:bd-bookshelf-orxk
zombor:bd-bookshelf-72g7
zombor:bd-bookshelf-gzqz
zombor:bd-bookshelf-o0uh
zombor:bd-bookshelf-3f0r.4
zombor:bd-bookshelf-jmn7.4.2
zombor:bd-bookshelf-jmn7.6
zombor:bd-bookshelf-kfi6
zombor:bd-bookshelf-ptbo
zombor:bd-bookshelf-pd9x
zombor:bd-bookshelf-3f0r.2
zombor:bd-bookshelf-7hc1
zombor:bd-bookshelf-jmn7.7
zombor:bd-bookshelf-l5mo.3
zombor:bd-bookshelf-uib3.1
zombor:bd-bookshelf-w1or
zombor:bd-bookshelf-l5mo.2
zombor:bd-bookshelf-ubad
zombor:bd-bookshelf-3f0r.1
zombor:bd-bookshelf-l5mo.1
zombor:bd-bookshelf-jmn7.4.1
zombor:bd-bookshelf-g3y8
zombor:bd-bookshelf-lv0a
zombor:bd-bookshelf-bp34
zombor:bd-bookshelf-4op.11.1
zombor:bd-bookshelf-72gh
zombor:bd-bookshelf-hsf2
zombor:bd-bookshelf-pmfb
zombor:bd-bookshelf-8xhu
zombor:bd-bookshelf-vguw.7
zombor:bd-bookshelf-nb5x
zombor:bd-bookshelf-m8mh
zombor:bd-bookshelf-ubre
zombor:bd-bookshelf-vguw.6
zombor:bd-bookshelf-zw7g
zombor:bd-zw7g
zombor:bd-bookshelf-99n8
zombor:bd-bookshelf-hj9.3
zombor:bd-bookshelf-xhqy
zombor:bd-bookshelf-4op.10
zombor:bd-bookshelf-lt2p
zombor:bd-bookshelf-c15a.3
zombor:bd-bookshelf-4op.6
zombor:bd-bookshelf-d1x3.2
zombor:bd-bookshelf-4op.9
zombor:bd-bookshelf-g3rl
zombor:bd-bookshelf-senm.4
zombor:bd-bookshelf-r6lx.2
zombor:bd-bookshelf-pogw
zombor:bd-bookshelf-cizu
zombor:bd-bookshelf-hcyq
zombor:bd-bookshelf-d1x3.1
zombor:bd-bookshelf-7rz7
zombor:bd-bookshelf-j4xx
zombor:bd-bookshelf-y59n.3
zombor:bd-bookshelf-senm.3
zombor:bd-bookshelf-y59n.2
zombor:bd-bookshelf-1s0y
zombor:bd-bookshelf-9q4t
zombor:bd-bookshelf-senm.2
zombor:bd-bookshelf-iyv2
zombor:bd-bookshelf-uyem
zombor:bd-bookshelf-7akw
zombor:bd-bookshelf-d1x3.5
zombor:bd-bookshelf-cojh
zombor:bd-bookshelf-r6lx.1
zombor:bd-bookshelf-d1x3.4
zombor:bd-bookshelf-c15a.4
zombor:bd-bookshelf-dz34
zombor:bd-bookshelf-ku5n
zombor:bd-bookshelf-qebx
zombor:bd-bookshelf-yfnr2
zombor:bd-bookshelf-c5dv
zombor:bd-bookshelf-c15a.2
zombor:bd-bookshelf-l4jy
zombor:bd-bookshelf-0sha
zombor:bd-bookshelf-yfnr
zombor:bd-bookshelf-4xvk
zombor:bd-bookshelf-5adv
zombor:bd-bookshelf-v726
zombor:bd-bookshelf-c15a.1
zombor:bd-bookshelf-etwt
zombor:bd-bookshelf-ae60
zombor:bd-bookshelf-a9uk
zombor:bd-bookshelf-ilm7
zombor:bd-bookshelf-u5wp
zombor:bd-bookshelf-4fjs.1
zombor:bd-bookshelf-effh
zombor:bd-bookshelf-z6c7
zombor:bd-bookshelf-eh09
zombor:bd-bookshelf-qjty
zombor:bd-bookshelf-3tor
zombor:bd-bookshelf-q8f4
zombor:bd-bookshelf-kmqq
zombor:bd-bookshelf-tzsy
zombor:bd-bookshelf-qfff.8
zombor:bd-bookshelf-qfff.5
zombor:bd-bookshelf-qfff.7
zombor:bd-bookshelf-qfff.4
zombor:bd-bookshelf-qfff.6
zombor:bd-bookshelf-h97u
zombor:bd-bookshelf-qfff.9
zombor:bd-bookshelf-qfff.1
zombor:bd-bookshelf-qfff.3
zombor:bd-bookshelf-qfff.2
zombor:bd-bookshelf-q9sg
zombor:bd-bookshelf-cvak.1
zombor:bd-bookshelf-nkni
zombor:bd-bookshelf-4hep
zombor:bd-bookshelf-3g0l
zombor:bd-bookshelf-bnuz
zombor:bd-bookshelf-tr2i
zombor:bd-bookshelf-kxyu
zombor:bd-bookshelf-o1v2
zombor:bd-bookshelf-nage
zombor:bd-bookshelf-uij9
zombor:bd-bookshelf-1utg
zombor:bd-bookshelf-8cu.2
zombor:bd-bookshelf-e5qr
zombor:bd-bookshelf-sw5a
zombor:bd-bookshelf-l94s
zombor:bd-bookshelf-5455
zombor:bd-bookshelf-rdcc
zombor:bd-bookshelf-81q5
zombor:bd-bookshelf-jgr1.3
zombor:bd-bookshelf-8cu.5
zombor:bd-bookshelf-htlp
zombor:bd-bookshelf-4y3y.5
zombor:bd-bookshelf-8fiv
zombor:bd-bookshelf-jgr1.2
zombor:bd-bookshelf-47m9
zombor:bd-bookshelf-dwt.7
zombor:bd-bookshelf-2i6b
zombor:bd-bookshelf-50z.6
zombor:bd-bookshelf-phay
zombor:bd-bookshelf-xtnx
zombor:bd-bookshelf-h448
zombor:bd-bookshelf-50z.7
zombor:bd-bookshelf-mtxh.6
zombor:bd-bookshelf-ptvk
zombor:bd-bookshelf-tv2h
zombor:bd-bookshelf-jgr1.1
zombor:bd-bookshelf-66to
zombor:bd-bookshelf-clwi
zombor:bd-bookshelf-wy3d
zombor:bd-bookshelf-xqdp
zombor:bd-bookshelf-j4v4
zombor:bd-bookshelf-9i06
zombor:bd-bookshelf-dj2u
zombor:bd-bookshelf-uz59
zombor:bd-bookshelf-jhqh
zombor:bd-bookshelf-hoxb
zombor:bd-bookshelf-8cu.4
zombor:bd-bookshelf-nki1
zombor:bd-bookshelf-cpxo
zombor:bd-bookshelf-lplk
zombor:bd-bookshelf-v1at
zombor:bd-bookshelf-hnen
zombor:bd-bookshelf-cupd
zombor:bd-bookshelf-oxau
zombor:bd-bookshelf-nmkq
zombor:bd-bookshelf-mjbo.4
zombor:bd-bookshelf-izde
zombor:bd-bookshelf-4y3y.4
zombor:bd-bookshelf-bu7h
zombor:bd-bookshelf-ixav
zombor:bd-bookshelf-491n
zombor:bd-bookshelf-93cc
zombor:bd-bookshelf-gdnn
zombor:bd-bookshelf-qk72.2
zombor:bd-bookshelf-cs9.2
zombor:bd-bookshelf-a977
zombor:bd-bookshelf-d7d5
zombor:bd-bookshelf-8cu.3
zombor:bd-bookshelf-4op.3.6
zombor:bd-bookshelf-w59y
zombor:bd-bookshelf-dwqu
zombor:bd-bookshelf-jdls
zombor:bd-bookshelf-jmn7.3
zombor:bd-bookshelf-37ef.1
zombor:bd-bookshelf-qk72.1
zombor:bd-bookshelf-4op.3.3
zombor:bd-bookshelf-4op.3.2
zombor:bd-bookshelf-8cu.1
zombor:bd-bookshelf-xm84.1
zombor:bd-bookshelf-4op.3.1
zombor:bd-bookshelf-qk72.3
zombor:bd-bookshelf-cs9.1
zombor:bd-bookshelf-4y3y.3
zombor:bd-bookshelf-4op.5
zombor:bd-bookshelf-80zv.1
zombor:bd-bookshelf-4y3y.6
zombor:bd-bookshelf-cvak
zombor:bd-o3np
zombor:bd-bookshelf-o3np
zombor:bd-bookshelf-4y3y.2
zombor:bd-bookshelf-wg60
zombor:bd-m6x0.4
zombor:bd-bookshelf-ecaf
zombor:bd-uqwl
zombor:bd-bookshelf-8duj
zombor:bd-bookshelf-5gko
zombor:bd-bookshelf-4y3y.1
zombor:bd-bookshelf-wnt7
zombor:bd-bookshelf-hx8i
zombor:bd-bookshelf-jmn7.1
zombor:bd-bookshelf-0cay
zombor:bd-bookshelf-69k4
zombor:bd-bookshelf-rh2k
zombor:bd-bookshelf-fflj
zombor:bd-bookshelf-po6m
zombor:bd-bookshelf-37jn
zombor:bd-bookshelf-xikn
zombor:bd-bookshelf-rgjd
zombor:bd-zz2b
zombor:bd-bookshelf-epxf
zombor:bd-bookshelf-2678
zombor:bd-bookshelf-jmn7.2
zombor:bd-bookshelf-5vc6
zombor:bd-bookshelf-ve3e.6
zombor:bd-bookshelf-kn7x
zombor:bd-bookshelf-9qsi
zombor:bd-bookshelf-pnzo
zombor:bd-bookshelf-yfhm
zombor:bd-bookshelf-kurr
zombor:bd-bookshelf-h8fw
zombor:bd-bookshelf-wzef
zombor:bd-bookshelf-c6bm
zombor:bd-bookshelf-obdm
zombor:bd-bookshelf-x1g6
zombor:bd-bookshelf-2b8a
zombor:bd-bookshelf-hpu4
zombor:bd-bookshelf-gm1p
zombor:bd-bookshelf-624i
zombor:bd-bookshelf-sjpx
zombor:bd-bookshelf-6b8h
zombor:bd-bookshelf-mjbo.3
zombor:bd-bookshelf-7tln
zombor:bd-bookshelf-evw2
zombor:bd-bookshelf-yb5n
zombor:bd-bookshelf-mjbo.2
zombor:bd-bookshelf-m6x0.3
zombor:bd-bookshelf-jmn7.5
zombor:bd-bookshelf-3ehn
zombor:bd-bookshelf-7dfl
zombor:bd-bookshelf-m9gp
zombor:bd-bookshelf-buk6
zombor:bd-bookshelf-53oh
zombor:bd-bookshelf-dn31
zombor:bd-bookshelf-mjbo.8
zombor:bd-bookshelf-6ibb
zombor:bd-bookshelf-mjbo.7
zombor:bd-bookshelf-m6x0.2
zombor:bd-bookshelf-m6x0.1
zombor:bd-bookshelf-ukqt.3
zombor:bd-bookshelf-ukqt.4
zombor:bd-bookshelf-ukqt.2
zombor:bd-bookshelf-mjbo.1
zombor:bd-bookshelf-ukqt.1
zombor:bd-8uqb
zombor:bd-bookshelf-90fa
zombor:bd-bookshelf-8j6l
zombor:bd-review-minors
zombor:bd-bookshelf-pp5v
zombor:bd-bookshelf-8eu.4
zombor:bd-bookshelf-mtxh.5
zombor:bd-bookshelf-mtxh.4
zombor:bd-bookshelf-mtxh.3
zombor:bd-bookshelf-mtxh.2
zombor:bd-bookshelf-mtxh.1
zombor:bd-bookshelf-pwix
zombor:bd-bookshelf-n6gq
zombor:bd-bookshelf-60i0
zombor:bd-bookshelf-135h
zombor:bd-bookshelf-8dbo
zombor:bd-bookshelf-xh7c
zombor:bd-bookshelf-l14e
zombor:bd-bookshelf-dwpw
zombor:bd-bookshelf-1kzh
zombor:bd-bookshelf-9rvz
zombor:bd-bookshelf-3zzc
zombor:bd-bookshelf-2yth.2
zombor:bd-bookshelf-2yth.1
zombor:bd-bookshelf-b9dx
zombor:bd-bookshelf-sp5u
zombor:bd-bookshelf-ve3e.5
zombor:bd-bookshelf-6jrd
zombor:bd-bookshelf-aoem
zombor:bd-bookshelf-eapl
zombor:bd-bookshelf-51tt
zombor:bd-bookshelf-yrz5
zombor:bd-bookshelf-ur0w
zombor:bd-bookshelf-3bxp
zombor:bd-bookshelf-juh7
zombor:poc/go-workflows
zombor:bd-bookshelf-nzv0
zombor:bd-bookshelf-ve3e.3
zombor:bd-bookshelf-ve3e.2
zombor:bd-bookshelf-b32t
zombor:bd-bookshelf-ve3e.1
zombor:bd-bookshelf-vguw.17
zombor:bd-bookshelf-vguw.16
zombor:bd-bookshelf-vguw.14
zombor:bd-bookshelf-vguw.13
zombor:bd-bookshelf-vguw.12
zombor:bd-bookshelf-vguw.10
zombor:bd-bookshelf-vguw.11
zombor:bd-bookshelf-vguw.9
zombor:bd-bookshelf-vguw.8
zombor:bd-bookshelf-vguw.5
zombor:bd-bookshelf-vguw.4
zombor:bd-bookshelf-vguw.3
zombor:bd-bookshelf-vguw.2
zombor:bd-bookshelf-vguw.1
zombor:bd-bookshelf-i32o
zombor:bd-8dbo
zombor:bd-bookshelf-1ho9
zombor:bd-bookshelf-0zpr.5
zombor:bd-bookshelf-0zpr.3
zombor:bd-bookshelf-0zpr.2
zombor:bd-mg0c
zombor:bd-bookshelf-qce9.2
zombor:bd-bookshelf-0zpr.4
zombor:bd-bookshelf-qce9.4
zombor:bd-bookshelf-imcd.3
zombor:bd-bookshelf-2jxl
zombor:bd-bookshelf-imcd.1
zombor:bd-bd4l
zombor:bd-bookshelf-bevu
zombor:bd-bookshelf-imcd.2
zombor:bd-bookshelf-rb97
zombor:bd-fp2r
zombor:bd-uml2
zombor:bd-bookshelf-gphb
zombor:bd-bookshelf-9hcf
zombor:bd-bookshelf-b5pj
zombor:bd-bookshelf-xnm6
zombor:bd-bookshelf-9f97
zombor:bd-bookshelf-50z.3
zombor:bd-bookshelf-50z.4
zombor:bd-50z1-repo-refs
zombor:bd-bookshelf-50z.1
zombor:bd-bookshelf-1xpy
zombor:bd-bookshelf-mn8s
zombor:bd-bookshelf-qce9.1
zombor:bd-bookshelf-9oeo
zombor:bd-bookshelf-idkg
zombor:bd-bookshelf-1nsa
zombor:bd-bookshelf-0zpr.1
zombor:bd-bookshelf-cipr
zombor:bd-bookshelf-n43x
zombor:bd-bookshelf-c9x5
zombor:bd-bookshelf-bukz
zombor:bd-bookshelf-b3s1
zombor:bd-bookshelf-dct5
zombor:bd-bookshelf-r0es
zombor:bd-bookshelf-kfu9
zombor:bd-bookshelf-2359
zombor:bd-bookshelf-qodb
zombor:bd-bookshelf-n2q6
zombor:bd-bookshelf-het5
zombor:bd-bookshelf-2loz
zombor:bd-bookshelf-00yo
zombor:bd-bookshelf-blm4
zombor:bd-bookshelf-3avz
zombor:bd-bookshelf-4op.8
zombor:bd-bookshelf-0j7.16
zombor:bd-bookshelf-v7na
zombor:bd-bookshelf-z83r
zombor:bd-bookshelf-akzq
zombor:bd-bookshelf-6bsv
zombor:bd-bookshelf-0915
zombor:bd-bookshelf-ji8p
zombor:bd-bookshelf-que1
zombor:bd-bookshelf-1axt
zombor:bd-bookshelf-pkbz
zombor:bd-bookshelf-8ggs
zombor:bd-bookshelf-vs3x
zombor:bd-bookshelf-cxlu
zombor:bd-bookshelf-oydh
zombor:bd-bookshelf-10jh
zombor:bd-bookshelf-r2er
zombor:bd-bookshelf-38if
zombor:bd-bookshelf-4c6n
zombor:bd-bookshelf-l9nn
zombor:bd-bookshelf-dwt.8
zombor:bd-bookshelf-w5y.2
zombor:bd-bookshelf-1ozd
zombor:bd-bookshelf-s2fd
zombor:bd-bookshelf-irw
zombor:bd-bookshelf-38y
zombor:bd-bookshelf-9wut.4
zombor:bd-bookshelf-88pi
zombor:bd-bookshelf-pw9
zombor:bd-bookshelf-9wut.3
zombor:bd-bookshelf-u1d
zombor:bd-bookshelf-7wr
zombor:bd-bookshelf-moxf
zombor:bd-bookshelf-9wut.2
zombor:bd-bookshelf-i3z
zombor:bd-bookshelf-s9vp
zombor:bd-bookshelf-nx9x
zombor:bd-bookshelf-fjjl
zombor:bd-bookshelf-s9wm
zombor:bd-bookshelf-9wut.1
zombor:bd-bookshelf-t1ax
zombor:bd-bookshelf-8eu.3
zombor:bd-bookshelf-dwt.5
zombor:bd-bookshelf-j0jh
zombor:bd-bookshelf-5fy
zombor:bd-bookshelf-dwt.9
zombor:bd-bookshelf-4op.4
zombor:bd-bookshelf-8eu.2
zombor:bd-bookshelf-dwt.3
zombor:bd-bookshelf-5j1f
zombor:bd-bookshelf-i8s
zombor:bd-bookshelf-dwt.6
zombor:bd-bookshelf-8eu.1
zombor:bd-bookshelf-pur
zombor:bd-bookshelf-0j7.8
zombor:bd-bookshelf-jgh
zombor:bd-bookshelf-9jt2
zombor:bd-bookshelf-dwt.4
zombor:bd-bookshelf-pts
zombor:bd-bookshelf-nz6
zombor:bd-ful
zombor:bd-bookshelf-3mod
zombor:bd-bookshelf-6xm
zombor:bd-bookshelf-6gj
zombor:bd-bookshelf-ztw
zombor:bd-bookshelf-a56k
zombor:bd-bookshelf-xwh
zombor:bd-93w
zombor:bd-80bn
zombor:bd-bookshelf-7lz
zombor:bd-bookshelf-b4y
zombor:bd-bookshelf-r64
zombor:bd-bookshelf-w8s
zombor:bd-bookshelf-sci
zombor:bd-bookshelf-hj9.2
zombor:bd-bookshelf-91o
zombor:bd-bookshelf-6hh
zombor:bd-bookshelf-ism
zombor:bd-bookshelf-dwt.10
zombor:bd-bookshelf-qm7
zombor:bd-prefix-fix
zombor:bd-bookshelf-abf
zombor:bd-bookshelf-3dz
zombor:bd-bookshelf-xpd.4
zombor:bd-bookshelf-xpd.2.3
zombor:bd-bookshelf-pcz
zombor:bd-bookshelf-e5h
zombor:bd-bookshelf-h6w
zombor:bd-bookshelf-l0m
zombor:bd-bookshelf-xp3
zombor:bd-bookshelf-xpd.3
zombor:bd-bookshelf-xpd.2.5
zombor:bd-bookshelf-adx
zombor:bd-bookshelf-xpd.2.2
zombor:bd-bookshelf-q5k
zombor:bd-bookshelf-6v1
zombor:bd-bookshelf-8cb
zombor:bd-bookshelf-p18
zombor:bd-bookshelf-o0q
zombor:bd-bookshelf-rw5
zombor:bd-bookshelf-z0j
zombor:bd-bookshelf-vtp.10
zombor:bd-bookshelf-fj9
zombor:bd-bookshelf-b9b
zombor:bd-bookshelf-3km
zombor:bd-bookshelf-gch
zombor:bd-bookshelf-as1
zombor:bd-bookshelf-vtp.1
zombor:bd-bookshelf-24j
zombor:bd-bookshelf-a09
zombor:bd-bookshelf-7ev
zombor:bd-bookshelf-krg
zombor:bd-bookshelf-vtp.12
zombor:bd-bookshelf-8jq
zombor:bd-bookshelf-u76
zombor:bd-bookshelf-7fc
zombor:bd-bookshelf-xpd.2.6
zombor:slicing-standard
zombor:bd-bookshelf-xpd.2.7
zombor:bd-bookshelf-28z
zombor:bd-bookshelf-nqx
zombor:bd-bookshelf-9g3.3
zombor:bd-7ev
zombor:bd-bookshelf-nrr
zombor:bd-bookshelf-2se
zombor:bd-bookshelf-hj9.1.1
zombor:bd-bookshelf-hj9.1
zombor:bd-bookshelf-dl3
zombor:bd-bookshelf-17t.9
zombor:bd-bookshelf-17t.4
zombor:bd-bookshelf-2zg
zombor:bd-bookshelf-xpd.2.1
zombor:bd-bookshelf-3ll
zombor:bd-bookshelf-17t.8
zombor:bd-bookshelf-0j7.4
zombor:bd-bookshelf-xpd.2.4
zombor:bd-bookshelf-xpd.1
zombor:bd-bookshelf-ee4
zombor:bd-bookshelf-h77
zombor:bd-bookshelf-466
zombor:bd-bookshelf-0j7.15
zombor:bd-bookshelf-dwt.2
zombor:bd-bookshelf-07i
zombor:bd-bookshelf-0j7.14.4
zombor:bd-bookshelf-rg8
zombor:bd-bookshelf-9g3.2
zombor:bd-bookshelf-17t.7
zombor:bd-bookshelf-17t.2
zombor:bd-bookshelf-17t.5
zombor:bd-bookshelf-17t.6
zombor:bd-bookshelf-17t.3
zombor:bd-bookshelf-17t.1
zombor:bd-bookshelf-o92
zombor:bd-bookshelf-gmp
zombor:bd-bookshelf-0j7.7
zombor:bd-bookshelf-dwt.1
zombor:bd-bookshelf-3u6
zombor:bd-bookshelf-0j7.14.1
zombor:bd-bookshelf-24a.5
zombor:bd-bookshelf-0j7.14.3
zombor:bd-bookshelf-0j7.14.2
zombor:bd-bookshelf-9g3.1
zombor:bd-bookshelf-7pu
zombor:bd-bookshelf-waj.2
zombor:bd-bookshelf-6lc
zombor:bd-bookshelf-um5
zombor:bd-bookshelf-0j7.11
zombor:bd-bookshelf-v0g
zombor:bd-bookshelf-7m0
zombor:bd-bookshelf-4op.7
zombor:bd-bookshelf-ukv.1
zombor:bd-bookshelf-474.3
zombor:bd-bookshelf-3zz
zombor:bd-bookshelf-kb1
zombor:bd-bookshelf-iox
zombor:bd-bookshelf-ahs
zombor:bd-bookshelf-waj.1
zombor:bd-bookshelf-0j7.12
zombor:bd-bookshelf-7ua
zombor:bd-bookshelf-tsa
zombor:bd-bookshelf-474.1
zombor:bd-bookshelf-0j7.10
zombor:bd-bookshelf-w5y.3
zombor:bd-bookshelf-0j7.3
zombor:bd-bookshelf-w3m.9
zombor:bd-bookshelf-w3m.10
zombor:bd-bookshelf-w3m.40
zombor:bd-bookshelf-0j7.13
zombor:bd-bookshelf-w3m.18
zombor:bd-bookshelf-4op.2
zombor:bd-bookshelf-w3m.34
zombor:bd-bookshelf-w3m.27
zombor:bd-bookshelf-ayw
zombor:bd-bookshelf-a86
zombor:bd-bookshelf-6a1
zombor:bd-bookshelf-4op.1
zombor:bd-bookshelf-dkc
zombor:bd-bookshelf-0j7.2
zombor:bd-bookshelf-w3m.24
zombor:bd-bookshelf-e7u
zombor:bd-bookshelf-932
zombor:bd-bookshelf-e69
zombor:bd-bookshelf-w5y.8
zombor:bd-bookshelf-fr5.2
zombor:bd-bookshelf-fnb
zombor:bd-bookshelf-w5y.6
zombor:bd-bookshelf-1pv
zombor:bd-bookshelf-24a.6
zombor:bd-bookshelf-zjb
zombor:bd-bookshelf-dbp
zombor:bd-bookshelf-24a.3
zombor:bd-bookshelf-w5y.5
zombor:bd-bookshelf-0j7.1
zombor:bd-bookshelf-w3m.23
zombor:bd-bookshelf-w3m.26
zombor:bd-bookshelf-w3m.36
zombor:bd-bookshelf-w3m.35
zombor:bd-bookshelf-w3m.39
zombor:bd-bookshelf-w3m.37
zombor:bd-bookshelf-axt.4
zombor:bd-bookshelf-24a.1
zombor:bd-bookshelf-omd
zombor:bd-bookshelf-0gz
zombor:bd-bookshelf-w5y.4
zombor:bd-bookshelf-fu9.3
zombor:bd-bookshelf-ejl.1
zombor:bd-bookshelf-o7z
zombor:bd-bookshelf-c6k
zombor:bd-bookshelf-ejl.3
zombor:bd-bookshelf-tp4
zombor:bd-bookshelf-ejl.2
zombor:bd-bookshelf-fu9.2
zombor:bd-bookshelf-p5w
zombor:bd-bookshelf-fu9.1
zombor:bd-bookshelf-j4f
zombor:bd-bookshelf-axt.3
zombor:bd-bookshelf-w5y.1
zombor:bd-bookshelf-yuh
zombor:bd-bookshelf-nfb
zombor:bd-bookshelf-axt.2
zombor:bd-bookshelf-rna
zombor:bd-bookshelf-2lh
zombor:bd-bookshelf-axt.1
zombor:bd-bookshelf-qqz.5
zombor:bd-bookshelf-s92
zombor:workflow-mergeability-check
zombor:bd-bookshelf-2tc
zombor:bd-bookshelf-8df
zombor:bd-bookshelf-qqz.9
zombor:bd-bookshelf-qqz.4
zombor:bd-bookshelf-qqz.7
zombor:code-style-rules
zombor:ci-drop-mysql-service
zombor:bd-bookshelf-qqz.8
zombor:bd-bookshelf-qqz.11
zombor:bd-bookshelf-qqz.6
zombor:bd-bookshelf-qqz.10
zombor:bd-bookshelf-qqz.3
zombor:bd-bookshelf-qqz.2
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
zombor/pergamum!1475
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-cs2zl.4"
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
Final vertical slice of the bulk scan-file-for-metadata epic (cs2zl.1-.3): cost guardrails + observability.
scan_file.max_books_per_runsetting (Settings -> Metadata -> LLM Scan Settings, default 500). The by-ids toolbar trigger hard-rejects a selection that exceeds the budget. The by-filter/whole-library trigger enforces the budget as a running total insideBulkByFilterWorkflowacrossContinueAsNewepochs (scoped to the scan-file op only), since the exact match count isn't cheaply known up front at that scale.~N sec/min/hr) alongside the book count.internal/books.ScanFileMetrics—scan_file_bulk_applied_total,_review_queued_total,_skipped_total,_apply_failed_total,_review_failed_total.Replay-safety / workflow versioning note
This PR changes the command sequence of
ScanFileBookWorkflow(adds aRecordSkipactivity call on the below-confidence-threshold branch) and adds new carry-forward fields toBulkByFilterInput/BulkScanFileByIDsInput/ScanFileBookInput. go-workflows in this codebase has nogowf.Versionprimitive, so this follows the established replay-safe input-flag-gate pattern already used byScanFileBookInput.ReviewModeandBookdropAutoFetchInput.ResolveOnTerminalFailure:ScanFileMaxBooks/ScanFileProcessed(BulkByFilterInput) default to0for any serialized input that predates this change, preserving the original unbounded/no-early-stop behavior for in-flight and other-op instances.EmitSkipMetric(ScanFileBookInput) defaults tofalsefor any serialized input that predates this change, so an in-flight instance replays its original (noRecordSkipcall) path; only brand-new sub-workflow instances (created after this deploy) set ittrueand take the new path.Test plan
go test ./internal/settings/... ./internal/wfengine/... ./internal/books/...— new budget-cap and metrics tests, including workflow-tester coverage of the budget-truncation and exact-boundary cases.make lint— clean.make coverage— 100% oninternal/.npm run coverage— 100% JS (new/updated Vitest specs forscan_file_settings_controller.jsandlibrary_kebab_menu_controller.js).Closes bead bookshelf-cs2zl.4 on merge.
Screenshot: new Max books per run budget-cap field in Settings → Metadata → LLM Scan Settings.
Screenshot: new Max books per run budget-cap field in Settings -> Metadata -> LLM Scan Settings.
UI Review (rendered screenshot)
Reviewed the attached screenshot of the LLM Scan Settings section plus the
templates/pages/settings_shell.htmldiff.[MINOR] templates/pages/settings_shell.html:730 — new field is the only one of the four in this section with a
.form-hint, slightly denser than its three siblings (Ebook/Comic threshold, review-mode checkbox have none). Not a foreign pattern (.form-hintis used elsewhere on this same page, e.g. line 614's token-budget field with near-identical shape), so this is a nit, not a defect — consider adding brief hints to the sibling threshold fields too for consistency, or leave as-is since the budget-cap semantics genuinely need more explanation.What's good:
.metadata-field/.metadata-field-label/.metadata-field-input/.form-hintclasses exactly — no bespoke styling.style=anywhere in the diff (grep confirmed).REVIEW VERDICT: 0 blocker, 0 major, 1 minor
UI Review (rendered screenshot)
Reviewed the attached screenshot of the LLM Scan Settings section plus the
templates/pages/settings_shell.htmldiff.[MINOR] templates/pages/settings_shell.html:730 — new field is the only one of the four in this section with a
.form-hint, slightly denser than its three siblings (Ebook/Comic threshold, review-mode checkbox have none). Not a foreign pattern (.form-hintis used elsewhere on this same page, e.g. line 614's token-budget field with near-identical shape), so this is a nit, not a defect — consider adding brief hints to the sibling threshold fields too for consistency, or leave as-is since the budget-cap semantics genuinely need more explanation.What's good:
.metadata-field/.metadata-field-label/.metadata-field-input/.form-hintclasses exactly — no bespoke styling.style=anywhere in the diff (grep confirmed).REVIEW VERDICT: 0 blocker, 0 major, 1 minor
UI Review (rendered screenshot)
Reviewed the attached screenshot of the LLM Scan Settings section plus the
templates/pages/settings_shell.htmldiff.[MINOR] templates/pages/settings_shell.html:730 — new field is the only one of the four in this section with a
.form-hint, slightly denser than its three siblings (Ebook/Comic threshold, review-mode checkbox have none). Not a foreign pattern (.form-hintis used elsewhere on this same page, e.g. line 614's token-budget field with near-identical shape), so this is a nit, not a defect — consider adding brief hints to the sibling threshold fields too for consistency, or leave as-is since the budget-cap semantics genuinely need more explanation.What's good:
.metadata-field/.metadata-field-label/.metadata-field-input/.form-hintclasses exactly — no bespoke styling.style=anywhere in the diff (grep confirmed).REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Security Review — PR #1475 (bd-bookshelf-cs2zl.4)
Reviewed the full diff (wfengine bulk_by_filter/bulk_scan_file workflows, app wiring, settings, metrics, JS/templates).
Findings: none rising to BLOCKER/MAJOR.
Verification performed:
Budget cap is genuine and server-enforced.
StartBulkScanFileByIDs(internal/app/build_wf_triggers.go) hard-rejects (422 viamiddleware.ErrValidation) a by-ids request whoselen(bookIDs) > maxBooks, wheremaxBookscomes fromsettings.LoadScanFileSettings/DefaultScanFileMaxBooksPerRun— never from the request body.ScanFileMaxBooks/ScanFileProcessed) carried acrossContinueAsNewepochs viascanFileBudgetTruncate, which truncates the batch and stops the workflow (no furtherContinueAsNew) once the budget is exhausted — confirmed by real multi-epoch tests inbulk_by_filter_workflow_test.go("scan-file op with a budget cap" / "...landing exactly on a batch boundary") that assert the workflow stops without spawning a further epoch.MaxScanFileMaxBooksPerRun(100000) bounds the admin-configurable value itself, preventing an operator from disabling the guardrail via an absurd setting.ScanFileMaxBooks/ScanFileProcessedare newBulkByFilterInputfields that default to 0 (unlimited) on unmarshal for pre-existing in-flight instances — correct replay-safety per the project's workflow-versioning convention (no command-sequence change, only a data value), matching the existingReviewMode/ResolveOnTerminalFailurepattern.Multi-user / authz — unaffected by this PR.
POST /books/bulk/scan-file,POST /books/bulk/filter/scan-fileroute registration (internal/books/routes.go) and by-filter library/user scoping (BulkByFilterFilter.UserID,UserLibraryIDs) are untouched by this diff; the budget fields are purely additive and do not widen any existing scope.Settings authz —
GET/PUT /settings/scan-file-settingsremain behindadminRequired(internal/settings/routes.go, unchanged). NewMaxBooksPerRunfield is validated server-side (validateMaxBooksPerRun: range[0, 100000], tested for negative and over-max in scan_file_settings_test.go) before persisting toapp_settings.Metrics —
internal/books/scan_file_metrics.gocounters (scan_file_bulk_applied_total,..._review_queued_total,..._skipped_total,..._apply_failed_total,..._review_failed_total) carry no labels at all — no book IDs, filenames, titles, or user identifiers as label values, so no PII leak and no cardinality risk.Template/JS — no inline
style=introduced. The new_scanFileEstimatedry-run message is built from a Stimulus-supplied numericbookCountValue(not free-text/HTML), and the confirm dialog message is plain text viaAppDialog.confirm, not an HTML sink — no injection vector. New settings input (#scan-file-max-books-per-run) is a plain numeric<input type="number">bound via Stimulus target, value rendered through Go template's default (HTML-escaped){{.MetaScanFileSettings.MaxBooksPerRun}}.Logging —
SaveScanFileSettingsHandlerlogs only numeric threshold/bool/int values (no secrets/PII); consistent with existing logging in that handler.Minor process notes (non-blocking, informational only):
internal/wfengine/export_test.goadditions (NewBulkScanFileActivitiesWithMetrics,BulkScanFileMetricHooksExport) are new white-box test exports, butinternal/wfenginecarries a standing, documented exception for this pattern (func-field DI + MySQL-only constructor makes black-box conversion impractical) — consistent with prior PRs in this package.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Security Review — PR #1475 (bd-bookshelf-cs2zl.4)
Reviewed the full diff (wfengine bulk_by_filter/bulk_scan_file workflows, app wiring, settings, metrics, JS/templates).
Findings: none rising to BLOCKER/MAJOR.
Verification performed:
Budget cap is genuine and server-enforced.
StartBulkScanFileByIDs(internal/app/build_wf_triggers.go) hard-rejects (422 viamiddleware.ErrValidation) a by-ids request whoselen(bookIDs) > maxBooks, wheremaxBookscomes fromsettings.LoadScanFileSettings/DefaultScanFileMaxBooksPerRun— never from the request body.ScanFileMaxBooks/ScanFileProcessed) carried acrossContinueAsNewepochs viascanFileBudgetTruncate, which truncates the batch and stops the workflow (no furtherContinueAsNew) once the budget is exhausted — confirmed by real multi-epoch tests inbulk_by_filter_workflow_test.go("scan-file op with a budget cap" / "...landing exactly on a batch boundary") that assert the workflow stops without spawning a further epoch.MaxScanFileMaxBooksPerRun(100000) bounds the admin-configurable value itself, preventing an operator from disabling the guardrail via an absurd setting.ScanFileMaxBooks/ScanFileProcessedare newBulkByFilterInputfields that default to 0 (unlimited) on unmarshal for pre-existing in-flight instances — correct replay-safety per the project's workflow-versioning convention (no command-sequence change, only a data value), matching the existingReviewMode/ResolveOnTerminalFailurepattern.Multi-user / authz — unaffected by this PR.
POST /books/bulk/scan-file,POST /books/bulk/filter/scan-fileroute registration (internal/books/routes.go) and by-filter library/user scoping (BulkByFilterFilter.UserID,UserLibraryIDs) are untouched by this diff; the budget fields are purely additive and do not widen any existing scope.Settings authz —
GET/PUT /settings/scan-file-settingsremain behindadminRequired(internal/settings/routes.go, unchanged). NewMaxBooksPerRunfield is validated server-side (validateMaxBooksPerRun: range[0, 100000], tested for negative and over-max in scan_file_settings_test.go) before persisting toapp_settings.Metrics —
internal/books/scan_file_metrics.gocounters (scan_file_bulk_applied_total,..._review_queued_total,..._skipped_total,..._apply_failed_total,..._review_failed_total) carry no labels at all — no book IDs, filenames, titles, or user identifiers as label values, so no PII leak and no cardinality risk.Template/JS — no inline
style=introduced. The new_scanFileEstimatedry-run message is built from a Stimulus-supplied numericbookCountValue(not free-text/HTML), and the confirm dialog message is plain text viaAppDialog.confirm, not an HTML sink — no injection vector. New settings input (#scan-file-max-books-per-run) is a plain numeric<input type="number">bound via Stimulus target, value rendered through Go template's default (HTML-escaped){{.MetaScanFileSettings.MaxBooksPerRun}}.Logging —
SaveScanFileSettingsHandlerlogs only numeric threshold/bool/int values (no secrets/PII); consistent with existing logging in that handler.Minor process notes (non-blocking, informational only):
internal/wfengine/export_test.goadditions (NewBulkScanFileActivitiesWithMetrics,BulkScanFileMetricHooksExport) are new white-box test exports, butinternal/wfenginecarries a standing, documented exception for this pattern (func-field DI + MySQL-only constructor makes black-box conversion impractical) — consistent with prior PRs in this package.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review — PR #1475 (bd-bookshelf-cs2zl.4)
Reviewed the diff only (no re-run of make test/coverage/e2e).
Workflow replay-safety (highest priority) — PASS
BulkByFilterInput.ScanFileMaxBooks/ScanFileProcessed(internal/wfengine/bulk_by_filter_workflow.go:110-121):scanFileBudgetTruncate(line ~262) is gated oninput.Op != BulkFilterOpScanFile || input.ScanFileMaxBooks <= 0— for every non-scan-file op, and for pre-deploy in-flight scan-file instances whose recorded input predates the field (unmarshals to 0), it returnsbookIDsunchanged andexhausted=false, so the emitted command sequence (ExecuteActivity/fan-out/ContinueAsNew) is byte-identical to before. Confirmed correctly gated — not a hazard.ScanFileBookInput.EmitSkipMetric(internal/wfengine/bulk_scan_file_workflow.go:80-88): the newRecordSkipExecuteActivity(line ~191) is gated oninput.EmitSkipMetric, which defaults tofalseon unmarshal for any in-flight sub-workflow instance recorded before this field existed — replay-safe. New parent-workflow epochs setting ittrueunconditionally for new sub-workflow instances does not affect the parent's own replay determinism (only sub-workflow input payload content changes, not the parent's command type/order).RecordSkipis a method on the already-registeredBulkScanFileActivitiesstruct, registered automatically viaws.registerActivity(scanActs)reflection (worker_set.go:246) — confirmed no missing-activity-registration bug, andmodule_domains_test.go's real-engine test still exercises that registration path.Budget cap correctness — PASS
len(bookIDs) > maxBooks, wrappingmiddleware.ErrValidation(→ 400).ContinueAsNewepochs viaScanFileProcessedcarried forward (bulk_by_filter_workflow.go:266-286); boundary-exact and mid-epoch-truncation cases are both covered by non-vacuous multi-epoch tests (bulk_by_filter_workflow_test.go:1669,:1749).DefaultScanFileMaxBooksPerRun), 0 = default viaEffectiveMaxBooksPerRun; settings round-trip and validation ([0, 100000]) covered inscan_file_settings_test.go.Metrics — PASS
registerScanFileCounter(internal/books/scan_file_metrics.go) returns the existing collector onAlreadyRegisteredError, safe for repeatedapp.Newcalls; non-vacuous counter-value tests viareg.Gather().Findings
[MAJOR] internal/app/build_wf_triggers.go:298-311 — by-ids budget-reject guardrail has zero test coverage
internal/appis (by project convention) untested at the unit level and relies on e2e tests to prove correctness ("pure wiring; no unit tests by design — correctness verified by e2e"). This PR adds real conditional business logic here — the per-run budget hard-reject forStartBulkScanFileByIDs— but the diff adds no e2e spec (browser or API) exercising it, and none of the existing scan-file e2e journeys (journey_bulk_scan_file_test.go,journey_scan_file_reviews_test.go,journey_library_scan_file_test.go) were touched. This is the primary cost-guardrail the bead exists to add, and it is currently unverified by any automated test — a typo or logic inversion (e.g.<vs>) here would ship silently. Add at least one e2e assertion that POSTing a by-ids selection larger than the configuredmax_books_per_runreturns 400/validation-error and does not start the workflow.Minor observations (non-blocking)
[MINOR] static/js/controllers/library_kebab_menu_controller.js:398-409 —
_scanFileEstimateuses a hardcoded20sec/book constant with no relation to any measured or server-configured value. It's documented as "rough"/"dry-run preview," which is honest, but if the LLM per-book latency changes materially the estimate will silently drift out of date with no test or config tying it to reality. Consider a code comment pointer or follow-up bead to revisit if provider latency changes significantly.[MINOR] internal/settings/scan_file_settings.go:210 —
ErrInvalidMaxBooksPerRunis built withfmt.Errorf(no%w) as a sentinelvar, unlike its siblingErrInvalidThresholdwhich useserrors.New. Cosmetic only (both work identically as comparison sentinels since there's no wrapped verb), but slightly inconsistent style — prefererrors.Newfor a pure sentinel.REVIEW VERDICT: 0 blocker, 1 major, 2 minor
Code Review — PR #1475 (bd-bookshelf-cs2zl.4)
Reviewed the diff only (no re-run of make test/coverage/e2e).
Workflow replay-safety (highest priority) — PASS
BulkByFilterInput.ScanFileMaxBooks/ScanFileProcessed(internal/wfengine/bulk_by_filter_workflow.go:110-121):scanFileBudgetTruncate(line ~262) is gated oninput.Op != BulkFilterOpScanFile || input.ScanFileMaxBooks <= 0— for every non-scan-file op, and for pre-deploy in-flight scan-file instances whose recorded input predates the field (unmarshals to 0), it returnsbookIDsunchanged andexhausted=false, so the emitted command sequence (ExecuteActivity/fan-out/ContinueAsNew) is byte-identical to before. Confirmed correctly gated — not a hazard.ScanFileBookInput.EmitSkipMetric(internal/wfengine/bulk_scan_file_workflow.go:80-88): the newRecordSkipExecuteActivity(line ~191) is gated oninput.EmitSkipMetric, which defaults tofalseon unmarshal for any in-flight sub-workflow instance recorded before this field existed — replay-safe. New parent-workflow epochs setting ittrueunconditionally for new sub-workflow instances does not affect the parent's own replay determinism (only sub-workflow input payload content changes, not the parent's command type/order).RecordSkipis a method on the already-registeredBulkScanFileActivitiesstruct, registered automatically viaws.registerActivity(scanActs)reflection (worker_set.go:246) — confirmed no missing-activity-registration bug, andmodule_domains_test.go's real-engine test still exercises that registration path.Budget cap correctness — PASS
len(bookIDs) > maxBooks, wrappingmiddleware.ErrValidation(→ 400).ContinueAsNewepochs viaScanFileProcessedcarried forward (bulk_by_filter_workflow.go:266-286); boundary-exact and mid-epoch-truncation cases are both covered by non-vacuous multi-epoch tests (bulk_by_filter_workflow_test.go:1669,:1749).DefaultScanFileMaxBooksPerRun), 0 = default viaEffectiveMaxBooksPerRun; settings round-trip and validation ([0, 100000]) covered inscan_file_settings_test.go.Metrics — PASS
registerScanFileCounter(internal/books/scan_file_metrics.go) returns the existing collector onAlreadyRegisteredError, safe for repeatedapp.Newcalls; non-vacuous counter-value tests viareg.Gather().Findings
[MAJOR] internal/app/build_wf_triggers.go:298-311 — by-ids budget-reject guardrail has zero test coverage
internal/appis (by project convention) untested at the unit level and relies on e2e tests to prove correctness ("pure wiring; no unit tests by design — correctness verified by e2e"). This PR adds real conditional business logic here — the per-run budget hard-reject forStartBulkScanFileByIDs— but the diff adds no e2e spec (browser or API) exercising it, and none of the existing scan-file e2e journeys (journey_bulk_scan_file_test.go,journey_scan_file_reviews_test.go,journey_library_scan_file_test.go) were touched. This is the primary cost-guardrail the bead exists to add, and it is currently unverified by any automated test — a typo or logic inversion (e.g.<vs>) here would ship silently. Add at least one e2e assertion that POSTing a by-ids selection larger than the configuredmax_books_per_runreturns 400/validation-error and does not start the workflow.Minor observations (non-blocking)
[MINOR] static/js/controllers/library_kebab_menu_controller.js:398-409 —
_scanFileEstimateuses a hardcoded20sec/book constant with no relation to any measured or server-configured value. It's documented as "rough"/"dry-run preview," which is honest, but if the LLM per-book latency changes materially the estimate will silently drift out of date with no test or config tying it to reality. Consider a code comment pointer or follow-up bead to revisit if provider latency changes significantly.[MINOR] internal/settings/scan_file_settings.go:210 —
ErrInvalidMaxBooksPerRunis built withfmt.Errorf(no%w) as a sentinelvar, unlike its siblingErrInvalidThresholdwhich useserrors.New. Cosmetic only (both work identically as comparison sentinels since there's no wrapped verb), but slightly inconsistent style — prefererrors.Newfor a pure sentinel.REVIEW VERDICT: 0 blocker, 1 major, 2 minor
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.