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 137 continue; 138 138 } 139 139 140 - // Calculate age at anchor date 141 - const anchorDate = new Date(check.anchorDate); 142 - const accountAge = calculateAccountAge(creationDate, anchorDate); 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); 143 145 144 146 logger.debug( 145 147 { 146 148 process: "ACCOUNT_AGE", 147 149 replyingDid: context.replyingDid, 148 150 creationDate: creationDate.toISOString(), 149 - anchorDate: check.anchorDate, 150 - accountAge, 151 - threshold: check.maxAgeDays, 151 + windowStart: windowStart.toISOString(), 152 + windowEnd: windowEnd.toISOString(), 152 153 }, 153 - "Account age calculated", 154 + "Checking if account creation date is within the window", 154 155 ); 155 156 156 - // Check if account is too new 157 - if (accountAge < check.maxAgeDays) { 157 + // Check if account was created within the window 158 + if (creationDate >= windowStart && creationDate <= windowEnd) { 158 159 logger.info( 159 160 { 160 161 process: "ACCOUNT_AGE", 161 162 replyingDid: context.replyingDid, 162 163 replyToDid: context.replyToDid, 163 - accountAge, 164 - threshold: check.maxAgeDays, 165 164 atURI: context.atURI, 166 165 }, 167 - "Labeling new account replying to monitored DID", 166 + "Labeling account created within the monitored date range", 168 167 ); 169 168 170 169 await createAccountLabel( 171 170 context.replyingDid, 172 171 check.label, 173 - `${context.time}: ${check.comment} - Account age: ${accountAge} days (threshold: ${check.maxAgeDays} days) - Reply: ${context.atURI}`, 172 + `${context.time}: ${check.comment} - Account created within monitored range - Reply: ${context.atURI}`, 174 173 ); 175 174 176 175 // Only apply one label per reply
+87 -125
src/rules/account/tests/age.test.ts
··· 173 173 it("should skip if reply is not to monitored DID", async () => { 174 174 ACCOUNT_AGE_CHECKS.push({ 175 175 monitoredDIDs: ["did:plc:monitored1"], 176 - anchorDate: "2025-01-15", 176 + anchorDate: "2025-10-15", 177 177 maxAgeDays: 7, 178 - label: "new-account-reply", 179 - comment: "New account reply", 178 + label: "window-reply", 179 + comment: "Account created in window", 180 180 }); 181 181 182 182 await checkAccountAge({ ··· 189 189 expect(createAccountLabel).not.toHaveBeenCalled(); 190 190 }); 191 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", 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 + }); 199 201 }); 200 202 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 - ]; 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 + }); 207 209 208 - (global.fetch as any).mockResolvedValueOnce({ 209 - ok: true, 210 - json: async () => mockDidDoc, 211 - }); 210 + await checkAccountAge({ 211 + replyToDid: "did:plc:monitored", 212 + replyingDid: "did:plc:inwindow", 213 + atURI: TEST_REPLY_URI, 214 + time: TEST_TIME, 215 + }); 212 216 213 - await checkAccountAge({ 214 - replyToDid: "did:plc:monitored", 215 - replyingDid: "did:plc:newaccount", 216 - atURI: TEST_REPLY_URI, 217 - time: TEST_TIME, 217 + expect(createAccountLabel).toHaveBeenCalledWith( 218 + "did:plc:inwindow", 219 + "window-reply", 220 + expect.stringContaining("Account created within monitored range"), 221 + ); 218 222 }); 219 223 220 - expect(createAccountLabel).toHaveBeenCalledWith( 221 - "did:plc:newaccount", 222 - "new-account-reply", 223 - expect.stringContaining("Account age: 2 days"), 224 - ); 225 - }); 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 + }); 226 230 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 - }); 231 + await checkAccountAge({ 232 + replyToDid: "did:plc:monitored", 233 + replyingDid: "did:plc:beforewindow", 234 + atURI: TEST_REPLY_URI, 235 + time: TEST_TIME, 236 + }); 235 237 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, 238 + expect(createAccountLabel).not.toHaveBeenCalled(); 246 239 }); 247 240 248 - await checkAccountAge({ 249 - replyToDid: "did:plc:monitored", 250 - replyingDid: "did:plc:oldaccount", 251 - atURI: TEST_REPLY_URI, 252 - time: TEST_TIME, 253 - }); 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 + }); 254 247 255 - expect(createAccountLabel).not.toHaveBeenCalled(); 256 - }); 248 + await checkAccountAge({ 249 + replyToDid: "did:plc:monitored", 250 + replyingDid: "did:plc:afterwindow", 251 + atURI: TEST_REPLY_URI, 252 + time: TEST_TIME, 253 + }); 257 254 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", 255 + expect(createAccountLabel).not.toHaveBeenCalled(); 265 256 }); 266 257 267 - const mockDidDoc = [ 268 - { 269 - createdAt: "2025-01-14T12:00:00.000Z", 270 - }, 271 - ]; 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 + }); 272 264 273 - (global.fetch as any).mockResolvedValueOnce({ 274 - ok: true, 275 - json: async () => mockDidDoc, 276 - }); 265 + await checkAccountAge({ 266 + replyToDid: "did:plc:monitored", 267 + replyingDid: "did:plc:startofwindow", 268 + atURI: TEST_REPLY_URI, 269 + time: TEST_TIME, 270 + }); 277 271 278 - await checkAccountAge({ 279 - replyToDid: "did:plc:monitored2", 280 - replyingDid: "did:plc:newaccount", 281 - atURI: TEST_REPLY_URI, 282 - time: TEST_TIME, 272 + expect(createAccountLabel).toHaveBeenCalled(); 283 273 }); 284 274 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 - ); 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 + }); 305 281 306 - const mockDidDoc = [ 307 - { 308 - createdAt: "2025-01-20T12:00:00.000Z", 309 - }, 310 - ]; 282 + await checkAccountAge({ 283 + replyToDid: "did:plc:monitored", 284 + replyingDid: "did:plc:endofwindow", 285 + atURI: TEST_REPLY_URI, 286 + time: TEST_TIME, 287 + }); 311 288 312 - (global.fetch as any).mockResolvedValueOnce({ 313 - ok: true, 314 - json: async () => mockDidDoc, 289 + expect(createAccountLabel).toHaveBeenCalled(); 315 290 }); 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 291 }); 331 292 332 293 it("should skip if creation date cannot be determined", async () => { ··· 362 323 ACCOUNT_AGE_CHECKS.push( 363 324 { 364 325 monitoredDIDs: ["did:plc:monitored"], 365 - anchorDate: "2025-01-15", 366 - maxAgeDays: 7, 326 + anchorDate: "2025-10-15", 327 + maxAgeDays: 7, // Oct 15-22 367 328 label: "label1", 368 329 comment: "First check", 369 330 }, 370 331 { 371 332 monitoredDIDs: ["did:plc:monitored"], 372 - anchorDate: "2025-01-15", 373 - maxAgeDays: 14, 333 + anchorDate: "2025-10-10", 334 + maxAgeDays: 20, // Oct 10-30 374 335 label: "label2", 375 336 comment: "Second check", 376 337 }, 377 338 ); 378 339 340 + // Created Oct 18, matches both windows 379 341 const mockDidDoc = [ 380 342 { 381 - createdAt: "2025-01-14T12:00:00.000Z", 343 + createdAt: "2025-10-18T12:00:00.000Z", 382 344 }, 383 345 ]; 384 346