Skip to content

Commit

Permalink
automod: mad science, attempting to factor engine/event/effect apart.
Browse files Browse the repository at this point in the history
Non-compiling checkpoint.

Separating events and effects feels good.  Read-only event structures
and append-only effects structures clarifies many semantics.

All of the persistence logic itself stayed close to the engine, and
takes the effects struct as a parameter.  This felt super good.
It means all the methods on the effects struct aire simply for
mutating it.  The struct itself remains legible and has no side-effects
on its own (except logging), so the ability to test this without
entanglements should be quite high.

Distinct packages for events and effects is up for debate.  It made it
easier to have the compiler help me enforce detanglement during the
experiment, but is not necessarily a good idea in the long run.

The biggest issue that needs further pondering is what we want to see
the ruletypes file look like.  We aimed to take one more step from here
to introduce a "business context" object and reduce the number of
parameters of rule funcs to approximately one (maybe two, for unpacked
specific values like "post")... but the trouble with that idea is that
we still see that e.g. IdentityEvent and RecordEvent are still already
distinct types.  We note that RecordEvent is a strict superset of
IdentityEvent, so maybe we just decide to take the superset one in
order to have a single simple "business context" object... but we're
not yet entirely sure if that flies the way we like.
  • Loading branch information
warpfork committed Dec 22, 2023
1 parent f886793 commit 3ad3cfc
Show file tree
Hide file tree
Showing 18 changed files with 966 additions and 948 deletions.
159 changes: 159 additions & 0 deletions automod/effects/effects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package effects

import (
"log/slog"
"time"
)

var (
// time period within which automod will not re-report an account for the same reasonType
ReportDupePeriod = 7 * 24 * time.Hour
// number of reports automod can file per day, for all subjects and types combined (circuit breaker)
QuotaModReportDay = 50
// number of takedowns automod can action per day, for all subjects combined (circuit breaker)
QuotaModTakedownDay = 10
)

type CounterRef struct {
Name string
Val string
Period *string
}

type CounterDistinctRef struct {
Name string
Bucket string
Val string
}

type RepoEffect struct {
// Any error encountered while processing the event can be stashed in this field and handled at the end of all processing.
//Err error // REVIEW: nothing seems to have actually used this. If we keep it we should decide the methods that pile mutations into this struct don't return errors; two paths is bad.

// slog logger handle, with event-specific structured fields pre-populated. Pointer, but expected to not be nil.
Logger *slog.Logger

// List of counters which should be incremented as part of processing this event. These are collected during rule execution and persisted in bulk at the end.
CounterIncrements []CounterRef
// Similar to "CounterIncrements", but for "distinct" style counters
CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
// Label values which should be applied to the overall account, as a result of rule execution.
AccountLabels []string
// Moderation flags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
AccountFlags []string
// Reports which should be filed against this account, as a result of rule execution.
AccountReports []ModReport
// If "true", indicates that a rule indicates that the entire account should have a takedown.
AccountTakedown bool
}

// Enqueues the named counter to be incremented at the end of all rule processing. Will automatically increment for all time periods.
//
// "name" is the counter namespace.
// "val" is the specific counter with that namespace.
func (e *RepoEffect) Increment(name, val string) {
e.CounterIncrements = append(e.CounterIncrements, CounterRef{Name: name, Val: val})
}

// Enqueues the named counter to be incremented at the end of all rule processing. Will only increment the indicated time period bucket.
func (e *RepoEffect) IncrementPeriod(name, val string, period string) {
e.CounterIncrements = append(e.CounterIncrements, CounterRef{Name: name, Val: val, Period: &period})
}

// Enqueues the named "distinct value" counter based on the supplied string value ("val") to be incremented at the end of all rule processing. Will automatically increment for all time periods.
func (e *RepoEffect) IncrementDistinct(name, bucket, val string) {
e.CounterDistinctIncrements = append(e.CounterDistinctIncrements, CounterDistinctRef{Name: name, Bucket: bucket, Val: val})
}

// Enqueues the provided label (string value) to be added to the account at the end of rule processing.
func (e *RepoEffect) AddAccountLabel(val string) {
e.AccountLabels = append(e.AccountLabels, val)
}

// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
func (e *RepoEffect) AddAccountFlag(val string) {
e.AccountFlags = append(e.AccountFlags, val)
}

// Enqueues a moderation report to be filed against the account at the end of rule processing.
func (e *RepoEffect) ReportAccount(reason, comment string) {
if comment == "" {
comment = "(no comment)"
}
comment = "automod: " + comment
e.AccountReports = append(e.AccountReports, ModReport{ReasonType: reason, Comment: comment})
}

// Enqueues the entire account to be taken down at the end of rule processing.
func (e *RepoEffect) TakedownAccount() {
e.AccountTakedown = true
}

func (e *RepoEffect) CanonicalLogLine() {
e.Logger.Info("canonical-event-line",
"accountLabels", e.AccountLabels,
"accountFlags", e.AccountFlags,
"accountTakedown", e.AccountTakedown,
"accountReports", len(e.AccountReports),
)
}

type IdentityEffect struct {
RepoEffect
}

type RecordEffect struct {
RepoEffect

// Same as "AccountLabels", but at record-level
RecordLabels []string
// Same as "AccountFlags", but at record-level
RecordFlags []string
// Same as "AccountReports", but at record-level
RecordReports []ModReport
// Same as "AccountTakedown", but at record-level
RecordTakedown bool
// TODO: commit metadata
}

// Enqueues the provided label (string value) to be added to the record at the end of rule processing.
func (e *RecordEffect) AddRecordLabel(val string) {
e.RecordLabels = append(e.RecordLabels, val)
}

// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
func (e *RecordEffect) AddRecordFlag(val string) {
e.RecordFlags = append(e.RecordFlags, val)
}

// Enqueues a moderation report to be filed against the record at the end of rule processing.
func (e *RecordEffect) ReportRecord(reason, comment string) {
if comment == "" {
comment = "(automod)"
} else {
comment = "automod: " + comment
}
e.RecordReports = append(e.RecordReports, ModReport{ReasonType: reason, Comment: comment})
}

// Enqueues the record to be taken down at the end of rule processing.
func (e *RecordEffect) TakedownRecord() {
e.RecordTakedown = true
}

func (e *RecordEffect) CanonicalLogLine() {
e.Logger.Info("canonical-event-line",
"accountLabels", e.AccountLabels,
"accountFlags", e.AccountFlags,
"accountTakedown", e.AccountTakedown,
"accountReports", len(e.AccountReports),
"recordLabels", e.RecordLabels,
"recordFlags", e.RecordFlags,
"recordTakedown", e.RecordTakedown,
"recordReports", len(e.RecordReports),
)
}

type RecordDeleteEffect struct {
RepoEffect
}
4 changes: 2 additions & 2 deletions automod/report.go → automod/effects/report.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package automod
package effects

type ModReport struct {
ReasonType string
Expand All @@ -14,7 +14,7 @@ var (
ReportReasonOther = "com.atproto.moderation.defs#reasonOther"
)

func reasonShortName(reason string) string {
func ReasonShortName(reason string) string {
switch reason {
case ReportReasonSpam:
return "spam"
Expand Down
221 changes: 0 additions & 221 deletions automod/engine.go

This file was deleted.

Loading

0 comments on commit 3ad3cfc

Please sign in to comment.