Skip to content

Commit d1e868b

Browse files
committed
fix: correct chunked write params, casts, heartbeat lifetime, and plus-tag noreply detection
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
1 parent f671ff7 commit d1e868b

3 files changed

Lines changed: 37 additions & 26 deletions

File tree

services/apps/packages_worker/src/security-contacts/processBatch.ts

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -223,33 +223,36 @@ export async function processBatch(
223223
return { repoId: target.repoId, status: 'extractor-failed' as const }
224224
}
225225
})
226+
227+
const extractionDurationMs = Date.now() - extractionStartedAt
228+
const ok = outcomes.filter((o) => o.status === 'ok').length
229+
log.info(
230+
{
231+
ok,
232+
failed: outcomes.length - ok,
233+
durationMs: extractionDurationMs,
234+
reposPerSec: Number((outcomes.length / (extractionDurationMs / 1000)).toFixed(1)),
235+
},
236+
'Security contacts batch extraction complete — persisting',
237+
)
238+
239+
// Heartbeat is kept alive through persistence too: a slow/lock-blocked write can outlast the
240+
// 2-minute heartbeat timeout just like extraction can, and letting the timer stop early would
241+
// let Temporal retry this activity while the original attempt is still writing.
242+
const writeStartedAt = Date.now()
243+
await writeContactsBatch(qx, outcomes)
244+
const writeDurationMs = Date.now() - writeStartedAt
245+
log.info(
246+
{
247+
durationMs: writeDurationMs,
248+
reposPerSec: Number((outcomes.length / (writeDurationMs / 1000)).toFixed(1)),
249+
},
250+
'Security contacts batch persistence complete',
251+
)
226252
} finally {
227253
clearInterval(heartbeatTimer)
228254
}
229255

230-
const extractionDurationMs = Date.now() - extractionStartedAt
231-
const ok = outcomes.filter((o) => o.status === 'ok').length
232-
log.info(
233-
{
234-
ok,
235-
failed: outcomes.length - ok,
236-
durationMs: extractionDurationMs,
237-
reposPerSec: Number((outcomes.length / (extractionDurationMs / 1000)).toFixed(1)),
238-
},
239-
'Security contacts batch extraction complete — persisting',
240-
)
241-
242-
const writeStartedAt = Date.now()
243-
await writeContactsBatch(qx, outcomes)
244-
const writeDurationMs = Date.now() - writeStartedAt
245-
log.info(
246-
{
247-
durationMs: writeDurationMs,
248-
reposPerSec: Number((outcomes.length / (writeDurationMs / 1000)).toFixed(1)),
249-
},
250-
'Security contacts batch persistence complete',
251-
)
252-
253256
log.info({ processed: targets.length }, 'Security contacts batch complete')
254257
return { processed: targets.length }
255258
}

services/apps/packages_worker/src/security-contacts/writeContacts.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const WRITE_CHUNK_SIZE = 100
122122
function prepareBulkPolicyUpdate(chunk: OkResult[]): string {
123123
const rows = chunk.map(
124124
(_, i) =>
125-
`($(rows.id${i}), $(rows.securityPolicyUrl${i}), $(rows.vulnerabilityReportingUrl${i}), $(rows.pvrResolved${i}), $(rows.bugBountyUrl${i}), $(rows.securityTxtUrl${i}), $(rows.pvrEnabled${i}))`,
125+
`($(id${i})::bigint, $(securityPolicyUrl${i}), $(vulnerabilityReportingUrl${i}), $(pvrResolved${i})::boolean, $(bugBountyUrl${i}), $(securityTxtUrl${i}), $(pvrEnabled${i})::boolean)`,
126126
)
127127

128128
const params = chunk.reduce(
@@ -186,6 +186,7 @@ export async function writeContactsBatch(
186186
.map((o) => o.repoId)
187187
const ok = outcomes.filter((o): o is OkResult => o.status === 'ok')
188188

189+
let firstError: Error | undefined
189190
for (let i = 0; i < ok.length; i += WRITE_CHUNK_SIZE) {
190191
const chunk = ok.slice(i, i + WRITE_CHUNK_SIZE)
191192
try {
@@ -194,13 +195,18 @@ export async function writeContactsBatch(
194195
// Isolates blast radius to this chunk: without this, one bad chunk would throw out of
195196
// the whole batch, leaving every repo's contacts_last_refreshed untouched and the sweep
196197
// stuck re-fetching + re-extracting the same batch forever instead of ever advancing.
198+
// Every remaining chunk is still attempted; the error is re-thrown only after everything
199+
// that can be persisted has been, so the activity attempt still surfaces as failed.
197200
log.error(
198201
{ errMsg: (err as Error).message, repoIds: chunk.map((r) => r.repoId) },
199202
'Batched contacts write failed for chunk — marking attempted without contacts update',
200203
)
201204
attemptedOnlyIds.push(...chunk.map((r) => r.repoId))
205+
firstError = firstError ?? (err as Error)
202206
}
203207
}
204208

205209
await markReposAttempted(qx, attemptedOnlyIds)
210+
211+
if (firstError) throw firstError
206212
}

services/libs/common/src/email.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,10 @@ export const classifyEmailReachability = (email: string): EmailReachability => {
148148
if (!isValidEmail(value)) return fail('invalid')
149149
if (PLACEHOLDER_EMAIL_DOMAINS.has(emailDomain(value))) return fail('placeholder-domain')
150150

151-
if (NOREPLY_LOCAL_PARTS.has(getEmailLocalPart(value))) return fail('noreply')
152-
if (looksLikeBot(getEmailLocalPart(value))) return fail('bot')
151+
const localPart = getEmailLocalPart(value)
152+
const baseLocalPart = localPart.split('+')[0]
153+
if (NOREPLY_LOCAL_PARTS.has(baseLocalPart)) return fail('noreply')
154+
if (looksLikeBot(localPart)) return fail('bot')
153155
if (disposableEmailDomains.has(emailDomain(value))) return fail('disposable')
154156

155157
return { email, reachable: true, reason: null }

0 commit comments

Comments
 (0)