@atproto/ozone 0.1.63 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api/moderation/queryEvents.d.ts.map +1 -1
  3. package/dist/api/moderation/queryEvents.js +2 -1
  4. package/dist/api/moderation/queryEvents.js.map +1 -1
  5. package/dist/lexicon/lexicons.d.ts +30 -0
  6. package/dist/lexicon/lexicons.d.ts.map +1 -1
  7. package/dist/lexicon/lexicons.js +15 -0
  8. package/dist/lexicon/lexicons.js.map +1 -1
  9. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
  10. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  11. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  12. package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +1 -0
  13. package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
  14. package/dist/mod-service/index.d.ts +1 -0
  15. package/dist/mod-service/index.d.ts.map +1 -1
  16. package/dist/mod-service/index.js +12 -1
  17. package/dist/mod-service/index.js.map +1 -1
  18. package/dist/mod-service/views.d.ts.map +1 -1
  19. package/dist/mod-service/views.js +8 -0
  20. package/dist/mod-service/views.js.map +1 -1
  21. package/dist/setting/constants.d.ts +1 -0
  22. package/dist/setting/constants.d.ts.map +1 -1
  23. package/dist/setting/constants.js +2 -1
  24. package/dist/setting/constants.js.map +1 -1
  25. package/dist/setting/validators.d.ts.map +1 -1
  26. package/dist/setting/validators.js +19 -0
  27. package/dist/setting/validators.js.map +1 -1
  28. package/package.json +3 -3
  29. package/src/api/moderation/queryEvents.ts +2 -0
  30. package/src/lexicon/lexicons.ts +17 -0
  31. package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
  32. package/src/lexicon/types/tools/ozone/moderation/queryEvents.ts +1 -0
  33. package/src/mod-service/index.ts +14 -0
  34. package/src/mod-service/views.ts +11 -0
  35. package/src/setting/constants.ts +1 -0
  36. package/src/setting/validators.ts +28 -1
  37. package/tests/takedown.test.ts +64 -0
  38. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -25,6 +25,7 @@ export default function (server: Server, ctx: AppContext) {
25
25
  reportTypes,
26
26
  collections = [],
27
27
  subjectType,
28
+ policies,
28
29
  } = params
29
30
  const db = ctx.db
30
31
  const modService = ctx.modService(db)
@@ -47,6 +48,7 @@ export default function (server: Server, ctx: AppContext) {
47
48
  reportTypes,
48
49
  collections,
49
50
  subjectType,
51
+ policies,
50
52
  })
51
53
  return {
52
54
  encoding: 'application/json',
@@ -11309,6 +11309,15 @@ export const schemaDict = {
11309
11309
  description:
11310
11310
  'If true, all other reports on content authored by this account will be resolved (acknowledged).',
11311
11311
  },
11312
+ policies: {
11313
+ type: 'array',
11314
+ maxLength: 5,
11315
+ items: {
11316
+ type: 'string',
11317
+ },
11318
+ description:
11319
+ 'Names/Keywords of the policies that drove the decision.',
11320
+ },
11312
11321
  },
11313
11322
  },
11314
11323
  modEventReverseTakedown: {
@@ -12345,6 +12354,14 @@ export const schemaDict = {
12345
12354
  type: 'string',
12346
12355
  },
12347
12356
  },
12357
+ policies: {
12358
+ type: 'array',
12359
+ items: {
12360
+ type: 'string',
12361
+ description:
12362
+ 'If specified, only events where the policy matches the given policy are returned',
12363
+ },
12364
+ },
12348
12365
  cursor: {
12349
12366
  type: 'string',
12350
12367
  },
@@ -174,6 +174,8 @@ export interface ModEventTakedown {
174
174
  durationInHours?: number
175
175
  /** If true, all other reports on content authored by this account will be resolved (acknowledged). */
176
176
  acknowledgeAccountSubjects?: boolean
177
+ /** Names/Keywords of the policies that drove the decision. */
178
+ policies?: string[]
177
179
  [k: string]: unknown
178
180
  }
179
181
 
@@ -40,6 +40,7 @@ export interface QueryParams {
40
40
  /** If specified, only events where all of these tags were removed are returned */
41
41
  removedTags?: string[]
42
42
  reportTypes?: string[]
43
+ policies?: string[]
43
44
  cursor?: string
44
45
  }
45
46
 
