swiftapi-python 1.3.0__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.0 → swiftapi_python-1.4.0}/CHANGELOG.md +6 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/MANIFEST.in +1 -0
- swiftapi_python-1.4.0/NATIVE_RECEIPTS.md +54 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/PKG-INFO +10 -13
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/README.md +9 -12
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/pyproject.toml +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/scripts/qualify_artifacts.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/__init__.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/broker.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/install.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/mcp.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/qualification.py +1 -1
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/client.py +3 -0
- {swiftapi_python-1.3.0 → 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.0 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/PKG-INFO +10 -13
- {swiftapi_python-1.3.0 → 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.0 → swiftapi_python-1.4.0}/tests/test_release_contract.py +13 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/LICENSE +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/MIGRATION_1_2_2.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/PRODUCT_TRUTH_MAP.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/benchmarks/corpora.lock.json +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/benchmarks/sources.lock.json +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/ACTION_ENVELOPES.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/BENCHMARKING.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/BYOK.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/CLAUDE_CODE.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/CODEX.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/CONCEPTS.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/CONDITIONS.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/LOCAL_BRAKE.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/NATIVE_NONCONTINUATION.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/PYTHON_SDK.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/QUICKSTART.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/RECEIPTS.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/REMOTE_SWIFTAPI.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/RESEARCH_PROVENANCE.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/SECURITY_MODEL.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/THREAT_MODEL.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/docs/VERIFICATION.md +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/scripts/fetch_benchmarks.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/scripts/fetch_corpora.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/setup.cfg +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/__main__.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/anthropic.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/__init__.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/benchmarks.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/core.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/observations.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/brake/remote.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/cli.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/enforcement.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/openai.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/py.typed +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/utils.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi/verifier.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/dependency_links.txt +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/entry_points.txt +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/requires.txt +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/top_level.txt +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/conftest.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_anthropic.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_brake.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_brake_benchmarks_remote.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_client.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_exceptions.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_legacy_security.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_openai.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_responses.py +0 -0
- {swiftapi_python-1.3.0 → swiftapi_python-1.4.0}/tests/test_verifier.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.1 — 2026-09-22
|
|
4
|
+
|
|
5
|
+
Fix package-index README guide links by pointing to the public source distribution
|
|
6
|
+
and documentation. Runtime behavior is unchanged from 1.3.0; version reporting
|
|
7
|
+
identifies this patch. Historical distributions remain immutable.
|
|
8
|
+
|
|
3
9
|
## 1.3.0 — 2026-09-22
|
|
4
10
|
|
|
5
11
|
Add local prerequisite evaluation, canonical action envelopes, Ed25519 decisions
|
|
@@ -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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swiftapi-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: SwiftAPI Python SDK - AI Action Verification Gateway
|
|
5
5
|
Author-email: Rayan Pal <rayan@swiftapi.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -131,17 +131,14 @@ causal, theoretical, and protocol boundaries.
|
|
|
131
131
|
|
|
132
132
|
## Documentation
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
[
|
|
143
|
-
|
|
144
|
-
[Migration from 1.2.2](MIGRATION_1_2_2.md) documents security corrections and
|
|
145
|
-
termination-metadata changes. Historical 1.2.2 bytes remain immutable on PyPI.
|
|
134
|
+
The full guides ship in the [source distribution on PyPI](https://pypi.org/project/swiftapi-python/#files),
|
|
135
|
+
inside `docs/`. Start with `docs/QUICKSTART.md`. The source also includes guides
|
|
136
|
+
for concepts, the local brake, Codex, Claude Code, the Python SDK, conditions,
|
|
137
|
+
action envelopes, receipts, verification, remote authority, BYOK, security,
|
|
138
|
+
the threat model, native noncontinuation, benchmarks, and research provenance.
|
|
139
|
+
|
|
140
|
+
`MIGRATION_1_2_2.md` documents security corrections and termination-metadata
|
|
141
|
+
changes. Historical 1.2.2 bytes remain immutable on PyPI. The public
|
|
142
|
+
[SwiftAPI documentation](https://getswiftapi.com/docs) remains available online.
|
|
146
143
|
|
|
147
144
|
MIT license. Research and software by Rayan Pal / SwiftAPI Labs.
|
|
@@ -95,17 +95,14 @@ causal, theoretical, and protocol boundaries.
|
|
|
95
95
|
|
|
96
96
|
## Documentation
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
[
|
|
107
|
-
|
|
108
|
-
[Migration from 1.2.2](MIGRATION_1_2_2.md) documents security corrections and
|
|
109
|
-
termination-metadata changes. Historical 1.2.2 bytes remain immutable on PyPI.
|
|
98
|
+
The full guides ship in the [source distribution on PyPI](https://pypi.org/project/swiftapi-python/#files),
|
|
99
|
+
inside `docs/`. Start with `docs/QUICKSTART.md`. The source also includes guides
|
|
100
|
+
for concepts, the local brake, Codex, Claude Code, the Python SDK, conditions,
|
|
101
|
+
action envelopes, receipts, verification, remote authority, BYOK, security,
|
|
102
|
+
the threat model, native noncontinuation, benchmarks, and research provenance.
|
|
103
|
+
|
|
104
|
+
`MIGRATION_1_2_2.md` documents security corrections and termination-metadata
|
|
105
|
+
changes. Historical 1.2.2 bytes remain immutable on PyPI. The public
|
|
106
|
+
[SwiftAPI documentation](https://getswiftapi.com/docs) remains available online.
|
|
110
107
|
|
|
111
108
|
MIT license. Research and software by Rayan Pal / SwiftAPI Labs.
|
|
@@ -60,7 +60,7 @@ def main():
|
|
|
60
60
|
[
|
|
61
61
|
python,
|
|
62
62
|
"-c",
|
|
63
|
-
'import swiftapi,sys,pathlib; assert pathlib.Path(sys.prefix) in pathlib.Path(swiftapi.__file__).parents; assert swiftapi.__version__ == "1.3.
|
|
63
|
+
'import swiftapi,sys,pathlib; assert pathlib.Path(sys.prefix) in pathlib.Path(swiftapi.__file__).parents; assert swiftapi.__version__ == "1.3.1"; print(swiftapi.__file__)',
|
|
64
64
|
],
|
|
65
65
|
)
|
|
66
66
|
run("doctor", [command, "doctor"])
|
|
@@ -260,7 +260,7 @@ def doctor(home: Optional[Path] = None) -> dict:
|
|
|
260
260
|
check["error"] = "configuration invalid"
|
|
261
261
|
checks[h] = check
|
|
262
262
|
return {
|
|
263
|
-
"version": "1.3.
|
|
263
|
+
"version": "1.3.1",
|
|
264
264
|
"python": sys.version.split()[0],
|
|
265
265
|
"local_brake": "available",
|
|
266
266
|
"hosts": checks,
|
|
@@ -94,7 +94,7 @@ def serve() -> None:
|
|
|
94
94
|
result = {
|
|
95
95
|
"protocolVersion": requested if requested in supported else supported[-1],
|
|
96
96
|
"capabilities": {"tools": {"listChanged": False}},
|
|
97
|
-
"serverInfo": {"name": "swiftapi_brake", "version": "1.3.
|
|
97
|
+
"serverInfo": {"name": "swiftapi_brake", "version": "1.3.1"},
|
|
98
98
|
"instructions": "Use the broker for its sandbox markers. Approval is operator-only. A denied action is GATE_BLOCK, never native Void.",
|
|
99
99
|
}
|
|
100
100
|
elif method == "ping":
|
|
@@ -102,7 +102,7 @@ def qualify(root: Path) -> dict:
|
|
|
102
102
|
checks.append({"name": name, "passed": blocked})
|
|
103
103
|
result = {
|
|
104
104
|
"schema": "swiftapi.qualification/v1",
|
|
105
|
-
"version": "1.3.
|
|
105
|
+
"version": "1.3.1",
|
|
106
106
|
"checks": checks,
|
|
107
107
|
"passed": sum(c["passed"] for c in checks),
|
|
108
108
|
"total": len(checks),
|
|
@@ -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()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swiftapi-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: SwiftAPI Python SDK - AI Action Verification Gateway
|
|
5
5
|
Author-email: Rayan Pal <rayan@swiftapi.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -131,17 +131,14 @@ causal, theoretical, and protocol boundaries.
|
|
|
131
131
|
|
|
132
132
|
## Documentation
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
[
|
|
143
|
-
|
|
144
|
-
[Migration from 1.2.2](MIGRATION_1_2_2.md) documents security corrections and
|
|
145
|
-
termination-metadata changes. Historical 1.2.2 bytes remain immutable on PyPI.
|
|
134
|
+
The full guides ship in the [source distribution on PyPI](https://pypi.org/project/swiftapi-python/#files),
|
|
135
|
+
inside `docs/`. Start with `docs/QUICKSTART.md`. The source also includes guides
|
|
136
|
+
for concepts, the local brake, Codex, Claude Code, the Python SDK, conditions,
|
|
137
|
+
action envelopes, receipts, verification, remote authority, BYOK, security,
|
|
138
|
+
the threat model, native noncontinuation, benchmarks, and research provenance.
|
|
139
|
+
|
|
140
|
+
`MIGRATION_1_2_2.md` documents security corrections and termination-metadata
|
|
141
|
+
changes. Historical 1.2.2 bytes remain immutable on PyPI. The public
|
|
142
|
+
[SwiftAPI documentation](https://getswiftapi.com/docs) remains available online.
|
|
146
143
|
|
|
147
144
|
MIT license. Research and software by Rayan Pal / SwiftAPI Labs.
|
|
@@ -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'
|
|
@@ -90,3 +90,16 @@ def test_revocation_unknown_stops_paranoid_callback():
|
|
|
90
90
|
api._request = Mock(side_effect=NetworkError("offline"))
|
|
91
91
|
with pytest.raises(NetworkError):
|
|
92
92
|
api.check_revocation("x")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_readme_document_links_resolve_outside_source_checkout():
|
|
96
|
+
from urllib.parse import urlparse
|
|
97
|
+
|
|
98
|
+
source = (Path(__file__).resolve().parents[1] / "README.md").read_text()
|
|
99
|
+
links = re.findall(r"\[[^\]]+\]\(([^)]+)\)", source)
|
|
100
|
+
assert links
|
|
101
|
+
for link in links:
|
|
102
|
+
parsed = urlparse(link)
|
|
103
|
+
assert parsed.scheme == "https" and parsed.netloc, (
|
|
104
|
+
"PyPI does not resolve repository-relative README links"
|
|
105
|
+
)
|
|
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.0 → 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
|