attestplane 0.0.3a0__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 (127) hide show
  1. attestplane-0.0.3a0/.gitignore +47 -0
  2. attestplane-0.0.3a0/PKG-INFO +229 -0
  3. attestplane-0.0.3a0/README.md +184 -0
  4. attestplane-0.0.3a0/pyproject.toml +191 -0
  5. attestplane-0.0.3a0/src/attestplane/__init__.py +312 -0
  6. attestplane-0.0.3a0/src/attestplane/adapter_conformance.py +248 -0
  7. attestplane-0.0.3a0/src/attestplane/adapters/__init__.py +40 -0
  8. attestplane-0.0.3a0/src/attestplane/adapters/aios_spec.py +125 -0
  9. attestplane-0.0.3a0/src/attestplane/adapters/base.py +162 -0
  10. attestplane-0.0.3a0/src/attestplane/adapters/langfuse.py +247 -0
  11. attestplane-0.0.3a0/src/attestplane/adapters/langsmith.py +247 -0
  12. attestplane-0.0.3a0/src/attestplane/anchoring/__init__.py +148 -0
  13. attestplane-0.0.3a0/src/attestplane/anchoring/base.py +255 -0
  14. attestplane-0.0.3a0/src/attestplane/anchoring/composite.py +106 -0
  15. attestplane-0.0.3a0/src/attestplane/anchoring/eidas.py +164 -0
  16. attestplane-0.0.3a0/src/attestplane/anchoring/http.py +337 -0
  17. attestplane-0.0.3a0/src/attestplane/anchoring/mock.py +100 -0
  18. attestplane-0.0.3a0/src/attestplane/anchoring/ocsp.py +239 -0
  19. attestplane-0.0.3a0/src/attestplane/anchoring/rfc3161.py +395 -0
  20. attestplane-0.0.3a0/src/attestplane/anchoring/sigstore.py +382 -0
  21. attestplane-0.0.3a0/src/attestplane/anchoring/testing.py +690 -0
  22. attestplane-0.0.3a0/src/attestplane/anchoring/verifier.py +428 -0
  23. attestplane-0.0.3a0/src/attestplane/anchoring/worker.py +282 -0
  24. attestplane-0.0.3a0/src/attestplane/canonical.py +177 -0
  25. attestplane-0.0.3a0/src/attestplane/canonical_text.py +124 -0
  26. attestplane-0.0.3a0/src/attestplane/cli/__init__.py +15 -0
  27. attestplane-0.0.3a0/src/attestplane/cli/main.py +348 -0
  28. attestplane-0.0.3a0/src/attestplane/event_payloads.py +581 -0
  29. attestplane-0.0.3a0/src/attestplane/event_types.py +88 -0
  30. attestplane-0.0.3a0/src/attestplane/hashchain.py +148 -0
  31. attestplane-0.0.3a0/src/attestplane/intoto.py +176 -0
  32. attestplane-0.0.3a0/src/attestplane/obligations/__init__.py +47 -0
  33. attestplane-0.0.3a0/src/attestplane/obligations/dora_article_8.json +106 -0
  34. attestplane-0.0.3a0/src/attestplane/obligations/eu_ai_act_article_12.json +161 -0
  35. attestplane-0.0.3a0/src/attestplane/obligations/registry.py +268 -0
  36. attestplane-0.0.3a0/src/attestplane/obligations/registry.schema.json +100 -0
  37. attestplane-0.0.3a0/src/attestplane/proof_bundle.py +432 -0
  38. attestplane-0.0.3a0/src/attestplane/py.typed +0 -0
  39. attestplane-0.0.3a0/src/attestplane/reason_codes.py +174 -0
  40. attestplane-0.0.3a0/src/attestplane/replay_verifier.py +196 -0
  41. attestplane-0.0.3a0/src/attestplane/settlement_verifier.py +214 -0
  42. attestplane-0.0.3a0/src/attestplane/signing/__init__.py +96 -0
  43. attestplane-0.0.3a0/src/attestplane/signing/base.py +316 -0
  44. attestplane-0.0.3a0/src/attestplane/signing/providers.py +253 -0
  45. attestplane-0.0.3a0/src/attestplane/signing/signer.py +437 -0
  46. attestplane-0.0.3a0/src/attestplane/signing/trust_roots.py +306 -0
  47. attestplane-0.0.3a0/src/attestplane/signing/verifier_ext.py +515 -0
  48. attestplane-0.0.3a0/src/attestplane/storage/__init__.py +34 -0
  49. attestplane-0.0.3a0/src/attestplane/storage/base.py +139 -0
  50. attestplane-0.0.3a0/src/attestplane/storage/jsonl.py +323 -0
  51. attestplane-0.0.3a0/src/attestplane/substrate.py +118 -0
  52. attestplane-0.0.3a0/src/attestplane/types.py +123 -0
  53. attestplane-0.0.3a0/src/attestplane/verifier.py +304 -0
  54. attestplane-0.0.3a0/tests/__init__.py +0 -0
  55. attestplane-0.0.3a0/tests/adapters/__init__.py +2 -0
  56. attestplane-0.0.3a0/tests/adapters/test_base.py +144 -0
  57. attestplane-0.0.3a0/tests/adapters/test_langfuse.py +223 -0
  58. attestplane-0.0.3a0/tests/adapters/test_langsmith.py +216 -0
  59. attestplane-0.0.3a0/tests/anchoring/__init__.py +2 -0
  60. attestplane-0.0.3a0/tests/anchoring/test_anchor_vectors.py +176 -0
  61. attestplane-0.0.3a0/tests/anchoring/test_anchoring.py +443 -0
  62. attestplane-0.0.3a0/tests/anchoring/test_eidas.py +227 -0
  63. attestplane-0.0.3a0/tests/anchoring/test_http.py +242 -0
  64. attestplane-0.0.3a0/tests/anchoring/test_multihop.py +249 -0
  65. attestplane-0.0.3a0/tests/anchoring/test_ocsp.py +161 -0
  66. attestplane-0.0.3a0/tests/anchoring/test_rfc3161.py +300 -0
  67. attestplane-0.0.3a0/tests/anchoring/test_sigstore.py +362 -0
  68. attestplane-0.0.3a0/tests/anchoring/test_worker.py +260 -0
  69. attestplane-0.0.3a0/tests/cli/__init__.py +2 -0
  70. attestplane-0.0.3a0/tests/cli/test_main.py +258 -0
  71. attestplane-0.0.3a0/tests/conformance/FIXTURE_HASHES.lock +26 -0
  72. attestplane-0.0.3a0/tests/conformance/__init__.py +0 -0
  73. attestplane-0.0.3a0/tests/conformance/anchor_vectors.json +55 -0
  74. attestplane-0.0.3a0/tests/conformance/generate_vectors.py +226 -0
  75. attestplane-0.0.3a0/tests/conformance/lease_lifecycle_event_vectors.json +170 -0
  76. attestplane-0.0.3a0/tests/conformance/negative/README.md +46 -0
  77. attestplane-0.0.3a0/tests/conformance/negative/broken_chain.json +71 -0
  78. attestplane-0.0.3a0/tests/conformance/negative/duplicate_event.json +72 -0
  79. attestplane-0.0.3a0/tests/conformance/negative/malformed_payload.json +71 -0
  80. attestplane-0.0.3a0/tests/conformance/negative/missing_event.json +50 -0
  81. attestplane-0.0.3a0/tests/conformance/negative/reordered_event.json +71 -0
  82. attestplane-0.0.3a0/tests/conformance/policy_check_event_vectors.json +209 -0
  83. attestplane-0.0.3a0/tests/conformance/proof_bundle_policy_trace_vectors.json +45 -0
  84. attestplane-0.0.3a0/tests/conformance/reason_codes_vectors.json +87 -0
  85. attestplane-0.0.3a0/tests/conformance/replay_event_vectors.json +330 -0
  86. attestplane-0.0.3a0/tests/conformance/settlement_precondition_vectors.json +197 -0
  87. attestplane-0.0.3a0/tests/conformance/signature_vectors.json +222 -0
  88. attestplane-0.0.3a0/tests/conformance/text_vectors.json +91 -0
  89. attestplane-0.0.3a0/tests/conformance/vectors.json +212 -0
  90. attestplane-0.0.3a0/tests/fault_injection/__init__.py +2 -0
  91. attestplane-0.0.3a0/tests/fault_injection/test_fault_matrix.py +350 -0
  92. attestplane-0.0.3a0/tests/fixtures/adapter_conformance/langfuse_v1.json +77 -0
  93. attestplane-0.0.3a0/tests/fixtures/adapter_conformance/langsmith_v1.json +78 -0
  94. attestplane-0.0.3a0/tests/obligations/__init__.py +2 -0
  95. attestplane-0.0.3a0/tests/obligations/test_registry.py +284 -0
  96. attestplane-0.0.3a0/tests/signing/__init__.py +2 -0
  97. attestplane-0.0.3a0/tests/signing/test_base.py +239 -0
  98. attestplane-0.0.3a0/tests/signing/test_proof_bundle_signatures.py +180 -0
  99. attestplane-0.0.3a0/tests/signing/test_providers.py +278 -0
  100. attestplane-0.0.3a0/tests/signing/test_signature_vectors.py +246 -0
  101. attestplane-0.0.3a0/tests/signing/test_signer.py +313 -0
  102. attestplane-0.0.3a0/tests/signing/test_trust_roots.py +244 -0
  103. attestplane-0.0.3a0/tests/signing/test_verifier_ext.py +431 -0
  104. attestplane-0.0.3a0/tests/storage/__init__.py +2 -0
  105. attestplane-0.0.3a0/tests/storage/test_jsonl.py +311 -0
  106. attestplane-0.0.3a0/tests/storage/test_storage_compatibility.py +138 -0
  107. attestplane-0.0.3a0/tests/test_adapter_conformance.py +182 -0
  108. attestplane-0.0.3a0/tests/test_canonical.py +136 -0
  109. attestplane-0.0.3a0/tests/test_canonical_text.py +156 -0
  110. attestplane-0.0.3a0/tests/test_conformance.py +100 -0
  111. attestplane-0.0.3a0/tests/test_conformance_negative.py +141 -0
  112. attestplane-0.0.3a0/tests/test_event_payloads.py +183 -0
  113. attestplane-0.0.3a0/tests/test_event_types.py +81 -0
  114. attestplane-0.0.3a0/tests/test_hashchain.py +158 -0
  115. attestplane-0.0.3a0/tests/test_import_surface.py +88 -0
  116. attestplane-0.0.3a0/tests/test_intoto.py +194 -0
  117. attestplane-0.0.3a0/tests/test_proof_bundle.py +335 -0
  118. attestplane-0.0.3a0/tests/test_proof_bundle_policy_trace.py +202 -0
  119. attestplane-0.0.3a0/tests/test_properties.py +217 -0
  120. attestplane-0.0.3a0/tests/test_public_api_manifest.py +130 -0
  121. attestplane-0.0.3a0/tests/test_reason_codes.py +117 -0
  122. attestplane-0.0.3a0/tests/test_release_claims.py +97 -0
  123. attestplane-0.0.3a0/tests/test_release_provenance_manifest.py +99 -0
  124. attestplane-0.0.3a0/tests/test_replay_event.py +189 -0
  125. attestplane-0.0.3a0/tests/test_schemas_v1.py +419 -0
  126. attestplane-0.0.3a0/tests/test_settlement_verifier.py +122 -0
  127. attestplane-0.0.3a0/tests/test_substrate.py +155 -0
