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.
- arkova-2.2.0/.gitignore +199 -0
- arkova-2.2.0/PKG-INFO +229 -0
- arkova-2.2.0/README.md +203 -0
- arkova-2.2.0/pyproject.toml +66 -0
- arkova-2.2.0/scripts/agents.md +23 -0
- arkova-2.2.0/scripts/manifest_lib.py +109 -0
- arkova-2.2.0/scripts/run_manifest.py +38 -0
- arkova-2.2.0/src/arkova/__init__.py +61 -0
- arkova-2.2.0/src/arkova/agents.md +76 -0
- arkova-2.2.0/src/arkova/client.py +603 -0
- arkova-2.2.0/src/arkova/errors.py +32 -0
- arkova-2.2.0/src/arkova/models.py +364 -0
- arkova-2.2.0/src/arkova/proofs.py +816 -0
- arkova-2.2.0/src/arkova/py.typed +1 -0
- arkova-2.2.0/tests/agents.md +28 -0
- arkova-2.2.0/tests/conftest.py +9 -0
- arkova-2.2.0/tests/test_client.py +1039 -0
- arkova-2.2.0/tests/test_proofs.py +267 -0
arkova-2.2.0/.gitignore
ADDED
|
@@ -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"
|