agent-comparability 0.5.2__tar.gz → 0.5.3__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.
- {agent_comparability-0.5.2/src/agent_comparability.egg-info → agent_comparability-0.5.3}/PKG-INFO +39 -2
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/README.md +34 -1
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/pyproject.toml +7 -1
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/__init__.py +1 -1
- agent_comparability-0.5.3/src/agent_comparability/__main__.py +131 -0
- agent_comparability-0.5.3/src/agent_comparability/evidence.py +109 -0
- agent_comparability-0.5.3/src/agent_comparability/inspect_log.py +283 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3/src/agent_comparability.egg-info}/PKG-INFO +39 -2
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability.egg-info/SOURCES.txt +7 -0
- agent_comparability-0.5.3/tests/test_cli.py +152 -0
- agent_comparability-0.5.3/tests/test_evidence.py +126 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/tests/test_gate_regressions.py +2 -2
- agent_comparability-0.5.3/tests/test_inspect_log.py +352 -0
- agent_comparability-0.5.3/tests/test_scenarios_001.py +54 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/LICENSE +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/NOTICE +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/setup.cfg +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/core.py +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/data/SPEC.md +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/data/boundary-cases.json +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/data/cdr.schema.json +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/data/frozen-cases-a.json +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability/py.typed +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability.egg-info/dependency_links.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability.egg-info/requires.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/src/agent_comparability.egg-info/top_level.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/tests/test_properties.py +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.3}/tests/test_smoke.py +0 -0
{agent_comparability-0.5.2/src/agent_comparability.egg-info → agent_comparability-0.5.3}/PKG-INFO
RENAMED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-comparability
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
[](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
|
|
26
30
|
[](LICENSE)
|
|
27
31
|
[](pyproject.toml)
|
|
32
|
+
[](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
|
|
|
@@ -281,6 +314,10 @@ 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
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](pyproject.toml)
|
|
10
|
+
[](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
|
|
|
@@ -263,6 +292,10 @@ 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
|
|
|
@@ -4,13 +4,19 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-comparability"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = {text = "Apache-2.0"}
|
|
12
12
|
dependencies = []
|
|
13
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
|
+
|
|
14
20
|
[project.optional-dependencies]
|
|
15
21
|
suite = ["pyyaml", "jsonschema"]
|
|
16
22
|
inspect = ["inspect-ai"]
|
|
@@ -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())
|
|
@@ -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
|