agent-comparability 0.5.2__tar.gz → 0.5.4__tar.gz

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 (30) hide show
  1. {agent_comparability-0.5.2/src/agent_comparability.egg-info → agent_comparability-0.5.4}/PKG-INFO +43 -6
  2. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/README.md +38 -5
  3. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/pyproject.toml +29 -23
  4. agent_comparability-0.5.4/src/agent_comparability/__init__.py +3 -0
  5. agent_comparability-0.5.4/src/agent_comparability/__main__.py +131 -0
  6. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/core.py +10 -5
  7. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/SPEC.md +7 -4
  8. agent_comparability-0.5.4/src/agent_comparability/data/cases-0.4.json +16 -0
  9. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/cdr.schema.json +1 -1
  10. agent_comparability-0.5.4/src/agent_comparability/evidence.py +109 -0
  11. agent_comparability-0.5.4/src/agent_comparability/inspect_log.py +283 -0
  12. {agent_comparability-0.5.2 → agent_comparability-0.5.4/src/agent_comparability.egg-info}/PKG-INFO +43 -6
  13. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/SOURCES.txt +8 -0
  14. agent_comparability-0.5.4/tests/test_cli.py +152 -0
  15. agent_comparability-0.5.4/tests/test_evidence.py +126 -0
  16. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_gate_regressions.py +7 -7
  17. agent_comparability-0.5.4/tests/test_inspect_log.py +352 -0
  18. agent_comparability-0.5.4/tests/test_scenarios_001.py +54 -0
  19. agent_comparability-0.5.2/src/agent_comparability/__init__.py +0 -3
  20. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/LICENSE +0 -0
  21. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/NOTICE +0 -0
  22. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/setup.cfg +0 -0
  23. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/boundary-cases.json +0 -0
  24. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/frozen-cases-a.json +0 -0
  25. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/py.typed +0 -0
  26. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/dependency_links.txt +0 -0
  27. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/requires.txt +0 -0
  28. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/top_level.txt +0 -0
  29. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_properties.py +0 -0
  30. {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_smoke.py +0 -0
@@ -1,8 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-comparability
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation
5
5
  License: Apache-2.0
6
+ Project-URL: Homepage, https://github.com/Rethymus/agent-comparability
7
+ Project-URL: Repository, https://github.com/Rethymus/agent-comparability
8
+ Project-URL: Issues, https://github.com/Rethymus/agent-comparability/issues
9
+ Project-URL: Changelog, https://github.com/Rethymus/agent-comparability/blob/main/CHANGELOG.md
6
10
  Requires-Python: >=3.10
7
11
  Description-Content-Type: text/markdown
8
12
  License-File: LICENSE
@@ -25,6 +29,7 @@ Dynamic: license-file
25
29
  [![CI](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml/badge.svg)](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
26
30
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
27
31
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)
32
+ [![PyPI](https://img.shields.io/pypi/v/agent-comparability)](https://pypi.org/project/agent-comparability/)
28
33
 
29
34
  **English** | [简体中文](README.zh-CN.md)
30
35
 
@@ -74,6 +79,12 @@ Agent Comparability is a framework-independent **specification** ([spec/SPEC.md]
74
79
  a frozen **conformance suite**, and a thin **reference implementation** that adjudicates
75
80
  *comparability* of agent evaluation records — never the scores themselves.
76
81
 
82
+ **Does it actually refuse unsupported claims?** See
83
+ [experiments/scenarios_001/RECORD.md](experiments/scenarios_001/RECORD.md): four real-pipeline
84
+ scenarios (real `.eval` logs → converter → core) covering all four verdicts — including the
85
+ confounded comparison and the missing-trajectory case, both correctly **refused** rather than
86
+ packaged as supported conclusions.
87
+
77
88
  **Who is this for**: developers comparing agent/prompt/harness evaluation results,
78
89
  benchmark authors documenting what conclusions their data supports, and teams building
79
90
  reliability pipelines on top of existing eval frameworks.
@@ -97,6 +108,8 @@ Three failure modes silently corrupt agent-evaluation conclusions:
97
108
  | **Evidence gaps** | Missing traces quietly inflate success rates; rankings reverse across harnesses |
98
109
 
99
110
  These boundaries should be **machine-checkable**, not vibes. That is what this project ships.
111
+ How this relates to benchmark-rigor and transcript-audit work (verified literature anchors):
112
+ see [docs/positioning.md](docs/positioning.md).
100
113
 
101
114
  ## 3. What Agent Comparability Does
102
115
 
@@ -153,10 +166,30 @@ pip install -e .[suite] # + suite basics (pyyaml, jsonschema)
153
166
  pip install -e .[suite,inspect,pydantic] # FULL conformance run — required by run_suite.py
154
167
  ```
155
168
 
156
- Requires Python ≥ 3.10. No Docker, no model keys.
169
+ Requires Python ≥ 3.10. No Docker, no model keys. Note: the core package supports 3.10+;
170
+ the optional `inspect`/`pydantic` extras pull dependencies that require Python ≥ 3.11 —
171
+ on 3.10 pip resolves older versions of them.
157
172
 
158
173
  ## 7. Quickstart
159
174
 
175
+ **Command line** (offline, zero extra dependencies — input contract below):
176
+
177
+ ```bash
178
+ python -m agent_comparability examples/demo-case.json --claim score_comparison
179
+ # → CDR JSON on stdout (exit 0). INADMISSIBLE / PARTIAL / INCONCLUSIVE are verdicts,
180
+ # not program failures. Invalid input (bad JSON / missing or mistyped fields) →
181
+ # exit 2 with a path-precise diagnostic — never silently converted into a verdict.
182
+ # -o cdr.json writes to a file instead. The demo case is SYNTHETIC/illustrative.
183
+ ```
184
+
185
+ Input contract (`{"id": str, "records": [{"task_id", "harness", "model", "reward":
186
+ number, "reward_status", ...optional}]}`); unknown record fields are ignored and
187
+ recorded in `unknown_conditions` (SPEC §5). Add `--manifest out.manifest.json` to
188
+ also emit an Evidence Manifest documenting artifact digests and evidence trust
189
+ states (never auto-upgraded; see [docs/evidence-manifest.md](docs/evidence-manifest.md)).
190
+
191
+ **Python API**:
192
+
160
193
  ```python
161
194
  from agent_comparability import adjudicate
162
195
 
@@ -222,7 +255,7 @@ CDR — actual output:
222
255
  "rec:t1/m1/h1",
223
256
  "rec:t1/m1/h2"
224
257
  ],
225
- "policy_version": "cmp/0.3.1",
258
+ "policy_version": "cmp/0.4.0",
226
259
  "limitations": [
227
260
  "摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
228
261
  ]
@@ -281,10 +314,14 @@ Both wrap the *same* core (no semantic duplication). Inspect scorer uses
281
314
  The Pydantic evaluator checks the **task's real output** (`ctx.output`) against
282
315
  frozen expectations — it never re-adjudicates inside the evaluator — and the
283
316
  adapter's exit code derives from the full 22-pair `report` (cases + failures).
317
+ For **real Inspect EvalLog files** (not frozen cases) use the converter:
318
+ `python -m agent_comparability.inspect_log run.eval --claim … --status …`
319
+ (official APIs, explicit mapping policy, no silent guessing — see
320
+ [docs/inspect-log.md](docs/inspect-log.md)).
284
321
 
285
322
  ## 12. Specification & Versioning
286
323
 
287
- Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.3.1`
324
+ Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.4.0`
288
325
  (inside SPEC), and `agent_comparability.__version__`. Judgment-table changes
289
326
  require a spec major bump (SPEC §5). See [CHANGELOG](CHANGELOG.md) for the full tag lineage
290
327
  (v0.3.1–v0.3.6 are deprecated candidates kept immutably for the record).
@@ -318,13 +355,13 @@ No — no network, no credentials, no Docker. It only ever sees the records you
318
355
 
319
356
  **Which versions should I depend on?**
320
357
  Package `v0.5.0+` (version lines unified and CI-enforced since then); specification
321
- `cmp/0.3.1` evolves independently — see [§12](#12-specification--versioning).
358
+ `cmp/0.4.0` evolves independently — see [§12](#12-specification--versioning).
322
359
 
323
360
  ## Project status
324
361
 
325
362
  | Component | Status |
326
363
  |---|---|
327
- | Specification `cmp/0.3.1` | Stable — frozen conformance set anchors it |
364
+ | Specification `cmp/0.4.0` | Stable — frozen conformance set anchors it |
328
365
  | Claim types | `score_comparison`, `causal`, `descriptive_registration` (+ unknown → `INCONCLUSIVE`) |
329
366
  | Conformance cases | 22 frozen pairs (A + B sets), three separated metrics, all green in CI |
330
367
  | Adapters | Inspect AI ✓ · Pydantic Evals ✓ |
@@ -7,6 +7,7 @@
7
7
  [![CI](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml/badge.svg)](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
8
8
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
9
9
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)
10
+ [![PyPI](https://img.shields.io/pypi/v/agent-comparability)](https://pypi.org/project/agent-comparability/)
10
11
 
11
12
  **English** | [简体中文](README.zh-CN.md)
12
13
 
@@ -56,6 +57,12 @@ Agent Comparability is a framework-independent **specification** ([spec/SPEC.md]
56
57
  a frozen **conformance suite**, and a thin **reference implementation** that adjudicates
57
58
  *comparability* of agent evaluation records — never the scores themselves.
58
59
 
60
+ **Does it actually refuse unsupported claims?** See
61
+ [experiments/scenarios_001/RECORD.md](experiments/scenarios_001/RECORD.md): four real-pipeline
62
+ scenarios (real `.eval` logs → converter → core) covering all four verdicts — including the
63
+ confounded comparison and the missing-trajectory case, both correctly **refused** rather than
64
+ packaged as supported conclusions.
65
+
59
66
  **Who is this for**: developers comparing agent/prompt/harness evaluation results,
60
67
  benchmark authors documenting what conclusions their data supports, and teams building
61
68
  reliability pipelines on top of existing eval frameworks.
@@ -79,6 +86,8 @@ Three failure modes silently corrupt agent-evaluation conclusions:
79
86
  | **Evidence gaps** | Missing traces quietly inflate success rates; rankings reverse across harnesses |
80
87
 
81
88
  These boundaries should be **machine-checkable**, not vibes. That is what this project ships.
89
+ How this relates to benchmark-rigor and transcript-audit work (verified literature anchors):
90
+ see [docs/positioning.md](docs/positioning.md).
82
91
 
83
92
  ## 3. What Agent Comparability Does
84
93
 
@@ -135,10 +144,30 @@ pip install -e .[suite] # + suite basics (pyyaml, jsonschema)
135
144
  pip install -e .[suite,inspect,pydantic] # FULL conformance run — required by run_suite.py
136
145
  ```
137
146
 
138
- Requires Python ≥ 3.10. No Docker, no model keys.
147
+ Requires Python ≥ 3.10. No Docker, no model keys. Note: the core package supports 3.10+;
148
+ the optional `inspect`/`pydantic` extras pull dependencies that require Python ≥ 3.11 —
149
+ on 3.10 pip resolves older versions of them.
139
150
 
140
151
  ## 7. Quickstart
141
152
 
153
+ **Command line** (offline, zero extra dependencies — input contract below):
154
+
155
+ ```bash
156
+ python -m agent_comparability examples/demo-case.json --claim score_comparison
157
+ # → CDR JSON on stdout (exit 0). INADMISSIBLE / PARTIAL / INCONCLUSIVE are verdicts,
158
+ # not program failures. Invalid input (bad JSON / missing or mistyped fields) →
159
+ # exit 2 with a path-precise diagnostic — never silently converted into a verdict.
160
+ # -o cdr.json writes to a file instead. The demo case is SYNTHETIC/illustrative.
161
+ ```
162
+
163
+ Input contract (`{"id": str, "records": [{"task_id", "harness", "model", "reward":
164
+ number, "reward_status", ...optional}]}`); unknown record fields are ignored and
165
+ recorded in `unknown_conditions` (SPEC §5). Add `--manifest out.manifest.json` to
166
+ also emit an Evidence Manifest documenting artifact digests and evidence trust
167
+ states (never auto-upgraded; see [docs/evidence-manifest.md](docs/evidence-manifest.md)).
168
+
169
+ **Python API**:
170
+
142
171
  ```python
143
172
  from agent_comparability import adjudicate
144
173
 
@@ -204,7 +233,7 @@ CDR — actual output:
204
233
  "rec:t1/m1/h1",
205
234
  "rec:t1/m1/h2"
206
235
  ],
207
- "policy_version": "cmp/0.3.1",
236
+ "policy_version": "cmp/0.4.0",
208
237
  "limitations": [
209
238
  "摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
210
239
  ]
@@ -263,10 +292,14 @@ Both wrap the *same* core (no semantic duplication). Inspect scorer uses
263
292
  The Pydantic evaluator checks the **task's real output** (`ctx.output`) against
264
293
  frozen expectations — it never re-adjudicates inside the evaluator — and the
265
294
  adapter's exit code derives from the full 22-pair `report` (cases + failures).
295
+ For **real Inspect EvalLog files** (not frozen cases) use the converter:
296
+ `python -m agent_comparability.inspect_log run.eval --claim … --status …`
297
+ (official APIs, explicit mapping policy, no silent guessing — see
298
+ [docs/inspect-log.md](docs/inspect-log.md)).
266
299
 
267
300
  ## 12. Specification & Versioning
268
301
 
269
- Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.3.1`
302
+ Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.4.0`
270
303
  (inside SPEC), and `agent_comparability.__version__`. Judgment-table changes
271
304
  require a spec major bump (SPEC §5). See [CHANGELOG](CHANGELOG.md) for the full tag lineage
272
305
  (v0.3.1–v0.3.6 are deprecated candidates kept immutably for the record).
@@ -300,13 +333,13 @@ No — no network, no credentials, no Docker. It only ever sees the records you
300
333
 
301
334
  **Which versions should I depend on?**
302
335
  Package `v0.5.0+` (version lines unified and CI-enforced since then); specification
303
- `cmp/0.3.1` evolves independently — see [§12](#12-specification--versioning).
336
+ `cmp/0.4.0` evolves independently — see [§12](#12-specification--versioning).
304
337
 
305
338
  ## Project status
306
339
 
307
340
  | Component | Status |
308
341
  |---|---|
309
- | Specification `cmp/0.3.1` | Stable — frozen conformance set anchors it |
342
+ | Specification `cmp/0.4.0` | Stable — frozen conformance set anchors it |
310
343
  | Claim types | `score_comparison`, `causal`, `descriptive_registration` (+ unknown → `INCONCLUSIVE`) |
311
344
  | Conformance cases | 22 frozen pairs (A + B sets), three separated metrics, all green in CI |
312
345
  | Adapters | Inspect AI ✓ · Pydantic Evals ✓ |
@@ -1,23 +1,29 @@
1
- [build-system]
2
- requires = ["setuptools>=68"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "agent-comparability"
7
- version = "0.5.2"
8
- description = "Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation"
9
- readme = "README.md"
10
- requires-python = ">=3.10"
11
- license = {text = "Apache-2.0"}
12
- dependencies = []
13
-
14
- [project.optional-dependencies]
15
- suite = ["pyyaml", "jsonschema"]
16
- inspect = ["inspect-ai"]
17
- pydantic = ["pydantic-evals"]
18
-
19
- [tool.setuptools.packages.find]
20
- where = ["src"]
21
-
22
- [tool.setuptools.package-data]
23
- agent_comparability = ["data/*"]
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-comparability"
7
+ version = "0.5.4"
8
+ description = "Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "Apache-2.0"}
12
+ dependencies = []
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/Rethymus/agent-comparability"
16
+ Repository = "https://github.com/Rethymus/agent-comparability"
17
+ Issues = "https://github.com/Rethymus/agent-comparability/issues"
18
+ Changelog = "https://github.com/Rethymus/agent-comparability/blob/main/CHANGELOG.md"
19
+
20
+ [project.optional-dependencies]
21
+ suite = ["pyyaml", "jsonschema"]
22
+ inspect = ["inspect-ai"]
23
+ pydantic = ["pydantic-evals"]
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [tool.setuptools.package-data]
29
+ agent_comparability = ["data/*"]
@@ -0,0 +1,3 @@
1
+ """Agent Comparability — cmp/0.4.0 参考实现。"""
2
+ from .core import CDR, adjudicate, POLICY_VERSION
3
+ __version__ = "0.5.4"
@@ -0,0 +1,131 @@
1
+ """离线 JSON → CDR 命令行闭环(#16,零额外运行时依赖)。
2
+
3
+ 用法:
4
+ python -m agent_comparability input.json --claim score_comparison [-o cdr.json]
5
+
6
+ 输入契约(= adjudicate 既有输入形状):
7
+ {"id": str,
8
+ "records": [{"task_id": str, "harness": str, "model": str,
9
+ "reward": number, "reward_status": str,
10
+ ...可选 trajectory_available/score_scale/repetition_count}]}
11
+
12
+ 退出码契约:
13
+ 0 = 产出 CDR(ADMISSIBLE/INADMISSIBLE/PARTIAL/INCONCLUSIVE 均为裁决成功,非程序失败)
14
+ 2 = 输入错误(文件不可读/JSON 语法/契约字段缺失或类型错)——诊断含出错路径;
15
+ 无效输入绝不允许被转换为貌似成功的裁决
16
+
17
+ 本模块只做契约存在性与类型检查;一切判定语义由 adjudicate() 负责(不重新实现判定表,
18
+ 不改规范语义)。未知字段按 SPEC §5 忽略并记入 CDR 的 unknown_conditions,不在此报错。
19
+ """
20
+ import argparse
21
+ import json
22
+ import sys
23
+
24
+ from . import adjudicate
25
+
26
+ REQUIRED_STR = ("task_id", "harness", "model", "reward_status")
27
+
28
+
29
+ class ContractError(Exception):
30
+ """输入违反契约:消息含出错路径,如 records[2].reward。"""
31
+
32
+
33
+ def _is_number(v):
34
+ return isinstance(v, (int, float)) and not isinstance(v, bool)
35
+
36
+
37
+ def _is_int(v):
38
+ return isinstance(v, int) and not isinstance(v, bool)
39
+
40
+
41
+ def validate_case(case):
42
+ if not isinstance(case, dict):
43
+ raise ContractError("输入顶层必须是 JSON 对象,实际是 " + type(case).__name__)
44
+ if "id" not in case:
45
+ raise ContractError("缺少必填字段 id")
46
+ if not isinstance(case["id"], str):
47
+ raise ContractError("id 必须是字符串,实际是 " + type(case["id"]).__name__)
48
+ if "records" not in case:
49
+ raise ContractError("缺少必填字段 records")
50
+ if not isinstance(case["records"], list) or not case["records"]:
51
+ raise ContractError("records 必须是非空数组")
52
+ for i, r in enumerate(case["records"]):
53
+ where = f"records[{i}]"
54
+ if not isinstance(r, dict):
55
+ raise ContractError(f"{where} 必须是 JSON 对象,实际是 " + type(r).__name__)
56
+ for k in REQUIRED_STR:
57
+ if k not in r:
58
+ raise ContractError(f"{where}.{k} 缺失(必填字段)")
59
+ if not isinstance(r[k], str):
60
+ raise ContractError(f"{where}.{k} 必须是字符串,实际是 " + type(r[k]).__name__)
61
+ if "reward" not in r:
62
+ raise ContractError(f"{where}.reward 缺失(必填字段)")
63
+ if not _is_number(r["reward"]):
64
+ raise ContractError(f"{where}.reward 必须是数值,实际是 " + type(r["reward"]).__name__)
65
+ if "trajectory_available" in r and not isinstance(r["trajectory_available"], bool):
66
+ raise ContractError(f"{where}.trajectory_available 必须是布尔值")
67
+ if "score_scale" in r and not isinstance(r["score_scale"], dict):
68
+ raise ContractError(f"{where}.score_scale 必须是对象 {{range, unit, verifier}}")
69
+ if "repetition_count" in r and (not _is_int(r["repetition_count"]) or r["repetition_count"] < 1):
70
+ raise ContractError(f"{where}.repetition_count 必须是 >=1 的整数")
71
+
72
+
73
+ def main(argv=None) -> int:
74
+ p = argparse.ArgumentParser(
75
+ prog="python -m agent_comparability",
76
+ description="离线评测记录 JSON → 结构化 CDR(Comparison Decision Record)")
77
+ p.add_argument("input", help="输入 JSON 文件路径(契约见模块文档)")
78
+ p.add_argument("--claim", required=True,
79
+ help="请求结论类型:score_comparison / causal / descriptive_registration"
80
+ "(未支持类型 → INCONCLUSIVE,不猜测)")
81
+ p.add_argument("-o", "--output", help="CDR 输出文件路径(缺省打印到 stdout)")
82
+ p.add_argument("--manifest", help="同时输出 Evidence Manifest(#18):输入文件作为证据制品"
83
+ "实算 sha256,全部记录 artifact_observed,不含任何独立验证声明")
84
+ args = p.parse_args(argv)
85
+
86
+ try:
87
+ raw = open(args.input, encoding="utf-8").read()
88
+ except OSError as e:
89
+ print(f"error: 无法读取输入文件 {args.input}: {e.strerror or e}", file=sys.stderr)
90
+ return 2
91
+ try:
92
+ case = json.loads(raw)
93
+ except json.JSONDecodeError as e:
94
+ print(f"error: JSON 解析错误 {args.input}(第 {e.lineno} 行第 {e.colno} 列): {e.msg}", file=sys.stderr)
95
+ return 2
96
+ try:
97
+ validate_case(case)
98
+ except ContractError as e:
99
+ print(f"error: 输入违反契约 {args.input}: {e}", file=sys.stderr)
100
+ return 2
101
+
102
+ cdr = adjudicate(case, args.claim).to_dict()
103
+ out = json.dumps(cdr, ensure_ascii=False, indent=2)
104
+ if args.output:
105
+ try:
106
+ with open(args.output, "w", encoding="utf-8") as f:
107
+ f.write(out + "\n")
108
+ except OSError as e:
109
+ print(f"error: 无法写入输出文件 {args.output}: {e.strerror or e}", file=sys.stderr)
110
+ return 2
111
+ else:
112
+ print(out)
113
+ if args.manifest:
114
+ from .evidence import build_manifest
115
+ # 离线 JSON 的诚实默认:输入文件即证据制品(读后实算摘要);无来源端验证能力,
116
+ # 无独立验证声明——绝不把 source_claimed 冒充为已验证。
117
+ m = build_manifest(case, source_kind="offline_json",
118
+ artifacts={i: args.input for i in range(len(case["records"]))},
119
+ requested_claim=args.claim,
120
+ unsupported_capabilities=["source_side_verification_unavailable"])
121
+ try:
122
+ with open(args.manifest, "w", encoding="utf-8") as f:
123
+ f.write(json.dumps(m, ensure_ascii=False, indent=2) + "\n")
124
+ except OSError as e:
125
+ print(f"error: 无法写入 manifest 文件 {args.manifest}: {e.strerror or e}", file=sys.stderr)
126
+ return 2
127
+ return 0
128
+
129
+
130
+ if __name__ == "__main__":
131
+ sys.exit(main())
@@ -1,16 +1,20 @@
1
- """比较判定核心库 cmp/core(cmp/0.3.1 规范参考实现,第十九轮升级)。
1
+ """比较判定核心库 cmp/core(cmp/0.4.0 规范参考实现)。
2
2
 
3
+ 相对 0.3.1 的判定表分支修正(#27 裁决方向 B,SPEC §5 允许 minor 新增分支):
4
+ ⑤score_comparison 的 INADMISSIBLE 触发移除 same_model——单变量对称性
5
+ (§4 行 2:仅模型不同 → ADMISSIBLE 并排);模型-only 变化由
6
+ score_scale_consistent / zero_semantics_uniform 独立把关(D2 冻结对证明)。
3
7
  相对 0.2 的规范符合性修复(依据 SPEC 0.3.1;B 集冻结预期先于本修改存在):
4
8
  ①同任务性检查入 score 判定优先级 2(B3 暴露 0.2 缺失)
5
9
  ②评分尺度一致性检查(含 0.3.1 隐式默认规则)(B4/B5 暴露 0.2 缺失)
6
10
  ③claim 路由补 registration/descriptive 关键词(B2-B5 暴露误判)
7
11
  ④policy_version 升 cmp/0.3.1(消费方按 SPEC §5 重判旧 CDR)
8
- 判定优先级(SPEC §3.3):INCONCLUSIVE(缺trace) > INADMISSIBLE(任务/模型/混淆/尺度) > PARTIAL(零语义) > ADMISSIBLE。
12
+ 判定优先级(SPEC §3.3):INCONCLUSIVE(缺trace) > INADMISSIBLE(任务/混淆/尺度) > PARTIAL(零语义) > ADMISSIBLE。
9
13
  """
10
14
 
11
15
  from dataclasses import dataclass, field, asdict
12
16
 
13
- POLICY_VERSION = "cmp/0.3.1"
17
+ POLICY_VERSION = "cmp/0.4.0"
14
18
  TAMPER_LIMITATION = "摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
15
19
  DEFAULT_SCALE = {"range": "0..1", "unit": "fraction", "verifier": "source"}
16
20
  KNOWN_FIELDS = {"task_id", "harness", "model", "reward", "reward_status",
@@ -92,9 +96,10 @@ def adjudicate(case: dict, requested_claim: str) -> CDR:
92
96
  cdr.admissibility = "INCONCLUSIVE"
93
97
  cdr.supported_claims.append("score_transcription(source_verifier_attested)")
94
98
  cdr.unsupported_claims.append("success_rate(含证据缺失记录)")
95
- elif len(tasks) > 1 or len(models) > 1 or confounded or "score_scale_consistent" in cdr.violated_conditions:
99
+ elif len(tasks) > 1 or confounded or "score_scale_consistent" in cdr.violated_conditions:
100
+ # 0.4:same_model 移出硬触发(#27 单变量对称性);尺度/零语义条件独立把关
96
101
  cdr.admissibility = "INADMISSIBLE"
97
- cdr.unsupported_claims.append("score_comparison(任务/模型/尺度条件冲突)")
102
+ cdr.unsupported_claims.append("score_comparison(任务/混淆/尺度条件冲突)")
98
103
  elif any(v.startswith("zero_semantics_uniform") for v in cdr.violated_conditions):
99
104
  cdr.admissibility = "PARTIAL"
100
105
  cdr.supported_claims.append("per_status_stratified_rate(verified_zero/unresolved_zero 分列)")
@@ -1,4 +1,4 @@
1
- # Agent Evaluation Comparability Specification — cmp/0.3.1 (Release Candidate)
1
+ # Agent Evaluation Comparability Specification — cmp/0.4.0 (Release Candidate)
2
2
 
3
3
  规范状态:候选规范(非行业标准、无第三方认证)。本文是判定语义的单一事实来源;参考实现(`scripts/cmp/core.py`)与一切第三方实现均以本文为准,冲突时以本文裁决。可脱离任何源码阅读。
4
4
 
@@ -32,7 +32,7 @@
32
32
  | 类型 | 语义 |
33
33
  |---|---|
34
34
  | `causal` | 断言某变量(模型/Harness)**导致**了分数差异 |
35
- | `score_comparison` | 请求对连续分数做(并排或汇总)比较 |
35
+ | `score_comparison` | 请求对连续分数做(并排或汇总)比较。历史冻结案例集使用早期写法 `score_compare`;参考实现通过下划线归一化与关键词匹配接受两者(实现细节,非规范语义),新代码应一律使用 `score_comparison`。 |
36
36
  | `descriptive_registration` | 仅登记条件与分数,不作比较结论 |
37
37
  | 其他 | **未支持类型** |
38
38
 
@@ -67,7 +67,10 @@
67
67
 
68
68
  **`score_comparison`**(按优先级,先命中先裁决):
69
69
  1. 违反 `trace_available` → `INCONCLUSIVE`(来源评分转录保留于 `unknown_conditions`,禁止删除)
70
- 2. 违反 `single_variable_isolation` **或** `same_task_id` 或 `same_model` → `INADMISSIBLE`
70
+ 2. 违反 `single_variable_isolation` **或** `same_task_id` → `INADMISSIBLE`
71
+ (**cmp/0.4 修正(#27)**:`same_model` 不再单独触发 INADMISSIBLE——单变量对称性,与
72
+ §4 行 1/行 2 一致;模型-only 变化由 `score_scale_consistent`/`zero_semantics_uniform`
73
+ 独立把关。0.3.x 行为见版本记录。)
71
74
  3. 违反 `score_scale_consistent` → `INADMISSIBLE`
72
75
  4. 违反 `zero_semantics_uniform` → `PARTIAL`(支持按 verified/unresolved 分列的率;禁止合并 fail 率)
73
76
  5. 其余 → `ADMISSIBLE`(同尺度连续分数并排)
@@ -95,7 +98,7 @@
95
98
 
96
99
  - **未知字段**:Record 中规范未定义的字段**必须忽略**(不得报错、不得猜测),并在 `unknown_conditions` 记 `ignored_fields:[...]`。
97
100
  - **未支持 Claim**:见 §2 —— INCONCLUSIVE + `unrecognized_claim_type`,禁止近似匹配。
98
- - **Policy 版本**:CDR 必须携带 `policy_version`。消费方遇到 `policy_version` 不认识时**必须丢弃该 CDR 并重新判定**,不得部分信任。本规范为 `cmp/0.3.1`(0.3 的澄清版:仅补隐式默认尺度规则,判定表未变)。
101
+ - **Policy 版本**:CDR 必须携带 `policy_version`。消费方遇到 `policy_version` 不认识时**必须丢弃该 CDR 并重新判定**,不得部分信任。本规范为 `cmp/0.4.0`(0.3→0.4:判定表分支修正——score_comparison 的 INADMISSIBLE 触发移除 `same_model`,实现单变量对称性,裁决记录见 #27;D 冻结对(D1/D2)先于实现冻结)。0.3.1 为 0.3 的澄清版(仅补隐式默认尺度规则)。
99
102
  - **规范不兼容变更**:升 minor(0.3→0.4)可新增判定分支;改判定表必须升 major 并迁移案例。
100
103
 
101
104
  ---
@@ -0,0 +1,16 @@
1
+ {
2
+ "policy_version": "cmp/0.4.0",
3
+ "frozen_note": "D 集(#27 裁决方向 B,先于实现冻结):单变量对称性——模型-only 变化遵循 SPEC §4 行 2。A/B 集与既有预期零改动;D2 证明尺度条件独立把关未被放松。",
4
+ "cases": [
5
+ {"id": "D1", "category": "model-only-variation",
6
+ "records": [
7
+ {"task_id": "t-model-only", "harness": "h1", "model": "model-a", "reward": 0.8, "reward_status": "verified"},
8
+ {"task_id": "t-model-only", "harness": "h1", "model": "model-b", "reward": 0.6, "reward_status": "verified"}],
9
+ "claims": {"score_comparison": "ADMISSIBLE", "causal": "INADMISSIBLE"}},
10
+ {"id": "D2", "category": "model-plus-scale-violation",
11
+ "records": [
12
+ {"task_id": "t-model-scale", "harness": "h1", "model": "model-a", "reward": 0.8, "reward_status": "verified"},
13
+ {"task_id": "t-model-scale", "harness": "h1", "model": "model-b", "reward": 62.0, "reward_status": "verified"}],
14
+ "claims": {"score_comparison": "INADMISSIBLE", "descriptive_registration": "ADMISSIBLE"}}
15
+ ]
16
+ }
@@ -74,7 +74,7 @@
74
74
  },
75
75
  "policy_version": {
76
76
  "type": "string",
77
- "pattern": "^cmp/0\\.3(\\.1)?$"
77
+ "pattern": "^cmp/0\\.[34](\\.\\d+)?$"
78
78
  },
79
79
  "limitations": {
80
80
  "type": "array",
@@ -0,0 +1,109 @@
1
+ """Evidence Manifest(#18)——CDR 外部的来源与证据可信边界。
2
+
3
+ 三态(**证据制品级**,与 SPEC §1 的记录级评分信任四层是不同维度,绝不进入
4
+ adjudicate、不改变任何 CDR 字段或判定结果):
5
+
6
+ source_claimed < artifact_observed < independently_verified
7
+
8
+ 升级只能由显式证据触发:
9
+ - artifact_observed ⟺ 制品路径存在且被实际读取且 sha256 已实算记录;
10
+ - independently_verified ⟺ 在 artifact_observed 之上存在显式 verification 条目
11
+ (method/operator/at 三者齐备);
12
+ - 无制品 → 地板 source_claimed + 警告;有 verification 无制品 → 不一致,不升级。
13
+ 不存在任何隐式跃迁(防"证据不足被包装成可信结论")。
14
+ """
15
+ import hashlib
16
+
17
+ from . import POLICY_VERSION
18
+
19
+ MANIFEST_VERSION = "evidence/0.1"
20
+ STATES = ("source_claimed", "artifact_observed", "independently_verified")
21
+
22
+
23
+ def _digest(path):
24
+ h = hashlib.sha256()
25
+ try:
26
+ with open(path, "rb") as f:
27
+ for chunk in iter(lambda: f.read(65536), b""):
28
+ h.update(chunk)
29
+ except OSError:
30
+ return None
31
+ return "sha256:" + h.hexdigest()
32
+
33
+
34
+ def build_manifest(case, source_kind, source_version=None, artifacts=None,
35
+ verifications=None, requested_claim=None,
36
+ unsupported_capabilities=None):
37
+ """构建 manifest。artifacts: {record_index: 制品路径};verifications: {record_index:
38
+ {method, operator, at}}。状态只由提供的证据决定——本函数绝不主动升级。"""
39
+ artifacts = artifacts or {}
40
+ verifications = verifications or {}
41
+ warnings = []
42
+ records = []
43
+ for i, r in enumerate(case["records"]):
44
+ state, digest, art_ref, reason = "source_claimed", None, None, None
45
+ if i in artifacts:
46
+ art_ref = artifacts[i]
47
+ digest = _digest(art_ref)
48
+ if digest is None:
49
+ warnings.append(f"records[{i}]:artifact_missing({art_ref} 不可读——保持 source_claimed)")
50
+ reason = f"artifact_unreadable:{art_ref}"
51
+ else:
52
+ state = "artifact_observed"
53
+ else:
54
+ warnings.append(f"records[{i}]:artifact_missing(未提供制品——保持 source_claimed)")
55
+ reason = "no_artifact_provided"
56
+ verification = None
57
+ if i in verifications:
58
+ v = verifications[i]
59
+ valid = isinstance(v, dict) and {"method", "operator", "at"} <= set(v)
60
+ if state != "artifact_observed":
61
+ warnings.append(f"records[{i}]:verification_without_artifact("
62
+ f"无法独立验证从未观察过的制品——不升级)")
63
+ elif not valid:
64
+ warnings.append(f"records[{i}]:verification_entry_invalid(需 method/operator/at)")
65
+ else:
66
+ state = "independently_verified"
67
+ verification = {"method": v["method"], "operator": v["operator"], "at": v["at"]}
68
+ records.append({
69
+ "record_id": f"records[{i}]",
70
+ "task_identity": {"task_id": r["task_id"], "model": r["model"], "harness": r["harness"]},
71
+ "artifact_ref": art_ref,
72
+ "file_digest": digest,
73
+ "verification_state": state,
74
+ "verification": verification,
75
+ "unverified_reason": reason,
76
+ "provenance_note": (f"state={state}; artifact="
77
+ f"{'read+sha256' if digest else 'none'}; source={source_kind}"),
78
+ })
79
+ return {
80
+ "manifest_version": MANIFEST_VERSION,
81
+ "policy_version": POLICY_VERSION, # 引用锚定:标识判定所用规范版本,不参与判定
82
+ "source_kind": source_kind,
83
+ "source_version": source_version, # None = 不可取得(诚实记录,不猜测)
84
+ "case_id": case["id"],
85
+ "requested_claim": requested_claim,
86
+ "records": records,
87
+ "mapping_warnings": warnings,
88
+ "unsupported_capabilities": unsupported_capabilities or [],
89
+ }
90
+
91
+
92
+ def verify_manifest(manifest):
93
+ """重算校验(供审计与后续转换器复用):重读 artifact 实算摘要比对记录值。
94
+ 返回问题列表;空列表 = 干净。只验证已声明观察过的制品——不产生新状态。"""
95
+ problems = []
96
+ for e in manifest.get("records", []):
97
+ rid, ref, dig = e.get("record_id"), e.get("artifact_ref"), e.get("file_digest")
98
+ if e.get("verification_state") in ("artifact_observed", "independently_verified"):
99
+ if not ref or not dig:
100
+ problems.append(f"{rid}:structural(声明已观察但缺 artifact_ref/file_digest)")
101
+ continue
102
+ now = _digest(ref)
103
+ if now is None:
104
+ problems.append(f"{rid}:artifact_missing_on_reverify({ref})")
105
+ elif now != dig:
106
+ problems.append(f"{rid}:digest_mismatch({ref})")
107
+ if e.get("verification_state") == "independently_verified" and not e.get("verification"):
108
+ problems.append(f"{rid}:structural(verified 态缺 verification 条目)")
109
+ return problems