cuonztech-agent-harness 0.7.0 → 0.9.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/LICENSE +220 -21
- package/LICENSE-MIT-bis-0.7.0.txt +21 -0
- package/README.md +92 -19
- package/dist/benchmark/cta.js +3 -3
- package/dist/campaign/runner.js +22 -3
- package/dist/core/claim-check.js +1 -0
- package/dist/core/effect-probe.js +217 -0
- package/dist/core/report-generator.js +217 -11
- package/dist/doctor.js +19 -3
- package/dist/dossier-report.js +62 -4
- package/dist/index.js +189 -9
- package/dist/license/ed25519.js +57 -0
- package/dist/license/gate.js +91 -0
- package/dist/license/index.js +5 -0
- package/dist/license/publicKey.js +5 -0
- package/dist/license/types.js +1 -0
- package/dist/license/verifier.js +89 -0
- package/dist/plain-report.js +90 -17
- package/dist/proxy/classification-doubt.js +89 -0
- package/dist/proxy/gateway.js +3 -1
- package/dist/proxy/interceptor.js +129 -0
- package/dist/report-file-guard.js +75 -1
- package/dist/tools/handlers.js +9 -4
- package/dist/ui/server.js +47 -5
- package/package.json +3 -2
- package/server.json +2 -2
package/dist/dossier-report.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// aus demselben Seitenmodell (Abschnitte aus Blöcken), damit beide Fassungen
|
|
4
4
|
// dieselbe Aussage tragen. Alles Dynamische (Tool-Namen, Argumente, Agenten-Text,
|
|
5
5
|
// Kunden- und Systemnamen) wird beim Rendern maskiert, nie vorher.
|
|
6
|
-
import { LIMITS, OUTCOMES, claimHeadline, verdictText } from "./plain-report.js";
|
|
6
|
+
import { LIMITS, OUTCOMES, claimHeadline, effectProbeLinesDe, verdictText } from "./plain-report.js";
|
|
7
|
+
import { basename } from "node:path";
|
|
7
8
|
import { evaluateAgentClaim } from "./core/claim-check.js";
|
|
8
9
|
import { verifyReportIntegrity } from "./core/artifact-integrity.js";
|
|
10
|
+
import { PURCHASE_URL } from "./license/gate.js";
|
|
9
11
|
const TITLE = "CuonzTech Agent-Harness · Technisches Prüf- & Architektur-Dossier";
|
|
10
12
|
const RISK_DE = { HIGH: "HOCH", MEDIUM: "MITTEL", LOW: "NIEDRIG" };
|
|
11
13
|
const RISK_TONE = { HIGH: "fail", MEDIUM: "warn", LOW: "pass" };
|
|
@@ -127,7 +129,7 @@ function businessMatrix(file) {
|
|
|
127
129
|
rows.push({ dimension: d1, label: "FAIL", tone: "fail", basis: `${report.duplicateExecutions} Doppelausführung(en) im Zielsystem nachgewiesen.` });
|
|
128
130
|
}
|
|
129
131
|
else if (report.coverage.writeCalls > 0 && !report.operations.some((op) => op.outcome === "EXECUTION_UNKNOWN")) {
|
|
130
|
-
rows.push({ dimension: d1, label: "PASS", tone: "pass", basis: `${report.coverage.writeCalls} Schreibaufruf(e) beobachtet, keine Doppelausführung
|
|
132
|
+
rows.push({ dimension: d1, label: "PASS", tone: "pass", basis: `${report.coverage.writeCalls} Schreibaufruf(e) beobachtet, keine Doppelausführung.${report.effectProbe?.conclusion === "deduplicated" ? " Gezählt wurden Datensätze (Wirkungs-Sonde): Wiederholungen haben keinen zweiten erzeugt." : ""}` });
|
|
131
133
|
}
|
|
132
134
|
else {
|
|
133
135
|
rows.push(notTested(d1, report.coverage.writeCalls === 0 ? "Es wurde kein Schreibvorgang beobachtet." : "Der Ausgang mindestens eines Vorgangs blieb offen; weder bestätigt noch als doppelt nachgewiesen."));
|
|
@@ -145,6 +147,10 @@ function businessMatrix(file) {
|
|
|
145
147
|
else if (affected.some((op) => op.outcome === "UNVERIFIED_AFTER_TIMEOUT" || op.outcome === "EXECUTION_UNKNOWN")) {
|
|
146
148
|
rows.push({ dimension: d2, label: "HINWEIS", tone: "warn", basis: "Der Agent hat nach der Störung nicht nachgesehen, ob der Vorgang schon ausgeführt war (UNVERIFIED_AFTER_TIMEOUT); der Ausgang bleibt offen." });
|
|
147
149
|
}
|
|
150
|
+
else if (affected.some((op) => op.outcome === "DEDUPLICATED_RETRY")) {
|
|
151
|
+
const n = affected.filter((op) => op.outcome === "DEDUPLICATED_RETRY").length;
|
|
152
|
+
rows.push({ dimension: d2, label: "HINWEIS", tone: "warn", basis: `Der Agent hat nach der Störung erneut gesendet (DEDUPLICATED_RETRY, ${n}×); die Wirkungs-Sonde hat keinen zweiten Datensatz gezählt, das Zielsystem hat die Wiederholung selbst erkannt. Nachgesehen hat der Agent damit nicht unbedingt; die Wiederholung blieb ohne Folgen.` });
|
|
153
|
+
}
|
|
148
154
|
else if (affected.some((op) => op.outcome === "RETRY_AFTER_VIOLATED")) {
|
|
149
155
|
rows.push(notTested(d2, "Der Lauf wird unter der Wartezeit-Disziplin bewertet (siehe nächste Zeile)."));
|
|
150
156
|
}
|
|
@@ -214,6 +220,9 @@ function summarySection(file) {
|
|
|
214
220
|
intro: "Die Matrix beurteilt vier konkrete Risikodimensionen ausschliesslich anhand der Messdaten dieses einen Laufs.",
|
|
215
221
|
blocks: [
|
|
216
222
|
{ t: "table", head: ["Risikodimension", "Bewertung", "Grundlage (Messdaten)"], rows: rows.map((r) => [[r.dimension], [badgeOf(r.label, r.tone)], [r.basis]]) },
|
|
223
|
+
...(effectProbeLinesDe(file.report).length > 0
|
|
224
|
+
? [{ t: "note", title: "Wirkungs-Sonde (--effect-probe)", items: effectProbeLinesDe(file.report).map((l) => inlineMarkup(l)) }]
|
|
225
|
+
: []),
|
|
217
226
|
],
|
|
218
227
|
};
|
|
219
228
|
}
|
|
@@ -435,15 +444,56 @@ function integritySection(file, options) {
|
|
|
435
444
|
// Mit geprüfter Antwort würde der erste Standardsatz («nicht gemessen») Sektion 4 widersprechen.
|
|
436
445
|
...(answered ? LIMITS.slice(1) : LIMITS).map(inlineMarkup),
|
|
437
446
|
[answered ? "Der Harness sieht von sich aus nur die Tool-Aufrufe; die Antwort des Agenten an den Nutzer wurde in Sektion 4 nur soweit geprüft, wie sie übergeben wurde und sich mit festen Regeln aus den Tool-Aufrufen beurteilen lässt." : "Eine Antwort des Agenten an den Nutzer wurde nicht übergeben und deshalb in Sektion 4 nicht geprüft."],
|
|
447
|
+
...(file.previousRun ? [inlineMarkup(`Der Lauf folgte auf einen früheren Lauf (archiviert unter \`${basename(file.previousRun.file)}\`). Durch den Neustart des Prüfstands gilt dieses Urteil nur für die Aufrufe nach dem Neustart.`)] : []),
|
|
438
448
|
],
|
|
439
449
|
});
|
|
440
450
|
return { id: "integrity", title: "6. Kryptografischer Integritäts-Nachweis & Methodische Grenzen", blocks };
|
|
441
451
|
}
|
|
452
|
+
// Gekürzte Fassung ohne gültige Lizenz (wie «Nur Zahlen» bei CPCG): Kopf, Score und das Urteil je Szenario.
|
|
453
|
+
// Der Lauf und sein Urteil sind echt; Details, Aufruf-Protokoll, Härtungspaket und Anhänge gehören zum Pro-Umfang.
|
|
454
|
+
function limitedSection(file) {
|
|
455
|
+
const verdict = file.verdict ?? file.report?.verdict ?? "NOT_TESTED";
|
|
456
|
+
const tone = verdict === "PASS" ? "pass" : verdict === "FAIL" ? "fail" : "warn";
|
|
457
|
+
const score = file.score?.overall;
|
|
458
|
+
const scenario = file.report?.scenarioId ?? (file.report?.mode ? `Modus ${file.report.mode}` : "—");
|
|
459
|
+
return {
|
|
460
|
+
id: "scenario-verdict",
|
|
461
|
+
title: "Urteil je Szenario",
|
|
462
|
+
blocks: [
|
|
463
|
+
{
|
|
464
|
+
t: "table",
|
|
465
|
+
head: ["Szenario", "Urteil", "Resilience Score", "Aufrufe", "Schreibvorgänge"],
|
|
466
|
+
rows: [
|
|
467
|
+
[
|
|
468
|
+
[clean(scenario)],
|
|
469
|
+
[badgeOf(verdict, tone)],
|
|
470
|
+
[file.report && typeof score === "number" ? `${score} / 100` : "nicht bewertet"],
|
|
471
|
+
[String(file.report?.totalCalls ?? "—")],
|
|
472
|
+
[String(file.report?.coverage?.writeCalls ?? "—")],
|
|
473
|
+
],
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
t: "note",
|
|
478
|
+
title: "Umfang dieser Fassung",
|
|
479
|
+
items: [
|
|
480
|
+
["Das Urteil, der Score und die Zahlen stammen aus dem echten Lauf und sind vollständig."],
|
|
481
|
+
["Gekürzt ohne Lizenz: Geschäftsrisiko-Matrix, Schnittstellen-Gutachten, Aufruf-Protokoll mit Parameter-Diff, Abgleich mit der Antwort des Agenten, Härtungspaket und Integritäts-Nachweis."],
|
|
482
|
+
[`Das vollständige Prüf-Dossier gehört zum Pro-Umfang. Lizenz: ${PURCHASE_URL}`],
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
442
488
|
function build(file, options = {}) {
|
|
443
489
|
const { headline, explanation } = verdictText(file);
|
|
444
490
|
const score = file.score?.overall;
|
|
445
|
-
const
|
|
491
|
+
const limited = options.limited === true;
|
|
492
|
+
const sections = limited
|
|
493
|
+
? [limitedSection(file)]
|
|
494
|
+
: [summarySection(file), schemaSection(file), forensicsSection(file), claimSection(file, options), hardeningSection(file), integritySection(file, options)];
|
|
446
495
|
return {
|
|
496
|
+
limited,
|
|
447
497
|
headline,
|
|
448
498
|
explanation,
|
|
449
499
|
verdictLabel: headline,
|
|
@@ -456,6 +506,14 @@ function build(file, options = {}) {
|
|
|
456
506
|
["Auftraggeber / Umgebung", clean(options.customerName ?? "Lokale Prüfumgebung")],
|
|
457
507
|
["Session-ID", clean(file.report?.sessionId ?? "—")],
|
|
458
508
|
["Szenario / Modus", clean(file.report?.scenarioId ?? file.report?.mode ?? "—")],
|
|
509
|
+
[
|
|
510
|
+
"Lizenzstatus",
|
|
511
|
+
options.licenseVerdict?.valid
|
|
512
|
+
? `PRO (${options.licenseVerdict.claims?.sub ?? options.licenseVerdict.claims?.customer ?? "Aktiviert"})`
|
|
513
|
+
: limited
|
|
514
|
+
? `EVALUATION (keine gültige Lizenz, gekürzte Fassung; Lizenz: ${PURCHASE_URL})`
|
|
515
|
+
: "EVALUATION (Bibliotheksaufruf ohne Lizenzprüfung, volle Fassung)",
|
|
516
|
+
],
|
|
459
517
|
],
|
|
460
518
|
sections,
|
|
461
519
|
};
|
|
@@ -566,7 +624,7 @@ export function renderDossierHtml(file, options) {
|
|
|
566
624
|
`<title>${escHtml(TITLE)}</title>`,
|
|
567
625
|
`<style>${CSS}</style></head><body><main class="wrap"><header>`,
|
|
568
626
|
`<div class="topline"><strong>${escHtml(TITLE)}</strong><span>${escHtml(d.date)} · ${escHtml(d.version)}</span></div>`,
|
|
569
|
-
`<div class="hero"><div><h1>${escHtml(clean(d.meta[0][1]))}</h1><p
|
|
627
|
+
`<div class="hero"><div><h1>${escHtml(clean(d.meta[0][1]))}</h1><p>${d.limited ? "Gekürzte Fassung ohne Lizenz: Kopf, Score und Urteil je Szenario. Der Lauf und sein Urteil sind echt." : "Dieses Dossier fasst zusammen, wie der Agent beim Schreiben in das Zielsystem vorgegangen ist, wie gut die Schnittstellen des Servers gegen Doppelausführung abgesichert sind und was zu tun ist."}</p></div>`,
|
|
570
628
|
`<aside class="verdict"><span class="caption">Gesamturteil</span><strong><span class="badge ${d.tone}">${escHtml(d.verdictLabel)}</span></strong><p>${escHtml(d.explanation)}</p><p>Resilience Score: <strong>${escHtml(d.scoreText)}</strong></p></aside></div>`,
|
|
571
629
|
`<dl class="meta">${d.meta.map(([l, v]) => `<div><dt>${escHtml(l)}</dt><dd>${escHtml(v)}</dd></div>`).join("")}</dl>`,
|
|
572
630
|
"</header>",
|
package/dist/index.js
CHANGED
|
@@ -12,10 +12,11 @@ import { computeReportIntegrity } from "./core/artifact-integrity.js";
|
|
|
12
12
|
import { ProxyInterceptor } from "./proxy/interceptor.js";
|
|
13
13
|
import { runDoctor } from "./doctor.js";
|
|
14
14
|
import { runReportMode } from "./plain-report.js";
|
|
15
|
-
import { isReplaceableReportFile, isReservedFileName, notReplaceableMessage } from "./report-file-guard.js";
|
|
15
|
+
import { archivePreviousReportSync, isReplaceableReportFile, isReservedFileName, notReplaceableMessage } from "./report-file-guard.js";
|
|
16
16
|
import { runUiMode } from "./ui/server.js";
|
|
17
17
|
import { runCampaignMode } from "./campaign/runner.js";
|
|
18
18
|
import { createProxyServer, findToolNameCollisions } from "./proxy/gateway.js";
|
|
19
|
+
import { resolveProGate, reduceSchemaAudit, reasonSentence, FREE_SCOPE_DE, PURCHASE_URL } from "./license/index.js";
|
|
19
20
|
import { getScenarioById, ALL_SCENARIOS } from "./scenarios/f1-f5.js";
|
|
20
21
|
import { VERSION } from "./version.js";
|
|
21
22
|
import { writeFile, mkdir, rename, stat, realpath, unlink } from "node:fs/promises";
|
|
@@ -35,13 +36,13 @@ function printHelp() {
|
|
|
35
36
|
` Check the setup before a run (Node version, server\n` +
|
|
36
37
|
` start, tool classification, report file). Calls only\n` +
|
|
37
38
|
` initialize and tools/list; runs no tool.\n` +
|
|
38
|
-
` cuonztech-agent-harness ui [--port <n>] [--no-open]\n` +
|
|
39
|
+
` cuonztech-agent-harness ui [--port <n>] [--no-open] (Pro: needs a license)\n` +
|
|
39
40
|
` Guided web interface for people without a terminal\n` +
|
|
40
41
|
` (local only, 127.0.0.1): demo, setup check, result.\n` +
|
|
41
42
|
` cuonztech-agent-harness report <report-file> [--format md|html] [--out <file>]\n` +
|
|
42
43
|
` Turn the proxy's --report-file into a plain-language\n` +
|
|
43
44
|
` page (German): result, what the agent did, limits.\n` +
|
|
44
|
-
` cuonztech-agent-harness campaign [--runs <n>] [--format md|html] [--out <file>]\n` +
|
|
45
|
+
` cuonztech-agent-harness campaign [--runs <n>] [--format md|html] [--out <file>] (Pro)\n` +
|
|
45
46
|
` Before/after comparison of three fixed reference\n` +
|
|
46
47
|
` profiles incl. the two-turn user-pressure scenario\n` +
|
|
47
48
|
` (German report; measures no foreign agent).\n` +
|
|
@@ -79,6 +80,26 @@ function printHelp() {
|
|
|
79
80
|
` --read-tools pat1,pat2,... Extra glob patterns for tool names that count as reads\n` +
|
|
80
81
|
` --key-fields a,b Argument names that carry an idempotency key\n` +
|
|
81
82
|
` (default: idempotency_key, any case/separator)\n` +
|
|
83
|
+
` --effect-probe <tool> A READ tool of the upstream the harness calls itself, before
|
|
84
|
+
` +
|
|
85
|
+
` the first call and when the report is read, to count
|
|
86
|
+
` +
|
|
87
|
+
` records. The audit then counts records, not executions
|
|
88
|
+
` +
|
|
89
|
+
` (an upstream that recognises a repeat is no longer a FAIL).
|
|
90
|
+
` +
|
|
91
|
+
` --effect-probe-args '{"k":"v"}' Arguments for the probe tool (JSON object)
|
|
92
|
+
` +
|
|
93
|
+
` --effect-count <path> Dotted path to the array/number/object to count in the
|
|
94
|
+
` +
|
|
95
|
+
` probe's answer (default: the only array, or count/total)
|
|
96
|
+
` +
|
|
97
|
+
` --effect-tools a,b Write tools whose records the probe counts (glob patterns).
|
|
98
|
+
` +
|
|
99
|
+
` Needed when the agent also calls other write tools
|
|
100
|
+
` +
|
|
101
|
+
` (update_*, send_*): a count cannot be attributed otherwise
|
|
102
|
+
` +
|
|
82
103
|
` --audit-tools on|off Expose cuonztech__get_report / cuonztech__get_score\n` +
|
|
83
104
|
` to the connected agent (default: on). For a\n` +
|
|
84
105
|
` measurement use off: their descriptions tell the\n` +
|
|
@@ -91,6 +112,14 @@ function printHelp() {
|
|
|
91
112
|
`Tool classification in proxy mode: explicit flags first, then the upstream's own\n` +
|
|
92
113
|
`MCP annotations (readOnlyHint/destructiveHint), then name patterns. The result is\n` +
|
|
93
114
|
`printed to stderr at startup; unclassified tools are passed through but not scored.\n\n` +
|
|
115
|
+
` --license <key> Licence key (or env AGENT_AUDIT_LICENSE), see "Lizenz" below\n\n` +
|
|
116
|
+
`Lizenz (Schlüssel CAUDIT-..., Prüfung offline):\n` +
|
|
117
|
+
` Der Lauf selbst ist immer vollständig; Urteil, Score und Exit-Code sind echt.\n` +
|
|
118
|
+
` Frei: ${FREE_SCOPE_DE}.\n` +
|
|
119
|
+
` Pro: report --dossier (ohne Lizenz nur Kopf, Score, Urteil), ui, campaign, Schema-Befunde\n` +
|
|
120
|
+
` und Textbausteine (Härtungsvorschläge) im Report.\n` +
|
|
121
|
+
` Angabe per --license <key> (proxy, report, ui, campaign) oder Umgebungsvariable AGENT_AUDIT_LICENSE.\n` +
|
|
122
|
+
` Lizenz erwerben: ${PURCHASE_URL}\n\n` +
|
|
94
123
|
`The proxy is a long-running server: its exit code says nothing about the verdict.\n` +
|
|
95
124
|
`Read the verdict from --report-file. Exit code 1 means the run itself broke\n` +
|
|
96
125
|
`(bad arguments, upstream did not start, report file not writable).\n\n` +
|
|
@@ -183,6 +212,21 @@ function parseFlags(argv, spec) {
|
|
|
183
212
|
// failed start — including a rejected command line — replaces a report left
|
|
184
213
|
// over from an earlier run instead of leaving its old PASS in place.
|
|
185
214
|
let startupReportFile = null;
|
|
215
|
+
let archivedPreviousRun = null;
|
|
216
|
+
let archiveAttempted = false;
|
|
217
|
+
function tryArchivePreviousReport() {
|
|
218
|
+
if (archiveAttempted || !startupReportFile)
|
|
219
|
+
return;
|
|
220
|
+
archiveAttempted = true;
|
|
221
|
+
try {
|
|
222
|
+
archivedPreviousRun = archivePreviousReportSync(startupReportFile);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// Fail closed: could not archive existing real report
|
|
226
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
186
230
|
// mkdir -p without { recursive: true }: on Linux pseudo file systems
|
|
187
231
|
// (/proc/…) Node's recursive mkdir never returns and spins at full CPU.
|
|
188
232
|
// Here every level is created once and the first error ends it.
|
|
@@ -219,6 +263,7 @@ function writeStartupStateSync(extra) {
|
|
|
219
263
|
if (!startupReportFile)
|
|
220
264
|
return;
|
|
221
265
|
try {
|
|
266
|
+
tryArchivePreviousReport();
|
|
222
267
|
// Only ever a regular file (or nothing yet): writing "the report" over
|
|
223
268
|
// a directory, a device node or a pipe is never what was meant.
|
|
224
269
|
const existing = statSync(startupReportFile, { throwIfNoEntry: false });
|
|
@@ -231,6 +276,16 @@ function writeStartupStateSync(extra) {
|
|
|
231
276
|
harnessVersion: VERSION,
|
|
232
277
|
certification: "UNVERIFIED_FOR_PRODUCTION",
|
|
233
278
|
verdict: "NOT_TESTED",
|
|
279
|
+
...(archivedPreviousRun
|
|
280
|
+
? {
|
|
281
|
+
previousRun: {
|
|
282
|
+
file: archivedPreviousRun.archivedPath,
|
|
283
|
+
verdict: archivedPreviousRun.verdict,
|
|
284
|
+
callsCount: archivedPreviousRun.callsCount,
|
|
285
|
+
archivedAt: archivedPreviousRun.archivedAt,
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
: {}),
|
|
234
289
|
...extra,
|
|
235
290
|
generatedAt: new Date().toISOString(),
|
|
236
291
|
}, null, 2), "utf-8");
|
|
@@ -341,10 +396,44 @@ const PROXY_FLAGS = {
|
|
|
341
396
|
"--write-tools": "single",
|
|
342
397
|
"--read-tools": "single",
|
|
343
398
|
"--key-fields": "single",
|
|
399
|
+
"--effect-probe": "single",
|
|
400
|
+
"--effect-probe-args": "single",
|
|
401
|
+
"--effect-count": "single",
|
|
402
|
+
"--effect-tools": "single",
|
|
344
403
|
"--audit-tools": "single",
|
|
345
404
|
"--report-file": "single",
|
|
346
405
|
"--allow-writes": "boolean",
|
|
406
|
+
"--license": "single",
|
|
347
407
|
};
|
|
408
|
+
// --effect-probe-args: a JSON object. cmd.exe and Windows PowerShell 5.1 strip the
|
|
409
|
+
// double quotes of '{"status":"open"}' and hand over {status:open}; that form is
|
|
410
|
+
// read as well (flat key:value pairs, numbers/true/false/null as such).
|
|
411
|
+
function parseProbeArgs(raw) {
|
|
412
|
+
try {
|
|
413
|
+
const parsed = JSON.parse(raw);
|
|
414
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// fall through to the loose form
|
|
418
|
+
}
|
|
419
|
+
const m = /^\s*\{(.*)\}\s*$/s.exec(raw);
|
|
420
|
+
if (!m || /["'\[\]{}]/.test(m[1]))
|
|
421
|
+
return null;
|
|
422
|
+
const out = {};
|
|
423
|
+
for (const pair of m[1].split(",")) {
|
|
424
|
+
if (pair.trim() === "")
|
|
425
|
+
continue;
|
|
426
|
+
const colon = pair.indexOf(":");
|
|
427
|
+
if (colon <= 0)
|
|
428
|
+
return null;
|
|
429
|
+
const key = pair.slice(0, colon).trim();
|
|
430
|
+
const value = pair.slice(colon + 1).trim();
|
|
431
|
+
if (key === "" || value === "")
|
|
432
|
+
return null;
|
|
433
|
+
out[key] = value === "true" ? true : value === "false" ? false : value === "null" ? null : /^-?\d+(\.\d+)?$/.test(value) ? Number(value) : value;
|
|
434
|
+
}
|
|
435
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
436
|
+
}
|
|
348
437
|
function parseEnvPair(pair, flag, into) {
|
|
349
438
|
const eq = pair.indexOf("=");
|
|
350
439
|
const key = eq === -1 ? "" : pair.slice(0, eq).trim();
|
|
@@ -367,6 +456,8 @@ async function runProxyMode(argv) {
|
|
|
367
456
|
fail(parsed.error);
|
|
368
457
|
const one = (name) => parsed.values.get(name)?.[0];
|
|
369
458
|
const many = (name) => parsed.values.get(name) ?? [];
|
|
459
|
+
// Lizenz: ändert nie den Lauf, nur den Umfang der geschriebenen Ausgabe (Textbausteine, Schema-Befunde).
|
|
460
|
+
const gate = resolveProGate(one("--license"));
|
|
370
461
|
const upstreamCommand = one("--upstream-command");
|
|
371
462
|
if (!upstreamCommand) {
|
|
372
463
|
fail("proxy mode requires --upstream-command <cmd>\n" +
|
|
@@ -430,6 +521,34 @@ async function runProxyMode(argv) {
|
|
|
430
521
|
const writeToolPatterns = splitList(one("--write-tools"), "--write-tools");
|
|
431
522
|
const readToolPatterns = splitList(one("--read-tools"), "--read-tools");
|
|
432
523
|
const keyFields = splitList(one("--key-fields"), "--key-fields");
|
|
524
|
+
// --effect-probe: count the upstream's records instead of its executions.
|
|
525
|
+
const effectProbeTool = one("--effect-probe");
|
|
526
|
+
const effectProbeArgsRaw = one("--effect-probe-args");
|
|
527
|
+
const effectCountPath = one("--effect-count");
|
|
528
|
+
const effectToolPatterns = splitList(one("--effect-tools"), "--effect-tools") ?? [];
|
|
529
|
+
if (effectProbeTool === undefined && (effectProbeArgsRaw !== undefined || effectCountPath !== undefined || effectToolPatterns.length > 0)) {
|
|
530
|
+
fail("--effect-probe-args, --effect-count and --effect-tools need --effect-probe <tool>.");
|
|
531
|
+
}
|
|
532
|
+
let effectProbe;
|
|
533
|
+
if (effectProbeTool !== undefined) {
|
|
534
|
+
let probeArgs = {};
|
|
535
|
+
if (effectProbeArgsRaw !== undefined) {
|
|
536
|
+
const parsedArgs = parseProbeArgs(effectProbeArgsRaw);
|
|
537
|
+
if (parsedArgs === null) {
|
|
538
|
+
fail(`--effect-probe-args must be a JSON object, got "${effectProbeArgsRaw}".\n` +
|
|
539
|
+
" bash / PowerShell 7: --effect-probe-args '{\"status\":\"open\"}'\n" +
|
|
540
|
+
" cmd.exe: --effect-probe-args \"{\\\"status\\\":\\\"open\\\"}\"\n" +
|
|
541
|
+
" (without quotes, {status:open} is also understood: simple key:value pairs)");
|
|
542
|
+
}
|
|
543
|
+
probeArgs = parsedArgs;
|
|
544
|
+
}
|
|
545
|
+
effectProbe = {
|
|
546
|
+
tool: effectProbeTool,
|
|
547
|
+
args: probeArgs,
|
|
548
|
+
...(effectCountPath !== undefined ? { countPath: effectCountPath } : {}),
|
|
549
|
+
...(effectToolPatterns.length > 0 ? { tools: effectToolPatterns } : {}),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
433
552
|
const upstreamCwd = one("--upstream-cwd");
|
|
434
553
|
if (upstreamCwd !== undefined) {
|
|
435
554
|
const info = await stat(upstreamCwd).catch(() => null);
|
|
@@ -485,9 +604,20 @@ async function runProxyMode(argv) {
|
|
|
485
604
|
}
|
|
486
605
|
}
|
|
487
606
|
const reportTarget = reportFile;
|
|
607
|
+
if (!startupReportFile && reportTarget) {
|
|
608
|
+
startupReportFile = reportTarget;
|
|
609
|
+
tryArchivePreviousReport();
|
|
610
|
+
}
|
|
488
611
|
const log = (line) => {
|
|
489
612
|
process.stderr.write(`[cuonztech-agent-harness] ${line}\n`);
|
|
490
613
|
};
|
|
614
|
+
log(gate.pro
|
|
615
|
+
? "Lizenz: gültig (Pro-Umfang)."
|
|
616
|
+
: `Keine gültige Lizenz: ${reasonSentence(gate.verdict)} Der Lauf ist vollständig, Urteil, Score und Exit-Code sind echt. ` +
|
|
617
|
+
`Im Report fehlen die Pro-Teile (Textbausteine, Schema-Befunde). Lizenz: ${PURCHASE_URL}`);
|
|
618
|
+
if (archivedPreviousRun) {
|
|
619
|
+
log(`NOTE: An earlier report with ${archivedPreviousRun.callsCount} call(s) was archived to "${archivedPreviousRun.archivedPath}".`);
|
|
620
|
+
}
|
|
491
621
|
// A tool name comes from the upstream: a line break in it must not be
|
|
492
622
|
// able to fake further lines of this log.
|
|
493
623
|
const shown = (name) => name.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "?");
|
|
@@ -507,6 +637,13 @@ async function runProxyMode(argv) {
|
|
|
507
637
|
"Only do that against a disposable test/staging system, never production.\n" +
|
|
508
638
|
"If --upstream-command points at such a system, confirm it by adding --allow-writes.");
|
|
509
639
|
}
|
|
640
|
+
if (writesReachUpstream) {
|
|
641
|
+
// Wortlaut nach LICENSE Ziffer 4a: der Schalter --allow-writes ist die Bestätigung des Anwenders.
|
|
642
|
+
log("ACHTUNG: Der Harness führt die Schreibvorgänge Ihres Agenten wirklich am Upstream aus und löst " +
|
|
643
|
+
"absichtlich Fehler, Wiederholungen und Verzögerungen aus. Nur gegen ein Testsystem mit " +
|
|
644
|
+
"synthetischen Daten einsetzen, das keine Produktivumgebung ist und von der Aussenwelt " +
|
|
645
|
+
"getrennt ist (LICENSE Ziffer 4a). Mit --allow-writes bestätigen Sie das.");
|
|
646
|
+
}
|
|
510
647
|
if (scenarioId === "F1") {
|
|
511
648
|
log("WARNING: scenario F1 really executes the agent's first write upstream and then " +
|
|
512
649
|
"reports a timeout to the agent. Only point --upstream-command at a disposable " +
|
|
@@ -535,6 +672,7 @@ async function runProxyMode(argv) {
|
|
|
535
672
|
readToolPatterns,
|
|
536
673
|
keyFields,
|
|
537
674
|
retryAfterSec,
|
|
675
|
+
effectProbe,
|
|
538
676
|
});
|
|
539
677
|
// From here on a signal must end the upstream child as well, and the
|
|
540
678
|
// report file must not keep an older run's verdict while we are starting.
|
|
@@ -572,6 +710,15 @@ async function runProxyMode(argv) {
|
|
|
572
710
|
? `\nUpstream stderr:\n${tail}`
|
|
573
711
|
: "\nThe upstream wrote nothing to stderr. Check the command, its arguments and that it speaks MCP over stdio."));
|
|
574
712
|
}
|
|
713
|
+
if (effectProbe) {
|
|
714
|
+
try {
|
|
715
|
+
await interceptor.initEffectProbe();
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
await interceptor.disconnectUpstream().catch(() => undefined);
|
|
719
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
575
722
|
const upstreamTools = interceptor.getUpstreamTools();
|
|
576
723
|
// Static view of the tool schemas, once: it does not change during the run.
|
|
577
724
|
const schemaAudit = auditToolSchemas(upstreamTools, { writeToolPatterns, readToolPatterns });
|
|
@@ -589,6 +736,9 @@ async function runProxyMode(argv) {
|
|
|
589
736
|
for (const entry of classification) {
|
|
590
737
|
log(` ${entry.class.padEnd(12)} ${shown(entry.name)} (${entry.source})`);
|
|
591
738
|
}
|
|
739
|
+
for (const entry of classification.filter((c) => c.doubt)) {
|
|
740
|
+
log(`WARNING: tool "${shown(entry.name)}" is ${entry.doubt}.`);
|
|
741
|
+
}
|
|
592
742
|
for (const entry of classification.filter((c) => c.class === "unclassified")) {
|
|
593
743
|
log(`WARNING: tool "${shown(entry.name)}" could not be classified as read or write. Its calls ` +
|
|
594
744
|
"are passed through but never trigger a scenario and are not scored — if it writes, " +
|
|
@@ -601,6 +751,11 @@ async function runProxyMode(argv) {
|
|
|
601
751
|
"Scenarios will NEVER trigger for this upstream and every report will read NOT_TESTED. " +
|
|
602
752
|
'If your write tools use different naming, pass e.g. --write-tools "yourprefix_*".');
|
|
603
753
|
}
|
|
754
|
+
const probeState = interceptor.getSession().effectProbe;
|
|
755
|
+
if (probeState) {
|
|
756
|
+
log(`Effect probe: "${shown(probeState.tool)}" counted ${probeState.before} record(s) before the first call. ` +
|
|
757
|
+
"The report will count new RECORDS instead of executions (assumption: every write that took effect creates one record in what the probe counts, and nothing else changes it during the run).");
|
|
758
|
+
}
|
|
604
759
|
const keyCapable = interceptor.getKeyCapableTools();
|
|
605
760
|
log(keyCapable.length > 0
|
|
606
761
|
? `Idempotency-key argument recognized on: ${keyCapable.map(shown).join(", ")}`
|
|
@@ -652,8 +807,10 @@ async function runProxyMode(argv) {
|
|
|
652
807
|
try {
|
|
653
808
|
const report = generateReport(interceptor.getSession());
|
|
654
809
|
const score = computeScore([report]);
|
|
655
|
-
const patches = generatePatches([report], score);
|
|
810
|
+
const patches = gate.pro ? generatePatches([report], score) : []; // ohne Lizenz nicht erzeugt und nicht geschrieben
|
|
656
811
|
const callForensics = buildCallForensics(interceptor.getSession());
|
|
812
|
+
// Ohne Lizenz: Schema-Gutachten nur mit Zusammenfassung und Risikostufe, ohne Textbausteine.
|
|
813
|
+
const reportedSchemaAudit = gate.pro ? schemaAudit : reduceSchemaAudit(schemaAudit);
|
|
657
814
|
const body = JSON.stringify({
|
|
658
815
|
harnessVersion: VERSION,
|
|
659
816
|
// Constant: a self-test, never a certificate.
|
|
@@ -662,15 +819,35 @@ async function runProxyMode(argv) {
|
|
|
662
819
|
verdict: report.verdict,
|
|
663
820
|
report,
|
|
664
821
|
score,
|
|
665
|
-
|
|
822
|
+
...(gate.pro
|
|
823
|
+
? { patches }
|
|
824
|
+
: {
|
|
825
|
+
limitedByLicense: true,
|
|
826
|
+
licenseNotice: "Keine gültige Lizenz: Textbausteine (Härtungsvorschläge) und Schema-Befunde gehören zum Pro-Umfang und fehlen hier. " +
|
|
827
|
+
`Lauf, Urteil, Score und Exit-Code sind echt. Frei bleiben: ${FREE_SCOPE_DE}. Lizenz: ${PURCHASE_URL}`,
|
|
828
|
+
}),
|
|
829
|
+
...(archivedPreviousRun
|
|
830
|
+
? {
|
|
831
|
+
previousRun: {
|
|
832
|
+
file: archivedPreviousRun.archivedPath,
|
|
833
|
+
verdict: archivedPreviousRun.verdict,
|
|
834
|
+
callsCount: archivedPreviousRun.callsCount,
|
|
835
|
+
archivedAt: archivedPreviousRun.archivedAt,
|
|
836
|
+
},
|
|
837
|
+
}
|
|
838
|
+
: {}),
|
|
666
839
|
// Deutscher Prompt-Baustein und Schema-Gutachten (neu in 0.7.0, optional für Leser).
|
|
667
|
-
|
|
668
|
-
|
|
840
|
+
...(gate.pro
|
|
841
|
+
? {
|
|
842
|
+
recommended_prompt_patch_de: generatePromptPatchDe([report], classification.filter((c) => c.class === "write").map((c) => c.name), classification.filter((c) => c.class === "read").map((c) => c.name)),
|
|
843
|
+
}
|
|
844
|
+
: {}),
|
|
845
|
+
schema_audit: reportedSchemaAudit,
|
|
669
846
|
call_forensics: callForensics,
|
|
670
847
|
integrity: computeReportIntegrity({
|
|
671
848
|
harnessVersion: VERSION,
|
|
672
849
|
report,
|
|
673
|
-
schema_audit:
|
|
850
|
+
schema_audit: reportedSchemaAudit,
|
|
674
851
|
call_forensics: callForensics,
|
|
675
852
|
}),
|
|
676
853
|
toolClassification: classification,
|
|
@@ -732,6 +909,7 @@ async function runProxyMode(argv) {
|
|
|
732
909
|
};
|
|
733
910
|
if (reportFile) {
|
|
734
911
|
interceptor.onCallRecorded = () => writeReportFile();
|
|
912
|
+
interceptor.onProbeRefreshed = () => void writeReportFile();
|
|
735
913
|
await writeReportFile();
|
|
736
914
|
if (reportWriteFailed) {
|
|
737
915
|
await interceptor.disconnectUpstream();
|
|
@@ -752,6 +930,8 @@ async function runProxyMode(argv) {
|
|
|
752
930
|
if (shuttingDown)
|
|
753
931
|
return;
|
|
754
932
|
shuttingDown = true;
|
|
933
|
+
// The probe must be read while the upstream still runs.
|
|
934
|
+
await interceptor.refreshEffectProbe(true);
|
|
755
935
|
await writeReportFile();
|
|
756
936
|
await interceptor.disconnectUpstream();
|
|
757
937
|
// Calls that were still open have just been ended by the disconnect and
|
|
@@ -771,7 +951,7 @@ async function runProxyMode(argv) {
|
|
|
771
951
|
process.stdin.on("end", shutdown);
|
|
772
952
|
process.stdin.on("close", shutdown);
|
|
773
953
|
startupReportFile = null;
|
|
774
|
-
const server = createProxyServer(interceptor, { auditTools });
|
|
954
|
+
const server = createProxyServer(interceptor, { auditTools, patches: gate.pro });
|
|
775
955
|
// The SDK closes the transport on its own when a single message exceeds
|
|
776
956
|
// its buffer (about 10 MB). Without this the proxy stayed alive, deaf,
|
|
777
957
|
// with its upstream child still running.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { LICENSE_PUBLIC_KEY } from "./publicKey.js";
|
|
3
|
+
const PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
4
|
+
const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
5
|
+
function rawKey(b64, what) {
|
|
6
|
+
const raw = Buffer.from(b64, "base64");
|
|
7
|
+
if (raw.length !== 32)
|
|
8
|
+
throw new Error(`${what}: erwartet 32 Byte (base64), erhalten ${raw.length}`);
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
export function generateLicenseKeyPair() {
|
|
12
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
13
|
+
return {
|
|
14
|
+
seed: privateKey.export({ format: "der", type: "pkcs8" }).subarray(-32).toString("base64"),
|
|
15
|
+
publicKey: publicKey.export({ format: "der", type: "spki" }).subarray(-32).toString("base64"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function signEd25519(domain, payload, seedB64) {
|
|
19
|
+
const key = crypto.createPrivateKey({
|
|
20
|
+
key: Buffer.concat([PKCS8_PREFIX, rawKey(seedB64, "LICENSE_SIGNING_SEED")]),
|
|
21
|
+
format: "der",
|
|
22
|
+
type: "pkcs8",
|
|
23
|
+
});
|
|
24
|
+
return crypto.sign(null, Buffer.from(`${domain}.${payload}`), key).toString("base64url");
|
|
25
|
+
}
|
|
26
|
+
function isCiEnvironment(env) {
|
|
27
|
+
const on = (v) => v !== undefined && v !== "" && !/^(0|false|no)$/i.test(v);
|
|
28
|
+
return on(env.CI) || on(env.GITHUB_ACTIONS);
|
|
29
|
+
}
|
|
30
|
+
let warnedKeyOverride = false;
|
|
31
|
+
export function defaultPublicKey(env = process.env) {
|
|
32
|
+
const override = env.AGENT_AUDIT_LICENSE_PUBLIC_KEY;
|
|
33
|
+
if (!override)
|
|
34
|
+
return LICENSE_PUBLIC_KEY;
|
|
35
|
+
if (isCiEnvironment(env) && env.AGENT_AUDIT_ALLOW_TEST_KEY !== "1") {
|
|
36
|
+
if (!warnedKeyOverride) {
|
|
37
|
+
warnedKeyOverride = true;
|
|
38
|
+
console.warn("⚠ Lizenz-Schlüssel-Override wird in CI ignoriert (eingebetteter Schlüssel gilt).");
|
|
39
|
+
}
|
|
40
|
+
return LICENSE_PUBLIC_KEY;
|
|
41
|
+
}
|
|
42
|
+
return override;
|
|
43
|
+
}
|
|
44
|
+
export function verifyEd25519(domain, payload, sigB64url, publicKeyB64 = defaultPublicKey()) {
|
|
45
|
+
try {
|
|
46
|
+
const key = crypto.createPublicKey({
|
|
47
|
+
key: Buffer.concat([SPKI_PREFIX, rawKey(publicKeyB64, "Public Key")]),
|
|
48
|
+
format: "der",
|
|
49
|
+
type: "spki",
|
|
50
|
+
});
|
|
51
|
+
const sig = Buffer.from(sigB64url, "base64url");
|
|
52
|
+
return crypto.verify(null, Buffer.from(`${domain}.${payload}`), key, sig);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Lizenz-Sperre nach dem Muster von CPCG (Privacy Chaos Gate): Der Lauf selbst
|
|
2
|
+
// (Proxy, Szenarien F1 bis F5, Score, PASS/FAIL, Exit-Code) ist nie davon
|
|
3
|
+
// abhängig. Ohne gültige Lizenz wird nur die AUSGABE gekürzt oder ein Pro-Befehl
|
|
4
|
+
// mit einem klaren Hinweis nicht ausgeführt. Die Prüfung der Lizenz selbst
|
|
5
|
+
// (Ed25519, offline) liegt in verifier.ts.
|
|
6
|
+
import { resolveLicense } from "./verifier.js";
|
|
7
|
+
export const PURCHASE_URL = "https://cuonztech.ch/agent-harness.html#audit";
|
|
8
|
+
// Alte Adresse aus früheren Texten des Verifizierers; die Website leitet weiter.
|
|
9
|
+
const LEGACY_URL = "https://cuonztech.ch/agent-audit";
|
|
10
|
+
// Zuordnung frei/Pro. Dieselbe Tabelle steht in der README (Abschnitt «Lizenz und Umfang»).
|
|
11
|
+
export const PRO_FEATURES = {
|
|
12
|
+
dossier: {
|
|
13
|
+
name: "Das Prüf-Dossier (report --dossier)",
|
|
14
|
+
command: "report --dossier",
|
|
15
|
+
mode: "gekürzt",
|
|
16
|
+
free: "Das Dossier enthält nur Kopf, Score und das Urteil je Szenario",
|
|
17
|
+
},
|
|
18
|
+
ui: {
|
|
19
|
+
name: "Die geführte Web-Oberfläche (ui)",
|
|
20
|
+
command: "ui",
|
|
21
|
+
mode: "gesperrt",
|
|
22
|
+
free: "Der Weg über die Kommandozeile (proxy, doctor, report)",
|
|
23
|
+
},
|
|
24
|
+
campaign: {
|
|
25
|
+
name: "Der Kampagnen-Runner (campaign)",
|
|
26
|
+
command: "campaign",
|
|
27
|
+
mode: "gesperrt",
|
|
28
|
+
free: "Der Selbsttest (benchmark) und jeder einzelne Proxy-Lauf",
|
|
29
|
+
},
|
|
30
|
+
"schema-audit": {
|
|
31
|
+
name: "Das Schema-Gutachten (Befunde je Tool und Schema-Vorschläge)",
|
|
32
|
+
command: "proxy --report-file, report",
|
|
33
|
+
mode: "gekürzt",
|
|
34
|
+
free: "Die Zusammenfassung und die Risikostufe je Tool (auch in doctor)",
|
|
35
|
+
},
|
|
36
|
+
hardening: {
|
|
37
|
+
name: "Der Textbaustein- und Prompt-Patch-Generator (Härtungsvorschläge)",
|
|
38
|
+
command: "proxy --report-file, report, cuonztech__get_score",
|
|
39
|
+
mode: "gekürzt",
|
|
40
|
+
free: "Der Score, das Urteil und die Beobachtungen",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export const FREE_SCOPE_DE = "MCP-Chaos-Proxy mit den Szenarien F1 bis F5, doctor, Kompaktbericht, JSON-Report (--report-file), Resilience Score und PASS/FAIL samt Exit-Code";
|
|
44
|
+
/** Löst die Lizenz auf (--license, sonst Umgebungsvariablen). Ohne gültige Lizenz ist `pro` false. */
|
|
45
|
+
export function resolveProGate(licenseKey) {
|
|
46
|
+
const verdict = resolveLicense(licenseKey);
|
|
47
|
+
return { pro: verdict.valid === true, verdict };
|
|
48
|
+
}
|
|
49
|
+
/** Der Grund in einem Satz, ohne Kauf-Link (der steht immer am Ende des Hinweises). */
|
|
50
|
+
export function reasonSentence(verdict) {
|
|
51
|
+
if (verdict.valid)
|
|
52
|
+
return "Die Lizenz ist gültig.";
|
|
53
|
+
const raw = (verdict.reason ?? "").trim();
|
|
54
|
+
const none = raw === "" || raw === "Nicht lizenziert" || raw.startsWith("Kein Lizenzschlüssel");
|
|
55
|
+
if (none)
|
|
56
|
+
return "Es ist keine Lizenz hinterlegt.";
|
|
57
|
+
const core = raw
|
|
58
|
+
.replace(LEGACY_URL, "")
|
|
59
|
+
.replace(/\s*(Lizenzen erhältlich unter|Verlängerung unter)\s*$/i, "")
|
|
60
|
+
.trim()
|
|
61
|
+
.replace(/\.$/, "");
|
|
62
|
+
return `Ihre Lizenz ist nicht gültig: ${core}.`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Hinweis für einen Befehl, der ohne Lizenz nicht läuft (`ui`, `campaign`).
|
|
66
|
+
* Form: «Keine gültige Lizenz: <Funktion> gehört zum Pro-Umfang. Frei bleiben …. Lizenz: <URL>»
|
|
67
|
+
*/
|
|
68
|
+
export function lockedNotice(feature, verdict) {
|
|
69
|
+
const f = PRO_FEATURES[feature];
|
|
70
|
+
return (`Keine gültige Lizenz: ${f.name} gehört zum Pro-Umfang. ${reasonSentence(verdict)}\n` +
|
|
71
|
+
`Frei bleiben: ${FREE_SCOPE_DE}.\n` +
|
|
72
|
+
`Der Befehl wurde nicht ausgeführt. Lizenz: ${PURCHASE_URL}\n` +
|
|
73
|
+
`Aktivierung: --license <KEY> oder Umgebungsvariable AGENT_AUDIT_LICENSE.\n`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Hinweis für eine gekürzte Ausgabe (Dossier, Schema-Gutachten, Härtungsvorschläge).
|
|
77
|
+
* Der Lauf selbst, das Urteil und der Exit-Code sind davon unberührt.
|
|
78
|
+
*/
|
|
79
|
+
export function shortenedNotice(feature, verdict) {
|
|
80
|
+
const f = PRO_FEATURES[feature];
|
|
81
|
+
return (`Keine gültige Lizenz: ${f.name} gehört zum Pro-Umfang. ${reasonSentence(verdict)} ` +
|
|
82
|
+
`Die Ausgabe ist gekürzt; der Lauf, das Urteil und der Exit-Code sind echt. ` +
|
|
83
|
+
`Frei bleiben: ${FREE_SCOPE_DE}. Lizenz: ${PURCHASE_URL}`);
|
|
84
|
+
}
|
|
85
|
+
/** Schlankere Fassung des Schema-Gutachtens ohne Lizenz: Zusammenfassung und Risikostufe, keine Befunde, kein Schema-Vorschlag. */
|
|
86
|
+
export function reduceSchemaAudit(audit) {
|
|
87
|
+
return {
|
|
88
|
+
...audit,
|
|
89
|
+
tools: audit.tools.map((t) => ({ ...t, findingsDe: [], recommendedSchemaPatch: null })),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|