lucairn 1.0.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,31 @@
1
+ # Node
2
+ node_modules/
3
+ dist/
4
+ .env
5
+ .env.local
6
+ *.log
7
+ npm-debug.log*
8
+ .DS_Store
9
+
10
+ # Python
11
+ __pycache__/
12
+ *.pyc
13
+ .venv/
14
+ venv/
15
+ .pytest_cache/
16
+ *.egg-info/
17
+ build/
18
+
19
+ # Go
20
+ vendor/
21
+ *.test
22
+ *.out
23
+ coverage.out
24
+
25
+ # Editor
26
+ .vscode/
27
+ .idea/
28
+ *.swp
29
+
30
+ # Superpowers skill output belongs in /tmp/, not the repo
31
+ docs/superpowers/
lucairn-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: lucairn
3
+ Version: 1.0.0
4
+ Summary: Lucairn — privacy-preserving AI gateway client for Python
5
+ Project-URL: Homepage, https://lucairn.eu
6
+ Project-URL: Repository, https://github.com/Declade/theveil-sdks
7
+ Project-URL: Issues, https://github.com/Declade/theveil-sdks/issues
8
+ Project-URL: Changelog, https://github.com/Declade/theveil-sdks/blob/main/CHANGELOG.md
9
+ Author: Declade
10
+ License: MIT
11
+ Keywords: ai,anthropic,eu-ai-act,gdpr,llm,openai,pii,privacy,pseudonymization
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: cryptography>=42
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: pydantic>=2.6
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: respx>=0.21; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # lucairn — Python SDK
33
+
34
+ Client for **Lucairn** — privacy-preserving AI gateway.
35
+
36
+ ## Status
37
+
38
+ `1.0.0`. Ships alongside the TypeScript SDK and behaves identically at the
39
+ observable level. See the [monorepo README](../README.md) for the full SDK
40
+ index.
41
+
42
+ Migration from the previous release: the package was previously
43
+ published under a different name (pre-1.0). For one minor-version cycle,
44
+ an in-tree compatibility shim re-exports every public symbol under its
45
+ previous name and emits a `DeprecationWarning` on import. To migrate,
46
+ change your imports to the new top-level names — the rename map and an
47
+ example are below.
48
+
49
+ ```python
50
+ # old (still works for one minor cycle, with DeprecationWarning):
51
+ from theveil import TheVeil, TheVeilConfig
52
+
53
+ # new:
54
+ from lucairn import Lucairn, LucairnConfig
55
+ ```
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install lucairn
61
+ ```
62
+
63
+ Requires Python 3.10+.
64
+
65
+ ## Quickstart
66
+
67
+ ```python
68
+ from lucairn import Lucairn, LucairnConfig, VerifyCertificateKeys
69
+
70
+ client = Lucairn(LucairnConfig(api_key="dsa_..."))
71
+
72
+ # Proxy a prompt through the Lucairn gateway (split-knowledge routing).
73
+ response = client.messages({
74
+ "prompt_template": "Summarize the following in one sentence: {text}",
75
+ "context": {"text": "Long input..."},
76
+ "model": "claude-opus-4-7",
77
+ "max_tokens": 256,
78
+ })
79
+
80
+ # Fetch the Veil Certificate for a known request_id (Pro / Enterprise tier).
81
+ cert = client.get_certificate("req_abc123")
82
+
83
+ # Verify the witness Ed25519 signature against pinned trust-root keys.
84
+ keys = VerifyCertificateKeys(
85
+ witness_key_id="witness_v1",
86
+ witness_public_key="<base64 of raw 32-byte Ed25519 public key>",
87
+ )
88
+ result = client.verify_certificate(cert, keys)
89
+ print(result.overall_verdict, result.anchor_status)
90
+ ```
91
+
92
+ ## Public API
93
+
94
+ ### `Lucairn(config: LucairnConfig)`
95
+
96
+ Constructor validates every input up front:
97
+
98
+ - `api_key` must match `^dsa_[0-9a-f]{32}$`.
99
+ - `base_url` must be `http://` or `https://`; defaults to
100
+ `https://gateway.lucairn.eu`.
101
+ - `timeout` must be a positive finite number of **seconds** (default `30.0`).
102
+ TS SDK equivalent is `timeoutMs` (milliseconds) — Python uses seconds to
103
+ match `httpx` / `requests` / `openai-python` / `anthropic-python`.
104
+
105
+ ### `client.messages(params, options=None)`
106
+
107
+ POST to `/api/v1/proxy/messages`. Returns a discriminated union:
108
+
109
+ - `ProxySyncResponse` — terminal result (gateway returned 200).
110
+ - `ProxyAcceptedResponse` — async processing receipt (gateway returned 202,
111
+ body `status: "processing"`). Poll the `status_url` until completion.
112
+
113
+ ### `client.get_certificate(request_id, options=None)`
114
+
115
+ GET `/api/v1/veil/certificate/{request_id}`. Happy-path returns a
116
+ `VeilCertificate`. Gateway-side pending (certificate not yet assembled, or
117
+ unknown request_id — the gateway does not distinguish) surfaces as
118
+ `LucairnHttpError` with `status=202` and a body
119
+ `{"status": "pending", "retry_after_seconds": 30, ...}` so the happy-path
120
+ return stays narrow. Inspect `err.body["retry_after_seconds"]` for the
121
+ retry signal.
122
+
123
+ No auto-verification — chain `client.verify_certificate()` explicitly.
124
+
125
+ ### `client.get_certificate_summary(request_id, options=None)`
126
+
127
+ GET `/api/v1/veil/certificate/{request_id}/summary`. Returns the
128
+ DPO-friendly HTML summary as a UTF-8 `str`. Per the gateway source the
129
+ pending case renders an HTML body at HTTP 200 (not a 202 wrapper), so
130
+ the SDK passes the rendered HTML straight back to the caller.
131
+
132
+ ### `client.list_audit_events(opts=None)`
133
+
134
+ GET `/api/v1/audit/export`. Returns an `AuditExportResponse` with the
135
+ customer's audit events for the requested lookback window:
136
+
137
+ ```python
138
+ from lucairn import AuditExportOptions
139
+
140
+ resp = client.list_audit_events(AuditExportOptions(days=7, type="proxy.completed"))
141
+ print(resp.tier, resp.total_events)
142
+ for e in resp.events:
143
+ print(e.timestamp, e.event_type, e.request_id)
144
+ ```
145
+
146
+ - `days`: int 1..90 (gateway default 30, max 90).
147
+ - `type`: optional event-type filter.
148
+ - 503 `audit_export_unavailable` (tier-gated; not enabled for the calling
149
+ customer) raises `LucairnHttpError` with `err.status == 503` and
150
+ `err.body["code"] == "audit_export_unavailable"`.
151
+
152
+ ### `client.verify_certificate(cert, keys)`
153
+
154
+ Verify a certificate's witness Ed25519 signature against the certificate's
155
+ canonical-JSON signed subset. Returns `VerifyCertificateResult` on success.
156
+ Raises `LucairnCertificateError` with one of five reasons on failure:
157
+
158
+ | reason | condition |
159
+ |-----------------------------------|----------------------------------------------------------------------|
160
+ | `malformed` | cert shape invalid, gateway invariant broken, or unknown verdict |
161
+ | `unsupported_protocol_version` | `protocol_version != 2` |
162
+ | `witness_mismatch` | `keys.witness_key_id != cert.witness_key_id` |
163
+ | `witness_signature_missing` | empty or whitespace-only `witness_signature` |
164
+ | `invalid_signature` | Ed25519 verify failed, or key input malformed |
165
+
166
+ External RFC 3161 timestamp + Sigstore Rekor transparency-log verification
167
+ are out of scope for this release (pending upstream gateway fixes).
168
+
169
+ ### `lucairn.get_client_id(cert)`
170
+
171
+ Module-level helper returning `cert.client_id` (the org-scoped
172
+ correlation field added by W2A-B1) or `None` if the certificate predates
173
+ the change. The field is unsigned metadata at the witness signable
174
+ layer — tamper evidence flows indirectly through the bridge claim's
175
+ bridge-signed `canonical_payload`.
176
+
177
+ ## Error hierarchy
178
+
179
+ All SDK errors inherit from `LucairnError`:
180
+
181
+ - `LucairnConfigError` — bad constructor input or per-call option.
182
+ - `LucairnHttpError` — gateway returned non-2xx (or 202 from
183
+ `get_certificate`); exposes `.status` and `.body`.
184
+ - `LucairnResponseValidationError` — gateway returned 2xx but the body
185
+ doesn't fit the declared response type (typically a gateway bug or
186
+ version skew); exposes `.body` (raw response). The underlying
187
+ `pydantic.ValidationError` or `ValueError` is preserved on
188
+ `__cause__` for field-level inspection.
189
+ - `LucairnTimeoutError` — request exceeded timeout.
190
+ - `LucairnCertificateError` — `verify_certificate` failed; exposes
191
+ `.reason` and (when available) `.certificate_id`.
192
+
193
+ Catch `LucairnError` to handle all SDK errors uniformly.
194
+
195
+ ## Behavioural parity with TS
196
+
197
+ This SDK is cross-language byte-equivalent to the TS SDK for
198
+ `canonical_json` and `verify_certificate`. The Go-assembler-signed cert
199
+ fixture (`cert-go-signed-reference.json`) verifies identically in both.
200
+
201
+ Intentional divergences where TS semantics don't port cleanly to Python:
202
+
203
+ - **Timeout**: seconds (Python) vs. milliseconds (TS). Validator shape
204
+ identical (positive finite).
205
+ - **Abort/cancel**: v1 sync Python has timeout only; no `signal` analogue.
206
+ Cancellation arrives with the async client in a later arc.
207
+ - **Malformed 2xx body**: TS passes through as raw text typed as
208
+ `VeilCertificate` (thin transport); Python calls
209
+ `VeilCertificate.model_validate` and, on a shape mismatch, raises
210
+ the dedicated `LucairnResponseValidationError` — NOT
211
+ `LucairnHttpError`. The Python class follows the established
212
+ Python-SDK precedent (`openai.APIResponseValidationError`,
213
+ `anthropic.APIResponseValidationError`): an HTTP 200 is not an HTTP
214
+ error, and callers benefit from being able to catch "transport
215
+ failed" separately from "body doesn't fit the declared type." TS's
216
+ pass-through model remains the authoritative behaviour for the TS
217
+ surface; Python fails earlier (at fetch) because Pydantic validates
218
+ at deserialize-time, and the failure class names the reason
219
+ precisely instead of lying via `status=200`.
220
+ - **Error `.body` type on over-cap**: Python stores the preserved
221
+ prefix as `str` (UTF-8-decoded with `errors='replace'`) — idiomatic
222
+ for Python SDK callers used to `httpx.Response.text` / `.json()`.
223
+ The Go SDK stores `.Body` as `[]byte` for the same case — idiomatic
224
+ for Go callers used to `resp.Body`-style byte-slice access. Behaviour
225
+ parity holds at the "the prefix is preserved, bounded, and
226
+ diagnostic-readable" level; the representation is intentionally
227
+ language-idiomatic, not byte-identical.
228
+ - **Literal JSON null body**: when the gateway returns a 2xx with the
229
+ literal `null` payload, the parsed body is Python `None`; the SDK
230
+ falls back to the raw pre-parse text (`"null"`) for
231
+ `LucairnResponseValidationError.body` so callers can distinguish
232
+ "gateway sent null" from "SDK forgot to populate the error body."
233
+
234
+ ## Development
235
+
236
+ ```bash
237
+ cd python
238
+ pip install -e ".[dev]"
239
+ pytest
240
+ ```
241
+
242
+ Tests include a byte-for-byte cross-check of Python canonical-JSON output
243
+ against the Go assembler's reference hex, and end-to-end verification of
244
+ a real Go-assembler-signed certificate. If either fails, the SDK's Ed25519
245
+ verify will silently produce `invalid_signature` on valid certs — do not
246
+ skip or soft-fail those tests.
247
+
248
+ ## License
249
+
250
+ MIT — see [LICENSE](../LICENSE).
@@ -0,0 +1,219 @@
1
+ # lucairn — Python SDK
2
+
3
+ Client for **Lucairn** — privacy-preserving AI gateway.
4
+
5
+ ## Status
6
+
7
+ `1.0.0`. Ships alongside the TypeScript SDK and behaves identically at the
8
+ observable level. See the [monorepo README](../README.md) for the full SDK
9
+ index.
10
+
11
+ Migration from the previous release: the package was previously
12
+ published under a different name (pre-1.0). For one minor-version cycle,
13
+ an in-tree compatibility shim re-exports every public symbol under its
14
+ previous name and emits a `DeprecationWarning` on import. To migrate,
15
+ change your imports to the new top-level names — the rename map and an
16
+ example are below.
17
+
18
+ ```python
19
+ # old (still works for one minor cycle, with DeprecationWarning):
20
+ from theveil import TheVeil, TheVeilConfig
21
+
22
+ # new:
23
+ from lucairn import Lucairn, LucairnConfig
24
+ ```
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install lucairn
30
+ ```
31
+
32
+ Requires Python 3.10+.
33
+
34
+ ## Quickstart
35
+
36
+ ```python
37
+ from lucairn import Lucairn, LucairnConfig, VerifyCertificateKeys
38
+
39
+ client = Lucairn(LucairnConfig(api_key="dsa_..."))
40
+
41
+ # Proxy a prompt through the Lucairn gateway (split-knowledge routing).
42
+ response = client.messages({
43
+ "prompt_template": "Summarize the following in one sentence: {text}",
44
+ "context": {"text": "Long input..."},
45
+ "model": "claude-opus-4-7",
46
+ "max_tokens": 256,
47
+ })
48
+
49
+ # Fetch the Veil Certificate for a known request_id (Pro / Enterprise tier).
50
+ cert = client.get_certificate("req_abc123")
51
+
52
+ # Verify the witness Ed25519 signature against pinned trust-root keys.
53
+ keys = VerifyCertificateKeys(
54
+ witness_key_id="witness_v1",
55
+ witness_public_key="<base64 of raw 32-byte Ed25519 public key>",
56
+ )
57
+ result = client.verify_certificate(cert, keys)
58
+ print(result.overall_verdict, result.anchor_status)
59
+ ```
60
+
61
+ ## Public API
62
+
63
+ ### `Lucairn(config: LucairnConfig)`
64
+
65
+ Constructor validates every input up front:
66
+
67
+ - `api_key` must match `^dsa_[0-9a-f]{32}$`.
68
+ - `base_url` must be `http://` or `https://`; defaults to
69
+ `https://gateway.lucairn.eu`.
70
+ - `timeout` must be a positive finite number of **seconds** (default `30.0`).
71
+ TS SDK equivalent is `timeoutMs` (milliseconds) — Python uses seconds to
72
+ match `httpx` / `requests` / `openai-python` / `anthropic-python`.
73
+
74
+ ### `client.messages(params, options=None)`
75
+
76
+ POST to `/api/v1/proxy/messages`. Returns a discriminated union:
77
+
78
+ - `ProxySyncResponse` — terminal result (gateway returned 200).
79
+ - `ProxyAcceptedResponse` — async processing receipt (gateway returned 202,
80
+ body `status: "processing"`). Poll the `status_url` until completion.
81
+
82
+ ### `client.get_certificate(request_id, options=None)`
83
+
84
+ GET `/api/v1/veil/certificate/{request_id}`. Happy-path returns a
85
+ `VeilCertificate`. Gateway-side pending (certificate not yet assembled, or
86
+ unknown request_id — the gateway does not distinguish) surfaces as
87
+ `LucairnHttpError` with `status=202` and a body
88
+ `{"status": "pending", "retry_after_seconds": 30, ...}` so the happy-path
89
+ return stays narrow. Inspect `err.body["retry_after_seconds"]` for the
90
+ retry signal.
91
+
92
+ No auto-verification — chain `client.verify_certificate()` explicitly.
93
+
94
+ ### `client.get_certificate_summary(request_id, options=None)`
95
+
96
+ GET `/api/v1/veil/certificate/{request_id}/summary`. Returns the
97
+ DPO-friendly HTML summary as a UTF-8 `str`. Per the gateway source the
98
+ pending case renders an HTML body at HTTP 200 (not a 202 wrapper), so
99
+ the SDK passes the rendered HTML straight back to the caller.
100
+
101
+ ### `client.list_audit_events(opts=None)`
102
+
103
+ GET `/api/v1/audit/export`. Returns an `AuditExportResponse` with the
104
+ customer's audit events for the requested lookback window:
105
+
106
+ ```python
107
+ from lucairn import AuditExportOptions
108
+
109
+ resp = client.list_audit_events(AuditExportOptions(days=7, type="proxy.completed"))
110
+ print(resp.tier, resp.total_events)
111
+ for e in resp.events:
112
+ print(e.timestamp, e.event_type, e.request_id)
113
+ ```
114
+
115
+ - `days`: int 1..90 (gateway default 30, max 90).
116
+ - `type`: optional event-type filter.
117
+ - 503 `audit_export_unavailable` (tier-gated; not enabled for the calling
118
+ customer) raises `LucairnHttpError` with `err.status == 503` and
119
+ `err.body["code"] == "audit_export_unavailable"`.
120
+
121
+ ### `client.verify_certificate(cert, keys)`
122
+
123
+ Verify a certificate's witness Ed25519 signature against the certificate's
124
+ canonical-JSON signed subset. Returns `VerifyCertificateResult` on success.
125
+ Raises `LucairnCertificateError` with one of five reasons on failure:
126
+
127
+ | reason | condition |
128
+ |-----------------------------------|----------------------------------------------------------------------|
129
+ | `malformed` | cert shape invalid, gateway invariant broken, or unknown verdict |
130
+ | `unsupported_protocol_version` | `protocol_version != 2` |
131
+ | `witness_mismatch` | `keys.witness_key_id != cert.witness_key_id` |
132
+ | `witness_signature_missing` | empty or whitespace-only `witness_signature` |
133
+ | `invalid_signature` | Ed25519 verify failed, or key input malformed |
134
+
135
+ External RFC 3161 timestamp + Sigstore Rekor transparency-log verification
136
+ are out of scope for this release (pending upstream gateway fixes).
137
+
138
+ ### `lucairn.get_client_id(cert)`
139
+
140
+ Module-level helper returning `cert.client_id` (the org-scoped
141
+ correlation field added by W2A-B1) or `None` if the certificate predates
142
+ the change. The field is unsigned metadata at the witness signable
143
+ layer — tamper evidence flows indirectly through the bridge claim's
144
+ bridge-signed `canonical_payload`.
145
+
146
+ ## Error hierarchy
147
+
148
+ All SDK errors inherit from `LucairnError`:
149
+
150
+ - `LucairnConfigError` — bad constructor input or per-call option.
151
+ - `LucairnHttpError` — gateway returned non-2xx (or 202 from
152
+ `get_certificate`); exposes `.status` and `.body`.
153
+ - `LucairnResponseValidationError` — gateway returned 2xx but the body
154
+ doesn't fit the declared response type (typically a gateway bug or
155
+ version skew); exposes `.body` (raw response). The underlying
156
+ `pydantic.ValidationError` or `ValueError` is preserved on
157
+ `__cause__` for field-level inspection.
158
+ - `LucairnTimeoutError` — request exceeded timeout.
159
+ - `LucairnCertificateError` — `verify_certificate` failed; exposes
160
+ `.reason` and (when available) `.certificate_id`.
161
+
162
+ Catch `LucairnError` to handle all SDK errors uniformly.
163
+
164
+ ## Behavioural parity with TS
165
+
166
+ This SDK is cross-language byte-equivalent to the TS SDK for
167
+ `canonical_json` and `verify_certificate`. The Go-assembler-signed cert
168
+ fixture (`cert-go-signed-reference.json`) verifies identically in both.
169
+
170
+ Intentional divergences where TS semantics don't port cleanly to Python:
171
+
172
+ - **Timeout**: seconds (Python) vs. milliseconds (TS). Validator shape
173
+ identical (positive finite).
174
+ - **Abort/cancel**: v1 sync Python has timeout only; no `signal` analogue.
175
+ Cancellation arrives with the async client in a later arc.
176
+ - **Malformed 2xx body**: TS passes through as raw text typed as
177
+ `VeilCertificate` (thin transport); Python calls
178
+ `VeilCertificate.model_validate` and, on a shape mismatch, raises
179
+ the dedicated `LucairnResponseValidationError` — NOT
180
+ `LucairnHttpError`. The Python class follows the established
181
+ Python-SDK precedent (`openai.APIResponseValidationError`,
182
+ `anthropic.APIResponseValidationError`): an HTTP 200 is not an HTTP
183
+ error, and callers benefit from being able to catch "transport
184
+ failed" separately from "body doesn't fit the declared type." TS's
185
+ pass-through model remains the authoritative behaviour for the TS
186
+ surface; Python fails earlier (at fetch) because Pydantic validates
187
+ at deserialize-time, and the failure class names the reason
188
+ precisely instead of lying via `status=200`.
189
+ - **Error `.body` type on over-cap**: Python stores the preserved
190
+ prefix as `str` (UTF-8-decoded with `errors='replace'`) — idiomatic
191
+ for Python SDK callers used to `httpx.Response.text` / `.json()`.
192
+ The Go SDK stores `.Body` as `[]byte` for the same case — idiomatic
193
+ for Go callers used to `resp.Body`-style byte-slice access. Behaviour
194
+ parity holds at the "the prefix is preserved, bounded, and
195
+ diagnostic-readable" level; the representation is intentionally
196
+ language-idiomatic, not byte-identical.
197
+ - **Literal JSON null body**: when the gateway returns a 2xx with the
198
+ literal `null` payload, the parsed body is Python `None`; the SDK
199
+ falls back to the raw pre-parse text (`"null"`) for
200
+ `LucairnResponseValidationError.body` so callers can distinguish
201
+ "gateway sent null" from "SDK forgot to populate the error body."
202
+
203
+ ## Development
204
+
205
+ ```bash
206
+ cd python
207
+ pip install -e ".[dev]"
208
+ pytest
209
+ ```
210
+
211
+ Tests include a byte-for-byte cross-check of Python canonical-JSON output
212
+ against the Go assembler's reference hex, and end-to-end verification of
213
+ a real Go-assembler-signed certificate. If either fails, the SDK's Ed25519
214
+ verify will silently produce `invalid_signature` on valid certs — do not
215
+ skip or soft-fail those tests.
216
+
217
+ ## License
218
+
219
+ MIT — see [LICENSE](../LICENSE).
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lucairn"
7
+ version = "1.0.0"
8
+ description = "Lucairn — privacy-preserving AI gateway client for Python"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Declade" }]
13
+ keywords = [
14
+ "privacy",
15
+ "gdpr",
16
+ "llm",
17
+ "ai",
18
+ "pii",
19
+ "pseudonymization",
20
+ "eu-ai-act",
21
+ "anthropic",
22
+ "openai",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Software Development :: Libraries :: Python Modules",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = [
38
+ "httpx>=0.27",
39
+ "pydantic>=2.6",
40
+ "cryptography>=42",
41
+ ]
42
+
43
+ [project.optional-dependencies]
44
+ dev = [
45
+ "pytest>=8.0",
46
+ "respx>=0.21",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://lucairn.eu"
51
+ Repository = "https://github.com/Declade/theveil-sdks"
52
+ Issues = "https://github.com/Declade/theveil-sdks/issues"
53
+ Changelog = "https://github.com/Declade/theveil-sdks/blob/main/CHANGELOG.md"
54
+
55
+ [tool.hatch.build.targets.wheel]
56
+ # Ship the new `lucairn` package and the in-tree compatibility shim so
57
+ # existing callers can keep their imports for one minor-version cycle.
58
+ # See python/README.md for the rename map.
59
+ packages = ["src/lucairn", "src/theveil"]
60
+
61
+ [tool.hatch.build.targets.sdist]
62
+ include = [
63
+ "/src/lucairn",
64
+ "/src/theveil",
65
+ "/README.md",
66
+ "/pyproject.toml",
67
+ ]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ python_files = ["test_*.py"]
72
+ python_classes = ["Test*"]
73
+ python_functions = ["test_*"]
74
+ addopts = "-ra --strict-markers"
@@ -0,0 +1,79 @@
1
+ from lucairn.client import Lucairn
2
+ from lucairn.errors import (
3
+ LucairnCertificateError,
4
+ LucairnConfigError,
5
+ LucairnError,
6
+ LucairnHttpError,
7
+ LucairnResponseValidationError,
8
+ LucairnTimeoutError,
9
+ )
10
+ from lucairn.types import (
11
+ AuditEntry,
12
+ AuditExportOptions,
13
+ AuditExportResponse,
14
+ MessagesOptions,
15
+ ProxyAcceptedResponse,
16
+ ProxyMessagesRequest,
17
+ ProxyPIIAnnotation,
18
+ ProxyRequest,
19
+ ProxyResponse,
20
+ ProxySyncResponse,
21
+ ProxyVeilReceipt,
22
+ LucairnConfig,
23
+ VeilAnchorStatusInfo,
24
+ VeilCertificate,
25
+ VeilClaim,
26
+ VeilExternalAttestation,
27
+ VeilVerificationResult,
28
+ VerifyCertificateFailureReason,
29
+ VerifyCertificateKeys,
30
+ VerifyCertificateResult,
31
+ )
32
+
33
+
34
+ def get_client_id(cert: VeilCertificate) -> str | None:
35
+ """Return ``cert.client_id`` (the org-scoped correlation field) or
36
+ ``None`` if the certificate predates W2A-B1 or the gateway omitted
37
+ the field.
38
+
39
+ The field is unsigned metadata at the witness signable layer (see
40
+ :class:`VeilCertificate` docstring); tamper evidence flows
41
+ indirectly through the bridge claim's bridge-signed
42
+ ``canonical_payload``.
43
+ """
44
+
45
+ return cert.client_id
46
+
47
+
48
+ __all__ = [
49
+ "AuditEntry",
50
+ "AuditExportOptions",
51
+ "AuditExportResponse",
52
+ "MessagesOptions",
53
+ "ProxyAcceptedResponse",
54
+ "ProxyMessagesRequest",
55
+ "ProxyPIIAnnotation",
56
+ "ProxyRequest",
57
+ "ProxyResponse",
58
+ "ProxySyncResponse",
59
+ "ProxyVeilReceipt",
60
+ "Lucairn",
61
+ "LucairnCertificateError",
62
+ "LucairnConfig",
63
+ "LucairnConfigError",
64
+ "LucairnError",
65
+ "LucairnHttpError",
66
+ "LucairnResponseValidationError",
67
+ "LucairnTimeoutError",
68
+ "VeilAnchorStatusInfo",
69
+ "VeilCertificate",
70
+ "VeilClaim",
71
+ "VeilExternalAttestation",
72
+ "VeilVerificationResult",
73
+ "VerifyCertificateFailureReason",
74
+ "VerifyCertificateKeys",
75
+ "VerifyCertificateResult",
76
+ "get_client_id",
77
+ ]
78
+
79
+ __version__ = "1.0.0"