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.
- swiftapi/__init__.py +75 -96
- swiftapi/__main__.py +2 -0
- swiftapi/anthropic.py +236 -222
- swiftapi/brake/__init__.py +45 -0
- swiftapi/brake/benchmarks.py +111 -0
- swiftapi/brake/broker.py +274 -0
- swiftapi/brake/core.py +420 -0
- swiftapi/brake/install.py +268 -0
- swiftapi/brake/mcp.py +160 -0
- swiftapi/brake/observations.py +61 -0
- swiftapi/brake/qualification.py +116 -0
- swiftapi/brake/remote.py +90 -0
- swiftapi/cli.py +150 -0
- swiftapi/client.py +328 -309
- swiftapi/enforcement.py +281 -280
- swiftapi/openai.py +243 -225
- swiftapi/py.typed +0 -0
- swiftapi/verifier.py +182 -130
- swiftapi_python-1.3.1.dist-info/METADATA +144 -0
- swiftapi_python-1.3.1.dist-info/RECORD +26 -0
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/WHEEL +1 -1
- swiftapi_python-1.3.1.dist-info/entry_points.txt +2 -0
- swiftapi_python-1.2.2.dist-info/METADATA +0 -255
- swiftapi_python-1.2.2.dist-info/RECORD +0 -13
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {swiftapi_python-1.2.2.dist-info → swiftapi_python-1.3.1.dist-info}/top_level.txt +0 -0
swiftapi/brake/mcp.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Dependency-light newline-delimited stdio MCP server. No approval tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from .broker import CapabilityBroker
|
|
7
|
+
from .core import loads, BrakeError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def tools() -> list:
|
|
11
|
+
def tool(name, description, properties, required=(), read_only=False):
|
|
12
|
+
return {
|
|
13
|
+
"name": name,
|
|
14
|
+
"description": description,
|
|
15
|
+
"annotations": {
|
|
16
|
+
"readOnlyHint": read_only,
|
|
17
|
+
"destructiveHint": False,
|
|
18
|
+
"openWorldHint": False,
|
|
19
|
+
},
|
|
20
|
+
"inputSchema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": properties,
|
|
23
|
+
"required": list(required),
|
|
24
|
+
"additionalProperties": False,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
string = {"type": "string"}
|
|
29
|
+
return [
|
|
30
|
+
tool(
|
|
31
|
+
"status",
|
|
32
|
+
"Read the broker sandbox, pending requests and public trust key.",
|
|
33
|
+
{},
|
|
34
|
+
read_only=True,
|
|
35
|
+
),
|
|
36
|
+
tool(
|
|
37
|
+
"propose_marker",
|
|
38
|
+
"Propose an exact new sandbox marker. Does not authorize or execute.",
|
|
39
|
+
{"name": string, "content": string},
|
|
40
|
+
("name", "content"),
|
|
41
|
+
),
|
|
42
|
+
tool(
|
|
43
|
+
"execute",
|
|
44
|
+
"Attempt a request. Missing/invalid approval returns GATE_BLOCK; it is not native Void.",
|
|
45
|
+
{"request_id": string},
|
|
46
|
+
("request_id",),
|
|
47
|
+
),
|
|
48
|
+
tool(
|
|
49
|
+
"read_marker",
|
|
50
|
+
"Read a small UTF-8 marker inside the configured broker sandbox.",
|
|
51
|
+
{"name": string},
|
|
52
|
+
("name",),
|
|
53
|
+
read_only=True,
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def call(broker: CapabilityBroker, name: str, args: dict) -> dict:
|
|
59
|
+
schemas = {t["name"]: t["inputSchema"] for t in tools()}
|
|
60
|
+
if name not in schemas or type(args) is not dict:
|
|
61
|
+
raise BrakeError("Unknown tool or invalid arguments")
|
|
62
|
+
schema = schemas[name]
|
|
63
|
+
if set(args) != set(schema["properties"]) or any(type(v) is not str for v in args.values()):
|
|
64
|
+
raise BrakeError("Invalid arguments")
|
|
65
|
+
if name == "status":
|
|
66
|
+
return broker.status()
|
|
67
|
+
if name == "propose_marker":
|
|
68
|
+
return broker.propose(**args)
|
|
69
|
+
if name == "execute":
|
|
70
|
+
return broker.execute(**args)
|
|
71
|
+
return broker.read_marker(**args)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def serve() -> None:
|
|
75
|
+
broker = CapabilityBroker()
|
|
76
|
+
while True:
|
|
77
|
+
line = sys.stdin.buffer.readline(2 * 1024 * 1024 + 1)
|
|
78
|
+
if not line:
|
|
79
|
+
break
|
|
80
|
+
rid = None
|
|
81
|
+
try:
|
|
82
|
+
if len(line) > 2 * 1024 * 1024:
|
|
83
|
+
raise ValueError("message too large")
|
|
84
|
+
req = loads(line)
|
|
85
|
+
if type(req) is not dict or req.get("jsonrpc") != "2.0":
|
|
86
|
+
raise ValueError("invalid request")
|
|
87
|
+
rid = req.get("id")
|
|
88
|
+
if rid is None:
|
|
89
|
+
continue
|
|
90
|
+
method = req.get("method")
|
|
91
|
+
if method == "initialize":
|
|
92
|
+
requested = req.get("params", {}).get("protocolVersion")
|
|
93
|
+
supported = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"]
|
|
94
|
+
result = {
|
|
95
|
+
"protocolVersion": requested if requested in supported else supported[-1],
|
|
96
|
+
"capabilities": {"tools": {"listChanged": False}},
|
|
97
|
+
"serverInfo": {"name": "swiftapi_brake", "version": "1.3.1"},
|
|
98
|
+
"instructions": "Use the broker for its sandbox markers. Approval is operator-only. A denied action is GATE_BLOCK, never native Void.",
|
|
99
|
+
}
|
|
100
|
+
elif method == "ping":
|
|
101
|
+
result = {}
|
|
102
|
+
elif method == "tools/list":
|
|
103
|
+
result = {"tools": tools()}
|
|
104
|
+
elif method == "tools/call":
|
|
105
|
+
try:
|
|
106
|
+
args = req["params"]
|
|
107
|
+
value = call(broker, args["name"], args.get("arguments", {}))
|
|
108
|
+
result = {
|
|
109
|
+
"content": [
|
|
110
|
+
{"type": "text", "text": json.dumps(value, ensure_ascii=False)}
|
|
111
|
+
],
|
|
112
|
+
"isError": False,
|
|
113
|
+
}
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
result = {
|
|
116
|
+
"content": [
|
|
117
|
+
{
|
|
118
|
+
"type": "text",
|
|
119
|
+
"text": json.dumps(
|
|
120
|
+
{
|
|
121
|
+
"classification": "ERROR",
|
|
122
|
+
"error": type(exc).__name__,
|
|
123
|
+
"executed": False,
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"isError": True,
|
|
129
|
+
}
|
|
130
|
+
else:
|
|
131
|
+
print(
|
|
132
|
+
json.dumps(
|
|
133
|
+
{
|
|
134
|
+
"jsonrpc": "2.0",
|
|
135
|
+
"id": rid,
|
|
136
|
+
"error": {"code": -32601, "message": "Method not found"},
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
flush=True,
|
|
140
|
+
)
|
|
141
|
+
continue
|
|
142
|
+
print(
|
|
143
|
+
json.dumps({"jsonrpc": "2.0", "id": rid, "result": result}, ensure_ascii=False),
|
|
144
|
+
flush=True,
|
|
145
|
+
)
|
|
146
|
+
except Exception:
|
|
147
|
+
print(
|
|
148
|
+
json.dumps(
|
|
149
|
+
{
|
|
150
|
+
"jsonrpc": "2.0",
|
|
151
|
+
"id": rid,
|
|
152
|
+
"error": {"code": -32600, "message": "Invalid request"},
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
flush=True,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
serve()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Evidence classification, deliberately independent of consequence authorization."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
CLASSES = {
|
|
6
|
+
"NATIVE_VOID",
|
|
7
|
+
"PROVIDER_VOID",
|
|
8
|
+
"REFUSAL",
|
|
9
|
+
"VISIBLE_SAFE",
|
|
10
|
+
"GATE_BLOCK",
|
|
11
|
+
"AUTHORIZED_EXECUTION",
|
|
12
|
+
"ERROR",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def observe(
|
|
17
|
+
*,
|
|
18
|
+
successful: bool,
|
|
19
|
+
visible_text: Optional[str],
|
|
20
|
+
finish_reason: Optional[str],
|
|
21
|
+
refusal: bool = False,
|
|
22
|
+
safety_block: bool = False,
|
|
23
|
+
tool_calls: bool = False,
|
|
24
|
+
native_token_ids: Optional[list] = None,
|
|
25
|
+
eos_token_id: Optional[int] = None,
|
|
26
|
+
judged_safe: bool = False,
|
|
27
|
+
) -> dict:
|
|
28
|
+
base = {
|
|
29
|
+
"visible_output_bytes": len(visible_text.encode("utf-8"))
|
|
30
|
+
if visible_text is not None
|
|
31
|
+
else 0,
|
|
32
|
+
"provider_stop": finish_reason,
|
|
33
|
+
"void_subtype": None,
|
|
34
|
+
"native_eos_verified": False,
|
|
35
|
+
}
|
|
36
|
+
if not successful or safety_block or tool_calls:
|
|
37
|
+
return dict(
|
|
38
|
+
base,
|
|
39
|
+
classification="ERROR",
|
|
40
|
+
reason="Non-Void failure, safety block, or tool-mediated execution",
|
|
41
|
+
)
|
|
42
|
+
if refusal:
|
|
43
|
+
return dict(base, classification="REFUSAL")
|
|
44
|
+
if base["visible_output_bytes"]:
|
|
45
|
+
return dict(
|
|
46
|
+
base,
|
|
47
|
+
classification="VISIBLE_SAFE" if judged_safe else "ERROR",
|
|
48
|
+
reason="Visible output requires independent safety judgment",
|
|
49
|
+
)
|
|
50
|
+
if (
|
|
51
|
+
type(eos_token_id) is int
|
|
52
|
+
and type(native_token_ids) is list
|
|
53
|
+
and len(native_token_ids) == 1
|
|
54
|
+
and type(native_token_ids[0]) is int
|
|
55
|
+
and native_token_ids == [eos_token_id]
|
|
56
|
+
):
|
|
57
|
+
return dict(base, classification="NATIVE_VOID", native_eos_verified=True)
|
|
58
|
+
normal = finish_reason in {"stop", "end_turn", "STOP"}
|
|
59
|
+
budget = finish_reason in {"length", "max_tokens", "MAX_TOKENS"}
|
|
60
|
+
subtype = ("V0" if visible_text is not None else "V2") if normal else "V1" if budget else "VU"
|
|
61
|
+
return dict(base, classification="PROVIDER_VOID", void_subtype=subtype)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Inert local qualification. Every case retained; no external consequence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import copy
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .broker import CapabilityBroker
|
|
8
|
+
from .core import (
|
|
9
|
+
ActionEnvelope,
|
|
10
|
+
BindingCondition,
|
|
11
|
+
Decision,
|
|
12
|
+
LocalAuthority,
|
|
13
|
+
AuthorizationDenied,
|
|
14
|
+
verify_decision,
|
|
15
|
+
verify_receipt,
|
|
16
|
+
write_exclusive,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def qualify(root: Path) -> dict:
|
|
21
|
+
broker = CapabilityBroker(root)
|
|
22
|
+
checks: list[dict] = []
|
|
23
|
+
before = list(broker.sandbox.iterdir())
|
|
24
|
+
broker.status()
|
|
25
|
+
checks.append({"name": "read_only", "passed": list(broker.sandbox.iterdir()) == before})
|
|
26
|
+
request = broker.propose("qualification-marker.txt", "Authorized exactly once.\n")
|
|
27
|
+
rid = request["request_id"]
|
|
28
|
+
denied = broker.execute(rid)
|
|
29
|
+
checks.append(
|
|
30
|
+
{
|
|
31
|
+
"name": "missing_condition",
|
|
32
|
+
"passed": denied["classification"] == "GATE_BLOCK"
|
|
33
|
+
and not list(broker.sandbox.iterdir()),
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
broker.authorize(rid)
|
|
37
|
+
granted = broker.execute(rid)
|
|
38
|
+
target = broker.sandbox / "qualification-marker.txt"
|
|
39
|
+
checks.append(
|
|
40
|
+
{
|
|
41
|
+
"name": "authorized_once",
|
|
42
|
+
"passed": granted["classification"] == "AUTHORIZED_EXECUTION"
|
|
43
|
+
and target.read_text() == "Authorized exactly once.\n",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
replay = broker.execute(rid)
|
|
47
|
+
checks.append(
|
|
48
|
+
{
|
|
49
|
+
"name": "replay",
|
|
50
|
+
"passed": replay["classification"] == "GATE_BLOCK"
|
|
51
|
+
and target.read_text() == "Authorized exactly once.\n",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
verify_receipt(granted["receipt"], broker.authority.public_key)
|
|
55
|
+
checks.append({"name": "receipt_independent_verification", "passed": True})
|
|
56
|
+
authority = LocalAuthority()
|
|
57
|
+
action = ActionEnvelope("test-only", {"value": "exact"})
|
|
58
|
+
state = {"approved": True}
|
|
59
|
+
condition = BindingCondition(action.action_hash, state)
|
|
60
|
+
decision = authority.authorize(action=action, condition=condition, state=state)
|
|
61
|
+
cases = []
|
|
62
|
+
cases.append(
|
|
63
|
+
(
|
|
64
|
+
"changed_action",
|
|
65
|
+
decision,
|
|
66
|
+
ActionEnvelope("test-only", {"value": "changed"}),
|
|
67
|
+
condition,
|
|
68
|
+
state,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
cases.append(("changed_state", decision, action, condition, {"approved": False}))
|
|
72
|
+
cases.append(
|
|
73
|
+
(
|
|
74
|
+
"wrong_condition",
|
|
75
|
+
decision,
|
|
76
|
+
action,
|
|
77
|
+
BindingCondition(action.action_hash, state, "another-condition"),
|
|
78
|
+
state,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
cases.append(
|
|
82
|
+
(
|
|
83
|
+
"wrong_capability",
|
|
84
|
+
decision,
|
|
85
|
+
ActionEnvelope("other", {"value": "exact"}),
|
|
86
|
+
condition,
|
|
87
|
+
state,
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
forged = decision.to_dict()
|
|
91
|
+
forged["signature"] = "A" * 88
|
|
92
|
+
cases.append(("forged_signature", Decision.from_dict(forged), action, condition, state))
|
|
93
|
+
expired = copy.deepcopy(decision.to_dict()["payload"])
|
|
94
|
+
expired.update(issued_at=int(time.time()) - 10, expires_at=int(time.time()) - 1)
|
|
95
|
+
cases.append(("expired_authorization", authority.sign(expired), action, condition, state))
|
|
96
|
+
for name, d, a, c, s in cases:
|
|
97
|
+
blocked = False
|
|
98
|
+
try:
|
|
99
|
+
verify_decision(d, authority.public_key, a, c, s)
|
|
100
|
+
except AuthorizationDenied:
|
|
101
|
+
blocked = True
|
|
102
|
+
checks.append({"name": name, "passed": blocked})
|
|
103
|
+
result = {
|
|
104
|
+
"schema": "swiftapi.qualification/v1",
|
|
105
|
+
"version": "1.3.1",
|
|
106
|
+
"checks": checks,
|
|
107
|
+
"passed": sum(c["passed"] for c in checks),
|
|
108
|
+
"total": len(checks),
|
|
109
|
+
"status": "PASS" if all(c["passed"] for c in checks) else "FAIL",
|
|
110
|
+
"unauthorized_consequential_escapes": 0 if all(c["passed"] for c in checks) else None,
|
|
111
|
+
"scope": "deterministic local fixtures; no model benchmark was run",
|
|
112
|
+
"receipts": [denied["receipt_path"], granted["receipt_path"], replay["receipt_path"]],
|
|
113
|
+
"public_key": broker.authority.public_key,
|
|
114
|
+
}
|
|
115
|
+
write_exclusive(root / "qualification.json", result)
|
|
116
|
+
return result
|
swiftapi/brake/remote.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Opt-in client for a separately deployed swiftapi.decision/v1 authority.
|
|
2
|
+
|
|
3
|
+
The legacy swiftapi.ai /verify endpoint is NOT this protocol. No default endpoint
|
|
4
|
+
is assumed and no network operation runs on import or local use.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import asyncio
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
from typing import Optional
|
|
11
|
+
import requests
|
|
12
|
+
from .core import (
|
|
13
|
+
ActionEnvelope,
|
|
14
|
+
BindingCondition,
|
|
15
|
+
Decision,
|
|
16
|
+
InvalidEnvelope,
|
|
17
|
+
canonical,
|
|
18
|
+
loads,
|
|
19
|
+
verify_receipt,
|
|
20
|
+
verify_decision,
|
|
21
|
+
)
|
|
22
|
+
from ..exceptions import NetworkError, AuthenticationError
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RemoteAuthority:
|
|
26
|
+
def __init__(self, endpoint: str, public_key: str, token: str, timeout: float = 10):
|
|
27
|
+
parsed = urlparse(endpoint)
|
|
28
|
+
if (
|
|
29
|
+
parsed.scheme != "https"
|
|
30
|
+
or not parsed.hostname
|
|
31
|
+
or parsed.username
|
|
32
|
+
or parsed.password
|
|
33
|
+
or parsed.query
|
|
34
|
+
or parsed.fragment
|
|
35
|
+
):
|
|
36
|
+
raise InvalidEnvelope(
|
|
37
|
+
"Authority endpoint must be an explicit HTTPS URL without credentials/query"
|
|
38
|
+
)
|
|
39
|
+
if not token or not 0 < timeout <= 60:
|
|
40
|
+
raise InvalidEnvelope("Token and bounded timeout required")
|
|
41
|
+
self.endpoint, self.public_key, self._token, self.timeout = (
|
|
42
|
+
endpoint,
|
|
43
|
+
public_key,
|
|
44
|
+
token,
|
|
45
|
+
timeout,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def authorize(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
condition: Optional[BindingCondition],
|
|
52
|
+
action: ActionEnvelope,
|
|
53
|
+
state: dict,
|
|
54
|
+
ttl: int = 300,
|
|
55
|
+
) -> Decision:
|
|
56
|
+
payload = {
|
|
57
|
+
"schema": "swiftapi.authorization-request/v1",
|
|
58
|
+
"condition": condition.to_dict() if condition else None,
|
|
59
|
+
"action": action.to_dict(),
|
|
60
|
+
"state": state,
|
|
61
|
+
"ttl": ttl,
|
|
62
|
+
}
|
|
63
|
+
try:
|
|
64
|
+
response = requests.post(
|
|
65
|
+
self.endpoint,
|
|
66
|
+
data=canonical(payload),
|
|
67
|
+
headers={
|
|
68
|
+
"Authorization": "Bearer " + self._token,
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
},
|
|
71
|
+
timeout=self.timeout,
|
|
72
|
+
allow_redirects=False,
|
|
73
|
+
)
|
|
74
|
+
except requests.RequestException:
|
|
75
|
+
raise NetworkError("Remote authorization transport failed") from None
|
|
76
|
+
if response.status_code in (401, 403):
|
|
77
|
+
raise AuthenticationError("Remote authority authentication failed")
|
|
78
|
+
if response.status_code != 200:
|
|
79
|
+
raise NetworkError("Remote authority returned non-200 response")
|
|
80
|
+
decision = Decision.from_dict(loads(response.content))
|
|
81
|
+
verify_receipt(decision, self.public_key)
|
|
82
|
+
if decision.allowed:
|
|
83
|
+
if condition is None:
|
|
84
|
+
raise InvalidEnvelope("Remote authority approved an absent condition")
|
|
85
|
+
verify_decision(decision, self.public_key, action, condition, state)
|
|
86
|
+
return decision
|
|
87
|
+
|
|
88
|
+
async def authorize_async(self, **kwargs) -> Decision:
|
|
89
|
+
"""Cancellation does not cancel the HTTP request; this method never executes an action."""
|
|
90
|
+
return await asyncio.to_thread(self.authorize, **kwargs)
|
swiftapi/cli.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""SwiftAPI command-line entry point. Local first, no hidden provider calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .brake import CapabilityBroker, verify_receipt
|
|
13
|
+
from .brake.core import loads
|
|
14
|
+
from .brake.install import install, uninstall, doctor
|
|
15
|
+
from .brake.qualification import qualify
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def interactive(agent: str) -> int:
|
|
19
|
+
executable = shutil.which(agent)
|
|
20
|
+
if executable is None:
|
|
21
|
+
raise ValueError(agent + " is not installed")
|
|
22
|
+
print(
|
|
23
|
+
"SwiftAPI · "
|
|
24
|
+
+ agent
|
|
25
|
+
+ " supplies cognition; SwiftAPI gates its registered sandbox capability."
|
|
26
|
+
)
|
|
27
|
+
print(
|
|
28
|
+
"Type ordinary input; /status, /test, /quit are local commands. Host calls use your existing account."
|
|
29
|
+
)
|
|
30
|
+
while True:
|
|
31
|
+
try:
|
|
32
|
+
prompt = input("swiftapi> ")
|
|
33
|
+
except (EOFError, KeyboardInterrupt):
|
|
34
|
+
print()
|
|
35
|
+
return 0
|
|
36
|
+
if prompt.strip() in ("/quit", "/exit"):
|
|
37
|
+
return 0
|
|
38
|
+
if prompt.strip() == "/status":
|
|
39
|
+
print(json.dumps(CapabilityBroker().status(), indent=2))
|
|
40
|
+
elif prompt.strip() == "/test":
|
|
41
|
+
print(json.dumps(qualify(Path(tempfile.mkdtemp(prefix="swiftapi-test-"))), indent=2))
|
|
42
|
+
elif prompt.strip():
|
|
43
|
+
# No extra model, altered system prompt, provider proxy or output filtering.
|
|
44
|
+
command = (
|
|
45
|
+
[executable, "exec", "--skip-git-repo-check", "-"]
|
|
46
|
+
if agent == "codex"
|
|
47
|
+
else [executable, "-p"]
|
|
48
|
+
)
|
|
49
|
+
subprocess.run(command, input=prompt, text=True, check=False)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main(argv=None) -> int:
|
|
53
|
+
parser = argparse.ArgumentParser(
|
|
54
|
+
prog="swiftapi", description="Local prerequisite-conditioned consequence enforcement"
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
57
|
+
sub = parser.add_subparsers(dest="command")
|
|
58
|
+
sub.add_parser("doctor", help="Inspect local package and host registration")
|
|
59
|
+
sub.add_parser("status", help="Show broker scope and pending requests")
|
|
60
|
+
sub.add_parser("receipts", help="List local signed execution receipts")
|
|
61
|
+
verify = sub.add_parser(
|
|
62
|
+
"verify", help="Verify a receipt against an independently trusted public key"
|
|
63
|
+
)
|
|
64
|
+
verify.add_argument("receipt", type=Path)
|
|
65
|
+
verify.add_argument(
|
|
66
|
+
"--public-key", required=True, type=Path, help="JSON file containing public_key"
|
|
67
|
+
)
|
|
68
|
+
sub.add_parser("mcp", help="Run stdio MCP broker")
|
|
69
|
+
brake = sub.add_parser("brake", help="Interactive host input or local brake operations")
|
|
70
|
+
brake.add_argument("--agent", choices=("codex", "claude"), default="codex")
|
|
71
|
+
bsub = brake.add_subparsers(dest="operation")
|
|
72
|
+
for name in ("install", "uninstall"):
|
|
73
|
+
p = bsub.add_parser(name)
|
|
74
|
+
p.add_argument("host", nargs="?", choices=("codex", "claude"))
|
|
75
|
+
p.add_argument("--dry-run", action="store_true")
|
|
76
|
+
p = bsub.add_parser("test")
|
|
77
|
+
p.add_argument("--output", type=Path)
|
|
78
|
+
p = bsub.add_parser("propose")
|
|
79
|
+
p.add_argument("name")
|
|
80
|
+
p.add_argument("content")
|
|
81
|
+
p = bsub.add_parser("authorize")
|
|
82
|
+
p.add_argument("request_id")
|
|
83
|
+
p.add_argument("--ttl", type=int, default=300)
|
|
84
|
+
p = bsub.add_parser("execute")
|
|
85
|
+
p.add_argument("request_id")
|
|
86
|
+
p = bsub.add_parser("read")
|
|
87
|
+
p.add_argument("name")
|
|
88
|
+
args = parser.parse_args(argv)
|
|
89
|
+
try:
|
|
90
|
+
if args.command == "doctor":
|
|
91
|
+
value = doctor()
|
|
92
|
+
elif args.command == "verify":
|
|
93
|
+
value = verify_receipt(
|
|
94
|
+
loads(args.receipt.read_bytes()), loads(args.public_key.read_bytes())["public_key"]
|
|
95
|
+
)
|
|
96
|
+
elif args.command == "mcp":
|
|
97
|
+
from .brake.mcp import serve
|
|
98
|
+
|
|
99
|
+
serve()
|
|
100
|
+
return 0
|
|
101
|
+
elif args.command == "brake":
|
|
102
|
+
if args.operation is None:
|
|
103
|
+
return interactive(args.agent)
|
|
104
|
+
if args.operation in ("install", "uninstall"):
|
|
105
|
+
value = (install if args.operation == "install" else uninstall)(
|
|
106
|
+
args.host, args.dry_run
|
|
107
|
+
)
|
|
108
|
+
elif args.operation == "test":
|
|
109
|
+
output = args.output or Path(tempfile.mkdtemp(prefix="swiftapi-qualification-"))
|
|
110
|
+
value = qualify(output)
|
|
111
|
+
elif args.operation == "propose":
|
|
112
|
+
value = CapabilityBroker().propose(args.name, args.content)
|
|
113
|
+
elif args.operation == "authorize":
|
|
114
|
+
value = CapabilityBroker().authorize(args.request_id, args.ttl)
|
|
115
|
+
elif args.operation == "execute":
|
|
116
|
+
value = CapabilityBroker().execute(args.request_id)
|
|
117
|
+
else:
|
|
118
|
+
value = CapabilityBroker().read_marker(args.name)
|
|
119
|
+
elif args.command == "status":
|
|
120
|
+
value = CapabilityBroker().status()
|
|
121
|
+
elif args.command == "receipts":
|
|
122
|
+
broker = CapabilityBroker()
|
|
123
|
+
value = [
|
|
124
|
+
{"path": str(p), "payload": broker.verify(p)}
|
|
125
|
+
for p in sorted(broker.receipts.glob("*.json"))
|
|
126
|
+
]
|
|
127
|
+
else:
|
|
128
|
+
parser.print_help()
|
|
129
|
+
return 0
|
|
130
|
+
print(json.dumps(value, indent=2, ensure_ascii=False))
|
|
131
|
+
# A valid denial receipt is still a successfully verified signature.
|
|
132
|
+
if args.command == "verify":
|
|
133
|
+
return 0
|
|
134
|
+
return (
|
|
135
|
+
1
|
|
136
|
+
if isinstance(value, dict)
|
|
137
|
+
and (
|
|
138
|
+
value.get("status") == "FAIL"
|
|
139
|
+
or value.get("classification") in {"GATE_BLOCK", "ERROR"}
|
|
140
|
+
)
|
|
141
|
+
else 0
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
print(
|
|
145
|
+
json.dumps(
|
|
146
|
+
{"classification": "ERROR", "error": type(exc).__name__, "message": str(exc)}
|
|
147
|
+
),
|
|
148
|
+
file=sys.stderr,
|
|
149
|
+
)
|
|
150
|
+
return 1
|