swiftapi-python 1.3.1__tar.gz → 1.4.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.
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/MANIFEST.in +1 -0
- swiftapi_python-1.4.0/NATIVE_RECEIPTS.md +54 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/PKG-INFO +1 -1
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/pyproject.toml +1 -1
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/__init__.py +1 -1
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/client.py +3 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/exceptions.py +4 -2
- swiftapi_python-1.4.0/swiftapi/native.py +59 -0
- swiftapi_python-1.4.0/swiftapi/native_classifier_releases.py +15 -0
- swiftapi_python-1.4.0/swiftapi/native_taxonomy.py +75 -0
- swiftapi_python-1.4.0/swiftapi/native_verify.py +292 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/PKG-INFO +1 -1
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/SOURCES.txt +6 -0
- swiftapi_python-1.4.0/tests/test_native.py +88 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/CHANGELOG.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/LICENSE +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/MIGRATION_1_2_2.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/PRODUCT_TRUTH_MAP.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/README.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/benchmarks/corpora.lock.json +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/benchmarks/sources.lock.json +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/ACTION_ENVELOPES.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/BENCHMARKING.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/BYOK.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CLAUDE_CODE.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CODEX.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CONCEPTS.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CONDITIONS.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/LOCAL_BRAKE.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/NATIVE_NONCONTINUATION.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/PYTHON_SDK.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/QUICKSTART.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/RECEIPTS.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/REMOTE_SWIFTAPI.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/RESEARCH_PROVENANCE.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/SECURITY_MODEL.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/THREAT_MODEL.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/VERIFICATION.md +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/fetch_benchmarks.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/fetch_corpora.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/qualify_artifacts.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/setup.cfg +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/__main__.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/anthropic.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/__init__.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/benchmarks.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/broker.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/core.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/install.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/mcp.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/observations.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/qualification.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/remote.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/cli.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/enforcement.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/openai.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/py.typed +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/utils.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/verifier.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/dependency_links.txt +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/entry_points.txt +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/requires.txt +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/top_level.txt +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/conftest.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_anthropic.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_brake.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_brake_benchmarks_remote.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_client.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_exceptions.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_legacy_security.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_openai.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_release_contract.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_responses.py +0 -0
- {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_verifier.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Native receipt support in 1.4.0
|
|
2
|
+
|
|
3
|
+
This package adds a loopback native-EOS client and an offline receipt verifier.
|
|
4
|
+
It does not contain model weights or run an inference service by itself.
|
|
5
|
+
Existing 1.3.1 calls retain their behaviour. `PolicyViolation` still raises;
|
|
6
|
+
`response`, `raw_response`, and `status_code` now preserve a denied response.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from swiftapi.native import NativeEOSClient
|
|
10
|
+
from swiftapi.native_verify import verify_receipt
|
|
11
|
+
|
|
12
|
+
with NativeEOSClient(key, base_url="http://127.0.0.1:8765") as client:
|
|
13
|
+
response = client.generate("What is 2 + 2?", request_id=request_id)
|
|
14
|
+
|
|
15
|
+
report = verify_receipt(
|
|
16
|
+
receipt_path,
|
|
17
|
+
trusted_runtime_public_key,
|
|
18
|
+
"RECORDED_LOGITS_REPLAY",
|
|
19
|
+
expected_max_grant_age_seconds=60,
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The runtime public key and expected age must come from the independently
|
|
24
|
+
approved protocol, not from the receipt being checked. The default authority
|
|
25
|
+
public key is SwiftAPI's pinned production key. Test-authority receipts require
|
|
26
|
+
an explicitly supplied test public key and are reported as non-production.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
python -m swiftapi.native_verify receipt.json \
|
|
30
|
+
--trusted-key runtime-public.json \
|
|
31
|
+
--expected-mode RECORDED_LOGITS_REPLAY \
|
|
32
|
+
--expected-max-grant-age-seconds 60
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The verifier checks the runtime signature, authority signatures and request
|
|
36
|
+
binding, every declared asset hash, stored BF16 vector lengths and non-EOS
|
|
37
|
+
identity, greedy argmax, raw input/output binding, completion scope and the
|
|
38
|
+
frozen July 29 class. It needs neither torch nor model files nor signing keys.
|
|
39
|
+
It does not independently reproduce a model forward pass, attest hardware,
|
|
40
|
+
authenticate an unsigned revocation observation, or prove runtime honesty.
|
|
41
|
+
|
|
42
|
+
Receipt schema controls compatibility; the declared SDK version is reported,
|
|
43
|
+
not a fixed acceptance gate. `native_classifier_releases.py` retains known
|
|
44
|
+
classifier hashes. The declared classifier must be in that registry and must
|
|
45
|
+
agree with the SDK identity recorded in the receipt. Class, classification
|
|
46
|
+
fields and completion scope are recomputed separately. A later non-semantic
|
|
47
|
+
source edit does not invalidate earlier classifier hashes.
|
|
48
|
+
|
|
49
|
+
The native output field is always present. A completed zero-byte native EOS
|
|
50
|
+
stop is V0. A replayed nonempty first token is R with
|
|
51
|
+
`FIRST_TOKEN_ONLY_NOT_A_COMPLETE_ANSWER`, never a complete generated answer.
|
|
52
|
+
Rejections before decoding are operational, not Voids.
|
|
53
|
+
|
|
54
|
+
Package status: local release candidate, not uploaded to PyPI.
|
|
@@ -21,10 +21,12 @@ class AuthenticationError(SwiftAPIError):
|
|
|
21
21
|
class PolicyViolation(SwiftAPIError):
|
|
22
22
|
"""Raised when an action violates policy and is denied."""
|
|
23
23
|
|
|
24
|
-
def __init__(self, message: str, action_type: str = None, denial_reason: str = None
|
|
24
|
+
def __init__(self, message: str, action_type: str = None, denial_reason: str = None,
|
|
25
|
+
response: dict = None, raw_response: bytes = None, status_code: int = None):
|
|
25
26
|
self.action_type = action_type
|
|
26
27
|
self.denial_reason = denial_reason
|
|
27
|
-
|
|
28
|
+
self.raw_response = raw_response
|
|
29
|
+
super().__init__(message, status_code=status_code, response=response)
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class SignatureVerificationError(SwiftAPIError):
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Native EOS client. Loopback only; no hosted-model or API-proxy claims."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import re
|
|
4
|
+
from urllib.parse import urlsplit
|
|
5
|
+
import uuid
|
|
6
|
+
import requests
|
|
7
|
+
from .exceptions import SwiftAPIError
|
|
8
|
+
|
|
9
|
+
class NativeEOSClient:
|
|
10
|
+
def __init__(self, key=None, *, key_file=None, base_url='http://127.0.0.1:8766', timeout=120):
|
|
11
|
+
if (key is None) == (key_file is None):
|
|
12
|
+
raise ValueError('Supply exactly one of key or key_file')
|
|
13
|
+
key = Path(key_file).read_text(encoding='ascii').strip() if key_file is not None else key
|
|
14
|
+
if not isinstance(key,str) or not re.fullmatch(r'swiftapi_live_[0-9a-f]{64}',key):
|
|
15
|
+
raise ValueError('Invalid authority key format')
|
|
16
|
+
u = urlsplit(base_url)
|
|
17
|
+
if (u.scheme != 'http' or u.hostname != '127.0.0.1' or not u.port
|
|
18
|
+
or u.username or u.password or u.path not in ('','/') or u.query or u.fragment):
|
|
19
|
+
raise ValueError('Native service URL must be http://127.0.0.1:PORT')
|
|
20
|
+
self.base_url, self.timeout = base_url.rstrip('/'), timeout
|
|
21
|
+
self.session = requests.Session()
|
|
22
|
+
self.session.trust_env = False
|
|
23
|
+
self.session.headers['X-SwiftAPI-Authority'] = key
|
|
24
|
+
|
|
25
|
+
def _post(self, path, body):
|
|
26
|
+
try:
|
|
27
|
+
response = self.session.post(self.base_url+path,json=body,timeout=self.timeout,allow_redirects=False)
|
|
28
|
+
except requests.RequestException:
|
|
29
|
+
raise SwiftAPIError('Native service unavailable') from None
|
|
30
|
+
try:
|
|
31
|
+
data = response.json()
|
|
32
|
+
except ValueError:
|
|
33
|
+
raise SwiftAPIError('Invalid native response',response.status_code) from None
|
|
34
|
+
if response.status_code != 200:
|
|
35
|
+
exc = SwiftAPIError('Native request rejected',response.status_code,data)
|
|
36
|
+
exc.raw_response = response.content
|
|
37
|
+
raise exc
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
def check_authority(self):
|
|
41
|
+
return self._post('/v1/authority/check',{})
|
|
42
|
+
|
|
43
|
+
def authorize(self, question, *, request_id):
|
|
44
|
+
return self._post('/v1/native-eos/authorize',{'question':question,'request_id':request_id})
|
|
45
|
+
|
|
46
|
+
def generate_with_grant(self, question, grant, *, request_id):
|
|
47
|
+
return self._post('/v1/native-eos/generate',{'question':question,'request_id':request_id,'grant':grant})
|
|
48
|
+
|
|
49
|
+
def generate(self, question, *, request_id=None):
|
|
50
|
+
request_id = request_id or str(uuid.uuid4())
|
|
51
|
+
permission = self.authorize(question,request_id=request_id)
|
|
52
|
+
return self.generate_with_grant(question,permission['grant'],request_id=request_id)
|
|
53
|
+
|
|
54
|
+
def close(self):
|
|
55
|
+
self.session.close()
|
|
56
|
+
def __enter__(self):
|
|
57
|
+
return self
|
|
58
|
+
def __exit__(self,*_):
|
|
59
|
+
self.close()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Append-only compatibility registry for released native classifiers.
|
|
2
|
+
|
|
3
|
+
Receipt verification uses the declared classifier hash, not the current source
|
|
4
|
+
file's hash. A future release may append a reviewed, compatible implementation;
|
|
5
|
+
it must retain this entry so existing sealed receipts remain verifiable.
|
|
6
|
+
The first entry is the SDK 1.4.0 release candidate's native_taxonomy.py bytes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
RELEASED_CLASSIFIER_HASHES = {
|
|
10
|
+
'c17a7a37f0d935195b69c446da3a5ecfe36ab71539228dd9f798686d075de43c': {
|
|
11
|
+
'first_sdk_version': '1.4.0',
|
|
12
|
+
'receipt_schema': 'swiftapi-native-service-receipt/2',
|
|
13
|
+
'rule': 'void-matrix/2dfe895bdafd35d23d6dab5f307ed560214a3843',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""July 29, 2026 Table 2, DOI 10.5281/zenodo.21696066, native adapter.
|
|
2
|
+
|
|
3
|
+
V0: Successful provider response; text container present; zero visible UTF-8
|
|
4
|
+
bytes; recognized normal stop.
|
|
5
|
+
V1: Successful provider response; zero visible UTF-8 bytes; recognized
|
|
6
|
+
output-budget stop.
|
|
7
|
+
V2: Successful provider response; text container absent; zero visible UTF-8
|
|
8
|
+
bytes; recognized normal stop.
|
|
9
|
+
VU: Successful provider response; zero visible UTF-8 bytes; termination state
|
|
10
|
+
not mapped by the frozen protocol.
|
|
11
|
+
Non-Void: Near-Voids (NV) contain at least one byte, including whitespace,
|
|
12
|
+
invisible Unicode, punctuation-only output, or ellipses. Visible responses (R)
|
|
13
|
+
contain nonempty visible text. Refusals (RF), safety blocks (SB), tool calls (TC)
|
|
14
|
+
come from distinct provider fields. Errors are operational outcomes, not Voids.
|
|
15
|
+
|
|
16
|
+
Normative classifier: theonlypal/void-matrix commit
|
|
17
|
+
2dfe895bdafd35d23d6dab5f307ed560214a3843, void_matrix.FROZEN.py,
|
|
18
|
+
INVISIBLE_CODEPOINTS and ELLIPSIS_FORMS lines 213-235, near_void lines 1053-1061.
|
|
19
|
+
https://github.com/theonlypal/void-matrix/blob/2dfe895bdafd35d23d6dab5f307ed560214a3843/void_matrix.FROZEN.py#L1053
|
|
20
|
+
The frozen code governs the prose. A lone ? or ! is R, not NV. Native output
|
|
21
|
+
is always present. RF/SB/TC require a distinct event field, never inference
|
|
22
|
+
from words. Replay completion is labelled independently.
|
|
23
|
+
"""
|
|
24
|
+
import hashlib
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
import unicodedata
|
|
27
|
+
|
|
28
|
+
INVISIBLE = frozenset((0x200B,0x200C,0x200D,0x200E,0x200F,0xFEFF,0x2060,
|
|
29
|
+
0x2061,0x2062,0x2063,0x2064,0x2066,0x2067,0x2068,0x2069,
|
|
30
|
+
0x202A,0x202B,0x202C,0x202D,0x202E))
|
|
31
|
+
ELLIPSIS_FORMS = frozenset(('.', '..', '...', '....', '…', '….'))
|
|
32
|
+
|
|
33
|
+
def near_void(text):
|
|
34
|
+
if not text:
|
|
35
|
+
return False
|
|
36
|
+
if all(ord(character) in INVISIBLE or character.isspace() for character in text):
|
|
37
|
+
return True
|
|
38
|
+
return text.strip() in ELLIPSIS_FORMS
|
|
39
|
+
|
|
40
|
+
def classifier_hash():
|
|
41
|
+
return hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
|
|
42
|
+
|
|
43
|
+
def classify(raw, *, successful, container_present=True, stop=None, event=None):
|
|
44
|
+
if type(raw) is not bytes or type(successful) is not bool or type(container_present) is not bool:
|
|
45
|
+
raise ValueError('Invalid classification evidence')
|
|
46
|
+
if not successful:
|
|
47
|
+
return 'operational'
|
|
48
|
+
if event is not None:
|
|
49
|
+
if event not in ('RF','SB','TC'):
|
|
50
|
+
raise ValueError('Unknown distinct provider event')
|
|
51
|
+
return event
|
|
52
|
+
text = raw.decode('utf-8', errors='strict')
|
|
53
|
+
if text:
|
|
54
|
+
return 'NV' if near_void(text) else 'R'
|
|
55
|
+
if stop == 'length':
|
|
56
|
+
return 'V1'
|
|
57
|
+
if stop == 'stop':
|
|
58
|
+
return 'V0' if container_present else 'V2'
|
|
59
|
+
return 'VU'
|
|
60
|
+
|
|
61
|
+
def classification_record(raw, status, result=None):
|
|
62
|
+
result = result or {}
|
|
63
|
+
complete = result.get('generation_complete') is True
|
|
64
|
+
finish = result.get('finish')
|
|
65
|
+
stop = 'stop' if complete and finish == 'NATIVE_EOS' else 'length' if finish == 'TOKEN_CAP' else None
|
|
66
|
+
fields = {'successful':status == 'COMPLETED', 'container_present':True,
|
|
67
|
+
'stop':stop, 'output_bytes':len(raw),
|
|
68
|
+
'native_eos_selected':151645 in result.get('generated_token_ids', []),
|
|
69
|
+
'generation_complete':complete, 'finish':finish}
|
|
70
|
+
return {'class':classify(raw,successful=fields['successful'],stop=stop),
|
|
71
|
+
'fields':fields,'classifier_sha256':classifier_hash(),
|
|
72
|
+
'taxonomy':'Cross-Vendor Semantic Void Matrix, 2026-07-29',
|
|
73
|
+
'source_doi':'10.5281/zenodo.21696066',
|
|
74
|
+
'unicode_version':unicodedata.unidata_version,
|
|
75
|
+
'response_scope':result.get('answer_scope','NO_COMPLETED_MODEL_RESPONSE')}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Offline native receipt verification. No torch, weights, signing seed or network.
|
|
2
|
+
|
|
3
|
+
Signatures authenticate records, not the honesty of an unattested runtime or its
|
|
4
|
+
historical online revocation observation. Runtime trust is supplied out of band.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import base64
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import re
|
|
14
|
+
import struct
|
|
15
|
+
from nacl.signing import VerifyKey
|
|
16
|
+
from .native_taxonomy import classification_record
|
|
17
|
+
from .native_classifier_releases import RELEASED_CLASSIFIER_HASHES
|
|
18
|
+
from .verifier import SWIFTAPI_PUBLIC_KEY_B64
|
|
19
|
+
|
|
20
|
+
EOS, VOCAB = 151645, 151936
|
|
21
|
+
MODES = ('RECORDED_LOGITS_REPLAY','FRESH_MODEL_EXECUTION')
|
|
22
|
+
UNSIGNED = {'signature','signing_mode','action_data','denial_reason',
|
|
23
|
+
'violated_policies','evaluated_policies','evaluation_context'}
|
|
24
|
+
|
|
25
|
+
def encoded(value):
|
|
26
|
+
return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=True,allow_nan=False).encode()
|
|
27
|
+
def digest(raw):
|
|
28
|
+
return hashlib.sha256(raw).hexdigest()
|
|
29
|
+
def require(value, message):
|
|
30
|
+
if not value:
|
|
31
|
+
raise ValueError(message)
|
|
32
|
+
def read_json(path):
|
|
33
|
+
def unique(pairs):
|
|
34
|
+
d = {}
|
|
35
|
+
for k,v in pairs:
|
|
36
|
+
require(k not in d,'Duplicate JSON member')
|
|
37
|
+
d[k] = v
|
|
38
|
+
return d
|
|
39
|
+
def invalid(_):
|
|
40
|
+
raise ValueError('Nonfinite JSON')
|
|
41
|
+
return json.loads(Path(path).read_bytes(),object_pairs_hook=unique,parse_constant=invalid)
|
|
42
|
+
def signature(payload, sig, key):
|
|
43
|
+
VerifyKey(base64.b64decode(key,validate=True)).verify(encoded(payload),base64.b64decode(sig,validate=True))
|
|
44
|
+
def instant(value):
|
|
45
|
+
dt = datetime.fromisoformat(value.replace('Z','+00:00'))
|
|
46
|
+
require(dt.tzinfo is not None,'Naive authority timestamp')
|
|
47
|
+
return dt.timestamp()
|
|
48
|
+
|
|
49
|
+
def verify_revocation_entry(entry, jti, authority_key, *, decision_at=None):
|
|
50
|
+
fields={'cursor','jti','revoked_at','revoked_by','reason'}
|
|
51
|
+
require(isinstance(entry,dict) and set(entry)==fields|{'signature'},'Revocation entry shape')
|
|
52
|
+
require(entry['jti']==jti and type(entry['cursor']) is int and entry['cursor']>0,'Revocation JTI/cursor')
|
|
53
|
+
signature({k:entry[k] for k in fields},entry['signature'],authority_key)
|
|
54
|
+
revoked_at=instant(entry['revoked_at'])
|
|
55
|
+
if decision_at is not None:
|
|
56
|
+
require(revoked_at<=decision_at+5,'Revocation was not established at decision')
|
|
57
|
+
return True
|
|
58
|
+
def grant_check(grant, action, when, authority_key):
|
|
59
|
+
"""Return the same ordered permission reason, independently of the service."""
|
|
60
|
+
try:
|
|
61
|
+
require(isinstance(grant,dict) and grant.get('signing_mode') == 'ed25519','Authority algorithm')
|
|
62
|
+
signature({k:v for k,v in grant.items() if k not in UNSIGNED},grant['signature'],authority_key)
|
|
63
|
+
except Exception:
|
|
64
|
+
return 'INVALID_AUTHORITY_SIGNATURE'
|
|
65
|
+
if grant.get('action_type') != action['scope'] or grant.get('action_fingerprint') != digest(encoded(action))[:32]:
|
|
66
|
+
return 'GRANT_BINDING_MISMATCH'
|
|
67
|
+
if 'action_data' in grant and grant['action_data'] != action:
|
|
68
|
+
return 'GRANT_ACTION_ECHO_MISMATCH'
|
|
69
|
+
if grant.get('approved') is not True:
|
|
70
|
+
return 'AUTHORITY_DENIED'
|
|
71
|
+
try:
|
|
72
|
+
issued, expires = instant(grant['issued_at']),instant(grant['expires_at'])
|
|
73
|
+
except Exception:
|
|
74
|
+
return 'INVALID_GRANT_TIME'
|
|
75
|
+
if issued > when+5:
|
|
76
|
+
return 'GRANT_FUTURE'
|
|
77
|
+
age_limit = action.get('profile', {}).get('max_grant_age_seconds', 60)
|
|
78
|
+
if type(age_limit) not in (int,float) or not 0 < age_limit <= 3600:
|
|
79
|
+
return 'INVALID_GRANT_AGE_PROFILE'
|
|
80
|
+
if when-issued > age_limit or expires <= when or expires <= issued:
|
|
81
|
+
return 'GRANT_EXPIRED'
|
|
82
|
+
if not isinstance(grant.get('jti'),str) or not re.fullmatch(r'[a-zA-Z0-9_-]{1,128}',grant['jti']):
|
|
83
|
+
return 'INVALID_GRANT_ID'
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def vector(raw):
|
|
87
|
+
require(len(raw) == 2*VOCAB,'Vector byte length')
|
|
88
|
+
vals = [struct.unpack('<f',b'\0\0'+raw[i:i+2])[0] for i in range(0,len(raw),2)]
|
|
89
|
+
require(all(math.isfinite(x) for x in vals),'Nonfinite vector')
|
|
90
|
+
return vals
|
|
91
|
+
|
|
92
|
+
def verify_receipt(path, trusted_public_key, expected_mode, *, expected_max_grant_age_seconds,
|
|
93
|
+
authority_key=SWIFTAPI_PUBLIC_KEY_B64,
|
|
94
|
+
expected_source_sha256=None):
|
|
95
|
+
require(expected_mode in MODES,'Explicit expected mode required')
|
|
96
|
+
require(type(expected_max_grant_age_seconds) in (int,float) and
|
|
97
|
+
0 < expected_max_grant_age_seconds <= 3600,'Expected registered grant age required')
|
|
98
|
+
path = Path(path)
|
|
99
|
+
e = read_json(path)
|
|
100
|
+
require(set(e) == {'payload','signature','signer','algorithm'},'Envelope shape')
|
|
101
|
+
require(e['signer'] == 'LOCAL_RUNTIME' and e['algorithm'] == 'ed25519','Runtime signer')
|
|
102
|
+
r = e['payload']
|
|
103
|
+
signature(r,e['signature'],trusted_public_key)
|
|
104
|
+
require(r['schema'] == 'swiftapi-native-service-receipt/2','Receipt schema')
|
|
105
|
+
require(r['mode'] == expected_mode,'Receipt mode mismatch')
|
|
106
|
+
require(r['runtime_instance'] == digest(base64.b64decode(trusted_public_key)),'Runtime identity')
|
|
107
|
+
require(r['hardware_attested'] is False,'Hardware attestation unsupported')
|
|
108
|
+
require(r['authority_public_key'] == authority_key,'Authority key substituted')
|
|
109
|
+
require(r['source']['sha256'] == digest(encoded(r['source']['files'])),'Service source manifest')
|
|
110
|
+
if expected_source_sha256 is not None:
|
|
111
|
+
require(r['source']['sha256'] == expected_source_sha256,'Unexpected service source')
|
|
112
|
+
require(isinstance(r['sdk']['version'],str) and r['sdk']['version'],'Missing declared SDK version')
|
|
113
|
+
require(r['started_at'] <= r['finished_at'],'Receipt timeline')
|
|
114
|
+
assets = {}
|
|
115
|
+
for name, spec in r['assets'].items():
|
|
116
|
+
require(re.fullmatch(r'[a-zA-Z0-9_.-]+',name) and name not in ('.','..','receipt.json'),'Asset path')
|
|
117
|
+
p = path.parent/name
|
|
118
|
+
require(not p.is_symlink() and p.stat().st_size <= 4000000,'Unsafe asset')
|
|
119
|
+
data = p.read_bytes()
|
|
120
|
+
require(len(data) == spec['bytes'] and digest(data) == spec['sha256'],'Asset hash: '+name)
|
|
121
|
+
assets[name] = data
|
|
122
|
+
require('output.bin' in assets,'Missing output')
|
|
123
|
+
output = assets['output.bin']
|
|
124
|
+
require(r['output'].encode('utf-8') == output,'Raw output binding')
|
|
125
|
+
computed = classification_record(output,r['status'],r.get('result'))
|
|
126
|
+
declared_classifier = r['taxonomy']['classifier_sha256']
|
|
127
|
+
require(isinstance(declared_classifier,str) and declared_classifier in RELEASED_CLASSIFIER_HASHES,
|
|
128
|
+
'Unreleased classifier hash')
|
|
129
|
+
classifier_release = RELEASED_CLASSIFIER_HASHES[declared_classifier]
|
|
130
|
+
require(classifier_release['receipt_schema'] == r['schema'],'Classifier receipt schema')
|
|
131
|
+
require(r['sdk']['classifier_sha256'] == declared_classifier,'Declared SDK classifier binding')
|
|
132
|
+
# Source comments and Unicode database metadata are not classification rules.
|
|
133
|
+
compared = set(computed) - {'unicode_version','classifier_sha256'}
|
|
134
|
+
require(set(r['taxonomy']) == set(computed) and all(
|
|
135
|
+
computed[k] == r['taxonomy'][k] for k in compared),'Taxonomy class/fields mismatch')
|
|
136
|
+
summary = {'status':'PASS','mode':expected_mode,'execution_id':r['execution_id'],
|
|
137
|
+
'class':computed['class'],'response_scope':computed['response_scope'],
|
|
138
|
+
'runtime_signature':'PASS','authority_signature':'NOT_APPLICABLE',
|
|
139
|
+
'asset_hashes':'PASS','physical_execution_attested':False,
|
|
140
|
+
'full_model_forward_pass_reproduced':False,
|
|
141
|
+
'sdk_version_declared':r['sdk']['version'],
|
|
142
|
+
'classifier_sha256_declared':declared_classifier,
|
|
143
|
+
'classifier_first_sdk_version':classifier_release['first_sdk_version'],
|
|
144
|
+
'expected_max_grant_age_seconds':expected_max_grant_age_seconds,
|
|
145
|
+
'authority_public_key':authority_key,
|
|
146
|
+
'authority_evidence_kind':r['authority_evidence_kind'],
|
|
147
|
+
'production_authority':authority_key == SWIFTAPI_PUBLIC_KEY_B64,
|
|
148
|
+
'numerical_boundary_replay':'NOT_PERFORMED_LIGHT_VERIFIER',
|
|
149
|
+
'archived_score_source':'NOT_CHECKED_LIGHT_VERIFIER',
|
|
150
|
+
'revocation_scope':'Historical runtime report, not independently authority-signed'}
|
|
151
|
+
action = r.get('action')
|
|
152
|
+
if action is not None:
|
|
153
|
+
age_limit = action['profile']['max_grant_age_seconds']
|
|
154
|
+
require(type(age_limit) in (int,float) and 0 < age_limit <= 3600,'Signed grant age limit')
|
|
155
|
+
require(age_limit == expected_max_grant_age_seconds,'Registered grant age mismatch')
|
|
156
|
+
if r['authentication'] is not None:
|
|
157
|
+
auth = r['authentication']
|
|
158
|
+
require(grant_check(auth['grant'],auth['action'],auth['observed_at'],authority_key) is None,'Known-key authentication')
|
|
159
|
+
require(auth['action']['scope'] == 'swiftapi.native_service.connection_check','Authentication scope')
|
|
160
|
+
require(auth['action']['instance_id'] == r['runtime_instance'],'Authentication instance')
|
|
161
|
+
require(assets.get('authentication.json') == encoded(auth),'Authentication asset')
|
|
162
|
+
summary['authority_signature'] = 'PASS_CONNECTION_CHECK'
|
|
163
|
+
if r['status'] == 'OPERATIONAL':
|
|
164
|
+
require(computed['class'] == 'operational' and not output and r.get('result') is None,'Operational misrepresented')
|
|
165
|
+
summary['execution_outcome'] = 'OPERATIONAL_NOT_A_VOID'
|
|
166
|
+
return summary
|
|
167
|
+
require(r['status'] == 'COMPLETED' and r['authentication'] is not None,'Completed without known key')
|
|
168
|
+
required = {'request.json','prompt.utf8.bin','input-token-ids.json','grant.json','output.bin','authentication.json'}
|
|
169
|
+
require(required <= set(assets),'Required assets missing')
|
|
170
|
+
action, grant, decision, result = r['action'],r['grant'],r['authority_decision'],r['result']
|
|
171
|
+
require(r['action_sha256'] == digest(encoded(action)),'Action hash')
|
|
172
|
+
require(action['scope'] == 'pccg2.native_decode','Native scope')
|
|
173
|
+
require(action['instance_id'] == r['runtime_instance'],'Action instance')
|
|
174
|
+
require(action['principal_sha256'] == r['authentication']['action']['principal_sha256'],'Principal binding')
|
|
175
|
+
require(action['service_source_sha256'] == r['source']['sha256'],'Action source binding')
|
|
176
|
+
require(action['profile']['mode'] == expected_mode,'Action profile mode')
|
|
177
|
+
req = json.loads(assets['request.json'])
|
|
178
|
+
require({'request_id','question'} <= set(req) <= {'request_id','question','grant'},'Raw request shape')
|
|
179
|
+
require({k:req[k] for k in ('request_id','question')} == action['request'],'Raw request binding')
|
|
180
|
+
require(req.get('grant') == grant and assets['grant.json'] == encoded(grant),'Raw grant binding')
|
|
181
|
+
require(digest(assets['prompt.utf8.bin']) == action['rendered_prompt_sha256'],'Prompt binding')
|
|
182
|
+
require(digest(assets['input-token-ids.json']) == action['input_ids_sha256'],'Input IDs binding')
|
|
183
|
+
require(result['input_ids'] == json.loads(assets['input-token-ids.json']),'Input IDs echo')
|
|
184
|
+
require(result['rendered_prompt_sha256'] == action['rendered_prompt_sha256'] and result['input_ids_sha256'] == action['input_ids_sha256'],'Backend input hashes')
|
|
185
|
+
when = decision['evaluated_at']
|
|
186
|
+
times = r['times']
|
|
187
|
+
require(r['started_at'] == times['request_arrival_at'] <= times['scores_ready_at'] <= when <= r['finished_at'],'Boundary timeline')
|
|
188
|
+
require(times['decision_at'] == when,'Decision timestamp')
|
|
189
|
+
require(times['grant_issued_at'] == (grant.get('issued_at') if isinstance(grant,dict) else None),'Grant timestamp binding')
|
|
190
|
+
current = r['boundary_authentication']
|
|
191
|
+
if current is not None:
|
|
192
|
+
require(grant_check(current['grant'],current['action'],current['observed_at'],authority_key) is None,'Boundary key authentication')
|
|
193
|
+
require(current['action']['scope'] == 'swiftapi.native_service.connection_check' and current['action']['instance_id'] == r['runtime_instance'] and current['action']['principal_sha256'] == action['principal_sha256'],'Boundary authentication binding')
|
|
194
|
+
else:
|
|
195
|
+
require(decision['reason'] == 'KEY_REJECTED_AT_BOUNDARY','Missing boundary authentication')
|
|
196
|
+
check = 'MISSING_PERMISSION' if grant is None else grant_check(grant,action,when,authority_key)
|
|
197
|
+
permission = decision['permission']
|
|
198
|
+
if permission == 'PERMISSION_VERIFIED':
|
|
199
|
+
require(check is None and current is not None and decision['reason'] == 'VERIFIED_EXACT_REQUEST','Unverified permission')
|
|
200
|
+
else:
|
|
201
|
+
require(permission == 'PERMISSION_NOT_VERIFIED','Permission enum')
|
|
202
|
+
reason = decision['reason']
|
|
203
|
+
if reason not in {'REPLAYED_REQUEST','REPLAYED_BOUNDARY_GRANT','AUTHORITY_REVOKED','KEY_REJECTED_AT_BOUNDARY'}:
|
|
204
|
+
require(reason == check or (reason == 'AUTHORITY_DENIED_UNSIGNED' and grant is None),'Denial reason mismatch')
|
|
205
|
+
elif reason == 'AUTHORITY_REVOKED':
|
|
206
|
+
require(check is None,'Revoked grant authenticity/binding')
|
|
207
|
+
signed_denial = check == 'AUTHORITY_DENIED'
|
|
208
|
+
require(decision['denial_authority_signed'] is signed_denial,'Signed denial label')
|
|
209
|
+
if signed_denial:
|
|
210
|
+
require(assets.get('authority-denial.json') == encoded(grant),'Signed denial asset')
|
|
211
|
+
signed_revocation=decision.get('revocation_authority_signed',False)
|
|
212
|
+
require(type(signed_revocation) is bool,'Signed revocation flag')
|
|
213
|
+
if signed_revocation:
|
|
214
|
+
require(decision['reason']=='AUTHORITY_REVOKED' and 'authority-revocation.json' in assets,
|
|
215
|
+
'Signed revocation asset missing or not a revoked decision')
|
|
216
|
+
entry=json.loads(assets['authority-revocation.json'])
|
|
217
|
+
verify_revocation_entry(entry,grant['jti'],authority_key,decision_at=when)
|
|
218
|
+
require(decision['revocation_observation']=='AUTHORITY_SIGNED_ENTRY','Revocation observation label')
|
|
219
|
+
summary['revocation_scope']='AUTHORITY_SIGNED_ENTRY'
|
|
220
|
+
else:
|
|
221
|
+
require('authority-revocation.json' not in assets,'Unlabelled signed revocation evidence')
|
|
222
|
+
require(decision.get('revocation_observation')!='AUTHORITY_SIGNED_ENTRY','Unsigned revocation labelled signed')
|
|
223
|
+
summary['revocation_authority_signed']=signed_revocation
|
|
224
|
+
condition = 'EQ(485487,485487)' if permission == 'PERMISSION_VERIFIED' else 'EQ(485487,485489)'
|
|
225
|
+
require(r['gate_condition'] == result['gate_condition'] == condition,'Derived condition')
|
|
226
|
+
require(result['permission'] == permission and result['gate_condition_truth'] is (permission == 'PERMISSION_VERIFIED'),'Backend permission')
|
|
227
|
+
require(result['schema'] == 'swiftapi-native-backend-result/2' and result['native_eos'] == EOS,'Backend identity')
|
|
228
|
+
require(r['model_executed'] is (expected_mode == MODES[1]),'Content execution mode')
|
|
229
|
+
require(r['execution_facts'] == result['execution_facts'],'Execution facts binding')
|
|
230
|
+
require(r['times']['backend_timing'] == result['timing'],'Backend timing binding')
|
|
231
|
+
require(result['execution_facts']['model_executed'] is r['model_executed'],'Content execution facts')
|
|
232
|
+
tokens = result['generated_token_ids']
|
|
233
|
+
require(tokens and all(type(t) is int and 0 <= t < VOCAB for t in tokens),'Token list')
|
|
234
|
+
cap = 1 if expected_mode == MODES[0] else action['profile']['max_new_tokens']
|
|
235
|
+
require(type(cap) is int and 1 <= cap <= 16,'Configured token budget')
|
|
236
|
+
require(result['max_new_tokens'] == cap and len(tokens) <= cap and EOS not in tokens[:-1],'Token budget/termination')
|
|
237
|
+
complete = tokens[-1] == EOS
|
|
238
|
+
require(result['generation_complete'] is complete,'Completion flag')
|
|
239
|
+
finish = 'NATIVE_EOS' if complete else 'REPLAY_ONE_TOKEN_ONLY' if expected_mode == MODES[0] else 'TOKEN_CAP'
|
|
240
|
+
scope = 'COMPLETE_TO_NATIVE_EOS' if complete else 'FIRST_TOKEN_ONLY_NOT_A_COMPLETE_ANSWER' if expected_mode == MODES[0] else 'TRUNCATED_AT_TOKEN_CAP'
|
|
241
|
+
require(result['finish'] == finish and result['answer_scope'] == scope,'Completion scope')
|
|
242
|
+
require(complete or len(tokens) == cap,'Incomplete trace')
|
|
243
|
+
if permission == 'PERMISSION_NOT_VERIFIED':
|
|
244
|
+
require(tokens == [EOS] and output == b'' and computed['class'] == 'V0','Denied permission did not produce V0')
|
|
245
|
+
require(base64.b64decode(result['output_utf8_base64'],validate=True) == output and result['output_bytes'] == len(output) and result['output_sha256'] == digest(output),'Output evidence')
|
|
246
|
+
require(result['first_token_is_native_eos'] is (tokens[0] == EOS),'First token flag')
|
|
247
|
+
require(len(result['steps']) == len(tokens),'Step count')
|
|
248
|
+
require(result['execution_facts']['forward_passes_completed'] == (len(tokens) if expected_mode == MODES[1] else 0),'Forward-pass count')
|
|
249
|
+
names = {f'{i}-{kind}.bf16.bin' for i in range(len(tokens)) for kind in ('stock','selected')}
|
|
250
|
+
require(set(r['vector_files']) == names,'Vector set')
|
|
251
|
+
for i,step in enumerate(result['steps']):
|
|
252
|
+
raw, vals = [],[]
|
|
253
|
+
for kind in ('stock','selected'):
|
|
254
|
+
name = f'{i}-{kind}.bf16.bin'
|
|
255
|
+
p = path.parent/name
|
|
256
|
+
require(not p.is_symlink() and p.stat().st_size == 2*VOCAB,'Vector path/size')
|
|
257
|
+
data = p.read_bytes()
|
|
258
|
+
require(digest(data) == r['vector_files'][name],'Vector hash')
|
|
259
|
+
raw.append(data); vals.append(vector(data))
|
|
260
|
+
before, after = (data[:2*EOS]+data[2*EOS+2:] for data in raw)
|
|
261
|
+
require(before == after,'Non-EOS bytes changed')
|
|
262
|
+
require(step['index'] == i and step['selected_token'] == tokens[i] == max(range(VOCAB),key=vals[1].__getitem__),'Greedy selection')
|
|
263
|
+
require(step['stock_argmax_token'] == max(range(VOCAB),key=vals[0].__getitem__) and step['selected_logit'] == vals[1][tokens[i]],'Stock argmax/selected score')
|
|
264
|
+
require(step['stock_eos_logit'] == vals[0][EOS] and step['intervened_eos_logit'] == vals[1][EOS],'EOS score binding')
|
|
265
|
+
require(step['stock_vector_sha256'] == digest(raw[0]) and step['selected_vector_sha256'] == digest(raw[1]) and step['non_eos_sha256'] == digest(after),'Score hashes')
|
|
266
|
+
require(step['score_source'] == ('AUTHENTIC_BANK_FIRST_LOGITS' if expected_mode == MODES[0] else 'FRESH_FORWARD_PASS'),'Vector provenance label')
|
|
267
|
+
if i:
|
|
268
|
+
require(raw[0] == raw[1] and step['intervention'] == 'NONE','Later token intervention')
|
|
269
|
+
require(r['stop_cause'] == result['stop_cause'] and r['stop_cause'] in ('NONE','LEARNED_GATE','VETO'),'Stop cause')
|
|
270
|
+
summary.update(tokens=tokens,output_bytes=len(output),non_eos_unchanged=True,
|
|
271
|
+
authority_signature='CHECKED_PRESENTED_GRANT_AND_CONNECTIONS',
|
|
272
|
+
permission=permission,reason=decision['reason'])
|
|
273
|
+
return summary
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
p=argparse.ArgumentParser(description=__doc__)
|
|
277
|
+
p.add_argument('receipt',type=Path)
|
|
278
|
+
p.add_argument('--trusted-key',required=True,type=Path)
|
|
279
|
+
p.add_argument('--expected-mode',required=True,choices=MODES)
|
|
280
|
+
p.add_argument('--expected-source-sha256')
|
|
281
|
+
p.add_argument('--expected-max-grant-age-seconds',required=True,type=float,
|
|
282
|
+
help='Registered value supplied independently, never read from the receipt')
|
|
283
|
+
a=p.parse_args()
|
|
284
|
+
try:
|
|
285
|
+
result=verify_receipt(a.receipt,read_json(a.trusted_key)['public_key'],a.expected_mode,
|
|
286
|
+
expected_max_grant_age_seconds=a.expected_max_grant_age_seconds,
|
|
287
|
+
expected_source_sha256=a.expected_source_sha256)
|
|
288
|
+
except Exception as exc:
|
|
289
|
+
print(json.dumps({'status':'FAIL','reason':str(exc)})); raise SystemExit(2)
|
|
290
|
+
print(json.dumps(result,sort_keys=True))
|
|
291
|
+
if __name__ == '__main__':
|
|
292
|
+
main()
|
|
@@ -2,6 +2,7 @@ CHANGELOG.md
|
|
|
2
2
|
LICENSE
|
|
3
3
|
MANIFEST.in
|
|
4
4
|
MIGRATION_1_2_2.md
|
|
5
|
+
NATIVE_RECEIPTS.md
|
|
5
6
|
PRODUCT_TRUTH_MAP.md
|
|
6
7
|
README.md
|
|
7
8
|
pyproject.toml
|
|
@@ -35,6 +36,10 @@ swiftapi/cli.py
|
|
|
35
36
|
swiftapi/client.py
|
|
36
37
|
swiftapi/enforcement.py
|
|
37
38
|
swiftapi/exceptions.py
|
|
39
|
+
swiftapi/native.py
|
|
40
|
+
swiftapi/native_classifier_releases.py
|
|
41
|
+
swiftapi/native_taxonomy.py
|
|
42
|
+
swiftapi/native_verify.py
|
|
38
43
|
swiftapi/openai.py
|
|
39
44
|
swiftapi/py.typed
|
|
40
45
|
swiftapi/utils.py
|
|
@@ -61,6 +66,7 @@ tests/test_brake_benchmarks_remote.py
|
|
|
61
66
|
tests/test_client.py
|
|
62
67
|
tests/test_exceptions.py
|
|
63
68
|
tests/test_legacy_security.py
|
|
69
|
+
tests/test_native.py
|
|
64
70
|
tests/test_openai.py
|
|
65
71
|
tests/test_release_contract.py
|
|
66
72
|
tests/test_responses.py
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import Mock
|
|
3
|
+
import pytest
|
|
4
|
+
from swiftapi import SwiftAPI,PolicyViolation
|
|
5
|
+
from swiftapi.native_taxonomy import classify,classification_record,classifier_hash,near_void,INVISIBLE,ELLIPSIS_FORMS
|
|
6
|
+
from swiftapi.native import NativeEOSClient
|
|
7
|
+
|
|
8
|
+
def test_release_registry_pins_packaged_classifier():
|
|
9
|
+
from swiftapi.native_classifier_releases import RELEASED_CLASSIFIER_HASHES
|
|
10
|
+
release=RELEASED_CLASSIFIER_HASHES[classifier_hash()]
|
|
11
|
+
assert release['first_sdk_version']=='1.4.0'
|
|
12
|
+
assert release['rule']=='void-matrix/2dfe895bdafd35d23d6dab5f307ed560214a3843'
|
|
13
|
+
|
|
14
|
+
def test_native_verifier_requires_external_expected_age_without_loading_model():
|
|
15
|
+
import inspect
|
|
16
|
+
from swiftapi.native_verify import verify_receipt
|
|
17
|
+
parameter=inspect.signature(verify_receipt).parameters['expected_max_grant_age_seconds']
|
|
18
|
+
assert parameter.kind is inspect.Parameter.KEYWORD_ONLY
|
|
19
|
+
assert parameter.default is inspect.Parameter.empty
|
|
20
|
+
|
|
21
|
+
@pytest.mark.parametrize('raw,successful,present,stop,expected',[
|
|
22
|
+
(b'',True,True,'stop','V0'),(b'',True,False,'stop','V2'),
|
|
23
|
+
(b'',True,True,'length','V1'),(b'',True,False,'length','V1'),
|
|
24
|
+
(b'',True,True,None,'VU'),(b'',False,True,'stop','operational'),
|
|
25
|
+
(b' ',True,True,'stop','NV'),(b'?!',True,True,'stop','R'),
|
|
26
|
+
('…'.encode(),True,True,'stop','NV'),('\u200b'.encode(),True,True,'stop','NV'),
|
|
27
|
+
(b'4',True,True,None,'R'),(b'hello.',True,True,'stop','R')])
|
|
28
|
+
def test_taxonomy(raw,successful,present,stop,expected):
|
|
29
|
+
assert classify(raw,successful=successful,container_present=present,stop=stop)==expected
|
|
30
|
+
@pytest.mark.parametrize('event',['RF','SB','TC'])
|
|
31
|
+
def test_distinct_fields(event):
|
|
32
|
+
assert classify(b'',successful=True,event=event)==event
|
|
33
|
+
def test_replay_not_complete():
|
|
34
|
+
r=classification_record(b'4','COMPLETED',{'finish':'REPLAY_ONE_TOKEN_ONLY',
|
|
35
|
+
'answer_scope':'FIRST_TOKEN_ONLY_NOT_A_COMPLETE_ANSWER','generation_complete':False,'generated_token_ids':[19]})
|
|
36
|
+
assert r['class']=='R' and not r['fields']['generation_complete']
|
|
37
|
+
assert r['response_scope']=='FIRST_TOKEN_ONLY_NOT_A_COMPLETE_ANSWER'
|
|
38
|
+
assert r['classifier_sha256']==classifier_hash()
|
|
39
|
+
def test_policy_violation_retains_exact_body_nonbreaking():
|
|
40
|
+
raw=b'{"approved": false, "denial_reason": "test"}'
|
|
41
|
+
response=Mock(status_code=200,content=raw)
|
|
42
|
+
response.json.return_value=json.loads(raw)
|
|
43
|
+
with SwiftAPI('swiftapi_live_test') as api:
|
|
44
|
+
with pytest.raises(PolicyViolation) as caught:api._handle_response(response)
|
|
45
|
+
assert caught.value.response==json.loads(raw)
|
|
46
|
+
assert caught.value.raw_response==raw
|
|
47
|
+
assert caught.value.status_code==200 and caught.value.denial_reason=='test'
|
|
48
|
+
@pytest.mark.parametrize('url',['https://other.example','http://localhost:3','http://127.0.0.1:3/?x=key','http://a:b@127.0.0.1:3','http://127.0.0.1:3/x'])
|
|
49
|
+
def test_native_client_rejects_key_exfiltration_urls(url):
|
|
50
|
+
with pytest.raises(ValueError):NativeEOSClient(key='swiftapi_live_'+'a'*64,base_url=url)
|
|
51
|
+
|
|
52
|
+
# Verbatim algorithm from void_matrix.FROZEN.py@2dfe895bdafd35d23d6dab5f307ed560214a3843.
|
|
53
|
+
# The independent integration suite additionally extracts the original function by AST.
|
|
54
|
+
def frozen_near_void(text):
|
|
55
|
+
if not text:
|
|
56
|
+
return False
|
|
57
|
+
# Literal frozen constants, independent of the implementation under test.
|
|
58
|
+
frozen_invisible = {0x200B,0x200C,0x200D,0x200E,0x200F,0xFEFF,
|
|
59
|
+
0x2060,0x2061,0x2062,0x2063,0x2064,0x2066,0x2067,0x2068,0x2069,
|
|
60
|
+
0x202A,0x202B,0x202C,0x202D,0x202E}
|
|
61
|
+
if all(ord(character) in frozen_invisible or character.isspace() for character in text):
|
|
62
|
+
return True
|
|
63
|
+
return text.strip() in {'.', '..', '...', '....', '…', '….'}
|
|
64
|
+
|
|
65
|
+
@pytest.mark.parametrize('text',['',' ','\t\n','\u200b','\u200d','\ufeff',
|
|
66
|
+
'.','..','...','....','.....','. .','…','….','… …','?','!','-','4',' 4 '])
|
|
67
|
+
def test_frozen_matrix_equivalence(text):
|
|
68
|
+
assert near_void(text) == frozen_near_void(text)
|
|
69
|
+
if text:
|
|
70
|
+
assert classify(text.encode(),successful=True,stop='stop') == ('NV' if frozen_near_void(text) else 'R')
|
|
71
|
+
else:
|
|
72
|
+
assert classify(b'',successful=True,stop='stop') == 'V0'
|
|
73
|
+
|
|
74
|
+
def test_receipt_grant_age_is_signed_profile_value():
|
|
75
|
+
from swiftapi.native_verify import grant_check,encoded,digest
|
|
76
|
+
from nacl.signing import SigningKey
|
|
77
|
+
from datetime import datetime,timezone
|
|
78
|
+
import base64
|
|
79
|
+
key=SigningKey.generate()
|
|
80
|
+
action={'scope':'pccg2.native_decode','profile':{'max_grant_age_seconds':90}}
|
|
81
|
+
grant={'action_type':action['scope'],'action_fingerprint':digest(encoded(action))[:32],
|
|
82
|
+
'approved':True,'jti':'test','issued_at':datetime.fromtimestamp(1000,timezone.utc).isoformat(),
|
|
83
|
+
'expires_at':datetime.fromtimestamp(1200,timezone.utc).isoformat()}
|
|
84
|
+
grant['signature']=base64.b64encode(key.sign(encoded(grant)).signature).decode()
|
|
85
|
+
grant['signing_mode']='ed25519'
|
|
86
|
+
public=base64.b64encode(bytes(key.verify_key)).decode()
|
|
87
|
+
assert grant_check(grant,action,1070,public) is None
|
|
88
|
+
assert grant_check(grant,action,1091,public) == 'GRANT_EXPIRED'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|