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.
Files changed (30) hide show
  1. {claimproofkit-0.1.0/claimproofkit.egg-info → claimproofkit-0.1.1}/PKG-INFO +25 -14
  2. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/README.md +24 -13
  3. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/cli.py +93 -17
  4. {claimproofkit-0.1.0 → claimproofkit-0.1.1/claimproofkit.egg-info}/PKG-INFO +25 -14
  5. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/SOURCES.txt +1 -0
  6. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/pyproject.toml +1 -1
  7. claimproofkit-0.1.1/tests/test_cli_folder.py +98 -0
  8. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/LICENSE +0 -0
  9. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/NOTICE +0 -0
  10. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/__init__.py +0 -0
  11. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/_text.py +0 -0
  12. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/benchmark.py +0 -0
  13. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/extractor.py +0 -0
  14. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/identity.py +0 -0
  15. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/models.py +0 -0
  16. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/pipeline.py +0 -0
  17. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/provenance.py +0 -0
  18. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/py.typed +0 -0
  19. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/report.py +0 -0
  20. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit/verifier.py +0 -0
  21. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/dependency_links.txt +0 -0
  22. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/entry_points.txt +0 -0
  23. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/requires.txt +0 -0
  24. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/claimproofkit.egg-info/top_level.txt +0 -0
  25. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/setup.cfg +0 -0
  26. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_benchmark_metrics.py +0 -0
  27. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_conformance.py +0 -0
  28. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_identity_and_bridge.py +0 -0
  29. {claimproofkit-0.1.0 → claimproofkit-0.1.1}/tests/test_swift_parity_regressions.py +0 -0
  30. {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.0
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 `conformance/vectors`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
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
- ```sh
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
- ```python
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 exposes the same command 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):
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
- ```sh
75
- claimproofkit verify input.json [--provenance trace.json]
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
- `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.
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
- ```python
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 ([`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.
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 `conformance/vectors`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
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
- ```sh
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
- ```python
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 exposes the same command 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):
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
- ```sh
47
- claimproofkit verify input.json [--provenance trace.json]
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
- `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.
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
- ```python
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 ([`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.
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
- """``claimproofkit`` CLI — mirrors the Swift executable's commands and exit codes.
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
- with open(path, encoding="utf-8") as handle:
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
- return _run_verification(command, None)
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.0
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 `conformance/vectors`. Fingerprints, verifier verdicts, and canonical payload bytes are checked across both implementations.
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
- ```sh
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
- ```python
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 exposes the same command 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):
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
- ```sh
75
- claimproofkit verify input.json [--provenance trace.json]
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
- `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.
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
- ```python
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 ([`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.
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.0"
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