Skip to content

Add StoreDigest plugin for content-based deduplication - #152

Merged
id774 merged 1 commit into
masterfrom
claude/store-digest-plugin-2vlhub
Aug 17, 2026
Merged

Add StoreDigest plugin for content-based deduplication#152
id774 merged 1 commit into
masterfrom
claude/store-digest-plugin-2vlhub

Conversation

@id774

@id774 id774 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Introduces a new StoreDigest plugin that deduplicates feed items based on content digest rather than URL, complementing the existing StorePermalink plugin's URL-based deduplication.

Key Changes

  • New plugin: StoreDigest (plugins/store/digest.rb)

    • Computes SHA-256 digest of configurable item fields (default: title and description)
    • Records digests in SQLite and passes only items with new digests downstream
    • Handles content-based deduplication: same article at different URLs is one item; same URL with different content is a new item
    • Normalizes field values (UTF-8 encoding, Unicode NFC, whitespace collapsing) before hashing for consistent matching
    • Validates configuration strictly: rejects empty field lists, unknown fields, duplicates, and missing database names
    • Handles edge cases: items with no digestible content are passed through; overlapping runs are protected by unique index constraints
  • Comprehensive test suite (spec/plugins/store/digest_spec.rb)

    • 424 lines of specifications covering normal operation, field configuration, normalization behavior, error handling, and database failure scenarios
    • Tests verify digest computation, deduplication logic, field ordering, Unicode handling, and whitespace normalization
  • Documentation updates (doc/PLUGINS.md)

    • Added detailed StoreDigest section explaining use cases, configuration, and behavior
    • Clarified differences between StorePermalink (URL identity), StoreFullText (URL+title+body), and StoreDigest (content identity)
    • Provided example recipes showing practical usage patterns
  • Supporting updates

    • Updated plugin count from 35 to 36 in documentation and README
    • Updated timestamps in related files (database.rb, DEPLOYMENT.md, BASIC_DESIGN.md, Gemfile, VERSIONS)

Notable Implementation Details

  • Uses null byte (\0) as separator in canonical digest string to prevent field/value collisions
  • Normalizes values through UTF-8 encoding with replacement, scrubbing, NFC normalization, whitespace collapsing, and trimming
  • Does not store item content—only the digest—allowing pairing with StoreFullText for complete deduplication with content preservation
  • Database failures are not rescued; failed writes end the run to prevent silent deduplication failures
  • Unique index on digest column prevents duplicate storage in overlapping runs

https://claude.ai/code/session_01PRMBU24jWQcDTH5GH5NJMy

StorePermalink asks whether a link has been seen. A page that reissues one
article under a new URL, and a feed built from an HTML index by CustomFeedWeb,
pose the other question: whether this content has been seen, whatever it was
published under. StoreDigest answers that one.

The fields the Recipe names -- title and description by default -- are read as
UTF-8, normalized to NFC with their whitespace runs collapsed, joined with
their field names into one canonical string and hashed with SHA-256. The
digest is recorded in SQLite and nothing else is: what an item said is
StoreFullText's business.

Exact matching of the selected content, and only that. There is no fuzzy
comparison, no similarity threshold and no algorithm setting. A Recipe that
names fields gets those fields, with no fallback to another when the one it
asked for is empty, so that what the Recipe says two identical items are is
what the plugin obeys. An item whose selected fields are all empty is passed
on with a warning rather than stored under the digest of the empty string,
which would make every item with no description the same item.

A database failure ends the run instead of being rescued, which is a
deliberate difference from StoreFullText: an item passed on after its digest
failed to store would be published again next run. The digest column carries a
unique index, so two overlapping runs cannot both store one digest.

The Database mixin supplies the connection, the table and the directory; the
digest loop is this plugin's own, because for_each_new_feed is written around a
link. Nothing in StorePermalink or StoreFullText changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRMBU24jWQcDTH5GH5NJMy
@id774
id774 merged commit 00ccc1a into master Aug 17, 2026
8 checks passed
@id774
id774 deleted the claude/store-digest-plugin-2vlhub branch August 17, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants