dsh-eval-guard 0.7.2 → 0.7.4

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 (63) hide show
  1. package/ISSUES.md +239 -0
  2. package/README.md +48 -18
  3. package/assets/neural-cifar/workspace.zip +0 -0
  4. package/assets/neural-cpp/workspace.zip +0 -0
  5. package/assets/neural-fp32/workspace.zip +0 -0
  6. package/assets/neural-pendulum/SPEC.md +43 -35
  7. package/assets/neural-pendulum/TASK.md +139 -33
  8. package/assets/neural-pendulum/rl-feasibility/README.md +60 -0
  9. package/assets/neural-pendulum/rl-feasibility/RESULTS.md +370 -0
  10. package/assets/neural-pendulum/rl-feasibility/evaluate.py +107 -0
  11. package/assets/neural-pendulum/rl-feasibility/finalize_run.py +168 -0
  12. package/assets/neural-pendulum/rl-feasibility/models.py +124 -0
  13. package/assets/neural-pendulum/rl-feasibility/obs.py +205 -0
  14. package/assets/neural-pendulum/rl-feasibility/plot_selected.py +55 -0
  15. package/assets/neural-pendulum/rl-feasibility/reference_controller.py +112 -0
  16. package/assets/neural-pendulum/rl-feasibility/results/curves_selected.png +0 -0
  17. package/assets/neural-pendulum/rl-feasibility/results/reference_controller.json +417 -0
  18. package/assets/neural-pendulum/rl-feasibility/results/summary.json +1599 -0
  19. package/assets/neural-pendulum/rl-feasibility/results/table.md +39 -0
  20. package/assets/neural-pendulum/rl-feasibility/sim.py +324 -0
  21. package/assets/neural-pendulum/rl-feasibility/summarize.py +175 -0
  22. package/assets/neural-pendulum/rl-feasibility/task.py +342 -0
  23. package/assets/neural-pendulum/rl-feasibility/test_contract.py +188 -0
  24. package/assets/neural-pendulum/rl-feasibility/train_ppo.py +403 -0
  25. package/assets/neural-pendulum/rl-feasibility/train_sac.py +406 -0
  26. package/assets/neural-pendulum/sim.cpp +13 -2
  27. package/assets/neural-pendulum/sim_eval +0 -0
  28. package/assets/neural-pendulum/workspace.zip +0 -0
  29. package/eval/README.md +19 -9
  30. package/eval/bomberman.py +5 -3
  31. package/eval/commit0.py +10 -3
  32. package/eval/common.py +169 -5
  33. package/eval/ctf.py +4 -2
  34. package/eval/featurebench.py +12 -8
  35. package/eval/land.py +4 -2
  36. package/eval/legacy/bomberman.py +5 -2
  37. package/eval/legacy/ctf.py +5 -2
  38. package/eval/legacy/featurebench.py +55 -15
  39. package/eval/legacy/land.py +6 -3
  40. package/eval/legacy/swe_bench_pro.py +57 -16
  41. package/eval/legacy/tank.py +2 -2
  42. package/eval/legacy/warehouse.py +4 -4
  43. package/eval/neural-cpp.py +9 -6
  44. package/eval/neural-fp32.py +17 -5
  45. package/eval/swe-bench-pro.py +11 -7
  46. package/eval/tank.py +11 -2
  47. package/eval/warehouse.py +17 -1
  48. package/lib/client/index.js +56 -29
  49. package/lib/client/index.js.map +1 -1
  50. package/lib/index.js +883 -298
  51. package/native/dsh-eval-supervisor +0 -0
  52. package/native/supervisor.c +232 -36
  53. package/neural-cifar-eval/README.md +5 -0
  54. package/neural-cifar-eval/common.py +13 -0
  55. package/neural-cifar-eval/ops_eval.py +17 -5
  56. package/neural-cifar-eval/run_eval.py +42 -32
  57. package/neural-pendulum-eval/README.md +62 -0
  58. package/neural-pendulum-eval/ops_eval.py +603 -0
  59. package/neural-pendulum-eval/run_eval.py +218 -137
  60. package/neural-pendulum-eval/sim_eval +0 -0
  61. package/package.json +7 -11
  62. package/assets/neural-pendulum/hidden/test_infra.cpp +0 -631
  63. package/assets/neural-pendulum/include/nn/nn.hpp +0 -88
package/eval/common.py CHANGED
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import argparse
5
+ import hashlib
5
6
  import json
6
7
  import os
7
8
  import shutil
@@ -12,6 +13,22 @@ from pathlib import Path
12
13
 
13
14
  EVAL_ROOT = Path(__file__).resolve().parent
14
15
 
16
+ # Variables the host injects for the evaluator itself. They must not leak into
17
+ # the legacy evaluator or into any candidate process it starts (a candidate
18
+ # that learns EVAL_RESULT_FILE could overwrite the result).
19
+ HOST_EVAL_KEYS = frozenset({
20
+ 'EVAL_RESULT_FILE', 'EVAL_JOB_DIR', 'EVAL_INSTALL_TOKEN',
21
+ 'EVAL_BASELINE_DIR', 'EVAL_VENV_MANIFEST',
22
+ })
23
+
24
+
25
+ def child_env() -> dict[str, str]:
26
+ """Environment for trusted legacy evaluators and their candidate children."""
27
+ return {
28
+ key: value for key, value in os.environ.items()
29
+ if key not in HOST_EVAL_KEYS and key not in {'PYTHONPATH', 'PYTHONHOME'}
30
+ }
31
+
15
32
 
16
33
  def parse_workspace() -> Path:
17
34
  parser = argparse.ArgumentParser(description='single-workspace evaluator')
@@ -28,18 +45,23 @@ def emit(payload: dict) -> None:
28
45
  Path(target).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')
29
46
 
30
47
 
31
- def clean_copy(source: Path, target: Path, ignore_venv: bool = False) -> None:
48
+ def clean_copy(source: Path, target: Path, ignore_venv: bool = False,
49
+ ignore_names: tuple[str, ...] = ()) -> None:
50
+ ignored = set(ignore_names)
51
+
32
52
  def ignore(directory: str, names: list[str]) -> set[str]:
33
- if ignore_venv and Path(directory) == source:
34
- return {name for name in names if name == '.venv'}
53
+ if Path(directory) == source:
54
+ skip = set(ignored)
55
+ if ignore_venv:
56
+ skip.add('.venv')
57
+ return {name for name in names if name in skip}
35
58
  return set()
36
59
  shutil.copytree(source, target, symlinks=True, ignore=ignore)
37
60
 
38
61
 
39
62
  def run_legacy(script: Path, args: list[str], out: Path) -> dict:
40
63
  cmd = [sys.executable, '-X', 'utf8', str(script), *args, '--out', str(out)]
41
- env = {k: v for k, v in os.environ.items() if k not in {'PYTHONPATH', 'PYTHONHOME'}}
42
- completed = subprocess.run(cmd, cwd=str(EVAL_ROOT), capture_output=True, text=True, encoding='utf-8', errors='replace', env=env, timeout=1800)
64
+ completed = subprocess.run(cmd, cwd=str(EVAL_ROOT), capture_output=True, text=True, encoding='utf-8', errors='replace', env=child_env(), timeout=1800)
43
65
  if completed.returncode != 0:
44
66
  return {'ok': False, 'error': (completed.stderr or completed.stdout or 'evaluator failed')[-4000:]}
45
67
  summary = out / 'summary.json'
@@ -59,3 +81,145 @@ def candidate_row(summary: dict, name: str = 'candidate') -> dict:
59
81
  def fail(payload: dict) -> int:
60
82
  emit(payload)
61
83
  return 1
84
+
85
+
86
+ def parse_pytest_results(output: str) -> dict[str, str]:
87
+ """Map pytest node ids to PASSED/FAILED/ERROR/SKIPPED from -rA output."""
88
+ import re
89
+ pattern = re.compile(r'^(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS) (\S+)', re.MULTILINE)
90
+ results: dict[str, str] = {}
91
+ for match in pattern.finditer(output):
92
+ results[match.group(2)] = match.group(1)
93
+ return results
94
+
95
+
96
+ def matches_node(node: str, selector: str) -> bool:
97
+ """Whether a pytest node id belongs to a file/node selector."""
98
+ if node == selector:
99
+ return True
100
+ return node.startswith(selector + '::') or node.startswith(selector + '/')
101
+
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Pristine test material and venv integrity
106
+ # ---------------------------------------------------------------------------
107
+
108
+ def sha256_file(path: Path) -> str:
109
+ digest = hashlib.sha256()
110
+ digest.update(path.read_bytes())
111
+ return digest.hexdigest()
112
+
113
+
114
+ def sha256_tree(root: Path) -> str:
115
+ digest = hashlib.sha256()
116
+
117
+ def walk(current: Path) -> None:
118
+ for entry in sorted(current.iterdir(), key=lambda item: item.name):
119
+ digest.update(str(entry.relative_to(root)).encode('utf-8'))
120
+ if entry.is_dir():
121
+ walk(entry)
122
+ elif entry.is_file():
123
+ digest.update(entry.read_bytes())
124
+ elif entry.is_symlink():
125
+ digest.update(b'link:' + os.readlink(entry).encode('utf-8'))
126
+
127
+ walk(root)
128
+ return digest.hexdigest()
129
+
130
+
131
+ def baseline_dir() -> Path | None:
132
+ raw = os.environ.get('EVAL_BASELINE_DIR', '')
133
+ if raw == '':
134
+ return None
135
+ path = Path(raw)
136
+ return path if path.is_dir() else None
137
+
138
+
139
+ def restore_baseline(candidate: Path) -> None:
140
+ """Overwrite candidate test material with the pristine install-time copy.
141
+
142
+ Also removes conftest.py files the agent added (the pristine snapshot keeps
143
+ the project's own ones), so a workspace hook cannot rewrite pytest results.
144
+ """
145
+ baseline = baseline_dir()
146
+ if baseline is None:
147
+ return
148
+ candidate = candidate.resolve()
149
+ baseline = baseline.resolve()
150
+ for hook in candidate.rglob('conftest.py'):
151
+ if hook.is_symlink():
152
+ hook.unlink()
153
+ continue
154
+ try:
155
+ relative = hook.relative_to(candidate)
156
+ except ValueError:
157
+ continue
158
+ if not (baseline / relative).is_file():
159
+ hook.unlink()
160
+ for entry in baseline.iterdir():
161
+ if entry.name in {'manifest.json', 'venv.json'}:
162
+ continue
163
+ destination = candidate / entry.name
164
+ if destination.exists() or destination.is_symlink():
165
+ if destination.is_dir() and not destination.is_symlink():
166
+ shutil.rmtree(destination, ignore_errors=True)
167
+ else:
168
+ destination.unlink()
169
+ if entry.is_dir():
170
+ shutil.copytree(entry, destination, symlinks=True)
171
+ else:
172
+ shutil.copy2(entry, destination)
173
+
174
+
175
+ def verify_venv(workspace: Path) -> str | None:
176
+ """Verify the venv control plane against the install-time fingerprint.
177
+
178
+ Returns a human-readable reason when the venv was tampered with, else None.
179
+ """
180
+ baseline = baseline_dir()
181
+ if baseline is None:
182
+ return None
183
+ manifest_path = baseline / 'venv.json'
184
+ if not manifest_path.is_file():
185
+ return None
186
+ try:
187
+ manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
188
+ except Exception:
189
+ return 'venv manifest is corrupt'
190
+ venv = workspace / '.venv'
191
+ if not venv.is_dir():
192
+ return 'the evaluator requires the installed .venv; it is missing'
193
+ expected_python = manifest.get('python')
194
+ if isinstance(expected_python, str) and expected_python != '':
195
+ python = venv / 'bin' / 'python'
196
+ try:
197
+ actual = str(python.resolve())
198
+ except OSError:
199
+ actual = ''
200
+ if actual != expected_python:
201
+ return 'the .venv python interpreter was replaced'
202
+ for relative, expected in (manifest.get('files') or {}).items():
203
+ target = venv / relative
204
+ if not target.is_file():
205
+ return f'the .venv file {relative} is missing'
206
+ try:
207
+ actual = sha256_file(target)
208
+ except OSError as error:
209
+ return f'cannot read the .venv file {relative}: {error}'
210
+ if actual != expected:
211
+ return f'the .venv file {relative} was modified'
212
+ expected_pytest = manifest.get('pytest')
213
+ if isinstance(expected_pytest, str) and expected_pytest != '':
214
+ found = False
215
+ for site in venv.glob('lib*/python*/site-packages/pytest'):
216
+ found = True
217
+ try:
218
+ actual = sha256_tree(site)
219
+ except OSError as error:
220
+ return f'cannot read the venv pytest package: {error}'
221
+ if actual != expected_pytest:
222
+ return 'the venv pytest package was modified'
223
+ if not found:
224
+ return 'the venv pytest package disappeared'
225
+ return None
package/eval/ctf.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Single-workspace tank evaluator wrapper."""
2
+ """Single-workspace ctf evaluator wrapper."""
3
3
  from __future__ import annotations
4
4
  import shutil, tempfile
5
5
  from pathlib import Path
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='ctf-eval-') as td:
17
17
  if not result.get('ok'):
18
18
  raise SystemExit(fail({'task': 'ctf', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
19
19
  row = candidate_row(result['summary'])
20
- emit({'task': 'ctf', 'ok': True, 'score': row.get('score', row.get('rating', row.get('theta', 0))), 'rating': row.get('rating', row.get('theta', 0)), 'wins': row.get('wins', 0), 'losses': row.get('losses', 0), 'draws': row.get('draws', 0)})
20
+ if not row:
21
+ raise SystemExit(fail({'task': 'ctf', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
22
+ emit({'task': 'ctf', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- """Single-workspace SWE-Bench Pro evaluator wrapper."""
2
+ """Single-workspace FeatureBench evaluator wrapper."""
3
3
  from __future__ import annotations
4
4
  import os, tempfile
5
5
  from pathlib import Path
6
- from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, run_legacy
6
+ from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, restore_baseline, run_legacy, verify_venv
7
7
 
8
8
  workspace = parse_workspace()
9
+ problem = verify_venv(workspace)
10
+ if problem is not None:
11
+ raise SystemExit(fail({'task': 'featurebench', 'ok': False, 'error': problem}))
9
12
  with tempfile.TemporaryDirectory(prefix='feature-eval-') as td:
10
13
  temp = Path(td)
11
14
  candidate = temp / 'candidate' / 'workspace'
@@ -13,13 +16,14 @@ with tempfile.TemporaryDirectory(prefix='feature-eval-') as td:
13
16
  clean_copy(workspace, candidate, ignore_venv=True)
14
17
  venv = workspace / '.venv'
15
18
  if venv.is_dir():
16
- os.symlink(venv, candidate / '.venv')
19
+ try:
20
+ os.symlink(venv, candidate / '.venv')
21
+ except FileExistsError:
22
+ pass
23
+ restore_baseline(candidate)
17
24
  result = run_legacy(EVAL_ROOT / 'legacy' / 'featurebench.py', ['--submissions', str(temp)], temp / 'out')
18
25
  if not result.get('ok'):
19
26
  raise SystemExit(fail({'task': 'featurebench', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
20
27
  row = candidate_row(result['summary'])
21
- passed = row.get('passed', 0)
22
- failed = row.get('failed', 0)
23
- errors = row.get('errors', 0)
24
- resolved = failed == 0 and errors == 0 and passed > 0
25
- emit({'task': 'featurebench', 'ok': True, 'score': 100.0 if resolved else 0.0, 'resolved': resolved, 'passRate': row.get('score', 0), 'passed': passed, 'failed': failed, 'errors': errors, 'status': row.get('status')})
28
+ resolved = row.get('resolved') is True
29
+ emit({'task': 'featurebench', 'ok': True, 'score': 100.0 if resolved else 0.0, 'resolved': resolved, 'passRate': row.get('score', 0), 'passed': row.get('passed', 0), 'failed': row.get('failed', 0), 'errors': row.get('errors', 0), 'f2pPassed': row.get('f2p_passed', 0), 'f2pTotal': row.get('f2p_total', 0), 'status': row.get('status')})
package/eval/land.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Single-workspace tank evaluator wrapper."""
2
+ """Single-workspace land evaluator wrapper."""
3
3
  from __future__ import annotations
4
4
  import shutil, tempfile
5
5
  from pathlib import Path
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='land-eval-') as td:
17
17
  if not result.get('ok'):
18
18
  raise SystemExit(fail({'task': 'land', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
19
19
  row = candidate_row(result['summary'])
20
- emit({'task': 'land', 'ok': True, 'score': row.get('score', row.get('rating', row.get('theta', 0))), 'rating': row.get('rating', row.get('theta', 0)), 'wins': row.get('wins', 0), 'losses': row.get('losses', 0), 'draws': row.get('draws', 0)})
20
+ if not row:
21
+ raise SystemExit(fail({'task': 'land', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
22
+ emit({'task': 'land', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
@@ -107,13 +107,16 @@ def main() -> int:
107
107
  cmd = [sys.executable, "-X", "utf8", str(task / "tournament" / "run.py"), "--output", str(run_dir),
108
108
  "--bots", *bots, "--sizes", *sizes, "--seeds", str(seeds), "--workers", str(max(1, args.workers))]
109
109
  print("tier=%s sizes=%s seeds=%d bots=%d" % (args.tier, sizes, seeds, len(bots)), flush=True)
110
- completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
110
+ completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=3600)
111
111
  if completed.returncode != 0:
112
112
  print(completed.stdout[-4000:], completed.stderr[-4000:], file=sys.stderr)
113
113
  return 2
114
114
  ratings_doc = load_json(run_dir / "bt.json") or {}
115
+ if ratings_doc.get("unavailable"):
116
+ print("bomberman rating unavailable: " + str(ratings_doc.get("unavailable")), file=sys.stderr)
117
+ return 2
115
118
  ratings = ratings_doc.get("ratings", {})
116
- prices = load_json(ROOT / "prices.json") or {}
119
+ prices = load_json(ROOT / "bomberman-prices.json") or load_json(ROOT / "prices.json") or {}
117
120
 
118
121
  rows = []
119
122
  for name, program in programs.items():
@@ -123,14 +123,17 @@ def main() -> int:
123
123
  print("tier=%s sizes=%s maps=%d players=%d" % (args.tier, sizes, maps_per_size, len(players)), flush=True)
124
124
  for step in steps:
125
125
  completed = subprocess.run([sys.executable, "-X", "utf8", tournament, *step], capture_output=True, text=True,
126
- encoding="utf-8", errors="replace")
126
+ encoding="utf-8", errors="replace", timeout=1800)
127
127
  if completed.returncode != 0:
128
128
  print(completed.stdout[-4000:], completed.stderr[-4000:], file=sys.stderr)
129
129
  return 2
130
130
  scores = load_json(games_dir / "成绩.json") or {}
131
+ if scores.get("unavailable"):
132
+ print("ctf rating unavailable: " + str(scores.get("unavailable")), file=sys.stderr)
133
+ return 2
131
134
  raw_ratings = scores.get("ratings", {})
132
135
  ratings = {name: (value[0] if isinstance(value, list) and value else value) for name, value in raw_ratings.items()}
133
- prices = load_json(ROOT / "prices.json") or {}
136
+ prices = load_json(ROOT / "ctf-prices.json") or load_json(ROOT / "prices.json") or {}
134
137
 
135
138
  rows = []
136
139
  for name, program in players.items():
@@ -4,8 +4,9 @@
4
4
  Submission layout:
5
5
  submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
6
6
 
7
- The hidden feature test file is copied from the plugin asset; scoring runs the
8
- instance's fail-to-pass and pass-to-pass files. Missing run/ statistics are skipped.
7
+ The hidden feature test file is copied from the plugin asset; scoring checks the
8
+ fail-to-pass file and every pass-to-pass file explicitly (a skipped or removed
9
+ test does not count as resolved).
9
10
  """
10
11
  from __future__ import annotations
11
12
 
@@ -23,6 +24,7 @@ ASSET = ROOT.parents[1] / "assets" / "featurebench"
23
24
  PASSED = re.compile(r"(\d+) passed")
24
25
  FAILED = re.compile(r"(\d+) failed")
25
26
  ERRORS = re.compile(r"(\d+) error")
27
+ RESULT = re.compile(r"^(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS) (\S+)", re.MULTILINE)
26
28
 
27
29
 
28
30
  def load_json(path: Path):
@@ -39,18 +41,29 @@ def find_workspace(directory: Path) -> Path | None:
39
41
  return None
40
42
 
41
43
 
42
- def install_hidden_test(workspace: Path) -> None:
44
+ def install_hidden_test(workspace: Path) -> str:
43
45
  source = ASSET / "hidden" / "test_regression.py"
44
- if source.is_file():
45
- shutil.copy2(source, workspace / "tests" / "test_regression.py")
46
+ if not source.is_file():
47
+ return "hidden regression test is missing"
48
+ shutil.copy2(source, workspace / "tests" / "test_regression.py")
49
+ return ""
46
50
 
47
51
 
48
- def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
52
+ def matches(node: str, selectors: list[str]) -> bool:
53
+ for selector in selectors:
54
+ if node == selector or node.startswith(selector + "::") or node.startswith(selector + "/"):
55
+ return True
56
+ return False
57
+
58
+
59
+ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str, dict]:
49
60
  venv = workspace / ".venv" / "bin" / "python"
50
61
  python = str(venv) if venv.is_file() else "python3"
51
- tests = list(instance.get("FAIL_TO_PASS", [])) + list(instance.get("PASS_TO_PASS", []))
62
+ fail_to_pass = list(instance.get("FAIL_TO_PASS", []))
63
+ pass_to_pass = list(instance.get("PASS_TO_PASS", []))
64
+ tests = fail_to_pass + pass_to_pass
52
65
  env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
53
- completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
66
+ completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no", "-rA"], cwd=workspace,
54
67
  capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
55
68
  output = (completed.stdout or "") + "\n" + (completed.stderr or "")
56
69
  passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
@@ -58,7 +71,31 @@ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
58
71
  errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
59
72
  if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
60
73
  errors = 1
61
- return passed, failed, errors, output[-3000:]
74
+ outcomes = {node: status for status, node in RESULT.findall(output)}
75
+ f2p_total = f2p_passed = 0
76
+ p2p_total = p2p_passed = 0
77
+ for node, status in outcomes.items():
78
+ if matches(node, fail_to_pass):
79
+ f2p_total += 1
80
+ f2p_passed += 1 if status == "PASSED" else 0
81
+ elif matches(node, pass_to_pass):
82
+ p2p_total += 1
83
+ p2p_passed += 1 if status == "PASSED" else 0
84
+ resolved = (
85
+ len(fail_to_pass) > 0
86
+ and f2p_total > 0
87
+ and f2p_passed == f2p_total
88
+ and p2p_total > 0
89
+ and p2p_passed == p2p_total
90
+ and completed.returncode == 0
91
+ )
92
+ return passed, failed, errors, output[-3000:], {
93
+ "resolved": resolved,
94
+ "f2p_total": f2p_total,
95
+ "f2p_passed": f2p_passed,
96
+ "p2p_total": p2p_total,
97
+ "p2p_passed": p2p_passed,
98
+ }
62
99
 
63
100
 
64
101
  def main() -> int:
@@ -78,20 +115,23 @@ def main() -> int:
78
115
  for directory in directories:
79
116
  workspace = find_workspace(directory)
80
117
  if workspace is None:
81
- rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
118
+ rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0, "resolved": False})
82
119
  continue
83
120
  try:
84
- install_hidden_test(workspace)
85
- passed, failed, errors, detail = run_tests(workspace, instance)
121
+ hidden_error = install_hidden_test(workspace)
122
+ if hidden_error:
123
+ rows.append({"name": directory.name, "status": "hidden-test-failed", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "resolved": False, "detail": hidden_error})
124
+ continue
125
+ passed, failed, errors, detail, extra = run_tests(workspace, instance)
86
126
  total = passed + failed + errors
87
127
  score = 100.0 * passed / total if total else 0.0
88
- rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
128
+ rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail, **extra})
89
129
  except Exception as error:
90
- rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
130
+ rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "resolved": False, "detail": str(error)})
91
131
  rows.sort(key=lambda row: (-row["score"], row["name"]))
92
132
  payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "featurebench", "instance": instance.get("instance_id"), "results": rows}
93
133
  (out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
94
- lines = ["# FeatureBench · 单 feature 排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
134
+ lines = ["# FeatureBench · 单 feature 排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 feature 测试与全部回归文件通过才 resolved", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
95
135
  for rank, row in enumerate(rows, 1):
96
136
  lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
97
137
  (out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
@@ -112,20 +112,23 @@ def main() -> int:
112
112
  if melee == 0:
113
113
  cmd += ["--only", "duel"]
114
114
  print("tier=%s maps=%d melee=%d entries=%d" % (args.tier, maps, melee, len(list(entries.iterdir()))), flush=True)
115
- completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
115
+ completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800)
116
116
  if completed.returncode != 0:
117
117
  print(completed.stdout[-4000:], completed.stderr[-4000:], file=sys.stderr)
118
118
  return 2
119
119
  ratings_prefix = out / "ratings"
120
120
  rate = subprocess.run([sys.executable, "-X", "utf8", str(task / "tournament" / "rate.py"), str(results_jsonl),
121
121
  "--entries", str(entries), "--out", str(ratings_prefix)], capture_output=True, text=True,
122
- encoding="utf-8", errors="replace")
122
+ encoding="utf-8", errors="replace", timeout=1800)
123
123
  if rate.returncode != 0:
124
124
  print(rate.stdout[-4000:], rate.stderr[-4000:], file=sys.stderr)
125
125
  return 2
126
126
  ratings = load_json(Path(str(ratings_prefix) + ".json")) or {}
127
+ if ratings.get("unavailable"):
128
+ print("land rating unavailable: " + str(ratings.get("unavailable")), file=sys.stderr)
129
+ return 2
127
130
  models = ratings.get("models", {})
128
- prices = load_json(ROOT / "prices.json") or {}
131
+ prices = load_json(ROOT / "land-prices.json") or load_json(ROOT / "prices.json") or {}
129
132
 
130
133
  rows = []
131
134
  all_names = list(names) + [name for name in models if name not in names]
@@ -4,8 +4,9 @@
4
4
  Submission layout:
5
5
  submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
6
6
 
7
- The hidden test patch is applied from the plugin asset; scoring runs the
8
- instance's fail-to-pass and pass-to-pass tests. Missing run/ statistics are skipped.
7
+ The hidden test patch is applied from the plugin asset; scoring checks the
8
+ instance's fail-to-pass and pass-to-pass node ids explicitly (a skipped or
9
+ renamed fail-to-pass test does not count as resolved).
9
10
  """
10
11
  from __future__ import annotations
11
12
 
@@ -22,6 +23,7 @@ ASSET = ROOT.parents[1] / "assets" / "swe-bench-pro"
22
23
  PASSED = re.compile(r"(\d+) passed")
23
24
  FAILED = re.compile(r"(\d+) failed")
24
25
  ERRORS = re.compile(r"(\d+) error")
26
+ RESULT = re.compile(r"^(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS) (\S+)", re.MULTILINE)
25
27
 
26
28
 
27
29
  def load_json(path: Path):
@@ -48,22 +50,34 @@ def find_workspace(directory: Path) -> Path | None:
48
50
  return None
49
51
 
50
52
 
51
- def apply_hidden_tests(workspace: Path) -> None:
53
+ def apply_hidden_tests(workspace: Path) -> str:
52
54
  patch = ASSET / "hidden" / "test.patch"
53
55
  if not patch.is_file():
54
- return
55
- subprocess.run(["patch", "-p1", "--forward", "-i", str(patch)], cwd=workspace,
56
- capture_output=True, text=True, encoding="utf-8", errors="replace")
56
+ return "hidden test patch is missing"
57
+ completed = subprocess.run(["patch", "-p1", "--forward", "-i", str(patch)], cwd=workspace,
58
+ capture_output=True, text=True, encoding="utf-8", errors="replace")
59
+ if completed.returncode != 0:
60
+ return "hidden test patch failed to apply: " + (completed.stdout + completed.stderr)[-2000:]
61
+ return ""
57
62
 
58
63
 
59
- def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
64
+ def matches(node: str, selectors: list[str]) -> bool:
65
+ for selector in selectors:
66
+ if node == selector or node.startswith(selector + "::") or node.startswith(selector + "/"):
67
+ return True
68
+ return False
69
+
70
+
71
+ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str, dict]:
60
72
  venv = workspace / ".venv" / "bin" / "python"
61
73
  python = str(venv) if venv.is_file() else "python3"
62
- tests = as_list(instance.get("fail_to_pass")) + as_list(instance.get("pass_to_pass"))
74
+ fail_to_pass = as_list(instance.get("fail_to_pass"))
75
+ pass_to_pass = as_list(instance.get("pass_to_pass"))
76
+ tests = fail_to_pass + pass_to_pass
63
77
  if not tests:
64
78
  tests = as_list(instance.get("selected_test_files_to_run"))
65
79
  env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
66
- completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
80
+ completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no", "-rA"], cwd=workspace,
67
81
  capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
68
82
  output = (completed.stdout or "") + "\n" + (completed.stderr or "")
69
83
  passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
@@ -71,7 +85,31 @@ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
71
85
  errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
72
86
  if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
73
87
  errors = 1
74
- return passed, failed, errors, output[-3000:]
88
+ outcomes = {node: status for status, node in RESULT.findall(output)}
89
+ f2p_total = f2p_passed = 0
90
+ p2p_total = p2p_passed = 0
91
+ for node, status in outcomes.items():
92
+ if matches(node, fail_to_pass):
93
+ f2p_total += 1
94
+ f2p_passed += 1 if status == "PASSED" else 0
95
+ elif matches(node, pass_to_pass):
96
+ p2p_total += 1
97
+ p2p_passed += 1 if status == "PASSED" else 0
98
+ resolved = (
99
+ len(fail_to_pass) > 0
100
+ and f2p_total > 0
101
+ and f2p_passed == f2p_total
102
+ and p2p_total > 0
103
+ and p2p_passed == p2p_total
104
+ and completed.returncode == 0
105
+ )
106
+ return passed, failed, errors, output[-3000:], {
107
+ "resolved": resolved,
108
+ "f2p_total": f2p_total,
109
+ "f2p_passed": f2p_passed,
110
+ "p2p_total": p2p_total,
111
+ "p2p_passed": p2p_passed,
112
+ }
75
113
 
76
114
 
77
115
  def main() -> int:
@@ -91,20 +129,23 @@ def main() -> int:
91
129
  for directory in directories:
92
130
  workspace = find_workspace(directory)
93
131
  if workspace is None:
94
- rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
132
+ rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0, "resolved": False})
95
133
  continue
96
134
  try:
97
- apply_hidden_tests(workspace)
98
- passed, failed, errors, detail = run_tests(workspace, instance)
135
+ patch_error = apply_hidden_tests(workspace)
136
+ if patch_error:
137
+ rows.append({"name": directory.name, "status": "hidden-test-failed", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "resolved": False, "detail": patch_error})
138
+ continue
139
+ passed, failed, errors, detail, extra = run_tests(workspace, instance)
99
140
  total = passed + failed + errors
100
141
  score = 100.0 * passed / total if total else 0.0
101
- rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
142
+ rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail, **extra})
102
143
  except Exception as error:
103
- rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
144
+ rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "resolved": False, "detail": str(error)})
104
145
  rows.sort(key=lambda row: (-row["score"], row["name"]))
