ski-schemas 0.1.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,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: ski-schemas
3
+ Version: 0.1.0
4
+ Summary: SKI Framework wire models: verdict envelope, signed LLM transcript, measurement record. One definition, no drift (RFC 0003).
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: pydantic<3,>=2.5
17
+
18
+ # ski-schemas
19
+
20
+ > Install from PyPI: `pip install ski-schemas`
21
+
22
+ The [SKI Framework](https://github.com/kpifinity/ski-framework) wire
23
+ models — the v3 verdict envelope, the signed LLM transcript, and the
24
+ measurement record — extracted into one dependency-light package per
25
+ RFC 0003. The server, the `ski-sdk` client, and the conformance suite
26
+ all import these models, so the wire contract has exactly one
27
+ definition.
28
+
29
+ ```python
30
+ from ski_schemas import V3VerdictEnvelope
31
+
32
+ envelope = V3VerdictEnvelope.model_validate_json(raw)
33
+ print(envelope.verdict, envelope.model_provenance.kg_version_hash)
34
+ ```
35
+
36
+ Versioned independently of the framework (it carries a wire contract,
37
+ not a runtime). Dependencies: `pydantic` only.
@@ -0,0 +1,20 @@
1
+ # ski-schemas
2
+
3
+ > Install from PyPI: `pip install ski-schemas`
4
+
5
+ The [SKI Framework](https://github.com/kpifinity/ski-framework) wire
6
+ models — the v3 verdict envelope, the signed LLM transcript, and the
7
+ measurement record — extracted into one dependency-light package per
8
+ RFC 0003. The server, the `ski-sdk` client, and the conformance suite
9
+ all import these models, so the wire contract has exactly one
10
+ definition.
11
+
12
+ ```python
13
+ from ski_schemas import V3VerdictEnvelope
14
+
15
+ envelope = V3VerdictEnvelope.model_validate_json(raw)
16
+ print(envelope.verdict, envelope.model_provenance.kg_version_hash)
17
+ ```
18
+
19
+ Versioned independently of the framework (it carries a wire contract,
20
+ not a runtime). Dependencies: `pydantic` only.
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ski-schemas"
7
+ version = "0.1.0"
8
+ description = "SKI Framework wire models: verdict envelope, signed LLM transcript, measurement record. One definition, no drift (RFC 0003)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "Apache-2.0"}
12
+ authors = [{name = "KpiFinity Inc."}]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ ]
19
+ dependencies = ["pydantic>=2.5,<3"]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/kpifinity/ski-framework"
23
+ Documentation = "https://kpifinity.github.io/ski-framework/"
24
+ Issues = "https://github.com/kpifinity/ski-framework/issues"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
28
+
29
+ [tool.setuptools.package-data]
30
+ ski_schemas = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """ski-schemas — the SKI Framework wire models, defined once (RFC 0003 PR 1).
2
+
3
+ The verdict envelope, the signed LLM transcript, and the measurement
4
+ record are normative (spec v3.0 §4, §6). This dependency-light package
5
+ is the single source of truth the server, the ski-sdk client, and the
6
+ conformance suite all import — one definition, no drift.
7
+ """
8
+
9
+ from .envelope import (
10
+ FormalizableAssertion,
11
+ KGCitation,
12
+ KGCitationRole,
13
+ ModelProvenance,
14
+ V3Verdict,
15
+ V3VerdictEnvelope,
16
+ VerifierResult,
17
+ VerifierStatus,
18
+ )
19
+ from .measurement import MeasurementRecord
20
+ from .transcript import LLMTranscript, hash_pair, signing_message
21
+
22
+ __version__ = "0.1.0"
23
+
24
+ __all__ = [
25
+ "FormalizableAssertion",
26
+ "KGCitation",
27
+ "KGCitationRole",
28
+ "LLMTranscript",
29
+ "MeasurementRecord",
30
+ "ModelProvenance",
31
+ "V3Verdict",
32
+ "V3VerdictEnvelope",
33
+ "VerifierResult",
34
+ "VerifierStatus",
35
+ "hash_pair",
36
+ "signing_message",
37
+ ]
@@ -0,0 +1,159 @@
1
+ """Pydantic models for the v3 verdict envelope per spec v3.0 §4.
2
+
3
+ * §4.1 — the five-verdict taxonomy (preserved from v2.1).
4
+ * §4.2 — required + optional envelope fields.
5
+ * §4.3 — KG citations with node_id / version / role.
6
+ * §4.4 — formalizable assertions the symbolic verifier can check.
7
+ * §4.5 — verifier result with status enum.
8
+ * §4.6 — model provenance (six required hash + id fields).
9
+
10
+ These models are the contract every v3 verdict honours. They MUST
11
+ round-trip through JSON without loss; the runtime's envelope tests
12
+ enforce this. This module is the single source of truth shared by the
13
+ server, the SDK, and the conformance suite (RFC 0003 PR 1).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from enum import Enum
19
+ from typing import Any, Dict, List, Optional, Union
20
+
21
+ from pydantic import BaseModel, ConfigDict, Field
22
+
23
+ # ---- Verdict taxonomy (§4.1) ----
24
+
25
+
26
+ class V3Verdict(str, Enum):
27
+ """The five-verdict taxonomy, preserved from v2.1."""
28
+
29
+ CLEAR = "CLEAR"
30
+ FLAG = "FLAG"
31
+ NULL_UNMAPPED = "NULL_UNMAPPED"
32
+ NULL_STALE = "NULL_STALE"
33
+ DISCRETIONARY = "DISCRETIONARY"
34
+
35
+
36
+ # ---- Verifier status (§4.5) ----
37
+
38
+
39
+ class VerifierStatus(str, Enum):
40
+ """Status returned by the Symbolic Verifier (spec §5.3)."""
41
+
42
+ AGREED = "AGREED"
43
+ LLM_CONTRADICTION = "LLM_CONTRADICTION"
44
+ NEURO_SYMBOLIC_DIVERGENCE = "NEURO_SYMBOLIC_DIVERGENCE"
45
+ UNVERIFIABLE = "UNVERIFIABLE"
46
+
47
+
48
+ # ---- KG citation (§4.3) ----
49
+
50
+
51
+ class KGCitationRole(str, Enum):
52
+ """The role a cited KG node played in the verdict."""
53
+
54
+ OBLIGATION = "obligation"
55
+ DEFINITION_RESOLVED = "definition_resolved"
56
+ EXEMPTION_CONSIDERED = "exemption_considered"
57
+ PRECEDENT_REFERENCED = "precedent_referenced"
58
+ JURISDICTION_MATCHED = "jurisdiction_matched"
59
+
60
+
61
+ class KGCitation(BaseModel):
62
+ """A single KG node the LLM cited as informing the verdict."""
63
+
64
+ model_config = ConfigDict(use_enum_values=True, extra="forbid")
65
+
66
+ node_id: str
67
+ version: str
68
+ role: KGCitationRole
69
+
70
+
71
+ # ---- Formalizable assertion (§4.4) ----
72
+
73
+
74
+ class FormalizableAssertion(BaseModel):
75
+ """A structured assertion the symbolic verifier can mechanically check."""
76
+
77
+ model_config = ConfigDict(extra="forbid")
78
+
79
+ predicate: str = Field(..., description="Predicate type, e.g. 'must_not_exceed'.")
80
+ metric: str = Field(..., description="Dotted-path into the measurement object.")
81
+ value: Union[float, int, str, List[Any], None] = None
82
+ observed: Union[float, int, str, List[Any], None] = None
83
+ satisfied: bool
84
+ obligation_id: str = Field(..., description="The KG obligation this assertion checks.")
85
+ window_seconds: Optional[int] = Field(
86
+ default=None,
87
+ ge=1,
88
+ description=(
89
+ "Time-window length in seconds for stateful predicates "
90
+ "(e.g. 86400 for a 24h rolling average). None for stateless "
91
+ "predicates."
92
+ ),
93
+ )
94
+
95
+
96
+ # ---- Verifier result (§4.5) ----
97
+
98
+
99
+ class VerifierResult(BaseModel):
100
+ """The symbolic verifier's per-assertion result."""
101
+
102
+ model_config = ConfigDict(use_enum_values=True, extra="forbid")
103
+
104
+ status: VerifierStatus
105
+ checked_assertions: int = Field(default=0, ge=0)
106
+ divergences: List[str] = Field(default_factory=list)
107
+
108
+
109
+ # ---- Model provenance (§4.6) ----
110
+
111
+
112
+ class ModelProvenance(BaseModel):
113
+ """Inference provenance metadata required for v3 replay.
114
+
115
+ All hash values MUST be lowercase hex prefixed with ``sha256:`` per
116
+ spec §4.6. The Pydantic layer enforces the prefix.
117
+ """
118
+
119
+ # model_weight_hash collides with Pydantic's reserved model_
120
+ # namespace; opt out explicitly. The field name is normative per
121
+ # spec §4.6 and cannot be renamed.
122
+ model_config = ConfigDict(extra="forbid", protected_namespaces=())
123
+
124
+ model_weight_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
125
+ kg_version_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
126
+ prompt_template_id: str = Field(..., description="Stable id, e.g. 'ski.v3.evaluate.1'.")
127
+ prompt_template_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
128
+ decoder_seed: int = Field(..., ge=0)
129
+ structured_grammar_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
130
+
131
+
132
+ # ---- Verdict envelope (§4.2) ----
133
+
134
+
135
+ class V3VerdictEnvelope(BaseModel):
136
+ """The complete v3 verdict envelope.
137
+
138
+ Every required field is enforced at the Pydantic layer. The
139
+ envelope MUST round-trip through JSON without loss; that property
140
+ is what makes the audit ledger's hash chain meaningful.
141
+ """
142
+
143
+ # model_provenance collides with Pydantic's reserved model_
144
+ # namespace; opt out explicitly. The field name is normative per
145
+ # spec §4.2 and cannot be renamed.
146
+ model_config = ConfigDict(use_enum_values=True, extra="forbid", protected_namespaces=())
147
+
148
+ verdict: V3Verdict
149
+ reasoning: str
150
+ kg_citations: List[KGCitation] = Field(default_factory=list)
151
+ formalizable_assertions: List[FormalizableAssertion] = Field(default_factory=list)
152
+ verifier_result: VerifierResult
153
+ model_provenance: ModelProvenance
154
+ transcript_ref: str = Field(..., description="Pointer to the LLM transcript in the ledger.")
155
+
156
+ # Optional fields per spec §4.2 + §5.4 + §5.6.
157
+ human_attestation: Optional[Dict[str, Any]] = None
158
+ notes: List[str] = Field(default_factory=list)
159
+ verdict_path: Optional[str] = Field(default=None, description="Per §5.6: 'fast' or None.")
@@ -0,0 +1,37 @@
1
+ """Measurement record — the input shape of ``POST /api/evaluate`` (RFC 0003)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class MeasurementRecord(BaseModel):
11
+ """Measurement input for /api/evaluate.
12
+
13
+ The v3 evaluator consumes the full measurement; the LLM scopes the KG
14
+ snapshot to the measurement's jurisdiction and effective date. PR 10b
15
+ passes the full KG as the snapshot. Jurisdiction-scoped snapshots are a
16
+ follow-up.
17
+ """
18
+
19
+ measurement_id: str = Field(..., description="Stable id for replay correlation.")
20
+ timestamp: str = Field(..., description="ISO-8601 UTC.")
21
+ subject: str = Field(..., description="Subject token (e.g. data source identifier).")
22
+ measurement: Dict[str, Any] = Field(
23
+ default_factory=dict,
24
+ description="Structured measurement values keyed by metric name.",
25
+ )
26
+ jurisdiction: Optional[str] = Field(
27
+ default=None,
28
+ description=(
29
+ "Tenant-declared jurisdiction (e.g. 'us-ca', 'eu', 'global'). When set, "
30
+ "the KG is scoped to obligations whose jurisdiction matches or is "
31
+ "universal ('global', '*'). None means 'no restriction' — all rules "
32
+ "effective at the measurement's timestamp are sent to the LLM."
33
+ ),
34
+ )
35
+
36
+
37
+ __all__ = ["MeasurementRecord"]
File without changes
@@ -0,0 +1,104 @@
1
+ """Provider-neutral LLM transcript record (spec v3.0 §6).
2
+
3
+ An :class:`LLMTranscript` captures the canonical input/output pair of a
4
+ single LLM evaluation call so the verdict can be independently replayed by
5
+ an auditor. The transcript is **provider-neutral by design**: no
6
+ Anthropic Messages blob, no OpenAI ChatCompletion shape, no Ollama
7
+ response envelope leaks into the audit trail. Each backend is responsible
8
+ for normalising its own request / response into the canonical strings the
9
+ transcript records.
10
+
11
+ Fields (spec §6.2):
12
+
13
+ * ``request_canonical`` — the prompt as actually sent to the LLM,
14
+ UTF-8 text. For chat-style APIs this is the formatted single string
15
+ the backend constructed; for raw-completion APIs it is the prompt
16
+ itself.
17
+ * ``request_hash`` — sha256 of ``request_canonical``.
18
+ * ``response_canonical`` — the structured-output dict matching
19
+ the framework's ``RESPONSE_GRAMMAR``. Vendor-specific
20
+ fields MUST be filtered out before canonicalisation.
21
+ * ``response_hash`` — sha256 of the canonical JSON serialisation of
22
+ ``response_canonical`` (sort_keys, compact separators).
23
+ * ``signature_hex`` — ed25519 signature over
24
+ ``request_hash || "|" || response_hash`` produced by the runtime's
25
+ the runtime's ``TranscriptSigner``.
26
+ * ``signing_key_id`` — sha256 of the public-key bytes; auditors look
27
+ up the corresponding public key to verify.
28
+ * ``backend_name`` — opaque observability tag (e.g. ``"fake-llm"``,
29
+ ``"ollama-v3:qwen2.5-7b"``). Not part of the audit contract.
30
+ * ``backend_metadata`` — opaque per-backend dict. Use freely for
31
+ observability; NEVER for contract.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import hashlib
37
+ import json
38
+ from datetime import datetime
39
+ from typing import Any, Dict, Tuple
40
+
41
+ from pydantic import BaseModel, ConfigDict, Field
42
+
43
+
44
+ def canonical_request(request_text: str) -> bytes:
45
+ """Canonical request bytes — UTF-8 encoding of the request string."""
46
+ return request_text.encode("utf-8")
47
+
48
+
49
+ def canonical_response(response: Dict[str, Any]) -> bytes:
50
+ """Canonical response bytes — sorted-keys compact JSON, UTF-8.
51
+
52
+ Vendor-specific fields are NOT filtered here — callers MUST construct
53
+ ``response`` from the structured-output portion of the LLM reply only.
54
+ The the framework's ``RESPONSE_GRAMMAR`` is the contract.
55
+ """
56
+ return json.dumps(response, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
57
+
58
+
59
+ def hash_pair(*, request_text: str, response: Dict[str, Any]) -> Tuple[str, str]:
60
+ """Compute ``(request_hash, response_hash)`` as sha256-prefixed hex."""
61
+ req_hash = "sha256:" + hashlib.sha256(canonical_request(request_text)).hexdigest()
62
+ resp_hash = "sha256:" + hashlib.sha256(canonical_response(response)).hexdigest()
63
+ return req_hash, resp_hash
64
+
65
+
66
+ def signing_message(*, request_hash: str, response_hash: str) -> bytes:
67
+ """Bytes to be signed — concatenation of the two hashes with a separator."""
68
+ return f"{request_hash}|{response_hash}".encode("ascii")
69
+
70
+
71
+ class LLMTranscript(BaseModel):
72
+ """A signed transcript of one LLM evaluation call.
73
+
74
+ Round-trips through JSON; auditors deserialise from the ledger and call
75
+ :func:`ski_model.v3.signing.verify_signature` to prove integrity.
76
+ """
77
+
78
+ model_config = ConfigDict(extra="forbid")
79
+
80
+ transcript_id: str = Field(..., description="Globally unique id for this transcript.")
81
+ request_canonical: str = Field(..., description="The prompt as actually sent.")
82
+ request_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
83
+ response_canonical: Dict[str, Any] = Field(
84
+ ..., description="Structured output matching RESPONSE_GRAMMAR."
85
+ )
86
+ response_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
87
+ signature_hex: str = Field(..., description="Ed25519 signature, hex-encoded.")
88
+ signing_key_id: str = Field(..., pattern=r"^sha256:[0-9a-f]+$")
89
+ backend_name: str = Field(..., description="Observability tag for the LLM backend; not contract.")
90
+ backend_metadata: Dict[str, Any] = Field(
91
+ default_factory=dict,
92
+ description="Opaque backend-specific metadata. Never contract.",
93
+ )
94
+ started_at: datetime
95
+ completed_at: datetime
96
+
97
+
98
+ __all__ = [
99
+ "LLMTranscript",
100
+ "canonical_request",
101
+ "canonical_response",
102
+ "hash_pair",
103
+ "signing_message",
104
+ ]
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: ski-schemas
3
+ Version: 0.1.0
4
+ Summary: SKI Framework wire models: verdict envelope, signed LLM transcript, measurement record. One definition, no drift (RFC 0003).
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: pydantic<3,>=2.5
17
+
18
+ # ski-schemas
19
+
20
+ > Install from PyPI: `pip install ski-schemas`
21
+
22
+ The [SKI Framework](https://github.com/kpifinity/ski-framework) wire
23
+ models — the v3 verdict envelope, the signed LLM transcript, and the
24
+ measurement record — extracted into one dependency-light package per
25
+ RFC 0003. The server, the `ski-sdk` client, and the conformance suite
26
+ all import these models, so the wire contract has exactly one
27
+ definition.
28
+
29
+ ```python
30
+ from ski_schemas import V3VerdictEnvelope
31
+
32
+ envelope = V3VerdictEnvelope.model_validate_json(raw)
33
+ print(envelope.verdict, envelope.model_provenance.kg_version_hash)
34
+ ```
35
+
36
+ Versioned independently of the framework (it carries a wire contract,
37
+ not a runtime). Dependencies: `pydantic` only.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/ski_schemas/__init__.py
4
+ src/ski_schemas/envelope.py
5
+ src/ski_schemas/measurement.py
6
+ src/ski_schemas/py.typed
7
+ src/ski_schemas/transcript.py
8
+ src/ski_schemas.egg-info/PKG-INFO
9
+ src/ski_schemas.egg-info/SOURCES.txt
10
+ src/ski_schemas.egg-info/dependency_links.txt
11
+ src/ski_schemas.egg-info/requires.txt
12
+ src/ski_schemas.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pydantic<3,>=2.5
@@ -0,0 +1 @@
1
+ ski_schemas