| 1 | #!/usr/bin/env python3
|
| 2 | """Re-derive public benchmark claims from raw JSONL evidence.
|
| 3 |
|
| 4 | This is deliberately separate from publish_gate.py. publish_gate checks whether
|
| 5 | rows are mechanically publishable; this script checks whether a specific public
|
| 6 | number still matches the raw rows it cites.
|
| 7 | """
|
| 8 |
|
| 9 | from __future__ import annotations
|
| 10 |
|
| 11 | import argparse
|
| 12 | import json
|
| 13 | import statistics
|
| 14 | import sys
|
| 15 | from pathlib import Path
|
| 16 | from typing import Any
|
| 17 |
|
| 18 | ROOT = Path(__file__).resolve().parents[1]
|
| 19 | sys.path.insert(0, str(ROOT / "scripts"))
|
| 20 |
|
| 21 | from oakbench.rows import row_returncode # noqa: E402
|
| 22 |
|
| 23 | KNOWN_TRANSPORTS = {"network", "local_file", "network_shaped"}
|
| 24 | TRANSPORT_FIELDS = ("remote_transport", "workspace_transport")
|
| 25 | FILTER_OPERATORS = {"exists", "eq", "ne", "in"}
|
| 26 | CLEANUP_FAILURE_FIELDS = ("cleanup_failures", "cleanup_failure_count")
|
| 27 |
|
| 28 |
|
| 29 | class ClaimError(Exception):
|
| 30 | pass
|
| 31 |
|
| 32 |
|
| 33 | def load_rows(paths: list[Path]) -> list[dict[str, Any]]:
|
| 34 | rows: list[dict[str, Any]] = []
|
| 35 | for path in paths:
|
| 36 | for line_no, line in enumerate(path.read_text().splitlines(), start=1):
|
| 37 | if not line.strip():
|
| 38 | continue
|
| 39 | try:
|
| 40 | row = json.loads(line)
|
| 41 | except json.JSONDecodeError as exc:
|
| 42 | raise ClaimError(f"{path}:{line_no}: invalid JSON: {exc}") from exc
|
| 43 | rows.append(row)
|
| 44 | return rows
|
| 45 |
|
| 46 |
|
| 47 | def dotted_get(row: dict[str, Any], key: str) -> Any:
|
| 48 | cur: Any = row
|
| 49 | for part in key.split("."):
|
| 50 | if not isinstance(cur, dict) or part not in cur:
|
| 51 | return None
|
| 52 | cur = cur[part]
|
| 53 | return cur
|
| 54 |
|
| 55 |
|
| 56 | def row_matches(row: dict[str, Any], filters: dict[str, Any]) -> bool:
|
| 57 | for key, expected in filters.items():
|
| 58 | actual = dotted_get(row, key)
|
| 59 | if isinstance(expected, list):
|
| 60 | if actual not in expected:
|
| 61 | return False
|
| 62 | elif isinstance(expected, dict):
|
| 63 | unknown = set(expected) - FILTER_OPERATORS
|
| 64 | if unknown:
|
| 65 | raise ClaimError(
|
| 66 | f"invalid filter operator(s) for {key}: {sorted(unknown)}; "
|
| 67 | f"known: {sorted(FILTER_OPERATORS)}"
|
| 68 | )
|
| 69 | if "exists" in expected and (actual is not None) is not bool(expected["exists"]):
|
| 70 | return False
|
| 71 | if "eq" in expected and actual != expected["eq"]:
|
| 72 | return False
|
| 73 | if "ne" in expected and actual == expected["ne"]:
|
| 74 | return False
|
| 75 | if "in" in expected and actual not in expected["in"]:
|
| 76 | return False
|
| 77 | elif actual != expected:
|
| 78 | return False
|
| 79 | return True
|
| 80 |
|
| 81 |
|
| 82 | def number(value: Any) -> float | None:
|
| 83 | if isinstance(value, bool) or value is None:
|
| 84 | return None
|
| 85 | if isinstance(value, (int, float)):
|
| 86 | return float(value)
|
| 87 | return None
|
| 88 |
|
| 89 |
|
| 90 | def metric_values(rows: list[dict[str, Any]], metric: str) -> list[float]:
|
| 91 | values = []
|
| 92 | for row in rows:
|
| 93 | value = number(dotted_get(row, metric))
|
| 94 | if value is not None:
|
| 95 | values.append(value)
|
| 96 | return values
|
| 97 |
|
| 98 |
|
| 99 | def statistic(values: list[float], name: str) -> float:
|
| 100 | if name == "median":
|
| 101 | return float(statistics.median(values))
|
| 102 | if name == "mean":
|
| 103 | return float(statistics.mean(values))
|
| 104 | if name == "sum":
|
| 105 | return float(sum(values))
|
| 106 | if name == "min":
|
| 107 | return float(min(values))
|
| 108 | if name == "max":
|
| 109 | return float(max(values))
|
| 110 | if name == "count":
|
| 111 | return float(len(values))
|
| 112 | raise ClaimError(f"unsupported statistic: {name}")
|
| 113 |
|
| 114 |
|
| 115 | def enforce_row_guards(claim: dict[str, Any], rows: list[dict[str, Any]], label: str) -> list[str]:
|
| 116 | errors: list[str] = []
|
| 117 | if not rows:
|
| 118 | errors.append(f"{label}: no matching rows")
|
| 119 | return errors
|
| 120 |
|
| 121 | guards = claim.get("guards") if isinstance(claim.get("guards"), dict) else {}
|
| 122 | require_success = bool(guards.get("require_success", True))
|
| 123 | if require_success:
|
| 124 | failed = [row for row in rows if row_returncode(row) != 0]
|
| 125 | if failed:
|
| 126 | preview = [(row.get("operation"), row.get("returncode"), row.get("skip_reason")) for row in failed[:5]]
|
| 127 | errors.append(f"{label}: {len(failed)} row(s) not successful: {preview}")
|
| 128 |
|
| 129 | min_rows = int(guards.get("min_rows", 1))
|
| 130 | if len(rows) < min_rows:
|
| 131 | errors.append(f"{label}: {len(rows)} row(s) below min_rows={min_rows}")
|
| 132 |
|
| 133 | if not bool(guards.get("allow_fake_provider", False)):
|
| 134 | fake = [
|
| 135 | row
|
| 136 | for row in rows
|
| 137 | if row.get("driver") == "fake-provider" or row.get("branch_triage_provider") == "fake"
|
| 138 | ]
|
| 139 | if fake:
|
| 140 | errors.append(f"{label}: fake-provider rows are not public claim evidence")
|
| 141 |
|
| 142 | if any("json_oracle_passed" in row for row in rows):
|
| 143 | bad = [row for row in rows if row.get("json_oracle_passed") is not True]
|
| 144 | if bad:
|
| 145 | errors.append(f"{label}: {len(bad)} row(s) have json_oracle_passed != true")
|
| 146 | for cleanup_field in CLEANUP_FAILURE_FIELDS:
|
| 147 | bad = [
|
| 148 | row
|
| 149 | for row in rows
|
| 150 | if cleanup_field in row and number(row.get(cleanup_field)) != 0.0
|
| 151 | ]
|
| 152 | if bad:
|
| 153 | errors.append(f"{label}: {len(bad)} row(s) have {cleanup_field} != 0")
|
| 154 |
|
| 155 | for field in guards.get("fields_true", []):
|
| 156 | bad = [row for row in rows if dotted_get(row, field) is not True]
|
| 157 | if bad:
|
| 158 | errors.append(f"{label}: {len(bad)} row(s) have {field} != true")
|
| 159 | for field in guards.get("fields_false", []):
|
| 160 | bad = [row for row in rows if dotted_get(row, field) is not False]
|
| 161 | if bad:
|
| 162 | errors.append(f"{label}: {len(bad)} row(s) have {field} != false")
|
| 163 | for field in guards.get("fields_zero", []):
|
| 164 | bad = [row for row in rows if number(dotted_get(row, field)) != 0.0]
|
| 165 | if bad:
|
| 166 | errors.append(f"{label}: {len(bad)} row(s) have {field} != 0")
|
| 167 | for field in guards.get("fields_present", []):
|
| 168 | bad = [row for row in rows if dotted_get(row, field) is None]
|
| 169 | if bad:
|
| 170 | errors.append(f"{label}: {len(bad)} row(s) are missing {field}")
|
| 171 | for item in guards.get("fields_equal", []):
|
| 172 | if not isinstance(item, dict) or "field" not in item or "value" not in item:
|
| 173 | errors.append(f"{label}: invalid fields_equal guard {item!r}")
|
| 174 | continue
|
| 175 | field = str(item["field"])
|
| 176 | expected = item["value"]
|
| 177 | bad = [row for row in rows if dotted_get(row, field) != expected]
|
| 178 | if bad:
|
| 179 | errors.append(f"{label}: {len(bad)} row(s) have {field} != {expected!r}")
|
| 180 | return errors
|
| 181 |
|
| 182 |
|
| 183 | def row_transport(row: dict[str, Any]) -> str | None:
|
| 184 | for field in TRANSPORT_FIELDS:
|
| 185 | value = row.get(field)
|
| 186 | if value:
|
| 187 | return str(value)
|
| 188 | return None
|
| 189 |
|
| 190 |
|
| 191 | def transport_identity(row: dict[str, Any]) -> tuple[str, str | None, str | None]:
|
| 192 | return (
|
| 193 | row_transport(row) or "<missing>",
|
| 194 | str(row.get("netshape_profile")) if row.get("netshape_profile") is not None else None,
|
| 195 | str(row.get("remote_server") or row.get("server") or "")
|
| 196 | or None,
|
| 197 | )
|
| 198 |
|
| 199 |
|
| 200 | def transport_set(rows: list[dict[str, Any]]) -> set[tuple[str, str | None, str | None]]:
|
| 201 | return {transport_identity(row) for row in rows if transport_identity(row)[0] != "<missing>"}
|
| 202 |
|
| 203 |
|
| 204 | def transport_errors(rows: list[dict[str, Any]], label: str) -> list[str]:
|
| 205 | errors: list[str] = []
|
| 206 | identities: set[tuple[str, str | None, str | None]] = set()
|
| 207 | for index, row in enumerate(rows, 1):
|
| 208 | transport = row_transport(row)
|
| 209 | if (row.get("remote_repo") or row.get("remote_server")) and transport is None:
|
| 210 | errors.append(f"{label}: row {index} remote-touching row missing transport")
|
| 211 | if transport is not None and transport not in KNOWN_TRANSPORTS:
|
| 212 | errors.append(
|
| 213 | f"{label}: row {index} unknown transport {transport!r}; known: {sorted(KNOWN_TRANSPORTS)}"
|
| 214 | )
|
| 215 | if transport == "network_shaped" and not row.get("netshape_profile"):
|
| 216 | errors.append(f"{label}: row {index} shaped transport missing netshape_profile")
|
| 217 | if transport is not None:
|
| 218 | identities.add(transport_identity(row))
|
| 219 | if len(identities) > 1:
|
| 220 | errors.append(f"{label}: matched rows mix transport identities: {sorted(identities)}")
|
| 221 | return errors
|
| 222 |
|
| 223 |
|
| 224 | def metric_coverage_errors(rows: list[dict[str, Any]], metric: str, values: list[float], label: str) -> list[str]:
|
| 225 | successful = [row for row in rows if row_returncode(row) == 0]
|
| 226 | if len(values) != len(successful):
|
| 227 | return [
|
| 228 | f"{label}: metric {metric} measured on {len(values)} of "
|
| 229 | f"{len(successful)} successful row(s)"
|
| 230 | ]
|
| 231 | return []
|
| 232 |
|
| 233 |
|
| 234 | def evaluate_claim(claim: dict[str, Any], rows: list[dict[str, Any]]) -> dict[str, Any]:
|
| 235 | claim_id = str(claim.get("id") or "<missing-id>")
|
| 236 | filters = claim.get("row_filter")
|
| 237 | if not isinstance(filters, dict):
|
| 238 | raise ClaimError(f"{claim_id}: row_filter is required")
|
| 239 | metric = str(claim.get("metric") or "")
|
| 240 | if not metric:
|
| 241 | raise ClaimError(f"{claim_id}: metric is required")
|
| 242 | stat_name = str(claim.get("statistic") or "median")
|
| 243 | expected = claim.get("expected")
|
| 244 | if not isinstance(expected, dict):
|
| 245 | raise ClaimError(f"{claim_id}: expected is required")
|
| 246 |
|
| 247 | matched = [row for row in rows if row_matches(row, filters)]
|
| 248 | errors = enforce_row_guards(claim, matched, "candidate")
|
| 249 | errors.extend(transport_errors(matched, "candidate"))
|
| 250 | values = metric_values(matched, metric)
|
| 251 | errors.extend(metric_coverage_errors(matched, metric, values, "candidate"))
|
| 252 | if not values:
|
| 253 | errors.append(f"candidate: metric {metric} unmeasured")
|
| 254 | candidate_value = None
|
| 255 | else:
|
| 256 | candidate_value = statistic(values, stat_name)
|
| 257 |
|
| 258 | baseline_value = None
|
| 259 | ratio = None
|
| 260 | delta_pct = None
|
| 261 | reduction_pct = None
|
| 262 | baseline_filter = claim.get("baseline_filter")
|
| 263 | if isinstance(baseline_filter, dict):
|
| 264 | baseline = [row for row in rows if row_matches(row, baseline_filter)]
|
| 265 | errors.extend(enforce_row_guards(claim, baseline, "baseline"))
|
| 266 | errors.extend(transport_errors(baseline, "baseline"))
|
| 267 | baseline_values = metric_values(baseline, metric)
|
| 268 | errors.extend(metric_coverage_errors(baseline, metric, baseline_values, "baseline"))
|
| 269 | if not baseline_values:
|
| 270 | errors.append(f"baseline: metric {metric} unmeasured")
|
| 271 | else:
|
| 272 | baseline_value = statistic(baseline_values, stat_name)
|
| 273 | if baseline_value == 0:
|
| 274 | errors.append("baseline: value is zero; ratio/delta undefined")
|
| 275 | elif candidate_value is not None:
|
| 276 | ratio = candidate_value / baseline_value
|
| 277 | delta_pct = (candidate_value - baseline_value) / baseline_value * 100.0
|
| 278 | reduction_pct = (baseline_value - candidate_value) / baseline_value * 100.0
|
| 279 |
|
| 280 | if bool(claim.get("require_same_transport", True)):
|
| 281 | candidate_transport = transport_set(matched)
|
| 282 | baseline_transport = transport_set(baseline)
|
| 283 | if candidate_transport or baseline_transport:
|
| 284 | if candidate_transport != baseline_transport:
|
| 285 | errors.append(
|
| 286 | "transport mismatch: "
|
| 287 | f"candidate={sorted(candidate_transport)} baseline={sorted(baseline_transport)}"
|
| 288 | )
|
| 289 |
|
| 290 | actuals = {
|
| 291 | "value": candidate_value,
|
| 292 | "baseline_value": baseline_value,
|
| 293 | "ratio": ratio,
|
| 294 | "delta_pct": delta_pct,
|
| 295 | "reduction_pct": reduction_pct,
|
| 296 | }
|
| 297 | field = str(expected.get("field") or "value")
|
| 298 | actual = actuals.get(field)
|
| 299 | if actual is None:
|
| 300 | errors.append(f"expected field {field} is unavailable")
|
| 301 | else:
|
| 302 | if "value" in expected:
|
| 303 | target = float(expected["value"])
|
| 304 | tolerance_pct = float(expected.get("tolerance_pct", 0.0))
|
| 305 | allowed = abs(target) * tolerance_pct / 100.0
|
| 306 | if abs(actual - target) > allowed:
|
| 307 | errors.append(
|
| 308 | f"{field} drifted: actual={actual:.6g} expected={target:.6g} "
|
| 309 | f"tolerance_pct={tolerance_pct:.3g}"
|
| 310 | )
|
| 311 | if "min" in expected and actual < float(expected["min"]):
|
| 312 | errors.append(f"{field} below min: actual={actual:.6g} min={float(expected['min']):.6g}")
|
| 313 | if "max" in expected and actual > float(expected["max"]):
|
| 314 | errors.append(f"{field} above max: actual={actual:.6g} max={float(expected['max']):.6g}")
|
| 315 |
|
| 316 | return {
|
| 317 | "id": claim_id,
|
| 318 | "description": claim.get("description"),
|
| 319 | "metric": metric,
|
| 320 | "statistic": stat_name,
|
| 321 | "n": len(values),
|
| 322 | "matched_rows": len(matched),
|
| 323 | **actuals,
|
| 324 | "expected": expected,
|
| 325 | "verdict": "PASS" if not errors else "FAIL",
|
| 326 | "errors": errors,
|
| 327 | }
|
| 328 |
|
| 329 |
|
| 330 | def load_spec(path: Path) -> list[dict[str, Any]]:
|
| 331 | spec = json.loads(path.read_text())
|
| 332 | if spec.get("schema_version") != 1:
|
| 333 | raise ClaimError(f"{path}: unsupported schema_version {spec.get('schema_version')!r}")
|
| 334 | claims = spec.get("claims")
|
| 335 | if not isinstance(claims, list):
|
| 336 | raise ClaimError(f"{path}: claims must be a list")
|
| 337 | return claims
|
| 338 |
|
| 339 |
|
| 340 | def parse_args() -> argparse.Namespace:
|
| 341 | parser = argparse.ArgumentParser(description=__doc__)
|
| 342 | parser.add_argument("jsonl", nargs="+", type=Path, help="raw evidence JSONL")
|
| 343 | parser.add_argument("--claims", required=True, type=Path, help="claim spec JSON")
|
| 344 | parser.add_argument("--json", action="store_true", help="emit JSON results")
|
| 345 | return parser.parse_args()
|
| 346 |
|
| 347 |
|
| 348 | def main() -> int:
|
| 349 | args = parse_args()
|
| 350 | try:
|
| 351 | rows = load_rows(args.jsonl)
|
| 352 | results = [evaluate_claim(claim, rows) for claim in load_spec(args.claims)]
|
| 353 | except ClaimError as exc:
|
| 354 | print(f"FAIL {exc}", file=sys.stderr)
|
| 355 | return 2
|
| 356 |
|
| 357 | if args.json:
|
| 358 | print(json.dumps({"schema_version": 1, "claims": results}, indent=2, sort_keys=True))
|
| 359 | else:
|
| 360 | for result in results:
|
| 361 | print(f"{result['verdict']} {result['id']}")
|
| 362 | print(
|
| 363 | " "
|
| 364 | f"value={result.get('value')} baseline={result.get('baseline_value')} "
|
| 365 | f"ratio={result.get('ratio')} reduction_pct={result.get('reduction_pct')}"
|
| 366 | )
|
| 367 | for error in result["errors"]:
|
| 368 | print(f" {error}")
|
| 369 | return 1 if any(result["verdict"] != "PASS" for result in results) else 0
|
| 370 |
|
| 371 |
|
| 372 | if __name__ == "__main__":
|
| 373 | raise SystemExit(main())
|