opentrust-sdk 1.0.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.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: opentrust-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK and MCP bridge for the OpenTrust tool trust registry
5
+ Author-email: Novel Hut Studios <founder@novelhut.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Costder/opentrust
8
+ Project-URL: Repository, https://github.com/Costder/opentrust
9
+ Project-URL: Issues, https://github.com/Costder/opentrust/issues
10
+ Keywords: opentrust,ai-agents,mcp,trust-registry,tool-passports
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: httpx>=0.28
14
+ Provides-Extra: mcp
15
+ Requires-Dist: mcp>=1.0; extra == "mcp"
16
+
17
+ # opentrust-sdk
18
+
19
+ Python SDK for OpenTrust, the trust registry and passport layer for AI-agent tools.
20
+
21
+ Use it to verify tools, fetch passports, search the registry, and expose an MCP bridge for agent runtimes.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install opentrust-sdk
27
+ ```
28
+
29
+ For MCP support:
30
+
31
+ ```bash
32
+ pip install 'opentrust-sdk[mcp]'
33
+ ```
34
+
35
+ ## Basic usage
36
+
37
+ ```python
38
+ import asyncio
39
+ from opentrust import verify
40
+
41
+ async def main():
42
+ result = await verify('github/file-search-mcp')
43
+ print(result)
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ ## MCP bridge
49
+
50
+ ```bash
51
+ opentrust-mcp
52
+ ```
53
+
54
+ ## Repository
55
+
56
+ https://github.com/Costder/opentrust
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,44 @@
1
+ # opentrust-sdk
2
+
3
+ Python SDK for OpenTrust, the trust registry and passport layer for AI-agent tools.
4
+
5
+ Use it to verify tools, fetch passports, search the registry, and expose an MCP bridge for agent runtimes.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install opentrust-sdk
11
+ ```
12
+
13
+ For MCP support:
14
+
15
+ ```bash
16
+ pip install 'opentrust-sdk[mcp]'
17
+ ```
18
+
19
+ ## Basic usage
20
+
21
+ ```python
22
+ import asyncio
23
+ from opentrust import verify
24
+
25
+ async def main():
26
+ result = await verify('github/file-search-mcp')
27
+ print(result)
28
+
29
+ asyncio.run(main())
30
+ ```
31
+
32
+ ## MCP bridge
33
+
34
+ ```bash
35
+ opentrust-mcp
36
+ ```
37
+
38
+ ## Repository
39
+
40
+ https://github.com/Costder/opentrust
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=82", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "opentrust-sdk"
7
+ version = "1.0.0"
8
+ description = "Python SDK and MCP bridge for the OpenTrust tool trust registry"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Novel Hut Studios", email = "founder@novelhut.net" }]
13
+ keywords = ["opentrust", "ai-agents", "mcp", "trust-registry", "tool-passports"]
14
+ dependencies = ["httpx>=0.28"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/Costder/opentrust"
18
+ Repository = "https://github.com/Costder/opentrust"
19
+ Issues = "https://github.com/Costder/opentrust/issues"
20
+
21
+ [project.optional-dependencies]
22
+ mcp = ["mcp>=1.0"]
23
+
24
+ [project.scripts]
25
+ opentrust-mcp = "opentrust.mcp:main"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,85 @@
1
+ """opentrust — Python SDK for the OpenTrust tool trust registry."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+
6
+ from ._client import _Client
7
+ from ._recommend import TRUST_LEVELS, recommend, risk_level
8
+ from ._types import ToolsPage, VerifyResult
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = [
12
+ "verify", "get", "search", "list",
13
+ "verify_sync", "get_sync",
14
+ "VerifyResult", "ToolsPage",
15
+ ]
16
+
17
+
18
+ def _build_result(passport: dict) -> VerifyResult:
19
+ status = passport.get("trust_status", "auto_generated_draft")
20
+ level = TRUST_LEVELS.get(status, 1)
21
+ perms = passport.get("permission_manifest") or {}
22
+ return VerifyResult(
23
+ slug=passport["slug"],
24
+ trust_status=status,
25
+ trust_level=level,
26
+ is_disputed=(status == "disputed"),
27
+ recommendation=recommend(status, perms),
28
+ risk=risk_level(status, perms),
29
+ passport=passport,
30
+ permissions=perms,
31
+ )
32
+
33
+
34
+ async def verify(slug: str, *, api_url: str | None = None) -> VerifyResult:
35
+ """Fetch a passport and return a VerifyResult with recommendation and risk level."""
36
+ passport = await get(slug, api_url=api_url)
37
+ return _build_result(passport)
38
+
39
+
40
+ async def get(slug: str, *, api_url: str | None = None) -> dict:
41
+ """Fetch the raw passport dict for a slug."""
42
+ client = _Client(base_url=api_url)
43
+ return await client.get(f"/tools/{slug}")
44
+
45
+
46
+ async def search(
47
+ query: str,
48
+ *,
49
+ trust_status: str | None = None,
50
+ api_url: str | None = None,
51
+ ) -> list[dict]:
52
+ """Search tools by query. Returns list of raw passport dicts."""
53
+ client = _Client(base_url=api_url)
54
+ page = await client.get("/tools", q=query, trust_status=trust_status)
55
+ return page.get("items", [])
56
+
57
+
58
+ async def list( # noqa: A001
59
+ *,
60
+ page: int = 1,
61
+ limit: int = 20,
62
+ trust_status: str | None = None,
63
+ api_url: str | None = None,
64
+ ) -> ToolsPage:
65
+ """List tools with optional filters. Returns a ToolsPage."""
66
+ client = _Client(base_url=api_url)
67
+ data = await client.get(
68
+ "/tools", page=page, limit=limit, trust_status=trust_status
69
+ )
70
+ return ToolsPage(
71
+ items=data.get("items", []),
72
+ total=data.get("total", 0),
73
+ page=data.get("page", page),
74
+ limit=data.get("limit", limit),
75
+ )
76
+
77
+
78
+ def verify_sync(slug: str, *, api_url: str | None = None) -> VerifyResult:
79
+ """Synchronous wrapper for verify(). Do not call from an async context."""
80
+ return asyncio.run(verify(slug, api_url=api_url))
81
+
82
+
83
+ def get_sync(slug: str, *, api_url: str | None = None) -> dict:
84
+ """Synchronous wrapper for get(). Do not call from an async context."""
85
+ return asyncio.run(get(slug, api_url=api_url))
@@ -0,0 +1,31 @@
1
+ """Internal async HTTP client wrapping httpx."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ _DEFAULT_URL = "https://api-kappa-pied-59.vercel.app"
10
+
11
+
12
+ class _Client:
13
+ def __init__(
14
+ self,
15
+ base_url: str | None = None,
16
+ transport: httpx.AsyncBaseTransport | None = None,
17
+ ) -> None:
18
+ self._base = (
19
+ base_url or os.getenv("OPENTRUST_API_URL") or _DEFAULT_URL
20
+ ).rstrip("/")
21
+ self._transport = transport
22
+
23
+ async def get(self, path: str, **params: Any) -> Any:
24
+ """GET /api/v1{path} with optional query params (None values are dropped)."""
25
+ filtered = {k: v for k, v in params.items() if v is not None}
26
+ async with httpx.AsyncClient(
27
+ base_url=self._base, transport=self._transport
28
+ ) as client:
29
+ resp = await client.get(f"/api/v1{path}", params=filtered)
30
+ resp.raise_for_status()
31
+ return resp.json()
@@ -0,0 +1,60 @@
1
+ """Recommendation table and risk logic for OpenTrust trust statuses."""
2
+
3
+ TRUST_LEVELS: dict[str, int] = {
4
+ "auto_generated_draft": 1,
5
+ "creator_claimed": 2,
6
+ "seller_confirmed": 3,
7
+ "community_reviewed": 4,
8
+ "reviewer_signed": 5,
9
+ "security_checked": 6,
10
+ "continuously_monitored": 7,
11
+ "disputed": 0,
12
+ }
13
+
14
+ _RECOMMENDATIONS: dict[int, str] = {
15
+ 0: "⛔ Under active dispute. Do not use until resolved.",
16
+ 1: "Auto-generated draft. Do not use in any agent workflow.",
17
+ 2: "Creator claimed. Verify source independently before use.",
18
+ 3: "Seller confirmed. Suitable for sandboxed/test environments only.",
19
+ 4: "Community reviewed. Safe for low-risk tasks. Require level 6+ for production.",
20
+ 5: "Reviewer signed. Suitable for most production tasks without sensitive permissions.",
21
+ 6: "Security checked. Safe for production including sensitive permissions.",
22
+ 7: "Continuously monitored. Highest trust level available.",
23
+ }
24
+
25
+
26
+ def _perm_active(val: object) -> bool:
27
+ """Return True if a permission value represents an active/granted permission."""
28
+ if val is True:
29
+ return True
30
+ if val and isinstance(val, dict):
31
+ return any(
32
+ v is True or (isinstance(v, list) and len(v) > 0)
33
+ for v in val.values()
34
+ )
35
+ return False
36
+
37
+
38
+ def recommend(trust_status: str, permission_manifest: dict) -> str:
39
+ """Return a plain-English recommendation for a trust status + permission manifest."""
40
+ level = TRUST_LEVELS.get(trust_status, 1)
41
+ text = _RECOMMENDATIONS.get(level, _RECOMMENDATIONS[1])
42
+ if _perm_active(permission_manifest.get("wallet")):
43
+ text += " ⚠ Wallet access active — verify payment amounts before use."
44
+ if _perm_active(permission_manifest.get("terminal")):
45
+ text += " ⚠ Terminal access active — review allowed commands carefully."
46
+ return text
47
+
48
+
49
+ def risk_level(trust_status: str, permission_manifest: dict) -> str:
50
+ """Return 'low', 'medium', or 'high' risk for a trust status + permission manifest."""
51
+ if trust_status == "disputed":
52
+ return "high"
53
+ level = TRUST_LEVELS.get(trust_status, 1)
54
+ dangerous = {"wallet", "terminal", "private_data", "browser"}
55
+ n = sum(1 for k in dangerous if _perm_active(permission_manifest.get(k)))
56
+ if level <= 2 or n >= 2:
57
+ return "high"
58
+ if n == 1 or level <= 4:
59
+ return "medium"
60
+ return "low"
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class VerifyResult:
6
+ slug: str
7
+ trust_status: str # e.g. "community_reviewed"
8
+ trust_level: int # 1–7; 0 if trust_status == "disputed"
9
+ is_disputed: bool # True when trust_status == "disputed"
10
+ recommendation: str # plain-English guidance
11
+ risk: str # "low" | "medium" | "high"
12
+ passport: dict # full raw passport
13
+ permissions: dict # permission_manifest
14
+
15
+
16
+ @dataclass
17
+ class ToolsPage:
18
+ items: list[dict]
19
+ total: int
20
+ page: int
21
+ limit: int
@@ -0,0 +1,69 @@
1
+ """OpenTrust MCP server — exposes trust registry as MCP tools via stdio."""
2
+ from __future__ import annotations
3
+
4
+ try:
5
+ from mcp.server.fastmcp import FastMCP
6
+ except ImportError as exc:
7
+ raise ImportError(
8
+ "MCP server requires the mcp extra: pip install opentrust-sdk[mcp]"
9
+ ) from exc
10
+
11
+ import opentrust
12
+
13
+ mcp_server = FastMCP(
14
+ "OpenTrust",
15
+ instructions=(
16
+ "Query the OpenTrust tool trust registry. "
17
+ "Use verify_tool before calling any external tool to check its "
18
+ "trust status and permissions."
19
+ ),
20
+ )
21
+
22
+
23
+ @mcp_server.tool()
24
+ async def verify_tool(slug: str) -> dict:
25
+ """Look up a tool's trust passport and get a plain-English safety recommendation."""
26
+ result = await opentrust.verify(slug)
27
+ return {
28
+ "passport": result.passport,
29
+ "trust_status": result.trust_status,
30
+ "trust_level": result.trust_level,
31
+ "is_disputed": result.is_disputed,
32
+ "recommendation": result.recommendation,
33
+ "risk": result.risk,
34
+ "permissions": result.permissions,
35
+ }
36
+
37
+
38
+ @mcp_server.tool()
39
+ async def search_tools(query: str, trust_status: str = "") -> list:
40
+ """Search the OpenTrust registry for tools matching a query."""
41
+ tools = await opentrust.search(query, trust_status=trust_status or None)
42
+ return [
43
+ {
44
+ "slug": t.get("slug"),
45
+ "name": t.get("name"),
46
+ "trust_status": t.get("trust_status"),
47
+ "description": t.get("description", ""),
48
+ }
49
+ for t in tools
50
+ ]
51
+
52
+
53
+ @mcp_server.tool()
54
+ async def list_tools(
55
+ page: int = 1, limit: int = 20, trust_status: str = ""
56
+ ) -> dict:
57
+ """List registered tools, optionally filtered by trust level."""
58
+ page_result = await opentrust.list(
59
+ page=page, limit=limit, trust_status=trust_status or None
60
+ )
61
+ return {"items": page_result.items, "total": page_result.total}
62
+
63
+
64
+ def main() -> None:
65
+ mcp_server.run()
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: opentrust-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK and MCP bridge for the OpenTrust tool trust registry
5
+ Author-email: Novel Hut Studios <founder@novelhut.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Costder/opentrust
8
+ Project-URL: Repository, https://github.com/Costder/opentrust
9
+ Project-URL: Issues, https://github.com/Costder/opentrust/issues
10
+ Keywords: opentrust,ai-agents,mcp,trust-registry,tool-passports
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: httpx>=0.28
14
+ Provides-Extra: mcp
15
+ Requires-Dist: mcp>=1.0; extra == "mcp"
16
+
17
+ # opentrust-sdk
18
+
19
+ Python SDK for OpenTrust, the trust registry and passport layer for AI-agent tools.
20
+
21
+ Use it to verify tools, fetch passports, search the registry, and expose an MCP bridge for agent runtimes.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install opentrust-sdk
27
+ ```
28
+
29
+ For MCP support:
30
+
31
+ ```bash
32
+ pip install 'opentrust-sdk[mcp]'
33
+ ```
34
+
35
+ ## Basic usage
36
+
37
+ ```python
38
+ import asyncio
39
+ from opentrust import verify
40
+
41
+ async def main():
42
+ result = await verify('github/file-search-mcp')
43
+ print(result)
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ ## MCP bridge
49
+
50
+ ```bash
51
+ opentrust-mcp
52
+ ```
53
+
54
+ ## Repository
55
+
56
+ https://github.com/Costder/opentrust
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,17 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/opentrust/__init__.py
4
+ src/opentrust/_client.py
5
+ src/opentrust/_recommend.py
6
+ src/opentrust/_types.py
7
+ src/opentrust/mcp.py
8
+ src/opentrust_sdk.egg-info/PKG-INFO
9
+ src/opentrust_sdk.egg-info/SOURCES.txt
10
+ src/opentrust_sdk.egg-info/dependency_links.txt
11
+ src/opentrust_sdk.egg-info/entry_points.txt
12
+ src/opentrust_sdk.egg-info/requires.txt
13
+ src/opentrust_sdk.egg-info/top_level.txt
14
+ tests/test_client.py
15
+ tests/test_mcp.py
16
+ tests/test_recommend.py
17
+ tests/test_sdk.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ opentrust-mcp = opentrust.mcp:main
@@ -0,0 +1,4 @@
1
+ httpx>=0.28
2
+
3
+ [mcp]
4
+ mcp>=1.0
@@ -0,0 +1,52 @@
1
+ import pytest
2
+ import httpx
3
+ from opentrust._client import _Client
4
+
5
+
6
+ def _transport(data: dict, status: int = 200) -> httpx.MockTransport:
7
+ def handler(request: httpx.Request) -> httpx.Response:
8
+ return httpx.Response(status, json=data)
9
+ return httpx.MockTransport(handler)
10
+
11
+
12
+ @pytest.mark.asyncio
13
+ async def test_get_returns_json():
14
+ client = _Client(base_url="http://test", transport=_transport({"slug": "t"}))
15
+ result = await client.get("/tools/t")
16
+ assert result["slug"] == "t"
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_get_raises_on_404():
21
+ client = _Client(base_url="http://test", transport=_transport({"detail": "not found"}, 404))
22
+ with pytest.raises(httpx.HTTPStatusError):
23
+ await client.get("/tools/missing")
24
+
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_get_strips_none_params():
28
+ seen: list[str] = []
29
+
30
+ def handler(request: httpx.Request) -> httpx.Response:
31
+ seen.append(str(request.url))
32
+ return httpx.Response(200, json={"items": [], "total": 0, "page": 1, "limit": 20})
33
+
34
+ client = _Client(base_url="http://test", transport=httpx.MockTransport(handler))
35
+ await client.get("/tools", q=None, trust_status=None, page=1)
36
+ assert "q=" not in seen[0]
37
+ assert "trust_status=" not in seen[0]
38
+ assert "page=1" in seen[0]
39
+
40
+
41
+ @pytest.mark.asyncio
42
+ async def test_get_includes_non_none_params():
43
+ seen: list[str] = []
44
+
45
+ def handler(request: httpx.Request) -> httpx.Response:
46
+ seen.append(str(request.url))
47
+ return httpx.Response(200, json={"items": [], "total": 0, "page": 1, "limit": 20})
48
+
49
+ client = _Client(base_url="http://test", transport=httpx.MockTransport(handler))
50
+ await client.get("/tools", q="github", trust_status="community_reviewed")
51
+ assert "q=github" in seen[0]
52
+ assert "trust_status=community_reviewed" in seen[0]
@@ -0,0 +1,85 @@
1
+ """Tests for the MCP server tools. Calls tool functions directly — no MCP transport."""
2
+ import pytest
3
+ from unittest.mock import AsyncMock, patch
4
+
5
+ try:
6
+ from opentrust.mcp import verify_tool, search_tools, list_tools
7
+ MCP_AVAILABLE = True
8
+ except ImportError:
9
+ MCP_AVAILABLE = False
10
+
11
+ from opentrust._types import ToolsPage, VerifyResult
12
+
13
+ pytestmark = pytest.mark.skipif(not MCP_AVAILABLE, reason="mcp extra not installed")
14
+
15
+ FAKE_RESULT = VerifyResult(
16
+ slug="test-tool",
17
+ trust_status="community_reviewed",
18
+ trust_level=4,
19
+ is_disputed=False,
20
+ recommendation="Community reviewed. Safe for low-risk tasks.",
21
+ risk="medium",
22
+ passport={"slug": "test-tool", "name": "Test"},
23
+ permissions={"network": True},
24
+ )
25
+
26
+
27
+ @pytest.mark.asyncio
28
+ async def test_verify_tool_returns_all_expected_keys():
29
+ with patch("opentrust.mcp.opentrust.verify", new_callable=AsyncMock) as m:
30
+ m.return_value = FAKE_RESULT
31
+ result = await verify_tool("test-tool")
32
+ assert result["trust_status"] == "community_reviewed"
33
+ assert result["trust_level"] == 4
34
+ assert result["is_disputed"] is False
35
+ assert "recommendation" in result
36
+ assert "permissions" in result
37
+ assert "passport" in result
38
+ assert result["risk"] == "medium"
39
+
40
+
41
+ @pytest.mark.asyncio
42
+ async def test_search_tools_returns_list_of_dicts():
43
+ with patch("opentrust.mcp.opentrust.search", new_callable=AsyncMock) as m:
44
+ m.return_value = [
45
+ {"slug": "t", "name": "T", "trust_status": "community_reviewed", "description": "x"}
46
+ ]
47
+ result = await search_tools("test")
48
+ assert isinstance(result, list)
49
+ assert result[0]["slug"] == "t"
50
+
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_search_tools_converts_empty_trust_status_to_none():
54
+ with patch("opentrust.mcp.opentrust.search", new_callable=AsyncMock) as m:
55
+ m.return_value = []
56
+ await search_tools("test", trust_status="")
57
+ m.assert_called_once_with("test", trust_status=None)
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_search_tools_passes_trust_status_when_given():
62
+ with patch("opentrust.mcp.opentrust.search", new_callable=AsyncMock) as m:
63
+ m.return_value = []
64
+ await search_tools("test", trust_status="security_checked")
65
+ m.assert_called_once_with("test", trust_status="security_checked")
66
+
67
+
68
+ @pytest.mark.asyncio
69
+ async def test_list_tools_returns_dict_with_items_and_total():
70
+ with patch("opentrust.mcp.opentrust.list", new_callable=AsyncMock) as m:
71
+ m.return_value = ToolsPage(
72
+ items=[{"slug": "a", "name": "A"}], total=1, page=1, limit=20
73
+ )
74
+ result = await list_tools(page=1, limit=20)
75
+ assert result["total"] == 1
76
+ assert isinstance(result["items"], list)
77
+
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_list_tools_converts_empty_trust_status_to_none():
81
+ with patch("opentrust.mcp.opentrust.list", new_callable=AsyncMock) as m:
82
+ m.return_value = ToolsPage(items=[], total=0, page=1, limit=20)
83
+ await list_tools(trust_status="")
84
+ _, kwargs = m.call_args
85
+ assert kwargs["trust_status"] is None
@@ -0,0 +1,76 @@
1
+ import pytest
2
+ from opentrust._recommend import recommend, risk_level, TRUST_LEVELS
3
+
4
+
5
+ def test_trust_levels_maps_all_statuses():
6
+ assert TRUST_LEVELS["auto_generated_draft"] == 1
7
+ assert TRUST_LEVELS["creator_claimed"] == 2
8
+ assert TRUST_LEVELS["seller_confirmed"] == 3
9
+ assert TRUST_LEVELS["community_reviewed"] == 4
10
+ assert TRUST_LEVELS["reviewer_signed"] == 5
11
+ assert TRUST_LEVELS["security_checked"] == 6
12
+ assert TRUST_LEVELS["continuously_monitored"] == 7
13
+ assert TRUST_LEVELS["disputed"] == 0
14
+
15
+
16
+ def test_recommend_draft_says_do_not_use():
17
+ r = recommend("auto_generated_draft", {})
18
+ assert "Do not use" in r
19
+
20
+
21
+ def test_recommend_disputed_mentions_dispute():
22
+ r = recommend("disputed", {})
23
+ assert "dispute" in r.lower()
24
+
25
+
26
+ def test_recommend_wallet_true_appends_warning():
27
+ r = recommend("security_checked", {"wallet": True})
28
+ assert "Wallet access active" in r
29
+
30
+
31
+ def test_recommend_terminal_true_appends_warning():
32
+ r = recommend("continuously_monitored", {"terminal": True})
33
+ assert "Terminal access active" in r
34
+
35
+
36
+ def test_recommend_no_warning_when_perms_false():
37
+ r = recommend("security_checked", {"wallet": False, "terminal": False})
38
+ assert "⚠" not in r
39
+
40
+
41
+ def test_recommend_granular_wallet_object_appends_warning():
42
+ r = recommend("security_checked", {"wallet": {"send": True}})
43
+ assert "Wallet access active" in r
44
+
45
+
46
+ def test_recommend_granular_empty_list_no_warning():
47
+ r = recommend("security_checked", {"wallet": {"read": []}})
48
+ assert "⚠" not in r
49
+
50
+
51
+ def test_risk_disputed_is_high():
52
+ assert risk_level("disputed", {}) == "high"
53
+
54
+
55
+ def test_risk_draft_is_high():
56
+ assert risk_level("auto_generated_draft", {}) == "high"
57
+
58
+
59
+ def test_risk_creator_claimed_is_high():
60
+ assert risk_level("creator_claimed", {}) == "high"
61
+
62
+
63
+ def test_risk_monitored_no_perms_is_low():
64
+ assert risk_level("continuously_monitored", {}) == "low"
65
+
66
+
67
+ def test_risk_security_checked_with_wallet_is_medium():
68
+ assert risk_level("security_checked", {"wallet": True}) == "medium"
69
+
70
+
71
+ def test_risk_two_dangerous_perms_is_high():
72
+ assert risk_level("security_checked", {"wallet": True, "terminal": True}) == "high"
73
+
74
+
75
+ def test_risk_community_reviewed_no_perms_is_medium():
76
+ assert risk_level("community_reviewed", {}) == "medium"
@@ -0,0 +1,90 @@
1
+ import pytest
2
+ from unittest.mock import AsyncMock, patch
3
+ from opentrust import verify, get, search, list as list_tools, verify_sync, VerifyResult, ToolsPage
4
+
5
+ FAKE_PASSPORT = {
6
+ "id": "abc123",
7
+ "slug": "github-file-search",
8
+ "name": "GitHub File Search",
9
+ "description": "Search repos",
10
+ "trust_status": "community_reviewed",
11
+ "tool_identity": {"slug": "github-file-search", "name": "GitHub File Search"},
12
+ "version_hash": {"version": "1.0.0"},
13
+ "capabilities": ["search"],
14
+ "permission_manifest": {"network": True, "file": False, "terminal": False, "wallet": False},
15
+ "commercial_status": {"status": "free"},
16
+ "agent_access": {"allowed": True},
17
+ }
18
+
19
+ FAKE_PAGE = {"items": [FAKE_PASSPORT], "total": 1, "page": 1, "limit": 20}
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_verify_returns_verify_result():
24
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
25
+ m.return_value = FAKE_PASSPORT
26
+ result = await verify("github-file-search")
27
+ assert isinstance(result, VerifyResult)
28
+ assert result.slug == "github-file-search"
29
+ assert result.trust_status == "community_reviewed"
30
+ assert result.trust_level == 4
31
+ assert result.is_disputed is False
32
+ assert "Community reviewed" in result.recommendation
33
+ assert result.risk == "medium"
34
+
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_verify_disputed_sets_is_disputed_and_level_zero():
38
+ disputed = {**FAKE_PASSPORT, "trust_status": "disputed"}
39
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
40
+ m.return_value = disputed
41
+ result = await verify("github-file-search")
42
+ assert result.is_disputed is True
43
+ assert result.trust_level == 0
44
+ assert result.risk == "high"
45
+ assert "dispute" in result.recommendation.lower()
46
+
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_get_returns_raw_passport_dict():
50
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
51
+ m.return_value = FAKE_PASSPORT
52
+ result = await get("github-file-search")
53
+ assert result["slug"] == "github-file-search"
54
+
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_search_returns_list_of_dicts():
58
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
59
+ m.return_value = FAKE_PAGE
60
+ result = await search("github")
61
+ assert isinstance(result, list)
62
+ assert result[0]["slug"] == "github-file-search"
63
+
64
+
65
+ @pytest.mark.asyncio
66
+ async def test_list_returns_tools_page():
67
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
68
+ m.return_value = FAKE_PAGE
69
+ result = await list_tools(trust_status="community_reviewed")
70
+ assert isinstance(result, ToolsPage)
71
+ assert result.total == 1
72
+ assert result.page == 1
73
+ assert result.limit == 20
74
+
75
+
76
+ @pytest.mark.asyncio
77
+ async def test_list_passes_none_trust_status_when_not_given():
78
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
79
+ m.return_value = FAKE_PAGE
80
+ await list_tools()
81
+ _, kwargs = m.call_args
82
+ assert kwargs.get("trust_status") is None
83
+
84
+
85
+ def test_verify_sync_returns_verify_result():
86
+ with patch("opentrust._client._Client.get", new_callable=AsyncMock) as m:
87
+ m.return_value = FAKE_PASSPORT
88
+ result = verify_sync("github-file-search")
89
+ assert isinstance(result, VerifyResult)
90
+ assert result.trust_level == 4