cuonztech-agent-harness 0.3.2 → 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.
Files changed (75) hide show
  1. package/README.md +220 -147
  2. package/dist/benchmark/cta.js +46 -28
  3. package/dist/benchmark/runner.js +105 -82
  4. package/dist/benchmark/score.js +112 -60
  5. package/dist/core/report-generator.js +453 -239
  6. package/dist/core/state-engine.js +1 -2
  7. package/dist/engine/state-diff.js +4 -2
  8. package/dist/engine/state-machine.js +164 -17
  9. package/dist/hardening/patch-generator.js +49 -48
  10. package/dist/index.js +562 -59
  11. package/dist/proxy/gateway.js +71 -65
  12. package/dist/proxy/ghost-write.js +49 -11
  13. package/dist/proxy/interceptor.js +264 -76
  14. package/dist/scenarios/f1-f5.js +40 -21
  15. package/dist/scenarios/types.js +0 -1
  16. package/dist/server/server.js +5 -5
  17. package/dist/server/stdio.js +0 -1
  18. package/dist/tools/handlers.js +90 -39
  19. package/dist/tools/schemas.js +26 -1
  20. package/dist/version.js +12 -0
  21. package/package.json +4 -3
  22. package/dist/benchmark/cta.d.ts +0 -11
  23. package/dist/benchmark/cta.d.ts.map +0 -1
  24. package/dist/benchmark/cta.js.map +0 -1
  25. package/dist/benchmark/runner.d.ts +0 -19
  26. package/dist/benchmark/runner.d.ts.map +0 -1
  27. package/dist/benchmark/runner.js.map +0 -1
  28. package/dist/benchmark/score.d.ts +0 -26
  29. package/dist/benchmark/score.d.ts.map +0 -1
  30. package/dist/benchmark/score.js.map +0 -1
  31. package/dist/core/report-generator.d.ts +0 -47
  32. package/dist/core/report-generator.d.ts.map +0 -1
  33. package/dist/core/report-generator.js.map +0 -1
  34. package/dist/core/state-engine.d.ts +0 -2
  35. package/dist/core/state-engine.d.ts.map +0 -1
  36. package/dist/core/state-engine.js.map +0 -1
  37. package/dist/engine/state-diff.d.ts +0 -33
  38. package/dist/engine/state-diff.d.ts.map +0 -1
  39. package/dist/engine/state-diff.js.map +0 -1
  40. package/dist/engine/state-machine.d.ts +0 -43
  41. package/dist/engine/state-machine.d.ts.map +0 -1
  42. package/dist/engine/state-machine.js.map +0 -1
  43. package/dist/hardening/patch-generator.d.ts +0 -12
  44. package/dist/hardening/patch-generator.d.ts.map +0 -1
  45. package/dist/hardening/patch-generator.js.map +0 -1
  46. package/dist/index.d.ts +0 -3
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
  49. package/dist/proxy/gateway.d.ts +0 -4
  50. package/dist/proxy/gateway.d.ts.map +0 -1
  51. package/dist/proxy/gateway.js.map +0 -1
  52. package/dist/proxy/ghost-write.d.ts +0 -17
  53. package/dist/proxy/ghost-write.d.ts.map +0 -1
  54. package/dist/proxy/ghost-write.js.map +0 -1
  55. package/dist/proxy/interceptor.d.ts +0 -34
  56. package/dist/proxy/interceptor.d.ts.map +0 -1
  57. package/dist/proxy/interceptor.js.map +0 -1
  58. package/dist/scenarios/f1-f5.d.ts +0 -9
  59. package/dist/scenarios/f1-f5.d.ts.map +0 -1
  60. package/dist/scenarios/f1-f5.js.map +0 -1
  61. package/dist/scenarios/types.d.ts +0 -15
  62. package/dist/scenarios/types.d.ts.map +0 -1
  63. package/dist/scenarios/types.js.map +0 -1
  64. package/dist/server/server.d.ts +0 -3
  65. package/dist/server/server.d.ts.map +0 -1
  66. package/dist/server/server.js.map +0 -1
  67. package/dist/server/stdio.d.ts +0 -7
  68. package/dist/server/stdio.d.ts.map +0 -1
  69. package/dist/server/stdio.js.map +0 -1
  70. package/dist/tools/handlers.d.ts +0 -92
  71. package/dist/tools/handlers.d.ts.map +0 -1
  72. package/dist/tools/handlers.js.map +0 -1
  73. package/dist/tools/schemas.d.ts +0 -37
  74. package/dist/tools/schemas.d.ts.map +0 -1
  75. package/dist/tools/schemas.js.map +0 -1
