···2828 CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
2929 // Label values which should be applied to the overall account, as a result of rule execution.
3030 AccountLabels []string
3131+ // Label values which should be removed from the overall account, as a result of rule execution.
3232+ RemovedAccountLabels []string
3133 // Moderation tags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
3234 AccountTags []string
3335 // automod flags (metadata) which should be applied to the account as a result of rule execution.
···4244 AccountAcknowledge bool
4345 // Same as "AccountLabels", but at record-level
4446 RecordLabels []string
4747+ // Same as "RemovedRecordLabels", but at record-level
4848+ RemovedRecordLabels []string
4549 // Same as "AccountTags", but at record-level
4650 RecordTags []string
4751 // Same as "AccountFlags", but at record-level
···98102 e.AccountLabels = append(e.AccountLabels, val)
99103}
100104105105+// Enqueues the provided label (string value) to be removed from the account at the end of rule processing.
106106+func (e *Effects) RemoveAccountLabel(val string) {
107107+ e.mu.Lock()
108108+ defer e.mu.Unlock()
109109+ for _, v := range e.RemovedAccountLabels {
110110+ if v == val {
111111+ return
112112+ }
113113+ }
114114+ e.RemovedAccountLabels = append(e.RemovedAccountLabels, val)
115115+}
116116+101117// Enqueues the provided label (string value) to be added to the account at the end of rule processing.
102118func (e *Effects) AddAccountTag(val string) {
103119 e.mu.Lock()
···162178 }
163179 }
164180 e.RecordLabels = append(e.RecordLabels, val)
181181+}
182182+183183+// Enqueues the provided label (string value) to be removed from the record at the end of rule processing.
184184+func (e *Effects) RemoveRecordLabel(val string) {
185185+ e.mu.Lock()
186186+ defer e.mu.Unlock()
187187+ for _, v := range e.RemovedRecordLabels {
188188+ if v == val {
189189+ return
190190+ }
191191+ }
192192+ e.RemovedRecordLabels = append(e.RemovedRecordLabels, val)
165193}
166194167195// Enqueues the provided tag (string value) to be added to the record at the end of rule processing.
+29-10
automod/engine/persist.go
···6677 comatproto "github.com/bluesky-social/indigo/api/atproto"
88 toolsozone "github.com/bluesky-social/indigo/api/ozone"
99+ "github.com/bluesky-social/indigo/automod/keyword"
910)
10111112func (eng *Engine) persistCounters(ctx context.Context, eff *Effects) error {
···42434344 // de-dupe actions
4445 newLabels := dedupeLabelActions(c.effects.AccountLabels, c.Account.AccountLabels, c.Account.AccountNegatedLabels)
4646+ rmdLabels := []string{}
4747+ for _, lbl := range dedupeStrings(c.effects.RemovedAccountLabels) {
4848+ // we don't need to try and remove labels whenever they are either _not_ already in the account labels, _or_ if they are
4949+ // being applied by some other rule before persisting
5050+ if !keyword.TokenInSet(lbl, c.Account.AccountLabels) || keyword.TokenInSet(lbl, c.effects.AccountLabels) {
5151+ continue
5252+ }
5353+ rmdLabels = append(rmdLabels, lbl)
5454+ }
4555 existingTags := []string{}
4656 if c.Account.Private != nil {
4757 existingTags = c.Account.Private.AccountTags
···8393 }
8494 }
85958686- anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
9696+ anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
8797 if anyModActions && eng.Notifier != nil {
8898 for _, srv := range dedupeStrings(c.effects.NotifyServices) {
8999 if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil {
···111121112122 xrpcc := eng.OzoneClient
113123114114- if len(newLabels) > 0 {
115115- c.Logger.Info("labeling account", "newLabels", newLabels)
124124+ if len(newLabels) > 0 || len(rmdLabels) > 0 {
125125+ c.Logger.Info("updating account labels", "newLabels", newLabels, "rmdLabels", rmdLabels)
116126 for _, val := range newLabels {
117127 // note: WithLabelValues is a prometheus label, not an atproto label
118128 actionNewLabelCount.WithLabelValues("account", val).Inc()
···123133 Event: &toolsozone.ModerationEmitEvent_Input_Event{
124134 ModerationDefs_ModEventLabel: &toolsozone.ModerationDefs_ModEventLabel{
125135 CreateLabelVals: newLabels,
126126- NegateLabelVals: []string{},
136136+ NegateLabelVals: rmdLabels,
127137 Comment: &comment,
128138 },
129139 },
···265275266276 atURI := c.RecordOp.ATURI().String()
267277 newLabels := dedupeStrings(c.effects.RecordLabels)
278278+ rmdLabels := []string{}
268279 newTags := dedupeStrings(c.effects.RecordTags)
269280 newEscalation := c.effects.RecordEscalate
270281 newAcknowledge := c.effects.RecordAcknowledge
271282272272- if (newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
283283+ if (newEscalation || newAcknowledge || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
273284 // fetch existing record labels, tags, etc
274285 rv, err := toolsozone.ModerationGetRecord(ctx, eng.OzoneClient, c.RecordOp.CID.String(), c.RecordOp.ATURI().String())
275286 if err != nil {
···288299 existingLabels = dedupeStrings(existingLabels)
289300 negLabels = dedupeStrings(negLabels)
290301 newLabels = dedupeLabelActions(newLabels, existingLabels, negLabels)
302302+ for _, lbl := range dedupeStrings(c.effects.RemovedRecordLabels) {
303303+ // we don't need to try and remove labels whenever they are either _not_ already in the record labels, _or_ if they are
304304+ // being applied by some other rule before persisting
305305+ if !keyword.TokenInSet(lbl, existingLabels) || keyword.TokenInSet(lbl, newLabels) {
306306+ continue
307307+ }
308308+ rmdLabels = append(rmdLabels, lbl)
309309+ }
291310 existingTags := []string{}
292311 hasSubjectStatus := rv.Moderation != nil && rv.Moderation.SubjectStatus != nil
293312 if hasSubjectStatus && rv.Moderation.SubjectStatus.Tags != nil {
···331350 return fmt.Errorf("circuit-breaking acknowledge: %w", err)
332351 }
333352334334- if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
353353+ if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
335354 if eng.Notifier != nil {
336355 for _, srv := range dedupeStrings(c.effects.NotifyServices) {
337356 if err := eng.Notifier.SendRecord(ctx, srv, c); err != nil {
···351370 }
352371353372 // exit early
354354- if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
373373+ if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(rmdLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
355374 return nil
356375 }
357376···371390 }
372391373392 xrpcc := eng.OzoneClient
374374- if len(newLabels) > 0 {
375375- c.Logger.Info("labeling record", "newLabels", newLabels)
393393+ if len(newLabels) > 0 || len(rmdLabels) > 0 {
394394+ c.Logger.Info("updating record labels", "newLabels", newLabels, "rmdLabels", rmdLabels)
376395 for _, val := range newLabels {
377396 // note: WithLabelValues is a prometheus label, not an atproto label
378397 actionNewLabelCount.WithLabelValues("record", val).Inc()
···383402 Event: &toolsozone.ModerationEmitEvent_Input_Event{
384403 ModerationDefs_ModEventLabel: &toolsozone.ModerationDefs_ModEventLabel{
385404 CreateLabelVals: newLabels,
386386- NegateLabelVals: []string{},
405405+ NegateLabelVals: rmdLabels,
387406 Comment: &comment,
388407 },
389408 },