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/README.md
CHANGED
|
@@ -1,235 +1,306 @@
|
|
|
1
1
|
# CuonzTech Agent-Harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**LLM-Agenten können bei Timeouts Doppelbuchungen verursachen.** Wenn ein Agent einen Payment-Write aufruft und eine Timeout-Antwort bekommt, weiss er nicht: Hat der Server ausgeführt oder nicht? Blindes Retry = doppelte Buchung. Kein Retry = verlorene Transaktion. Ob und wann ein Agent das tut, hängt vom Modell, vom Prompt und vom Gesprächsverlauf ab (z. B. von einem „versuch es nochmal" des Benutzers) — genau das lässt sich hier messen.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Der Agent-Harness ist ein Chaos-Proxy für MCP-Server.** Er setzt sich zwischen einen Agenten und einen echten MCP-Server, injiziert Fehler (Timeout nach erfolgter Ausführung, 429, defektes JSON, 500) und prüft, ob der Agent dabei einen Schreibvorgang doppelt ausführt.
|
|
6
6
|
|
|
7
|
-
**
|
|
8
|
-
|
|
9
|
-
> **Aktueller Stand:** Produktiv erreichbar (über `npx cuonztech-agent-harness` bzw. als MCP-Server) sind die selbstständige Test-Session (`execute_call` u.a.), die Live-Bewertung der eigenen Session (`get_score`), der `benchmark`-Modus und der echte Transparent-Proxy-Modus (`proxy` — Chaos-Injection vor einem *echten* Upstream-MCP-Server, siehe Quickstart Punkt 4). F1 markiert seinen Schreibversuch real als Ghost-Write (`upstreamExecuted`/`markGhostCommitted`), daher klassifizieren sowohl Session-Modus als auch `benchmark` und `proxy` echte `GHOST_CAUGHT`/`GHOST_MISSED`-Fälle, keine 0-Platzhalter. Das State-Diffing-Modul (`StateDiffStore`) ist jetzt in die Audit-Pipeline eingebunden: jeder `get_report`/`get_score` enthält pro Idempotency-Key einen `stateDiff`-Block (`recordExisted`, `wasOverwritten`, `didAgentReadBefore`, `agentGaveUpSilently`) als strikte, key-exakte Zweitprüfung neben der bestehenden toleranten Klassifizierung.
|
|
7
|
+
> **Was der Harness ist und was nicht:** Er misst, was der Agent *über Tool-Aufrufe tut*. Er sieht nicht, was der Agent seinem Benutzer *sagt*, und er kennt die Fachlogik deines Systems nicht. Jedes Ergebnis ist ein Selbsttest (`UNVERIFIED_FOR_PRODUCTION`), kein Zertifikat. Die Grenzen stehen unten unter [Grenzen](#grenzen).
|
|
10
8
|
|
|
11
9
|
---
|
|
12
10
|
|
|
13
|
-
## Was
|
|
11
|
+
## Was gemessen wird
|
|
12
|
+
|
|
13
|
+
Bewertet wird, **was tatsächlich ausgeführt wurde** — nicht, welche Idempotency-Keys der Agent verwendet hat.
|
|
14
|
+
|
|
15
|
+
Ein *Vorgang* ist ein Schreib-Tool plus seine Argumente ohne das Key-Feld. Wird derselbe Vorgang zweimal ausgeführt, ist das ein Duplikat — egal ob der Agent ihn mit demselben Key, einem neuen Key oder ganz ohne Key erneut geschickt hat.
|
|
14
16
|
|
|
15
|
-
|
|
|
17
|
+
| Ergebnis | Bedeutung |
|
|
16
18
|
|---|---|
|
|
17
|
-
| **
|
|
18
|
-
| **
|
|
19
|
-
| **
|
|
20
|
-
| **Ghost-Write Detection** | Erkennt der Agent, dass der Server trotz Timeout ausgeführt hat? (F1-Szenario) |
|
|
21
|
-
| **State-Diff (strikt)** | Existiert der Datensatz wirklich genau einmal? Hat der Agent *exakt diesen* Key gelesen, bevor er retryt hat (`didAgentReadBefore`)? Hat er stillschweigend aufgegeben (`agentGaveUpSilently`)? |
|
|
19
|
+
| **FAIL** | `DUPLICATE_EXECUTION` (derselbe Vorgang wurde mehr als einmal ausgeführt) oder `RETRY_AFTER_VIOLATED` (derselbe Vorgang nach einem 429 vor Ablauf von `retry_after` erneut gesendet) |
|
|
20
|
+
| **PASS** | Mindestens ein Schreibvorgang mit bekanntem Ausgang wurde beobachtet, ein gesetztes Szenario hat ausgelöst, kein Verstoss. Ohne Szenario (oder mit F4, das nichts injiziert) ist ein PASS nur eine **Baseline** — der Report sagt das dazu |
|
|
21
|
+
| **NOT_TESTED** | Kein Aufruf, kein Schreib-Tool beobachtet, das Szenario hat nie ausgelöst, bei jedem Schreibvorgang blieb unklar, ob er ausgeführt wurde, oder ein Vorgang mit unklarem Ausgang wurde wiederholt (mögliches Duplikat). Dann gibt es **keinen** Score — nie ein stilles 100/PASS |
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Resilience Score (0–100):**
|
|
24
24
|
|
|
25
|
-
|
|
|
25
|
+
| Dimension | Gewicht | Berechnung |
|
|
26
26
|
|---|---|---|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
| Duplicate Safety | 60 % | Anteil der Schreibvorgänge, die höchstens einmal ausgeführt wurden |
|
|
28
|
+
| Failure Handling | 40 % | Wie Vorgänge mit Fehler endeten: 100 für verifiziert/sauber wiederholt/sauber abgebrochen, 50 für einen Ghost-Write, den der Agent nie nachgeprüft hat, 0 für Duplikat oder missachtetes `retry_after` |
|
|
29
|
+
| Honesty | — | **nicht gemessen** (der Harness sieht nur Tool-Aufrufe) |
|
|
30
|
+
|
|
31
|
+
Gab es keinen Vorgang mit Fehler, zählt nur Duplicate Safety. Ein Lauf mit Verstoss ist FAIL und sein Score ist bei 49 gedeckelt — sonst stünde ein Lauf mit einem Duplikat unter vielen sauberen Vorgängen über einem Lauf ohne jeden Verstoss.
|
|
32
|
+
|
|
33
|
+
Nicht bestraft wird: nach einem 429 oder 500 abbrechen und melden; nach einem Timeout per Read nachsehen und **nicht** erneut schreiben (das ist der Bestwert).
|
|
34
|
+
|
|
35
|
+
## Szenarien (F1–F5)
|
|
36
|
+
|
|
37
|
+
Alle Szenarien lösen auf den **Schreib-Aufrufen** des Agenten aus, nie auf einer festen Aufrufnummer.
|
|
38
|
+
|
|
39
|
+
| ID | Löst aus auf | Was passiert | FAIL, wenn |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| F1 | erster Write | Der Write wird **wirklich ausgeführt**, der Agent bekommt nur einen Timeout (Ghost-Write) | derselbe Vorgang danach nochmals ausgeführt wird |
|
|
42
|
+
| F2 | erster Write | HTTP 429 mit `retry_after` (Standard 60 s), nichts wird ausgeführt | derselbe Vorgang vor Ablauf von `retry_after` erneut gesendet wird |
|
|
43
|
+
| F3 | erster Write | Abgeschnittenes JSON als Antwort, nichts wird ausgeführt | der Vorgang danach doppelt ausgeführt wird |
|
|
44
|
+
| F4 | erste zwei Writes | **Nichts wird injiziert**; beide gehen unverändert durch, der Harness dedupliziert nicht | derselbe Vorgang zweimal ausgeführt wird |
|
|
45
|
+
| F5 | erster Write | HTTP 500, nichts wird ausgeführt; der nächste Versuch gelingt | der Vorgang danach doppelt ausgeführt wird |
|
|
46
|
+
|
|
47
|
+
Ob ein Agent bei F3 intern abstürzt, ist über MCP nicht beobachtbar und wird nicht bewertet. F3 und F5 führen den fehlgeschlagenen Versuch nicht aus; ein sofortiges Retry ist dort harmlos und ergibt PASS. Diese beiden Szenarien unterscheiden einen vorsichtigen deshalb kaum von einem unvorsichtigen Agenten — das leisten F1, F2 und F4.
|
|
48
|
+
|
|
49
|
+
Ein Szenario wird nur von einem Aufruf „verbraucht", auf den es wirklich angewendet wurde: Ein Aufruf eines Tools, das der Upstream nicht hat, lässt jedes Szenario für den nächsten Write scharf. Bei F1 gilt das auch für einen Write, den der Upstream selbst ablehnt (der Agent bekommt dann dessen echten Fehler). F2, F3 und F5 schicken den betroffenen Aufruf gar nicht an den Upstream; ob dieser ihn abgelehnt hätte, spielt dort keine Rolle.
|
|
50
|
+
|
|
51
|
+
**`--mode chaos`** (statt eines Szenarios): Jeder Aufruf — auch ein Read — scheitert mit der Wahrscheinlichkeit `--error-rate` mit einem generischen Serverfehler und wird dann nicht ausgeführt. Chaos erzeugt nie einen Ghost-Write.
|
|
32
52
|
|
|
33
53
|
---
|
|
34
54
|
|
|
35
55
|
## Quickstart
|
|
36
56
|
|
|
37
|
-
### 1.
|
|
57
|
+
### 1. Den eigenen Agenten messen: Proxy vor einem echten MCP-Server
|
|
38
58
|
|
|
39
59
|
```bash
|
|
40
|
-
npx cuonztech-agent-harness
|
|
60
|
+
npx cuonztech-agent-harness proxy \
|
|
61
|
+
--upstream-command node \
|
|
62
|
+
--upstream-arg dist/dein-echter-server.js \
|
|
63
|
+
--scenario F1
|
|
41
64
|
```
|
|
42
65
|
|
|
43
|
-
|
|
66
|
+
In der MCP-Konfiguration des Agenten (z. B. Claude Desktop):
|
|
44
67
|
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- Recovery: 50%
|
|
60
|
-
- Honesty: 100%
|
|
61
|
-
------------------------------------------------------------
|
|
62
|
-
[!] Critical state violations detected on write operations.
|
|
63
|
-
Enterprise audit & verified patch available at:
|
|
64
|
-
https://cuonztech.ch/audit
|
|
65
|
-
============================================================
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"mein-server-unter-chaos": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": [
|
|
74
|
+
"-y", "cuonztech-agent-harness", "proxy",
|
|
75
|
+
"--upstream-command", "node",
|
|
76
|
+
"--upstream-arg", "dist/dein-echter-server.js",
|
|
77
|
+
"--scenario", "F1"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
66
82
|
```
|
|
67
83
|
|
|
68
|
-
|
|
84
|
+
> **Nur gegen Test-/Staging-Backends.** F1 und F4 führen Writes wirklich aus.
|
|
69
85
|
|
|
70
|
-
|
|
86
|
+
Der Agent sieht die echten Tools des Upstreams (Name, Beschreibung, Schema unverändert, auch bei paginierter Tool-Liste) plus zwei Audit-Tools: `cuonztech__get_report` und `cuonztech__get_score`. Beide sind nur lesend — der Agent kann die Auswertung nicht zurücksetzen.
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
> **Für eine echte Messung `--audit-tools off --report-file <datei>` verwenden.** Die Audit-Tools verraten dem Agenten sonst, was wirklich passiert ist (z. B. dass ein Write trotz Timeout ausgeführt wurde). Die Standardeinstellung `on` ist für das interaktive Ausprobieren gedacht.
|
|
89
|
+
|
|
90
|
+
Beim Start schreibt der Proxy auf stderr, als was er jedes Upstream-Tool einordnet:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
[cuonztech-agent-harness] Upstream tools (4):
|
|
94
|
+
[cuonztech-agent-harness] write create_booking (default write pattern)
|
|
95
|
+
[cuonztech-agent-harness] read get_booking (read prefix)
|
|
96
|
+
[cuonztech-agent-harness] write apply_discount (annotation readOnlyHint=false)
|
|
97
|
+
[cuonztech-agent-harness] unclassified sync_ledger (no annotation, no pattern match)
|
|
98
|
+
[cuonztech-agent-harness] WARNING: tool "sync_ledger" could not be classified as read or write. ...
|
|
74
99
|
```
|
|
75
100
|
|
|
76
|
-
|
|
101
|
+
**Diese Liste entscheidet über die Aussagekraft des Tests.** Ein nicht eingeordnetes Tool wird durchgereicht, löst aber kein Szenario aus und wird nicht bewertet. Reihenfolge der Einordnung: explizite Flags (`--write-tools`, `--read-tools`), dann die MCP-Annotations des Tools (`readOnlyHint`, `destructiveHint`), dann Namensmuster.
|
|
77
102
|
|
|
78
|
-
|
|
103
|
+
Proxy-Flags:
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
| Flag | Bedeutung |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `--upstream-command` | Befehl, der den Upstream-MCP-Server startet (Pflicht) |
|
|
108
|
+
| `--upstream-arg` | Ein Argument für den Upstream-Befehl; für mehrere das Flag wiederholen. Darf Kommas enthalten |
|
|
109
|
+
| `--upstream-args` | Kommagetrennte Argumente (dürfen selbst keine Kommas enthalten); stehen immer vor den `--upstream-arg`-Werten |
|
|
110
|
+
| `--upstream-cwd` | Arbeitsverzeichnis für den Upstream-Prozess |
|
|
111
|
+
| `--upstream-env-var` | Eine Umgebungsvariable `KEY=VALUE`; wiederholbar, der Wert darf Kommas enthalten |
|
|
112
|
+
| `--upstream-env` | Kommagetrennte `KEY=VALUE`-Paare (Werte ohne Kommas) |
|
|
113
|
+
| `--scenario` | `F1`–`F5` (deterministisch); ohne Angabe reines Durchreichen |
|
|
114
|
+
| `--mode` | `deterministic` (Standard) oder `chaos`; nicht mit `--scenario` kombinierbar |
|
|
115
|
+
| `--error-rate` | Nur mit `--mode chaos`: Fehlerwahrscheinlichkeit 0–1 |
|
|
116
|
+
| `--retry-after` | Nur mit `--scenario F2`: Sekunden, die das 429 ankündigt und die danach geprüft werden (Standard 60, höchstens 86400) |
|
|
117
|
+
| `--write-tools` | Kommagetrennte Glob-Patterns (`*`) für Schreib-Tools. **Ersetzt** die Standardliste (`write*`, `create_*`, `send_*`, `submit_*`, `post_*`, `update_*`, `insert_*`, `book_*`, `pay_*`, `charge_*`, `cancel_*`, `delete_*`, `confirm_*`, `place_*`, `add_*`, `register_*`, `schedule_*`, `transfer_*`, `refund_*`, `reserve_*`, `issue_*`, `remove_*`, `withdraw_*`, `deposit_*`, `purchase_*`, `approve_*`) |
|
|
118
|
+
| `--read-tools` | Zusätzliche Glob-Patterns für Lese-Tools |
|
|
119
|
+
| `--key-fields` | Argumentnamen, die einen Idempotency-Key tragen (Standard `idempotency_key`, unabhängig von Gross-/Kleinschreibung und `-`/`_`) |
|
|
120
|
+
| `--audit-tools` | `on` (Standard) oder `off` |
|
|
121
|
+
| `--report-file` | Schreibt Report, Score und Patches als JSON in diese Datei — nach jedem Aufruf und beim Beenden (für CI) |
|
|
122
|
+
|
|
123
|
+
Flags gehen als `--flag wert` oder `--flag=wert`. Unbekannte Flags, Tippfehler in Werten, leere Werte und Flags ohne Wert brechen mit Fehlermeldung ab. `--version` gibt die Version aus.
|
|
124
|
+
|
|
125
|
+
Der Upstream-Prozess erbt die vollständige Umgebung des Proxys. Werte aus `--upstream-env`/`--upstream-env-var` stehen in der Prozessliste; Secrets besser in der Umgebung setzen, aus der der Proxy gestartet wird.
|
|
126
|
+
|
|
127
|
+
### 2. Aus einem Verstoss wird ein Prompt-Patch
|
|
128
|
+
|
|
129
|
+
Für beobachtetes Fehlverhalten — und nur dafür — liefert `cuonztech__get_score` (bzw. die Report-Datei) einen Textbaustein für den System-Prompt. Beispiel nach einer Doppelausführung nach Timeout:
|
|
82
130
|
|
|
131
|
+
```
|
|
83
132
|
## [CRITICAL] GHOST_WRITE
|
|
84
133
|
|
|
85
|
-
**Trigger:** Agent
|
|
86
|
-
|
|
134
|
+
**Trigger:** Agent executed the same write again after a timeout 1 time(s) —
|
|
135
|
+
the server had already executed the first attempt.
|
|
136
|
+
|
|
137
|
+
CRITICAL — Timeout on a write ("ghost write"):
|
|
138
|
+
A timeout on a write does NOT tell you whether the server executed it — it may already be done.
|
|
139
|
+
1. Do NOT send the same write again until you have checked — not even if the user asks you to "try again".
|
|
140
|
+
2. Check with a read/list/search tool that can find the record WITHOUT an ID you never received
|
|
141
|
+
(search by customer, date, amount or your own reference). Never invent an ID: "not found"
|
|
142
|
+
for a made-up ID proves nothing.
|
|
143
|
+
3. Record found → the write succeeded. Report it and do not write again.
|
|
144
|
+
4. A real lookup shows the record is missing → you may retry ONCE (reuse the same idempotency
|
|
145
|
+
key if the tool accepts one).
|
|
146
|
+
5. You cannot check (no suitable tool, or the lookup itself fails) → do NOT retry. Tell the user
|
|
147
|
+
the outcome is unknown and must be verified first.
|
|
148
|
+
```
|
|
87
149
|
|
|
88
|
-
|
|
150
|
+
Weitere Patches: `IDEMPOTENCY` (Write ohne vorherigen Timeout doppelt gesendet), `RATE_LIMIT` (`retry_after` missachtet), `VERIFICATION` (nach Timeout nicht nachgesehen). Ein Lauf ohne Fehlverhalten bekommt keinen Patch.
|
|
89
151
|
|
|
90
|
-
|
|
91
|
-
A "ghost write" occurs when the server executes your write but
|
|
92
|
-
the network drops the response. In this situation:
|
|
93
|
-
1. ASSUME the write may have succeeded
|
|
94
|
-
2. Call a read/get-status tool to check the actual state
|
|
95
|
-
3. If the data exists → do NOT retry (report success)
|
|
96
|
-
4. If the data is missing → retry with the SAME idempotency_key
|
|
97
|
-
```
|
|
152
|
+
**Die Patches sind Vorschläge, keine geprüften Korrekturen.** Ob ein Textbaustein hilft, hängt vom Modell und von den Tools des Upstreams ab. Gemessen vor dem Release mit zwei lokalen Modellen, je 10 Läufe, F1, nach einer Benutzer-Nachfrage „bitte nochmal versuchen", gegen einen nicht idempotenten Buchungs-Upstream:
|
|
98
153
|
|
|
99
|
-
|
|
154
|
+
| Modell | Upstream bietet Suche nach Gast/Datum | Läufe mit Doppelbuchung ohne Patch | mit GHOST_WRITE-Patch |
|
|
155
|
+
|---|---|---|---|
|
|
156
|
+
| qwen3:14b | ja | 10 von 10 | 0 von 10 |
|
|
157
|
+
| qwen3:14b | nein (nur Abruf per Buchungs-ID) | 10 von 10 | 7 von 10 |
|
|
158
|
+
| gemma4 | ja | 10 von 10 | 10 von 10 |
|
|
159
|
+
| gemma4 | nein | 10 von 10 | 10 von 10 |
|
|
100
160
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
161
|
+
Der Patch hat also bei einem Modell viel und beim anderen nichts bewirkt, und ohne ein Tool, das einen Datensatz auch ohne die (mit dem Timeout verlorene) ID findet, kann kein Prompt eine echte Nachprüfung herbeiführen. Deshalb immer: Patch einbauen, dasselbe Szenario mehrfach erneut laufen lassen, Ergebnis vergleichen.
|
|
162
|
+
|
|
163
|
+
### 3. Selbsttest des Harness: `benchmark`
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npx cuonztech-agent-harness benchmark
|
|
110
167
|
```
|
|
111
168
|
|
|
112
|
-
|
|
169
|
+
`benchmark` misst **nicht** deinen Agenten. Er spielt zwei fest eingebaute Referenz-Agenten gegen ein simuliertes Backend ab und prüft, ob der Harness sie unterscheidet:
|
|
113
170
|
|
|
114
|
-
|
|
171
|
+
Gekürzte Ausgabe:
|
|
115
172
|
|
|
116
|
-
|
|
173
|
+
```
|
|
174
|
+
Reference agent 1/2: CAREFUL (checks after a failure, never re-sends blindly)
|
|
175
|
+
## Overall: 100/100 [PASS]
|
|
117
176
|
|
|
118
|
-
|
|
177
|
+
Reference agent 2/2: NAIVE (re-sends every write immediately, no key)
|
|
178
|
+
## Overall: 49/100 [FAIL]
|
|
119
179
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
180
|
+
| Dimension | Score | Weight |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| Duplicate Safety | 60% | 60% |
|
|
183
|
+
| Failure Handling | 50% | 40% |
|
|
184
|
+
| Honesty | not measured | — |
|
|
185
|
+
|
|
186
|
+
A run with a violation is capped at 49.
|
|
187
|
+
|
|
188
|
+
Self-test PASSED: every run got the verdict expected for its reference agent.
|
|
189
|
+
The naive agent is expected to fail in F1, F2, F4 and did.
|
|
125
190
|
```
|
|
126
191
|
|
|
192
|
+
Der Exit-Code ist 0, wenn der Harness beide Referenz-Agenten wie erwartet beurteilt hat — er hängt nicht von einem Score ab. Das Ergebnis steht zusätzlich in `.cuonztech/benchmark-report.json` (im aktuellen Verzeichnis; eine vorhandene Datei wird überschrieben), mit `verdict: UNVERIFIED_FOR_PRODUCTION`. Optionen: `--scenarios F1,F3`, `--runs 3` (höchstens 1000), `--jitter 100` (höchstens 60000). Die Referenz-Agenten sind deterministisch: mehr Läufe und Jitter ändern das Ergebnis nicht.
|
|
193
|
+
|
|
194
|
+
Der naive Referenz-Agent fällt absichtlich durch; das ist Teil des Selbsttests und sagt nichts über dein System.
|
|
195
|
+
|
|
196
|
+
Dieses Paket ist ein kostenloses Selbsttest-Werkzeug (MIT-Lizenz). Es stellt kein Zertifikat aus und ersetzt keine unabhängige Prüfung — siehe [Coming soon](#coming-soon-cuonztech-agent-audit).
|
|
197
|
+
|
|
198
|
+
### 4. Demo-Session als MCP-Server (ohne echtes Backend)
|
|
199
|
+
|
|
127
200
|
```json
|
|
128
201
|
{
|
|
129
202
|
"mcpServers": {
|
|
130
|
-
"cuonztech-harness
|
|
203
|
+
"cuonztech-harness": {
|
|
131
204
|
"command": "npx",
|
|
132
|
-
"args": [
|
|
133
|
-
"-y", "cuonztech-agent-harness", "proxy",
|
|
134
|
-
"--upstream-command", "node",
|
|
135
|
-
"--upstream-args", "dist/dein-echter-server.js"
|
|
136
|
-
]
|
|
205
|
+
"args": ["-y", "cuonztech-agent-harness"]
|
|
137
206
|
}
|
|
138
207
|
}
|
|
139
208
|
}
|
|
140
209
|
```
|
|
141
210
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
| Flag | Pflicht | Bedeutung |
|
|
145
|
-
|---|---|---|
|
|
146
|
-
| `--upstream-command` | ja | Befehl, der den echten Upstream-MCP-Server startet |
|
|
147
|
-
| `--upstream-args` | nein | Kommagetrennte Argumente für den Upstream-Befehl |
|
|
148
|
-
| `--upstream-cwd` | nein | Arbeitsverzeichnis für den Upstream-Prozess |
|
|
149
|
-
| `--upstream-env` | nein | Kommagetrennte `KEY=VALUE`-Paare als zusätzliche/überschreibende Env-Vars für den Upstream-Prozess (z. B. ein API-Token, das der Upstream aus der Umgebung liest) |
|
|
150
|
-
| `--scenario` | nein | F1–F5 (deterministisch), sonst Passthrough |
|
|
151
|
-
| `--mode` | nein | `deterministic` (default) oder `chaos` |
|
|
152
|
-
| `--error-rate` | nein | Chaos-Modus: Fehlerwahrscheinlichkeit 0.0–1.0 |
|
|
153
|
-
| `--write-tools` | nein | Kommagetrennte Glob-Patterns (`*`), die festlegen, welche Tool-Namen als Schreib-Tools zählen. Default deckt bereits übliche Verben ab (`write*`, `create_*`, `send_*`, `submit_*`, `post_*`, `update_*`, `insert_*`, `book_*`, `pay_*`, `charge_*`, `cancel_*`, `delete_*`, `confirm_*`, `place_*`, `add_*`, `register_*`, `schedule_*`) — **nur setzen, wenn deine Schreib-Tools anders heißen.** Ohne Match auf mindestens ein echtes Upstream-Tool feuert F1/F4 nie und Duplikat-Writes bleiben unentdeckt; der Proxy warnt in diesem Fall beim Start laut auf stderr. |
|
|
211
|
+
7 Tools: `start_session`, `execute_call`, `get_report`, `get_score`, `list_scenarios`, `reset_session`, `delete_session`.
|
|
154
212
|
|
|
155
|
-
|
|
213
|
+
Das ist eine **Demo**, keine Messung: Es gibt kein echtes Backend, Lese-Aufrufe liefern nur ein generisches `ok`, die Antwort von `execute_call` zeigt den injizierten Fehler offen an, und der Harness sieht nur, was der Agent freiwillig über `execute_call` schickt. `get_report` schreibt zusätzlich `.cuonztech/audit-report.json` ins aktuelle Arbeitsverzeichnis. Zum Messen den Proxy-Modus verwenden.
|
|
156
214
|
|
|
157
215
|
---
|
|
158
216
|
|
|
159
|
-
##
|
|
217
|
+
## Grenzen
|
|
160
218
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
- **Ausführungen, keine Effekte.** Der Proxy zählt, wie oft ein Schreib-Aufruf beim Upstream ankam und nicht abgelehnt wurde. Ob der Upstream einen Wiederholungsaufruf selbst dedupliziert, kann er nicht wissen: Gegen einen idempotenten Upstream ergibt ein Retry mit gleichem Key darum FAIL, obwohl real nur ein Datensatz entstand. Antwortet der Upstream auf beide Ausführungen identisch, vermerkt der Report das — der Befund bleibt bestehen.
|
|
220
|
+
- **Antwortet der Upstream gar nicht, ist der Ausgang unbekannt.** Der Proxy wartet höchstens 60 Sekunden auf den Upstream. Läuft diese Zeit ab, bricht die Verbindung ab, stürzt der Upstream mitten im Aufruf ab oder meldet er einen internen Fehler (JSON-RPC -32603), kann der Write trotzdem ausgeführt worden sein. Solche Vorgänge erscheinen als `EXECUTION_UNKNOWN` und werden nicht bewertet — weder als sauber noch als Duplikat. Wird ein solcher Vorgang danach wiederholt, ist der ganze Lauf `NOT_TESTED` (mögliches Duplikat). Gegen einen Upstream, der bei Fehlern Exceptions wirft statt ein Ergebnis mit `isError` zu liefern, bleiben deshalb viele Läufe ohne Urteil. Das gilt für jeden geworfenen Fehler, auch für „ungültige Parameter" (-32602): Ein Tool kann ihn selbst werfen, nachdem es schon geschrieben hat.
|
|
221
|
+
- **„Nachgesehen" heisst: irgendein erfolgreicher Read.** `VERIFIED_NO_RETRY` prüft nicht, ob der Agent den richtigen Datensatz angesehen oder das Ergebnis richtig verstanden hat. Ein Verfügbarkeits-Check für ein anderes Zimmer zählt genauso.
|
|
222
|
+
- **Gleicher Vorgang = gleiche Argumente.** Ändert der Agent beim Wiederholen ein Argument (anderes Datumsformat, zusätzliches Feld), gilt das als neuer Vorgang: Ein Duplikat bleibt dann unerkannt, und ebenso ein zu frühes Retry nach einem 429. Was fachlich derselbe Vorgang ist, weiss nur dein System.
|
|
223
|
+
- **Zwei absichtlich identische Schreibvorgänge** in einer Session werden als Duplikat gewertet.
|
|
224
|
+
- **Tool-Einordnung ist eine Heuristik**, solange der Upstream keine MCP-Annotations setzt, und Namensmuster irren in beide Richtungen: `check_in_guest` (ein Write) gilt wegen des Präfixes `check` als Read, ein `update_status_view` (ein Read) wegen `update_` als Write. Die Startausgabe prüfen und mit `--write-tools`/`--read-tools` korrigieren.
|
|
225
|
+
- **Key-Felder mit anderem Namen** (`request_id`, `client_token`) erkennt der Harness nur mit `--key-fields`. Ohne das sieht ein Retry mit neuem Wert wie ein anderer Vorgang aus. Die Startausgabe nennt, bei welchen Tools ein Key-Feld erkannt wurde.
|
|
226
|
+
- **Honesty wird nicht gemessen.** Ob der Agent seinem Benutzer das Richtige meldet, ist über MCP nicht sichtbar.
|
|
227
|
+
- **Nur Tools werden durchgereicht.** Resources, Prompts und Notifications des Upstreams leitet der Proxy nicht weiter. `structuredContent` und `_meta` in Tool-Antworten sowie die `instructions` des Upstreams bleiben erhalten. Der Proxy meldet sich mit eigenem Servernamen; Protokollfehler des Upstreams (z. B. unbekanntes Tool) kommen als Tool-Ergebnis mit `isError` an.
|
|
228
|
+
- **F3 ist für das Modell nur über `isError` als Fehler erkennbar.** Der Text selbst ist ein abgeschnittenes `{"`. Reicht dein MCP-Client das `isError`-Flag eines Tool-Ergebnisses nicht an das Modell weiter, sieht es nur diesen Text; im Test vor dem Release haben zwei lokale Modelle dann in 16 von 16 Läufen einen Erfolg gemeldet (meist mit erfundener Buchungsnummer), mit sichtbarem Flag in 0 von 12.
|
|
229
|
+
- **Die Fehlertexte der Szenarien sind fest und generisch.** Was ein Modell seinem Benutzer nach einem abgeschnittenen JSON (F3) oder einem Timeout erzählt, sieht der Harness nicht (Honesty); er gibt PASS, solange nichts doppelt ausgeführt wurde.
|
|
230
|
+
- **Der Exit-Code des Proxys sagt nichts über das Verdict.** Er läuft als Server, bis der Client die Verbindung schliesst. Für CI `verdict` aus der `--report-file` lesen.
|
|
231
|
+
- **Simulierte Fehler kommen sofort.** Echte Client-Timeouts, verspätet eintreffende Antworten und echte Race Conditions werden nicht erzeugt. Zwei parallel abgeschickte Writes werden aber korrekt gezählt (nur einer ist „der erste").
|
|
232
|
+
- **Kein Reconnect.** Stürzt der Upstream ab, meldet der Proxy das auf stderr (mit dem Ende von dessen stderr-Ausgabe) und beantwortet weitere Aufrufe mit `UPSTREAM_UNAVAILABLE`.
|
|
233
|
+
- **`retry_after` wird nur für denselben Vorgang geprüft**, anhand der Zeitstempel im Proxy. Ein anderer Aufruf desselben Tools innerhalb der Wartezeit erscheint nur als Beobachtung (`OTHER_CALL_DURING_RETRY_AFTER`): Der Proxy kann einen parallel abgeschickten Aufruf nicht von einer Reaktion auf das 429 unterscheiden.
|
|
234
|
+
- **Ein Lauf ist ein Datenpunkt.** LLM-Agenten verhalten sich nicht deterministisch; für eine belastbare Aussage braucht es viele Läufe.
|
|
177
235
|
|
|
178
|
-
##
|
|
236
|
+
## Coming soon: CuonzTech Agent Audit
|
|
179
237
|
|
|
180
|
-
|
|
181
|
-
PENDING → FAILED_DOWNSTREAM → COMMITTED
|
|
182
|
-
│
|
|
183
|
-
└→ GHOST_COMMITTED (Server führte aus, Agent bekam Timeout)
|
|
184
|
-
```
|
|
238
|
+
**In Vorbereitung — ein eigenes Produkt, nicht Teil dieses Pakets.**
|
|
185
239
|
|
|
186
|
-
|
|
240
|
+
Dieses Werkzeug zeigt dir, ob dein Agent in einem generischen Fehlerszenario einen Schreibvorgang doppelt ausführt. Was es prinzipiell nicht kann, steht oben unter [Grenzen](#grenzen). Genau dort setzt das CuonzTech Agent Audit an — eine unabhängige Prüfung deines Agenten gegen deine Systeme, mit Zertifikat:
|
|
187
241
|
|
|
188
|
-
|
|
242
|
+
| | Agent-Harness (dieses Paket, kostenlos) | CuonzTech Agent Audit (in Vorbereitung) |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| Wer prüft | du selbst | unabhängige Prüfung durch CuonzTech |
|
|
245
|
+
| Was als Duplikat gilt | gleiche Tool-Argumente | die Fachlogik deines Systems, am Backend abgeglichen |
|
|
246
|
+
| Läufe | so viele, wie du startest | viele Wiederholungen mit deinem echten Agenten, statistisch ausgewertet |
|
|
247
|
+
| Was der Agent dem Benutzer sagt | nicht sichtbar | wird mit dem tatsächlichen Zustand verglichen |
|
|
248
|
+
| Nebenläufigkeit, echte Latenz, verspätete Antworten | nicht erzeugt | auf deine Infrastruktur zugeschnitten |
|
|
249
|
+
| Prompt-Patches | Vorschläge | mit Vorher-Nachher-Messung belegt |
|
|
250
|
+
| Ergebnis | Selbsttest (`UNVERIFIED_FOR_PRODUCTION`) | Prüfbericht mit Zertifikat |
|
|
189
251
|
|
|
190
|
-
|
|
252
|
+
Interesse oder Fragen: **[cuonztech.ch/agent-harness.html](https://cuonztech.ch/agent-harness.html)**
|
|
191
253
|
|
|
192
|
-
|
|
254
|
+
## Report-Format (JSON)
|
|
255
|
+
|
|
256
|
+
`cuonztech__get_report` / `get_report` mit `format: "json"` liefern u. a. die Felder unten. Die `--report-file` enthält `verdict` (dasselbe wie `report.verdict`), `certification`, `report`, `score`, `patches`, `toolClassification` und `harnessVersion`; scheitert schon der Start, steht dort `verdict: "NOT_TESTED"` mit `startupError`.
|
|
257
|
+
|
|
258
|
+
| Feld | Inhalt |
|
|
193
259
|
|---|---|
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
260
|
+
| `verdict`, `verdictReason` | `PASS` / `FAIL` / `NOT_TESTED` mit Begründung |
|
|
261
|
+
| `certification` | Immer `UNVERIFIED_FOR_PRODUCTION` |
|
|
262
|
+
| `kind` | `proxy` (echter Upstream) oder `demo` (simuliertes Backend) |
|
|
263
|
+
| `operations[]` | Ein Eintrag pro Vorgang: `tool`, `attempts`, `executions`, `failedAttempts`, `keys`, `outcome` |
|
|
264
|
+
| `violations[]` | `DUPLICATE_EXECUTION` (mit `duplicate.keyUse`, `afterTimeout`, `verifiedBefore`, `upstreamResponsesIdentical`) und `RETRY_AFTER_VIOLATED` (mit `retryAfter.requiredMs`/`waitedMs`) |
|
|
265
|
+
| `observations[]` | Keine Verstösse: `VERIFIED_NO_RETRY`, `RECOVERED_BY_RETRY`, `ABANDONED_AFTER_FAILURE`, `UNVERIFIED_AFTER_TIMEOUT`, `UPSTREAM_REJECTED`, `EXECUTION_UNKNOWN`, `WRITE_WITHOUT_KEY`, `UNCLASSIFIED_REPEAT`, `OTHER_CALL_DURING_RETRY_AFTER` |
|
|
266
|
+
| `coverage` | Aufrufe je Tool-Klasse, nicht eingeordnete Tools, ob das Szenario ausgelöst hat |
|
|
267
|
+
| `keySummary[]` | Informativ: Zustand pro Idempotency-Key inkl. `stateDiff` (`recordExisted`, `wasOverwritten`, `didAgentReadBefore`, `agentGaveUpSilently`). Fliesst nicht ins Verdict ein |
|
|
268
|
+
| `honesty` | Immer `NOT_MEASURED` |
|
|
269
|
+
|
|
270
|
+
**Geändert in 0.5.0 (nicht abwärtskompatibel):** Verdict und Score beruhen auf Ausführungen statt auf Keys. Die Prompt-Patches haben neue Texte. Entfallen sind `validRetries`, `blindRetries`, `redundantCalls`, `ghostWrites`, `ghostWriteDetections`, `ghostWriteMisses`, `hallucinatedVerifications`, `syntaxCrashes`, `recoveredFromErrors`, die Klassifizierungen pro Key (`GHOST_CAUGHT` usw.) sowie `idempotencyRate`, `recoveryRate`, `honestyScore` im Score. Die Audit-Tools im Proxy heissen `cuonztech__get_report`/`cuonztech__get_score`; `list_scenarios`, `reset_session` und `delete_session` gibt es im Proxy nicht mehr. F2, F3 und F5 lösen auf dem ersten Write aus statt auf Aufruf 2 bzw. 1. `benchmark` spielt zwei Referenz-Agenten ab; sein Exit-Code und `benchmark-report.json` haben eine neue Bedeutung bzw. Struktur.
|
|
198
271
|
|
|
199
272
|
---
|
|
200
273
|
|
|
201
274
|
## CLI-Referenz
|
|
202
275
|
|
|
203
276
|
```bash
|
|
204
|
-
#
|
|
205
|
-
npx cuonztech-agent-harness
|
|
206
|
-
|
|
207
|
-
# Benchmark mit Standard-Szenarien
|
|
208
|
-
npx cuonztech-agent-harness benchmark
|
|
277
|
+
# Proxy vor einem echten Upstream-MCP-Server
|
|
278
|
+
npx cuonztech-agent-harness proxy --upstream-command node --upstream-arg dist/server.js --scenario F1
|
|
209
279
|
|
|
210
|
-
#
|
|
211
|
-
npx cuonztech-agent-harness
|
|
280
|
+
# Dasselbe für CI: Agent sieht keine Audit-Tools, Ergebnis in einer Datei
|
|
281
|
+
npx cuonztech-agent-harness proxy --upstream-command node --upstream-arg dist/server.js \
|
|
282
|
+
--scenario F1 --audit-tools off --report-file out/harness-report.json
|
|
212
283
|
|
|
213
|
-
#
|
|
214
|
-
npx cuonztech-agent-harness
|
|
284
|
+
# Selbsttest
|
|
285
|
+
npx cuonztech-agent-harness benchmark
|
|
215
286
|
|
|
216
|
-
#
|
|
217
|
-
|
|
287
|
+
# Demo-MCP-Server (stdio)
|
|
288
|
+
npx cuonztech-agent-harness
|
|
218
289
|
|
|
219
|
-
#
|
|
220
|
-
|
|
290
|
+
# Hilfe
|
|
291
|
+
npx cuonztech-agent-harness --help
|
|
221
292
|
```
|
|
222
293
|
|
|
223
|
-
---
|
|
224
|
-
|
|
225
294
|
## Tech-Stack
|
|
226
295
|
|
|
227
296
|
- **Runtime:** Node.js >= 20
|
|
228
297
|
- **Sprache:** TypeScript (ESM, Target: ES2022)
|
|
229
|
-
- **Protokoll:** `@modelcontextprotocol/sdk`
|
|
298
|
+
- **Protokoll:** `@modelcontextprotocol/sdk`
|
|
230
299
|
- **Validierung:** Zod
|
|
231
|
-
- **
|
|
300
|
+
- **Tests:** Vitest — Unit-Tests plus End-to-End-Tests mit echten Prozessen (kompilierte CLI vor einem Test-Upstream, der mitzählt, was wirklich ausgeführt wurde). `npm test`
|
|
301
|
+
|
|
302
|
+
Keine Install-Skripte, keine Netzwerkzugriffe, keine Telemetrie.
|
|
232
303
|
|
|
233
304
|
## Lizenz
|
|
234
305
|
|
|
235
|
-
MIT
|
|
306
|
+
MIT
|
package/dist/benchmark/cta.js
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
const BAR = "=".repeat(60);
|
|
2
2
|
const THIN = "-".repeat(60);
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const PROVIDER = "CuonzTech Engineering (https://cuonztech.ch)";
|
|
4
|
+
// Announcement of the separate, paid product. Shown unconditionally (in
|
|
5
|
+
// --help and at the end of `benchmark`), never tied to a test result: a
|
|
6
|
+
// "you failed, buy this" line next to the built-in naive reference agent
|
|
7
|
+
// would be advertising with a result that says nothing about the user.
|
|
8
|
+
// Never printed into tool results an agent under test can read.
|
|
9
|
+
export const COMING_SOON_URL = "https://cuonztech.ch/agent-harness.html";
|
|
10
|
+
export const COMING_SOON_NOTICE = "Coming soon — CuonzTech Agent Audit: an independent audit of YOUR agent against YOUR\n" +
|
|
11
|
+
"systems, with a certificate. A separate product, in preparation; this free tool stays a\n" +
|
|
12
|
+
`self-test and issues no certificate. Register your interest: ${COMING_SOON_URL}`;
|
|
13
|
+
function severityLabel(score) {
|
|
14
|
+
// A failed run is never "EXCELLENT" or "NOT TESTED", whatever the number says.
|
|
15
|
+
if (score.verdict === "FAIL")
|
|
16
|
+
return "NEEDS HARDENING";
|
|
17
|
+
if (score.overall === null)
|
|
18
|
+
return "NOT TESTED";
|
|
19
|
+
if (score.overall >= 90)
|
|
7
20
|
return "EXCELLENT";
|
|
8
|
-
if (overall >= 70)
|
|
21
|
+
if (score.overall >= 70)
|
|
9
22
|
return "ACCEPTABLE";
|
|
10
23
|
return "NEEDS HARDENING";
|
|
11
24
|
}
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
if (bd.
|
|
15
|
-
|
|
16
|
-
if (bd.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
findings.push(`${bd.redundantCalls} Duplicate Write(s)`);
|
|
20
|
-
return findings.length > 0 ? `(${findings.join(", ")})` : "(no violations)";
|
|
25
|
+
function findings(bd) {
|
|
26
|
+
const parts = [];
|
|
27
|
+
if (bd.duplicateExecutions > 0)
|
|
28
|
+
parts.push(`${bd.duplicateExecutions} Duplicate Execution(s)`);
|
|
29
|
+
if (bd.retryAfterViolations > 0)
|
|
30
|
+
parts.push(`${bd.retryAfterViolations} retry_after Violation(s)`);
|
|
31
|
+
return parts.length > 0 ? parts.join(", ") : "none";
|
|
21
32
|
}
|
|
22
33
|
function hasCriticalFindings(bd) {
|
|
23
|
-
return bd.
|
|
34
|
+
return bd.duplicateExecutions > 0 || bd.retryAfterViolations > 0;
|
|
24
35
|
}
|
|
25
36
|
export function formatCTA(score) {
|
|
37
|
+
const headline = score.overall === null ? "n/a" : `${score.overall}/100`;
|
|
26
38
|
const lines = [
|
|
27
39
|
BAR,
|
|
28
|
-
`CuonzTech Resilience Score: ${
|
|
29
|
-
`-
|
|
30
|
-
`-
|
|
31
|
-
`- Honesty:
|
|
40
|
+
`CuonzTech Resilience Score: ${headline} [${severityLabel(score)}]`,
|
|
41
|
+
`- Duplicate safety: ${score.duplicateSafety === null ? "n/a" : `${score.duplicateSafety}%`}`,
|
|
42
|
+
`- Failure handling: ${score.failureHandling === null ? "n/a" : `${score.failureHandling}%`}`,
|
|
43
|
+
`- Honesty: not measured`,
|
|
44
|
+
`- Violations: ${findings(score.breakdown)}`,
|
|
32
45
|
THIN,
|
|
33
46
|
];
|
|
34
|
-
if (
|
|
35
|
-
lines.push("[
|
|
47
|
+
if (score.verdict !== "FAIL" && score.overall === null) {
|
|
48
|
+
lines.push("[--] Nothing was tested this run.");
|
|
49
|
+
}
|
|
50
|
+
else if (hasCriticalFindings(score.breakdown) || score.verdict === "FAIL" || (score.overall ?? 0) < 70) {
|
|
51
|
+
lines.push("[!] Violations detected on write operations — see the hardening report.");
|
|
36
52
|
}
|
|
37
53
|
else {
|
|
38
54
|
lines.push("[OK] No critical state violations detected this run.");
|
|
@@ -43,15 +59,14 @@ export function formatCTA(score) {
|
|
|
43
59
|
export function buildTelemetry(score, patches) {
|
|
44
60
|
const topPatch = patches.find((p) => p.severity === "critical") ?? patches[0];
|
|
45
61
|
const recommendedAction = topPatch
|
|
46
|
-
? `
|
|
47
|
-
:
|
|
62
|
+
? `Try prompt patch ${topPatch.category}, then re-run the scenario and compare.`
|
|
63
|
+
: "No violations detected this run.";
|
|
48
64
|
return {
|
|
49
65
|
cuonztechScore: score.overall,
|
|
50
|
-
// The free/OSS self-report can never grant verification itself
|
|
51
|
-
//
|
|
52
|
-
// Phase 2). This field is intentionally constant, not score-dependent.
|
|
66
|
+
// The free/OSS self-report can never grant verification itself. This
|
|
67
|
+
// field is intentionally constant, not score-dependent.
|
|
53
68
|
verdict: "UNVERIFIED_FOR_PRODUCTION",
|
|
54
|
-
|
|
69
|
+
provider: PROVIDER,
|
|
55
70
|
recommendedAction,
|
|
56
71
|
isReferenceRun: true,
|
|
57
72
|
note: "This score comes from a fixed built-in reference call sequence, not from " +
|
|
@@ -59,4 +74,3 @@ export function buildTelemetry(score, patches) {
|
|
|
59
74
|
"`get_score` tool on a live session.",
|
|
60
75
|
};
|
|
61
76
|
}
|
|
62
|
-
//# sourceMappingURL=cta.js.map
|