@atproto/ozone 0.1.165 → 0.1.167

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 (65) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/api/moderation/emitEvent.d.ts.map +1 -1
  3. package/dist/api/moderation/emitEvent.js +8 -0
  4. package/dist/api/moderation/emitEvent.js.map +1 -1
  5. package/dist/api/util.d.ts +1 -1
  6. package/dist/api/util.d.ts.map +1 -1
  7. package/dist/api/util.js +1 -0
  8. package/dist/api/util.js.map +1 -1
  9. package/dist/db/schema/moderation_event.d.ts +1 -1
  10. package/dist/db/schema/moderation_event.d.ts.map +1 -1
  11. package/dist/db/schema/moderation_event.js.map +1 -1
  12. package/dist/lexicon/lexicons.d.ts +70 -6
  13. package/dist/lexicon/lexicons.d.ts.map +1 -1
  14. package/dist/lexicon/lexicons.js +35 -0
  15. package/dist/lexicon/lexicons.js.map +1 -1
  16. package/dist/lexicon/types/app/bsky/actor/getSuggestions.d.ts +3 -1
  17. package/dist/lexicon/types/app/bsky/actor/getSuggestions.d.ts.map +1 -1
  18. package/dist/lexicon/types/app/bsky/actor/getSuggestions.js.map +1 -1
  19. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.d.ts +3 -1
  20. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.d.ts.map +1 -1
  21. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.js.map +1 -1
  22. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts +3 -1
  23. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts.map +1 -1
  24. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.js.map +1 -1
  25. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsers.d.ts +3 -1
  26. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsers.d.ts.map +1 -1
  27. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
  28. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.d.ts +3 -1
  29. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.d.ts.map +1 -1
  30. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.js.map +1 -1
  31. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +10 -2
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  33. package/dist/lexicon/types/tools/ozone/moderation/defs.js +9 -0
  34. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  35. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  36. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  37. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  38. package/dist/mod-service/index.d.ts +3 -2
  39. package/dist/mod-service/index.d.ts.map +1 -1
  40. package/dist/mod-service/index.js +11 -0
  41. package/dist/mod-service/index.js.map +1 -1
  42. package/dist/mod-service/status.d.ts.map +1 -1
  43. package/dist/mod-service/status.js +7 -0
  44. package/dist/mod-service/status.js.map +1 -1
  45. package/package.json +5 -5
  46. package/src/api/moderation/emitEvent.ts +17 -1
  47. package/src/api/util.ts +1 -0
  48. package/src/db/schema/moderation_event.ts +1 -0
  49. package/src/lexicon/lexicons.ts +36 -0
  50. package/src/lexicon/types/app/bsky/actor/getSuggestions.ts +3 -1
  51. package/src/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.ts +3 -1
  52. package/src/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.ts +3 -1
  53. package/src/lexicon/types/app/bsky/unspecced/getSuggestedUsers.ts +3 -1
  54. package/src/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.ts +3 -1
  55. package/src/lexicon/types/tools/ozone/moderation/defs.ts +19 -0
  56. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +1 -0
  57. package/src/mod-service/index.ts +14 -0
  58. package/src/mod-service/status.ts +8 -0
  59. package/tests/__snapshots__/get-starter-pack.test.ts.snap +9 -9
  60. package/tests/__snapshots__/get-subjects.test.ts.snap +2 -2
  61. package/tests/__snapshots__/team.test.ts.snap +4 -4
  62. package/tests/__snapshots__/verification-listener.test.ts.snap +2 -2
  63. package/tests/__snapshots__/verification.test.ts.snap +3 -3
  64. package/tests/_util.ts +6 -4
  65. package/tests/age-assurance.test.ts +134 -7
@@ -19,7 +19,7 @@ Array [
19
19
  "lists": 0,
20
20
  "starterPacks": 0,
21
21
  },
22
- "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg",
22
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)",
23
23
  "createdAt": "1970-01-01T00:00:00.000Z",
24
24
  "description": "hi im bob label_me",
25
25
  "did": "user(0)",
@@ -76,7 +76,7 @@ Array [
76
76
  "lists": 0,
77
77
  "starterPacks": 0,
78
78
  },
79
- "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg",
79
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)",
80
80
  "createdAt": "1970-01-01T00:00:00.000Z",
81
81
  "description": "its me!",
82
82
  "did": "user(1)",
@@ -18,7 +18,7 @@ Object {
18
18
  "lists": 0,
19
19
  "starterPacks": 0,
20
20
  },
21
- "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg",
21
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)",
22
22
  "createdAt": "1970-01-01T00:00:00.000Z",
23
23
  "description": "its me!",
24
24
  "did": "user(0)",