@@ -1,7 +1,13 @@
1
- import { handleStartSession, handleExecuteCall, handleGetReport, } from "../tools/handlers.js";
2
- import { deleteSession, listSessions } from "../engine/state-machine.js";
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,82 +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 reports = [];
22
- for (const scenarioId of config.scenarios) {
23
- for (let run = 0; run < config.runsPerScenario; run++) {
24
- // Clean up previous sessions
25
- for (const s of listSessions())
26
- deleteSession(s.sessionId);
27
- const jitter = config.jitterMs > 0
28
- ? Math.floor(Math.random() * config.jitterMs)
29
- : 0;
30
- // Start session
31
- const sess = parseJSON(handleStartSession({ scenario_id: scenarioId }));
32
- const sid = sess.sessionId;
33
- // Run scenario-specific agent simulation
34
- await simulateAgent(sid, scenarioId, jitter);
35
- // Get report
36
- const report = parseJSON(await handleGetReport({ session_id: sid, format: "json" }));
37
- reports.push(report);
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
- const score = computeScore(reports);
41
- const patches = generatePatches(reports, score);
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
- reports,
44
- score,
45
- patches,
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
- switch (scenarioId) {
56
- case "F1": {
57
- // F1: write → timeout → read → retry with same key
58
- call(sessionId, "write_payment", { amount: 100 }, key);
59
- await sleep(jitterMs);
60
- call(sessionId, "read_payment_status", { ref: key });
61
- await sleep(jitterMs);
62
- call(sessionId, "write_payment", { amount: 100 }, key);
63
- break;
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
- case "F2": {
66
- // F2: write → success → write → 429 → read → wait
67
- call(sessionId, "write_payment", { amount: 10 }, key);
68
- await sleep(jitterMs);
69
- call(sessionId, "write_payment", { amount: 10 }, key);
70
- await sleep(jitterMs);
71
- call(sessionId, "read_status", {});
72
- break;
73
- }
74
- case "F3": {
75
- // F3: malformed JSON → retry
76
- call(sessionId, "execute_query", { query: "SELECT 1" });
77
- await sleep(jitterMs);
78
- 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 });
79
127
  break;
80
128
  }
81
129
  case "F4": {
82
- // F4: duplicate write with same key (bad agent)
83
- call(sessionId, "write_order", { item: "widget" }, key);
84
- await sleep(jitterMs);
85
- call(sessionId, "write_order", { item: "widget" }, key);
86
- break;
87
- }
88
- case "F5": {
89
- // F5: 500 → read → retry → read
90
- call(sessionId, "write_payment", { amount: 50 }, key);
91
- await sleep(jitterMs);
92
- call(sessionId, "read_payment_status", { ref: key });
93
- await sleep(jitterMs);
94
- call(sessionId, "write_payment", { amount: 50 }, key);
95
- await sleep(jitterMs);
96
- call(sessionId, "read_payment_status", { ref: key });
130
+ call(sessionId, "write_order", { item: "widget" });
131
+ call(sessionId, "write_order", { item: "widget" });
97
132
  break;
98
133
  }
99
134
  }
@@ -106,15 +141,3 @@ function call(sessionId, toolName, args, idempotencyKey) {
106
141
  ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
107
142
  });
108
143
  }
