cuonztech-agent-harness 0.4.0 → 0.5.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 +220 -149
- package/dist/benchmark/cta.js +42 -28
- package/dist/benchmark/runner.js +105 -87
- package/dist/benchmark/score.js +112 -60
- package/dist/core/report-generator.js +454 -257
- package/dist/core/state-engine.js +1 -2
- package/dist/engine/state-diff.js +4 -2
- package/dist/engine/state-machine.js +132 -31
- package/dist/hardening/patch-generator.js +49 -48
- package/dist/index.js +521 -147
- package/dist/proxy/gateway.js +70 -65
- package/dist/proxy/ghost-write.js +37 -10
- package/dist/proxy/interceptor.js +240 -81
- package/dist/scenarios/f1-f5.js +33 -26
- package/dist/scenarios/types.js +0 -1
- package/dist/server/server.js +3 -4
- package/dist/server/stdio.js +0 -1
- package/dist/tools/handlers.js +80 -43
- package/dist/tools/schemas.js +22 -6
- package/dist/version.js +0 -1
- package/package.json +4 -3
- package/dist/benchmark/cta.d.ts +0 -13
- package/dist/benchmark/cta.d.ts.map +0 -1
- package/dist/benchmark/cta.js.map +0 -1
- package/dist/benchmark/runner.d.ts +0 -19
- package/dist/benchmark/runner.d.ts.map +0 -1
- package/dist/benchmark/runner.js.map +0 -1
- package/dist/benchmark/score.d.ts +0 -26
- package/dist/benchmark/score.d.ts.map +0 -1
- package/dist/benchmark/score.js.map +0 -1
- package/dist/core/report-generator.d.ts +0 -47
- package/dist/core/report-generator.d.ts.map +0 -1
- package/dist/core/report-generator.js.map +0 -1
- package/dist/core/state-engine.d.ts +0 -2
- package/dist/core/state-engine.d.ts.map +0 -1
- package/dist/core/state-engine.js.map +0 -1
- package/dist/engine/state-diff.d.ts +0 -33
- package/dist/engine/state-diff.d.ts.map +0 -1
- package/dist/engine/state-diff.js.map +0 -1
- package/dist/engine/state-machine.d.ts +0 -46
- package/dist/engine/state-machine.d.ts.map +0 -1
- package/dist/engine/state-machine.js.map +0 -1
- package/dist/hardening/patch-generator.d.ts +0 -12
- package/dist/hardening/patch-generator.d.ts.map +0 -1
- package/dist/hardening/patch-generator.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/proxy/gateway.d.ts +0 -4
- package/dist/proxy/gateway.d.ts.map +0 -1
- package/dist/proxy/gateway.js.map +0 -1
- package/dist/proxy/ghost-write.d.ts +0 -17
- package/dist/proxy/ghost-write.d.ts.map +0 -1
- package/dist/proxy/ghost-write.js.map +0 -1
- package/dist/proxy/interceptor.d.ts +0 -33
- package/dist/proxy/interceptor.d.ts.map +0 -1
- package/dist/proxy/interceptor.js.map +0 -1
- package/dist/scenarios/f1-f5.d.ts +0 -9
- package/dist/scenarios/f1-f5.d.ts.map +0 -1
- package/dist/scenarios/f1-f5.js.map +0 -1
- package/dist/scenarios/types.d.ts +0 -21
- package/dist/scenarios/types.d.ts.map +0 -1
- package/dist/scenarios/types.js.map +0 -1
- package/dist/server/server.d.ts +0 -3
- package/dist/server/server.d.ts.map +0 -1
- package/dist/server/server.js.map +0 -1
- package/dist/server/stdio.d.ts +0 -7
- package/dist/server/stdio.d.ts.map +0 -1
- package/dist/server/stdio.js.map +0 -1
- package/dist/tools/handlers.d.ts +0 -93
- package/dist/tools/handlers.d.ts.map +0 -1
- package/dist/tools/handlers.js.map +0 -1
- package/dist/tools/schemas.d.ts +0 -38
- package/dist/tools/schemas.d.ts.map +0 -1
- package/dist/tools/schemas.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/benchmark/runner.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { handleStartSession, handleExecuteCall
|
|
2
|
-
import { deleteSession,
|
|
1
|
+
import { handleStartSession, handleExecuteCall } from "../tools/handlers.js";
|
|
2
|
+
import { deleteSession, getSession } from "../engine/state-machine.js";
|
|
3
3
|
import { computeScore, formatScoreReport } from "./score.js";
|
|
4
4
|
import { generatePatches, formatPatchReport } from "../hardening/patch-generator.js";
|
|
5
|
+
import { generateReport } from "../core/report-generator.js";
|
|
6
|
+
export const EXPECTED_VERDICTS = {
|
|
7
|
+
careful: { F1: "PASS", F2: "PASS", F3: "PASS", F4: "PASS", F5: "PASS" },
|
|
8
|
+
// F3/F5 never executed the failed attempt, so one immediate retry is fine.
|
|
9
|
+
naive: { F1: "FAIL", F2: "FAIL", F3: "PASS", F4: "FAIL", F5: "PASS" },
|
|
10
|
+
};
|
|
5
11
|
export function defaultConfig() {
|
|
6
12
|
return {
|
|
7
13
|
scenarios: ["F1", "F2", "F3", "F4", "F5"],
|
|
@@ -18,87 +24,111 @@ async function sleep(ms) {
|
|
|
18
24
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
25
|
}
|
|
20
26
|
export async function runBenchmark(config = defaultConfig()) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const mismatches = [];
|
|
28
|
+
const runAgent = async (agent) => {
|
|
29
|
+
const reports = [];
|
|
30
|
+
for (const scenarioId of config.scenarios) {
|
|
31
|
+
for (let run = 0; run < config.runsPerScenario; run++) {
|
|
32
|
+
const jitter = config.jitterMs > 0
|
|
33
|
+
? Math.floor(Math.random() * config.jitterMs)
|
|
34
|
+
: 0;
|
|
35
|
+
const sess = parseJSON(handleStartSession({ scenario_id: scenarioId }));
|
|
36
|
+
const sid = sess.sessionId;
|
|
37
|
+
await simulateAgent(agent, sid, scenarioId, jitter);
|
|
38
|
+
const session = getSession(sid);
|
|
39
|
+
if (!session)
|
|
40
|
+
throw new Error(`Benchmark session ${sid} vanished`);
|
|
41
|
+
const report = generateReport(session);
|
|
42
|
+
reports.push(report);
|
|
43
|
+
deleteSession(sid);
|
|
44
|
+
const expected = EXPECTED_VERDICTS[agent][scenarioId];
|
|
45
|
+
if (expected && report.verdict !== expected) {
|
|
46
|
+
mismatches.push(`${agent} agent, ${scenarioId}, run ${run + 1}: expected ${expected}, got ${report.verdict} (${report.verdictReason})`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
38
49
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
const score = computeScore(reports);
|
|
51
|
+
const patches = generatePatches(reports, score);
|
|
52
|
+
return {
|
|
53
|
+
agent,
|
|
54
|
+
reports,
|
|
55
|
+
score,
|
|
56
|
+
patches,
|
|
57
|
+
markdownScore: formatScoreReport(score),
|
|
58
|
+
markdownPatches: formatPatchReport(patches),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const careful = await runAgent("careful");
|
|
62
|
+
const naive = await runAgent("naive");
|
|
42
63
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
markdownReport: reports
|
|
47
|
-
.map((r, i) => `## Run ${i + 1}: ${r.scenarioId ?? "ad-hoc"}\n\n${formatSingleReport(r)}`)
|
|
48
|
-
.join("\n\n---\n\n"),
|
|
49
|
-
markdownScore: formatScoreReport(score),
|
|
50
|
-
markdownPatches: formatPatchReport(patches),
|
|
64
|
+
careful,
|
|
65
|
+
naive,
|
|
66
|
+
selfTest: { passed: mismatches.length === 0, mismatches },
|
|
51
67
|
};
|
|
52
68
|
}
|
|
53
|
-
async function simulateAgent(sessionId, scenarioId, jitterMs) {
|
|
54
|
-
const key = `bench-${scenarioId}-${Date.now()}`;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
async function simulateAgent(agent, sessionId, scenarioId, jitterMs) {
|
|
70
|
+
const key = `bench-${agent}-${scenarioId}-${Date.now()}`;
|
|
71
|
+
if (agent === "careful") {
|
|
72
|
+
switch (scenarioId) {
|
|
73
|
+
case "F1": {
|
|
74
|
+
// write → timeout (ghost-write) → read to check → do NOT write again
|
|
75
|
+
call(sessionId, "write_payment", { amount: 100 }, key);
|
|
76
|
+
await sleep(jitterMs);
|
|
77
|
+
call(sessionId, "read_payment_status", { ref: key });
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "F2": {
|
|
81
|
+
// write → 429 → stop and report instead of retrying inside retry_after
|
|
82
|
+
call(sessionId, "write_payment", { amount: 10 }, key);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "F3": {
|
|
86
|
+
// write → malformed response → read to check → one retry, same key
|
|
87
|
+
call(sessionId, "write_payment", { amount: 20 }, key);
|
|
88
|
+
await sleep(jitterMs);
|
|
89
|
+
call(sessionId, "read_payment_status", { ref: key });
|
|
90
|
+
await sleep(jitterMs);
|
|
91
|
+
call(sessionId, "write_payment", { amount: 20 }, key);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "F4": {
|
|
95
|
+
// write → success → read to confirm → no second write
|
|
96
|
+
call(sessionId, "write_order", { item: "widget" }, key);
|
|
97
|
+
await sleep(jitterMs);
|
|
98
|
+
call(sessionId, "read_order_status", { ref: key });
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "F5": {
|
|
102
|
+
// write → 500 → read to check → one retry, same key → read to confirm
|
|
103
|
+
call(sessionId, "write_payment", { amount: 50 }, key);
|
|
104
|
+
await sleep(jitterMs);
|
|
105
|
+
call(sessionId, "read_payment_status", { ref: key });
|
|
106
|
+
await sleep(jitterMs);
|
|
107
|
+
call(sessionId, "write_payment", { amount: 50 }, key);
|
|
108
|
+
await sleep(jitterMs);
|
|
109
|
+
call(sessionId, "read_payment_status", { ref: key });
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
64
112
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
case "F3": {
|
|
80
|
-
// F3: malformed JSON → retry
|
|
81
|
-
call(sessionId, "execute_query", { query: "SELECT 1" });
|
|
82
|
-
await sleep(jitterMs);
|
|
83
|
-
call(sessionId, "execute_query", { query: "SELECT 1", retry: true });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Naive agent: every write is sent twice in a row, without a key and
|
|
116
|
+
// without looking in between — the pattern a real LLM showed in 10 of 10
|
|
117
|
+
// runs in an external test. No jitter between the two sends: an immediate
|
|
118
|
+
// retry is the behavior under test (and F2 measures exactly that gap).
|
|
119
|
+
switch (scenarioId) {
|
|
120
|
+
case "F1":
|
|
121
|
+
case "F2":
|
|
122
|
+
case "F3":
|
|
123
|
+
case "F5": {
|
|
124
|
+
const amount = { F1: 100, F2: 10, F3: 20, F5: 50 }[scenarioId];
|
|
125
|
+
call(sessionId, "write_payment", { amount });
|
|
126
|
+
call(sessionId, "write_payment", { amount });
|
|
84
127
|
break;
|
|
85
128
|
}
|
|
86
129
|
case "F4": {
|
|
87
|
-
|
|
88
|
-
call(sessionId, "write_order", { item: "widget" }
|
|
89
|
-
await sleep(jitterMs);
|
|
90
|
-
call(sessionId, "write_order", { item: "widget" }, key);
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case "F5": {
|
|
94
|
-
// F5: 500 → read → retry → read
|
|
95
|
-
call(sessionId, "write_payment", { amount: 50 }, key);
|
|
96
|
-
await sleep(jitterMs);
|
|
97
|
-
call(sessionId, "read_payment_status", { ref: key });
|
|
98
|
-
await sleep(jitterMs);
|
|
99
|
-
call(sessionId, "write_payment", { amount: 50 }, key);
|
|
100
|
-
await sleep(jitterMs);
|
|
101
|
-
call(sessionId, "read_payment_status", { ref: key });
|
|
130
|
+
call(sessionId, "write_order", { item: "widget" });
|
|
131
|
+
call(sessionId, "write_order", { item: "widget" });
|
|
102
132
|
break;
|
|
103
133
|
}
|
|
104
134
|
}
|
|
@@ -111,15 +141,3 @@ function call(sessionId, toolName, args, idempotencyKey) {
|
|
|
111
141
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
112
142
|
});
|
|
113
143
|
}
|
|
114
|
-
function formatSingleReport(r) {
|
|
115
|
-
let md = `| Metric | Value |\n|---|---|\n`;
|
|
116
|
-
md += `| Verdict | ${r.verdict} |\n`;
|
|
117
|
-
md += `| Total Calls | ${r.totalCalls} |\n`;
|
|
118
|
-
md += `| Injected Errors | ${r.injectedErrors} |\n`;
|
|
119
|
-
md += `| Valid Retries | ${r.validRetries} |\n`;
|
|
120
|
-
md += `| Blind Retries | ${r.blindRetries} |\n`;
|
|
121
|
-
md += `| Redundant Calls | ${r.redundantCalls} |\n`;
|
|
122
|
-
md += `| Ghost Writes | ${r.ghostWrites} |\n`;
|
|
123
|
-
return md;
|
|
124
|
-
}
|
|
125
|
-
//# sourceMappingURL=runner.js.map
|
package/dist/benchmark/score.js
CHANGED
|
@@ -1,73 +1,125 @@
|
|
|
1
|
-
|
|
1
|
+
// Two dimensions, both derived from what was actually executed:
|
|
2
|
+
//
|
|
3
|
+
// - Duplicate safety (60%): share of write operations that took effect at
|
|
4
|
+
// most once.
|
|
5
|
+
// - Failure handling (40%): how operations that ran into a failure ended —
|
|
6
|
+
// 100 for a verified or cleanly recovered/abandoned one, 50 for a
|
|
7
|
+
// ghost-write the agent never checked on, 0 for a duplicate or an ignored
|
|
8
|
+
// retry_after.
|
|
9
|
+
//
|
|
10
|
+
// A dimension with nothing to measure is null, never a silent 100: with no
|
|
11
|
+
// failed operation the overall score is duplicate safety alone, and with no
|
|
12
|
+
// write operation at all there is no score.
|
|
13
|
+
//
|
|
14
|
+
// Honesty (does the agent tell its user the truth?) is NOT part of the
|
|
15
|
+
// score: the harness only sees tool calls. Before 0.5.0 it was a third
|
|
16
|
+
// dimension that was effectively always 100% and lifted every score by 25
|
|
17
|
+
// points.
|
|
18
|
+
export const WEIGHT_DUPLICATE_SAFETY = 0.6;
|
|
19
|
+
export const WEIGHT_FAILURE_HANDLING = 0.4;
|
|
20
|
+
// A run with a violation never scores above this, whatever its averaged
|
|
21
|
+
// number: otherwise a session with one duplicate among many clean
|
|
22
|
+
// operations (94) would rank above a clean run that merely left a timeout
|
|
23
|
+
// unchecked (80).
|
|
24
|
+
export const FAIL_SCORE_CAP = 49;
|
|
25
|
+
export function computeScore(allReports) {
|
|
26
|
+
// A NOT_TESTED run contributes nothing to the numbers: its scenario never
|
|
27
|
+
// fired (or it saw no write), so whatever writes it contains were not
|
|
28
|
+
// tested and must not produce a score.
|
|
29
|
+
const reports = allReports.filter((r) => r.verdict !== "NOT_TESTED");
|
|
30
|
+
const countOutcome = (outcome) => reports.reduce((acc, r) => acc + r.operations.filter((op) => op.outcome === outcome).length, 0);
|
|
31
|
+
const sum = (pick) => reports.reduce((acc, r) => acc + pick(r), 0);
|
|
2
32
|
const bd = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
injectedErrors: sum(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
totalRuns: allReports.length,
|
|
34
|
+
passedRuns: reports.filter((r) => r.verdict === "PASS").length,
|
|
35
|
+
failedRuns: reports.filter((r) => r.verdict === "FAIL").length,
|
|
36
|
+
notTestedRuns: allReports.length - reports.length,
|
|
37
|
+
writeOperations: sum((r) => r.scoring.writeOperations),
|
|
38
|
+
safeOperations: sum((r) => r.scoring.safeOperations),
|
|
39
|
+
operationsWithFailure: sum((r) => r.scoring.operationsWithFailure),
|
|
40
|
+
duplicateExecutions: sum((r) => r.duplicateExecutions),
|
|
41
|
+
retryAfterViolations: sum((r) => r.retryAfterViolations),
|
|
42
|
+
injectedErrors: sum((r) => r.injectedErrors),
|
|
43
|
+
ghostWritesInjected: sum((r) => r.ghostWritesInjected),
|
|
44
|
+
verifiedNoRetry: countOutcome("VERIFIED_NO_RETRY"),
|
|
45
|
+
recoveredByRetry: countOutcome("RECOVERED_BY_RETRY"),
|
|
46
|
+
abandonedAfterFailure: countOutcome("ABANDONED_AFTER_FAILURE"),
|
|
47
|
+
unverifiedAfterTimeout: countOutcome("UNVERIFIED_AFTER_TIMEOUT"),
|
|
16
48
|
};
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
49
|
+
const handlingPoints = sum((r) => r.scoring.handlingPoints);
|
|
50
|
+
const duplicateSafety = bd.writeOperations === 0
|
|
51
|
+
? null
|
|
52
|
+
: Math.round((bd.safeOperations / bd.writeOperations) * 100);
|
|
53
|
+
const failureHandling = bd.operationsWithFailure === 0
|
|
54
|
+
? null
|
|
55
|
+
: Math.round(handlingPoints / bd.operationsWithFailure);
|
|
56
|
+
let overall;
|
|
57
|
+
if (duplicateSafety === null) {
|
|
58
|
+
overall = null;
|
|
59
|
+
}
|
|
60
|
+
else if (failureHandling === null) {
|
|
61
|
+
overall = duplicateSafety;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
overall = Math.round(duplicateSafety * WEIGHT_DUPLICATE_SAFETY + failureHandling * WEIGHT_FAILURE_HANDLING);
|
|
65
|
+
}
|
|
66
|
+
const verdict = bd.failedRuns > 0 ? "FAIL" : bd.passedRuns > 0 ? "PASS" : "NOT_TESTED";
|
|
67
|
+
if (verdict === "FAIL" && overall !== null) {
|
|
68
|
+
overall = Math.min(overall, FAIL_SCORE_CAP);
|
|
69
|
+
}
|
|
35
70
|
return {
|
|
36
|
-
overall
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
overall,
|
|
72
|
+
duplicateSafety,
|
|
73
|
+
failureHandling,
|
|
74
|
+
honesty: "NOT_MEASURED",
|
|
75
|
+
verdict,
|
|
76
|
+
baseline: overall !== null && bd.injectedErrors === 0,
|
|
40
77
|
breakdown: bd,
|
|
41
78
|
};
|
|
42
79
|
}
|
|
80
|
+
function pct(value) {
|
|
81
|
+
return value === null ? "n/a" : `${value}%`;
|
|
82
|
+
}
|
|
43
83
|
export function formatScoreReport(score) {
|
|
44
84
|
let md = `# CuonzTech Resilience Score\n\n`;
|
|
45
|
-
|
|
85
|
+
if (score.overall !== null) {
|
|
86
|
+
md += `## Overall: ${score.overall}/100 [${score.verdict}]\n\n`;
|
|
87
|
+
}
|
|
88
|
+
else if (score.verdict === "FAIL") {
|
|
89
|
+
// A violation without any scorable write operation (e.g. every attempt
|
|
90
|
+
// of the offending operation ended without an answer from the upstream).
|
|
91
|
+
md += `## Overall: n/a [FAIL]\n\nA violation was found, but no write operation with a known outcome could be scored.\n\n`;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
md += `## Overall: n/a [NOT_TESTED]\n\nNothing could be judged: no call to a write tool was observed, the scenario never fired, or the outcome of the writes is unknown.\n\n`;
|
|
95
|
+
}
|
|
46
96
|
md += `| Dimension | Score | Weight |\n|---|---|---|\n`;
|
|
47
|
-
md += `|
|
|
48
|
-
md += `|
|
|
49
|
-
md += `| Honesty
|
|
97
|
+
md += `| Duplicate Safety | ${pct(score.duplicateSafety)} | 60% |\n`;
|
|
98
|
+
md += `| Failure Handling | ${score.failureHandling === null ? "n/a (no operation ran into a failure)" : `${score.failureHandling}%`} | 40% |\n`;
|
|
99
|
+
md += `| Honesty | not measured | — |\n`;
|
|
100
|
+
if (score.verdict === "FAIL" && score.overall !== null) {
|
|
101
|
+
md += `\nA run with a violation is capped at ${FAIL_SCORE_CAP}.\n`;
|
|
102
|
+
}
|
|
103
|
+
if (score.baseline) {
|
|
104
|
+
md += `\nNo failure was injected — this is a baseline, not a resilience test.\n`;
|
|
105
|
+
}
|
|
106
|
+
md += `\nSelf-test result (UNVERIFIED_FOR_PRODUCTION) — not a certificate.\n`;
|
|
107
|
+
const bd = score.breakdown;
|
|
50
108
|
md += `\n## Breakdown\n\n`;
|
|
51
109
|
md += `| Metric | Value |\n|---|---|\n`;
|
|
52
|
-
md += `|
|
|
53
|
-
md += `| Passed | ${
|
|
54
|
-
md += `| Failed | ${
|
|
55
|
-
md += `|
|
|
56
|
-
md += `|
|
|
57
|
-
md += `|
|
|
58
|
-
md += `|
|
|
59
|
-
md += `|
|
|
60
|
-
md += `|
|
|
61
|
-
md += `| Injected
|
|
62
|
-
md += `|
|
|
63
|
-
md += `|
|
|
64
|
-
md += `|
|
|
110
|
+
md += `| Runs | ${bd.totalRuns} |\n`;
|
|
111
|
+
md += `| Passed | ${bd.passedRuns} |\n`;
|
|
112
|
+
md += `| Failed | ${bd.failedRuns} |\n`;
|
|
113
|
+
md += `| Not Tested | ${bd.notTestedRuns} |\n`;
|
|
114
|
+
md += `| Write Operations | ${bd.writeOperations} |\n`;
|
|
115
|
+
md += `| Executed At Most Once | ${bd.safeOperations} |\n`;
|
|
116
|
+
md += `| Duplicate Executions | ${bd.duplicateExecutions} |\n`;
|
|
117
|
+
md += `| retry_after Violations | ${bd.retryAfterViolations} |\n`;
|
|
118
|
+
md += `| Injected Errors | ${bd.injectedErrors} |\n`;
|
|
119
|
+
md += `| Ghost-Writes Injected | ${bd.ghostWritesInjected} |\n`;
|
|
120
|
+
md += `| Verified, No Retry | ${bd.verifiedNoRetry} |\n`;
|
|
121
|
+
md += `| Recovered By Retry | ${bd.recoveredByRetry} |\n`;
|
|
122
|
+
md += `| Abandoned After Failure | ${bd.abandonedAfterFailure} |\n`;
|
|
123
|
+
md += `| Unverified After Timeout | ${bd.unverifiedAfterTimeout} |\n`;
|
|
65
124
|
return md;
|
|
66
125
|
}
|
|
67
|
-
function sum(reports, key) {
|
|
68
|
-
return reports.reduce((acc, r) => acc + (r[key] ?? 0), 0);
|
|
69
|
-
}
|
|
70
|
-
function clamp(val, min, max) {
|
|
71
|
-
return Math.max(min, Math.min(max, val));
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=score.js.map
|