From 0122c41ecb77058c6540669e33a0b3e2e1c6bfae Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 6 Jun 2026 16:54:21 +0000 Subject: [PATCH] =?UTF-8?q?feed:=20verb-aware=20smart=20dedupe=20=E2=80=94?= =?UTF-8?q?=20reactions=20collapse=20cross-actor,=20posts=20stay=20per-act?= =?UTF-8?q?or=20+=209=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/feed/conformance.sh | 2 +- lib/feed/dedupe.sx | 20 ++++++++++++++ lib/feed/scoreboard.json | 7 ++--- lib/feed/scoreboard.md | 3 ++- lib/feed/tests/dedupe.sx | 56 ++++++++++++++++++++++++++++++++++++++++ plans/feed-on-sx.md | 6 ++++- 6 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 lib/feed/tests/dedupe.sx diff --git a/lib/feed/conformance.sh b/lib/feed/conformance.sh index 5897e9b7..dc2fe512 100755 --- a/lib/feed/conformance.sh +++ b/lib/feed/conformance.sh @@ -13,7 +13,7 @@ if [ ! -x "$SX_SERVER" ]; then exit 1 fi -SUITES=(basic fanout rank integration content notify home) +SUITES=(basic fanout rank integration content notify home dedupe) OUT_JSON="lib/feed/scoreboard.json" OUT_MD="lib/feed/scoreboard.md" diff --git a/lib/feed/dedupe.sx b/lib/feed/dedupe.sx index fb850fb1..12dd9a36 100644 --- a/lib/feed/dedupe.sx +++ b/lib/feed/dedupe.sx @@ -46,11 +46,31 @@ ((a (get ev :activity))) (list (get ev :to) (get a :actor) (get a :verb) (get a :object))))) +; verbs whose duplicates collapse across actors (reactions, not authorship). +; rebindable: callers can (set! feed/collapse-verbs ...) to tune the policy. +(define + feed/collapse-verbs + (list "like" "favourite" "follow" "boost" "repost")) + +; per-verb key: collapse-verbs fold on (verb object); the rest key on +; (actor verb object). +(define + feed/smart-key + (fn + (a) + (if + (feed/-elem? (get a :verb) feed/collapse-verbs) + (feed/collapse-key a) + (feed/activity-key a)))) + ; --- ready-made dedupers ---------------------------------------------------- (define feed/dedupe-activities (fn (s) (feed/dedupe s feed/activity-key))) (define feed/dedupe-collapse (fn (s) (feed/dedupe s feed/collapse-key))) +; verb-aware: reactions collapse cross-actor, posts stay distinct per actor +(define feed/dedupe-smart (fn (s) (feed/dedupe s feed/smart-key))) + ; dedupe an inbox: at most one event per receiver per (actor verb object) (define feed/dedupe-inbox (fn (inbox) (feed/dedupe inbox feed/event-key))) diff --git a/lib/feed/scoreboard.json b/lib/feed/scoreboard.json index 74dcdc97..e8a40eae 100644 --- a/lib/feed/scoreboard.json +++ b/lib/feed/scoreboard.json @@ -6,9 +6,10 @@ "integration": {"pass": 22, "fail": 0}, "content": {"pass": 15, "fail": 0}, "notify": {"pass": 8, "fail": 0}, - "home": {"pass": 6, "fail": 0} + "home": {"pass": 6, "fail": 0}, + "dedupe": {"pass": 9, "fail": 0} }, - "total_pass": 134, + "total_pass": 143, "total_fail": 0, - "total": 134 + "total": 143 } diff --git a/lib/feed/scoreboard.md b/lib/feed/scoreboard.md index 0e5120b3..e6895f72 100644 --- a/lib/feed/scoreboard.md +++ b/lib/feed/scoreboard.md @@ -11,4 +11,5 @@ _Generated by `lib/feed/conformance.sh`_ | content | 15 | 0 | 15 | | notify | 8 | 0 | 8 | | home | 6 | 0 | 6 | -| **Total** | **134** | **0** | **134** | +| dedupe | 9 | 0 | 9 | +| **Total** | **143** | **0** | **143** | diff --git a/lib/feed/tests/dedupe.sx b/lib/feed/tests/dedupe.sx new file mode 100644 index 00000000..69f129af --- /dev/null +++ b/lib/feed/tests/dedupe.sx @@ -0,0 +1,56 @@ +; Follow-up — verb-aware (smart) dedupe. (feed-test name got expected) + +; reactions (like/follow) collapse cross-actor; posts stay distinct per actor +(define + M + (feed/stream + (list + (feed/activity "alice" "like" "X" 1 (list)) + (feed/activity "bob" "like" "X" 2 (list)) + (feed/activity "alice" "post" "P" 3 (list)) + (feed/activity "bob" "post" "P" 4 (list)) + (feed/activity "alice" "follow" "C" 5 (list)) + (feed/activity "bob" "follow" "C" 6 (list))))) ; collapses + +(feed-test + "smart dedupe total" + (feed/count (feed/dedupe-smart M)) + 4) +(feed-test + "smart keeps both posts" + (feed/count (feed/by-verb (feed/dedupe-smart M) "post")) + 2) +(feed-test + "smart collapses likes to one" + (feed/count (feed/by-verb (feed/dedupe-smart M) "like")) + 1) +(feed-test + "smart collapses follows to one" + (feed/count (feed/by-verb (feed/dedupe-smart M) "follow")) + 1) +(feed-test + "collapsed like keeps first actor" + (map feed/actor (feed/items (feed/by-verb (feed/dedupe-smart M) "like"))) + (list "alice")) + +; contrast: plain activity dedupe keeps cross-actor likes distinct +(feed-test + "activity dedupe keeps both likes" + (feed/count (feed/by-verb (feed/dedupe-activities M) "like")) + 2) + +; contrast: blanket collapse folds the two posts (same verb+object) too +(feed-test + "collapse dedupe folds posts" + (feed/count (feed/by-verb (feed/dedupe-collapse M) "post")) + 1) + +; smart-key dispatch +(feed-test + "smart-key reaction -> (verb object)" + (feed/smart-key (feed/activity "alice" "like" "X" 0 (list))) + (list "like" "X")) +(feed-test + "smart-key post -> (actor verb object)" + (feed/smart-key (feed/activity "alice" "post" "P" 0 (list))) + (list "alice" "post" "P")) diff --git a/plans/feed-on-sx.md b/plans/feed-on-sx.md index 60fcc098..80022842 100644 --- a/plans/feed-on-sx.md +++ b/plans/feed-on-sx.md @@ -14,7 +14,7 @@ APL, ACL visibility filtering via `lib/acl/`, federation via fed-sx. ## Status (rolling) -`bash lib/feed/conformance.sh` → **134/134** (Phases 1–4 + TF-IDF + notifications + home) +`bash lib/feed/conformance.sh` → **143/143** (Phases 1–4 + TF-IDF + notifications + home + smart-dedupe) ## Ground rules @@ -147,6 +147,10 @@ are function parameters. Real acl-sx / fed-sx wire in at the call site unchanged - [x] **Capstone** `feed/home` — the whole pipeline as one line: fanout ∘ inbox ∘ dedupe ∘ ACL ∘ rank ∘ take (`home.sx`); 6 tests incl. per-viewer ACL + cross-post dedupe. (134/134 total.) +- [x] Per-verb dedupe rules (briefing gotcha #3) — `feed/dedupe-smart` / + `feed/smart-key`: reactions (like/follow/boost/...) collapse cross-actor on + (verb,object); posts stay distinct per actor. `feed/collapse-verbs` is + rebindable policy; 9 tests. (143/143 total.) (none)