A tool for parsing traffic on the jetstream and applying a moderation workstream based on regexp based rules

Refactor account age check to use a date window

This change updates the account age check to use a date window defined
by the anchor date and max age days. Instead of calculating the age, it
now checks if the account creation date falls within the specified
window. This simplifies the logic and makes it more readable.
Additionally, the test suite has been updated to reflect the new
behavior, and a more descriptive comment has been added.

+99 -138
+12 -13
src/rules/account/age.ts
··· 137 continue; 138 } 139 140 - // Calculate age at anchor date 141 - const anchorDate = new Date(check.anchorDate); 142 - const accountAge = calculateAccountAge(creationDate, anchorDate); 143 144 logger.debug( 145 { 146 process: "ACCOUNT_AGE", 147 replyingDid: context.replyingDid, 148 creationDate: creationDate.toISOString(), 149 - anchorDate: check.anchorDate, 150 - accountAge, 151 - threshold: check.maxAgeDays, 152 }, 153 - "Account age calculated", 154 ); 155 156 - // Check if account is too new 157 - if (accountAge < check.maxAgeDays) { 158 logger.info( 159 { 160 process: "ACCOUNT_AGE", 161 replyingDid: context.replyingDid, 162 replyToDid: context.replyToDid, 163 - accountAge, 164 - threshold: check.maxAgeDays, 165 atURI: context.atURI, 166 }, 167 - "Labeling new account replying to monitored DID", 168 ); 169 170 await createAccountLabel( 171 context.replyingDid, 172 check.label, 173 - `${context.time}: ${check.comment} - Account age: ${accountAge} days (threshold: ${check.maxAgeDays} days) - Reply: ${context.atURI}`, 174 ); 175 176 // Only apply one label per reply
··· 137 continue; 138 } 139 140 + // Define the flagging window 141 + const windowStart = new Date(check.anchorDate); 142 + const windowEnd = new Date(windowStart); 143 + windowEnd.setUTCDate(windowEnd.getUTCDate() + check.maxAgeDays); 144 + windowEnd.setUTCHours(23, 59, 59, 999); 145 146 logger.debug( 147 { 148 process: "ACCOUNT_AGE", 149 replyingDid: context.replyingDid, 150 creationDate: creationDate.toISOString(), 151 + windowStart: windowStart.toISOString(), 152 + windowEnd: windowEnd.toISOString(), 153 }, 154 + "Checking if account creation date is within the window", 155 ); 156 157 + // Check if account was created within the window 158 + if (creationDate >= windowStart && creationDate <= windowEnd) { 159 logger.info( 160 { 161 process: "ACCOUNT_AGE", 162 replyingDid: context.replyingDid, 163 replyToDid: context.replyToDid, 164 atURI: context.atURI, 165 }, 166 + "Labeling account created within the monitored date range", 167 ); 168 169 await createAccountLabel( 170 context.replyingDid, 171 check.label, 172 + `${context.time}: ${check.comment} - Account created within monitored range - Reply: ${context.atURI}`, 173 ); 174 175 // Only apply one label per reply
+87 -125
src/rules/account/tests/age.test.ts
··· 173 it("should skip if reply is not to monitored DID", async () => { 174 ACCOUNT_AGE_CHECKS.push({ 175 monitoredDIDs: ["did:plc:monitored1"], 176 - anchorDate: "2025-01-15", 177 maxAgeDays: 7, 178 - label: "new-account-reply", 179 - comment: "New account reply", 180 }); 181 182 await checkAccountAge({ ··· 189 expect(createAccountLabel).not.toHaveBeenCalled(); 190 }); 191 192 - it("should label account if too new", async () => { 193 - ACCOUNT_AGE_CHECKS.push({ 194 - monitoredDIDs: ["did:plc:monitored"], 195 - anchorDate: "2025-01-15", 196 - maxAgeDays: 7, 197 - label: "new-account-reply", 198 - comment: "New account replying during campaign", 199 }); 200 201 - // Mock account created on Jan 12 noon (2.5 days before anchor at midnight = 2 days floored) 202 - const mockDidDoc = [ 203 - { 204 - createdAt: "2025-01-12T12:00:00.000Z", 205 - }, 206 - ]; 207 208 - (global.fetch as any).mockResolvedValueOnce({ 209 - ok: true, 210 - json: async () => mockDidDoc, 211 - }); 212 213 - await checkAccountAge({ 214 - replyToDid: "did:plc:monitored", 215 - replyingDid: "did:plc:newaccount", 216 - atURI: TEST_REPLY_URI, 217 - time: TEST_TIME, 218 }); 219 220 - expect(createAccountLabel).toHaveBeenCalledWith( 221 - "did:plc:newaccount", 222 - "new-account-reply", 223 - expect.stringContaining("Account age: 2 days"), 224 - ); 225 - }); 226 227 - it("should not label account if old enough", async () => { 228 - ACCOUNT_AGE_CHECKS.push({ 229 - monitoredDIDs: ["did:plc:monitored"], 230 - anchorDate: "2025-01-15", 231 - maxAgeDays: 7, 232 - label: "new-account-reply", 233 - comment: "New account reply", 234 - }); 235 236 - // Mock account created on Jan 5 (10 days before anchor) 237 - const mockDidDoc = [ 238 - { 239 - createdAt: "2025-01-05T12:00:00.000Z", 240 - }, 241 - ]; 242 - 243 - (global.fetch as any).mockResolvedValueOnce({ 244 - ok: true, 245 - json: async () => mockDidDoc, 246 }); 247 248 - await checkAccountAge({ 249 - replyToDid: "did:plc:monitored", 250 - replyingDid: "did:plc:oldaccount", 251 - atURI: TEST_REPLY_URI, 252 - time: TEST_TIME, 253 - }); 254 255 - expect(createAccountLabel).not.toHaveBeenCalled(); 256 - }); 257 258 - it("should handle multiple monitored DIDs", async () => { 259 - ACCOUNT_AGE_CHECKS.push({ 260 - monitoredDIDs: ["did:plc:monitored1", "did:plc:monitored2"], 261 - anchorDate: "2025-01-15", 262 - maxAgeDays: 7, 263 - label: "new-account-reply", 264 - comment: "New account reply", 265 }); 266 267 - const mockDidDoc = [ 268 - { 269 - createdAt: "2025-01-14T12:00:00.000Z", 270 - }, 271 - ]; 272 273 - (global.fetch as any).mockResolvedValueOnce({ 274 - ok: true, 275 - json: async () => mockDidDoc, 276 - }); 277 278 - await checkAccountAge({ 279 - replyToDid: "did:plc:monitored2", 280 - replyingDid: "did:plc:newaccount", 281 - atURI: TEST_REPLY_URI, 282 - time: TEST_TIME, 283 }); 284 285 - expect(createAccountLabel).toHaveBeenCalledOnce(); 286 - }); 287 - 288 - it("should handle multiple check configurations", async () => { 289 - ACCOUNT_AGE_CHECKS.push( 290 - { 291 - monitoredDIDs: ["did:plc:monitored1"], 292 - anchorDate: "2025-01-15", 293 - maxAgeDays: 7, 294 - label: "new-account-campaign1", 295 - comment: "Campaign 1", 296 - }, 297 - { 298 - monitoredDIDs: ["did:plc:monitored2"], 299 - anchorDate: "2025-02-01", 300 - maxAgeDays: 14, 301 - label: "new-account-campaign2", 302 - comment: "Campaign 2", 303 - }, 304 - ); 305 306 - const mockDidDoc = [ 307 - { 308 - createdAt: "2025-01-20T12:00:00.000Z", 309 - }, 310 - ]; 311 312 - (global.fetch as any).mockResolvedValueOnce({ 313 - ok: true, 314 - json: async () => mockDidDoc, 315 }); 316 - 317 - // Reply to monitored2 - account created Jan 20 noon, checked against Feb 1 midnight (11.5 days = 11 floored) 318 - await checkAccountAge({ 319 - replyToDid: "did:plc:monitored2", 320 - replyingDid: "did:plc:newaccount", 321 - atURI: TEST_REPLY_URI, 322 - time: TEST_TIME, 323 - }); 324 - 325 - expect(createAccountLabel).toHaveBeenCalledWith( 326 - "did:plc:newaccount", 327 - "new-account-campaign2", 328 - expect.stringContaining("Account age: 11 days"), 329 - ); 330 }); 331 332 it("should skip if creation date cannot be determined", async () => { ··· 362 ACCOUNT_AGE_CHECKS.push( 363 { 364 monitoredDIDs: ["did:plc:monitored"], 365 - anchorDate: "2025-01-15", 366 - maxAgeDays: 7, 367 label: "label1", 368 comment: "First check", 369 }, 370 { 371 monitoredDIDs: ["did:plc:monitored"], 372 - anchorDate: "2025-01-15", 373 - maxAgeDays: 14, 374 label: "label2", 375 comment: "Second check", 376 }, 377 ); 378 379 const mockDidDoc = [ 380 { 381 - createdAt: "2025-01-14T12:00:00.000Z", 382 }, 383 ]; 384
··· 173 it("should skip if reply is not to monitored DID", async () => { 174 ACCOUNT_AGE_CHECKS.push({ 175 monitoredDIDs: ["did:plc:monitored1"], 176 + anchorDate: "2025-10-15", 177 maxAgeDays: 7, 178 + label: "window-reply", 179 + comment: "Account created in window", 180 }); 181 182 await checkAccountAge({ ··· 189 expect(createAccountLabel).not.toHaveBeenCalled(); 190 }); 191 192 + describe("when checking a date window", () => { 193 + beforeEach(() => { 194 + ACCOUNT_AGE_CHECKS.push({ 195 + monitoredDIDs: ["did:plc:monitored"], 196 + anchorDate: "2025-10-15", 197 + maxAgeDays: 7, // Window is inclusive: Oct 15 -> Oct 22 198 + label: "window-reply", 199 + comment: "Account created in window", 200 + }); 201 }); 202 203 + it("should label account created within the monitored window", async () => { 204 + const mockDidDoc = [{ createdAt: "2025-10-18T12:00:00.000Z" }]; 205 + (global.fetch as any).mockResolvedValueOnce({ 206 + ok: true, 207 + json: async () => mockDidDoc, 208 + }); 209 210 + await checkAccountAge({ 211 + replyToDid: "did:plc:monitored", 212 + replyingDid: "did:plc:inwindow", 213 + atURI: TEST_REPLY_URI, 214 + time: TEST_TIME, 215 + }); 216 217 + expect(createAccountLabel).toHaveBeenCalledWith( 218 + "did:plc:inwindow", 219 + "window-reply", 220 + expect.stringContaining("Account created within monitored range"), 221 + ); 222 }); 223 224 + it("should not label account created before the window", async () => { 225 + const mockDidDoc = [{ createdAt: "2025-10-14T23:59:59.999Z" }]; 226 + (global.fetch as any).mockResolvedValueOnce({ 227 + ok: true, 228 + json: async () => mockDidDoc, 229 + }); 230 231 + await checkAccountAge({ 232 + replyToDid: "did:plc:monitored", 233 + replyingDid: "did:plc:beforewindow", 234 + atURI: TEST_REPLY_URI, 235 + time: TEST_TIME, 236 + }); 237 238 + expect(createAccountLabel).not.toHaveBeenCalled(); 239 }); 240 241 + it("should not label account created after the window", async () => { 242 + const mockDidDoc = [{ createdAt: "2025-10-23T00:00:00.000Z" }]; 243 + (global.fetch as any).mockResolvedValueOnce({ 244 + ok: true, 245 + json: async () => mockDidDoc, 246 + }); 247 248 + await checkAccountAge({ 249 + replyToDid: "did:plc:monitored", 250 + replyingDid: "did:plc:afterwindow", 251 + atURI: TEST_REPLY_URI, 252 + time: TEST_TIME, 253 + }); 254 255 + expect(createAccountLabel).not.toHaveBeenCalled(); 256 }); 257 258 + it("should label account created on the first moment of the window", async () => { 259 + const mockDidDoc = [{ createdAt: "2025-10-15T00:00:00.000Z" }]; 260 + (global.fetch as any).mockResolvedValueOnce({ 261 + ok: true, 262 + json: async () => mockDidDoc, 263 + }); 264 265 + await checkAccountAge({ 266 + replyToDid: "did:plc:monitored", 267 + replyingDid: "did:plc:startofwindow", 268 + atURI: TEST_REPLY_URI, 269 + time: TEST_TIME, 270 + }); 271 272 + expect(createAccountLabel).toHaveBeenCalled(); 273 }); 274 275 + it("should label account created on the last moment of the window", async () => { 276 + const mockDidDoc = [{ createdAt: "2025-10-22T23:59:59.999Z" }]; 277 + (global.fetch as any).mockResolvedValueOnce({ 278 + ok: true, 279 + json: async () => mockDidDoc, 280 + }); 281 282 + await checkAccountAge({ 283 + replyToDid: "did:plc:monitored", 284 + replyingDid: "did:plc:endofwindow", 285 + atURI: TEST_REPLY_URI, 286 + time: TEST_TIME, 287 + }); 288 289 + expect(createAccountLabel).toHaveBeenCalled(); 290 }); 291 }); 292 293 it("should skip if creation date cannot be determined", async () => { ··· 323 ACCOUNT_AGE_CHECKS.push( 324 { 325 monitoredDIDs: ["did:plc:monitored"], 326 + anchorDate: "2025-10-15", 327 + maxAgeDays: 7, // Oct 15-22 328 label: "label1", 329 comment: "First check", 330 }, 331 { 332 monitoredDIDs: ["did:plc:monitored"], 333 + anchorDate: "2025-10-10", 334 + maxAgeDays: 20, // Oct 10-30 335 label: "label2", 336 comment: "Second check", 337 }, 338 ); 339 340 + // Created Oct 18, matches both windows 341 const mockDidDoc = [ 342 { 343 + createdAt: "2025-10-18T12:00:00.000Z", 344 }, 345 ]; 346