agent-trust-plane 0.3.2__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.
Files changed (98) hide show
  1. agent_trust_plane-0.3.2.dist-info/METADATA +143 -0
  2. agent_trust_plane-0.3.2.dist-info/RECORD +98 -0
  3. agent_trust_plane-0.3.2.dist-info/WHEEL +4 -0
  4. agent_trust_plane-0.3.2.dist-info/entry_points.txt +3 -0
  5. agent_trust_plane-0.3.2.dist-info/licenses/LICENSE +21 -0
  6. atp_adapter_http/__init__.py +19 -0
  7. atp_adapter_http/client.py +334 -0
  8. atp_adapter_http/py.typed +0 -0
  9. atp_adapter_mcp/__init__.py +30 -0
  10. atp_adapter_mcp/__main__.py +6 -0
  11. atp_adapter_mcp/config.py +187 -0
  12. atp_adapter_mcp/discovery.py +189 -0
  13. atp_adapter_mcp/mapping.py +138 -0
  14. atp_adapter_mcp/proxy.py +389 -0
  15. atp_adapter_mcp/py.typed +0 -0
  16. atp_audit/__init__.py +22 -0
  17. atp_audit/events.py +92 -0
  18. atp_audit/py.typed +0 -0
  19. atp_audit/store.py +269 -0
  20. atp_cli/__init__.py +1 -0
  21. atp_cli/demos.py +409 -0
  22. atp_cli/doctor.py +136 -0
  23. atp_cli/home.py +138 -0
  24. atp_cli/main.py +755 -0
  25. atp_cli/mcp_cmds.py +220 -0
  26. atp_cli/py.typed +0 -0
  27. atp_core/__init__.py +64 -0
  28. atp_core/authority.py +56 -0
  29. atp_core/canonical.py +48 -0
  30. atp_core/decision.py +126 -0
  31. atp_core/envelope.py +145 -0
  32. atp_core/errors.py +28 -0
  33. atp_core/files.py +18 -0
  34. atp_core/ids.py +14 -0
  35. atp_core/money.py +60 -0
  36. atp_core/principals.py +23 -0
  37. atp_core/py.typed +0 -0
  38. atp_core/reasons.py +93 -0
  39. atp_core/resources.py +14 -0
  40. atp_core/sqlite.py +57 -0
  41. atp_core/timeutil.py +18 -0
  42. atp_evals/__init__.py +20 -0
  43. atp_evals/__main__.py +62 -0
  44. atp_evals/analysis.py +454 -0
  45. atp_evals/harness.py +80 -0
  46. atp_evals/model.py +69 -0
  47. atp_evals/py.typed +0 -0
  48. atp_evals/regression/__init__.py +43 -0
  49. atp_evals/regression/format.py +198 -0
  50. atp_evals/regression/record.py +156 -0
  51. atp_evals/regression/runner.py +300 -0
  52. atp_evals/runner.py +108 -0
  53. atp_evals/scenarios.py +557 -0
  54. atp_gateway/__init__.py +25 -0
  55. atp_gateway/__main__.py +66 -0
  56. atp_gateway/api.py +330 -0
  57. atp_gateway/app.py +99 -0
  58. atp_gateway/auth.py +83 -0
  59. atp_gateway/evidence.py +115 -0
  60. atp_gateway/grants.py +277 -0
  61. atp_gateway/local.py +169 -0
  62. atp_gateway/py.typed +0 -0
  63. atp_gateway/reports.py +66 -0
  64. atp_gateway/schemas.py +116 -0
  65. atp_gateway/service.py +753 -0
  66. atp_gateway/settings.py +90 -0
  67. atp_gateway/tools.py +177 -0
  68. atp_gateway/wiring.py +140 -0
  69. atp_identity/__init__.py +40 -0
  70. atp_identity/credentials.py +254 -0
  71. atp_identity/grants.py +91 -0
  72. atp_identity/provider.py +68 -0
  73. atp_identity/py.typed +0 -0
  74. atp_identity/scope.py +121 -0
  75. atp_identity/service.py +299 -0
  76. atp_identity/store.py +127 -0
  77. atp_policy/__init__.py +45 -0
  78. atp_policy/analysis.py +182 -0
  79. atp_policy/base.py +111 -0
  80. atp_policy/declarative.py +417 -0
  81. atp_policy/directory.py +45 -0
  82. atp_policy/engine.py +66 -0
  83. atp_policy/explain.py +256 -0
  84. atp_policy/policies/__init__.py +27 -0
  85. atp_policy/policies/authority.py +89 -0
  86. atp_policy/policies/payments.py +255 -0
  87. atp_policy/py.typed +0 -0
  88. atp_policy/registry.py +119 -0
  89. finance_agent/__init__.py +44 -0
  90. finance_agent/__main__.py +86 -0
  91. finance_agent/agent.py +155 -0
  92. finance_agent/invoices.py +89 -0
  93. finance_agent/py.typed +0 -0
  94. finance_agent/runner.py +172 -0
  95. finance_agent/scenario.py +157 -0
  96. notes_mcp_server/__init__.py +81 -0
  97. notes_mcp_server/__main__.py +4 -0
  98. notes_mcp_server/py.typed +0 -0
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-trust-plane
3
+ Version: 0.3.2
4
+ Summary: Authorization boundary and security regression testing for AI agent tool calls (MCP): decide on arguments against delegated authority, bind decisions to single-use grants, replay, and pin denials as CI tests. Experimental.
5
+ Project-URL: Homepage, https://github.com/TimelessVisions/agent-trust-plane
6
+ Project-URL: Repository, https://github.com/TimelessVisions/agent-trust-plane
7
+ Project-URL: Issues, https://github.com/TimelessVisions/agent-trust-plane/issues
8
+ Project-URL: Documentation, https://github.com/TimelessVisions/agent-trust-plane/tree/main/docs
9
+ Project-URL: Changelog, https://github.com/TimelessVisions/agent-trust-plane/blob/main/CHANGELOG.md
10
+ Project-URL: Security, https://github.com/TimelessVisions/agent-trust-plane/blob/main/SECURITY.md
11
+ Author-email: George Gakravyi <george@agentcommercegateway.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Keywords: agent-security,ai-agents,authorization,llm-security,mcp,model-context-protocol,regression-testing,security-testing
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Intended Audience :: Information Technology
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Security
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Topic :: Software Development :: Testing
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.12
31
+ Requires-Dist: fastapi>=0.115
32
+ Requires-Dist: httpx>=0.28
33
+ Requires-Dist: mcp<3,>=2.2
34
+ Requires-Dist: pydantic-settings>=2.7
35
+ Requires-Dist: pydantic>=2.10
36
+ Requires-Dist: pyyaml>=6.0
37
+ Requires-Dist: uvicorn[standard]>=0.34
38
+ Description-Content-Type: text/markdown
39
+
40
+ # Agent Trust Plane
41
+
42
+ **Put a real authorization boundary between your AI agent and its tools, and turn every dangerous action into a regression test so it cannot silently come back.**
43
+
44
+ ![Real transcript of `atp demo wrap`: a write is allowed, a delete is denied before it reaches the server, the denial is explained, pinned as a regression test, and the test passes](https://raw.githubusercontent.com/TimelessVisions/agent-trust-plane/main/docs/images/demo-wrap.svg)
45
+
46
+ ```bash
47
+ uv run atp demo wrap # a real MCP server, wrapped; one ALLOW, one DENY, one regression test
48
+ uv run atp mcp init -- <your mcp server> # then: atp mcp wrap (Streamable HTTP upstreams: --url)
49
+ uv run atp regression add <trace> && uv run atp test atp-regression.yaml
50
+ ```
51
+
52
+ > **Status: experimental, v0.3.2, no users yet, not independently audited.** Every claim below is backed by a test in this repository or says otherwise. What it does *not* protect is in [docs/security/threat-model.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/threat-model.md).
53
+
54
+ ## Why this exists
55
+
56
+ Agents act on tool calls, and the tool call is where prompt injection turns into damage: pay this account, delete that file, move this repo. Gateways in front of MCP servers authenticate the caller and allow tools by *name*; most cannot decide on the *arguments* (amount, path, destination) and none replay a recorded decision or keep it fixed in CI ([research, with citations](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/research/competitive-code-review.md)). ATP does that part and only that part: it is a decision-and-evidence layer, not a gateway ([why-atp.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/why-atp.md)).
57
+
58
+ ## How it works
59
+
60
+ ```
61
+ agent / MCP client ──stdio──▶ atp mcp wrap ──▶ upstream MCP server (stdio or Streamable HTTP)
62
+ │
63
+ authenticate the agent; resolve HUMAN → agent delegation (capabilities,
64
+ resource scope, limits — a child can only narrow)
65
+ build an ActionEnvelope from the call (paths normalised, args bounded)
66
+ decide: kernel policies + your declared policy set → ALLOW / DENY / WOULD_DENY
67
+ ALLOW → single-use grant over the exact action hash → forward → record outcome
68
+ DENY → refuse (enforce) | forward and record WOULD_DENY (shadow)
69
+ everything on a hash-chained trace in .atp/
70
+ ```
71
+
72
+ Then, offline: `atp trace list` · `atp policy explain TRACE` · `atp regression add TRACE` · `atp test` · `atp policy impact --from A --to B` · `atp mutate TRACE` · `atp evidence export TRACE`.
73
+
74
+ ## Quickstart (measured: [docs/first-user-test-v2.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/first-user-test-v2.md))
75
+
76
+ Prerequisites: Python ≥ 3.12 and [uv](https://docs.astral.sh/uv/). No API keys, no Docker, no database setup, no secrets to copy.
77
+
78
+ ```bash
79
+ git clone <this repository> && cd agent-trust-plane
80
+ uv run atp demo wrap # syncs the workspace on first run; 37 s cold on the maintainer machine
81
+ ```
82
+
83
+ The same commands work in PowerShell. `uv run atp doctor` explains anything missing. Other demos: `atp demo injection` (an injected invoice tries to pay $12,500 on a $1,000 delegation), `atp demo regression` (a permitted payment reveals a policy hole; replay under the fix flips it), `atp demo mcp` (the proxy against a remote-style gateway) — [docs/demos.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/demos.md).
84
+
85
+ ## Wrap your own MCP server
86
+
87
+ ```bash
88
+ uv run atp mcp init --name fs -- npx -y @modelcontextprotocol/server-filesystem /abs/sandbox
89
+ # reads tools/list; proposes capabilities from the server's annotations (unverified: review them);
90
+ # writes atp-mcp.yaml and .atp/policies.yaml (declared set fs-v1)
91
+ uv run atp mcp wrap --mode shadow # observe first: denials are recorded as WOULD_DENY and forwarded
92
+ uv run atp mcp wrap # enforce
93
+ ```
94
+
95
+ Tools the server itself marks destructive (`write_file`, `edit_file`, `move_file` on the reference server) get `fs:destroy`, which is **not delegated by default**: the first write is denied with `CAPABILITY_NOT_GRANTED` until you add `fs:destroy` to `authority.capabilities` in `atp-mcp.yaml` — a deliberate fail-closed default, and the one thing the first-user test tripped over.
96
+
97
+ Point your MCP client at the wrap command instead of the server ([config example](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/integrations/frameworks.md)). Verified upstreams: this repo's notes server (stdio and Streamable HTTP, every CI run) and the reference `@modelcontextprotocol/server-filesystem` (opt-in test, run 2026-09-17). Everything else is listed as *not verified* in [docs/integrations/compatibility.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/integrations/compatibility.md).
98
+
99
+ Scope paths, not just tool names: `resource_scope: ["path:/abs/sandbox/*"]` with `..`, separators and (optionally) case normalised before matching. Argument limits go in [`.atp/policies.yaml`](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/policies/policies.md):
100
+
101
+ ```yaml
102
+ rules:
103
+ - id: no-large-writes
104
+ applies_to: {tool: mcp.fs, action: write_file}
105
+ kind: argument_max_length
106
+ argument: content
107
+ max_length: 20000
108
+ ```
109
+
110
+ ## Security regression tests
111
+
112
+ ```bash
113
+ uv run atp trace list # what was ALLOWed / DENIED / WOULD_DENY
114
+ uv run atp policy explain 457328bdd4ef # which constraint failed, and what would need to change
115
+ uv run atp regression add 457328bdd4ef --name "delete stays denied"
116
+ uv run atp test atp-regression.yaml # exit 1 if the decision ever changes; --junit/--json for CI
117
+ uv run atp policy impact --from fs-v1 --to fs-v2 # which recorded actions flip; DENY -> ALLOW first
118
+ uv run atp mutate 457328bdd4ef # perturb the recorded action; authorize only, never execute
119
+ ```
120
+
121
+ A suite is YAML: principals, a delegation graph, cases with the expected outcome / reason / policy. Runs use an ephemeral in-process gateway and call only `/authorize` — a spy on `execute` sees zero calls. CI example without secrets: [examples/regression-suite](https://github.com/TimelessVisions/agent-trust-plane/tree/main/examples/regression-suite/) and a copy-in [composite action](https://github.com/TimelessVisions/agent-trust-plane/tree/main/examples/github-action/). Guide: [docs/regression/regression-testing.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/regression/regression-testing.md).
122
+
123
+ ## Architecture
124
+
125
+ Protocol-neutral kernel (`atp_core` envelope/decision, `atp_identity` credentials + delegation chains, `atp_policy` engine + declared sets, `atp_audit` hash chain, `atp_gateway` service/grants/API) with adapters on the edge (`atp_adapter_mcp` proxy, `atp_adapter_http` client) and the `atp` CLI. One distribution, `agent-trust-plane`, installs everything (`uvx --from <wheel-or-git-url> atp …`). The kernel imports nothing from MCP — a test enforces it. Details: [docs/architecture/architecture.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/architecture/architecture.md), [first principles](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/architecture/first-principles.md), [ADRs](https://github.com/TimelessVisions/agent-trust-plane/tree/main/docs/adr/).
126
+
127
+ Overhead ([docs/benchmarks.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/benchmarks.md), single machine, sequential): policy engine 0.26 ms; `atp mcp wrap` adds ~11 ms p50 to an MCP tool call with durable SQLite evidence; the HTTP-gateway proxy path adds ~21 ms.
128
+
129
+ ## Security model and limitations
130
+
131
+ Guarantees, assumptions, deployment requirements, non-guarantees and known gaps are separated in [docs/security/threat-model.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/threat-model.md); attempted attacks and results in [docs/red-team/architecture-attacks.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/red-team/architecture-attacks.md); the self-review with severities in [docs/security/security-review.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/security-review.md). Headlines:
132
+
133
+ - **Only the mediated path is protected.** An agent that can reach the tool without the proxy is not stopped — a test shows it on purpose. Whether that can happen is a deployment property: [docs/security/enforcing-the-boundary.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/enforcing-the-boundary.md).
134
+ - **The operator (and `.atp/keys.env`) is omnipotent**; agent credentials are bearer tokens with expiry and revocation, no proof of possession.
135
+ - **Single-use grants prove one release, not exactly-once external effects**: [docs/security/idempotency.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/idempotency.md).
136
+ - The hash chain catches edits by non-writers; a DB writer can recompute it: [docs/security/trace-integrity.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/security/trace-integrity.md).
137
+ - Declared policies are limit/equality rules; use OPA/Cedar for more ([why-not-just-opa.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/policies/why-not-just-opa.md)). Prompt injection is bounded, not detected.
138
+
139
+ Evidence for what *is* handled: 439 tests including property-based tests (delegation monotonicity, scope-algebra soundness, a stateful grant lifecycle with tampering and revocation interleaved), an exhaustive check of the grant state machine ([spec](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/formal/grant-lifecycle.md)), eight adversarial evals, and real-protocol MCP end-to-end tests.
140
+
141
+ ## Contributing
142
+
143
+ [CONTRIBUTING.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/CONTRIBUTING.md), [DEVELOPMENT.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/DEVELOPMENT.md) (module map, invariants not to break, how to add a policy or adapter), [ROADMAP.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/ROADMAP.md), [docs/good-first-issues.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/good-first-issues.md), [SECURITY.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/SECURITY.md) for reporting a vulnerability, [CHANGELOG.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/CHANGELOG.md). [MIT licensed](https://github.com/TimelessVisions/agent-trust-plane/blob/main/LICENSE) (dependency and copyright record: [docs/release/licensing.md](https://github.com/TimelessVisions/agent-trust-plane/blob/main/docs/release/licensing.md)). No telemetry.
@@ -0,0 +1,98 @@
1
+ atp_adapter_http/__init__.py,sha256=cayabsdf7y-SCOyTvjfpAb8aQy6zG8tMnwQ1z-NTUSA,373
2
+ atp_adapter_http/client.py,sha256=vOdB8Cd0o_vx3OzdzGCKwWLBQjDu03DM4pBCfES_R1w,12089
3
+ atp_adapter_http/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ atp_adapter_mcp/__init__.py,sha256=yIcqDGrtt44o9JQtSPwxcVNrrZkngGSttFS2P-0I0yw,817
5
+ atp_adapter_mcp/__main__.py,sha256=6nT2r0nwRpalNxRBOyAcOasKt6RYNiw8nbUJ9Cwq-gE,159
6
+ atp_adapter_mcp/config.py,sha256=_O0oe0gA9RjFybPKNqLctbi9Dkz52CUsV3yld0s9I7E,7388
7
+ atp_adapter_mcp/discovery.py,sha256=v4eSyVzxSexqhsWjZRhl9JtfAek0ljFQZKlZinT7REk,6908
8
+ atp_adapter_mcp/mapping.py,sha256=Pc4GVajMRSCvnRfVWpMs71zt7zvSg9DQP0ojar0Y4e0,5742
9
+ atp_adapter_mcp/proxy.py,sha256=032C1oUrUlqxQpzD5JXYFLDoz-ya7cjB-QDe0W9W-4Y,15397
10
+ atp_adapter_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ finance_agent/__init__.py,sha256=qh2iGwaRkaDiAcpeZumMhu_FQLa9_BG16txHo_VhDbk,1049
12
+ finance_agent/__main__.py,sha256=5UT3THST_Wr2wQ59vldph44d_RxjUfVo5JngX2HO6hE,3225
13
+ finance_agent/agent.py,sha256=Gbh7PEfJT6CmBAJEhTOsaNnYZOQD0NPgQVb8Wj4Y0gU,6101
14
+ finance_agent/invoices.py,sha256=ucmCFZfaTAHcaAekCz4aTUYkeyH2Ob56oiC_ybZef_c,2597
15
+ finance_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ finance_agent/runner.py,sha256=GuLbyH_ab3NZBdHQ3NnqHdpiZIn02uciJ61-A9dU_c0,5387
17
+ finance_agent/scenario.py,sha256=BMWFQJzhsMuoHWwSXkMp9eNc938qS4Q_8EjjxXhPmrQ,5684
18
+ notes_mcp_server/__init__.py,sha256=9_Ea2pyTp5avA0Gti3aHSXuGrgHE9LxHIwtCt6ikHfc,2445
19
+ notes_mcp_server/__main__.py,sha256=ZXP75ar_BovRn47BIZw_CTbDamfyqFKBe-DTQ_JsLUM,73
20
+ notes_mcp_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ atp_audit/__init__.py,sha256=MPMorkdLw6FeX5q8Dy00pHXOcoZCkSs3Wy6wDsu-15A,461
22
+ atp_audit/events.py,sha256=89xVGMcdhBvH6KcltbC6QylcW_GsSwbKBDFEsJtFEFA,2757
23
+ atp_audit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ atp_audit/store.py,sha256=VCTbZgF_lQDpCysAh7nna4rnzXHmCEJmhYqJ6tQTf70,9204
25
+ atp_cli/__init__.py,sha256=ZwGz11lkrOGo7JfZ14zj3ZlYEBkbyB6rbjIh-Sctj0o,28
26
+ atp_cli/demos.py,sha256=585CYONG8YM3H-v1fAWKLAaQ91CRd64JniLRJhTwE6I,17610
27
+ atp_cli/doctor.py,sha256=Ofb20WK_b2685ddgzkwLSq3HtqyJSu3jKEesBekIbCE,3809
28
+ atp_cli/home.py,sha256=ZP9cZLmmPV8pzn3nsrbyyXmuxleOgIiFkaDimM8vEBo,5173
29
+ atp_cli/main.py,sha256=-6PNqhODsQjLEgNbGewPxVWc7CoIB8dZLkS3B3kC9vQ,28155
30
+ atp_cli/mcp_cmds.py,sha256=SNxxlLVGZe8UYpEWFWMquOCIZzSUEZgWcUQDLaYiMrE,7773
31
+ atp_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ atp_core/__init__.py,sha256=BFqVDlN7KnXpqTsCUDEsPShnMDN5QpCyQuSD-6zddkM,1823
33
+ atp_core/authority.py,sha256=vLuX9iJ4IcboPHsNqYWHlaMTUIZytqvYHND65PxlLnU,2017
34
+ atp_core/canonical.py,sha256=ZZi44Dr889CXANQpnENwPYA4_HESyH1KaEc6oOoxx58,1335
35
+ atp_core/decision.py,sha256=-EeevrnT66lPXFCAPydL0eNOGsh2A742r-Roe1XSwEc,4011
36
+ atp_core/envelope.py,sha256=1ZhdueqRxTseZ0B0oNU8D2s0oizaRMgxz2PiFfrD5-A,5270
37
+ atp_core/errors.py,sha256=qILZdMm7RDSYr74hTj80Dn-fe3FeGTxqvHsUHasWI40,750
38
+ atp_core/files.py,sha256=LRjqQjTlTthZ3-VC93kKbfSLhlMkSHsEOa50e8TnVBY,643
39
+ atp_core/ids.py,sha256=oD9dR_M4I1jMk9RgZgCVzIXriH-qm3DgcqHgXeN4S1k,369
40
+ atp_core/money.py,sha256=derCJcyhIXTM2gS6SRWzikfXsYhxFnktB18At2oW4-I,2320
41
+ atp_core/principals.py,sha256=UqQLMCZJAiMwTS9nCCfXGqDUhwKWYHSteJcs-GHjas8,558
42
+ atp_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ atp_core/reasons.py,sha256=QVcvvZWKoEJB3ZlcM0WSKGCFEVjk5MIfYHBjVTbh_7w,4741
44
+ atp_core/resources.py,sha256=rS1F69qdgQjchxQq5ZVgY4wKaWlGT-DD001xxyHbZbI,662
45
+ atp_core/sqlite.py,sha256=jCzlpSlq3W8VR8CjCkOTx9eF0xCAD2m29a-VN3I8SLs,1912
46
+ atp_core/timeutil.py,sha256=pqBed32JiE2kR9vDmXdQ95G63rPZEh3GCO04LIjx5sI,450
47
+ atp_evals/__init__.py,sha256=S334F8N68dzJ1sE5UMhIrycgn8TOyXGEHlLaTMJZypI,541
48
+ atp_evals/__main__.py,sha256=mz9p12DK2UTXHSgrq_aHcZYcDDkDvvz8UOJ8nt71fnA,2145
49
+ atp_evals/analysis.py,sha256=Y4ea3g-0hdzgrTylxJup0klo-I-8c18dwUzPXOS_DLk,16520
50
+ atp_evals/harness.py,sha256=jWJvfI-xn1gGBUc4q0Nfg6Rol8yKm9mmUfA4KcfgPBA,3048
51
+ atp_evals/model.py,sha256=Atlv6buNFPALimTJshGafElYXOzcOUHcImFg8lYmmhA,1656
52
+ atp_evals/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ atp_evals/runner.py,sha256=1DNdfSOoqiXdJuqP52bNdkHcOK2Its8LwHyb4YxYxro,3976
54
+ atp_evals/scenarios.py,sha256=kESq9OJ2G-VQN2DZRKtTqej7LCw40HWObwMVZ51n6Wc,22895
55
+ atp_evals/regression/__init__.py,sha256=Xxl3bV4KtfYv_KxG2JyOaZ4qkMWTiCTbpluCMlKj0oc,850
56
+ atp_evals/regression/format.py,sha256=672U_P1q0tMD_VimqpKyxLAaRTqJqRDVAvYnbiLpmyo,7355
57
+ atp_evals/regression/record.py,sha256=AmVCw4FvUOn3Js3cw73Tly5oCgL9Fx62kih1lPDe88A,6094
58
+ atp_evals/regression/runner.py,sha256=ZOT0iymqOUHd-Nr-tt7q7OwsTxP2O1wedrPTbMr7T4U,10923
59
+ atp_identity/__init__.py,sha256=SEEbef1rBdbygguHwqG331d8-au5uWJBEWGjzWCV9gI,1161
60
+ atp_identity/credentials.py,sha256=xaXC0miMWGa6jYrEZxRiSOYelR-P32OVqZ66LsCEzK4,9725
61
+ atp_identity/grants.py,sha256=2tjIwC7alchG4rZfQhhFU7ccgj6kOUWsx1pH4K9b2Ng,3163
62
+ atp_identity/provider.py,sha256=j1mU-2xC7L-eGIdLTBojMXrsYM4EhK6JkbPKHCHSv04,2897
63
+ atp_identity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ atp_identity/scope.py,sha256=-4pPwv_yEgijxGsDRHnhBGYnBusrLIjKcOQ2eUWGqwI,4133
65
+ atp_identity/service.py,sha256=MYh1rvGZ79I4ORFfqSWIpZfW7siwiCSSTzClhJK-Zd8,11881
66
+ atp_identity/store.py,sha256=gSjmV0awEqf41nwbZ-nifUbp1IYhUXd5IOXOOPx2duQ,4471
67
+ atp_policy/__init__.py,sha256=Bvjb0XhztNxx4phvJTlcAQMO0jL0-5IdcsQDazQDWrk,1038
68
+ atp_policy/analysis.py,sha256=vbAH8ydEh7AGjB3QSgeenkgtZ3kJ9WEoZTIpXCCG3iU,6768
69
+ atp_policy/base.py,sha256=UZp4dxxSrqouI0DJW6UQknhax819wmXRq3-RksWQ-9I,3052
70
+ atp_policy/declarative.py,sha256=AN5s02XVGylQO0xAtnQbz_GqnIFgKznhJGZBRUcwdho,15596
71
+ atp_policy/directory.py,sha256=3y_eVAFYoayFwW0Qbz7Ngj0sufWt-mkM-bzo9R-_q68,1390
72
+ atp_policy/engine.py,sha256=JC5NCE6GHoupCh1D8iJQfbYpLokgt941Q2J1L2ew4ts,2403
73
+ atp_policy/explain.py,sha256=dZ-WeE8h8lxidK--taKmlxwCxy5dcDza-_YIoS8QFYs,10246
74
+ atp_policy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
+ atp_policy/registry.py,sha256=5HnTJLlLUEiG3kNXSXtgOwaN7rMMIYQe4MZUABWeQnU,4105
76
+ atp_policy/policies/__init__.py,sha256=9JSxomjd4mThRnPB_OUaBZV8MfBNsph0Fz-oRpsH0gU,670
77
+ atp_policy/policies/authority.py,sha256=vwbjM-Vd4HKpvXESXCz-opxnTOaj2xPLIoGLKdemSrk,3241
78
+ atp_policy/policies/payments.py,sha256=hLYk40yNmax1R041ZJs15PO40hw7VUWQTrlRhTp1f6c,9505
79
+ atp_gateway/__init__.py,sha256=ELYFCJmXJGrCf3B26JAoN9eP6M_QzfXtuDG0o9zQm_4,565
80
+ atp_gateway/__main__.py,sha256=hOmM2QKRzs-gKm-ecfXS6eLJ_Ub0OO2rbi-Zr7fIWCE,2108
81
+ atp_gateway/api.py,sha256=j5WFcGkkHvszrlPNF-JwVl3PyvU5l7DanLfuAngRuLo,13202
82
+ atp_gateway/app.py,sha256=SKnfh9IEzD5rBugW4diWvEV36LCtITXvgTymoaKTMK0,3032
83
+ atp_gateway/auth.py,sha256=U5YAkt4Zoi4az0MPWl7FRYvXbw4jF67Ycgd7ArwSxPE,2718
84
+ atp_gateway/evidence.py,sha256=MnGI6qXVjum97kFfJCrE4sG7WyyLUzHYS-OK_sWgvVA,4713
85
+ atp_gateway/grants.py,sha256=CRwLpwAuTGLwGixe7n6eW7kfOLPNByq9jljAbItfHUo,9785
86
+ atp_gateway/local.py,sha256=6IwKOapxiv-_A4EY4rOZ7JWAEXPOjPCOmZ1z6J6k4kk,6059
87
+ atp_gateway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
+ atp_gateway/reports.py,sha256=a1L7zaREp-92JAQ6XO3Ax2dbQcvRwd4yTUcxq4f8GZI,1842
89
+ atp_gateway/schemas.py,sha256=qQ5c4lcWVloRRV-hvNIn8HszLlpueKZgf91eg5ijIrY,2984
90
+ atp_gateway/service.py,sha256=uR4WehiKFVxwmY1PYsuBRauPfImGFfYRRuyMNykSwjg,29321
91
+ atp_gateway/settings.py,sha256=Xx6Sa6AJ19Y-qoJKw4vsPmu38R25tRGbqB1luweb778,3556
92
+ atp_gateway/tools.py,sha256=V_n43dsTaPQP5WQx5Si_poey903ED3cxvPZq5F9HnCQ,5882
93
+ atp_gateway/wiring.py,sha256=yAAuhJQd0NN1Ct_TvkwZg2JJY0qodedDCUSpfQPXkNs,4928
94
+ agent_trust_plane-0.3.2.dist-info/METADATA,sha256=HvZp5tIJ3uAO7sxF_2JP2_B6At5h8mZtwGaWgYnvtps,13085
95
+ agent_trust_plane-0.3.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
96
+ agent_trust_plane-0.3.2.dist-info/entry_points.txt,sha256=NjniUmUvyMYvXV0HmfGGGc2gLSP1v8OtI9jYUL5gQZA,80
97
+ agent_trust_plane-0.3.2.dist-info/licenses/LICENSE,sha256=reFTCTIZRQSFNzHX10QcDeTgX4s2ddAlE9TF14fEbpM,1072
98
+ agent_trust_plane-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ agent-trust-plane = atp_cli.main:main
3
+ atp = atp_cli.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Gakravyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ """HTTP client SDK for agents talking to the trust gateway."""
2
+
3
+ from atp_adapter_http.client import (
4
+ AgentClient,
5
+ AuthorizeResponse,
6
+ ExecuteResponse,
7
+ ExecutionGrant,
8
+ GatewayError,
9
+ TrustPlaneClient,
10
+ )
11
+
12
+ __all__ = [
13
+ "AgentClient",
14
+ "AuthorizeResponse",
15
+ "ExecuteResponse",
16
+ "ExecutionGrant",
17
+ "GatewayError",
18
+ "TrustPlaneClient",
19
+ ]
@@ -0,0 +1,334 @@
1
+ """Agent-side client for the trust gateway.
2
+
3
+ An agent framework integrates by building an ``ActionEnvelope`` for every
4
+ consequential tool call and going through ``authorize`` then ``execute``.
5
+ The client never decides anything; it transports envelopes and grants.
6
+
7
+ ``TrustPlaneClient`` speaks HTTP. It can also be pointed at an ASGI app
8
+ in-process (``TrustPlaneClient.for_app(app)``), which is how the eval harness
9
+ exercises the real API without a network.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import contextlib
15
+ import weakref
16
+ from typing import Any, Protocol
17
+
18
+ import httpx
19
+ from pydantic import BaseModel, ConfigDict
20
+
21
+ from atp_core import ActionEnvelope, Decision, ReasonCode
22
+
23
+
24
+ class GatewayError(Exception):
25
+ def __init__(self, status_code: int, reason_code: str, message: str) -> None:
26
+ super().__init__(f"{status_code} {reason_code}: {message}")
27
+ self.status_code = status_code
28
+ self.reason_code = reason_code
29
+ self.message = message
30
+
31
+
32
+ class ExecutionGrant(BaseModel):
33
+ model_config = ConfigDict(frozen=True, extra="forbid")
34
+
35
+ token: str
36
+ grant_id: str
37
+ expires_at: str
38
+
39
+
40
+ class AuthorizeResponse(BaseModel):
41
+ model_config = ConfigDict(frozen=True, extra="forbid")
42
+
43
+ decision: Decision
44
+ execution_grant: ExecutionGrant | None = None
45
+
46
+
47
+ class ExecuteResponse(BaseModel):
48
+ model_config = ConfigDict(frozen=True, extra="forbid")
49
+
50
+ trace_id: str
51
+ envelope_id: str
52
+ status: str
53
+ reason_code: ReasonCode
54
+ message: str
55
+ result: dict[str, Any] | None = None
56
+
57
+ @property
58
+ def executed(self) -> bool:
59
+ return self.status == "completed"
60
+
61
+
62
+ class AgentClient(Protocol):
63
+ """The agent-facing subset of the client, implemented over HTTP by
64
+ ``TrustPlaneClient`` and in-process by ``atp_gateway.local.InProcessAgentClient``.
65
+ Adapters (the MCP proxy) depend on this, not on a transport."""
66
+
67
+ def authorize(
68
+ self, envelope: ActionEnvelope, *, policy_set_version: str | None = None
69
+ ) -> AuthorizeResponse: ...
70
+
71
+ def execute(
72
+ self, envelope: ActionEnvelope, grant: ExecutionGrant | str | None
73
+ ) -> ExecuteResponse: ...
74
+
75
+ def report_outcome(
76
+ self, grant_id: str, *, succeeded: bool, summary: dict[str, Any]
77
+ ) -> dict[str, Any]: ...
78
+
79
+ def report_shadow_outcome(
80
+ self, trace_id: str, *, succeeded: bool, summary: dict[str, Any]
81
+ ) -> dict[str, Any]: ...
82
+
83
+
84
+ class _SyncASGITransport(httpx.BaseTransport):
85
+ """Drive an ASGI app from synchronous code over one long-lived event loop.
86
+
87
+ httpx's ASGITransport is async-only. A blocking portal keeps a single
88
+ loop alive in a background thread for the client's lifetime, so each
89
+ request costs a thread hop rather than a fresh event loop (which made
90
+ `atp mcp wrap` slower than going over HTTP before v0.3.0).
91
+ """
92
+
93
+ def __init__(self, app: Any) -> None:
94
+ from anyio.from_thread import start_blocking_portal
95
+
96
+ self._asgi = httpx.ASGITransport(app=app)
97
+ self._portal_cm = start_blocking_portal()
98
+ self._portal = self._portal_cm.__enter__()
99
+ # The portal thread is not a daemon; make sure an unclosed client
100
+ # cannot keep the interpreter alive.
101
+ weakref.finalize(self, _close_portal, self._portal_cm)
102
+
103
+ async def _run(self, request: httpx.Request) -> httpx.Response:
104
+ response = await self._asgi.handle_async_request(request)
105
+ await response.aread()
106
+ return response
107
+
108
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
109
+ response = self._portal.call(self._run, request)
110
+ return httpx.Response(
111
+ status_code=response.status_code,
112
+ headers=response.headers,
113
+ content=response.content,
114
+ request=request,
115
+ )
116
+
117
+ def close(self) -> None:
118
+ _close_portal(self._portal_cm)
119
+
120
+
121
+ def _close_portal(cm: Any) -> None:
122
+ with contextlib.suppress(Exception):
123
+ cm.__exit__(None, None, None)
124
+
125
+
126
+ class TrustPlaneClient:
127
+ def __init__(
128
+ self,
129
+ base_url: str = "http://127.0.0.1:8000",
130
+ *,
131
+ timeout: float = 10.0,
132
+ operator_key: str | None = None,
133
+ agent_token: str | None = None,
134
+ ) -> None:
135
+ self._http = httpx.Client(base_url=base_url, timeout=timeout)
136
+ self._operator_key = operator_key
137
+ self._agent_token = agent_token
138
+
139
+ @classmethod
140
+ def for_app(
141
+ cls, app: Any, *, operator_key: str | None = None, agent_token: str | None = None
142
+ ) -> TrustPlaneClient:
143
+ """In-process client over an ASGI app (no sockets, real routing)."""
144
+ client = cls.__new__(cls)
145
+ client._http = httpx.Client(
146
+ transport=_SyncASGITransport(app), base_url="http://trust-plane"
147
+ )
148
+ client._operator_key = operator_key
149
+ client._agent_token = agent_token
150
+ return client
151
+
152
+ @classmethod
153
+ def from_http(
154
+ cls, http: httpx.Client, *, operator_key: str | None = None, agent_token: str | None = None
155
+ ) -> TrustPlaneClient:
156
+ client = cls.__new__(cls)
157
+ client._http = http
158
+ client._operator_key = operator_key
159
+ client._agent_token = agent_token
160
+ return client
161
+
162
+ def as_agent(self, agent_token: str) -> TrustPlaneClient:
163
+ """A client sharing this transport but authenticating as an agent.
164
+ Operator privileges are deliberately not carried over."""
165
+ client = TrustPlaneClient.__new__(TrustPlaneClient)
166
+ client._http = self._http
167
+ client._operator_key = None
168
+ client._agent_token = agent_token
169
+ return client
170
+
171
+ def as_operator(self, operator_key: str) -> TrustPlaneClient:
172
+ client = TrustPlaneClient.__new__(TrustPlaneClient)
173
+ client._http = self._http
174
+ client._operator_key = operator_key
175
+ client._agent_token = None
176
+ return client
177
+
178
+ def with_operator(self, operator_key: str) -> TrustPlaneClient:
179
+ """Keep the agent identity and add operator privileges (operator tooling only)."""
180
+ client = TrustPlaneClient.__new__(TrustPlaneClient)
181
+ client._http = self._http
182
+ client._operator_key = operator_key
183
+ client._agent_token = self._agent_token
184
+ return client
185
+
186
+ @property
187
+ def agent_token(self) -> str | None:
188
+ return self._agent_token
189
+
190
+ @property
191
+ def operator_key(self) -> str | None:
192
+ return self._operator_key
193
+
194
+ def close(self) -> None:
195
+ self._http.close()
196
+
197
+ def __enter__(self) -> TrustPlaneClient:
198
+ return self
199
+
200
+ def __exit__(self, *exc: object) -> None:
201
+ self.close()
202
+
203
+ # ---------------------------------------------------------------- helpers
204
+ def _request(self, method: str, path: str, **kwargs: Any) -> Any:
205
+ headers: dict[str, str] = dict(kwargs.pop("headers", None) or {})
206
+ if self._agent_token:
207
+ headers.setdefault("Authorization", f"Bearer {self._agent_token}")
208
+ if self._operator_key:
209
+ headers.setdefault("X-ATP-Operator-Key", self._operator_key)
210
+ response = self._http.request(method, path, headers=headers, **kwargs)
211
+ if response.status_code >= 400:
212
+ try:
213
+ body = response.json()
214
+ except ValueError:
215
+ body = {}
216
+ raise GatewayError(
217
+ response.status_code,
218
+ str(body.get("reason_code", "HTTP_ERROR")),
219
+ str(body.get("message") or body.get("detail") or response.text),
220
+ )
221
+ return response.json()
222
+
223
+ # ------------------------------------------------------------ operations
224
+ def authorize(
225
+ self, envelope: ActionEnvelope, *, policy_set_version: str | None = None
226
+ ) -> AuthorizeResponse:
227
+ params = {"policy_set_version": policy_set_version} if policy_set_version else None
228
+ data = self._request(
229
+ "POST", "/authorize", json=envelope.model_dump(mode="json"), params=params
230
+ )
231
+ return AuthorizeResponse.model_validate(data)
232
+
233
+ def execute(
234
+ self, envelope: ActionEnvelope, grant: ExecutionGrant | str | None
235
+ ) -> ExecuteResponse:
236
+ token = grant.token if isinstance(grant, ExecutionGrant) else grant
237
+ data = self._request(
238
+ "POST",
239
+ "/execute",
240
+ json={"envelope": envelope.model_dump(mode="json"), "execution_grant": token},
241
+ )
242
+ return ExecuteResponse.model_validate(data)
243
+
244
+ def record_event(
245
+ self, trace_id: str, event_type: str, payload: dict[str, Any]
246
+ ) -> dict[str, Any]:
247
+ """Record provenance as the authenticated agent."""
248
+ result: dict[str, Any] = self._request(
249
+ "POST",
250
+ f"/traces/{trace_id}/events",
251
+ json={"event_type": event_type, "payload": payload},
252
+ )
253
+ return result
254
+
255
+ def report_outcome(
256
+ self, grant_id: str, *, succeeded: bool, summary: dict[str, Any]
257
+ ) -> dict[str, Any]:
258
+ """External executor: report the result of a released execution."""
259
+ result: dict[str, Any] = self._request(
260
+ "POST",
261
+ f"/executions/{grant_id}/outcome",
262
+ json={"succeeded": succeeded, "summary": summary},
263
+ )
264
+ return result
265
+
266
+ def report_shadow_outcome(
267
+ self, trace_id: str, *, succeeded: bool, summary: dict[str, Any]
268
+ ) -> dict[str, Any]:
269
+ """Shadow mode: report that a WOULD_DENY action was executed anyway."""
270
+ result: dict[str, Any] = self._request(
271
+ "POST",
272
+ f"/traces/{trace_id}/shadow-outcome",
273
+ json={"succeeded": succeeded, "summary": summary},
274
+ )
275
+ return result
276
+
277
+ # ----------------------------------------------------------- credentials
278
+ def issue_credential(
279
+ self, agent: dict[str, str], label: str, expires_at: str | None = None
280
+ ) -> dict[str, Any]:
281
+ """Operator-only. The returned token is shown once."""
282
+ result: dict[str, Any] = self._request(
283
+ "POST",
284
+ f"/agents/{agent['id']}/credentials",
285
+ json={"agent": agent, "label": label, "expires_at": expires_at},
286
+ )
287
+ return result
288
+
289
+ def list_credentials(self, agent_id: str) -> list[dict[str, Any]]:
290
+ """Operator-only. Metadata only; never tokens."""
291
+ result: list[dict[str, Any]] = self._request("GET", f"/agents/{agent_id}/credentials")
292
+ return result
293
+
294
+ def revoke_credential(self, credential_id: str) -> dict[str, Any]:
295
+ result: dict[str, Any] = self._request("POST", f"/credentials/{credential_id}/revoke")
296
+ return result
297
+
298
+ def get_trace(self, trace_id: str) -> dict[str, Any]:
299
+ result: dict[str, Any] = self._request("GET", f"/traces/{trace_id}")
300
+ return result
301
+
302
+ def list_traces(self, limit: int = 50) -> list[dict[str, Any]]:
303
+ result: list[dict[str, Any]] = self._request("GET", "/traces", params={"limit": limit})
304
+ return result
305
+
306
+ def replay(self, trace_id: str, *, policy_set_version: str | None = None) -> dict[str, Any]:
307
+ result: dict[str, Any] = self._request(
308
+ "POST", f"/replay/{trace_id}", json={"policy_set_version": policy_set_version}
309
+ )
310
+ return result
311
+
312
+ def issue_delegation(self, request: dict[str, Any]) -> dict[str, Any]:
313
+ result: dict[str, Any] = self._request("POST", "/delegations", json=request)
314
+ return result
315
+
316
+ def revoke_delegation(self, grant_id: str) -> dict[str, Any]:
317
+ result: dict[str, Any] = self._request("POST", f"/delegations/{grant_id}/revoke")
318
+ return result
319
+
320
+ def policy_sets(self) -> list[dict[str, Any]]:
321
+ result: list[dict[str, Any]] = self._request("GET", "/policy-sets")
322
+ return result
323
+
324
+ def run_evals(self) -> dict[str, Any]:
325
+ result: dict[str, Any] = self._request("POST", "/evals/run")
326
+ return result
327
+
328
+ def ledger(self) -> list[dict[str, Any]]:
329
+ result: list[dict[str, Any]] = self._request("GET", "/ledger/payments")
330
+ return result
331
+
332
+ def health(self) -> dict[str, Any]:
333
+ result: dict[str, Any] = self._request("GET", "/health")
334
+ return result
File without changes