105
146
  payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "swe-bench-pro", "instance": instance.get("instance_id"), "results": rows}
106
147
  (out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
107
- lines = ["# SWE-Bench Pro · 单任务排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
148
+ lines = ["# SWE-Bench Pro · 单任务排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 节点全部通过才 resolved", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
108
149
  for rank, row in enumerate(rows, 1):
109
150
  lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
110
151
  (out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
@@ -258,8 +258,8 @@ def main() -> int:
258
258
  if not directories:
259
259
  print("no submissions found under " + str(submissions_root), file=sys.stderr)
260
260
  return 2
261
- prices_path = ROOT / "prices.json"
262
- prices = load_json(prices_path) or {}
261
+ prices_path = ROOT / "tank-prices.json"
262
+ prices = load_json(prices_path) or load_json(ROOT / "prices.json") or {}
263
263
 
264
264
  participants: list[Participant] = [Participant("baseline", workspace / "baseline.cpp", "baseline")]
265
265
  participants += [load_submission(directory) for directory in directories]
@@ -2,9 +2,9 @@
2
2
  """Independent evaluation for dsh-eval-guard warehouse submissions.
3
3
 
4
4
  Reads submissions/<name>/{src/*.cpp,run/,cost.json}, compiles with the fixed
5
- g++ flags, compares every public workload byte-for-byte with the expected
6
- output, measures CPU time against the baseline, and writes a leaderboard plus
7
- cost/violation/static-scan reports.
5
+ g++ flags, compares every public workload byte-for-byte (CRLF normalized) with
6
+ the expected output, measures CPU time against the baseline, and writes a
7
+ leaderboard plus cost/violation/static-scan reports.
8
8
 
9
9
  The evaluator is independent of the DSH plugin: copy the run/ directory from
10
10
  $DSH_HOME/eval-guard/runs/<run-id>/ next to src/.
@@ -220,7 +220,7 @@ def main() -> int:
220
220
  out_dir = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
221
221
  out_dir.mkdir(parents=True, exist_ok=True)
222
222
  build_dir = out_dir / "build"
223
- prices = load_json(ROOT / "prices.json") or {}
223
+ prices = load_json(ROOT / "warehouse-prices.json") or load_json(ROOT / "prices.json") or {}
224
224
 
225
225
  print("tier=%s workloads=%s repeats=%d" % (args.tier, ",".join(names), repeats))
226
226
  baseline = Participant("baseline", baseline_src, "baseline")