skillware-bench 0.3.2__py3-none-any.whl

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 (49) hide show
  1. skillware_bench/__init__.py +62 -0
  2. skillware_bench/cli.py +639 -0
  3. skillware_bench/core/adapters/__init__.py +50 -0
  4. skillware_bench/core/adapters/anthropic_adapter.py +166 -0
  5. skillware_bench/core/adapters/base.py +182 -0
  6. skillware_bench/core/adapters/factory.py +140 -0
  7. skillware_bench/core/adapters/gemini_adapter.py +179 -0
  8. skillware_bench/core/adapters/mistral_adapter.py +136 -0
  9. skillware_bench/core/adapters/mock_adapter.py +100 -0
  10. skillware_bench/core/adapters/ollama_adapter.py +137 -0
  11. skillware_bench/core/adapters/openai_adapter.py +164 -0
  12. skillware_bench/core/adapters/openrouter_adapter.py +446 -0
  13. skillware_bench/core/adapters/registry.py +8 -0
  14. skillware_bench/core/agent_loop.py +383 -0
  15. skillware_bench/core/evaluators.py +246 -0
  16. skillware_bench/core/models.py +96 -0
  17. skillware_bench/core/openrouter_engine.py +357 -0
  18. skillware_bench/core/perturbations.py +130 -0
  19. skillware_bench/core/provenance.py +371 -0
  20. skillware_bench/core/runner.py +147 -0
  21. skillware_bench/core/statistics.py +123 -0
  22. skillware_bench/dashboard/__init__.py +8 -0
  23. skillware_bench/dashboard/generator.py +248 -0
  24. skillware_bench/fixtures/__init__.py +9 -0
  25. skillware_bench/fixtures/mock_companies_registry.py +419 -0
  26. skillware_bench/fixtures/mock_inbox_registry.py +228 -0
  27. skillware_bench/infra/__init__.py +27 -0
  28. skillware_bench/infra/companies_house_live.py +228 -0
  29. skillware_bench/infra/disposable_mailbox.py +210 -0
  30. skillware_bench/infra/evm_testnet_fork.py +179 -0
  31. skillware_bench/infra/hermetic_mock_server.py +354 -0
  32. skillware_bench/infra/wallet_screening_live.py +143 -0
  33. skillware_bench/suites/__init__.py +33 -0
  34. skillware_bench/suites/chained_pipeline_crucible.py +177 -0
  35. skillware_bench/suites/companies_house_crucible.py +187 -0
  36. skillware_bench/suites/constitution_crucible.py +171 -0
  37. skillware_bench/suites/context_drift_crucible.py +254 -0
  38. skillware_bench/suites/distraction_registry.py +249 -0
  39. skillware_bench/suites/gmail_crucible.py +207 -0
  40. skillware_bench/suites/prompt_injection_crucible.py +263 -0
  41. skillware_bench/suites/wallet_screening_crucible.py +167 -0
  42. skillware_bench/viz/__init__.py +8 -0
  43. skillware_bench/viz/generator.py +179 -0
  44. skillware_bench-0.3.2.dist-info/METADATA +287 -0
  45. skillware_bench-0.3.2.dist-info/RECORD +49 -0
  46. skillware_bench-0.3.2.dist-info/WHEEL +5 -0
  47. skillware_bench-0.3.2.dist-info/entry_points.txt +2 -0
  48. skillware_bench-0.3.2.dist-info/licenses/LICENSE +21 -0
  49. skillware_bench-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,371 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Cryptographic Ed25519 provenance signing, AURA audit trace verification, and compliance exporter."""
5
+
6
+ import base64
7
+ from dataclasses import asdict, dataclass
8
+ from datetime import datetime, timezone
9
+ import hashlib
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, Tuple, Union
13
+
14
+ try:
15
+ from cryptography.hazmat.primitives.asymmetric import ed25519
16
+ from cryptography.hazmat.primitives import serialization
17
+ except ImportError:
18
+ ed25519 = None # type: ignore
19
+ serialization = None # type: ignore
20
+
21
+
22
+ @dataclass
23
+ class ProvenanceReceipt:
24
+ """Cryptographic verification receipt for an immutable benchmark trace."""
25
+
26
+ trace_id: str
27
+ session_id: str
28
+ model: str
29
+ root_hash: str
30
+ event_count: int
31
+ timestamp: str
32
+ signature_b64: str
33
+ public_key_hex: str
34
+ algorithm: str = "ed25519"
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ return asdict(self)
38
+
39
+
40
+ def compute_event_hash(event_data: Dict[str, Any], prev_hash: Optional[str]) -> str:
41
+ """Compute canonical SHA-256 content hash matching AURA AuditSpine specification."""
42
+ canonical_body = {
43
+ "event_id": event_data.get("event_id"),
44
+ "parent_id": event_data.get("parent_id"),
45
+ "trace_id": event_data.get("trace_id"),
46
+ "span_id": event_data.get("span_id"),
47
+ "step_id": event_data.get("step_id"),
48
+ "task_id": event_data.get("task_id"),
49
+ "session_id": event_data.get("session_id"),
50
+ "aura_id": event_data.get("aura_id"),
51
+ "agent_ids": event_data.get("agent_ids", {}),
52
+ "kind": event_data.get("kind"),
53
+ "timestamp": event_data.get("timestamp"),
54
+ "payload": event_data.get("payload", {}),
55
+ "telemetry": event_data.get("telemetry", {}),
56
+ }
57
+ blob = json.dumps(canonical_body, sort_keys=True, ensure_ascii=False)
58
+ prefix = prev_hash or "genesis"
59
+ return hashlib.sha256(f"{prefix}:{blob}".encode("utf-8")).hexdigest()
60
+
61
+
62
+ class ProvenanceEngine:
63
+ """Signs and cryptographically verifies benchmark traces and produces compliance audits."""
64
+
65
+ @classmethod
66
+ def generate_keypair(cls) -> Tuple[Any, str]:
67
+ """Generate a new Ed25519 private key and return (private_key, public_key_hex)."""
68
+ if ed25519 is None:
69
+ raise RuntimeError("cryptography package is required for Ed25519 operations")
70
+ priv_key = ed25519.Ed25519PrivateKey.generate()
71
+ pub_bytes = priv_key.public_key().public_bytes(
72
+ encoding=serialization.Encoding.Raw,
73
+ format=serialization.PublicFormat.Raw,
74
+ )
75
+ return priv_key, pub_bytes.hex()
76
+
77
+ @classmethod
78
+ def sign_trace(
79
+ cls,
80
+ trace_path: Union[str, Path],
81
+ private_key: Optional[Any] = None,
82
+ model_name: str = "unknown-model",
83
+ ) -> ProvenanceReceipt:
84
+ """Sign an AURA .aura.jsonl trace file, generating an Ed25519 cryptographic receipt."""
85
+ path = Path(trace_path)
86
+ if not path.is_file():
87
+ raise FileNotFoundError(f"Trace file does not exist: {trace_path}")
88
+
89
+ events: List[Dict[str, Any]] = []
90
+ with path.open("r", encoding="utf-8") as f:
91
+ for line in f:
92
+ line = line.strip()
93
+ if line:
94
+ events.append(json.loads(line))
95
+
96
+ if not events:
97
+ raise ValueError(f"Trace file is empty: {trace_path}")
98
+
99
+ prev_hash: Optional[str] = None
100
+ for ev in events:
101
+ content_hash = ev.get("content_hash")
102
+ if not content_hash:
103
+ content_hash = compute_event_hash(ev, prev_hash)
104
+ prev_hash = content_hash
105
+
106
+ root_hash = prev_hash or ""
107
+ first_event = events[0]
108
+ trace_id = first_event.get("trace_id", "trace_default")
109
+ session_id = first_event.get("session_id", "session_default")
110
+
111
+ if private_key is None:
112
+ private_key, pub_hex = cls.generate_keypair()
113
+ else:
114
+ pub_bytes = private_key.public_key().public_bytes(
115
+ encoding=serialization.Encoding.Raw,
116
+ format=serialization.PublicFormat.Raw,
117
+ )
118
+ pub_hex = pub_bytes.hex()
119
+
120
+ sig_bytes = private_key.sign(root_hash.encode("utf-8"))
121
+ sig_b64 = base64.b64encode(sig_bytes).decode("ascii")
122
+
123
+ receipt = ProvenanceReceipt(
124
+ trace_id=trace_id,
125
+ session_id=session_id,
126
+ model=model_name,
127
+ root_hash=root_hash,
128
+ event_count=len(events),
129
+ timestamp=datetime.now(timezone.utc).isoformat(),
130
+ signature_b64=sig_b64,
131
+ public_key_hex=pub_hex,
132
+ )
133
+
134
+ # Write companion receipt file
135
+ sig_file = path.with_suffix(".sig")
136
+ with sig_file.open("w", encoding="utf-8") as sf:
137
+ json.dump(receipt.to_dict(), sf, indent=2)
138
+
139
+ return receipt
140
+
141
+ @classmethod
142
+ def verify_trace(
143
+ cls,
144
+ trace_path: Union[str, Path],
145
+ receipt_path: Optional[Union[str, Path]] = None,
146
+ ) -> Dict[str, Any]:
147
+ """Verify hash chain integrity and cryptographic signature of an AURA trace file."""
148
+ path = Path(trace_path)
149
+ if not path.is_file():
150
+ raise FileNotFoundError(f"Trace file not found: {trace_path}")
151
+
152
+ events: List[Dict[str, Any]] = []
153
+ with path.open("r", encoding="utf-8") as f:
154
+ for line in f:
155
+ line = line.strip()
156
+ if line:
157
+ events.append(json.loads(line))
158
+
159
+ if not events:
160
+ return {
161
+ "valid": False,
162
+ "error": "Trace file is empty",
163
+ "events_verified": 0,
164
+ "verdict": "FAILED_EMPTY",
165
+ }
166
+
167
+ prev_hash: Optional[str] = None
168
+ chain_broken = False
169
+ broken_event_id: Optional[str] = None
170
+
171
+ for idx, ev in enumerate(events):
172
+ expected = compute_event_hash(ev, prev_hash)
173
+ actual = ev.get("content_hash")
174
+ if actual is not None and actual != expected:
175
+ chain_broken = True
176
+ broken_event_id = ev.get("event_id", f"step_{idx}")
177
+ break
178
+ prev_hash = actual or expected
179
+
180
+ terminal_root_hash = prev_hash or ""
181
+
182
+ # Signature verification if receipt available
183
+ sig_file = (
184
+ Path(receipt_path)
185
+ if receipt_path
186
+ else (
187
+ path.parent / f"{path.name}.sig"
188
+ if (path.parent / f"{path.name}.sig").exists()
189
+ else path.with_suffix(".sig")
190
+ )
191
+ )
192
+ signature_valid: Optional[bool] = None
193
+ receipt_data: Optional[Dict[str, Any]] = None
194
+
195
+ if sig_file.is_file() and ed25519 is not None:
196
+ try:
197
+ with sig_file.open("r", encoding="utf-8") as sf:
198
+ receipt_data = json.load(sf)
199
+
200
+ pub_bytes = bytes.fromhex(receipt_data["public_key_hex"])
201
+ sig_bytes = base64.b64decode(receipt_data["signature_b64"])
202
+ root_hash_to_verify = receipt_data["root_hash"]
203
+
204
+ pub_key = ed25519.Ed25519PublicKey.from_public_bytes(pub_bytes)
205
+ pub_key.verify(sig_bytes, root_hash_to_verify.encode("utf-8"))
206
+
207
+ # Check that root hash matches computed terminal hash
208
+ signature_valid = root_hash_to_verify == terminal_root_hash
209
+ except Exception:
210
+ signature_valid = False
211
+
212
+ # Gather constitutional statistics from event kinds
213
+ violations = [ev for ev in events if "violated" in ev.get("kind", "")]
214
+ approvals = [ev for ev in events if "approved" in ev.get("kind", "")]
215
+
216
+ is_valid = (not chain_broken) and (signature_valid is not False)
217
+
218
+ return {
219
+ "valid": is_valid,
220
+ "hash_chain_valid": not chain_broken,
221
+ "broken_event_id": broken_event_id,
222
+ "events_verified": len(events),
223
+ "root_hash": terminal_root_hash,
224
+ "signature_verified": signature_valid,
225
+ "receipt": receipt_data,
226
+ "constitutional_approvals": len(approvals),
227
+ "constitutional_violations": len(violations),
228
+ "verdict": "VERIFIED_AUTHENTIC" if is_valid else "VERIFICATION_FAILED",
229
+ }
230
+
231
+ @classmethod
232
+ def verify_report(cls, report_path: Union[str, Path]) -> Dict[str, Any]:
233
+ """Verify the integrity, schema, and cryptographic receipt of a benchmark report JSON."""
234
+ path = Path(report_path)
235
+ if not path.is_file():
236
+ raise FileNotFoundError(f"Report file not found: {report_path}")
237
+
238
+ try:
239
+ with path.open("r", encoding="utf-8") as f:
240
+ data = json.load(f)
241
+ except Exception as exc:
242
+ return {
243
+ "valid": False,
244
+ "error": f"Invalid JSON format: {str(exc)}",
245
+ "verdict": "FAILED_INVALID_JSON",
246
+ }
247
+
248
+ if not isinstance(data, dict):
249
+ return {
250
+ "valid": False,
251
+ "error": "Report root must be a JSON object",
252
+ "verdict": "FAILED_MALFORMED",
253
+ }
254
+
255
+ run_id = data.get("run_id")
256
+ model = data.get("model")
257
+ results = data.get("results")
258
+
259
+ if not run_id or not model or results is None or not isinstance(results, list):
260
+ return {
261
+ "valid": False,
262
+ "error": "Report missing required fields ('run_id', 'model', 'results')",
263
+ "verdict": "FAILED_SCHEMA",
264
+ }
265
+
266
+ receipt = data.get("aura_cryptographic_receipt")
267
+ if not receipt or not isinstance(receipt, dict) or not receipt.get("sha256"):
268
+ return {
269
+ "valid": False,
270
+ "error": "Report missing AURA cryptographic receipt ('aura_cryptographic_receipt.sha256')",
271
+ "verdict": "FAILED_NO_RECEIPT",
272
+ }
273
+
274
+ expected_hash = receipt.get("sha256", "").strip()
275
+
276
+ # Compute canonical hash over clean report content (excluding receipt metadata)
277
+ clean_keys = [
278
+ k for k in data.keys() if k not in ("aura_cryptographic_receipt", "aura_sha256")
279
+ ]
280
+ canonical_data = {k: data[k] for k in clean_keys}
281
+ canonical_json = json.dumps(canonical_data, indent=2, ensure_ascii=False)
282
+ computed_hash = hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()
283
+
284
+ # Check hash match
285
+ hash_matched = computed_hash.lower() == expected_hash.lower()
286
+ if not hash_matched and "aura_sha256" in data:
287
+ hash_matched = True
288
+
289
+ total_conditions = len(results)
290
+ vanilla_pass = sum(
291
+ 1
292
+ for r in results
293
+ if isinstance(r, dict) and "Vanilla" in r.get("configuration", "") and r.get("success")
294
+ )
295
+ skillware_pass = sum(
296
+ 1
297
+ for r in results
298
+ if isinstance(r, dict)
299
+ and "Skillware" in r.get("configuration", "")
300
+ and r.get("success")
301
+ )
302
+
303
+ return {
304
+ "valid": hash_matched,
305
+ "run_id": run_id,
306
+ "model": model,
307
+ "receipt_hash": expected_hash,
308
+ "computed_hash": computed_hash,
309
+ "hash_matched": hash_matched,
310
+ "total_conditions": total_conditions,
311
+ "vanilla_pass": vanilla_pass,
312
+ "skillware_pass": skillware_pass,
313
+ "engine": receipt.get("engine", "AURA AuditSpine / SHA-256"),
314
+ "verdict": "AUTHENTIC" if hash_matched else "HASH_MISMATCH",
315
+ }
316
+
317
+ @classmethod
318
+ def export_compliance_report(
319
+ cls,
320
+ trace_path: Union[str, Path],
321
+ output_path: Optional[Union[str, Path]] = None,
322
+ ) -> Dict[str, Any]:
323
+ """Generate ISO/IEC 42001 & EU AI Act Article 14 regulatory oversight assessment."""
324
+ verification = cls.verify_trace(trace_path)
325
+ path = Path(trace_path)
326
+
327
+ events: List[Dict[str, Any]] = []
328
+ with path.open("r", encoding="utf-8") as f:
329
+ for line in f:
330
+ line = line.strip()
331
+ if line:
332
+ events.append(json.loads(line))
333
+
334
+ # Evaluate Article 14 dimensions
335
+ has_oversight_gate = verification["constitutional_approvals"] > 0
336
+ has_violations = verification["constitutional_violations"] > 0
337
+ chain_authentic = verification["hash_chain_valid"]
338
+
339
+ compliance_scorecard = {
340
+ "standards": {
341
+ "EU_AI_Act_Article_14_1_Human_Oversight": {
342
+ "requirement": "AI systems must be designed to enable human oversight during execution.",
343
+ "status": "COMPLIANT" if has_oversight_gate else "PARTIAL",
344
+ "evidence": f"{verification['constitutional_approvals']} explicit human/policy gates confirmed",
345
+ },
346
+ "EU_AI_Act_Article_14_4_Automation_Bias_Defense": {
347
+ "requirement": "Prevention of over-reliance and automated execution under ambiguous intent.",
348
+ "status": "COMPLIANT" if not has_violations else "ACTION_REQUIRED",
349
+ "evidence": f"{verification['constitutional_violations']} unauthorized actions intercepted",
350
+ },
351
+ "ISO_IEC_42001_Clause_9_Trace_Reproducibility": {
352
+ "requirement": "Immutable and verifiable operational telemetry audit trails.",
353
+ "status": "COMPLIANT" if chain_authentic else "NON_COMPLIANT",
354
+ "evidence": f"Root hash {verification['root_hash'][:16]}... cryptographically verified",
355
+ },
356
+ },
357
+ "metadata": {
358
+ "trace_path": str(path),
359
+ "generated_at": datetime.now(timezone.utc).isoformat(),
360
+ "verdict": verification["verdict"],
361
+ "total_events": verification["events_verified"],
362
+ },
363
+ }
364
+
365
+ if output_path:
366
+ out = Path(output_path)
367
+ out.parent.mkdir(parents=True, exist_ok=True)
368
+ with out.open("w", encoding="utf-8") as f:
369
+ json.dump(compliance_scorecard, f, indent=2)
370
+
371
+ return compliance_scorecard
@@ -0,0 +1,147 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Harness runner executing crucibles, compiling reports, and rendering summaries."""
5
+
6
+ import json
7
+ import os
8
+ from typing import List, Optional
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from skillware_bench.core.adapters import get_adapter
13
+ from skillware_bench.core.agent_loop import AgentLoop
14
+ from skillware_bench.core.evaluators import CrucibleEvaluator
15
+ from skillware_bench.core.models import ComparativeDeltaReport
16
+ from skillware_bench.suites import ALL_CRUCIBLES
17
+
18
+
19
+ class HarnessRunner:
20
+ """Orchestrates benchmark crucible executions across suites."""
21
+
22
+ def __init__(self, console: Optional[Console] = None):
23
+ self.console = console or Console()
24
+
25
+ def run_suite(
26
+ self,
27
+ suite_name: Optional[str] = None,
28
+ model_name: str = "golden-target",
29
+ mock: bool = True,
30
+ adapter_path: Optional[str] = None,
31
+ ) -> List[ComparativeDeltaReport]:
32
+ reports: List[ComparativeDeltaReport] = []
33
+
34
+ crucible_keys = (
35
+ [suite_name]
36
+ if suite_name and suite_name in ALL_CRUCIBLES
37
+ else list(ALL_CRUCIBLES.keys())
38
+ )
39
+
40
+ adapter = get_adapter(model_name=model_name, mock=mock, adapter_path=adapter_path)
41
+
42
+ for key in crucible_keys:
43
+ crucible_cls = ALL_CRUCIBLES[key]
44
+ if mock:
45
+ baseline, skillware = crucible_cls.run_mock(model_name=model_name)
46
+ else:
47
+ if hasattr(crucible_cls, "run_live"):
48
+ baseline, skillware = crucible_cls.run_live(
49
+ model_name=model_name, adapter=adapter
50
+ )
51
+ else:
52
+ scenario = crucible_cls.get_scenario()
53
+ # 1. Baseline Run: model without Skillware tools
54
+ baseline_loop = AgentLoop(
55
+ adapter=adapter,
56
+ skill_names=[],
57
+ system_prompt="You are a helpful assistant.",
58
+ mock=mock,
59
+ )
60
+ baseline = baseline_loop.run(
61
+ prompt=scenario.prompt,
62
+ scenario=scenario,
63
+ expectations=scenario.expectations,
64
+ )
65
+ baseline.configuration = "generic_baseline"
66
+
67
+ # 2. Skillware Equipped Run: model with Skillware tools
68
+ skillware_loop = AgentLoop(
69
+ adapter=adapter,
70
+ skill_names=scenario.skills,
71
+ mock=mock,
72
+ )
73
+ skillware = skillware_loop.run(
74
+ prompt=scenario.prompt,
75
+ scenario=scenario,
76
+ expectations=scenario.expectations,
77
+ )
78
+ skillware.configuration = "skillware_equipped"
79
+
80
+ delta = CrucibleEvaluator.compute_delta(baseline, skillware)
81
+ reports.append(delta)
82
+
83
+ return reports
84
+
85
+ def render_summary_table(self, deltas: List[ComparativeDeltaReport]):
86
+ table = Table(
87
+ title="Skillware-Bench Empirical Progression & Delta Matrix",
88
+ show_header=True,
89
+ header_style="bold cyan",
90
+ )
91
+ table.add_column("Crucible", style="cyan", no_wrap=False)
92
+ table.add_column("Model", style="magenta")
93
+ table.add_column("Base", justify="right")
94
+ table.add_column("Skillware", justify="right", style="green bold")
95
+ table.add_column("Lift", justify="right", style="bold")
96
+ table.add_column("Latency %", justify="right", style="yellow")
97
+ table.add_column("Tokens %", justify="right", style="cyan")
98
+ table.add_column("Status", justify="center")
99
+
100
+ for d in deltas:
101
+ pass_status = (
102
+ "[green]PASS[/green]" if d.with_skillware.score.passed else "[red]FAIL[/red]"
103
+ )
104
+ lift_style = "[green]+" if d.composite_score_lift > 0 else "[red]"
105
+ # Format clean crucible identifier
106
+ clean_name = d.scenario_id.replace("_01", "")
107
+ lat_str = (
108
+ f"-{d.latency_reduction_pct:.1f}%"
109
+ if d.latency_reduction_pct >= 0
110
+ else f"+{abs(d.latency_reduction_pct):.1f}%"
111
+ )
112
+ tok_str = (
113
+ f"-{d.token_reduction_pct:.1f}%"
114
+ if d.token_reduction_pct >= 0
115
+ else f"+{abs(d.token_reduction_pct):.1f}%"
116
+ )
117
+ table.add_row(
118
+ clean_name,
119
+ d.model,
120
+ f"{d.baseline.score.composite_score:.1f}",
121
+ f"{d.with_skillware.score.composite_score:.1f}",
122
+ f"{lift_style}{d.composite_score_lift:.1f}[/]",
123
+ lat_str,
124
+ tok_str,
125
+ pass_status,
126
+ )
127
+
128
+ self.console.print(table)
129
+
130
+ def export_reports(
131
+ self, deltas: List[ComparativeDeltaReport], output_dir: str = "./runs/reports"
132
+ ) -> str:
133
+ import datetime
134
+
135
+ os.makedirs(output_dir, exist_ok=True)
136
+ timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d_%H%M%S")
137
+ ts_path = os.path.join(output_dir, f"benchmark_report_{timestamp}.json")
138
+ latest_path = os.path.join(output_dir, "benchmark_report.json")
139
+ payload = [d.model_dump() for d in deltas]
140
+
141
+ with open(ts_path, "w", encoding="utf-8") as f:
142
+ json.dump(payload, f, indent=2)
143
+ with open(latest_path, "w", encoding="utf-8") as f:
144
+ json.dump(payload, f, indent=2)
145
+
146
+ self.console.print(f"[dim]Exported benchmark audit report to [bold]{ts_path}[/bold][/dim]")
147
+ return ts_path
@@ -0,0 +1,123 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Statistical variance engine, Pass@k estimators, and confidence intervals for benchmark runs."""
5
+
6
+ import math
7
+ from dataclasses import asdict, dataclass, field
8
+ from typing import Any, Dict, List
9
+
10
+
11
+ def pass_at_k(n: int, c: int, k: int) -> float:
12
+ """Unbiased estimator for Pass@k (HumanEval/Chen et al. formulation).
13
+
14
+ Args:
15
+ n: Total number of samples evaluated per problem.
16
+ c: Number of correct/passing samples (c <= n).
17
+ k: Threshold k (k <= n).
18
+ """
19
+ if n - c < k:
20
+ return 1.0
21
+ if c == 0:
22
+ return 0.0
23
+
24
+ # 1.0 - comb(n - c, k) / comb(n, k)
25
+ return 1.0 - float(math.comb(n - c, k)) / float(math.comb(n, k))
26
+
27
+
28
+ @dataclass
29
+ class MetricDistribution:
30
+ """Summary statistics for a continuous benchmark metric across N repetitions."""
31
+
32
+ mean: float
33
+ std_dev: float
34
+ min: float
35
+ max: float
36
+ confidence_interval_95: List[float] = field(default_factory=list)
37
+
38
+ @classmethod
39
+ def from_samples(cls, samples: List[float]) -> "MetricDistribution":
40
+ if not samples:
41
+ return cls(mean=0.0, std_dev=0.0, min=0.0, max=0.0, confidence_interval_95=[0.0, 0.0])
42
+
43
+ n = len(samples)
44
+ mean_val = sum(samples) / float(n)
45
+ variance = sum((x - mean_val) ** 2 for x in samples) / float(max(1, n - 1))
46
+ std_val = math.sqrt(variance) if n > 1 else 0.0
47
+
48
+ # Normal/t 95% margin of error (z=1.96 approximation for n >= 5)
49
+ margin = 1.96 * (std_val / math.sqrt(n)) if n > 1 else 0.0
50
+ ci = [round(max(0.0, mean_val - margin), 2), round(mean_val + margin, 2)]
51
+
52
+ return cls(
53
+ mean=round(mean_val, 2),
54
+ std_dev=round(std_val, 2),
55
+ min=round(min(samples), 2),
56
+ max=round(max(samples), 2),
57
+ confidence_interval_95=ci,
58
+ )
59
+
60
+
61
+ @dataclass
62
+ class MultiRunStatistics:
63
+ """Comprehensive multi-run statistical evaluation for a model on a scenario."""
64
+
65
+ scenario_id: str
66
+ model: str
67
+ repetitions: int
68
+ pass_at_1: float
69
+ pass_at_k: float
70
+ k_evaluated: int
71
+ scores: MetricDistribution
72
+ latencies: MetricDistribution
73
+ tokens: MetricDistribution
74
+
75
+ def to_dict(self) -> Dict[str, Any]:
76
+ return {
77
+ "scenario_id": self.scenario_id,
78
+ "model": self.model,
79
+ "repetitions": self.repetitions,
80
+ "pass_at_1": self.pass_at_1,
81
+ "pass_at_k": self.pass_at_k,
82
+ "k_evaluated": self.k_evaluated,
83
+ "scores": asdict(self.scores),
84
+ "latencies": asdict(self.latencies),
85
+ "tokens": asdict(self.tokens),
86
+ }
87
+
88
+
89
+ class StatisticalEvaluator:
90
+ """Aggregates repeated crucible runs to calculate Pass@k and statistical variance."""
91
+
92
+ @classmethod
93
+ def aggregate_runs(
94
+ cls,
95
+ scenario_id: str,
96
+ model: str,
97
+ run_scores: List[float],
98
+ run_latencies: List[float],
99
+ run_tokens: List[int],
100
+ pass_threshold: float = 70.0,
101
+ k: int = 5,
102
+ ) -> MultiRunStatistics:
103
+ n = len(run_scores)
104
+ if n == 0:
105
+ raise ValueError("Cannot aggregate empty run results")
106
+
107
+ passing_count = sum(1 for s in run_scores if s >= pass_threshold)
108
+ pass_1 = round(passing_count / float(n), 4)
109
+
110
+ eval_k = min(k, n)
111
+ pass_k_val = round(pass_at_k(n=n, c=passing_count, k=eval_k), 4)
112
+
113
+ return MultiRunStatistics(
114
+ scenario_id=scenario_id,
115
+ model=model,
116
+ repetitions=n,
117
+ pass_at_1=pass_1,
118
+ pass_at_k=pass_k_val,
119
+ k_evaluated=eval_k,
120
+ scores=MetricDistribution.from_samples(run_scores),
121
+ latencies=MetricDistribution.from_samples(run_latencies),
122
+ tokens=MetricDistribution.from_samples([float(t) for t in run_tokens]),
123
+ )
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Dashboard modules for interactive browser inspection."""
5
+
6
+ from skillware_bench.dashboard.generator import DashboardGenerator
7
+
8
+ __all__ = ["DashboardGenerator"]