@@ -152,6 +152,7 @@ export class ModerationService {
152
152
  reportTypes?: string[]
153
153
  collections: string[]
154
154
  subjectType?: string
155
+ policies?: string[]
155
156
  }): Promise<{ cursor?: string; events: ModerationEventRow[] }> {
156
157
  const {
157
158
  subject,
@@ -172,6 +173,7 @@ export class ModerationService {
172
173
  reportTypes,
173
174
  collections,
174
175
  subjectType,
176
+ policies,
175
177
  } = opts
176
178
  const { ref } = this.db.db.dynamic
177
179
  let builder = this.db.db.selectFrom('moderation_event').selectAll()
@@ -264,6 +266,14 @@ export class ModerationService {
264
266
  if (reportTypes?.length) {
265
267
  builder = builder.where(sql`meta->>'reportType'`, 'in', reportTypes)
266
268
  }
269
+ if (policies?.length) {
270
+ builder = builder.where((qb) => {
271
+ policies.forEach((policy) => {
272
+ qb = qb.orWhere(sql`meta->>'policies'`, 'ilike', `%${policy}%`)
273
+ })
274
+ return qb
275
+ })
276
+ }
267
277
 
268
278
  const keyset = new TimeIdKeyset(
269
279
  ref(`moderation_event.createdAt`),
@@ -435,6 +445,10 @@ export class ModerationService {
435
445
  meta.acknowledgeAccountSubjects = true
436
446
  }
437
447
 
448
+ if (isModEventTakedown(event) && event.policies?.length) {
449
+ meta.policies = event.policies.join(',')
450
+ }
451
+
438
452
  // Keep trace of reports that came in while the reporter was in muted stated
439
453
  if (isModEventReport(event)) {
440
454
  const isReportingMuted = await this.isReportingMutedForSubject(createdBy)
@@ -137,6 +137,17 @@ export class ModerationViews {
137
137
  }
138
138
  }
139
139
 
140
+ if (
141
+ event.action === 'tools.ozone.moderation.defs#modEventTakedown' &&
142
+ typeof event.meta?.policies === 'string' &&
143
+ event.meta.policies.length > 0
144
+ ) {
145
+ eventView.event = {
146
+ ...eventView.event,
147
+ policies: event.meta.policies.split(','),
148
+ }
149
+ }
150
+
140
151
  if (event.action === 'tools.ozone.moderation.defs#modEventLabel') {
141
152
  eventView.event = {
142
153
  ...eventView.event,
@@ -1 +1,2 @@
1
1
  export const ProtectedTagSettingKey = 'tools.ozone.setting.protectedTags'
2
+ export const PolicyListSettingKey = 'tools.ozone.setting.policyList'
@@ -1,6 +1,6 @@
1
1
  import { Selectable } from 'kysely'
2
2
  import { Setting } from '../db/schema/setting'
3
- import { ProtectedTagSettingKey } from './constants'
3
+ import { PolicyListSettingKey, ProtectedTagSettingKey } from './constants'
4
4
  import { InvalidRequestError } from '@atproto/xrpc-server'
5
5
 
6
6
  export const settingValidators = new Map<
@@ -58,4 +58,31 @@ export const settingValidators = new Map<
58
58
  }
59
59
  },
60
60
  ],
61
+ [
62
+ PolicyListSettingKey,
63
+ async (setting: Partial<Selectable<Setting>>) => {
64
+ if (setting.managerRole !== 'tools.ozone.team.defs#roleAdmin') {
65
+ throw new InvalidRequestError(
66
+ 'Only admins should be able to manage policy list',
67
+ )
68
+ }
69
+
70
+ if (typeof setting.value !== 'object') {
71
+ throw new InvalidRequestError('Invalid value')
72
+ }
73
+ for (const [key, val] of Object.entries(setting.value)) {
74
+ if (!val || typeof val !== 'object') {
75
+ throw new InvalidRequestError(
76
+ `Invalid configuration for policy ${key}`,
77
+ )
78
+ }
79
+
80
+ if (!val['name'] || !val['description']) {
81
+ throw new InvalidRequestError(
82
+ `Must define a name and description for policy ${key}`,
83
+ )
84
+ }
85
+ }
86
+ },
87
+ ],
61
88
  ])
@@ -0,0 +1,64 @@
1
+ import {
2
+ TestNetwork,
3
+ TestOzone,
4
+ SeedClient,
5
+ basicSeed,
6
+ ModeratorClient,
7
+ } from '@atproto/dev-env'
8
+ import { AtpAgent } from '@atproto/api'
9
+
10
+ describe('moderation', () => {
11
+ let network: TestNetwork
12
+ let ozone: TestOzone
13
+ let agent: AtpAgent
14
+ let bskyAgent: AtpAgent
15
+ let pdsAgent: AtpAgent
16
+ let sc: SeedClient
17
+ let modClient: ModeratorClient
18
+
19
+ const repoSubject = (did: string) => ({
20
+ $type: 'com.atproto.admin.defs#repoRef',
21
+ did,
22
+ })
23
+
24
+ beforeAll(async () => {
25
+ network = await TestNetwork.create({
26
+ dbPostgresSchema: 'ozone_takedown',
27
+ })
28
+ ozone = network.ozone
29
+ agent = network.ozone.getClient()
30
+ bskyAgent = network.bsky.getClient()
31
+ pdsAgent = network.pds.getClient()
32
+ sc = network.getSeedClient()
33
+ modClient = network.ozone.getModClient()
34
+ await basicSeed(sc)
35
+ await network.processAll()
36
+ })
37
+
38
+ afterAll(async () => {
39
+ await network.close()
40
+ })
41
+
42
+ it('allows specifying policy for takedown actions.', async () => {
43
+ await modClient.performTakedown({
44
+ subject: repoSubject(sc.dids.bob),
45
+ policies: ['trolling'],
46
+ })
47
+
48
+ // Verify that that the takedown even exposes the policy specified for it
49
+ const { events } = await modClient.queryEvents({
50
+ subject: sc.dids.bob,
51
+ types: ['tools.ozone.moderation.defs#modEventTakedown'],
52
+ })
53
+
54
+ expect(events[0].event.policies?.[0]).toEqual('trolling')
55
+
56
+ // Verify that event stream can be filtered by policy
57
+ const { events: filteredEvents } = await modClient.queryEvents({
58
+ subject: sc.dids.bob,
59
+ policies: ['trolling'],
60
+ })
61
+
62
+ expect(filteredEvents[0].subject.did).toEqual(sc.dids.bob)
63
+ })
64
+ })
@@ -1 +1 @@
1
- {"root":["./tests/3p-labeler.test.ts","./tests/_util.ts","./tests/ack-all-subjects-of-account.test.ts","./tests/blob-divert.test.ts","./tests/communication-templates.test.ts","./tests/content-tagger.test.ts","./tests/db.test.ts","./tests/get-config.test.ts","./tests/get-lists.test.ts","./tests/get-profiles.test.ts","./tests/get-record.test.ts","./tests/get-records.test.ts","./tests/get-repo.test.ts","./tests/get-repos.test.ts","./tests/get-starter-pack.test.ts","./tests/moderation-appeals.test.ts","./tests/moderation-events.test.ts","./tests/moderation-status-tags.test.ts","./tests/moderation-statuses.test.ts","./tests/moderation.test.ts","./tests/protected-tags.test.ts","./tests/query-labels.test.ts","./tests/record-and-account-events.test.ts","./tests/repo-search.test.ts","./tests/report-muting.test.ts","./tests/sequencer.test.ts","./tests/server.test.ts","./tests/sets.test.ts","./tests/settings.test.ts","./tests/team.test.ts"],"version":"5.6.3"}
1
+ {"root":["./tests/3p-labeler.test.ts","./tests/_util.ts","./tests/ack-all-subjects-of-account.test.ts","./tests/blob-divert.test.ts","./tests/communication-templates.test.ts","./tests/content-tagger.test.ts","./tests/db.test.ts","./tests/get-config.test.ts","./tests/get-lists.test.ts","./tests/get-profiles.test.ts","./tests/get-record.test.ts","./tests/get-records.test.ts","./tests/get-repo.test.ts","./tests/get-repos.test.ts","./tests/get-starter-pack.test.ts","./tests/moderation-appeals.test.ts","./tests/moderation-events.test.ts","./tests/moderation-status-tags.test.ts","./tests/moderation-statuses.test.ts","./tests/moderation.test.ts","./tests/protected-tags.test.ts","./tests/query-labels.test.ts","./tests/record-and-account-events.test.ts","./tests/repo-search.test.ts","./tests/report-muting.test.ts","./tests/sequencer.test.ts","./tests/server.test.ts","./tests/sets.test.ts","./tests/settings.test.ts","./tests/takedown.test.ts","./tests/team.test.ts"],"version":"5.6.3"}