@@ -0,0 +1,47 @@
1
+ # OS
2
+ .DS_Store
3
+ Thumbs.db
4
+
5
+ # Editor
6
+ .vscode/
7
+ .idea/
8
+ *.swp
9
+ *.swo
10
+ *~
11
+
12
+ # Environment / secrets
13
+ .env
14
+ .env.*
15
+ !.env.example
16
+ *.pem
17
+ *.key
18
+ secrets/
19
+
20
+ # Build / dependencies (placeholders for upcoming SDKs)
21
+ node_modules/
22
+ dist/
23
+ build/
24
+ target/
25
+ __pycache__/
26
+ *.pyc
27
+ .venv/
28
+ venv/
29
+
30
+ # Logs
31
+ *.log
32
+
33
+ # Test / coverage
34
+ coverage/
35
+ .coverage
36
+ .pytest_cache/
37
+ .mypy_cache/
38
+ .ruff_cache/
39
+ htmlcov/
40
+ *.egg-info/
41
+
42
+ # Local config
43
+ .local/
44
+
45
+ # Cross-SDK round-trip ephemeral artefacts (regenerated on every run)
46
+ tests/cross_sdk/py_emit.json
47
+ tests/cross_sdk/ts_reemit.json
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: attestplane
3
+ Version: 0.0.3a0
4
+ Summary: Apache-2.0 attestation and audit substrate for AI agent evidence chains.
5
+ Project-URL: Homepage, https://attestplane.io
6
+ Project-URL: Repository, https://github.com/attestplane/attestplane
7
+ Project-URL: Issues, https://github.com/attestplane/attestplane/issues
8
+ Project-URL: Changelog, https://github.com/attestplane/attestplane/releases
9
+ Project-URL: Documentation, https://github.com/attestplane/attestplane/tree/main/sdk/python
10
+ Author: The Attestplane Authors
11
+ License-Expression: Apache-2.0
12
+ Keywords: audit,compliance,dora,eu-ai-act,gdpr,hash-chain,provenance,transparency
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Legal Industry
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: uuid-utils>=0.9
27
+ Provides-Extra: anchor
28
+ Requires-Dist: asn1crypto>=1.5; extra == 'anchor'
29
+ Requires-Dist: cryptography>=43; extra == 'anchor'
30
+ Provides-Extra: dev
31
+ Requires-Dist: asn1crypto>=1.5; extra == 'dev'
32
+ Requires-Dist: build>=1.2; extra == 'dev'
33
+ Requires-Dist: cryptography>=43; extra == 'dev'
34
+ Requires-Dist: cyclonedx-bom>=4.5; extra == 'dev'
35
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
36
+ Requires-Dist: mypy>=1.11; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
38
+ Requires-Dist: pytest>=8; extra == 'dev'
39
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
40
+ Requires-Dist: ruff>=0.6; extra == 'dev'
41
+ Provides-Extra: signing
42
+ Requires-Dist: cryptography>=43; extra == 'signing'
43
+ Requires-Dist: pyyaml>=6.0; extra == 'signing'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # attestplane — Python SDK
47
+
48
+ Apache-2.0 attestation and audit substrate for AI agent evidence chains.
49
+
50
+ > **Status: alpha (v0.0.3a0).** APIs may change before v0.1.0. The canonical
51
+ > hash format and conformance vectors, however, are frozen — see [ADR-0002][adr2].
52
+ > The current CLI `attestplane verify` path is chain/report-oriented with
53
+ > ProofBundle metadata and `policy_trace_refs` closure checks. It does not
54
+ > perform full ProofBundle, signature, anchor, or compliance certification
55
+ > verification.
56
+
57
+ See the [project README][project-readme] for background, governance, and
58
+ trademark policy. The full design rationale for this SDK lives in [ADR-0002][adr2].
59
+
60
+ [project-readme]: https://github.com/attestplane/attestplane
61
+ [adr2]: https://github.com/attestplane/attestplane/blob/main/docs/adr/0002-substrate-data-model-and-hash-chain-v0.md
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install attestplane
67
+ ```
68
+
69
+ Requires Python ≥ 3.11.
70
+
71
+ Optional sidecar extras are deliberately separate from the core substrate:
72
+
73
+ ```bash
74
+ pip install 'attestplane[anchor]' # RFC-3161/OCSP/HTTP anchoring helpers
75
+ pip install 'attestplane[signing]' # Ed25519 signing helpers + YAML trust roots
76
+ ```
77
+
78
+ `import attestplane` works without those extras. Symbols that require optional
79
+ dependencies are exported only when the corresponding extra is installed.
80
+
81
+ ## Quickstart
82
+
83
+ ```python
84
+ from attestplane import AttestSubstrate, EventDraft, SubjectRef
85
+
86
+ sub = AttestSubstrate()
87
+
88
+ sub.append(
89
+ EventDraft(
90
+ event_type="ai_decision",
91
+ actor="agent://recsys/v1",
92
+ payload={"outcome": "approved", "confidence_bp": 9120},
93
+ session_id="session-2026-05-17-abc",
94
+ subject_ref=SubjectRef(scheme="sha256_salted", value="2c1b...e9"),
95
+ )
96
+ )
97
+
98
+ print(sub.tip()) # ChainHead(seq=0, event_hash=...)
99
+ assert sub.verify().ok # True
100
+ ```
101
+
102
+ ## What this SDK gives you
103
+
104
+ - An **append-only** audit log with cryptographic integrity (SHA-256 hash chain).
105
+ - Built-in fields designed toward **EU AI Act Art. 12(2)(a)** auditability from day one.
106
+ - A **deterministic canonical format** that the TypeScript SDK also verifies;
107
+ future SDKs must match the same conformance vector file shipped in this
108
+ package.
109
+ - Strong **GDPR pseudonymization typing** via `SubjectRef`.
110
+
111
+ ## What this SDK does NOT give you (yet)
112
+
113
+ | Feature | Where |
114
+ |---|---|
115
+ | Production storage guarantees | JSONL exists as an alpha backend; validate durability and concurrency for your deployment |
116
+ | Multi-process concurrency | Bring your own locking/backend policy for alpha deployments |
117
+ | Full ProofBundle verification | Current CLI is chain/report-oriented only |
118
+ | Signed or anchored CLI verification | Signing/anchoring are sidecar primitives unless an explicit verifier path performs those checks |
119
+ | Retention / truncation policy | Out of scope — deployer responsibility |
120
+
121
+ ## EU AI Act Article 12(2)(a) mapping
122
+
123
+ The four enumerated subitems of Art. 12(2)(a) are surfaced as `EventDraft`
124
+ fields. All remain optional in v0.0.3a0 — populate the ones your use case
125
+ requires.
126
+
127
+ | Art. 12(2)(a) language | `EventDraft` field | Type |
128
+ |---|---|---|
129
+ | "period of each use of the system" | `session_id` | `str \| None` |
130
+ | "the reference database against which input data has been checked by the system" | `reference_db_ref` | `str \| None` |
131
+ | "the input data for which the search has resulted in a match" | `matched_input_ref` | `str \| None` |
132
+ | "the identification of the natural persons involved in the verification of the results" | `human_verifier` | `SubjectRef \| None` |
133
+
134
+ These are **references**, not the data itself. A real reference database or
135
+ matched input must not be inlined into the audit log — that would violate
136
+ GDPR Art. 5(1)(c) data minimization. Store the data elsewhere (object storage,
137
+ hashed lookup table) and put a stable, content-addressed identifier here.
138
+
139
+ ## Restricted JSON profile
140
+
141
+ To make Python, TypeScript, and Rust SDKs produce byte-identical hashes,
142
+ canonicalization is stricter than vanilla JCS (RFC 8785):
143
+
144
+ | Allowed in `payload` | Forbidden in `payload` |
145
+ |---|---|
146
+ | UTF-8 NFC strings | Other Unicode normalization forms |
147
+ | Signed-int64 integers | Floats, NaN, ±Inf |
148
+ | `True` / `False` / `None` | — |
149
+ | `dict` (string keys, sorted on emit) | Non-string keys; duplicate keys |
150
+ | `list` / `tuple` (order preserved) | — |
151
+ | `bytes` (emitted as base64url, no padding) | Raw bytes |
152
+ | `datetime` (RFC 3339 UTC microsecond, `Z` suffix) | Naive datetimes; non-UTC offsets |
153
+
154
+ Violations raise `CanonicalizationError` at append time.
155
+
156
+ If you need to record a real-valued measurement, multiply into a fixed-point
157
+ integer (e.g., basis points, microseconds, microcents) and document the unit.
158
+
159
+ ## Conformance vectors
160
+
161
+ Ten golden `(EventDraft → event_hash hex)` pairs live in
162
+ [`tests/conformance/vectors.json`](tests/conformance/vectors.json). These
163
+ values are a **permanent external contract**:
164
+
165
+ - An auditor can re-implement canonicalization from ADR-0002 alone and verify
166
+ against this file without trusting any SDK code.
167
+ - TypeScript and Rust SDKs (M6, M7) MUST produce identical hex for the same
168
+ inputs, or the release is blocked.
169
+ - The file is frozen for `schema_version = 1`. Adding fields requires a new
170
+ `schema_version` and a new vector set under a separate filename; the
171
+ existing vectors stay valid forever.
172
+
173
+ The CI conformance test re-derives each hex on every run, so accidental
174
+ canonicalization drift fails the build immediately.
175
+
176
+ ## Threading and processes
177
+
178
+ `AttestSubstrate` is safe for concurrent use from multiple threads of the same
179
+ process. It is **not** safe across process boundaries — it holds no durable
180
+ storage and uses a process-local lock. Multi-process / multi-machine backends
181
+ are deferred to ADR-0004 (anticipated M6).
182
+
183
+ ## JSONL storage alpha semantics
184
+
185
+ `JsonlStorageBackend` is an opt-in alpha backend for one JSONL record per
186
+ `ChainedEvent`. Appends write newline-terminated rows, flush, and fsync by
187
+ default. Callers may disable fsync with `durable=False` for tests or explicit
188
+ best-effort storage.
189
+
190
+ Reads are fail-closed by default. `read_all()` raises on malformed JSON,
191
+ missing fields, invalid UTF-8, or a partial trailing line. `scan()` is a
192
+ read-only diagnostic path: it returns the valid prefix plus exact line/byte
193
+ offset issues, but it never repairs, truncates, or treats corrupted data as a
194
+ valid full chain.
195
+
196
+ Concurrency remains single-process. The backend uses a process-local lock only;
197
+ multi-writer safety, file locking, database semantics, ACID guarantees, and
198
+ production crash-safety are not claimed.
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ git clone https://github.com/attestplane/attestplane
204
+ cd attestplane/sdk/python
205
+ uv venv --python 3.12
206
+ . .venv/bin/activate
207
+ uv pip install -e '.[dev]'
208
+
209
+ pytest # unit tests + conformance vectors
210
+ mypy # strict type check
211
+ ruff check src tests # lint
212
+ ```
213
+
214
+ Coverage gate: ≥ 90 % (currently ~ 98 %).
215
+
216
+ Type check is `mypy --strict`. The public API is fully type-annotated; consumers
217
+ should expect type checks against this package to be precise.
218
+
219
+ ## License
220
+
221
+ Apache License 2.0. See [LICENSE](https://github.com/attestplane/attestplane/blob/main/LICENSE)
222
+ and [NOTICE](https://github.com/attestplane/attestplane/blob/main/NOTICE) at
223
+ the project root.
224
+
225
+ ## Reporting issues
226
+
227
+ - **Bugs / feature requests:** https://github.com/attestplane/attestplane/issues
228
+ - **Security vulnerabilities:** see [SECURITY.md](https://github.com/attestplane/attestplane/blob/main/SECURITY.md) — do not open public issues.
229
+ - **Trademark questions:** see [TRADEMARK.md](https://github.com/attestplane/attestplane/blob/main/TRADEMARK.md).
@@ -0,0 +1,184 @@
1
+ # attestplane — Python SDK
2
+
3
+ Apache-2.0 attestation and audit substrate for AI agent evidence chains.
4
+
5
+ > **Status: alpha (v0.0.3a0).** APIs may change before v0.1.0. The canonical
6
+ > hash format and conformance vectors, however, are frozen — see [ADR-0002][adr2].
7
+ > The current CLI `attestplane verify` path is chain/report-oriented with
8
+ > ProofBundle metadata and `policy_trace_refs` closure checks. It does not
9
+ > perform full ProofBundle, signature, anchor, or compliance certification
10
+ > verification.
11
+
12
+ See the [project README][project-readme] for background, governance, and
13
+ trademark policy. The full design rationale for this SDK lives in [ADR-0002][adr2].
14
+
15
+ [project-readme]: https://github.com/attestplane/attestplane
16
+ [adr2]: https://github.com/attestplane/attestplane/blob/main/docs/adr/0002-substrate-data-model-and-hash-chain-v0.md
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install attestplane
22
+ ```
23
+
24
+ Requires Python ≥ 3.11.
25
+
26
+ Optional sidecar extras are deliberately separate from the core substrate:
27
+
28
+ ```bash
29
+ pip install 'attestplane[anchor]' # RFC-3161/OCSP/HTTP anchoring helpers
30
+ pip install 'attestplane[signing]' # Ed25519 signing helpers + YAML trust roots
31
+ ```
32
+
33
+ `import attestplane` works without those extras. Symbols that require optional
34
+ dependencies are exported only when the corresponding extra is installed.
35
+
36
+ ## Quickstart
37
+
38
+ ```python
39
+ from attestplane import AttestSubstrate, EventDraft, SubjectRef
40
+
41
+ sub = AttestSubstrate()
42
+
43
+ sub.append(
44
+ EventDraft(
45
+ event_type="ai_decision",
46
+ actor="agent://recsys/v1",
47
+ payload={"outcome": "approved", "confidence_bp": 9120},
48
+ session_id="session-2026-05-17-abc",
49
+ subject_ref=SubjectRef(scheme="sha256_salted", value="2c1b...e9"),
50
+ )
51
+ )
52
+
53
+ print(sub.tip()) # ChainHead(seq=0, event_hash=...)
54
+ assert sub.verify().ok # True
55
+ ```
56
+
57
+ ## What this SDK gives you
58
+
59
+ - An **append-only** audit log with cryptographic integrity (SHA-256 hash chain).
60
+ - Built-in fields designed toward **EU AI Act Art. 12(2)(a)** auditability from day one.
61
+ - A **deterministic canonical format** that the TypeScript SDK also verifies;
62
+ future SDKs must match the same conformance vector file shipped in this
63
+ package.
64
+ - Strong **GDPR pseudonymization typing** via `SubjectRef`.
65
+
66
+ ## What this SDK does NOT give you (yet)
67
+
68
+ | Feature | Where |
69
+ |---|---|
70
+ | Production storage guarantees | JSONL exists as an alpha backend; validate durability and concurrency for your deployment |
71
+ | Multi-process concurrency | Bring your own locking/backend policy for alpha deployments |
72
+ | Full ProofBundle verification | Current CLI is chain/report-oriented only |
73
+ | Signed or anchored CLI verification | Signing/anchoring are sidecar primitives unless an explicit verifier path performs those checks |
74
+ | Retention / truncation policy | Out of scope — deployer responsibility |
75
+
76
+ ## EU AI Act Article 12(2)(a) mapping
77
+
78
+ The four enumerated subitems of Art. 12(2)(a) are surfaced as `EventDraft`
79
+ fields. All remain optional in v0.0.3a0 — populate the ones your use case
80
+ requires.
81
+
82
+ | Art. 12(2)(a) language | `EventDraft` field | Type |
83
+ |---|---|---|
84
+ | "period of each use of the system" | `session_id` | `str \| None` |
85
+ | "the reference database against which input data has been checked by the system" | `reference_db_ref` | `str \| None` |
86
+ | "the input data for which the search has resulted in a match" | `matched_input_ref` | `str \| None` |
87
+ | "the identification of the natural persons involved in the verification of the results" | `human_verifier` | `SubjectRef \| None` |
88
+
89
+ These are **references**, not the data itself. A real reference database or
90
+ matched input must not be inlined into the audit log — that would violate
91
+ GDPR Art. 5(1)(c) data minimization. Store the data elsewhere (object storage,
92
+ hashed lookup table) and put a stable, content-addressed identifier here.
93
+
94
+ ## Restricted JSON profile
95
+
96
+ To make Python, TypeScript, and Rust SDKs produce byte-identical hashes,
97
+ canonicalization is stricter than vanilla JCS (RFC 8785):
98
+
99
+ | Allowed in `payload` | Forbidden in `payload` |
100
+ |---|---|
101
+ | UTF-8 NFC strings | Other Unicode normalization forms |
102
+ | Signed-int64 integers | Floats, NaN, ±Inf |
103
+ | `True` / `False` / `None` | — |
104
+ | `dict` (string keys, sorted on emit) | Non-string keys; duplicate keys |
105
+ | `list` / `tuple` (order preserved) | — |
106
+ | `bytes` (emitted as base64url, no padding) | Raw bytes |
107
+ | `datetime` (RFC 3339 UTC microsecond, `Z` suffix) | Naive datetimes; non-UTC offsets |
108
+
109
+ Violations raise `CanonicalizationError` at append time.
110
+
111
+ If you need to record a real-valued measurement, multiply into a fixed-point
112
+ integer (e.g., basis points, microseconds, microcents) and document the unit.
113
+
114
+ ## Conformance vectors
115
+
116
+ Ten golden `(EventDraft → event_hash hex)` pairs live in
117
+ [`tests/conformance/vectors.json`](tests/conformance/vectors.json). These
118
+ values are a **permanent external contract**:
119
+
120
+ - An auditor can re-implement canonicalization from ADR-0002 alone and verify
121
+ against this file without trusting any SDK code.
122
+ - TypeScript and Rust SDKs (M6, M7) MUST produce identical hex for the same
123
+ inputs, or the release is blocked.
124
+ - The file is frozen for `schema_version = 1`. Adding fields requires a new
125
+ `schema_version` and a new vector set under a separate filename; the
126
+ existing vectors stay valid forever.
127
+
128
+ The CI conformance test re-derives each hex on every run, so accidental
129
+ canonicalization drift fails the build immediately.
130
+
131
+ ## Threading and processes
132
+
133
+ `AttestSubstrate` is safe for concurrent use from multiple threads of the same
134
+ process. It is **not** safe across process boundaries — it holds no durable
135
+ storage and uses a process-local lock. Multi-process / multi-machine backends
136
+ are deferred to ADR-0004 (anticipated M6).
137
+
138
+ ## JSONL storage alpha semantics
139
+
140
+ `JsonlStorageBackend` is an opt-in alpha backend for one JSONL record per
141
+ `ChainedEvent`. Appends write newline-terminated rows, flush, and fsync by
142
+ default. Callers may disable fsync with `durable=False` for tests or explicit
143
+ best-effort storage.
144
+
145
+ Reads are fail-closed by default. `read_all()` raises on malformed JSON,
146
+ missing fields, invalid UTF-8, or a partial trailing line. `scan()` is a
147
+ read-only diagnostic path: it returns the valid prefix plus exact line/byte
148
+ offset issues, but it never repairs, truncates, or treats corrupted data as a
149
+ valid full chain.
150
+
151
+ Concurrency remains single-process. The backend uses a process-local lock only;
152
+ multi-writer safety, file locking, database semantics, ACID guarantees, and
153
+ production crash-safety are not claimed.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ git clone https://github.com/attestplane/attestplane
159
+ cd attestplane/sdk/python
160
+ uv venv --python 3.12
161
+ . .venv/bin/activate
162
+ uv pip install -e '.[dev]'
163
+
164
+ pytest # unit tests + conformance vectors
165
+ mypy # strict type check
166
+ ruff check src tests # lint
167
+ ```
168
+
169
+ Coverage gate: ≥ 90 % (currently ~ 98 %).
170
+
171
+ Type check is `mypy --strict`. The public API is fully type-annotated; consumers
172
+ should expect type checks against this package to be precise.
173
+
174
+ ## License
175
+
176
+ Apache License 2.0. See [LICENSE](https://github.com/attestplane/attestplane/blob/main/LICENSE)
177
+ and [NOTICE](https://github.com/attestplane/attestplane/blob/main/NOTICE) at
178
+ the project root.
179
+
180
+ ## Reporting issues
181
+
182
+ - **Bugs / feature requests:** https://github.com/attestplane/attestplane/issues
183
+ - **Security vulnerabilities:** see [SECURITY.md](https://github.com/attestplane/attestplane/blob/main/SECURITY.md) — do not open public issues.
184
+ - **Trademark questions:** see [TRADEMARK.md](https://github.com/attestplane/attestplane/blob/main/TRADEMARK.md).
@@ -0,0 +1,191 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "attestplane"
7
+ version = "0.0.3a0"
8
+ description = "Apache-2.0 attestation and audit substrate for AI agent evidence chains."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE", "NOTICE"]
13
+ authors = [
14
+ { name = "The Attestplane Authors" },
15
+ ]
16
+ keywords = [
17
+ "audit",
18
+ "compliance",
19
+ "eu-ai-act",
20
+ "dora",
21
+ "gdpr",
22
+ "hash-chain",
23
+ "provenance",
24
+ "transparency",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 2 - Pre-Alpha",
28
+ "Intended Audience :: Developers",
29
+ "Intended Audience :: Legal Industry",
30
+ "License :: OSI Approved :: Apache Software License",
31
+ "Operating System :: OS Independent",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Topic :: Security :: Cryptography",
37
+ "Topic :: Software Development :: Libraries :: Python Modules",
38
+ "Typing :: Typed",
39
+ ]
40
+ dependencies = [
41
+ "uuid-utils>=0.9",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ anchor = [
46
+ "cryptography>=43",
47
+ "asn1crypto>=1.5",
48
+ ]
49
+ signing = [
50
+ "cryptography>=43",
51
+ "pyyaml>=6.0",
52
+ ]
53
+ dev = [
54
+ "pytest>=8",
55
+ "pytest-cov>=5",
56
+ "mypy>=1.11",
57
+ "ruff>=0.6",
58
+ "hypothesis>=6.100",
59
+ "cyclonedx-bom>=4.5",
60
+ "build>=1.2",
61
+ "cryptography>=43",
62
+ "asn1crypto>=1.5",
63
+ "pyyaml>=6.0",
64
+ ]
65
+
66
+ [project.scripts]
67
+ attestplane = "attestplane.cli:main"
68
+
69
+ [project.urls]
70
+ Homepage = "https://attestplane.io"
71
+ Repository = "https://github.com/attestplane/attestplane"
72
+ Issues = "https://github.com/attestplane/attestplane/issues"
73
+ Changelog = "https://github.com/attestplane/attestplane/releases"
74
+ Documentation = "https://github.com/attestplane/attestplane/tree/main/sdk/python"
75
+
76
+ [tool.hatch.build.targets.wheel]
77
+ packages = ["src/attestplane"]
78
+
79
+ [tool.hatch.build.targets.sdist]
80
+ include = [
81
+ "src/attestplane",
82
+ "tests",
83
+ "README.md",
84
+ "LICENSE",
85
+ "NOTICE",
86
+ ]
87
+
88
+ [tool.ruff]
89
+ # 120 covers ~all existing long lines that are doc-strings or composite f-strings.
90
+ # Hard-wrapping below that yielded noise without readability gain.
91
+ line-length = 120
92
+ target-version = "py311"
93
+
94
+ [tool.ruff.lint]
95
+ select = [
96
+ "E", "F", "W", "I", "B", "UP", "S", "RUF", "PL", "PT", "SIM", "TID",
97
+ ]
98
+ ignore = [
99
+ # ----- Pylint refactoring suggestions (advisory only; cosmetic) -----
100
+ "PLR0911", # too many return statements (verifier paths legitimately fan out)
101
+ "PLR0912", # too many branches (canonicalize / verify_chain are unavoidably branchy)
102
+ "PLR0913", # too many arguments (some dataclasses have many fields by design)
103
+ "PLR0915", # too many statements (composed verifier/builder routines)
104
+ "PLR2004", # magic-value comparison (test code + protocol-version checks)
105
+ "PLC0415", # `import` should be top-level — we deliberately lazy-import yaml /
106
+ # cryptography / hashlib in functions to keep optional extras truly optional.
107
+ # ----- Test assertion noise -----
108
+ "S101", # `assert` used (pytest tests deliberately use bare assert)
109
+ # ----- Security-rule false positives in this codebase -----
110
+ # S105/S107/S110/S112/S314/S324: domain-specific reasons documented at use sites.
111
+ # Selectively re-enable in future when each call site is reviewed; for now they
112
+ # are advisory not blocking.
113
+ "S105", # "Possible hardcoded password" — false-positive on identifier names
114
+ # like `secret = ...` in test fixtures.
115
+ "S107", # function-argument password — same false-positive on kw names.
116
+ "S110", # `try-except-pass` — used intentionally in defensive importable
117
+ # adapters.
118
+ "S112", # `try-except-continue` — same.
119
+ "S314", # `xml.etree` use — `intoto.py` deserialises trusted DSSE payloads only.
120
+ "S324", # `hashlib.sha1` — used for non-cryptographic identifiers (e.g.
121
+ # AnchorRecord opaque tag deduplication), never as a security
122
+ # primitive.
123
+ # ----- RUF suggestions that don't apply here -----
124
+ "RUF002", # ambiguous-unicode-character in docstring (legitimate full-width
125
+ # punctuation in CJK examples).
126
+ "RUF005", # iterable-unpacking suggestion (cosmetic).
127
+ "RUF022", # `__all__` is not sorted — keep current grouping (alpha by category).
128
+ "RUF059", # unpacked-variable-not-used — used intentionally to document tuple shape.
129
+ "RUF100", # unused-noqa — stale noqa entries from earlier ruff versions; benign.
130
+ # ----- pytest style suggestions (advisory, not bugs) -----
131
+ "PT006", # parametrize argument-name type (cosmetic).
132
+ "PT011", # `pytest.raises(ValueError)` too broad — domain code raises
133
+ # `ValueError` widely; match parameter is per-call decision.
134
+ "PT012", # raises-block-multiple-statements — fixture setup inside raises.
135
+ "PT018", # composite-assertion — used for clarity in test expectations.
136
+ # ----- SIM style suggestions (cosmetic refactors) -----
137
+ "SIM102", # nested-if combinable into single if.
138
+ "SIM108", # ternary suggestion.
139
+ "SIM115", # context-manager suggestion (test-only file-handle patterns).
140
+ # ----- Other low-signal rules -----
141
+ "B007", # unused-loop-control-variable (idiomatic in some test patterns).
142
+ "F841", # unused-local — intentional reservation in fixture wiring.
143
+ "PLC0206", # extracting-value-without-items() — dict iteration style choice.
144
+ ]
145
+
146
+ [tool.ruff.lint.per-file-ignores]
147
+ "tests/**" = [
148
+ "S101",
149
+ "PLR2004",
150
+ "PLC0415", # tests legitimately import-then-use inside test functions
151
+ ]
152
+
153
+ [tool.mypy]
154
+ python_version = "3.11"
155
+ strict = true
156
+ warn_unreachable = true
157
+ warn_unused_ignores = true
158
+ disallow_any_explicit = false
159
+ files = ["src/attestplane"]
160
+
161
+ [[tool.mypy.overrides]]
162
+ module = ["asn1crypto", "asn1crypto.*"]
163
+ ignore_missing_imports = true
164
+
165
+ [[tool.mypy.overrides]]
166
+ module = ["yaml"]
167
+ ignore_missing_imports = true
168
+
169
+ [tool.pytest.ini_options]
170
+ testpaths = ["tests"]
171
+ addopts = "-ra --strict-markers --strict-config"
172
+ xfail_strict = true
173
+
174
+ [tool.coverage.run]
175
+ source = ["src/attestplane"]
176
+ branch = true
177
+
178
+ [tool.coverage.report]
179
+ fail_under = 90
180
+ show_missing = true
181
+ skip_covered = false
182
+
183
+ # Mutation testing (Gap G6, advisory only, nightly).
184
+ # Narrow scope to small core modules so mutmut completes in the nightly
185
+ # budget: canonical_text.py (cross-SDK byte contract) + reason_codes.py
186
+ # (ADR-0010 verification semantics). Other modules will be added as
187
+ # mutmut budget permits.
188
+ [tool.mutmut]
189
+ paths_to_mutate = "src/attestplane/canonical_text.py:src/attestplane/reason_codes.py"
190
+ runner = "python -m pytest -x -q --no-cov"
191
+ tests_dir = "tests"