-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
automod: action limits; create reports for interaction churn (#465)
The motivation here is to start auto-reporting in production based on specific rules. Specifically, this PR would start reporting on interactions churn (follow/unfollow), letting human mods confirm before taking action on an account. Before we do that, need to de-duplicate reports. For example, if an account creates thousands of spammy posts, only want to report the account once. Generally want to prevent run-away rules from creating millions of reports, or doing thousands of automated account takedowns (for example). This PR prevents re-reporting based on daily counters (fast checks), as well as double-checking against the mod service API just before filing a report (slower, but reliable). This PR also adds "quotas" for mod actions, implemented using the counter system. This isn't perfect (the counter system itself might be buggy or broken (causing duplicate actions), but seems like a good start. If quotas are exceeded, automod will log and skip taking additional actions until the next day.
- Loading branch information
Showing
7 changed files
with
364 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package automod | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
appbsky "github.com/bluesky-social/indigo/api/bsky" | ||
"github.com/bluesky-social/indigo/atproto/identity" | ||
"github.com/bluesky-social/indigo/atproto/syntax" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func alwaysReportAccountRule(evt *RecordEvent) error { | ||
evt.ReportAccount(ReportReasonOther, "test report") | ||
return nil | ||
} | ||
|
||
func TestAccountReportDedupe(t *testing.T) { | ||
assert := assert.New(t) | ||
ctx := context.Background() | ||
engine := engineFixture() | ||
engine.Rules = RuleSet{ | ||
RecordRules: []RecordRuleFunc{ | ||
alwaysReportAccountRule, | ||
}, | ||
} | ||
|
||
path := "app.bsky.feed.post/abc123" | ||
cid1 := "cid123" | ||
p1 := appbsky.FeedPost{Text: "some post blah"} | ||
id1 := identity.Identity{ | ||
DID: syntax.DID("did:plc:abc111"), | ||
Handle: syntax.Handle("handle.example.com"), | ||
} | ||
|
||
// exact same event multiple times; should only report once | ||
for i := 0; i < 5; i++ { | ||
assert.NoError(engine.ProcessRecord(ctx, id1.DID, path, cid1, &p1)) | ||
} | ||
|
||
reports, err := engine.GetCount("automod-quota", "report", PeriodDay) | ||
assert.NoError(err) | ||
assert.Equal(1, reports) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package automod | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
appbsky "github.com/bluesky-social/indigo/api/bsky" | ||
"github.com/bluesky-social/indigo/atproto/identity" | ||
"github.com/bluesky-social/indigo/atproto/syntax" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func alwaysTakedownRecordRule(evt *RecordEvent) error { | ||
evt.TakedownRecord() | ||
return nil | ||
} | ||
|
||
func alwaysReportRecordRule(evt *RecordEvent) error { | ||
evt.ReportRecord(ReportReasonOther, "test report") | ||
return nil | ||
} | ||
|
||
func TestTakedownCircuitBreaker(t *testing.T) { | ||
assert := assert.New(t) | ||
ctx := context.Background() | ||
engine := engineFixture() | ||
dir := identity.NewMockDirectory() | ||
engine.Directory = &dir | ||
// note that this is a record-level action, not account-level | ||
engine.Rules = RuleSet{ | ||
RecordRules: []RecordRuleFunc{ | ||
alwaysTakedownRecordRule, | ||
}, | ||
} | ||
|
||
path := "app.bsky.feed.post/abc123" | ||
cid1 := "cid123" | ||
p1 := appbsky.FeedPost{Text: "some post blah"} | ||
|
||
// generate double the quote of events; expect to only count the quote worth of actions | ||
for i := 0; i < 2*QuotaModTakedownDay; i++ { | ||
ident := identity.Identity{ | ||
DID: syntax.DID(fmt.Sprintf("did:plc:abc%d", i)), | ||
Handle: syntax.Handle("handle.example.com"), | ||
} | ||
dir.Insert(ident) | ||
assert.NoError(engine.ProcessRecord(ctx, ident.DID, path, cid1, &p1)) | ||
} | ||
|
||
takedowns, err := engine.GetCount("automod-quota", "takedown", PeriodDay) | ||
assert.NoError(err) | ||
assert.Equal(QuotaModTakedownDay, takedowns) | ||
|
||
reports, err := engine.GetCount("automod-quota", "report", PeriodDay) | ||
assert.NoError(err) | ||
assert.Equal(0, reports) | ||
} | ||
|
||
func TestReportCircuitBreaker(t *testing.T) { | ||
assert := assert.New(t) | ||
ctx := context.Background() | ||
engine := engineFixture() | ||
dir := identity.NewMockDirectory() | ||
engine.Directory = &dir | ||
engine.Rules = RuleSet{ | ||
RecordRules: []RecordRuleFunc{ | ||
alwaysReportRecordRule, | ||
}, | ||
} | ||
|
||
path := "app.bsky.feed.post/abc123" | ||
cid1 := "cid123" | ||
p1 := appbsky.FeedPost{Text: "some post blah"} | ||
|
||
// generate double the quota of events; expect to only count the quota worth of actions | ||
for i := 0; i < 2*QuotaModReportDay; i++ { | ||
ident := identity.Identity{ | ||
DID: syntax.DID(fmt.Sprintf("did:plc:abc%d", i)), | ||
Handle: syntax.Handle("handle.example.com"), | ||
} | ||
dir.Insert(ident) | ||
assert.NoError(engine.ProcessRecord(ctx, ident.DID, path, cid1, &p1)) | ||
} | ||
|
||
takedowns, err := engine.GetCount("automod-quota", "takedown", PeriodDay) | ||
assert.NoError(err) | ||
assert.Equal(0, takedowns) | ||
|
||
reports, err := engine.GetCount("automod-quota", "report", PeriodDay) | ||
assert.NoError(err) | ||
assert.Equal(QuotaModReportDay, reports) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.