Discovery final review fixes: lean publish, robust scoring, cheap prune (BL-0051)

- Cache prune counts directory entries first and only reads files when over
  the cap; tests for cross-kind prune, schema-mismatch and corrupt entries.
- Scoring tolerates malformed manga.json and cached records (non-list tags,
  object or null relations, non-object items); a tag without a rank no longer
  passes the top-tag filter; authors without an integer AniList id are dropped.
- discovery.json publishes only candidates some feed shows; examined/clipped
  still describe the pool. Tests that asserted the published count equals the
  pool size now check the pool via candidatePool() and the published set
  against the feeds.
- The post-harvest rescore failure test now breaks discovery.json right before
  the publish (via a scraper stand-in) instead of relying on tags: 5, and a
  following successful harvest clears the stale lastError.
- Consent text names everything sent to AniList; paused label mentions admin
  harvests; the UI test asserts the XSS seed instead of skipping it.
- Slot-mix test proves the fresh/outside reservations matter.
- Docs: privacy note, fresh fallback reason, per-period cap, dry-run wording,
  published-candidate rule; plan/spec hygiene and spec status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MaashkinoozheandClaude Opus 5.5 committed 2026-10-25 09:43:31 +01:00
1 parent 613db96355
commit cd6a30a76b
18 files changed
+250 -65

No files matched your search

+4 -2
View File
@@ -317,8 +317,10 @@ try {
const harvester = new Harvester({ cache: harvestCache, client: createAniListClient(), store: discoveryStore,
targetsFor: harvestTargets, onDone: async job => {
// A failed follow-up rescore must reach the admin panel (lastError), not only the log.
try { await rescore(job.kind === 'user' ? { onlyUser: job.user } : {}); }
catch (error) { discoveryError = `Re-scoring after harvest ${job.id} failed: ${String(error.message || error)}`.slice(0, 300); throw error; }
try {
await rescore(job.kind === 'user' ? { onlyUser: job.user } : {});
discoveryError = null; // a later success must not leave an old failure on the admin panel
} catch (error) { discoveryError = `Re-scoring after harvest ${job.id} failed: ${String(error.message || error)}`.slice(0, 300); throw error; }
} });
function requestHarvest(args) {
readDiscovery(); // A corrupt store fails closed before any AniList call.