@atproto/ozone 0.1.62 → 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 (42) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/api/health.d.ts.map +1 -1
  3. package/dist/api/health.js +0 -24
  4. package/dist/api/health.js.map +1 -1
  5. package/dist/api/moderation/queryEvents.d.ts.map +1 -1
  6. package/dist/api/moderation/queryEvents.js +2 -1
  7. package/dist/api/moderation/queryEvents.js.map +1 -1
  8. package/dist/lexicon/lexicons.d.ts +32 -2
  9. package/dist/lexicon/lexicons.d.ts.map +1 -1
  10. package/dist/lexicon/lexicons.js +16 -1
  11. package/dist/lexicon/lexicons.js.map +1 -1
  12. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
  13. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  14. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  15. package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +1 -0
  16. package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
  17. package/dist/mod-service/index.d.ts +1 -0
  18. package/dist/mod-service/index.d.ts.map +1 -1
  19. package/dist/mod-service/index.js +12 -1
  20. package/dist/mod-service/index.js.map +1 -1
  21. package/dist/mod-service/views.d.ts.map +1 -1
  22. package/dist/mod-service/views.js +8 -0
  23. package/dist/mod-service/views.js.map +1 -1
  24. package/dist/setting/constants.d.ts +1 -0
  25. package/dist/setting/constants.d.ts.map +1 -1
  26. package/dist/setting/constants.js +2 -1
  27. package/dist/setting/constants.js.map +1 -1
  28. package/dist/setting/validators.d.ts.map +1 -1
  29. package/dist/setting/validators.js +19 -0
  30. package/dist/setting/validators.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/api/health.ts +0 -25
  33. package/src/api/moderation/queryEvents.ts +2 -0
  34. package/src/lexicon/lexicons.ts +18 -1
  35. package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
  36. package/src/lexicon/types/tools/ozone/moderation/queryEvents.ts +1 -0
  37. package/src/mod-service/index.ts +14 -0
  38. package/src/mod-service/views.ts +11 -0
  39. package/src/setting/constants.ts +1 -0
  40. package/src/setting/validators.ts +28 -1
  41. package/tests/takedown.test.ts +64 -0
  42. package/tsconfig.tests.tsbuildinfo +1 -1
package/src/api/health.ts CHANGED
@@ -5,31 +5,6 @@ import AppContext from '../context'
5
5
  export const createRouter = (ctx: AppContext): express.Router => {
6
6
  const router = express.Router()
7
7
 
8
- router.get('/', function (req, res) {
9
- res.type('text/plain')
10
- res.send(`
11
- ,o888888o. 8888888888',8888' ,o888888o. b. 8 8 8888888888
12
- . 8888 '88. ,8',8888'. 8888 '88. 888o. 8 8 8888
13
- ,8 8888 '8b ,8',8888',8 8888 '8b Y88888o. 8 8 8888
14
- 88 8888 '8b ,8',8888' 88 8888 '8b .'Y888888o. 8 8 8888
15
- 88 8888 88 ,8',8888' 88 8888 88 8o. 'Y888888o. 8 8 888888888888
16
- 88 8888 88 ,8',8888' 88 8888 88 8'Y8o. 'Y88888o8 8 8888
17
- 88 8888 ,8P ,8',8888' 88 8888 ,8P 8 'Y8o. 'Y8888 8 8888
18
- '8 8888 ,8P ,8',8888' '8 8888 ,8P 8 'Y8o. 'Y8 8 8888
19
- ' 8888 ,88' ,8',8888' ' 8888 ,88' 8 'Y8o.' 8 8888
20
- '8888888P' ,8',8888888888888 '8888888P' 8 'Yo 8 888888888888
21
-
22
-
23
- This is an AT Protocol Moderation Service API Server.
24
-
25
- Most API routes are under /xrpc/
26
-
27
- Code: https://github.com/bluesky-social/atproto
28
- Self-Host: https://github.com/bluesky-social/ozone
29
- Protocol: https://atproto.com
30
- `)
31
- })
32
-
33
8
  router.get('/robots.txt', function (req, res) {
34
9
  res.type('text/plain')
35
10
  res.send(
@@ -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
  },
@@ -12515,9 +12532,9 @@ export const schemaDict = {
12515
12532
  },
12516
12533
  tags: {
12517
12534
  type: 'array',
12535
+ maxLength: 25,
12518
12536
  items: {
12519
12537
  type: 'string',
12520
- maxLength: 25,
12521
12538
  description:
12522
12539
  'Items in this array are applied with OR filters. To apply AND filter, put all tags in the same string and separate using && characters',
12523
12540
  },
@@ -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"}