@@ -104,7 +104,7 @@ Object {
104
104
  "lists": 0,
105
105
  "starterPacks": 0,
106
106
  },
107
- "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg",
107
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)",
108
108
  "createdAt": "1970-01-01T00:00:00.000Z",
109
109
  "description": "hi im bob label_me",
110
110
  "did": "user(1)",
@@ -182,7 +182,7 @@ Object {
182
182
  "lists": 0,
183
183
  "starterPacks": 0,
184
184
  },
185
- "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg",
185
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)",
186
186
  "createdAt": "1970-01-01T00:00:00.000Z",
187
187
  "description": "its me!",
188
188
  "did": "user(0)",
package/tests/_util.ts CHANGED
@@ -64,14 +64,16 @@ export const forSnapshot = (obj: unknown) => {
64
64
  if (str.match(/^\d+::bafy/)) {
65
65
  return constantKeysetCursor
66
66
  }
67
- if (str.match(/\/img\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) {
68
- // Match image urls
67
+ if (str.match(/\/img\/[^/]+\/.+\/did:plc:[^/]+\/[^/@]+(?:@[\w]+)?$/)) {
68
+ // Match image urls, stripping optional format suffix (e.g. @webp) for stable snapshots
69
69
  const match = str.match(
70
- /\/img\/[^/]+\/.+\/(did:plc:[^/]+)\/([^/]+)@[\w]+$/,
70
+ /\/img\/[^/]+\/.+\/(did:plc:[^/]+)\/([^/@]+)(?:@[\w]+)?$/,
71
71
  )
72
72
  if (!match) return str
73
73
  const [, did, cid] = match
74
- return str.replace(did, take(users, did)).replace(cid, take(cids, cid))
74
+ return str
75
+ .replace(did, take(users, did))
76
+ .replace(new RegExp(`${cid}(?:@\\w+)?`), take(cids, cid))
75
77
  }
76
78
  // decent check for 64-byte base64 encoded signatures
77
79
  if (str.length === 86 && !str.includes(' ')) {
@@ -107,7 +107,7 @@ describe('age assurance events', () => {
107
107
  expect(event.event.$type).toBe(
108
108
  'tools.ozone.moderation.defs#ageAssuranceEvent',
109
109
  )
110
- expect((event.event as any).status).toBe('pending')
110
+ expect(event.event['status']).toBe('pending')
111
111
  })
112
112
 
113
113
  expect(unknownEvents.length).toBeGreaterThan(0)
@@ -115,7 +115,7 @@ describe('age assurance events', () => {
115
115
  expect(event.event.$type).toBe(
116
116
  'tools.ozone.moderation.defs#ageAssuranceEvent',
117
117
  )
118
- expect((event.event as any).status).toBe('assured')
118
+ expect(event.event['status']).toBe('assured')
119
119
  })
120
120
  } catch (error) {
121
121
  console.error('Error querying events:', error)
@@ -132,7 +132,80 @@ describe('age assurance events', () => {
132
132
  })
133
133
  })
134
134
 
135
- it('age assurance event fails for record subjects', async () => {
135
+ it('purge event removes ageAssuranceEvents but keeps overrides, and resets status', async () => {
136
+ const danSubject = {
137
+ $type: 'com.atproto.admin.defs#repoRef',
138
+ did: sc.dids.dan,
139
+ }
140
+
141
+ await modClient.emitEvent({
142
+ subject: danSubject,
143
+ event: {
144
+ $type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
145
+ status: 'pending',
146
+ createdAt: new Date().toISOString(),
147
+ attemptId: 'attempt-dan-1',
148
+ },
149
+ })
150
+ await modClient.emitEvent({
151
+ subject: danSubject,
152
+ event: {
153
+ $type: 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent',
154
+ status: 'assured',
155
+ comment: 'Admin verified dan',
156
+ },
157
+ })
158
+
159
+ const { subjectStatuses: beforePurge } = await modClient.queryStatuses({
160
+ subject: sc.dids.dan,
161
+ })
162
+ expect(beforePurge[0].ageAssuranceState).toBe('assured')
163
+ expect(beforePurge[0].ageAssuranceUpdatedBy).toBe('admin')
164
+
165
+ const { events: beforePurgeEvents } = await modClient.queryEvents({
166
+ subject: sc.dids.dan,
167
+ types: [
168
+ 'tools.ozone.moderation.defs#ageAssuranceEvent',
169
+ 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent',
170
+ ],
171
+ })
172
+ expect(beforePurgeEvents.length).toBe(2)
173
+
174
+ // Emit the purge event
175
+ const purgeEvent = await modClient.emitEvent({
176
+ subject: danSubject,
177
+ event: {
178
+ $type: 'tools.ozone.moderation.defs#ageAssurancePurgeEvent',
179
+ comment: 'Purging age assurance data per user request',
180
+ },
181
+ })
182
+ expect(purgeEvent.event.$type).toBe(
183
+ 'tools.ozone.moderation.defs#ageAssurancePurgeEvent',
184
+ )
185
+
186
+ // ageAssuranceEvent rows should be deleted
187
+ const { events: aaEventsAfterPurge } = await modClient.queryEvents({
188
+ subject: sc.dids.dan,
189
+ types: ['tools.ozone.moderation.defs#ageAssuranceEvent'],
190
+ })
191
+ expect(aaEventsAfterPurge.length).toBe(0)
192
+
193
+ // ageAssuranceOverrideEvent rows should be preserved
194
+ const { events: overrideEventsAfterPurge } = await modClient.queryEvents({
195
+ subject: sc.dids.dan,
196
+ types: ['tools.ozone.moderation.defs#ageAssuranceOverrideEvent'],
197
+ })
198
+ expect(overrideEventsAfterPurge.length).toBe(1)
199
+
200
+ // Status should be reset to unknown and updatedBy set to the purging moderator's DID
201
+ const { subjectStatuses: afterPurge } = await modClient.queryStatuses({
202
+ subject: sc.dids.dan,
203
+ })
204
+ expect(afterPurge[0].ageAssuranceState).toBe('unknown')
205
+ expect(afterPurge[0].ageAssuranceUpdatedBy).toBeFalsy()
206
+ })
207
+
208
+ it('purge event fails for record subjects', async () => {
136
209
  const postSubject = {
137
210
  $type: 'com.atproto.repo.strongRef',
138
211
  uri: sc.posts[sc.dids.alice][0].ref.uriStr,
@@ -143,15 +216,69 @@ describe('age assurance events', () => {
143
216
  modClient.emitEvent({
144
217
  subject: postSubject,
145
218
  event: {
146
- $type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
147
- status: 'pending',
148
- createdAt: new Date().toISOString(),
149
- attemptId: 'attempt-123',
219
+ $type: 'tools.ozone.moderation.defs#ageAssurancePurgeEvent',
220
+ comment: 'Should fail',
150
221
  },
151
222
  }),
152
223
  ).rejects.toThrow('Invalid subject type')
153
224
  })
154
225
 
226
+ it('purge event only removes ageAssuranceEvents, not overrides or other events', async () => {
227
+ const carolSubject = {
228
+ $type: 'com.atproto.admin.defs#repoRef',
229
+ did: sc.dids.carol,
230
+ }
231
+
232
+ // Add a non-AA event that should survive the purge
233
+ const commentEvent = await modClient.emitEvent({
234
+ subject: carolSubject,
235
+ event: {
236
+ $type: 'tools.ozone.moderation.defs#modEventComment',
237
+ comment: 'A non-AA comment that should survive purge',
238
+ },
239
+ })
240
+
241
+ // Add an ageAssuranceEvent that should be removed
242
+ await modClient.emitEvent({
243
+ subject: carolSubject,
244
+ event: {
245
+ $type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
246
+ status: 'pending',
247
+ createdAt: new Date().toISOString(),
248
+ attemptId: 'attempt-carol-purge-1',
249
+ },
250
+ })
251
+
252
+ // Add an ageAssuranceOverrideEvent that should survive the purge
253
+ const overrideEvent = await modClient.emitEvent({
254
+ subject: carolSubject,
255
+ event: {
256
+ $type: 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent',
257
+ status: 'assured',
258
+ comment: 'Override that should survive purge',
259
+ },
260
+ })
261
+
262
+ await modClient.emitEvent({
263
+ subject: carolSubject,
264
+ event: {
265
+ $type: 'tools.ozone.moderation.defs#ageAssurancePurgeEvent',
266
+ comment: 'Purging carol age assurance data',
267
+ },
268
+ })
269
+
270
+ const { events: afterPurge } = await modClient.queryEvents({
271
+ subject: sc.dids.carol,
272
+ })
273
+
274
+ const aaEventsAfterPurge = afterPurge.filter(
275
+ (e) => e.event.$type === 'tools.ozone.moderation.defs#ageAssuranceEvent',
276
+ )
277
+ expect(aaEventsAfterPurge.length).toBe(0)
278
+ expect(afterPurge.some((e) => e.id === overrideEvent.id)).toBe(true)
279
+ expect(afterPurge.some((e) => e.id === commentEvent.id)).toBe(true)
280
+ })
281
+
155
282
  it('admin override behavior for age assurance states', async () => {
156
283
  const carolSubject = {
157
284
  $type: 'com.atproto.admin.defs#repoRef',