@nifrajs/testing 2.14.1 → 3.2.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/README.md +8 -0
- package/dist/agent-certification.d.ts +41 -0
- package/dist/agent-certification.d.ts.map +1 -0
- package/dist/agent-certification.js +242 -0
- package/dist/agent-certification.js.map +1 -0
- package/dist/agent-eval.d.ts +192 -0
- package/dist/agent-eval.d.ts.map +1 -0
- package/dist/agent-eval.js +410 -0
- package/dist/agent-eval.js.map +1 -0
- package/dist/contract-lab.d.ts +43 -0
- package/dist/contract-lab.d.ts.map +1 -0
- package/dist/contract-lab.js +139 -0
- package/dist/contract-lab.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
- package/src/agent-certification.ts +296 -0
- package/src/agent-eval.ts +660 -0
- package/src/contract-lab.ts +196 -0
- package/src/index.ts +52 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/testing/agent-eval` - a deterministic, evidence-only eval harness for agent runs.
|
|
3
|
+
*
|
|
4
|
+
* A suite is a bounded set of cases, each scored by one or more typed rubrics. A rubric is a closed
|
|
5
|
+
* contract: an ordered outcome allow-list, a finite score range, and a set of named numeric metrics.
|
|
6
|
+
* Nothing else is admitted - a verdict carrying free-form evaluator prose, an out-of-range score, or
|
|
7
|
+
* an unknown outcome is rejected at parse time. There is no opaque blended "agent score": the report
|
|
8
|
+
* is the evidence contract (ids, codes, bounded numbers, digests) and nothing more.
|
|
9
|
+
*
|
|
10
|
+
* Determinism is structural: cases run in sorted id order and every digest is taken over a canonical,
|
|
11
|
+
* order-independent projection, so reordering declarations cannot change a digest. Baseline
|
|
12
|
+
* comparison classifies each (case, rubric) as equal / improved / tolerated / regressed / missing /
|
|
13
|
+
* incomparable against explicit tolerances, and every regression carries a stable, addressable id.
|
|
14
|
+
*/
|
|
15
|
+
import { FailureInjectedError, runFailureScenario } from "./failure-lab.js";
|
|
16
|
+
import { runFaultProfile } from "./fault-profile.js";
|
|
17
|
+
import { proveIdempotency } from "./idempotency.js";
|
|
18
|
+
const TOKEN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
19
|
+
const MAX_CASES = 512;
|
|
20
|
+
const MAX_RUBRICS = 32;
|
|
21
|
+
const MAX_OUTCOMES = 32;
|
|
22
|
+
const MAX_METRICS = 32;
|
|
23
|
+
/** Thrown by {@link assertAgentEvalBaseline} when a comparison contains a failing code. */
|
|
24
|
+
export class AgentEvalRegressionError extends Error {
|
|
25
|
+
comparison;
|
|
26
|
+
constructor(comparison) {
|
|
27
|
+
super(`agent eval ${comparison.suiteId}: ${comparison.regressions.length} regression(s): ` +
|
|
28
|
+
comparison.regressions.join(", "));
|
|
29
|
+
this.comparison = comparison;
|
|
30
|
+
this.name = "AgentEvalRegressionError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class AgentFailureMatrixError extends Error {
|
|
34
|
+
report;
|
|
35
|
+
constructor(report) {
|
|
36
|
+
super(`agent failure matrix: ${report.cases
|
|
37
|
+
.filter((entry) => !entry.ok)
|
|
38
|
+
.map((entry) => entry.regressionId)
|
|
39
|
+
.join(", ")}`);
|
|
40
|
+
this.report = report;
|
|
41
|
+
this.name = "AgentFailureMatrixError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const MATRIX_CASES = [
|
|
45
|
+
{ id: "approval-denied", kind: "approval" },
|
|
46
|
+
{ id: "cancel-race", kind: "cancellation" },
|
|
47
|
+
{ id: "cursor-gap", kind: "cursor" },
|
|
48
|
+
{ id: "deployment-loss", kind: "deployment" },
|
|
49
|
+
{ id: "lease-expired", kind: "lease" },
|
|
50
|
+
{ id: "model-reply", kind: "model" },
|
|
51
|
+
{ id: "registry-drift", kind: "registry" },
|
|
52
|
+
{ id: "tool-effect", kind: "tool" },
|
|
53
|
+
];
|
|
54
|
+
function matrixSchedule(caseId) {
|
|
55
|
+
return [{ point: `agent.${caseId}`, kind: "crash" }];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run the same token-only crash fixture at each agent boundary. The implementation intentionally
|
|
59
|
+
* delegates scheduling and replay identity to the existing failure laboratory; this is a matrix
|
|
60
|
+
* consumer, not a second fault engine.
|
|
61
|
+
*/
|
|
62
|
+
export async function runAgentFailureMatrix(options = {}) {
|
|
63
|
+
const seed = Number.isFinite(options.seed) ? Math.trunc(options.seed) >>> 0 : 0x4e494652;
|
|
64
|
+
const cases = [];
|
|
65
|
+
for (const entry of MATRIX_CASES) {
|
|
66
|
+
const caseId = entry.id;
|
|
67
|
+
const report = await runFailureScenario({
|
|
68
|
+
name: `agent-${caseId}`,
|
|
69
|
+
execute(lab) {
|
|
70
|
+
lab.checkpoint(`agent.${caseId}`);
|
|
71
|
+
return true;
|
|
72
|
+
},
|
|
73
|
+
verify: ({ error }) => error instanceof FailureInjectedError,
|
|
74
|
+
}, { seed, schedule: matrixSchedule(caseId) });
|
|
75
|
+
const scheduleToken = `${report.replay.seed}:${caseId}:1`;
|
|
76
|
+
const regressionId = `${caseId}/${scheduleToken}`;
|
|
77
|
+
cases.push({ id: caseId, kind: entry.kind, scheduleToken, ok: report.ok, regressionId });
|
|
78
|
+
}
|
|
79
|
+
const digest = await sha256(canonical({ version: 1, seed, cases }));
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
version: 1,
|
|
82
|
+
seed,
|
|
83
|
+
cases: Object.freeze(cases),
|
|
84
|
+
digest,
|
|
85
|
+
ok: cases.every((entry) => entry.ok),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export function assertAgentFailureMatrix(report) {
|
|
89
|
+
if (!report.ok)
|
|
90
|
+
throw new AgentFailureMatrixError(report);
|
|
91
|
+
}
|
|
92
|
+
/** Compose existing trajectory, fault, contract, idempotency, and certification owners. */
|
|
93
|
+
export async function runAgentEvalComposition(options) {
|
|
94
|
+
const suite = await options.suite.run();
|
|
95
|
+
const components = [];
|
|
96
|
+
const regressionIds = [];
|
|
97
|
+
if (options.trajectory !== undefined) {
|
|
98
|
+
const result = await options.trajectory();
|
|
99
|
+
components.push({ id: "trajectory", ok: result.ok, digest: result.digest });
|
|
100
|
+
for (const id of result.regressionIds ?? [])
|
|
101
|
+
regressionIds.push(id);
|
|
102
|
+
}
|
|
103
|
+
if (options.faultProfile !== undefined) {
|
|
104
|
+
const result = await runFaultProfile(options.faultProfile.profile, options.faultProfile.seed === undefined ? {} : { seed: options.faultProfile.seed });
|
|
105
|
+
components.push({ id: "fault-profile", ok: result.ok });
|
|
106
|
+
if (!result.ok)
|
|
107
|
+
regressionIds.push(`fault-profile/${result.name}`);
|
|
108
|
+
}
|
|
109
|
+
if (options.contractLab !== undefined) {
|
|
110
|
+
const result = await options.contractLab();
|
|
111
|
+
components.push({
|
|
112
|
+
id: "contract-lab",
|
|
113
|
+
ok: result.ok,
|
|
114
|
+
...(result.digest === undefined ? {} : { digest: result.digest }),
|
|
115
|
+
...(result.code === undefined ? {} : { code: result.code }),
|
|
116
|
+
});
|
|
117
|
+
if (!result.ok)
|
|
118
|
+
regressionIds.push(`contract-lab/${result.code ?? "failed"}`);
|
|
119
|
+
}
|
|
120
|
+
if (options.idempotency !== undefined) {
|
|
121
|
+
const result = await proveIdempotency(options.idempotency);
|
|
122
|
+
components.push({ id: "idempotency", ok: result.ok });
|
|
123
|
+
if (!result.ok)
|
|
124
|
+
regressionIds.push("idempotency/divergent");
|
|
125
|
+
}
|
|
126
|
+
if (options.certification !== undefined) {
|
|
127
|
+
const result = await options.certification();
|
|
128
|
+
components.push({
|
|
129
|
+
id: "certification",
|
|
130
|
+
ok: result.ok,
|
|
131
|
+
...(result.digest === undefined ? {} : { digest: result.digest }),
|
|
132
|
+
...(result.code === undefined ? {} : { code: result.code }),
|
|
133
|
+
});
|
|
134
|
+
if (!result.ok)
|
|
135
|
+
regressionIds.push(`certification/${result.code ?? "failed"}`);
|
|
136
|
+
}
|
|
137
|
+
const ok = regressionIds.length === 0 && components.every((entry) => entry.ok);
|
|
138
|
+
const digest = await sha256(canonical({ version: 1, suite: suite.digest, components, regressionIds }));
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
version: 1,
|
|
141
|
+
suite,
|
|
142
|
+
components: Object.freeze(components),
|
|
143
|
+
regressionIds: Object.freeze(regressionIds.sort()),
|
|
144
|
+
digest,
|
|
145
|
+
ok,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function assertAgentEval(report) {
|
|
149
|
+
if (!report.ok)
|
|
150
|
+
throw new AgentEvalRegressionError({
|
|
151
|
+
suiteId: report.suite.suiteId,
|
|
152
|
+
comparisons: [],
|
|
153
|
+
regressions: report.regressionIds,
|
|
154
|
+
digest: report.digest,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
function canonical(value) {
|
|
160
|
+
if (Array.isArray(value))
|
|
161
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
162
|
+
if (isRecord(value))
|
|
163
|
+
return `{${Object.keys(value)
|
|
164
|
+
.sort()
|
|
165
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
166
|
+
.join(",")}}`;
|
|
167
|
+
return JSON.stringify(value);
|
|
168
|
+
}
|
|
169
|
+
async function sha256(value) {
|
|
170
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
171
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
172
|
+
}
|
|
173
|
+
function assertToken(value, what) {
|
|
174
|
+
if (typeof value !== "string" || !TOKEN.test(value))
|
|
175
|
+
throw new TypeError(`agent eval: ${what} must be a bounded token`);
|
|
176
|
+
}
|
|
177
|
+
function assertOnlyKeys(record, allowed) {
|
|
178
|
+
for (const key of Object.keys(record))
|
|
179
|
+
if (!allowed.includes(key))
|
|
180
|
+
throw new TypeError(`agent eval: verdict carries unexpected key '${key}'`);
|
|
181
|
+
}
|
|
182
|
+
function validateRubric(rubric) {
|
|
183
|
+
assertToken(rubric.id, "rubric id");
|
|
184
|
+
if (rubric.outcomes.length < 1 || rubric.outcomes.length > MAX_OUTCOMES)
|
|
185
|
+
throw new RangeError(`agent eval: rubric must declare 1-${MAX_OUTCOMES} outcomes`);
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const outcome of rubric.outcomes) {
|
|
188
|
+
assertToken(outcome, "outcome code");
|
|
189
|
+
if (seen.has(outcome))
|
|
190
|
+
throw new TypeError(`agent eval: duplicate outcome '${outcome}'`);
|
|
191
|
+
seen.add(outcome);
|
|
192
|
+
}
|
|
193
|
+
if (!Number.isFinite(rubric.score.min) || !Number.isFinite(rubric.score.max))
|
|
194
|
+
throw new TypeError("agent eval: rubric score range must be finite");
|
|
195
|
+
if (rubric.score.min > rubric.score.max)
|
|
196
|
+
throw new RangeError("agent eval: rubric score min must not exceed max");
|
|
197
|
+
if (rubric.metrics !== undefined) {
|
|
198
|
+
if (rubric.metrics.length > MAX_METRICS)
|
|
199
|
+
throw new RangeError(`agent eval: rubric declares more than ${MAX_METRICS} metrics`);
|
|
200
|
+
const metricSeen = new Set();
|
|
201
|
+
for (const metric of rubric.metrics) {
|
|
202
|
+
assertToken(metric, "metric name");
|
|
203
|
+
if (metricSeen.has(metric))
|
|
204
|
+
throw new TypeError(`agent eval: duplicate metric '${metric}'`);
|
|
205
|
+
metricSeen.add(metric);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Parse one verdict against its rubric. Fails closed on unknown outcome, bad range, or stray key. */
|
|
210
|
+
export function parseRubricVerdict(rubric, value) {
|
|
211
|
+
if (!isRecord(value))
|
|
212
|
+
throw new TypeError("agent eval: verdict must be an object");
|
|
213
|
+
assertOnlyKeys(value, ["rubricId", "outcome", "score", "metrics"]);
|
|
214
|
+
if (value.rubricId !== rubric.id)
|
|
215
|
+
throw new TypeError(`agent eval: verdict rubricId '${String(value.rubricId)}' != '${rubric.id}'`);
|
|
216
|
+
if (typeof value.outcome !== "string" || !rubric.outcomes.includes(value.outcome))
|
|
217
|
+
throw new TypeError(`agent eval: unknown outcome '${String(value.outcome)}' for rubric ${rubric.id}`);
|
|
218
|
+
if (typeof value.score !== "number" || !Number.isFinite(value.score))
|
|
219
|
+
throw new TypeError(`agent eval: verdict score for ${rubric.id} must be a finite number`);
|
|
220
|
+
if (value.score < rubric.score.min || value.score > rubric.score.max)
|
|
221
|
+
throw new RangeError(`agent eval: score ${value.score} out of [${rubric.score.min}, ${rubric.score.max}] for ${rubric.id}`);
|
|
222
|
+
let metrics;
|
|
223
|
+
if (value.metrics !== undefined) {
|
|
224
|
+
if (!isRecord(value.metrics))
|
|
225
|
+
throw new TypeError("agent eval: verdict metrics must be an object");
|
|
226
|
+
const allowed = new Set(rubric.metrics ?? []);
|
|
227
|
+
const out = {};
|
|
228
|
+
for (const [key, metricValue] of Object.entries(value.metrics)) {
|
|
229
|
+
if (!allowed.has(key))
|
|
230
|
+
throw new TypeError(`agent eval: unknown metric '${key}' for ${rubric.id}`);
|
|
231
|
+
if (typeof metricValue !== "number" || !Number.isFinite(metricValue))
|
|
232
|
+
throw new TypeError(`agent eval: metric '${key}' must be a finite number`);
|
|
233
|
+
out[key] = metricValue;
|
|
234
|
+
}
|
|
235
|
+
metrics = out;
|
|
236
|
+
}
|
|
237
|
+
return Object.freeze({
|
|
238
|
+
rubricId: rubric.id,
|
|
239
|
+
outcome: value.outcome,
|
|
240
|
+
score: value.score,
|
|
241
|
+
...(metrics !== undefined ? { metrics: Object.freeze(metrics) } : {}),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// ── Suite construction and execution ──────────────────────────────────────────────────────────
|
|
245
|
+
/** Declare a bounded, deterministic eval suite. Duplicate case or rubric ids fail closed. */
|
|
246
|
+
export function defineAgentEvalSuite(spec) {
|
|
247
|
+
assertToken(spec.id, "suite id");
|
|
248
|
+
const version = spec.version ?? 1;
|
|
249
|
+
if (!Number.isInteger(version) || version < 1)
|
|
250
|
+
throw new RangeError("agent eval: suite version must be a positive integer");
|
|
251
|
+
if (spec.rubrics.length < 1 || spec.rubrics.length > MAX_RUBRICS)
|
|
252
|
+
throw new RangeError(`agent eval: suite must declare 1-${MAX_RUBRICS} rubrics`);
|
|
253
|
+
const rubricById = new Map();
|
|
254
|
+
for (const rubric of spec.rubrics) {
|
|
255
|
+
validateRubric(rubric);
|
|
256
|
+
if (rubricById.has(rubric.id))
|
|
257
|
+
throw new TypeError(`agent eval: duplicate rubric id '${rubric.id}'`);
|
|
258
|
+
rubricById.set(rubric.id, rubric);
|
|
259
|
+
}
|
|
260
|
+
if (spec.cases.length < 1 || spec.cases.length > MAX_CASES)
|
|
261
|
+
throw new RangeError(`agent eval: suite must declare 1-${MAX_CASES} cases`);
|
|
262
|
+
const caseById = new Map();
|
|
263
|
+
for (const testCase of spec.cases) {
|
|
264
|
+
assertToken(testCase.id, "case id");
|
|
265
|
+
if (caseById.has(testCase.id))
|
|
266
|
+
throw new TypeError(`agent eval: duplicate case id '${testCase.id}'`);
|
|
267
|
+
caseById.set(testCase.id, testCase);
|
|
268
|
+
}
|
|
269
|
+
const caseIds = [...caseById.keys()].sort();
|
|
270
|
+
const rubricIds = [...rubricById.keys()].sort();
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
id: spec.id,
|
|
273
|
+
version,
|
|
274
|
+
caseIds: Object.freeze(caseIds),
|
|
275
|
+
rubricIds: Object.freeze(rubricIds),
|
|
276
|
+
async run() {
|
|
277
|
+
const rubricShapes = await Promise.all(rubricIds.map((id) => shapeOf(rubricById.get(id))));
|
|
278
|
+
const cases = [];
|
|
279
|
+
for (const caseId of caseIds) {
|
|
280
|
+
const produced = await caseById.get(caseId).evaluate();
|
|
281
|
+
const perRubric = new Map();
|
|
282
|
+
for (const raw of produced) {
|
|
283
|
+
if (!isRecord(raw) || typeof raw.rubricId !== "string")
|
|
284
|
+
throw new TypeError(`agent eval: case '${caseId}' produced a verdict without a rubricId`);
|
|
285
|
+
const rubric = rubricById.get(raw.rubricId);
|
|
286
|
+
if (rubric === undefined)
|
|
287
|
+
throw new TypeError(`agent eval: case '${caseId}' scored unknown rubric '${raw.rubricId}'`);
|
|
288
|
+
if (perRubric.has(rubric.id))
|
|
289
|
+
throw new TypeError(`agent eval: case '${caseId}' scored rubric '${rubric.id}' twice`);
|
|
290
|
+
perRubric.set(rubric.id, parseRubricVerdict(rubric, raw));
|
|
291
|
+
}
|
|
292
|
+
const verdicts = [...perRubric.values()].sort((a, b) => a.rubricId.localeCompare(b.rubricId));
|
|
293
|
+
cases.push({
|
|
294
|
+
caseId,
|
|
295
|
+
verdicts: Object.freeze(verdicts),
|
|
296
|
+
digest: await sha256(canonical({ caseId, verdicts })),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const digest = await sha256(canonical({
|
|
300
|
+
suiteId: spec.id,
|
|
301
|
+
suiteVersion: version,
|
|
302
|
+
rubricShapes,
|
|
303
|
+
cases: cases.map((entry) => ({ caseId: entry.caseId, digest: entry.digest })),
|
|
304
|
+
}));
|
|
305
|
+
return Object.freeze({
|
|
306
|
+
version: 1,
|
|
307
|
+
suiteId: spec.id,
|
|
308
|
+
suiteVersion: version,
|
|
309
|
+
rubricShapes: Object.freeze(rubricShapes),
|
|
310
|
+
cases: Object.freeze(cases),
|
|
311
|
+
digest,
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async function shapeOf(rubric) {
|
|
317
|
+
const higherIsBetter = rubric.higherIsBetter ?? true;
|
|
318
|
+
const projection = {
|
|
319
|
+
rubricId: rubric.id,
|
|
320
|
+
outcomes: rubric.outcomes,
|
|
321
|
+
scoreMin: rubric.score.min,
|
|
322
|
+
scoreMax: rubric.score.max,
|
|
323
|
+
higherIsBetter,
|
|
324
|
+
};
|
|
325
|
+
return Object.freeze({ ...projection, digest: await sha256(canonical(projection)) });
|
|
326
|
+
}
|
|
327
|
+
// ── Baseline comparison ───────────────────────────────────────────────────────────────────────
|
|
328
|
+
/** Compare a fresh report against a baseline report. Every (case, rubric) gets a stable id. */
|
|
329
|
+
export async function compareAgentEvalBaseline(baseline, current, options = {}) {
|
|
330
|
+
const baseShapes = new Map(baseline.rubricShapes.map((shape) => [shape.rubricId, shape]));
|
|
331
|
+
const curShapes = new Map(current.rubricShapes.map((shape) => [shape.rubricId, shape]));
|
|
332
|
+
const baseVerdicts = indexVerdicts(baseline);
|
|
333
|
+
const curVerdicts = indexVerdicts(current);
|
|
334
|
+
const keys = new Set([...baseVerdicts.keys(), ...curVerdicts.keys()]);
|
|
335
|
+
const comparisons = [];
|
|
336
|
+
for (const key of [...keys].sort()) {
|
|
337
|
+
const [caseId, rubricId] = key.split("");
|
|
338
|
+
const regressionId = `${current.suiteId}/${caseId}/${rubricId}`;
|
|
339
|
+
const base = baseVerdicts.get(key);
|
|
340
|
+
const cur = curVerdicts.get(key);
|
|
341
|
+
const code = classify(base, cur, baseShapes.get(rubricId), curShapes.get(rubricId), rubricId, options);
|
|
342
|
+
comparisons.push(Object.freeze({
|
|
343
|
+
caseId,
|
|
344
|
+
rubricId,
|
|
345
|
+
code,
|
|
346
|
+
regressionId,
|
|
347
|
+
...(base !== undefined ? { baselineScore: base.score } : {}),
|
|
348
|
+
...(cur !== undefined ? { currentScore: cur.score } : {}),
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
const failOn = new Set(options.failOn ?? ["regressed"]);
|
|
352
|
+
const regressions = comparisons
|
|
353
|
+
.filter((entry) => failOn.has(entry.code))
|
|
354
|
+
.map((entry) => entry.regressionId);
|
|
355
|
+
const digest = await sha256(canonical({ suiteId: current.suiteId, comparisons }));
|
|
356
|
+
return Object.freeze({
|
|
357
|
+
suiteId: current.suiteId,
|
|
358
|
+
comparisons: Object.freeze(comparisons),
|
|
359
|
+
regressions: Object.freeze(regressions),
|
|
360
|
+
digest,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** Assert no failing comparison. Throws {@link AgentEvalRegressionError} with the stable ids. */
|
|
364
|
+
export async function assertAgentEvalBaseline(baseline, current, options = {}) {
|
|
365
|
+
const comparison = await compareAgentEvalBaseline(baseline, current, options);
|
|
366
|
+
if (comparison.regressions.length > 0)
|
|
367
|
+
throw new AgentEvalRegressionError(comparison);
|
|
368
|
+
return comparison;
|
|
369
|
+
}
|
|
370
|
+
function indexVerdicts(report) {
|
|
371
|
+
const map = new Map();
|
|
372
|
+
for (const entry of report.cases)
|
|
373
|
+
for (const verdict of entry.verdicts)
|
|
374
|
+
map.set(`${entry.caseId}${verdict.rubricId}`, verdict);
|
|
375
|
+
return map;
|
|
376
|
+
}
|
|
377
|
+
function classify(base, cur, baseShape, curShape, rubricId, options) {
|
|
378
|
+
if (base === undefined || cur === undefined)
|
|
379
|
+
return "missing";
|
|
380
|
+
if (baseShape === undefined || curShape === undefined || baseShape.digest !== curShape.digest)
|
|
381
|
+
return "incomparable";
|
|
382
|
+
const baseRank = curShape.outcomes.indexOf(base.outcome);
|
|
383
|
+
const curRank = curShape.outcomes.indexOf(cur.outcome);
|
|
384
|
+
if (baseRank !== curRank)
|
|
385
|
+
return curRank > baseRank ? "improved" : "regressed";
|
|
386
|
+
// Same outcome: compare the score in the rubric's better-direction.
|
|
387
|
+
if (cur.score === base.score)
|
|
388
|
+
return "equal";
|
|
389
|
+
const better = curShape.higherIsBetter ? cur.score > base.score : cur.score < base.score;
|
|
390
|
+
if (better)
|
|
391
|
+
return "improved";
|
|
392
|
+
const worseBy = Math.abs(cur.score - base.score);
|
|
393
|
+
const tolerance = resolveTolerance(rubricId, options);
|
|
394
|
+
const allowed = Math.max(tolerance.abs, tolerance.rel * Math.abs(base.score));
|
|
395
|
+
return worseBy <= allowed ? "tolerated" : "regressed";
|
|
396
|
+
}
|
|
397
|
+
function resolveTolerance(rubricId, options) {
|
|
398
|
+
let abs = 0;
|
|
399
|
+
let rel = 0;
|
|
400
|
+
for (const tolerance of options.tolerances ?? []) {
|
|
401
|
+
if (tolerance.rubricId !== undefined && tolerance.rubricId !== rubricId)
|
|
402
|
+
continue;
|
|
403
|
+
if (tolerance.abs !== undefined)
|
|
404
|
+
abs = Math.max(abs, tolerance.abs);
|
|
405
|
+
if (tolerance.rel !== undefined)
|
|
406
|
+
rel = Math.max(rel, tolerance.rel);
|
|
407
|
+
}
|
|
408
|
+
return { abs, rel };
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=agent-eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-eval.js","sourceRoot":"","sources":["../src/agent-eval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral HTTP contract witnesses.
|
|
3
|
+
*
|
|
4
|
+
* The witness set is deliberately made only of Web `Request`/`Response` values. Runtime suites provide
|
|
5
|
+
* the handler under test - the full Bun server, the compact edge server, or a native Node/Deno adapter -
|
|
6
|
+
* and this module compares the wire contract without importing any runtime-specific implementation.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContractLabHandler {
|
|
9
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
10
|
+
}
|
|
11
|
+
export interface ContractLabServer {
|
|
12
|
+
readonly origin: string;
|
|
13
|
+
stop(): void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface ContractLabRuntimeAdapter {
|
|
16
|
+
start(app: ContractLabHandler): ContractLabServer | Promise<ContractLabServer>;
|
|
17
|
+
}
|
|
18
|
+
export interface ContractLabWitness {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly request: {
|
|
21
|
+
readonly method: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
24
|
+
readonly body?: string;
|
|
25
|
+
};
|
|
26
|
+
readonly expected: {
|
|
27
|
+
readonly status: number;
|
|
28
|
+
readonly contentType: "json" | "text";
|
|
29
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
30
|
+
readonly body: unknown;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The shared cross-runtime wire contract. Keep this list small and stable: it is evidence, not a load test. */
|
|
34
|
+
export declare const contractLabWitnesses: readonly ContractLabWitness[];
|
|
35
|
+
/** Execute every witness against one runtime and throw a bounded, replayable mismatch. */
|
|
36
|
+
export declare function runContractLab(handler: ContractLabHandler, origin?: string): Promise<void>;
|
|
37
|
+
/** Execute the same shared witnesses through a real HTTP origin. */
|
|
38
|
+
export declare function runContractLabOverHttp(origin: string, fetcher?: typeof fetch): Promise<void>;
|
|
39
|
+
/** Run the shared witnesses through a real HTTP adapter and always release its server. */
|
|
40
|
+
export declare function runContractLabThroughAdapter(adapter: ContractLabRuntimeAdapter, app?: ContractLabHandler): Promise<void>;
|
|
41
|
+
/** A reference Web handler for adapter-only suites. Core and edge suites use their real routers instead. */
|
|
42
|
+
export declare function createReferenceContractLabHandler(): ContractLabHandler;
|
|
43
|
+
//# sourceMappingURL=contract-lab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-lab.d.ts","sourceRoot":"","sources":["../src/contract-lab.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,GAAG,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAC/E;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;QACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAA;QACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;QACnD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KACvB,CAAA;CACF;AAED,gHAAgH;AAChH,eAAO,MAAM,oBAAoB,EAAE,SAAS,kBAAkB,EAgD5D,CAAA;AAgDF,0FAA0F;AAC1F,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,SAAsC,GAC3C,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,oEAAoE;AACpE,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,KAAwB,GACvC,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,0FAA0F;AAC1F,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,yBAAyB,EAClC,GAAG,GAAE,kBAAwD,GAC5D,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,4GAA4G;AAC5G,wBAAgB,iCAAiC,IAAI,kBAAkB,CA2BtE"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral HTTP contract witnesses.
|
|
3
|
+
*
|
|
4
|
+
* The witness set is deliberately made only of Web `Request`/`Response` values. Runtime suites provide
|
|
5
|
+
* the handler under test - the full Bun server, the compact edge server, or a native Node/Deno adapter -
|
|
6
|
+
* and this module compares the wire contract without importing any runtime-specific implementation.
|
|
7
|
+
*/
|
|
8
|
+
/** The shared cross-runtime wire contract. Keep this list small and stable: it is evidence, not a load test. */
|
|
9
|
+
export const contractLabWitnesses = Object.freeze([
|
|
10
|
+
{
|
|
11
|
+
id: "params-query-header",
|
|
12
|
+
request: {
|
|
13
|
+
method: "GET",
|
|
14
|
+
path: "/lab/users/alice?tag=one&tag=two",
|
|
15
|
+
headers: { "x-lab": "yes" },
|
|
16
|
+
},
|
|
17
|
+
expected: {
|
|
18
|
+
status: 200,
|
|
19
|
+
contentType: "json",
|
|
20
|
+
body: { id: "alice", tags: ["one", "two"], lab: "yes" },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "json-body",
|
|
25
|
+
request: {
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: "/lab/echo",
|
|
28
|
+
headers: { "content-type": "application/json" },
|
|
29
|
+
body: JSON.stringify({ message: "hello", count: 2 }),
|
|
30
|
+
},
|
|
31
|
+
expected: {
|
|
32
|
+
status: 200,
|
|
33
|
+
contentType: "json",
|
|
34
|
+
body: { message: "hello", count: 2 },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "response-bridge",
|
|
39
|
+
request: { method: "GET", path: "/lab/created" },
|
|
40
|
+
expected: { status: 201, contentType: "text", body: "created" },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "not-found",
|
|
44
|
+
request: { method: "GET", path: "/lab/missing" },
|
|
45
|
+
expected: { status: 404, contentType: "json", body: { ok: false, error: "not_found" } },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "method-not-allowed",
|
|
49
|
+
request: { method: "POST", path: "/lab/users/alice" },
|
|
50
|
+
expected: {
|
|
51
|
+
status: 405,
|
|
52
|
+
contentType: "json",
|
|
53
|
+
headers: { allow: "GET, HEAD" },
|
|
54
|
+
body: { ok: false, error: "method_not_allowed" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
function canonical(value) {
|
|
59
|
+
if (Array.isArray(value))
|
|
60
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
61
|
+
if (value !== null && typeof value === "object") {
|
|
62
|
+
const record = value;
|
|
63
|
+
return `{${Object.keys(record)
|
|
64
|
+
.sort()
|
|
65
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`)
|
|
66
|
+
.join(",")}}`;
|
|
67
|
+
}
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
}
|
|
70
|
+
function witnessRequest(origin, witness) {
|
|
71
|
+
return new Request(`${origin}${witness.request.path}`, {
|
|
72
|
+
method: witness.request.method,
|
|
73
|
+
headers: witness.request.headers,
|
|
74
|
+
...(witness.request.body === undefined ? {} : { body: witness.request.body }),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function assertWitnessResponse(witness, response) {
|
|
78
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0] ?? "";
|
|
79
|
+
const actual = witness.expected.contentType === "json" ? await response.json() : await response.text();
|
|
80
|
+
const expectedContentType = witness.expected.contentType === "json" ? "application/json" : "text/plain";
|
|
81
|
+
const headersMatch = Object.entries(witness.expected.headers ?? {}).every(([name, value]) => response.headers.get(name) === value);
|
|
82
|
+
if (response.status !== witness.expected.status ||
|
|
83
|
+
contentType !== expectedContentType ||
|
|
84
|
+
!headersMatch ||
|
|
85
|
+
canonical(actual) !== canonical(witness.expected.body)) {
|
|
86
|
+
throw new Error(`contract witness ${witness.id} failed: expected ${witness.expected.status} ` +
|
|
87
|
+
`${expectedContentType} ${canonical(witness.expected.body)}, got ${response.status} ` +
|
|
88
|
+
`${contentType || "(missing content type)"} ${canonical(actual)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Execute every witness against one runtime and throw a bounded, replayable mismatch. */
|
|
92
|
+
export async function runContractLab(handler, origin = "http://nifra-contract-lab.invalid") {
|
|
93
|
+
for (const witness of contractLabWitnesses) {
|
|
94
|
+
await assertWitnessResponse(witness, await handler.fetch(witnessRequest(origin, witness)));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Execute the same shared witnesses through a real HTTP origin. */
|
|
98
|
+
export async function runContractLabOverHttp(origin, fetcher = globalThis.fetch) {
|
|
99
|
+
for (const witness of contractLabWitnesses) {
|
|
100
|
+
await assertWitnessResponse(witness, await fetcher(witnessRequest(origin, witness)));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Run the shared witnesses through a real HTTP adapter and always release its server. */
|
|
104
|
+
export async function runContractLabThroughAdapter(adapter, app = createReferenceContractLabHandler()) {
|
|
105
|
+
const server = await adapter.start(app);
|
|
106
|
+
try {
|
|
107
|
+
await runContractLabOverHttp(server.origin);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
await server.stop();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** A reference Web handler for adapter-only suites. Core and edge suites use their real routers instead. */
|
|
114
|
+
export function createReferenceContractLabHandler() {
|
|
115
|
+
return {
|
|
116
|
+
async fetch(request) {
|
|
117
|
+
const url = new URL(request.url);
|
|
118
|
+
const user = /^\/lab\/users\/([^/]+)$/.exec(url.pathname);
|
|
119
|
+
if (request.method === "GET" && user !== null) {
|
|
120
|
+
return Response.json({
|
|
121
|
+
id: decodeURIComponent(user[1] ?? ""),
|
|
122
|
+
tags: url.searchParams.getAll("tag"),
|
|
123
|
+
lab: request.headers.get("x-lab"),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (request.method === "POST" && url.pathname === "/lab/echo") {
|
|
127
|
+
return Response.json(await request.json());
|
|
128
|
+
}
|
|
129
|
+
if (request.method === "GET" && url.pathname === "/lab/created") {
|
|
130
|
+
return new Response("created", { status: 201, headers: { "content-type": "text/plain" } });
|
|
131
|
+
}
|
|
132
|
+
if (url.pathname === "/lab/users/alice") {
|
|
133
|
+
return Response.json({ ok: false, error: "method_not_allowed" }, { status: 405, headers: { allow: "GET, HEAD" } });
|
|
134
|
+
}
|
|
135
|
+
return Response.json({ ok: false, error: "not_found" }, { status: 404 });
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=contract-lab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-lab.js","sourceRoot":"","sources":["../src/contract-lab.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+BH,gHAAgH;AAChH,MAAM,CAAC,MAAM,oBAAoB,GAAkC,MAAM,CAAC,MAAM,CAAC;IAC/E;QACE,EAAE,EAAE,qBAAqB;QACzB,OAAO,EAAE;YACP,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,kCAAkC;YACxC,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;SAC5B;QACD,QAAQ,EAAE;YACR,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,MAAM;YACnB,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE;SACxD;KACF;IACD;QACE,EAAE,EAAE,WAAW;QACf,OAAO,EAAE;YACP,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;SACrD;QACD,QAAQ,EAAE;YACR,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,MAAM;YACnB,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;SACrC;KACF;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE;QAChD,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;KAChE;IACD;QACE,EAAE,EAAE,WAAW;QACf,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE;QAChD,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;KACxF;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE;QACrD,QAAQ,EAAE;YACR,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,MAAM;YACnB,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;YAC/B,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE;SACjD;KACF;CACF,CAAC,CAAA;AAEF,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACtE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;aAC3B,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;aAChE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACjB,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,OAA2B;IACjE,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE;QACrD,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO;QAChC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;KAC9E,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,OAA2B,EAC3B,QAAkB;IAElB,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAChF,MAAM,MAAM,GACV,OAAO,CAAC,QAAQ,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IACzF,MAAM,mBAAmB,GACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAA;IAC7E,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,KAAK,CACvE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,CACxD,CAAA;IACD,IACE,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,MAAM;QAC3C,WAAW,KAAK,mBAAmB;QACnC,CAAC,YAAY;QACb,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EACtD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,oBAAoB,OAAO,CAAC,EAAE,qBAAqB,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG;YAC3E,GAAG,mBAAmB,IAAI,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,GAAG;YACrF,GAAG,WAAW,IAAI,wBAAwB,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CACpE,CAAA;IACH,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA2B,EAC3B,MAAM,GAAG,mCAAmC;IAE5C,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;QAC3C,MAAM,qBAAqB,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5F,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,UAAwB,UAAU,CAAC,KAAK;IAExC,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;QAC3C,MAAM,qBAAqB,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACtF,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAAkC,EAClC,MAA0B,iCAAiC,EAAE;IAE7D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACvC,IAAI,CAAC;QACH,MAAM,sBAAsB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7C,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;IACrB,CAAC;AACH,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,iCAAiC;IAC/C,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAChC,MAAM,IAAI,GAAG,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACzD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC9C,OAAO,QAAQ,CAAC,IAAI,CAAC;oBACnB,EAAE,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;oBACrC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC;oBACpC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;iBAClC,CAAC,CAAA;YACJ,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBAC9D,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;YAC5C,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,cAAc,EAAE,CAAC;gBAChE,OAAO,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE,CAAC,CAAA;YAC5F,CAAC;YACD,IAAI,GAAG,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;gBACxC,OAAO,QAAQ,CAAC,IAAI,CAClB,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAC1C,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CACjD,CAAA;YACH,CAAC;YACD,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC1E,CAAC;KACF,CAAA;AACH,CAAC"}
|