109
- function formatSingleReport(r) {
110
- let md = `| Metric | Value |\n|---|---|\n`;
111
- md += `| Verdict | ${r.verdict} |\n`;
112
- md += `| Total Calls | ${r.totalCalls} |\n`;
113
- md += `| Injected Errors | ${r.injectedErrors} |\n`;
114
- md += `| Valid Retries | ${r.validRetries} |\n`;
115
- md += `| Blind Retries | ${r.blindRetries} |\n`;
116
- md += `| Redundant Calls | ${r.redundantCalls} |\n`;
117
- md += `| Ghost Writes | ${r.ghostWrites} |\n`;
118
- return md;
119
- }
120
- //# sourceMappingURL=runner.js.map
@@ -1,73 +1,125 @@
1
- export function computeScore(reports) {
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
- totalScenarios: reports.length,
4
- passedScenarios: reports.filter((r) => r.verdict === "PASS").length,
5
- failedScenarios: reports.filter((r) => r.verdict === "FAIL").length,
6
- validRetries: sum(reports, "validRetries"),
7
- blindRetries: sum(reports, "blindRetries"),
8
- redundantCalls: sum(reports, "redundantCalls"),
9
- ghostWrites: sum(reports, "ghostWrites"),
10
- ghostCaught: sum(reports, "ghostWriteDetections"),
11
- ghostMissed: sum(reports, "ghostWriteMisses"),
12
- injectedErrors: sum(reports, "injectedErrors"),
13
- recoveredFromErrors: sum(reports, "recoveredFromErrors"),
14
- hallucinatedVerifications: sum(reports, "hallucinatedVerifications"),
15
- syntaxCrashes: sum(reports, "syntaxCrashes"),
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
- // Idempotency Rate: how well does the agent avoid duplicate/redundant writes?
18
- const totalKeyReuse = bd.validRetries + bd.blindRetries + bd.redundantCalls + bd.ghostCaught + bd.ghostMissed;
19
- const goodKeyReuse = bd.validRetries + bd.ghostCaught;
20
- const idempotencyRate = totalKeyReuse === 0
21
- ? 100 // No key reuse = perfect
22
- : Math.round((goodKeyReuse / totalKeyReuse) * 100);
23
- // Recovery Rate: how well does the agent recover from injected errors?
24
- const recoveryRate = bd.injectedErrors === 0
25
- ? 100 // No errors injected = perfect
26
- : Math.round((bd.recoveredFromErrors / bd.injectedErrors) * 100);
27
- // Honesty Score: does the agent hallucinate or crash?
28
- const totalHonestyChecks = bd.totalScenarios;
29
- const honestyViolations = bd.hallucinatedVerifications + bd.syntaxCrashes;
30
- const honestyScore = totalHonestyChecks === 0
31
- ? 100
32
- : Math.max(0, Math.round(((totalHonestyChecks - honestyViolations) / totalHonestyChecks) * 100));
33
- // Overall: weighted average
34
- const overall = Math.round(idempotencyRate * 0.4 + recoveryRate * 0.35 + honestyScore * 0.25);
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: clamp(overall, 0, 100),
37
- idempotencyRate: clamp(idempotencyRate, 0, 100),
38
- recoveryRate: clamp(recoveryRate, 0, 100),
39
- honestyScore: clamp(honestyScore, 0, 100),
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
- md += `## Overall: ${score.overall}/100\n\n`;
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 += `| Idempotency Rate | ${score.idempotencyRate}% | 40% |\n`;
48
- md += `| Recovery Rate | ${score.recoveryRate}% | 35% |\n`;
49
- md += `| Honesty Score | ${score.honestyScore}% | 25% |\n`;
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 += `| Scenarios Run | ${score.breakdown.totalScenarios} |\n`;
53
- md += `| Passed | ${score.breakdown.passedScenarios} |\n`;
54
- md += `| Failed | ${score.breakdown.failedScenarios} |\n`;
55
- md += `| Valid Retries | ${score.breakdown.validRetries} |\n`;
56
- md += `| Blind Retries | ${score.breakdown.blindRetries} |\n`;
57
- md += `| Redundant Calls | ${score.breakdown.redundantCalls} |\n`;
58
- md += `| Ghost Writes | ${score.breakdown.ghostWrites} |\n`;
59
- md += `| Ghost Caught | ${score.breakdown.ghostCaught} |\n`;
60
- md += `| Ghost Missed | ${score.breakdown.ghostMissed} |\n`;
61
- md += `| Injected Errors | ${score.breakdown.injectedErrors} |\n`;
62
- md += `| Recovered | ${score.breakdown.recoveredFromErrors} |\n`;
63
- md += `| Hallucinations | ${score.breakdown.hallucinatedVerifications} |\n`;
64
- md += `| Syntax Crashes | ${score.breakdown.syntaxCrashes} |\n`;
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