arkova 2.2.0__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.
@@ -0,0 +1,199 @@
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Build outputs
7
+ dist/
8
+ build/
9
+ .next/
10
+ out/
11
+
12
+ # Environment files - NEVER commit secrets (SEC-02)
13
+ .env
14
+ .env.local
15
+ .env.test
16
+ .env.production
17
+ .env.staging
18
+ .env.development.local
19
+ .env.test.local
20
+ .env.production.local
21
+ .env*.local
22
+ !.env.example
23
+
24
+ # Logs
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+ pnpm-debug.log*
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+ *~
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Testing
42
+ coverage/
43
+ .nyc_output/
44
+
45
+ # Supabase
46
+ supabase/.branches/
47
+ supabase/.temp/
48
+
49
+ # TypeScript
50
+ *.tsbuildinfo
51
+
52
+ # Generated docs
53
+ docs/stories/docx/
54
+
55
+ # Sentry
56
+ .sentryclirc
57
+
58
+ # Generated artifacts
59
+ .generated-machines/
60
+ .playwright-mcp/
61
+ .auth/
62
+
63
+ # HyperFrames video demo projects (scratch output, not repo code)
64
+ arkova-product-demo/
65
+
66
+ # Design mockups and screenshots (root level)
67
+ /arkova-*.png
68
+ /redesign-*.png
69
+ /journey*.png
70
+
71
+ # Test files at root
72
+ /test_*.pdf
73
+
74
+ # Claude agent skills — per-user / per-machine state ignored, project-level
75
+ # config tracked. settings.json + hooks/ are checked in so the team-wide
76
+ # enforcement (e.g. staging-evidence pre-merge hook) ships with the repo.
77
+ .agents/
78
+ .claude/*
79
+ !.claude/settings.json
80
+ !.claude/hooks
81
+ !.claude/hooks/**
82
+ skills-lock.json
83
+
84
+ # Other AI tool configs (may contain pre-approved commands with tokens — see Anthropic 2026-04 source-map leak)
85
+ .cursor/
86
+ .aider*
87
+ .continue/
88
+ .cody/
89
+ .codex/
90
+
91
+ # Private keys / certs (never commit — none currently tracked, this is preventative)
92
+ *.pem
93
+ *.key
94
+ *.p12
95
+ *.pfx
96
+ *.crt
97
+ *.cer
98
+
99
+ # Cloud / infra credentials (developer-local; treasury & service-account JSONs must never land in git)
100
+ .aws/
101
+ .azure/
102
+ .gcloud/
103
+ .ssh/
104
+ .kube/
105
+ .docker/config.json
106
+ credentials.json
107
+ service-account*.json
108
+ gcp-key*.json
109
+
110
+ # Terraform state (plaintext secrets — preventative; deployment/self-hosted/terraform/ uses tf)
111
+ .terraform/
112
+ *.tfstate
113
+ *.tfstate.*
114
+ *.tfvars
115
+ !*.tfvars.example
116
+
117
+ # Nested repos (separate git projects)
118
+ arkova-marketing/
119
+ arkova-demo/
120
+
121
+ # Demo assets and loose screenshots
122
+ demo-assets/
123
+ /prod-*.png
124
+ /swagger-*.png
125
+ /app-*.png
126
+ /search-*.png
127
+
128
+ # macOS duplicate files (Finder "name N.ext" pattern)
129
+ *\ 2.*
130
+ *\ 2/
131
+ *\ 3.*
132
+ *\ 3/
133
+
134
+ # Binary artifacts
135
+ *.sym
136
+ *.ptau
137
+ *.zkey
138
+ *.r1cs
139
+ *.wasm
140
+
141
+ # ZK circuit compiled artifacts (rebuild from .circom source)
142
+ services/worker/circuits/artifacts/
143
+
144
+ # Strategy docs (binary .docx)
145
+ docs/strategy/*.docx
146
+
147
+ # Env backups
148
+ .env.local.bak
149
+ .env.bak
150
+
151
+ # Bug report artifacts (screenshots)
152
+ docs/bugs/*.png
153
+ docs/bugs/*.pdf
154
+
155
+ # Eval data (large JSON — keep .md summaries only)
156
+ docs/eval/*.json
157
+ services/worker/docs/eval/*.json
158
+
159
+ # Training data (large JSONL files) — exclude contents but allow committed fixture set.
160
+ # Git can't re-include files under a fully-ignored directory, so use `dir/*` pattern
161
+ # with negations rather than `dir/`. See SCRUM-1549.
162
+ services/worker/training-data/*
163
+ !services/worker/training-data/fixtures/
164
+ !services/worker/training-data/.gitkeep
165
+
166
+ # Playwright reports
167
+ playwright-report/
168
+ /output/
169
+
170
+ # UAT screenshots
171
+ /uat-*.png
172
+
173
+ # Test files at root
174
+ /test-*.csv
175
+ /test-*.txt
176
+
177
+ # Self-hosted NER PII model weights (S1.4 / WEBEXT-CSP / SCRUM-2503).
178
+ # Vendored on-device by `npx tsx scripts/fetch-ner-model.ts` into the served
179
+ # app origin. These are large binaries (~130 MB q8 weights) — never committed;
180
+ # the build/deploy pipeline re-fetches them. The runtime bundle in
181
+ # public/vendor/ stays tracked; only the downloaded model weights are ignored.
182
+ public/models/
183
+
184
+ # WEBEXT-01 F-2: onnxruntime WASM artifacts, vendored on-device by
185
+ # `npx tsx scripts/vendor-ner-runtime.ts` (npm run prebuild) from the exact
186
+ # npm-pinned onnxruntime-web package, SHA-256-verified against
187
+ # scripts/ner-runtime.lock.json. ~24 MB binaries — never committed; the
188
+ # build/deploy pipeline re-vendors them. The transformers.js runtime bundle
189
+ # (public/vendor/transformers.bundle.min.js) stays tracked.
190
+ public/vendor/ort/
191
+
192
+ # Misc
193
+ .cache/
194
+ .parcel-cache/
195
+ .vercel
196
+
197
+ # Python bytecode
198
+ __pycache__/
199
+ *.pyc
arkova-2.2.0/PKG-INFO ADDED
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: arkova
3
+ Version: 2.2.0
4
+ Summary: Typed Python SDK for the Arkova Verification APIs
5
+ Project-URL: Homepage, https://arkova.ai
6
+ Project-URL: Documentation, https://arkova.ai/docs/v2
7
+ Project-URL: Repository, https://github.com/carson-see/ArkovaCarson
8
+ Author-email: Arkova Engineering <engineering@arkova.ai>
9
+ License: MIT
10
+ Keywords: anchors,api,arkova,credentials,verification
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx<1,>=0.27
20
+ Requires-Dist: pydantic<3,>=2.7
21
+ Provides-Extra: dev
22
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
23
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
24
+ Requires-Dist: ruff==0.16.0; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Arkova Python SDK
28
+
29
+ Typed Python client for the Arkova Verification APIs.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install arkova
35
+ ```
36
+
37
+ Python 3.10 or newer is supported.
38
+
39
+ ## Supported methods
40
+
41
+ - `anchor(data=None, *, fingerprint=None)`
42
+ - `anchor_bulk(inputs, *, dry_run=None, duplicate_strategy=None, batch_id=None)`
43
+ - `fingerprint(data)`
44
+ - `search(q, type="all", cursor=None, limit=50)`
45
+ - `verify(public_id)`
46
+ - `verify_fingerprint(fingerprint)`
47
+ - `get_anchor(public_id)`
48
+ - `list_orgs()`
49
+
50
+ ## Quick start
51
+
52
+ ```python
53
+ import os
54
+ from arkova import Arkova
55
+
56
+ with Arkova(api_key=os.environ["ARKOVA_API_KEY"]) as arkova:
57
+ results = arkova.search("registered nurse", type="record", limit=5)
58
+ for item in results.results:
59
+ print(item.public_id, item.snippet)
60
+ ```
61
+
62
+ ## Anchor a document (HAKI-REQ-02)
63
+
64
+ `anchor()` fingerprints `data` client-side (SHA-256, in-process — the raw
65
+ content is never sent, only the 64-char hex fingerprint) and submits it for
66
+ network anchoring. Pass a pre-computed `fingerprint` instead if you already
67
+ hashed the document elsewhere.
68
+
69
+ ```python
70
+ from arkova import Arkova
71
+
72
+ with Arkova(api_key="ak_live_...") as arkova:
73
+ # Raw content — fingerprinted in-process before anything is sent.
74
+ receipt = arkova.anchor("document content goes here")
75
+ print(receipt.public_id, receipt.status) # "PENDING" -> "SUBMITTED" -> "SECURED"
76
+
77
+ # Or: you already have the fingerprint.
78
+ receipt = arkova.anchor(fingerprint="a" * 64)
79
+ ```
80
+
81
+ The same fingerprint always returns the same `public_id` — anchoring
82
+ identical content twice is a no-op.
83
+
84
+ ## Bulk-anchor documents (HAKI-REQ-02)
85
+
86
+ `anchor_bulk()` anchors up to `BULK_ANCHOR_MAX_ROWS` (1000) documents in one
87
+ call. Each `BulkAnchorInput` row provides exactly one of `fingerprint` (a
88
+ pre-computed 64-char hex SHA-256) or `data` (raw content, fingerprinted
89
+ client-side the same way `anchor()` does it) — you can mix both forms across
90
+ rows in one call.
91
+
92
+ ```python
93
+ from arkova import Arkova, BulkAnchorInput
94
+
95
+ with Arkova(api_key="ak_live_...") as arkova:
96
+ with open("contract.pdf", "rb") as f:
97
+ contract_bytes = f.read()
98
+
99
+ result = arkova.anchor_bulk(
100
+ [
101
+ # Already hashed elsewhere — send the fingerprint directly.
102
+ BulkAnchorInput(fingerprint="a" * 64, external_id="invoice-001"),
103
+ # Raw content — the SDK hashes it for you before it's ever sent.
104
+ BulkAnchorInput(
105
+ data=contract_bytes,
106
+ credential_type="CONTRACT_PRESIGNING",
107
+ document_type="contract",
108
+ matter_or_case_ref="CASE-42",
109
+ ),
110
+ ],
111
+ duplicate_strategy="skip",
112
+ batch_id="nightly-2026-07-28",
113
+ )
114
+
115
+ print(result.queued, result.duplicates, result.errors)
116
+ for anchor in result.anchors or []:
117
+ print(anchor.public_id, anchor.status)
118
+ ```
119
+
120
+ **Options:** `dry_run=True` validates every row (including dedup checks)
121
+ without queuing or deducting credits — `result.anchors` is `None` on a dry
122
+ run. `duplicate_strategy` controls what happens when a fingerprint already
123
+ exists in-batch or in your org; the server default is `"fail"` (raises
124
+ `ArkovaError(code="duplicate_fingerprints")` on any duplicate — pass
125
+ `"skip"`, `"supersede"`, or `"link"` to proceed instead). `batch_id` is your
126
+ own correlation ID, echoed back and surfaced in audit events.
127
+
128
+ **Limits:** empty input returns a zero-row response immediately, no network
129
+ call. More than 1000 rows raises `ArkovaError(code="batch_too_large")` — the
130
+ SDK does **not** auto-chunk (splitting a logical batch across requests would
131
+ let a duplicate fingerprint slip past the cheaper intra-batch check and
132
+ would deduct credits per chunk instead of atomically for the whole batch;
133
+ split manually and correlate with a shared `batch_id` if you need more than
134
+ 1000 rows). A row with neither `fingerprint` nor `data` (or with both)
135
+ raises `ArkovaError(code="invalid_request")`, checked before any network
136
+ call.
137
+
138
+ ## Verify a fingerprint
139
+
140
+ ```python
141
+ from arkova import Arkova
142
+
143
+ fingerprint = "a" * 64
144
+
145
+ with Arkova(api_key="ak_live_...") as arkova:
146
+ result = arkova.verify_fingerprint(fingerprint)
147
+ print(result.verified, result.public_id)
148
+ ```
149
+
150
+ ## Verify a public ID
151
+
152
+ ```python
153
+ from arkova import Arkova
154
+
155
+ with Arkova(api_key="ak_live_...") as arkova:
156
+ result = arkova.verify("ARK-2026-ABC")
157
+ print(result.verified, result.description, result.confidence_scores)
158
+ ```
159
+
160
+ `verify()` returns the rich v1 verification shape, including API-RICH-01 fields
161
+ such as `compliance_controls`, `chain_confirmations`, `parent_public_id`,
162
+ `version_number`, `file_mime`, and `file_size`, plus API-RICH-02 fields
163
+ `confidence_scores` and `sub_type` when the API response includes them.
164
+ The same optional rich fields are typed on v2 `verify_fingerprint()` and
165
+ `get_anchor()` responses, so newer API payloads are not silently hidden by the
166
+ SDK model layer.
167
+
168
+ ## Async client
169
+
170
+ ```python
171
+ import asyncio
172
+ from arkova import AsyncArkova
173
+
174
+
175
+ async def main() -> None:
176
+ async with AsyncArkova(api_key="ak_live_...") as arkova:
177
+ orgs = await arkova.list_orgs()
178
+ print([org.display_name for org in orgs.organizations])
179
+
180
+
181
+ asyncio.run(main())
182
+ ```
183
+
184
+ ## Errors and retries
185
+
186
+ `ArkovaError` preserves the API v2 RFC 7807 problem document and the `Retry-After`
187
+ header when present. `code` carries the machine-readable error code — from the
188
+ plain-JSON `error` field on v1 write-path errors (`anchor()` / `anchor_bulk()`
189
+ codes include `"insufficient_credits"`, `"duplicate_fingerprints"`,
190
+ `"batch_too_large"`, `"invalid_request"`), or the RFC 7807 `type` slug on v2
191
+ problem documents.
192
+
193
+ ```python
194
+ from arkova import Arkova, ArkovaError
195
+
196
+ try:
197
+ with Arkova(api_key="ak_live_...") as arkova:
198
+ arkova.get_anchor("ARK-DOC-MISSING")
199
+ except ArkovaError as exc:
200
+ print(exc.status_code, exc.code, exc.problem.type if exc.problem else None)
201
+ ```
202
+
203
+ The client retries `429` and `5xx` responses by default and respects `Retry-After`.
204
+ Pass `retries=0` to disable retries.
205
+
206
+ ## Offline proof verification (no network, no API key)
207
+
208
+ `verify_bundle` verifies an exported Arkova proof package entirely offline —
209
+ an independent re-derivation of the documented bundle format (Merkle
210
+ recompute with the CVE-2012-2459 structural guard, fixed-offset on-chain
211
+ payload decode, 80-byte header rules, timestamp honesty, Ed25519 signed
212
+ bundles). It makes zero network calls and never contacts Arkova; on-chain
213
+ confirmation runs only against canned or caller-supplied independent-node
214
+ responses.
215
+
216
+ ```python
217
+ import json
218
+ from arkova import verify_bundle
219
+
220
+ packet = json.load(open("proof.json"))
221
+ outcome = verify_bundle(packet) # recompute-only
222
+ print(outcome.verdict, outcome.reason_code) # "VERIFIED" / None, or a frozen code
223
+ ```
224
+
225
+ Every NOT-VERIFIED outcome carries one frozen machine reason code
226
+ (`arkova.REASON_CODES`), kept in lockstep with the TypeScript reference
227
+ verifier via a cross-runtime parity gate in the Arkova repo. A passing
228
+ signature never substitutes for the cryptographic recompute; a failing
229
+ explicitly-requested signature check fails the verdict closed.
arkova-2.2.0/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # Arkova Python SDK
2
+
3
+ Typed Python client for the Arkova Verification APIs.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install arkova
9
+ ```
10
+
11
+ Python 3.10 or newer is supported.
12
+
13
+ ## Supported methods
14
+
15
+ - `anchor(data=None, *, fingerprint=None)`
16
+ - `anchor_bulk(inputs, *, dry_run=None, duplicate_strategy=None, batch_id=None)`
17
+ - `fingerprint(data)`
18
+ - `search(q, type="all", cursor=None, limit=50)`
19
+ - `verify(public_id)`
20
+ - `verify_fingerprint(fingerprint)`
21
+ - `get_anchor(public_id)`
22
+ - `list_orgs()`
23
+
24
+ ## Quick start
25
+
26
+ ```python
27
+ import os
28
+ from arkova import Arkova
29
+
30
+ with Arkova(api_key=os.environ["ARKOVA_API_KEY"]) as arkova:
31
+ results = arkova.search("registered nurse", type="record", limit=5)
32
+ for item in results.results:
33
+ print(item.public_id, item.snippet)
34
+ ```
35
+
36
+ ## Anchor a document (HAKI-REQ-02)
37
+
38
+ `anchor()` fingerprints `data` client-side (SHA-256, in-process — the raw
39
+ content is never sent, only the 64-char hex fingerprint) and submits it for
40
+ network anchoring. Pass a pre-computed `fingerprint` instead if you already
41
+ hashed the document elsewhere.
42
+
43
+ ```python
44
+ from arkova import Arkova
45
+
46
+ with Arkova(api_key="ak_live_...") as arkova:
47
+ # Raw content — fingerprinted in-process before anything is sent.
48
+ receipt = arkova.anchor("document content goes here")
49
+ print(receipt.public_id, receipt.status) # "PENDING" -> "SUBMITTED" -> "SECURED"
50
+
51
+ # Or: you already have the fingerprint.
52
+ receipt = arkova.anchor(fingerprint="a" * 64)
53
+ ```
54
+
55
+ The same fingerprint always returns the same `public_id` — anchoring
56
+ identical content twice is a no-op.
57
+
58
+ ## Bulk-anchor documents (HAKI-REQ-02)
59
+
60
+ `anchor_bulk()` anchors up to `BULK_ANCHOR_MAX_ROWS` (1000) documents in one
61
+ call. Each `BulkAnchorInput` row provides exactly one of `fingerprint` (a
62
+ pre-computed 64-char hex SHA-256) or `data` (raw content, fingerprinted
63
+ client-side the same way `anchor()` does it) — you can mix both forms across
64
+ rows in one call.
65
+
66
+ ```python
67
+ from arkova import Arkova, BulkAnchorInput
68
+
69
+ with Arkova(api_key="ak_live_...") as arkova:
70
+ with open("contract.pdf", "rb") as f:
71
+ contract_bytes = f.read()
72
+
73
+ result = arkova.anchor_bulk(
74
+ [
75
+ # Already hashed elsewhere — send the fingerprint directly.
76
+ BulkAnchorInput(fingerprint="a" * 64, external_id="invoice-001"),
77
+ # Raw content — the SDK hashes it for you before it's ever sent.
78
+ BulkAnchorInput(
79
+ data=contract_bytes,
80
+ credential_type="CONTRACT_PRESIGNING",
81
+ document_type="contract",
82
+ matter_or_case_ref="CASE-42",
83
+ ),
84
+ ],
85
+ duplicate_strategy="skip",
86
+ batch_id="nightly-2026-07-28",
87
+ )
88
+
89
+ print(result.queued, result.duplicates, result.errors)
90
+ for anchor in result.anchors or []:
91
+ print(anchor.public_id, anchor.status)
92
+ ```
93
+
94
+ **Options:** `dry_run=True` validates every row (including dedup checks)
95
+ without queuing or deducting credits — `result.anchors` is `None` on a dry
96
+ run. `duplicate_strategy` controls what happens when a fingerprint already
97
+ exists in-batch or in your org; the server default is `"fail"` (raises
98
+ `ArkovaError(code="duplicate_fingerprints")` on any duplicate — pass
99
+ `"skip"`, `"supersede"`, or `"link"` to proceed instead). `batch_id` is your
100
+ own correlation ID, echoed back and surfaced in audit events.
101
+
102
+ **Limits:** empty input returns a zero-row response immediately, no network
103
+ call. More than 1000 rows raises `ArkovaError(code="batch_too_large")` — the
104
+ SDK does **not** auto-chunk (splitting a logical batch across requests would
105
+ let a duplicate fingerprint slip past the cheaper intra-batch check and
106
+ would deduct credits per chunk instead of atomically for the whole batch;
107
+ split manually and correlate with a shared `batch_id` if you need more than
108
+ 1000 rows). A row with neither `fingerprint` nor `data` (or with both)
109
+ raises `ArkovaError(code="invalid_request")`, checked before any network
110
+ call.
111
+
112
+ ## Verify a fingerprint
113
+
114
+ ```python
115
+ from arkova import Arkova
116
+
117
+ fingerprint = "a" * 64
118
+
119
+ with Arkova(api_key="ak_live_...") as arkova:
120
+ result = arkova.verify_fingerprint(fingerprint)
121
+ print(result.verified, result.public_id)
122
+ ```
123
+
124
+ ## Verify a public ID
125
+
126
+ ```python
127
+ from arkova import Arkova
128
+
129
+ with Arkova(api_key="ak_live_...") as arkova:
130
+ result = arkova.verify("ARK-2026-ABC")
131
+ print(result.verified, result.description, result.confidence_scores)
132
+ ```
133
+
134
+ `verify()` returns the rich v1 verification shape, including API-RICH-01 fields
135
+ such as `compliance_controls`, `chain_confirmations`, `parent_public_id`,
136
+ `version_number`, `file_mime`, and `file_size`, plus API-RICH-02 fields
137
+ `confidence_scores` and `sub_type` when the API response includes them.
138
+ The same optional rich fields are typed on v2 `verify_fingerprint()` and
139
+ `get_anchor()` responses, so newer API payloads are not silently hidden by the
140
+ SDK model layer.
141
+
142
+ ## Async client
143
+
144
+ ```python
145
+ import asyncio
146
+ from arkova import AsyncArkova
147
+
148
+
149
+ async def main() -> None:
150
+ async with AsyncArkova(api_key="ak_live_...") as arkova:
151
+ orgs = await arkova.list_orgs()
152
+ print([org.display_name for org in orgs.organizations])
153
+
154
+
155
+ asyncio.run(main())
156
+ ```
157
+
158
+ ## Errors and retries
159
+
160
+ `ArkovaError` preserves the API v2 RFC 7807 problem document and the `Retry-After`
161
+ header when present. `code` carries the machine-readable error code — from the
162
+ plain-JSON `error` field on v1 write-path errors (`anchor()` / `anchor_bulk()`
163
+ codes include `"insufficient_credits"`, `"duplicate_fingerprints"`,
164
+ `"batch_too_large"`, `"invalid_request"`), or the RFC 7807 `type` slug on v2
165
+ problem documents.
166
+
167
+ ```python
168
+ from arkova import Arkova, ArkovaError
169
+
170
+ try:
171
+ with Arkova(api_key="ak_live_...") as arkova:
172
+ arkova.get_anchor("ARK-DOC-MISSING")
173
+ except ArkovaError as exc:
174
+ print(exc.status_code, exc.code, exc.problem.type if exc.problem else None)
175
+ ```
176
+
177
+ The client retries `429` and `5xx` responses by default and respects `Retry-After`.
178
+ Pass `retries=0` to disable retries.
179
+
180
+ ## Offline proof verification (no network, no API key)
181
+
182
+ `verify_bundle` verifies an exported Arkova proof package entirely offline —
183
+ an independent re-derivation of the documented bundle format (Merkle
184
+ recompute with the CVE-2012-2459 structural guard, fixed-offset on-chain
185
+ payload decode, 80-byte header rules, timestamp honesty, Ed25519 signed
186
+ bundles). It makes zero network calls and never contacts Arkova; on-chain
187
+ confirmation runs only against canned or caller-supplied independent-node
188
+ responses.
189
+
190
+ ```python
191
+ import json
192
+ from arkova import verify_bundle
193
+
194
+ packet = json.load(open("proof.json"))
195
+ outcome = verify_bundle(packet) # recompute-only
196
+ print(outcome.verdict, outcome.reason_code) # "VERIFIED" / None, or a frozen code
197
+ ```
198
+
199
+ Every NOT-VERIFIED outcome carries one frozen machine reason code
200
+ (`arkova.REASON_CODES`), kept in lockstep with the TypeScript reference
201
+ verifier via a cross-runtime parity gate in the Arkova repo. A passing
202
+ signature never substitutes for the cryptographic recompute; a failing
203
+ explicitly-requested signature check fails the verdict closed.
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26,<2"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "arkova"
7
+ version = "2.2.0"
8
+ description = "Typed Python SDK for the Arkova Verification APIs"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Arkova Engineering", email = "engineering@arkova.ai" }]
13
+ keywords = ["arkova", "verification", "credentials", "anchors", "api"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.27,<1",
25
+ "pydantic>=2.7,<3",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "build>=1.2,<2",
31
+ "pytest>=8,<9",
32
+ # EXACT pin, on purpose. ruff has no stable-default guarantee below 1.0:
33
+ # 0.16.0 widened its implicit default rule set (adding I / UP / SIM / PYI /
34
+ # BLE / RUF), which turned the publish workflow's `ruff check src tests` red
35
+ # with 87 findings against unchanged code. The old `>=0.8,<1` range let that
36
+ # land silently.
37
+ #
38
+ # A range like `>=0.16,<0.17` is NOT enough: the default set is a curated
39
+ # ~413-rule subset, so a PATCH release can add a rule to it too. And it cannot
40
+ # be fenced with an explicit `[tool.ruff.lint] select` either — that subset is
41
+ # not expressible as rule prefixes (selecting the 38 prefixes it draws from
42
+ # yields 288 findings, because it includes only parts of E, D, PTH, PLR, ...).
43
+ # Pinning the exact version is the only way to make this gate deterministic.
44
+ #
45
+ # To upgrade: bump this, run `ruff check src tests`, fix any new findings in
46
+ # the same PR.
47
+ "ruff==0.16.0",
48
+ ]
49
+
50
+ [project.urls]
51
+ Homepage = "https://arkova.ai"
52
+ Documentation = "https://arkova.ai/docs/v2"
53
+ Repository = "https://github.com/carson-see/ArkovaCarson"
54
+
55
+ [tool.hatch.build.targets.wheel]
56
+ packages = ["src/arkova"]
57
+
58
+ [tool.hatch.build.targets.wheel.force-include]
59
+ "src/arkova/py.typed" = "arkova/py.typed"
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+
64
+ [tool.ruff]
65
+ line-length = 100
66
+ target-version = "py310"