-
Notifications
You must be signed in to change notification settings - Fork 306
Expand file tree
/
Copy pathprocessor.ts
More file actions
95 lines (87 loc) · 3.26 KB
/
Copy pathprocessor.ts
File metadata and controls
95 lines (87 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* @module milestones/divergence/processor
* @description BullMQ processor for the milestone divergence scan job.
*
* Wraps {@link MilestoneDivergenceScanner} with queue-level concerns:
* payload validation (terminal on invalid input, so the job is not retried
* pointlessly), correlation-id propagation, and a structured job result.
*
* Retry semantics:
* - Invalid payload → throws `InvalidJobPayloadError` (terminal, quarantined).
* - Head-ledger RPC failure → the scanner throws; the error propagates so the
* queue retries with the job type's backoff policy.
* - Per-contract RPC failures → recorded as `unavailable` reports inside the
* scan; the job itself succeeds and reports the counts.
*/
import { z } from 'zod';
import { createLogger } from '../../logger';
import { InvalidJobPayloadError } from '../../queue/queue-errors';
import type { JobResult } from '../../queue/types';
import type { MilestoneDivergenceScanPayload } from './types';
import {
MilestoneDivergenceScanner,
MAX_CONTRACTS_PER_RUN,
DEFAULT_MAX_CONTRACTS_PER_RUN,
} from './scanner';
import { getDefaultDivergenceDependencies } from './dependencies';
/** Zod schema for the scan job payload (mirrors the queue payload type). */
export const milestoneDivergenceScanPayloadSchema = z
.object({
tenantId: z.string().min(1).max(128).optional(),
maxContracts: z
.number()
.int()
.min(1)
.max(MAX_CONTRACTS_PER_RUN)
.optional(),
cursor: z.string().max(256).optional(),
runId: z.string().min(1).max(128).optional(),
correlationId: z.string().max(256).optional(),
requestId: z.string().max(256).optional(),
})
.strict();
export type MilestoneDivergenceDependencies = {
scanner?: MilestoneDivergenceScanner;
};
/**
* Processes one milestone divergence scan job.
*
* @param payload - Scan configuration from the queue.
* @param deps - Optional injected dependencies (tests); defaults to the
* production wiring.
*/
export async function processMilestoneDivergenceScan(
payload: MilestoneDivergenceScanPayload,
deps: MilestoneDivergenceDependencies = {},
): Promise<JobResult> {
const log = createLogger({
processor: 'milestone-divergence-scan',
...(payload.correlationId && { correlationId: payload.correlationId }),
...(payload.requestId && { requestId: payload.requestId }),
});
const parsed = milestoneDivergenceScanPayloadSchema.safeParse(payload);
if (!parsed.success) {
log.warn('Milestone divergence scan rejected: invalid payload', {
issues: parsed.error.issues.map((i) => i.message),
});
throw new InvalidJobPayloadError(
`Invalid milestone divergence scan payload: ${parsed.error.issues
.map((i) => i.message)
.join('; ')}`,
);
}
const scanner = deps.scanner ?? getDefaultDivergenceDependencies().scanner;
const summary = await scanner.run(parsed.data);
log.info('Milestone divergence scan job completed', {
runId: summary.runId,
contractsScanned: summary.contractsScanned,
inSync: summary.inSync,
divergent: summary.divergent,
unavailable: summary.unavailable,
});
return {
success: true,
message: `Milestone divergence scan completed: ${summary.contractsScanned} contract(s) compared`,
data: summary,
};
}