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

+8
View File
@@ -34,6 +34,14 @@ test('normalizeMedia maps fields and drops novels', () => {
assert.equal(normalizeMedia({ ...media, status: 'HIATUS' }).status, 'unknown');
});
test('normalizeMedia drops authors without an integer staff id', () => {
const media = fixture('title').data.Media;
const edge = (id, full) => ({ role: 'Story', node: { id, name: { full } } });
const record = normalizeMedia({ ...media, staff: { edges: [edge(5, 'Has Id'), edge(undefined, 'No Id'),
edge('6', 'String Id'), edge(1.5, 'Float Id'), edge(null, 'Null Id')] } });
assert.deepEqual(record.authors, [{ staffId: 5, name: 'Has Id', role: 'Story' }]);
});
test('title returns a record and sends the fixed headers', async () => {
const { client, calls } = stub(() => json(fixture('title')));
const record = await client.title(101);