Select the types of activity you want to include in your feed.
automod: engine support for adding tags (#752)
I want to test this (eg, in staging) with example rules for both
accounts and records before merging. But adding for visibility, in case
we need this for other rules sooner.
···4040 CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
4141 // Label values which should be applied to the overall account, as a result of rule execution.
4242 AccountLabels []string
4343- // Moderation flags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
4343+ // Moderation tags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
4444+ AccountTags []string
4545+ // automod flags (metadata) which should be applied to the account as a result of rule execution.
4446 AccountFlags []string
4547 // Reports which should be filed against this account, as a result of rule execution.
4648 AccountReports []ModReport
···5254 AccountAcknowledge bool
5355 // Same as "AccountLabels", but at record-level
5456 RecordLabels []string
5757+ // Same as "AccountTags", but at record-level
5858+ RecordTags []string
5559 // Same as "AccountFlags", but at record-level
5660 RecordFlags []string
5761 // Same as "AccountReports", but at record-level
···102106 e.AccountLabels = append(e.AccountLabels, val)
103107}
104108109109+// Enqueues the provided label (string value) to be added to the account at the end of rule processing.
110110+func (e *Effects) AddAccountTag(val string) {
111111+ e.mu.Lock()
112112+ defer e.mu.Unlock()
113113+ for _, v := range e.AccountTags {
114114+ if v == val {
115115+ return
116116+ }
117117+ }
118118+ e.AccountTags = append(e.AccountTags, val)
119119+}
120120+105121// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
106122func (e *Effects) AddAccountFlag(val string) {
107123 e.mu.Lock()
···154170 }
155171 }
156172 e.RecordLabels = append(e.RecordLabels, val)
173173+}
174174+175175+// Enqueues the provided tag (string value) to be added to the record at the end of rule processing.
176176+func (e *Effects) AddRecordTag(val string) {
177177+ e.mu.Lock()
178178+ defer e.mu.Unlock()
179179+ for _, v := range e.RecordTags {
180180+ if v == val {
181181+ return
182182+ }
183183+ }
184184+ e.RecordTags = append(e.RecordTags, val)
157185}
158186159187// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
+5
automod/engine/metrics.go
···2525 Help: "Number of new labels persisted",
2626}, []string{"type", "val"})
27272828+var actionNewTagCount = promauto.NewCounterVec(prometheus.CounterOpts{
2929+ Name: "automod_new_action_tags",
3030+ Help: "Number of new tags persisted",
3131+}, []string{"type", "val"})
3232+2833var actionNewFlagCount = promauto.NewCounterVec(prometheus.CounterOpts{
2934 Name: "automod_new_action_flags",
3035 Help: "Number of new flags persisted",
+69-9
automod/engine/persist.go
···3232 return nil
3333}
34343535-// Persists account-level moderation actions: new labels, new flags, new takedowns, and reports.
3535+// Persists account-level moderation actions: new labels, new tags, new flags, new takedowns, and reports.
3636//
3737// If necessary, will "purge" identity and account caches, so that state updates will be picked up for subsequent events.
3838//
···42424343 // de-dupe actions
4444 newLabels := dedupeLabelActions(c.effects.AccountLabels, c.Account.AccountLabels, c.Account.AccountNegatedLabels)
4545+ existingTags := []string{}
4646+ if c.Account.Private != nil {
4747+ existingTags = c.Account.Private.AccountTags
4848+ }
4949+ newTags := dedupeTagActions(c.effects.AccountTags, existingTags)
4550 newFlags := dedupeFlagActions(c.effects.AccountFlags, c.Account.AccountFlags)
46514752 // don't report the same account multiple times on the same day for the same reason. this is a quick check; we also query the mod service API just before creating the report.
···7883 }
7984 }
80858181- anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0
8686+ anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
8287 if anyModActions && eng.Notifier != nil {
8388 for _, srv := range dedupeStrings(c.effects.NotifyServices) {
8489 if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil {
···107112 xrpcc := eng.OzoneClient
108113109114 if len(newLabels) > 0 {
110110- c.Logger.Info("labeling record", "newLabels", newLabels)
115115+ c.Logger.Info("labeling account", "newLabels", newLabels)
111116 for _, val := range newLabels {
112117 // note: WithLabelValues is a prometheus label, not an atproto label
113118 actionNewLabelCount.WithLabelValues("account", val).Inc()
···133138 }
134139 }
135140141141+ if len(newTags) > 0 {
142142+ c.Logger.Info("tagging account", "newTags", newTags)
143143+ for _, val := range newTags {
144144+ // note: WithLabelValues is a prometheus label, not an atproto label
145145+ actionNewTagCount.WithLabelValues("account", val).Inc()
146146+ }
147147+ comment := "[automod]: auto-tagging account"
148148+ _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{
149149+ CreatedBy: xrpcc.Auth.Did,
150150+ Event: &toolsozone.ModerationEmitEvent_Input_Event{
151151+ ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{
152152+ Add: newTags,
153153+ Remove: []string{},
154154+ Comment: &comment,
155155+ },
156156+ },
157157+ Subject: &toolsozone.ModerationEmitEvent_Input_Subject{
158158+ AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{
159159+ Did: c.Account.Identity.DID.String(),
160160+ },
161161+ },
162162+ })
163163+ if err != nil {
164164+ c.Logger.Error("failed to create account tags", "err", err)
165165+ }
166166+ }
167167+136168 // reports are additionally de-duped when persisting the action, so track with a flag
137169 createdReports := false
138170 for _, mr := range newReports {
···214246 }
215247 }
216248217217- needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || createdReports
249249+ needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || createdReports
218250 if needCachePurge {
219251 return eng.PurgeAccountCaches(ctx, c.Account.Identity.DID)
220252 }
···222254 return nil
223255}
224256225225-// Persists some record-level state: labels, takedowns, reports.
257257+// Persists some record-level state: labels, tags, takedowns, reports.
226258//
227259// NOTE: this method currently does *not* persist record-level flags to any storage, and does not de-dupe most actions, on the assumption that the record is new (from firehose) and has no existing mod state.
228260func (eng *Engine) persistRecordModActions(c *RecordContext) error {
···233265234266 atURI := c.RecordOp.ATURI().String()
235267 newLabels := dedupeStrings(c.effects.RecordLabels)
236236- if len(newLabels) > 0 && eng.OzoneClient != nil {
268268+ newTags := dedupeStrings(c.effects.RecordTags)
269269+ if (len(newLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
270270+ // fetch existing record labels, tags, etc
237271 rv, err := toolsozone.ModerationGetRecord(ctx, eng.OzoneClient, c.RecordOp.CID.String(), c.RecordOp.ATURI().String())
238272 if err != nil {
239273 // NOTE: there is a frequent 4xx error here from Ozone because this record has not been indexed yet
···250284 }
251285 existingLabels = dedupeStrings(existingLabels)
252286 negLabels = dedupeStrings(negLabels)
253253- // fetch existing record labels
254287 newLabels = dedupeLabelActions(newLabels, existingLabels, negLabels)
288288+ newTags = dedupeTagActions(newTags, rv.Moderation.SubjectStatus.Tags)
255289 }
256290 }
291291+257292 newFlags := dedupeStrings(c.effects.RecordFlags)
258293 if len(newFlags) > 0 {
259294 // fetch existing flags, and de-dupe
···278313 return fmt.Errorf("failed to circuit break takedowns: %w", err)
279314 }
280315281281- if newTakedown || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
316316+ if newTakedown || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
282317 if eng.Notifier != nil {
283318 for _, srv := range dedupeStrings(c.effects.NotifyServices) {
284319 if err := eng.Notifier.SendRecord(ctx, srv, c); err != nil {
···298333 }
299334300335 // exit early
301301- if !newTakedown && len(newLabels) == 0 && len(newReports) == 0 {
336336+ if !newTakedown && len(newLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
302337 return nil
303338 }
304339···340375 })
341376 if err != nil {
342377 c.Logger.Error("failed to create record label", "err", err)
378378+ }
379379+ }
380380+381381+ if len(newTags) > 0 {
382382+ c.Logger.Info("tagging record", "newTags", newTags)
383383+ for _, val := range newTags {
384384+ // note: WithLabelValues is a prometheus label, not an atproto label
385385+ actionNewTagCount.WithLabelValues("record", val).Inc()
386386+ }
387387+ comment := "[automod]: auto-tagging record"
388388+ _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{
389389+ CreatedBy: xrpcc.Auth.Did,
390390+ Event: &toolsozone.ModerationEmitEvent_Input_Event{
391391+ ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{
392392+ Add: newLabels,
393393+ Remove: []string{},
394394+ Comment: &comment,
395395+ },
396396+ },
397397+ Subject: &toolsozone.ModerationEmitEvent_Input_Subject{
398398+ RepoStrongRef: &strongRef,
399399+ },
400400+ })
401401+ if err != nil {
402402+ c.Logger.Error("failed to create record tag", "err", err)
343403 }
344404 }
345405
+17
automod/engine/persisthelpers.go
···3535 return newLabels
3636}
37373838+func dedupeTagActions(tags, existing []string) []string {
3939+ newTags := []string{}
4040+ for _, val := range dedupeStrings(tags) {
4141+ exists := false
4242+ for _, e := range existing {
4343+ if val == e {
4444+ exists = true
4545+ break
4646+ }
4747+ }
4848+ if !exists {
4949+ newTags = append(newTags, val)
5050+ }
5151+ }
5252+ return newTags
5353+}
5454+3855func dedupeFlagActions(flags, existing []string) []string {
3956 newFlags := []string{}
4057 for _, val := range dedupeStrings(flags) {