@qcobro/common 1.12.0 → 1.13.0
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.
- package/dist/bin/engineEval.d.ts +67 -0
- package/dist/bin/engineEval.d.ts.map +1 -0
- package/dist/bin/engineEval.js +290 -0
- package/dist/bin/engineEval.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/evaluation/evaluate.d.ts +4 -0
- package/dist/evaluation/evaluate.d.ts.map +1 -0
- package/dist/evaluation/evaluate.js +504 -0
- package/dist/evaluation/evaluate.js.map +1 -0
- package/dist/evaluation/index.d.ts +3 -0
- package/dist/evaluation/index.d.ts.map +1 -0
- package/dist/evaluation/index.js +3 -0
- package/dist/evaluation/index.js.map +1 -0
- package/dist/evaluation/scorecard.d.ts +84 -0
- package/dist/evaluation/scorecard.d.ts.map +1 -0
- package/dist/evaluation/scorecard.js +29 -0
- package/dist/evaluation/scorecard.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/engineEvents.d.ts +407 -0
- package/dist/schemas/engineEvents.d.ts.map +1 -0
- package/dist/schemas/engineEvents.js +179 -0
- package/dist/schemas/engineEvents.js.map +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/types/email.d.ts +5 -0
- package/dist/types/email.d.ts.map +1 -1
- package/dist/types/engine.d.ts +5 -0
- package/dist/types/engine.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/pacing.d.ts +17 -0
- package/dist/utils/pacing.d.ts.map +1 -0
- package/dist/utils/pacing.js +18 -0
- package/dist/utils/pacing.js.map +1 -0
- package/dist/utils/time.d.ts +16 -0
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +17 -0
- package/dist/utils/time.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { isWithinScheduleWindow, localDateString } from "../utils/time.js";
|
|
2
|
+
import { bucketOf, perTickCapacity } from "../utils/pacing.js";
|
|
3
|
+
/**
|
|
4
|
+
* The judge: replay an engine event stream against the invariant catalog and
|
|
5
|
+
* produce a scorecard. Pure — no I/O, no clock, no randomness — so the same
|
|
6
|
+
* input always yields a deeply equal scorecard, whether the stream came from a
|
|
7
|
+
* CI simulation or a production export.
|
|
8
|
+
*
|
|
9
|
+
* A stream is allowed to be partial (workspace-filtered, time-bounded): checks
|
|
10
|
+
* only fire on breaches the stream itself proves, so a partial stream can
|
|
11
|
+
* under-detect but never false-positive. Deployment-scoped invariants (SAF-5,
|
|
12
|
+
* PERF-1) read the shared tick lifecycle events, which carry aggregate counts.
|
|
13
|
+
*/
|
|
14
|
+
const SUPPRESSION_DECISIONS = new Set([
|
|
15
|
+
"no_phone",
|
|
16
|
+
"no_email",
|
|
17
|
+
"intent_suppressed",
|
|
18
|
+
"account_suppressed",
|
|
19
|
+
"promise_suppressed",
|
|
20
|
+
"lifetime_cap",
|
|
21
|
+
"daily_cap"
|
|
22
|
+
]);
|
|
23
|
+
function rateFor(channel, rates) {
|
|
24
|
+
return rates[bucketOf(channel)];
|
|
25
|
+
}
|
|
26
|
+
function p95(values) {
|
|
27
|
+
if (values.length === 0)
|
|
28
|
+
return 0;
|
|
29
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
30
|
+
return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)];
|
|
31
|
+
}
|
|
32
|
+
/** The engine's own window rule, applied to the snapshot the stream carries. */
|
|
33
|
+
function inSnapshotWindow(s, at) {
|
|
34
|
+
return isWithinScheduleWindow({
|
|
35
|
+
startDate: new Date(s.startDate),
|
|
36
|
+
endDate: s.endDate ? new Date(s.endDate) : null,
|
|
37
|
+
daysOfWeek: s.daysOfWeek,
|
|
38
|
+
startTime: s.startTime,
|
|
39
|
+
endTime: s.endTime
|
|
40
|
+
}, at, s.timezone);
|
|
41
|
+
}
|
|
42
|
+
const key = (campaignId, accountId) => `${campaignId}|${accountId}`;
|
|
43
|
+
function indexStream(events) {
|
|
44
|
+
const byTick = new Map();
|
|
45
|
+
const tickOrder = [];
|
|
46
|
+
const slice = (tickId) => {
|
|
47
|
+
let s = byTick.get(tickId);
|
|
48
|
+
if (!s) {
|
|
49
|
+
s = { tickId, evaluated: [], decided: [], reserved: [], requested: [] };
|
|
50
|
+
byTick.set(tickId, s);
|
|
51
|
+
tickOrder.push(tickId);
|
|
52
|
+
}
|
|
53
|
+
return s;
|
|
54
|
+
};
|
|
55
|
+
const idx = {
|
|
56
|
+
ticks: [],
|
|
57
|
+
snapshotAt: new Map(),
|
|
58
|
+
lastSnapshot: new Map(),
|
|
59
|
+
reservedByPair: new Map(),
|
|
60
|
+
requested: [],
|
|
61
|
+
succeeded: [],
|
|
62
|
+
failed: [],
|
|
63
|
+
decided: []
|
|
64
|
+
};
|
|
65
|
+
for (const e of events) {
|
|
66
|
+
switch (e.kind) {
|
|
67
|
+
case "tick.started":
|
|
68
|
+
if (e.tickId)
|
|
69
|
+
slice(e.tickId).started = e;
|
|
70
|
+
break;
|
|
71
|
+
case "tick.completed":
|
|
72
|
+
if (e.tickId)
|
|
73
|
+
slice(e.tickId).completed = e;
|
|
74
|
+
break;
|
|
75
|
+
case "campaign.evaluated":
|
|
76
|
+
if (e.tickId)
|
|
77
|
+
slice(e.tickId).evaluated.push(e);
|
|
78
|
+
idx.snapshotAt.set(key(e.tickId ?? "", e.campaignId), e.snapshot);
|
|
79
|
+
idx.lastSnapshot.set(e.campaignId, e.snapshot);
|
|
80
|
+
break;
|
|
81
|
+
case "account.decided":
|
|
82
|
+
if (e.tickId)
|
|
83
|
+
slice(e.tickId).decided.push(e);
|
|
84
|
+
idx.decided.push(e);
|
|
85
|
+
break;
|
|
86
|
+
case "attempt.reserved": {
|
|
87
|
+
if (e.tickId)
|
|
88
|
+
slice(e.tickId).reserved.push(e);
|
|
89
|
+
const k = key(e.campaignId, e.portfolioAccountId);
|
|
90
|
+
let agg = idx.reservedByPair.get(k);
|
|
91
|
+
if (!agg) {
|
|
92
|
+
agg = {
|
|
93
|
+
campaignId: e.campaignId,
|
|
94
|
+
portfolioAccountId: e.portfolioAccountId,
|
|
95
|
+
reservations: []
|
|
96
|
+
};
|
|
97
|
+
idx.reservedByPair.set(k, agg);
|
|
98
|
+
}
|
|
99
|
+
agg.reservations.push(e);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "dispatch.requested":
|
|
103
|
+
if (e.tickId)
|
|
104
|
+
slice(e.tickId).requested.push(e);
|
|
105
|
+
idx.requested.push(e);
|
|
106
|
+
break;
|
|
107
|
+
case "dispatch.succeeded":
|
|
108
|
+
idx.succeeded.push(e);
|
|
109
|
+
break;
|
|
110
|
+
case "dispatch.failed":
|
|
111
|
+
idx.failed.push(e);
|
|
112
|
+
break;
|
|
113
|
+
case "provider.event":
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
idx.ticks = tickOrder.map((t) => byTick.get(t));
|
|
118
|
+
return idx;
|
|
119
|
+
}
|
|
120
|
+
/** Snapshot for a campaign as of a given tick, falling back to the last known one. */
|
|
121
|
+
function snapshotFor(idx, tickId, campaignId) {
|
|
122
|
+
return idx.snapshotAt.get(key(tickId ?? "", campaignId)) ?? idx.lastSnapshot.get(campaignId);
|
|
123
|
+
}
|
|
124
|
+
function result(id, name, scope, violations, metric) {
|
|
125
|
+
return {
|
|
126
|
+
id,
|
|
127
|
+
name,
|
|
128
|
+
scope,
|
|
129
|
+
verdict: violations.length === 0 ? "pass" : "fail",
|
|
130
|
+
metric,
|
|
131
|
+
violations
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function evaluate(events, parameters) {
|
|
135
|
+
// Deterministic order regardless of how the caller assembled the stream.
|
|
136
|
+
const sorted = [...events].sort((a, b) => a.at.localeCompare(b.at) ||
|
|
137
|
+
(a.tickId ?? "").localeCompare(b.tickId ?? "") ||
|
|
138
|
+
(a.seq ?? -1) - (b.seq ?? -1) ||
|
|
139
|
+
a.id.localeCompare(b.id));
|
|
140
|
+
const idx = indexStream(sorted);
|
|
141
|
+
const { tickSeconds, ratesPerMinute, thresholds } = parameters;
|
|
142
|
+
// SAF-1 — no dispatch outside the campaign window.
|
|
143
|
+
const saf1 = [];
|
|
144
|
+
for (const d of idx.requested) {
|
|
145
|
+
const snap = snapshotFor(idx, d.tickId, d.campaignId);
|
|
146
|
+
if (!snap)
|
|
147
|
+
continue; // partial stream: nothing to judge against
|
|
148
|
+
if (!inSnapshotWindow(snap, new Date(d.at))) {
|
|
149
|
+
saf1.push({
|
|
150
|
+
campaignId: d.campaignId,
|
|
151
|
+
portfolioAccountId: d.portfolioAccountId,
|
|
152
|
+
eventIds: [d.id],
|
|
153
|
+
detail: `dispatched at ${d.at} outside window ${snap.startTime}–${snap.endTime} (${snap.timezone})`
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// SAF-2 — lifetime attempt cap, judged against the cap in force at each
|
|
158
|
+
// reservation's tick (caps are editable mid-campaign, so the latest snapshot
|
|
159
|
+
// must not retroactively re-judge older attempts). In-stream reservations
|
|
160
|
+
// alone exceeding the cap is a proven breach (the true total can only be higher).
|
|
161
|
+
const saf2 = [];
|
|
162
|
+
for (const pair of idx.reservedByPair.values()) {
|
|
163
|
+
const offending = [];
|
|
164
|
+
let capAtBreach = 0;
|
|
165
|
+
pair.reservations.forEach((r, i) => {
|
|
166
|
+
const snap = snapshotFor(idx, r.tickId, pair.campaignId);
|
|
167
|
+
if (!snap)
|
|
168
|
+
return;
|
|
169
|
+
if (i + 1 > snap.maxAttemptsPerAccount) {
|
|
170
|
+
offending.push(r);
|
|
171
|
+
capAtBreach = snap.maxAttemptsPerAccount;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
if (offending.length > 0) {
|
|
175
|
+
saf2.push({
|
|
176
|
+
campaignId: pair.campaignId,
|
|
177
|
+
portfolioAccountId: pair.portfolioAccountId,
|
|
178
|
+
eventIds: offending.map((r) => r.id),
|
|
179
|
+
detail: `${pair.reservations.length} attempts recorded, cap in force was ${capAtBreach}`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// SAF-3 — daily attempt cap on the workspace-local calendar day, judged
|
|
184
|
+
// against the cap (and timezone) in force at each reservation's tick.
|
|
185
|
+
const saf3 = [];
|
|
186
|
+
for (const pair of idx.reservedByPair.values()) {
|
|
187
|
+
const countByDay = new Map();
|
|
188
|
+
const offendingByDay = new Map();
|
|
189
|
+
for (const r of pair.reservations) {
|
|
190
|
+
const snap = snapshotFor(idx, r.tickId, pair.campaignId);
|
|
191
|
+
if (!snap)
|
|
192
|
+
continue;
|
|
193
|
+
const day = localDateString(new Date(r.at), snap.timezone);
|
|
194
|
+
const count = (countByDay.get(day) ?? 0) + 1;
|
|
195
|
+
countByDay.set(day, count);
|
|
196
|
+
if (count > snap.maxAttemptsPerDay) {
|
|
197
|
+
const o = offendingByDay.get(day) ?? { events: [], cap: snap.maxAttemptsPerDay };
|
|
198
|
+
o.events.push(r);
|
|
199
|
+
o.cap = snap.maxAttemptsPerDay;
|
|
200
|
+
offendingByDay.set(day, o);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const [day, o] of offendingByDay) {
|
|
204
|
+
saf3.push({
|
|
205
|
+
campaignId: pair.campaignId,
|
|
206
|
+
portfolioAccountId: pair.portfolioAccountId,
|
|
207
|
+
eventIds: o.events.map((r) => r.id),
|
|
208
|
+
detail: `${countByDay.get(day)} attempts on ${day}, daily cap in force was ${o.cap}`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// SAF-4 — an account the funnel suppressed in a tick must not be dispatched in
|
|
213
|
+
// that same tick (cross-tick dispatch is legitimate: suppressions expire).
|
|
214
|
+
const saf4 = [];
|
|
215
|
+
for (const t of idx.ticks) {
|
|
216
|
+
const suppressed = new Map();
|
|
217
|
+
for (const d of t.decided) {
|
|
218
|
+
if (SUPPRESSION_DECISIONS.has(d.decision)) {
|
|
219
|
+
suppressed.set(key(d.campaignId, d.portfolioAccountId), d);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (const r of t.requested) {
|
|
223
|
+
const hit = suppressed.get(key(r.campaignId, r.portfolioAccountId));
|
|
224
|
+
if (hit) {
|
|
225
|
+
saf4.push({
|
|
226
|
+
campaignId: r.campaignId,
|
|
227
|
+
portfolioAccountId: r.portfolioAccountId,
|
|
228
|
+
eventIds: [hit.id, r.id],
|
|
229
|
+
detail: `dispatched at ${r.at} in the same tick the funnel excluded it (${hit.decision})`
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// SAF-5 — per-channel rate caps, from the deployment-level tick aggregates
|
|
235
|
+
// with the engine's own per-tick capacity semantics.
|
|
236
|
+
const saf5 = [];
|
|
237
|
+
let peakRate = 0;
|
|
238
|
+
let peakChannel = "";
|
|
239
|
+
for (const t of idx.ticks) {
|
|
240
|
+
if (!t.completed)
|
|
241
|
+
continue;
|
|
242
|
+
for (const [channel, usage] of Object.entries(t.completed.channelUsage)) {
|
|
243
|
+
if (!usage)
|
|
244
|
+
continue;
|
|
245
|
+
const cap = perTickCapacity(rateFor(channel, ratesPerMinute), tickSeconds);
|
|
246
|
+
const perMinute = (usage.dispatched * 60) / tickSeconds;
|
|
247
|
+
if (perMinute > peakRate) {
|
|
248
|
+
peakRate = perMinute;
|
|
249
|
+
peakChannel = channel;
|
|
250
|
+
}
|
|
251
|
+
if (usage.dispatched > cap) {
|
|
252
|
+
saf5.push({
|
|
253
|
+
eventIds: [t.completed.id],
|
|
254
|
+
detail: `tick ${t.tickId}: ${usage.dispatched} ${channel} dispatches, per-tick cap is ${cap}`
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// SAF-6 — at-most-once: every dispatch has its reservation in the same tick,
|
|
260
|
+
// one dispatch per reservation, and no provider ref appears twice.
|
|
261
|
+
const saf6 = [];
|
|
262
|
+
for (const t of idx.ticks) {
|
|
263
|
+
const perPair = new Map();
|
|
264
|
+
const slot = (campaignId, portfolioAccountId) => {
|
|
265
|
+
const k = key(campaignId, portfolioAccountId);
|
|
266
|
+
let sl = perPair.get(k);
|
|
267
|
+
if (!sl) {
|
|
268
|
+
sl = { campaignId, portfolioAccountId, reserved: 0, requested: [] };
|
|
269
|
+
perPair.set(k, sl);
|
|
270
|
+
}
|
|
271
|
+
return sl;
|
|
272
|
+
};
|
|
273
|
+
for (const r of t.reserved)
|
|
274
|
+
slot(r.campaignId, r.portfolioAccountId).reserved += 1;
|
|
275
|
+
for (const d of t.requested)
|
|
276
|
+
slot(d.campaignId, d.portfolioAccountId).requested.push(d);
|
|
277
|
+
for (const sl of perPair.values()) {
|
|
278
|
+
if (sl.requested.length > sl.reserved) {
|
|
279
|
+
saf6.push({
|
|
280
|
+
campaignId: sl.campaignId,
|
|
281
|
+
portfolioAccountId: sl.portfolioAccountId,
|
|
282
|
+
eventIds: sl.requested.map((d) => d.id),
|
|
283
|
+
detail: `${sl.requested.length} dispatch(es) against ${sl.reserved} reservation(s) in tick ${t.tickId}`
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const refSeen = new Map();
|
|
289
|
+
for (const s of idx.succeeded) {
|
|
290
|
+
const prior = refSeen.get(s.providerRef);
|
|
291
|
+
if (prior) {
|
|
292
|
+
saf6.push({
|
|
293
|
+
campaignId: s.campaignId,
|
|
294
|
+
portfolioAccountId: s.portfolioAccountId,
|
|
295
|
+
eventIds: [prior.id, s.id],
|
|
296
|
+
detail: `provider ref ${s.providerRef} appears on two successful dispatches`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
refSeen.set(s.providerRef, s);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// PERF-1 — ticks must finish inside the tick interval.
|
|
304
|
+
const perf1 = [];
|
|
305
|
+
const durations = [];
|
|
306
|
+
for (const t of idx.ticks) {
|
|
307
|
+
if (!t.completed)
|
|
308
|
+
continue;
|
|
309
|
+
durations.push(t.completed.durationMs);
|
|
310
|
+
if (t.completed.durationMs > tickSeconds * 1000) {
|
|
311
|
+
perf1.push({
|
|
312
|
+
eventIds: [t.completed.id],
|
|
313
|
+
detail: `tick ${t.tickId} ran ${Math.round(t.completed.durationMs)}ms, interval is ${tickSeconds * 1000}ms`
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// PERF-2 — dispatch latency p95.
|
|
318
|
+
const allDispatches = [...idx.succeeded, ...idx.failed];
|
|
319
|
+
const latencies = allDispatches.map((d) => d.latencyMs);
|
|
320
|
+
const latP95 = p95(latencies);
|
|
321
|
+
const perf2 = [];
|
|
322
|
+
if (latencies.length > 0 && latP95 > thresholds.dispatchLatencyP95Ms) {
|
|
323
|
+
const worst = allDispatches.reduce((w, d) => (d.latencyMs > w.latencyMs ? d : w));
|
|
324
|
+
perf2.push({
|
|
325
|
+
campaignId: worst.campaignId,
|
|
326
|
+
portfolioAccountId: worst.portfolioAccountId,
|
|
327
|
+
eventIds: [worst.id],
|
|
328
|
+
detail: `p95 ${Math.round(latP95)}ms over ${latencies.length} dispatches (threshold ${thresholds.dispatchLatencyP95Ms}ms; worst ${Math.round(worst.latencyMs)}ms)`
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// PERF-3 — per-channel error rate.
|
|
332
|
+
const perf3 = [];
|
|
333
|
+
const byChannel = new Map();
|
|
334
|
+
for (const s of idx.succeeded) {
|
|
335
|
+
const c = byChannel.get(s.channel) ?? { ok: 0, failed: [] };
|
|
336
|
+
c.ok += 1;
|
|
337
|
+
byChannel.set(s.channel, c);
|
|
338
|
+
}
|
|
339
|
+
for (const f of idx.failed) {
|
|
340
|
+
const c = byChannel.get(f.channel) ?? { ok: 0, failed: [] };
|
|
341
|
+
c.failed.push(f);
|
|
342
|
+
byChannel.set(f.channel, c);
|
|
343
|
+
}
|
|
344
|
+
let worstRate = 0;
|
|
345
|
+
for (const [channel, c] of [...byChannel.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
346
|
+
const total = c.ok + c.failed.length;
|
|
347
|
+
const rate = total === 0 ? 0 : c.failed.length / total;
|
|
348
|
+
worstRate = Math.max(worstRate, rate);
|
|
349
|
+
if (rate > thresholds.maxErrorRate) {
|
|
350
|
+
perf3.push({
|
|
351
|
+
eventIds: c.failed.map((f) => f.id),
|
|
352
|
+
detail: `${channel}: ${c.failed.length}/${total} dispatches failed (${(rate * 100).toFixed(1)}%, threshold ${(thresholds.maxErrorRate * 100).toFixed(1)}%)`
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// PERF-4 — budget utilization: budget_exhausted while the channel bucket had
|
|
357
|
+
// tokens left (voice channels share one bucket; skipped when the deployment
|
|
358
|
+
// per-tick cap was the limiter).
|
|
359
|
+
const perf4 = [];
|
|
360
|
+
for (const t of idx.ticks) {
|
|
361
|
+
if (!t.completed || t.completed.perTickMaxReached)
|
|
362
|
+
continue;
|
|
363
|
+
const starved = t.decided.filter((d) => d.decision === "budget_exhausted");
|
|
364
|
+
if (starved.length === 0)
|
|
365
|
+
continue;
|
|
366
|
+
const usage = t.completed.channelUsage;
|
|
367
|
+
// Budgets are per pacing bucket: sum the bucket's channels' consumption
|
|
368
|
+
// against the shared (max-reported) budget. bucketOf is the single encoding
|
|
369
|
+
// of which channels share a bucket.
|
|
370
|
+
const spentAll = (channel) => {
|
|
371
|
+
if (!channel)
|
|
372
|
+
return true;
|
|
373
|
+
const bucket = bucketOf(channel);
|
|
374
|
+
let dispatched = 0;
|
|
375
|
+
let budget = 0;
|
|
376
|
+
for (const [ch, u] of Object.entries(usage)) {
|
|
377
|
+
if (!u || bucketOf(ch) !== bucket)
|
|
378
|
+
continue;
|
|
379
|
+
dispatched += u.dispatched;
|
|
380
|
+
budget = Math.max(budget, u.budget);
|
|
381
|
+
}
|
|
382
|
+
return dispatched >= budget;
|
|
383
|
+
};
|
|
384
|
+
for (const d of starved) {
|
|
385
|
+
const snap = snapshotFor(idx, t.tickId, d.campaignId);
|
|
386
|
+
if (!snap || spentAll(snap.channel))
|
|
387
|
+
continue;
|
|
388
|
+
perf4.push({
|
|
389
|
+
campaignId: d.campaignId,
|
|
390
|
+
portfolioAccountId: d.portfolioAccountId,
|
|
391
|
+
eventIds: [d.id, t.completed.id],
|
|
392
|
+
detail: `skipped as budget_exhausted in tick ${t.tickId} while the ${snap.channel} budget had tokens left`
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// LIVE-1 — no account starves: consecutive budget_exhausted ticks are bounded.
|
|
397
|
+
const live1 = [];
|
|
398
|
+
const streaks = new Map();
|
|
399
|
+
let maxStreak = 0;
|
|
400
|
+
for (const t of idx.ticks) {
|
|
401
|
+
for (const d of t.decided) {
|
|
402
|
+
const k = key(d.campaignId, d.portfolioAccountId);
|
|
403
|
+
const s = streaks.get(k) ?? { streak: 0, eventIds: [], flagged: false };
|
|
404
|
+
if (d.decision === "budget_exhausted") {
|
|
405
|
+
s.streak += 1;
|
|
406
|
+
s.eventIds.push(d.id);
|
|
407
|
+
maxStreak = Math.max(maxStreak, s.streak);
|
|
408
|
+
if (s.streak > thresholds.livenessTicks && !s.flagged) {
|
|
409
|
+
s.flagged = true;
|
|
410
|
+
live1.push({
|
|
411
|
+
campaignId: d.campaignId,
|
|
412
|
+
portfolioAccountId: d.portfolioAccountId,
|
|
413
|
+
eventIds: [...s.eventIds],
|
|
414
|
+
detail: `starved for ${s.streak} consecutive ticks (threshold ${thresholds.livenessTicks})`
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
s.streak = 0;
|
|
420
|
+
s.eventIds = [];
|
|
421
|
+
s.flagged = false;
|
|
422
|
+
}
|
|
423
|
+
streaks.set(k, s);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const invariants = [
|
|
427
|
+
result("SAF-1", "window compliance", "workspace", saf1),
|
|
428
|
+
result("SAF-2", "lifetime attempt cap", "workspace", saf2),
|
|
429
|
+
result("SAF-3", "daily attempt cap", "workspace", saf3),
|
|
430
|
+
result("SAF-4", "suppression respected", "workspace", saf4),
|
|
431
|
+
result("SAF-5", "channel rate caps", "deployment", saf5, peakChannel ? `peak ${peakRate.toFixed(1)}/min on ${peakChannel}` : undefined),
|
|
432
|
+
result("SAF-6", "at-most-once dispatch", "workspace", saf6),
|
|
433
|
+
result("PERF-1", "tick duration", "deployment", perf1, durations.length > 0 ? `p95 ${Math.round(p95(durations))}ms` : undefined),
|
|
434
|
+
result("PERF-2", "dispatch latency p95", "workspace", perf2, latencies.length > 0 ? `p95 ${Math.round(latP95)}ms` : undefined),
|
|
435
|
+
result("PERF-3", "dispatch error rate", "workspace", perf3, latencies.length > 0 ? `worst ${(worstRate * 100).toFixed(1)}%` : undefined),
|
|
436
|
+
result("PERF-4", "budget utilization", "workspace", perf4),
|
|
437
|
+
result("LIVE-1", "ticks to first attempt", "workspace", live1, `max streak ${maxStreak}`)
|
|
438
|
+
];
|
|
439
|
+
// Per-campaign breakdown + violation attribution.
|
|
440
|
+
const campaigns = new Map();
|
|
441
|
+
const breakdown = (campaignId, workspaceRef) => {
|
|
442
|
+
let b = campaigns.get(campaignId);
|
|
443
|
+
if (!b) {
|
|
444
|
+
b = {
|
|
445
|
+
campaignId,
|
|
446
|
+
workspaceRef,
|
|
447
|
+
ticksSeen: 0,
|
|
448
|
+
considered: 0,
|
|
449
|
+
dispatched: 0,
|
|
450
|
+
failed: 0,
|
|
451
|
+
suppressed: 0,
|
|
452
|
+
violations: {}
|
|
453
|
+
};
|
|
454
|
+
campaigns.set(campaignId, b);
|
|
455
|
+
}
|
|
456
|
+
return b;
|
|
457
|
+
};
|
|
458
|
+
for (const t of idx.ticks) {
|
|
459
|
+
for (const e of t.evaluated) {
|
|
460
|
+
const b = breakdown(e.campaignId, e.workspaceRef);
|
|
461
|
+
b.ticksSeen += 1;
|
|
462
|
+
if (e.campaignName)
|
|
463
|
+
b.name = e.campaignName;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const pairs = new Set();
|
|
467
|
+
for (const d of idx.decided) {
|
|
468
|
+
const b = breakdown(d.campaignId, d.workspaceRef);
|
|
469
|
+
b.considered += 1;
|
|
470
|
+
pairs.add(key(d.campaignId, d.portfolioAccountId));
|
|
471
|
+
if (d.decision === "dispatched")
|
|
472
|
+
b.dispatched += 1;
|
|
473
|
+
else if (d.decision === "dispatch_failed")
|
|
474
|
+
b.failed += 1;
|
|
475
|
+
else if (SUPPRESSION_DECISIONS.has(d.decision))
|
|
476
|
+
b.suppressed += 1;
|
|
477
|
+
}
|
|
478
|
+
for (const inv of invariants) {
|
|
479
|
+
for (const v of inv.violations) {
|
|
480
|
+
if (!v.campaignId)
|
|
481
|
+
continue;
|
|
482
|
+
const b = campaigns.get(v.campaignId);
|
|
483
|
+
if (!b)
|
|
484
|
+
continue;
|
|
485
|
+
b.violations[inv.id] = (b.violations[inv.id] ?? 0) + 1;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const gaps = idx.ticks
|
|
489
|
+
.filter((t) => t.started && !t.completed)
|
|
490
|
+
.map((t) => ({ tickId: t.tickId, at: t.started.at }));
|
|
491
|
+
return {
|
|
492
|
+
verdict: invariants.some((i) => i.verdict === "fail") ? "fail" : "pass",
|
|
493
|
+
invariants,
|
|
494
|
+
campaigns: [...campaigns.values()].sort((a, b) => a.campaignId.localeCompare(b.campaignId)),
|
|
495
|
+
gaps,
|
|
496
|
+
totals: {
|
|
497
|
+
events: sorted.length,
|
|
498
|
+
ticks: idx.ticks.length,
|
|
499
|
+
campaigns: campaigns.size,
|
|
500
|
+
accountsConsidered: pairs.size
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/evaluation/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/evaluation/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/evaluation/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Contracts for the engine judge: the parameter set `evaluate` runs against and
|
|
4
|
+
* the scorecard it produces. Thresholds default here; rate caps and the tick
|
|
5
|
+
* interval mirror the deployment's engine config and are supplied by the caller
|
|
6
|
+
* (the events endpoint returns them alongside the stream).
|
|
7
|
+
*/
|
|
8
|
+
export declare const evaluationParametersSchema: z.ZodObject<{
|
|
9
|
+
tickSeconds: z.ZodNumber;
|
|
10
|
+
ratesPerMinute: z.ZodObject<{
|
|
11
|
+
voice: z.ZodNumber;
|
|
12
|
+
sms: z.ZodNumber;
|
|
13
|
+
email: z.ZodNumber;
|
|
14
|
+
whatsApp: z.ZodNumber;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
thresholds: z.ZodPrefault<z.ZodObject<{
|
|
17
|
+
dispatchLatencyP95Ms: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
maxErrorRate: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
livenessTicks: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type EvaluationParameters = z.infer<typeof evaluationParametersSchema>;
|
|
23
|
+
export type InvariantId = "SAF-1" | "SAF-2" | "SAF-3" | "SAF-4" | "SAF-5" | "SAF-6" | "PERF-1" | "PERF-2" | "PERF-3" | "PERF-4" | "LIVE-1";
|
|
24
|
+
export type Verdict = "pass" | "fail";
|
|
25
|
+
/**
|
|
26
|
+
* Which slice of the deployment an invariant was verified over. A
|
|
27
|
+
* workspace-filtered stream still verifies `deployment`-scoped invariants from
|
|
28
|
+
* the shared tick lifecycle events (aggregate counts).
|
|
29
|
+
*/
|
|
30
|
+
export type InvariantScope = "workspace" | "deployment";
|
|
31
|
+
/** One proven breach, traceable through the correlation spine. */
|
|
32
|
+
export interface Violation {
|
|
33
|
+
campaignId?: string;
|
|
34
|
+
portfolioAccountId?: string;
|
|
35
|
+
/** The events that evidence the breach. */
|
|
36
|
+
eventIds: string[];
|
|
37
|
+
detail: string;
|
|
38
|
+
}
|
|
39
|
+
export interface InvariantResult {
|
|
40
|
+
id: InvariantId;
|
|
41
|
+
/** Human name, stable for display. */
|
|
42
|
+
name: string;
|
|
43
|
+
scope: InvariantScope;
|
|
44
|
+
verdict: Verdict;
|
|
45
|
+
/** Measured value where the invariant is a threshold (e.g. "p95 2410ms"). */
|
|
46
|
+
metric?: string;
|
|
47
|
+
violations: Violation[];
|
|
48
|
+
}
|
|
49
|
+
/** Per-campaign slice of the run. */
|
|
50
|
+
export interface CampaignBreakdown {
|
|
51
|
+
campaignId: string;
|
|
52
|
+
/** Display name from the stream's `campaign.evaluated` events (absent on old streams). */
|
|
53
|
+
name?: string;
|
|
54
|
+
workspaceRef: string;
|
|
55
|
+
/** Ticks in which the campaign was evaluated. */
|
|
56
|
+
ticksSeen: number;
|
|
57
|
+
/** Account decisions recorded (one per considered account per tick). */
|
|
58
|
+
considered: number;
|
|
59
|
+
dispatched: number;
|
|
60
|
+
failed: number;
|
|
61
|
+
/** Funnel exclusions (missing contact, suppressions, caps). */
|
|
62
|
+
suppressed: number;
|
|
63
|
+
/** Violation count per invariant attributed to this campaign. */
|
|
64
|
+
violations: Partial<Record<InvariantId, number>>;
|
|
65
|
+
}
|
|
66
|
+
/** A tick that started but never completed (crash or lost flush) — reported, not failed. */
|
|
67
|
+
export interface StreamGap {
|
|
68
|
+
tickId: string;
|
|
69
|
+
at: string;
|
|
70
|
+
}
|
|
71
|
+
export interface Scorecard {
|
|
72
|
+
verdict: Verdict;
|
|
73
|
+
invariants: InvariantResult[];
|
|
74
|
+
campaigns: CampaignBreakdown[];
|
|
75
|
+
gaps: StreamGap[];
|
|
76
|
+
totals: {
|
|
77
|
+
events: number;
|
|
78
|
+
ticks: number;
|
|
79
|
+
campaigns: number;
|
|
80
|
+
/** Distinct (campaign, account) pairs that received at least one decision. */
|
|
81
|
+
accountsConsidered: number;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=scorecard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorecard.d.ts","sourceRoot":"","sources":["../../src/evaluation/scorecard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AAEH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;iBAoBrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtC;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,YAAY,CAAC;AAExD,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,WAAW,CAAC;IAChB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAED,4FAA4F;AAC5F,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,8EAA8E;QAC9E,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH"}
|