swiftapi-python 1.2.2__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ """Local consequence enforcement; no model or cloud account required."""
2
+
3
+ from .core import (
4
+ ActionEnvelope,
5
+ BindingCondition,
6
+ ContinuationAuthority,
7
+ Decision,
8
+ DecisionReceipt,
9
+ LocalAuthority,
10
+ LocalGuard,
11
+ ReplayLedger,
12
+ AuthorizationDenied,
13
+ ReplayError,
14
+ InvalidEnvelope,
15
+ BrakeError,
16
+ canonical,
17
+ digest,
18
+ verify_receipt,
19
+ verify_decision,
20
+ )
21
+ from .broker import CapabilityBroker
22
+
23
+ __all__ = [
24
+ "ActionEnvelope",
25
+ "BindingCondition",
26
+ "ContinuationAuthority",
27
+ "Decision",
28
+ "DecisionReceipt",
29
+ "LocalAuthority",
30
+ "LocalGuard",
31
+ "ReplayLedger",
32
+ "AuthorizationDenied",
33
+ "ReplayError",
34
+ "InvalidEnvelope",
35
+ "BrakeError",
36
+ "canonical",
37
+ "digest",
38
+ "verify_receipt",
39
+ "verify_decision",
40
+ "CapabilityBroker",
41
+ ]
42
+
43
+ from .remote import RemoteAuthority
44
+
45
+ __all__ += ["RemoteAuthority"]
@@ -0,0 +1,111 @@
1
+ """Import externally judged defensive evaluations without copying their corpora.
2
+
3
+ Rows must carry immutable framework/corpus identities and explicit executor
4
+ observations. This adapter does not infer safety, authority, or escape from text.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from collections import Counter
9
+ from pathlib import Path
10
+ from typing import Iterable
11
+ from .core import InvalidEnvelope, digest, loads, write_exclusive
12
+ from .observations import CLASSES
13
+
14
+ FRAMEWORKS = {"JailbreakBench", "HarmBench", "StrongREJECT"}
15
+
16
+
17
+ def summarize(rows: Iterable[dict], expected_ids: Iterable[str]) -> dict:
18
+ ids = list(expected_ids)
19
+ if not ids or len(ids) != len(set(ids)):
20
+ raise InvalidEnvelope("Expected unique nonempty benchmark case IDs")
21
+ results = list(rows)
22
+ seen = []
23
+ counts: Counter = Counter()
24
+ failures = []
25
+ for row in results:
26
+ required = {
27
+ "case_id",
28
+ "framework",
29
+ "revision",
30
+ "corpus_sha256",
31
+ "classification",
32
+ "authorized",
33
+ "consequence_observed",
34
+ "evidence_sha256",
35
+ }
36
+ if (
37
+ not required <= set(row)
38
+ or row["framework"] not in FRAMEWORKS
39
+ or row["classification"] not in CLASSES
40
+ ):
41
+ raise InvalidEnvelope("Missing framework, evidence or outcome fields")
42
+ for field, size in (("revision", 40), ("corpus_sha256", 64), ("evidence_sha256", 64)):
43
+ v = row[field]
44
+ if type(v) is not str or len(v) != size or any(c not in "0123456789abcdef" for c in v):
45
+ raise InvalidEnvelope("Immutable source and evidence hashes required")
46
+ if type(row["authorized"]) is not bool or type(row["consequence_observed"]) is not bool:
47
+ raise InvalidEnvelope("Explicit independent execution observations required")
48
+ seen.append(row["case_id"])
49
+ counts[row["classification"]] += 1
50
+ if row["consequence_observed"] and not row["authorized"]:
51
+ failures.append(row)
52
+ if len(seen) != len(set(seen)) or set(seen) != set(ids):
53
+ raise InvalidEnvelope("Missing, duplicate, or unexpected cases; denominator cannot change")
54
+ return {
55
+ "schema": "swiftapi.benchmark/v1",
56
+ "total": len(ids),
57
+ "counts": dict(counts),
58
+ "unauthorized_consequential_escapes": len(failures),
59
+ "status": "FAIL" if failures or counts["ERROR"] else "PASS",
60
+ "failures": failures,
61
+ "rows_sha256": digest(results),
62
+ "results": results,
63
+ }
64
+
65
+
66
+ def import_results(source: Path, output: Path, expected_ids: list) -> dict:
67
+ """Preserve all supplied rows. Output must be new; failures remain regressions."""
68
+ rows = [loads(line) for line in source.read_bytes().splitlines() if line.strip()]
69
+ report = summarize(rows, expected_ids)
70
+ write_exclusive(output, report)
71
+ return report
72
+
73
+
74
+ def load_cases(framework: str, corpus: Path, expected_sha256: str) -> list:
75
+ """Read original upstream CSV bytes after pin verification; never rewrite corpora."""
76
+ import csv
77
+ import hashlib
78
+ import io
79
+
80
+ if framework not in FRAMEWORKS:
81
+ raise InvalidEnvelope("Unknown framework")
82
+ raw = corpus.read_bytes()
83
+ if hashlib.sha256(raw).hexdigest() != expected_sha256:
84
+ raise InvalidEnvelope("Corpus hash mismatch")
85
+ rows = list(csv.DictReader(io.StringIO(raw.decode("utf-8-sig"))))
86
+ mappings = {
87
+ "JailbreakBench": ("Goal", "Behavior", "Category"),
88
+ "HarmBench": ("Behavior", "BehaviorID", "SemanticCategory"),
89
+ "StrongREJECT": ("forbidden_prompt", None, "category"),
90
+ }
91
+ prompt, identifier, category = mappings[framework]
92
+ result = []
93
+ for index, row in enumerate(rows):
94
+ if prompt not in row or category not in row or (identifier and identifier not in row):
95
+ raise InvalidEnvelope("Unexpected upstream corpus columns")
96
+ result.append(
97
+ {
98
+ "case_id": framework
99
+ + ":"
100
+ + corpus.stem
101
+ + ":"
102
+ + (row[identifier] if identifier else str(index)),
103
+ "prompt": row[prompt],
104
+ "context": row.get("ContextString", ""),
105
+ "category": row[category],
106
+ "corpus_sha256": expected_sha256,
107
+ }
108
+ )
109
+ if len({r["case_id"] for r in result}) != len(result):
110
+ raise InvalidEnvelope("Duplicate corpus IDs")
111
+ return result
@@ -0,0 +1,274 @@
1
+ """A narrow credential-owning capability broker, independent of host hooks.
2
+
3
+ The bundled capability creates a NEW UTF-8 marker inside one configured sandbox.
4
+ No arbitrary shell execution, overwrite, path traversal, or model calls.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ import os
9
+ import re
10
+ from pathlib import Path
11
+ from typing import Optional
12
+ from .core import (
13
+ ActionEnvelope,
14
+ AuthorizationDenied,
15
+ BindingCondition,
16
+ Decision,
17
+ InvalidEnvelope,
18
+ LocalAuthority,
19
+ ReplayLedger,
20
+ digest,
21
+ loads,
22
+ secure_dir,
23
+ verify_decision,
24
+ verify_receipt,
25
+ write_exclusive,
26
+ )
27
+
28
+
29
+ class CapabilityBroker:
30
+ def __init__(self, root: Optional[Path] = None):
31
+ self.root = secure_dir(
32
+ Path(root or os.environ.get("SWIFTAPI_HOME", str(Path.home() / ".local/share/swiftapi")))
33
+ )
34
+ self.sandbox = secure_dir(self.root / "sandbox")
35
+ self.pending = secure_dir(self.root / "pending")
36
+ self.grants = secure_dir(self.root / "grants")
37
+ self.receipts = secure_dir(self.root / "receipts")
38
+ self.authority = LocalAuthority.load(self.root)
39
+ self.ledger = ReplayLedger(self.root)
40
+ key = self.root / "public-key.json"
41
+ if not key.exists():
42
+ try:
43
+ write_exclusive(key, {"public_key": self.authority.public_key})
44
+ except FileExistsError:
45
+ pass
46
+ if key.is_symlink() or loads(key.read_bytes()) != {"public_key": self.authority.public_key}:
47
+ raise InvalidEnvelope("Trust key changed")
48
+
49
+ def status(self) -> dict:
50
+ return {
51
+ "version": "1.3.1",
52
+ "scope": "write_marker in configured sandbox only",
53
+ "sandbox": str(self.sandbox),
54
+ "public_key_file": str(self.root / "public-key.json"),
55
+ "public_key": self.authority.public_key,
56
+ "pending": sorted(p.stem for p in self.pending.glob("*.json")),
57
+ "receipt_count": len(list(self.receipts.glob("*.json"))),
58
+ "native_eos_claim": False,
59
+ }
60
+
61
+ def _action(self, name: str, content: str) -> ActionEnvelope:
62
+ if type(name) is not str or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,99}", name):
63
+ raise InvalidEnvelope(
64
+ "Marker name must be a basename of 1..100 ASCII letters, digits, _, ., -"
65
+ )
66
+ if type(content) is not str or len(content.encode("utf-8")) > 4096:
67
+ raise InvalidEnvelope("Marker content must be UTF-8 text of at most 4096 bytes")
68
+ return ActionEnvelope(
69
+ "write_marker",
70
+ {"sandbox": str(self.sandbox), "name": name, "content": content},
71
+ "Create the exact marker once",
72
+ )
73
+
74
+ def _state(self, action: ActionEnvelope) -> dict:
75
+ p = action.to_dict()["params"]
76
+ if self._action(p["name"], p["content"]).to_dict() != action.to_dict():
77
+ raise InvalidEnvelope("Unregistered capability or sandbox")
78
+ if self.sandbox.is_symlink() or self.sandbox.resolve() != self.sandbox:
79
+ raise InvalidEnvelope("Sandbox alias rejected")
80
+ s = self.sandbox.stat()
81
+ target = self.sandbox / p["name"]
82
+ return {
83
+ "sandbox": str(self.sandbox),
84
+ "device": s.st_dev,
85
+ "inode": s.st_ino,
86
+ "target_absent": not os.path.lexists(str(target)),
87
+ }
88
+
89
+ def propose(self, name: str, content: str) -> dict:
90
+ action = self._action(name, content)
91
+ state = self._state(action)
92
+ if state["target_absent"] is not True:
93
+ raise AuthorizationDenied("Existing targets cannot be overwritten")
94
+ condition = BindingCondition(action.action_hash, state, "operator-authorized-new-marker")
95
+ obj = {"action": action.to_dict(), "condition": condition.to_dict()}
96
+ request_id = digest(obj)
97
+ path = self.pending / (request_id + ".json")
98
+ if not path.exists():
99
+ try:
100
+ write_exclusive(path, obj)
101
+ except FileExistsError:
102
+ pass
103
+ return {
104
+ "request_id": request_id,
105
+ **obj,
106
+ "authorized": (self.grants / (request_id + ".json")).exists(),
107
+ "next": "Operator: swiftapi brake authorize " + request_id,
108
+ }
109
+
110
+ def request(self, request_id: str) -> tuple:
111
+ if type(request_id) is not str or not re.fullmatch("[a-f0-9]{64}", request_id):
112
+ raise InvalidEnvelope("Invalid request ID")
113
+ path = self.pending / (request_id + ".json")
114
+ if path.is_symlink():
115
+ raise InvalidEnvelope("Request alias rejected")
116
+ obj = loads(path.read_bytes())
117
+ if digest(obj) != request_id:
118
+ raise AuthorizationDenied("Request bytes changed")
119
+ action = ActionEnvelope(**obj["action"])
120
+ condition = BindingCondition(**obj["condition"])
121
+ self._state(action)
122
+ return action, condition
123
+
124
+ def authorize(self, request_id: str, ttl: int = 300) -> dict:
125
+ """Operator/application entry point. Deliberately absent from MCP tools."""
126
+ action, condition = self.request(request_id)
127
+ state = self._state(action)
128
+ decision = self.authority.authorize(
129
+ condition=condition, action=action, state=state, ttl=ttl
130
+ )
131
+ verify_decision(decision, self.authority.public_key, action, condition, state)
132
+ # Never replace a consumed/expired grant. A request has at most one grant.
133
+ write_exclusive(self.grants / (request_id + ".json"), decision.to_dict())
134
+ return {
135
+ "request_id": request_id,
136
+ "decision": decision.to_dict(),
137
+ "classification": "VISIBLE_SAFE",
138
+ "executed": False,
139
+ }
140
+
141
+ def _receipt(
142
+ self,
143
+ classification: str,
144
+ request_id: str,
145
+ action: Optional[ActionEnvelope],
146
+ decision: Optional[Decision],
147
+ phase: str,
148
+ reason: str,
149
+ executed: bool,
150
+ ) -> dict:
151
+ import secrets
152
+ import time
153
+
154
+ payload: dict = {
155
+ "schema": "swiftapi.execution/v1",
156
+ "issuer": self.authority.public_key,
157
+ "receipt_id": secrets.token_hex(32),
158
+ "created_at": int(time.time()),
159
+ "classification": classification,
160
+ "phase": phase,
161
+ "request_id": request_id,
162
+ "action_hash": action.action_hash if action else None,
163
+ "decision_hash": digest(decision.to_dict()) if decision else None,
164
+ "executed": executed,
165
+ "reason": reason,
166
+ }
167
+ receipt = self.authority.sign(payload)
168
+ path = self.receipts / (payload["receipt_id"] + ".json")
169
+ write_exclusive(path, receipt.to_dict())
170
+ return {
171
+ "classification": classification,
172
+ "executed": executed,
173
+ "receipt_path": str(path),
174
+ "receipt": receipt.to_dict(),
175
+ }
176
+
177
+ def execute(self, request_id: str) -> dict:
178
+ action = decision = None
179
+ try:
180
+ action, condition = self.request(request_id)
181
+ grant_path = self.grants / (request_id + ".json")
182
+ if grant_path.is_symlink():
183
+ raise AuthorizationDenied("Grant alias rejected")
184
+ decision = Decision.from_dict(loads(grant_path.read_bytes()))
185
+ state = self._state(action)
186
+ p = verify_decision(decision, self.authority.public_key, action, condition, state)
187
+ self.ledger.claim(p["issuer"], p["nonce"])
188
+ except (AuthorizationDenied, InvalidEnvelope, FileNotFoundError) as exc:
189
+ return self._receipt(
190
+ "GATE_BLOCK",
191
+ request_id,
192
+ action,
193
+ decision,
194
+ "authorization",
195
+ type(exc).__name__,
196
+ False,
197
+ )
198
+ # Persist authorization intent before executing. Any persistence failure aborts.
199
+ self._receipt(
200
+ "VISIBLE_SAFE",
201
+ request_id,
202
+ action,
203
+ decision,
204
+ "authorized_intent",
205
+ "verified; execution has not started",
206
+ False,
207
+ )
208
+ params = action.to_dict()["params"]
209
+ fd = None
210
+ try:
211
+ if self._state(action) != state:
212
+ raise AuthorizationDenied("State changed at execution boundary")
213
+ directory = os.open(str(self.sandbox), os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
214
+ try:
215
+ st = os.fstat(directory)
216
+ if (st.st_dev, st.st_ino) != (state["device"], state["inode"]):
217
+ raise AuthorizationDenied("Sandbox identity changed")
218
+ fd = os.open(
219
+ params["name"],
220
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
221
+ 0o600,
222
+ dir_fd=directory,
223
+ )
224
+ with os.fdopen(fd, "wb") as stream:
225
+ fd = None
226
+ stream.write(params["content"].encode("utf-8"))
227
+ stream.flush()
228
+ os.fsync(stream.fileno())
229
+ os.fsync(directory)
230
+ finally:
231
+ os.close(directory)
232
+ except Exception as exc:
233
+ if fd is not None:
234
+ os.close(fd)
235
+ # May contain a partial write; never retry or claim no consequence.
236
+ return self._receipt(
237
+ "ERROR",
238
+ request_id,
239
+ action,
240
+ decision,
241
+ "execution",
242
+ type(exc).__name__ + "; inspect sandbox; do not retry",
243
+ False,
244
+ )
245
+ return self._receipt(
246
+ "AUTHORIZED_EXECUTION",
247
+ request_id,
248
+ action,
249
+ decision,
250
+ "execution",
251
+ "exact new marker written",
252
+ True,
253
+ )
254
+
255
+ def read_marker(self, name: str) -> dict:
256
+ action = self._action(name, "")
257
+ self._state(action)
258
+ directory = os.open(str(self.sandbox), os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
259
+ try:
260
+ fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
261
+ with os.fdopen(fd, "rb") as f:
262
+ import stat
263
+
264
+ if not stat.S_ISREG(os.fstat(f.fileno()).st_mode):
265
+ raise InvalidEnvelope("Not a regular marker")
266
+ data = f.read(4097)
267
+ finally:
268
+ os.close(directory)
269
+ if len(data) > 4096:
270
+ raise InvalidEnvelope("Marker exceeds limit")
271
+ return {"classification": "VISIBLE_SAFE", "name": name, "content": data.decode("utf-8")}
272
+
273
+ def verify(self, path: Path) -> dict:
274
+ return verify_receipt(loads(path.read_bytes()), self.authority.public_key)