ski-sdk 0.1.0__py3-none-any.whl

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.
ski_sdk/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """ski-sdk — typed Python client for the SKI Framework SKI Model.
2
+
3
+ > Early alpha: wraps an alpha HTTP API; pin your versions.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from .client import AsyncSKIClient, SKIClient
9
+ from .errors import (
10
+ SKIAuthError,
11
+ SKIError,
12
+ SKIResponseError,
13
+ SKIServiceUnavailable,
14
+ SKITransportError,
15
+ SKIValidationError,
16
+ )
17
+ from .models import (
18
+ HealthStatus,
19
+ LLMTranscript,
20
+ MeasurementRecord,
21
+ V3Verdict,
22
+ V3VerdictEnvelope,
23
+ VerifierStatus,
24
+ )
25
+ from .verify import VerificationReport, verify_transcript
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ __all__ = [
30
+ "AsyncSKIClient",
31
+ "HealthStatus",
32
+ "LLMTranscript",
33
+ "MeasurementRecord",
34
+ "SKIAuthError",
35
+ "SKIClient",
36
+ "SKIError",
37
+ "SKIResponseError",
38
+ "SKIServiceUnavailable",
39
+ "SKITransportError",
40
+ "SKIValidationError",
41
+ "V3Verdict",
42
+ "V3VerdictEnvelope",
43
+ "VerificationReport",
44
+ "VerifierStatus",
45
+ "__version__",
46
+ "verify_transcript",
47
+ ]
ski_sdk/client.py ADDED
@@ -0,0 +1,242 @@
1
+ """Synchronous and asynchronous HTTP clients for the SKI Model.
2
+
3
+ Thin wrappers over httpx that send the API key as the ``x-api-key`` header,
4
+ verify TLS by default, parse responses into typed models, and map HTTP status
5
+ codes onto the SDK error hierarchy. Idempotent GETs are retried with backoff;
6
+ non-idempotent POSTs are not.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import time
13
+ from typing import Any, Dict, Optional, Union
14
+
15
+ import httpx
16
+
17
+ from .errors import (
18
+ SKIAuthError,
19
+ SKIResponseError,
20
+ SKIServiceUnavailable,
21
+ SKITransportError,
22
+ SKIValidationError,
23
+ )
24
+ from .models import HealthStatus, MeasurementRecord, V3VerdictEnvelope
25
+
26
+ _DEFAULT_TIMEOUT = 30.0
27
+
28
+
29
+ def _detail(resp: httpx.Response) -> Optional[str]:
30
+ try:
31
+ body = resp.json()
32
+ return body.get("detail") if isinstance(body, dict) else None
33
+ except Exception:
34
+ return None
35
+
36
+
37
+ def _raise_for_status(resp: httpx.Response) -> None:
38
+ if resp.status_code < 400:
39
+ return
40
+ detail = _detail(resp)
41
+ if resp.status_code == 401:
42
+ raise SKIAuthError(401, detail)
43
+ if resp.status_code == 503:
44
+ raise SKIServiceUnavailable(503, detail)
45
+ if 400 <= resp.status_code < 500:
46
+ raise SKIValidationError(resp.status_code, detail)
47
+ raise SKIResponseError(resp.status_code, detail)
48
+
49
+
50
+ def _headers(api_key: Optional[str]) -> Dict[str, str]:
51
+ return {"x-api-key": api_key} if api_key else {}
52
+
53
+
54
+ class _Base:
55
+ def __init__(
56
+ self,
57
+ endpoint: str,
58
+ api_key: Optional[str] = None,
59
+ *,
60
+ verify_tls: Union[bool, str] = True,
61
+ timeout: float = _DEFAULT_TIMEOUT,
62
+ max_retries: int = 2,
63
+ ) -> None:
64
+ self._endpoint = endpoint.rstrip("/")
65
+ self._api_key = api_key
66
+ self._max_retries = max_retries
67
+
68
+ def __repr__(self) -> str: # never leak the API key
69
+ return (
70
+ f"{type(self).__name__}(endpoint={self._endpoint!r}, api_key={'***' if self._api_key else None})"
71
+ )
72
+
73
+
74
+ class SKIClient(_Base):
75
+ """Synchronous SKI Model client."""
76
+
77
+ def __init__(
78
+ self,
79
+ endpoint: str,
80
+ api_key: Optional[str] = None,
81
+ *,
82
+ verify_tls: Union[bool, str] = True,
83
+ timeout: float = _DEFAULT_TIMEOUT,
84
+ max_retries: int = 2,
85
+ transport: Optional[httpx.BaseTransport] = None,
86
+ ) -> None:
87
+ super().__init__(endpoint, api_key, verify_tls=verify_tls, timeout=timeout, max_retries=max_retries)
88
+ self._client = httpx.Client(
89
+ base_url=self._endpoint,
90
+ verify=verify_tls,
91
+ timeout=timeout,
92
+ headers=_headers(api_key),
93
+ transport=transport,
94
+ )
95
+
96
+ def __enter__(self) -> SKIClient:
97
+ return self
98
+
99
+ def __exit__(self, *exc: object) -> None:
100
+ self.close()
101
+
102
+ def close(self) -> None:
103
+ self._client.close()
104
+
105
+ def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> httpx.Response:
106
+ attempt = 0
107
+ while True:
108
+ try:
109
+ resp = self._client.get(path, params=params)
110
+ except httpx.RequestError as exc:
111
+ if attempt >= self._max_retries:
112
+ raise SKITransportError(str(exc)) from exc
113
+ else:
114
+ if resp.status_code < 500 or attempt >= self._max_retries:
115
+ _raise_for_status(resp)
116
+ return resp
117
+ time.sleep(0.25 * (2**attempt))
118
+ attempt += 1
119
+
120
+ def _post(self, path: str, json_body: Dict[str, Any]) -> httpx.Response:
121
+ try:
122
+ resp = self._client.post(path, json=json_body)
123
+ except httpx.RequestError as exc:
124
+ raise SKITransportError(str(exc)) from exc
125
+ _raise_for_status(resp)
126
+ return resp
127
+
128
+ def evaluate(
129
+ self,
130
+ *,
131
+ measurement_id: str,
132
+ timestamp: str,
133
+ subject: str,
134
+ measurement: Dict[str, Any],
135
+ jurisdiction: Optional[str] = None,
136
+ ) -> V3VerdictEnvelope:
137
+ body = MeasurementRecord(
138
+ measurement_id=measurement_id,
139
+ timestamp=timestamp,
140
+ subject=subject,
141
+ measurement=measurement,
142
+ jurisdiction=jurisdiction,
143
+ ).model_dump()
144
+ return V3VerdictEnvelope.model_validate(self._post("/api/evaluate", body).json())
145
+
146
+ def health(self) -> HealthStatus:
147
+ return HealthStatus.model_validate(self._get("/api/health").json())
148
+
149
+ def list_verdicts(self, *, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
150
+ return dict(self._get("/api/verdicts", params={"limit": limit, "offset": offset}).json())
151
+
152
+ def load_kg(self, signed_kg: Dict[str, Any]) -> Dict[str, Any]:
153
+ return dict(self._post("/api/kg/load", signed_kg).json())
154
+
155
+
156
+ class AsyncSKIClient(_Base):
157
+ """Asynchronous SKI Model client (mirrors :class:`SKIClient`)."""
158
+
159
+ def __init__(
160
+ self,
161
+ endpoint: str,
162
+ api_key: Optional[str] = None,
163
+ *,
164
+ verify_tls: Union[bool, str] = True,
165
+ timeout: float = _DEFAULT_TIMEOUT,
166
+ max_retries: int = 2,
167
+ transport: Optional[httpx.AsyncBaseTransport] = None,
168
+ ) -> None:
169
+ super().__init__(endpoint, api_key, verify_tls=verify_tls, timeout=timeout, max_retries=max_retries)
170
+ self._client = httpx.AsyncClient(
171
+ base_url=self._endpoint,
172
+ verify=verify_tls,
173
+ timeout=timeout,
174
+ headers=_headers(api_key),
175
+ transport=transport,
176
+ )
177
+
178
+ async def __aenter__(self) -> AsyncSKIClient:
179
+ return self
180
+
181
+ async def __aexit__(self, *exc: object) -> None:
182
+ await self.aclose()
183
+
184
+ async def aclose(self) -> None:
185
+ await self._client.aclose()
186
+
187
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> httpx.Response:
188
+ attempt = 0
189
+ while True:
190
+ try:
191
+ resp = await self._client.get(path, params=params)
192
+ except httpx.RequestError as exc:
193
+ if attempt >= self._max_retries:
194
+ raise SKITransportError(str(exc)) from exc
195
+ else:
196
+ if resp.status_code < 500 or attempt >= self._max_retries:
197
+ _raise_for_status(resp)
198
+ return resp
199
+ await asyncio.sleep(0.25 * (2**attempt))
200
+ attempt += 1
201
+
202
+ async def _post(self, path: str, json_body: Dict[str, Any]) -> httpx.Response:
203
+ try:
204
+ resp = await self._client.post(path, json=json_body)
205
+ except httpx.RequestError as exc:
206
+ raise SKITransportError(str(exc)) from exc
207
+ _raise_for_status(resp)
208
+ return resp
209
+
210
+ async def evaluate(
211
+ self,
212
+ *,
213
+ measurement_id: str,
214
+ timestamp: str,
215
+ subject: str,
216
+ measurement: Dict[str, Any],
217
+ jurisdiction: Optional[str] = None,
218
+ ) -> V3VerdictEnvelope:
219
+ body = MeasurementRecord(
220
+ measurement_id=measurement_id,
221
+ timestamp=timestamp,
222
+ subject=subject,
223
+ measurement=measurement,
224
+ jurisdiction=jurisdiction,
225
+ ).model_dump()
226
+ resp = await self._post("/api/evaluate", body)
227
+ return V3VerdictEnvelope.model_validate(resp.json())
228
+
229
+ async def health(self) -> HealthStatus:
230
+ resp = await self._get("/api/health")
231
+ return HealthStatus.model_validate(resp.json())
232
+
233
+ async def list_verdicts(self, *, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
234
+ resp = await self._get("/api/verdicts", params={"limit": limit, "offset": offset})
235
+ return dict(resp.json())
236
+
237
+ async def load_kg(self, signed_kg: Dict[str, Any]) -> Dict[str, Any]:
238
+ resp = await self._post("/api/kg/load", signed_kg)
239
+ return dict(resp.json())
240
+
241
+
242
+ __all__ = ["AsyncSKIClient", "SKIClient"]
ski_sdk/errors.py ADDED
@@ -0,0 +1,48 @@
1
+ """Typed error hierarchy for the SKI SDK.
2
+
3
+ Callers branch on the exception type rather than parsing strings. The API
4
+ key is never included in any message or repr.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+
12
+ class SKIError(Exception):
13
+ """Base class for all SKI SDK errors."""
14
+
15
+
16
+ class SKITransportError(SKIError):
17
+ """Network/TLS failure reaching the SKI Model (no HTTP response)."""
18
+
19
+
20
+ class SKIResponseError(SKIError):
21
+ """The SKI Model returned an HTTP error status."""
22
+
23
+ def __init__(self, status_code: int, detail: Optional[str] = None) -> None:
24
+ self.status_code = status_code
25
+ self.detail = detail
26
+ super().__init__(f"SKI Model returned HTTP {status_code}" + (f": {detail}" if detail else ""))
27
+
28
+
29
+ class SKIAuthError(SKIResponseError):
30
+ """401 — missing or invalid API key."""
31
+
32
+
33
+ class SKIServiceUnavailable(SKIResponseError):
34
+ """503 — service not ready (e.g. no Knowledge Graph loaded)."""
35
+
36
+
37
+ class SKIValidationError(SKIResponseError):
38
+ """4xx — the request payload was rejected by the SKI Model."""
39
+
40
+
41
+ __all__ = [
42
+ "SKIAuthError",
43
+ "SKIError",
44
+ "SKIResponseError",
45
+ "SKIServiceUnavailable",
46
+ "SKITransportError",
47
+ "SKIValidationError",
48
+ ]
ski_sdk/models.py ADDED
@@ -0,0 +1,145 @@
1
+ """Wire-contract models, mirrored from the SKI Model v3 envelope (spec §4-§6).
2
+
3
+ These intentionally duplicate the server's Pydantic models so the SDK has no
4
+ dependency on the reference implementation. A contract-drift test
5
+ (`tests/test_contract_drift.py`) asserts the field sets stay in lock-step with
6
+ the server's `V3VerdictEnvelope` / `MeasurementRecord` / `LLMTranscript`.
7
+
8
+ Response models use ``extra="ignore"`` so a forward-compatible server that adds
9
+ a field does not break older SDKs at runtime; the drift test is what flags the
10
+ divergence in CI.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from datetime import datetime
16
+ from enum import Enum
17
+ from typing import Any, Dict, List, Optional, Union
18
+
19
+ from pydantic import BaseModel, ConfigDict, Field
20
+
21
+ _RESPONSE = ConfigDict(use_enum_values=True, extra="ignore", protected_namespaces=())
22
+
23
+
24
+ class V3Verdict(str, Enum):
25
+ CLEAR = "CLEAR"
26
+ FLAG = "FLAG"
27
+ DISCRETIONARY = "DISCRETIONARY"
28
+ NULL_UNMAPPED = "NULL_UNMAPPED"
29
+ NULL_STALE = "NULL_STALE"
30
+
31
+
32
+ class VerifierStatus(str, Enum):
33
+ AGREED = "AGREED"
34
+ LLM_CONTRADICTION = "LLM_CONTRADICTION"
35
+ NEURO_SYMBOLIC_DIVERGENCE = "NEURO_SYMBOLIC_DIVERGENCE"
36
+ UNVERIFIABLE = "UNVERIFIABLE"
37
+
38
+
39
+ class KGCitationRole(str, Enum):
40
+ OBLIGATION = "obligation"
41
+ DEFINITION = "definition"
42
+ PRECEDENT = "precedent"
43
+
44
+
45
+ class KGCitation(BaseModel):
46
+ model_config = _RESPONSE
47
+ node_id: str
48
+ version: str
49
+ role: str
50
+
51
+
52
+ class FormalizableAssertion(BaseModel):
53
+ model_config = _RESPONSE
54
+ predicate: str
55
+ metric: str
56
+ value: Union[float, int, str, List[Any], None] = None
57
+ observed: Union[float, int, str, List[Any], None] = None
58
+ satisfied: bool
59
+ obligation_id: str
60
+ window_seconds: Optional[int] = None
61
+
62
+
63
+ class VerifierResult(BaseModel):
64
+ model_config = _RESPONSE
65
+ status: str
66
+ checked_assertions: int = 0
67
+ divergences: List[str] = Field(default_factory=list)
68
+
69
+
70
+ class ModelProvenance(BaseModel):
71
+ model_config = _RESPONSE
72
+ model_weight_hash: str
73
+ kg_version_hash: str
74
+ prompt_template_id: str
75
+ prompt_template_hash: str
76
+ decoder_seed: int
77
+ structured_grammar_hash: str
78
+
79
+
80
+ class V3VerdictEnvelope(BaseModel):
81
+ model_config = _RESPONSE
82
+ verdict: str
83
+ reasoning: str
84
+ kg_citations: List[KGCitation] = Field(default_factory=list)
85
+ formalizable_assertions: List[FormalizableAssertion] = Field(default_factory=list)
86
+ verifier_result: VerifierResult
87
+ model_provenance: ModelProvenance
88
+ transcript_ref: str
89
+ human_attestation: Optional[Dict[str, Any]] = None
90
+ notes: List[str] = Field(default_factory=list)
91
+ verdict_path: Optional[str] = None
92
+
93
+
94
+ class MeasurementRecord(BaseModel):
95
+ """Request body for ``POST /api/evaluate``."""
96
+
97
+ model_config = ConfigDict(extra="forbid")
98
+ measurement_id: str
99
+ timestamp: str
100
+ subject: str
101
+ measurement: Dict[str, Any] = Field(default_factory=dict)
102
+ jurisdiction: Optional[str] = None
103
+
104
+
105
+ class LLMTranscript(BaseModel):
106
+ """The signed transcript an auditor verifies (spec §6.2)."""
107
+
108
+ model_config = ConfigDict(extra="ignore")
109
+ transcript_id: str
110
+ request_canonical: str
111
+ request_hash: str
112
+ response_canonical: Dict[str, Any]
113
+ response_hash: str
114
+ signature_hex: str
115
+ signing_key_id: str
116
+ backend_name: str
117
+ backend_metadata: Dict[str, Any] = Field(default_factory=dict)
118
+ started_at: datetime
119
+ completed_at: datetime
120
+
121
+
122
+ class HealthStatus(BaseModel):
123
+ model_config = _RESPONSE
124
+ status: str
125
+ kg_loaded: bool
126
+ kg_signature_verified: bool
127
+ canary_status: str
128
+ verdicts_produced: int
129
+ timestamp: str
130
+ runtime_version: str
131
+
132
+
133
+ __all__ = [
134
+ "FormalizableAssertion",
135
+ "HealthStatus",
136
+ "KGCitation",
137
+ "KGCitationRole",
138
+ "LLMTranscript",
139
+ "MeasurementRecord",
140
+ "ModelProvenance",
141
+ "V3Verdict",
142
+ "V3VerdictEnvelope",
143
+ "VerifierResult",
144
+ "VerifierStatus",
145
+ ]
ski_sdk/py.typed ADDED
File without changes
ski_sdk/verify.py ADDED
@@ -0,0 +1,80 @@
1
+ """Verify the signed provenance of a verdict's LLM transcript (spec §6.2).
2
+
3
+ Re-implements the framework's canonicalisation and Ed25519 verification so the
4
+ SDK depends only on `cryptography` — not on the reference implementation. The
5
+ runtime signs ``request_hash || "|" || response_hash``; an auditor recomputes
6
+ those hashes from the canonical request/response, then verifies the signature
7
+ against the runtime's public key.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ from dataclasses import dataclass
15
+ from typing import Any, Dict, Mapping, Union
16
+
17
+ from cryptography.exceptions import InvalidSignature
18
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
19
+ from cryptography.hazmat.primitives.serialization import load_pem_public_key
20
+
21
+ from .models import LLMTranscript
22
+
23
+
24
+ def _sha256_prefixed(data: bytes) -> str:
25
+ return "sha256:" + hashlib.sha256(data).hexdigest()
26
+
27
+
28
+ def _canonical_response(response: Mapping[str, Any]) -> bytes:
29
+ return json.dumps(dict(response), sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode(
30
+ "utf-8"
31
+ )
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class VerificationReport:
36
+ """Result of verifying a transcript."""
37
+
38
+ signature_valid: bool
39
+ hashes_match: bool
40
+ ok: bool
41
+
42
+ def __bool__(self) -> bool:
43
+ return self.ok
44
+
45
+
46
+ def verify_transcript(
47
+ transcript: Union[LLMTranscript, Dict[str, Any]],
48
+ public_key_pem: Union[str, bytes],
49
+ *,
50
+ strict: bool = True,
51
+ ) -> VerificationReport:
52
+ """Verify a transcript's Ed25519 signature (and, in strict mode, its hashes).
53
+
54
+ ``strict=True`` (default) also recomputes ``request_hash``/``response_hash``
55
+ from the canonical request/response and confirms they match the recorded
56
+ values — catching a response that was altered after signing.
57
+ """
58
+ t = transcript if isinstance(transcript, LLMTranscript) else LLMTranscript.model_validate(transcript)
59
+
60
+ recomputed_req = _sha256_prefixed(t.request_canonical.encode("utf-8"))
61
+ recomputed_resp = _sha256_prefixed(_canonical_response(t.response_canonical))
62
+ hashes_match = recomputed_req == t.request_hash and recomputed_resp == t.response_hash
63
+
64
+ pem = public_key_pem.encode("ascii") if isinstance(public_key_pem, str) else public_key_pem
65
+ key = load_pem_public_key(pem)
66
+ if not isinstance(key, Ed25519PublicKey):
67
+ raise ValueError("public_key_pem is not an Ed25519 public key.")
68
+
69
+ message = f"{t.request_hash}|{t.response_hash}".encode("ascii")
70
+ try:
71
+ key.verify(bytes.fromhex(t.signature_hex), message)
72
+ signature_valid = True
73
+ except (InvalidSignature, ValueError):
74
+ signature_valid = False
75
+
76
+ ok = signature_valid and (hashes_match or not strict)
77
+ return VerificationReport(signature_valid=signature_valid, hashes_match=hashes_match, ok=ok)
78
+
79
+
80
+ __all__ = ["VerificationReport", "verify_transcript"]
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: ski-sdk
3
+ Version: 0.1.0
4
+ Summary: Typed Python client for the SKI Framework SKI Model, with one-call provenance verification.
5
+ Author: KpiFinity Inc.
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/kpifinity/ski-framework
8
+ Project-URL: Documentation, https://kpifinity.github.io/ski-framework/
9
+ Project-URL: Issues, https://github.com/kpifinity/ski-framework/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: httpx<0.29,>=0.27
17
+ Requires-Dist: pydantic<3,>=2.13.4
18
+ Requires-Dist: cryptography<48,>=46
19
+
20
+ # ski-sdk
21
+
22
+ > Install from PyPI: `pip install ski-sdk` (publishing starts with the first release after June 2026).
23
+
24
+ Typed Python client for the [SKI Framework](https://github.com/kpifinity/ski-framework)
25
+ SKI Model, with one-call verification of a verdict's signed provenance.
26
+
27
+ > **Status: early alpha.** This wraps an alpha HTTP API; pin your versions. The
28
+ > SDK is versioned independently of the framework — see the compatibility note
29
+ > in the docs.
30
+
31
+ ```python
32
+ from ski_sdk import SKIClient
33
+
34
+ client = SKIClient(endpoint="https://ski.internal:8000", api_key="…")
35
+ env = client.evaluate(
36
+ measurement_id="m-001",
37
+ timestamp="2026-06-05T12:00:00Z",
38
+ subject="stack-7",
39
+ measurement={"so2_ppm": 150},
40
+ )
41
+ print(env.verdict, [c.node_id for c in env.kg_citations]) # FLAG ['energy.so2.lte_100ppm']
42
+ ```
43
+
44
+ Verify a verdict's signed transcript (tamper-evident provenance):
45
+
46
+ ```python
47
+ from ski_sdk import verify_transcript
48
+
49
+ report = verify_transcript(transcript, public_key_pem)
50
+ assert report.ok # signature valid AND recorded hashes match the canonical pair
51
+ ```
@@ -0,0 +1,10 @@
1
+ ski_sdk/__init__.py,sha256=UmYrKdo5ELLyPnkq11blKR2DkXGevUeklgjOPoPaBOo,984
2
+ ski_sdk/client.py,sha256=huuDLjDuV1sOI8bTT40R9K8qWdst293MeG7fm8k9fmA,7824
3
+ ski_sdk/errors.py,sha256=B9nveH9_srhwPRellx9HoQQ0AW3mGUWVu5g-B3U6WOU,1235
4
+ ski_sdk/models.py,sha256=K6_mkv8Gp8yXgbr2rbpy6bt0rVLyvkeSh6C-gGa4rc8,3891
5
+ ski_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ ski_sdk/verify.py,sha256=dERYk5-u-KgIpZdcRYYD9MYGeTIviYtwv8HxIEuORRQ,2878
7
+ ski_sdk-0.1.0.dist-info/METADATA,sha256=Kab6MIiLjowBqZJBYnHOKuTmHJUiEK3Ahf47SdNRJ9Q,1840
8
+ ski_sdk-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ ski_sdk-0.1.0.dist-info/top_level.txt,sha256=wXpUfrUOWsOSAPCLcuQ7fo6mKx1cjurpveP9keBPfKQ,8
10
+ ski_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ski_sdk