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.
Files changed (74) hide show
  1. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/MANIFEST.in +1 -0
  2. swiftapi_python-1.4.0/NATIVE_RECEIPTS.md +54 -0
  3. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/PKG-INFO +1 -1
  4. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/pyproject.toml +1 -1
  5. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/__init__.py +1 -1
  6. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/client.py +3 -0
  7. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/exceptions.py +4 -2
  8. swiftapi_python-1.4.0/swiftapi/native.py +59 -0
  9. swiftapi_python-1.4.0/swiftapi/native_classifier_releases.py +15 -0
  10. swiftapi_python-1.4.0/swiftapi/native_taxonomy.py +75 -0
  11. swiftapi_python-1.4.0/swiftapi/native_verify.py +292 -0
  12. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/PKG-INFO +1 -1
  13. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/SOURCES.txt +6 -0
  14. swiftapi_python-1.4.0/tests/test_native.py +88 -0
  15. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/CHANGELOG.md +0 -0
  16. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/LICENSE +0 -0
  17. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/MIGRATION_1_2_2.md +0 -0
  18. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/PRODUCT_TRUTH_MAP.md +0 -0
  19. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/README.md +0 -0
  20. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/benchmarks/corpora.lock.json +0 -0
  21. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/benchmarks/sources.lock.json +0 -0
  22. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/ACTION_ENVELOPES.md +0 -0
  23. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/BENCHMARKING.md +0 -0
  24. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/BYOK.md +0 -0
  25. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CLAUDE_CODE.md +0 -0
  26. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CODEX.md +0 -0
  27. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CONCEPTS.md +0 -0
  28. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/CONDITIONS.md +0 -0
  29. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/LOCAL_BRAKE.md +0 -0
  30. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/NATIVE_NONCONTINUATION.md +0 -0
  31. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/PYTHON_SDK.md +0 -0
  32. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/QUICKSTART.md +0 -0
  33. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/RECEIPTS.md +0 -0
  34. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/REMOTE_SWIFTAPI.md +0 -0
  35. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/RESEARCH_PROVENANCE.md +0 -0
  36. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/SECURITY_MODEL.md +0 -0
  37. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/THREAT_MODEL.md +0 -0
  38. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/docs/VERIFICATION.md +0 -0
  39. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/fetch_benchmarks.py +0 -0
  40. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/fetch_corpora.py +0 -0
  41. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/scripts/qualify_artifacts.py +0 -0
  42. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/setup.cfg +0 -0
  43. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/__main__.py +0 -0
  44. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/anthropic.py +0 -0
  45. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/__init__.py +0 -0
  46. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/benchmarks.py +0 -0
  47. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/broker.py +0 -0
  48. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/core.py +0 -0
  49. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/install.py +0 -0
  50. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/mcp.py +0 -0
  51. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/observations.py +0 -0
  52. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/qualification.py +0 -0
  53. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/brake/remote.py +0 -0
  54. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/cli.py +0 -0
  55. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/enforcement.py +0 -0
  56. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/openai.py +0 -0
  57. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/py.typed +0 -0
  58. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/utils.py +0 -0
  59. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi/verifier.py +0 -0
  60. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/dependency_links.txt +0 -0
  61. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/entry_points.txt +0 -0
  62. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/requires.txt +0 -0
  63. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/swiftapi_python.egg-info/top_level.txt +0 -0
  64. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/conftest.py +0 -0
  65. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_anthropic.py +0 -0
  66. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_brake.py +0 -0
  67. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_brake_benchmarks_remote.py +0 -0
  68. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_client.py +0 -0
  69. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_exceptions.py +0 -0
  70. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_legacy_security.py +0 -0
  71. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_openai.py +0 -0
  72. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_release_contract.py +0 -0
  73. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_responses.py +0 -0
  74. {swiftapi_python-1.3.1 → swiftapi_python-1.4.0}/tests/test_verifier.py +0 -0
@@ -4,3 +4,4 @@ recursive-include tests *.py
4
4
  recursive-include scripts *.py
5
5
  recursive-include benchmarks *.json
6
6
  recursive-include swiftapi py.typed
7
+ include NATIVE_RECEIPTS.md
@@ -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.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swiftapi-python"
7
- version = "1.3.1"
7
+ version = "1.4.0"
8
8
  description = "SwiftAPI Python SDK - AI Action Verification Gateway"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,5 +1,5 @@
1
1
  """SwiftAPI: verified execution for explicitly mediated capabilities."""
2
- __version__ = "1.3.1"
2
+ __version__ = "1.4.0"
3
3
  __author__ = "Rayan Pal"
4
4
 
5
5
  # Core exports
@@ -130,6 +130,9 @@ class SwiftAPI:
130
130
  message=reason,
131
131
  action_type=action_type,
132
132
  denial_reason=reason,
133
+ response=data,
134
+ raw_response=response.content,
135
+ status_code=response.status_code,
133
136
  )
134
137
 
135
138
  # Other errors
@@ -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
- super().__init__(message)
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.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
@@ -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