this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

automod: account ack and escalation (#782)

@foysalit: this is part of
https://github.com/bluesky-social/indigo/pull/740

I pulled out the record-level ack and escalation for now, for
simplicity, even though it will probably be necessary for that other
branch to get merged.

authored by

bnewbold and committed by
GitHub
5bbea226 296087d0

+151 -4
+10
automod/engine/account_meta.go
··· 6 6 "github.com/bluesky-social/indigo/atproto/identity" 7 7 ) 8 8 9 + var ( 10 + ReviewStateEscalated = "escalated" 11 + ReviewStateOpen = "open" 12 + ReviewStateClosed = "closed" 13 + ReviewStateNone = "none" 14 + ) 15 + 9 16 // information about a repo/account/identity, always pre-populated and relevant to many rules 10 17 type AccountMeta struct { 11 18 Identity *identity.Identity ··· 34 41 EmailConfirmed bool 35 42 IndexedAt *time.Time 36 43 AccountTags []string 44 + // ReviewState will be one of ReviewStateEscalated, ReviewStateOpen, ReviewStateClosed, ReviewStateNone, or "" (unknown) 45 + ReviewState string 46 + Appealed bool 37 47 }
+8
automod/engine/context.go
··· 279 279 c.effects.TakedownAccount() 280 280 } 281 281 282 + func (c *AccountContext) EscalateAccount() { 283 + c.effects.EscalateAccount() 284 + } 285 + 286 + func (c *AccountContext) AcknowledgeAccount() { 287 + c.effects.AcknowledgeAccount() 288 + } 289 + 282 290 func (c *RecordContext) AddRecordFlag(val string) { 283 291 c.effects.AddRecordFlag(val) 284 292 }
+17 -1
automod/engine/effects.go
··· 12 12 QuotaModReportDay = 2000 13 13 // number of takedowns automod can action per day, for all subjects combined (circuit breaker) 14 14 QuotaModTakedownDay = 200 15 + // number of misc actions automod can do per day, for all subjects combined (circuit breaker) 16 + QuotaModActionDay = 1000 15 17 ) 16 18 17 19 type CounterRef struct { ··· 42 44 AccountFlags []string 43 45 // Reports which should be filed against this account, as a result of rule execution. 44 46 AccountReports []ModReport 45 - // If "true", indicates that a rule indicates that the entire account should have a takedown. 47 + // If "true", a rule decided that the entire account should have a takedown. 46 48 AccountTakedown bool 49 + // If "true", a rule decided that the reported account should be escalated. 50 + AccountEscalate bool 51 + // If "true", a rule decided that the reports on account should be resolved as acknowledged. 52 + AccountAcknowledge bool 47 53 // Same as "AccountLabels", but at record-level 48 54 RecordLabels []string 49 55 // Same as "AccountFlags", but at record-level ··· 126 132 // Enqueues the entire account to be taken down at the end of rule processing. 127 133 func (e *Effects) TakedownAccount() { 128 134 e.AccountTakedown = true 135 + } 136 + 137 + // Enqueues the account to be "escalated" for mod review at the end of rule processing. 138 + func (e *Effects) EscalateAccount() { 139 + e.AccountEscalate = true 140 + } 141 + 142 + // Enqueues reports on account to be "acknowledged" (closed) at the end of rule processing. 143 + func (e *Effects) AcknowledgeAccount() { 144 + e.AccountAcknowledge = true 129 145 } 130 146 131 147 // Enqueues the provided label (string value) to be added to the record at the end of rule processing.
+15
automod/engine/fetch_account_meta.go
··· 131 131 if rd.Moderation.SubjectStatus.Takendown != nil && *rd.Moderation.SubjectStatus.Takendown == true { 132 132 am.Takendown = true 133 133 } 134 + if rd.Moderation.SubjectStatus.Appealed != nil && *rd.Moderation.SubjectStatus.Appealed == true { 135 + ap.Appealed = true 136 + } 134 137 ap.AccountTags = dedupeStrings(rd.Moderation.SubjectStatus.Tags) 138 + if rd.Moderation.SubjectStatus.ReviewState != nil { 139 + switch *rd.Moderation.SubjectStatus.ReviewState { 140 + case "#reviewOpen": 141 + ap.ReviewState = ReviewStateOpen 142 + case "#reviewEscalated": 143 + ap.ReviewState = ReviewStateEscalated 144 + case "#reviewClosed": 145 + ap.ReviewState = ReviewStateClosed 146 + case "#reviewNonde": 147 + ap.ReviewState = ReviewStateNone 148 + } 149 + } 135 150 } 136 151 am.Private = &ap 137 152 }
+11 -1
automod/engine/metrics.go
··· 37 37 38 38 var actionNewTakedownCount = promauto.NewCounterVec(prometheus.CounterOpts{ 39 39 Name: "automod_new_action_takedowns", 40 - Help: "Number of new flags persisted", 40 + Help: "Number of new takedowns", 41 + }, []string{"type"}) 42 + 43 + var actionNewEscalationCount = promauto.NewCounterVec(prometheus.CounterOpts{ 44 + Name: "automod_new_action_escalations", 45 + Help: "Number of new subject escalations", 46 + }, []string{"type"}) 47 + 48 + var actionNewAcknowledgeCount = promauto.NewCounterVec(prometheus.CounterOpts{ 49 + Name: "automod_new_action_acknowledges", 50 + Help: "Number of new subjects acknowledged", 41 51 }, []string{"type"}) 42 52 43 53 var accountMetaFetches = promauto.NewCounter(prometheus.CounterOpts{
+70 -2
automod/engine/persist.go
··· 57 57 if err != nil { 58 58 return fmt.Errorf("circuit-breaking takedowns: %w", err) 59 59 } 60 + newEscalation := c.effects.AccountEscalate 61 + if c.Account.Private != nil && c.Account.Private.ReviewState == ReviewStateEscalated { 62 + // de-dupe account escalation 63 + newEscalation = false 64 + } else { 65 + newEscalation, err = eng.circuitBreakModAction(ctx, newEscalation) 66 + if err != nil { 67 + return fmt.Errorf("circuit-breaking escalation: %w", err) 68 + } 69 + } 70 + newAcknowledge := c.effects.AccountAcknowledge 71 + if c.Account.Private != nil && (c.Account.Private.ReviewState == "closed" || c.Account.Private.ReviewState == "none") { 72 + // de-dupe account escalation 73 + newAcknowledge = false 74 + } else { 75 + newAcknowledge, err = eng.circuitBreakModAction(ctx, newAcknowledge) 76 + if err != nil { 77 + return fmt.Errorf("circuit-breaking acknowledge: %w", err) 78 + } 79 + } 60 80 61 - anyModActions := newTakedown || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 81 + anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 62 82 if anyModActions && eng.Notifier != nil { 63 83 for _, srv := range dedupeStrings(c.effects.NotifyServices) { 64 84 if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil { ··· 145 165 if err != nil { 146 166 c.Logger.Error("failed to execute account takedown", "err", err) 147 167 } 168 + 169 + // we don't want to escalate if there is a takedown 170 + newEscalation = false 148 171 } 149 172 150 - needCachePurge := newTakedown || len(newLabels) > 0 || len(newFlags) > 0 || createdReports 173 + if newEscalation { 174 + c.Logger.Warn("account-escalate") 175 + actionNewEscalationCount.WithLabelValues("account").Inc() 176 + comment := "[automod]: auto account-escalation" 177 + _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{ 178 + CreatedBy: xrpcc.Auth.Did, 179 + Event: &toolsozone.ModerationEmitEvent_Input_Event{ 180 + ModerationDefs_ModEventEscalate: &toolsozone.ModerationDefs_ModEventEscalate{ 181 + Comment: &comment, 182 + }, 183 + }, 184 + Subject: &toolsozone.ModerationEmitEvent_Input_Subject{ 185 + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ 186 + Did: c.Account.Identity.DID.String(), 187 + }, 188 + }, 189 + }) 190 + if err != nil { 191 + c.Logger.Error("failed to execute account escalation", "err", err) 192 + } 193 + } 194 + 195 + if newAcknowledge { 196 + c.Logger.Warn("account-acknowledge") 197 + actionNewAcknowledgeCount.WithLabelValues("account").Inc() 198 + comment := "[automod]: auto account-acknowledge" 199 + _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{ 200 + CreatedBy: xrpcc.Auth.Did, 201 + Event: &toolsozone.ModerationEmitEvent_Input_Event{ 202 + ModerationDefs_ModEventAcknowledge: &toolsozone.ModerationDefs_ModEventAcknowledge{ 203 + Comment: &comment, 204 + }, 205 + }, 206 + Subject: &toolsozone.ModerationEmitEvent_Input_Subject{ 207 + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ 208 + Did: c.Account.Identity.DID.String(), 209 + }, 210 + }, 211 + }) 212 + if err != nil { 213 + c.Logger.Error("failed to execute account acknowledge", "err", err) 214 + } 215 + } 216 + 217 + needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || createdReports 151 218 if needCachePurge { 152 219 return eng.PurgeAccountCaches(ctx, c.Account.Identity.DID) 153 220 } ··· 303 370 c.Logger.Error("failed to execute record takedown", "err", err) 304 371 } 305 372 } 373 + 306 374 return nil 307 375 }
+20
automod/engine/persisthelpers.go
··· 111 111 return takedown, nil 112 112 } 113 113 114 + // Combined circuit breaker for miscellaneous mod actions like: escalate, acknowledge 115 + func (eng *Engine) circuitBreakModAction(ctx context.Context, action bool) (bool, error) { 116 + if !action { 117 + return false, nil 118 + } 119 + c, err := eng.Counters.GetCount(ctx, "automod-quota", "mod-action", countstore.PeriodDay) 120 + if err != nil { 121 + return false, fmt.Errorf("checking mod action quota: %w", err) 122 + } 123 + if c >= QuotaModActionDay { 124 + eng.Logger.Warn("CIRCUIT BREAKER: automod action") 125 + return false, nil 126 + } 127 + err = eng.Counters.Increment(ctx, "automod-quota", "mod-action") 128 + if err != nil { 129 + return false, fmt.Errorf("incrementing mod action quota: %w", err) 130 + } 131 + return action, nil 132 + } 133 + 114 134 // Creates a moderation report, but checks first if there was a similar recent one, and skips if so. 115 135 // 116 136 // Returns a bool indicating if a new report was created.