claimproofkit 0.1.0__tar.gz → 0.1.1__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.
- {claimproofkit-0.1.0/claimproofkit.egg-info → claimproofkit-0.1.1}/PKG-INFO +25 -14
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/README.md +24 -13
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/cli.py +93 -17
- {claimproofkit-0.1.0 → claimproofkit-0.1.1/claimproofkit.egg-info}/PKG-INFO +25 -14
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/SOURCES.txt +1 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/pyproject.toml +1 -1
- claimproofkit-0.1.1/tests/test_cli_folder.py +98 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/LICENSE +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/NOTICE +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/__init__.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/_text.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/benchmark.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/extractor.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/identity.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/models.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/pipeline.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/provenance.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/py.typed +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/report.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/verifier.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/dependency_links.txt +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/entry_points.txt +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/requires.txt +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/top_level.txt +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/setup.cfg +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_benchmark_metrics.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_conformance.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_identity_and_bridge.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_swift_parity_regressions.py +0 -0
- {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_verifier.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claimproofkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Apache-2.0 licensed, evidence-bound claim verification that blocks generated documents whose material claims the supplied evidence does not support. Python implementation of Claim Proof Specification v1, in locked parity with the Swift reference implementation.
|
|
5
5
|
Author: ClaimProofKit
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
|
|
33
33
|
ClaimProofKit implements **Claim Proof Specification v1**: it checks whether the material claims in a generated document are supported by supplied evidence, and blocks publication when claims are unsupported or when no material claims can be extracted. It uses only the Python standard library at runtime.
|
|
34
34
|
|
|
35
|
-
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under
|
|
35
|
+
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under \`conformance/vectors\`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
|
|
36
36
|
|
|
37
37
|
## License
|
|
38
38
|
|
|
@@ -42,17 +42,17 @@ The Apache-2.0 license applies to the code in this repository; it does not grant
|
|
|
42
42
|
|
|
43
43
|
## Install and test
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
\`\`\`sh
|
|
46
46
|
pip install -e ".[dev]"
|
|
47
47
|
pytest
|
|
48
48
|
python conformance/run_conformance.py
|
|
49
|
-
|
|
49
|
+
\`\`\`
|
|
50
50
|
|
|
51
51
|
## Verify a document
|
|
52
52
|
|
|
53
53
|
A document with no extractable material claims is **not** considered safe to publish. A successful verification gate requires at least one claim, and every extracted claim must have an approved verdict. This avoids treating an empty or unrecognized document as verified by vacuous success.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
\`\`\`python
|
|
56
56
|
from claimproofkit import ClaimVerifier, EvidencePassage, SourceType
|
|
57
57
|
|
|
58
58
|
evidence = [
|
|
@@ -67,29 +67,40 @@ report = ClaimVerifier().verify_document(
|
|
|
67
67
|
"The court denied the request for an extension.", evidence
|
|
68
68
|
)
|
|
69
69
|
assert report.is_safe_to_publish
|
|
70
|
-
|
|
70
|
+
\`\`\`
|
|
71
71
|
|
|
72
|
-
The CLI
|
|
72
|
+
The CLI accepts one input JSON file or a folder of input JSON files. Each JSON file must contain the usual \`document\` string and \`evidence\` array. Folder mode checks top-level \`*.json\` files in filename order, prints a report for every file, and returns a failing exit code if any document is blocked or unreadable. It does not recursively scan subfolders.
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
\`\`\`sh
|
|
75
|
+
# Verify one document
|
|
76
|
+
claimproofkit verify input.json
|
|
77
|
+
|
|
78
|
+
# Verify every JSON document directly inside ./documents
|
|
79
|
+
claimproofkit verify ./documents
|
|
80
|
+
|
|
81
|
+
# Optional: write one provenance trace per document into ./traces
|
|
82
|
+
claimproofkit verify ./documents --provenance ./traces
|
|
83
|
+
|
|
84
|
+
# Benchmark a corpus
|
|
76
85
|
claimproofkit benchmark corpus.json [--json]
|
|
77
|
-
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
The CLI exposes the same core verification semantics and exit codes as the Swift binary (0 published, 2 blocked, 3 benchmark gate failed, 64 usage, 65 unreadable input, 73 trace write failed). In folder mode, exit code 0 means **every** discovered JSON document passed; 2 means at least one document was blocked; 65 means at least one document could not be read or parsed; 73 means a requested provenance trace could not be written.
|
|
78
89
|
|
|
79
90
|
## Working with DProvenanceKit
|
|
80
91
|
|
|
81
|
-
|
|
92
|
+
\`claimproofkit.provenance\` exports a verification run as a DProvenanceKit-compatible trace, identical to the Swift bridge's output: stable \`cpk_*\` event vocabulary, canonical payload bytes (Trace Specification v1 §2), lineage edges, and the §5 run fingerprint.
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
\`\`\`python
|
|
84
95
|
from claimproofkit import ProvenanceExporter, VerificationPolicy
|
|
85
96
|
|
|
86
97
|
trace = ProvenanceExporter().trace(report, VerificationPolicy())
|
|
87
98
|
trace.events_json() # decodes as [TraceEvent<AnyTraceableEvent>] in Swift DProvenanceKit
|
|
88
99
|
trace.manifest_json() # full manifest with fingerprints and edges
|
|
89
|
-
|
|
100
|
+
\`\`\`
|
|
90
101
|
|
|
91
102
|
The conformance runner proves the fingerprint against the real Python [dprovenancekit](https://github.com/Therealdk8890/DProvenanceKitPython) package when it is importable: recording the trace through its SQLite store reproduces the bridge's run fingerprint byte-for-byte. The same trace fingerprints identically in Swift ClaimProofKit, Swift DProvenanceKit, and Python dprovenancekit.
|
|
92
103
|
|
|
93
104
|
## Changing locked behavior
|
|
94
105
|
|
|
95
|
-
Specification changes originate in the Swift repo ([
|
|
106
|
+
Specification changes originate in the Swift repo ([\`Docs/PROOF_SPEC_V1.md\`](https://github.com/Therealdk8890/ClaimProofKit/blob/main/Docs/PROOF_SPEC_V1.md)): bump the spec/verifier version there, regenerate vectors, then re-sync here with \`conformance/sync-vectors.sh\` and update this implementation to conform.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
ClaimProofKit implements **Claim Proof Specification v1**: it checks whether the material claims in a generated document are supported by supplied evidence, and blocks publication when claims are unsupported or when no material claims can be extracted. It uses only the Python standard library at runtime.
|
|
6
6
|
|
|
7
|
-
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under
|
|
7
|
+
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under \`conformance/vectors\`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
|
|
8
8
|
|
|
9
9
|
## License
|
|
10
10
|
|
|
@@ -14,17 +14,17 @@ The Apache-2.0 license applies to the code in this repository; it does not grant
|
|
|
14
14
|
|
|
15
15
|
## Install and test
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
\`\`\`sh
|
|
18
18
|
pip install -e ".[dev]"
|
|
19
19
|
pytest
|
|
20
20
|
python conformance/run_conformance.py
|
|
21
|
-
|
|
21
|
+
\`\`\`
|
|
22
22
|
|
|
23
23
|
## Verify a document
|
|
24
24
|
|
|
25
25
|
A document with no extractable material claims is **not** considered safe to publish. A successful verification gate requires at least one claim, and every extracted claim must have an approved verdict. This avoids treating an empty or unrecognized document as verified by vacuous success.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
\`\`\`python
|
|
28
28
|
from claimproofkit import ClaimVerifier, EvidencePassage, SourceType
|
|
29
29
|
|
|
30
30
|
evidence = [
|
|
@@ -39,29 +39,40 @@ report = ClaimVerifier().verify_document(
|
|
|
39
39
|
"The court denied the request for an extension.", evidence
|
|
40
40
|
)
|
|
41
41
|
assert report.is_safe_to_publish
|
|
42
|
-
|
|
42
|
+
\`\`\`
|
|
43
43
|
|
|
44
|
-
The CLI
|
|
44
|
+
The CLI accepts one input JSON file or a folder of input JSON files. Each JSON file must contain the usual \`document\` string and \`evidence\` array. Folder mode checks top-level \`*.json\` files in filename order, prints a report for every file, and returns a failing exit code if any document is blocked or unreadable. It does not recursively scan subfolders.
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
\`\`\`sh
|
|
47
|
+
# Verify one document
|
|
48
|
+
claimproofkit verify input.json
|
|
49
|
+
|
|
50
|
+
# Verify every JSON document directly inside ./documents
|
|
51
|
+
claimproofkit verify ./documents
|
|
52
|
+
|
|
53
|
+
# Optional: write one provenance trace per document into ./traces
|
|
54
|
+
claimproofkit verify ./documents --provenance ./traces
|
|
55
|
+
|
|
56
|
+
# Benchmark a corpus
|
|
48
57
|
claimproofkit benchmark corpus.json [--json]
|
|
49
|
-
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
The CLI exposes the same core verification semantics and exit codes as the Swift binary (0 published, 2 blocked, 3 benchmark gate failed, 64 usage, 65 unreadable input, 73 trace write failed). In folder mode, exit code 0 means **every** discovered JSON document passed; 2 means at least one document was blocked; 65 means at least one document could not be read or parsed; 73 means a requested provenance trace could not be written.
|
|
50
61
|
|
|
51
62
|
## Working with DProvenanceKit
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
\`claimproofkit.provenance\` exports a verification run as a DProvenanceKit-compatible trace, identical to the Swift bridge's output: stable \`cpk_*\` event vocabulary, canonical payload bytes (Trace Specification v1 §2), lineage edges, and the §5 run fingerprint.
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
\`\`\`python
|
|
56
67
|
from claimproofkit import ProvenanceExporter, VerificationPolicy
|
|
57
68
|
|
|
58
69
|
trace = ProvenanceExporter().trace(report, VerificationPolicy())
|
|
59
70
|
trace.events_json() # decodes as [TraceEvent<AnyTraceableEvent>] in Swift DProvenanceKit
|
|
60
71
|
trace.manifest_json() # full manifest with fingerprints and edges
|
|
61
|
-
|
|
72
|
+
\`\`\`
|
|
62
73
|
|
|
63
74
|
The conformance runner proves the fingerprint against the real Python [dprovenancekit](https://github.com/Therealdk8890/DProvenanceKitPython) package when it is importable: recording the trace through its SQLite store reproduces the bridge's run fingerprint byte-for-byte. The same trace fingerprints identically in Swift ClaimProofKit, Swift DProvenanceKit, and Python dprovenancekit.
|
|
64
75
|
|
|
65
76
|
## Changing locked behavior
|
|
66
77
|
|
|
67
|
-
Specification changes originate in the Swift repo ([
|
|
78
|
+
Specification changes originate in the Swift repo ([\`Docs/PROOF_SPEC_V1.md\`](https://github.com/Therealdk8890/ClaimProofKit/blob/main/Docs/PROOF_SPEC_V1.md)): bump the spec/verifier version there, regenerate vectors, then re-sync here with \`conformance/sync-vectors.sh\` and update this implementation to conform.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright 2026 Daniel Kissel
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
"""
|
|
4
|
+
"""claimproofkit CLI — mirrors the Swift executable's commands and exit codes.
|
|
5
5
|
|
|
6
6
|
Exit codes: 0 published / gate passed, 2 blocked, 3 benchmark gate failed,
|
|
7
7
|
64 usage, 65 unreadable input, 73 provenance trace write failed.
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import json
|
|
13
13
|
import sys
|
|
14
14
|
import time
|
|
15
|
+
from pathlib import Path
|
|
15
16
|
from typing import List, Optional
|
|
16
17
|
|
|
17
18
|
from .benchmark import BenchmarkCorpus, BenchmarkRunner
|
|
@@ -21,9 +22,15 @@ from .report import benchmark_report_json, render_benchmark_markdown, render_rep
|
|
|
21
22
|
from .verifier import ClaimVerifier
|
|
22
23
|
|
|
23
24
|
USAGE = """Usage:
|
|
24
|
-
claimproofkit verify <input.json> [--provenance <trace.json>]
|
|
25
|
+
claimproofkit verify <input.json|folder> [--provenance <trace.json|trace-folder>]
|
|
25
26
|
claimproofkit benchmark <corpus.json> [--json]
|
|
26
27
|
|
|
28
|
+
When input is a folder, ClaimProofKit verifies each top-level *.json file in
|
|
29
|
+
filename order. Each JSON file must use the normal single-document input
|
|
30
|
+
schema. All files are processed; the command fails closed if any file is
|
|
31
|
+
unreadable or any report is blocked. With folder input, --provenance names an
|
|
32
|
+
output folder and each trace is written as <input-stem>.trace.json.
|
|
33
|
+
|
|
27
34
|
--provenance writes a DProvenanceKit-compatible trace manifest of the
|
|
28
35
|
verification run alongside the report.
|
|
29
36
|
|
|
@@ -39,23 +46,28 @@ def _usage(code: int) -> int:
|
|
|
39
46
|
return code
|
|
40
47
|
|
|
41
48
|
|
|
49
|
+
def _read_and_verify(path: str):
|
|
50
|
+
with open(path, encoding="utf-8") as handle:
|
|
51
|
+
data = json.load(handle)
|
|
52
|
+
evidence = [
|
|
53
|
+
EvidencePassage(
|
|
54
|
+
id=p["id"],
|
|
55
|
+
source=p["source"],
|
|
56
|
+
source_type=SourceType(p["sourceType"]),
|
|
57
|
+
text=p["text"],
|
|
58
|
+
)
|
|
59
|
+
for p in data["evidence"]
|
|
60
|
+
]
|
|
61
|
+
policy = VerificationPolicy()
|
|
62
|
+
report = ClaimVerifier(policy).verify_document(data["document"], evidence, created_at=time.time())
|
|
63
|
+
return report, policy
|
|
64
|
+
|
|
65
|
+
|
|
42
66
|
def _run_verification(path: str, provenance_path: Optional[str]) -> int:
|
|
43
67
|
try:
|
|
44
|
-
|
|
45
|
-
data = json.load(handle)
|
|
46
|
-
evidence = [
|
|
47
|
-
EvidencePassage(
|
|
48
|
-
id=p["id"],
|
|
49
|
-
source=p["source"],
|
|
50
|
-
source_type=SourceType(p["sourceType"]),
|
|
51
|
-
text=p["text"],
|
|
52
|
-
)
|
|
53
|
-
for p in data["evidence"]
|
|
54
|
-
]
|
|
55
|
-
policy = VerificationPolicy()
|
|
56
|
-
report = ClaimVerifier(policy).verify_document(data["document"], evidence, created_at=time.time())
|
|
68
|
+
report, policy = _read_and_verify(path)
|
|
57
69
|
except Exception as error: # noqa: BLE001 - CLI boundary
|
|
58
|
-
sys.stderr.write(f"claimproofkit: {error}\n")
|
|
70
|
+
sys.stderr.write(f"claimproofkit: failed to verify {path}: {error}\n")
|
|
59
71
|
return 65
|
|
60
72
|
|
|
61
73
|
# The report is always emitted, even if the trace write fails afterwards.
|
|
@@ -74,6 +86,65 @@ def _run_verification(path: str, provenance_path: Optional[str]) -> int:
|
|
|
74
86
|
return 0 if report.is_safe_to_publish else 2
|
|
75
87
|
|
|
76
88
|
|
|
89
|
+
def _run_verification_folder(folder_path: str, provenance_path: Optional[str]) -> int:
|
|
90
|
+
folder = Path(folder_path)
|
|
91
|
+
try:
|
|
92
|
+
if not folder.is_dir():
|
|
93
|
+
raise NotADirectoryError(f"not a directory: {folder}")
|
|
94
|
+
# Snapshot inputs before writing any provenance traces.
|
|
95
|
+
inputs = sorted(
|
|
96
|
+
(entry for entry in folder.iterdir() if entry.is_file() and entry.suffix == ".json"),
|
|
97
|
+
key=lambda entry: entry.name,
|
|
98
|
+
)
|
|
99
|
+
except Exception as error: # noqa: BLE001 - CLI boundary
|
|
100
|
+
sys.stderr.write(f"claimproofkit: cannot read input folder {folder}: {error}\n")
|
|
101
|
+
return 65
|
|
102
|
+
|
|
103
|
+
if not inputs:
|
|
104
|
+
sys.stderr.write(f"claimproofkit: no top-level .json documents found in {folder}\n")
|
|
105
|
+
return 65
|
|
106
|
+
|
|
107
|
+
trace_folder: Optional[Path] = None
|
|
108
|
+
if provenance_path is not None:
|
|
109
|
+
trace_folder = Path(provenance_path)
|
|
110
|
+
try:
|
|
111
|
+
if trace_folder.resolve() == folder.resolve():
|
|
112
|
+
sys.stderr.write("claimproofkit: provenance output folder must differ from the input folder\n")
|
|
113
|
+
return 64
|
|
114
|
+
trace_folder.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
if not trace_folder.is_dir():
|
|
116
|
+
raise NotADirectoryError(str(trace_folder))
|
|
117
|
+
except Exception as error: # noqa: BLE001 - CLI boundary
|
|
118
|
+
sys.stderr.write(f"claimproofkit: cannot prepare provenance output folder {trace_folder}: {error}\n")
|
|
119
|
+
return 73
|
|
120
|
+
|
|
121
|
+
results = []
|
|
122
|
+
for path in inputs:
|
|
123
|
+
print(f"\n## Document: {path.name}\n")
|
|
124
|
+
trace_path = str(trace_folder / f"{path.stem}.trace.json") if trace_folder is not None else None
|
|
125
|
+
results.append((path.name, _run_verification(str(path), trace_path)))
|
|
126
|
+
|
|
127
|
+
passed = sum(code == 0 for _, code in results)
|
|
128
|
+
blocked = sum(code == 2 for _, code in results)
|
|
129
|
+
unreadable = sum(code == 65 for _, code in results)
|
|
130
|
+
trace_errors = sum(code == 73 for _, code in results)
|
|
131
|
+
print(
|
|
132
|
+
f"\n## Folder summary\n\n"
|
|
133
|
+
f"Documents: {len(results)} · Safe to publish: {passed} · "
|
|
134
|
+
f"Blocked: {blocked} · Unreadable: {unreadable} · Trace errors: {trace_errors}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# A folder is successful only when every document passes and every requested
|
|
138
|
+
# trace is written. Input errors and trace errors must not be hidden by a pass.
|
|
139
|
+
if trace_errors:
|
|
140
|
+
return 73
|
|
141
|
+
if unreadable:
|
|
142
|
+
return 65
|
|
143
|
+
if blocked:
|
|
144
|
+
return 2
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
77
148
|
def _run_benchmark(path: str, as_json: bool) -> int:
|
|
78
149
|
try:
|
|
79
150
|
with open(path, encoding="utf-8") as handle:
|
|
@@ -118,6 +189,8 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
118
189
|
return _usage(64)
|
|
119
190
|
if path is None:
|
|
120
191
|
return _usage(64)
|
|
192
|
+
if Path(path).is_dir():
|
|
193
|
+
return _run_verification_folder(path, provenance)
|
|
121
194
|
return _run_verification(path, provenance)
|
|
122
195
|
if command == "benchmark":
|
|
123
196
|
rest = arguments[1:]
|
|
@@ -128,7 +201,10 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
128
201
|
return _usage(64)
|
|
129
202
|
if len(arguments) == 1:
|
|
130
203
|
# Backward compatibility: a lone path runs verification.
|
|
131
|
-
|
|
204
|
+
path = arguments[0]
|
|
205
|
+
if Path(path).is_dir():
|
|
206
|
+
return _run_verification_folder(path, None)
|
|
207
|
+
return _run_verification(path, None)
|
|
132
208
|
return _usage(64)
|
|
133
209
|
|
|
134
210
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claimproofkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Apache-2.0 licensed, evidence-bound claim verification that blocks generated documents whose material claims the supplied evidence does not support. Python implementation of Claim Proof Specification v1, in locked parity with the Swift reference implementation.
|
|
5
5
|
Author: ClaimProofKit
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
|
|
33
33
|
ClaimProofKit implements **Claim Proof Specification v1**: it checks whether the material claims in a generated document are supported by supplied evidence, and blocks publication when claims are unsupported or when no material claims can be extracted. It uses only the Python standard library at runtime.
|
|
34
34
|
|
|
35
|
-
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under
|
|
35
|
+
The Swift [ClaimProofKit](https://github.com/Therealdk8890/ClaimProofKit) is the reference implementation. This Python implementation is held in **locked parity** with it through the golden vectors under \`conformance/vectors\`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
|
|
36
36
|
|
|
37
37
|
## License
|
|
38
38
|
|
|
@@ -42,17 +42,17 @@ The Apache-2.0 license applies to the code in this repository; it does not grant
|
|
|
42
42
|
|
|
43
43
|
## Install and test
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
\`\`\`sh
|
|
46
46
|
pip install -e ".[dev]"
|
|
47
47
|
pytest
|
|
48
48
|
python conformance/run_conformance.py
|
|
49
|
-
|
|
49
|
+
\`\`\`
|
|
50
50
|
|
|
51
51
|
## Verify a document
|
|
52
52
|
|
|
53
53
|
A document with no extractable material claims is **not** considered safe to publish. A successful verification gate requires at least one claim, and every extracted claim must have an approved verdict. This avoids treating an empty or unrecognized document as verified by vacuous success.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
\`\`\`python
|
|
56
56
|
from claimproofkit import ClaimVerifier, EvidencePassage, SourceType
|
|
57
57
|
|
|
58
58
|
evidence = [
|
|
@@ -67,29 +67,40 @@ report = ClaimVerifier().verify_document(
|
|
|
67
67
|
"The court denied the request for an extension.", evidence
|
|
68
68
|
)
|
|
69
69
|
assert report.is_safe_to_publish
|
|
70
|
-
|
|
70
|
+
\`\`\`
|
|
71
71
|
|
|
72
|
-
The CLI
|
|
72
|
+
The CLI accepts one input JSON file or a folder of input JSON files. Each JSON file must contain the usual \`document\` string and \`evidence\` array. Folder mode checks top-level \`*.json\` files in filename order, prints a report for every file, and returns a failing exit code if any document is blocked or unreadable. It does not recursively scan subfolders.
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
\`\`\`sh
|
|
75
|
+
# Verify one document
|
|
76
|
+
claimproofkit verify input.json
|
|
77
|
+
|
|
78
|
+
# Verify every JSON document directly inside ./documents
|
|
79
|
+
claimproofkit verify ./documents
|
|
80
|
+
|
|
81
|
+
# Optional: write one provenance trace per document into ./traces
|
|
82
|
+
claimproofkit verify ./documents --provenance ./traces
|
|
83
|
+
|
|
84
|
+
# Benchmark a corpus
|
|
76
85
|
claimproofkit benchmark corpus.json [--json]
|
|
77
|
-
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
The CLI exposes the same core verification semantics and exit codes as the Swift binary (0 published, 2 blocked, 3 benchmark gate failed, 64 usage, 65 unreadable input, 73 trace write failed). In folder mode, exit code 0 means **every** discovered JSON document passed; 2 means at least one document was blocked; 65 means at least one document could not be read or parsed; 73 means a requested provenance trace could not be written.
|
|
78
89
|
|
|
79
90
|
## Working with DProvenanceKit
|
|
80
91
|
|
|
81
|
-
|
|
92
|
+
\`claimproofkit.provenance\` exports a verification run as a DProvenanceKit-compatible trace, identical to the Swift bridge's output: stable \`cpk_*\` event vocabulary, canonical payload bytes (Trace Specification v1 §2), lineage edges, and the §5 run fingerprint.
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
\`\`\`python
|
|
84
95
|
from claimproofkit import ProvenanceExporter, VerificationPolicy
|
|
85
96
|
|
|
86
97
|
trace = ProvenanceExporter().trace(report, VerificationPolicy())
|
|
87
98
|
trace.events_json() # decodes as [TraceEvent<AnyTraceableEvent>] in Swift DProvenanceKit
|
|
88
99
|
trace.manifest_json() # full manifest with fingerprints and edges
|
|
89
|
-
|
|
100
|
+
\`\`\`
|
|
90
101
|
|
|
91
102
|
The conformance runner proves the fingerprint against the real Python [dprovenancekit](https://github.com/Therealdk8890/DProvenanceKitPython) package when it is importable: recording the trace through its SQLite store reproduces the bridge's run fingerprint byte-for-byte. The same trace fingerprints identically in Swift ClaimProofKit, Swift DProvenanceKit, and Python dprovenancekit.
|
|
92
103
|
|
|
93
104
|
## Changing locked behavior
|
|
94
105
|
|
|
95
|
-
Specification changes originate in the Swift repo ([
|
|
106
|
+
Specification changes originate in the Swift repo ([\`Docs/PROOF_SPEC_V1.md\`](https://github.com/Therealdk8890/ClaimProofKit/blob/main/Docs/PROOF_SPEC_V1.md)): bump the spec/verifier version there, regenerate vectors, then re-sync here with \`conformance/sync-vectors.sh\` and update this implementation to conform.
|
|
@@ -21,6 +21,7 @@ claimproofkit.egg-info/entry_points.txt
|
|
|
21
21
|
claimproofkit.egg-info/requires.txt
|
|
22
22
|
claimproofkit.egg-info/top_level.txt
|
|
23
23
|
tests/test_benchmark_metrics.py
|
|
24
|
+
tests/test_cli_folder.py
|
|
24
25
|
tests/test_conformance.py
|
|
25
26
|
tests/test_identity_and_bridge.py
|
|
26
27
|
tests/test_swift_parity_regressions.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claimproofkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "Apache-2.0 licensed, evidence-bound claim verification that blocks generated documents whose material claims the supplied evidence does not support. Python implementation of Claim Proof Specification v1, in locked parity with the Swift reference implementation."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright 2026 Daniel Kissel
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Folder-mode CLI tests."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _write_input(path: Path, document: str) -> None:
|
|
14
|
+
path.write_text(
|
|
15
|
+
json.dumps(
|
|
16
|
+
{
|
|
17
|
+
"document": document,
|
|
18
|
+
"evidence": [
|
|
19
|
+
{
|
|
20
|
+
"id": "order-1",
|
|
21
|
+
"source": "Scheduling Order",
|
|
22
|
+
"sourceType": "courtRecord",
|
|
23
|
+
"text": "The court denied the request for an extension.",
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
),
|
|
28
|
+
encoding="utf-8",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _run(folder: Path, *extra: str) -> subprocess.CompletedProcess:
|
|
33
|
+
env = dict(os.environ)
|
|
34
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
|
35
|
+
return subprocess.run(
|
|
36
|
+
[sys.executable, "-m", "claimproofkit.cli", "verify", str(folder), *extra],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
encoding="utf-8",
|
|
40
|
+
env=env,
|
|
41
|
+
check=False,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_folder_verification_processes_json_in_filename_order_and_passes(tmp_path: Path):
|
|
46
|
+
_write_input(tmp_path / "b.json", "The court denied the request for an extension.")
|
|
47
|
+
_write_input(tmp_path / "a.json", "The court denied the request for an extension.")
|
|
48
|
+
(tmp_path / "notes.txt").write_text("not an input", encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
result = _run(tmp_path)
|
|
51
|
+
|
|
52
|
+
assert result.returncode == 0
|
|
53
|
+
assert result.stdout.index("Document: a.json") < result.stdout.index("Document: b.json")
|
|
54
|
+
assert "Documents: 2 · Safe to publish: 2 · Blocked: 0" in result.stdout
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_folder_verification_fails_closed_when_any_document_is_blocked(tmp_path: Path):
|
|
58
|
+
_write_input(tmp_path / "supported.json", "The court denied the request for an extension.")
|
|
59
|
+
_write_input(tmp_path / "unsupported.json", "The plaintiff filed a second amended complaint.")
|
|
60
|
+
|
|
61
|
+
result = _run(tmp_path)
|
|
62
|
+
|
|
63
|
+
assert result.returncode == 2
|
|
64
|
+
assert "Safe to publish: 1 · Blocked: 1" in result.stdout
|
|
65
|
+
assert "Document: supported.json" in result.stdout
|
|
66
|
+
assert "Document: unsupported.json" in result.stdout
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_folder_verification_continues_after_unreadable_document(tmp_path: Path):
|
|
70
|
+
_write_input(tmp_path / "a-good.json", "The court denied the request for an extension.")
|
|
71
|
+
(tmp_path / "b-bad.json").write_text("{not json", encoding="utf-8")
|
|
72
|
+
_write_input(tmp_path / "c-good.json", "The court denied the request for an extension.")
|
|
73
|
+
|
|
74
|
+
result = _run(tmp_path)
|
|
75
|
+
|
|
76
|
+
assert result.returncode == 65
|
|
77
|
+
assert "Document: c-good.json" in result.stdout
|
|
78
|
+
assert "Unreadable: 1" in result.stdout
|
|
79
|
+
assert "failed to verify" in result.stderr
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_folder_verification_rejects_empty_folder(tmp_path: Path):
|
|
83
|
+
result = _run(tmp_path)
|
|
84
|
+
|
|
85
|
+
assert result.returncode == 65
|
|
86
|
+
assert "no top-level .json documents found" in result.stderr
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_folder_provenance_writes_one_trace_per_input(tmp_path: Path):
|
|
90
|
+
_write_input(tmp_path / "a.json", "The court denied the request for an extension.")
|
|
91
|
+
_write_input(tmp_path / "b.json", "The court denied the request for an extension.")
|
|
92
|
+
trace_folder = tmp_path / "traces"
|
|
93
|
+
|
|
94
|
+
result = _run(tmp_path, "--provenance", str(trace_folder))
|
|
95
|
+
|
|
96
|
+
assert result.returncode == 0
|
|
97
|
+
assert (trace_folder / "a.trace.json").is_file()
|
|
98
|
+
assert (trace_folder / "b.trace.json").is_file()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|