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.
- agent_trust_plane-0.3.2.dist-info/METADATA +143 -0
- agent_trust_plane-0.3.2.dist-info/RECORD +98 -0
- agent_trust_plane-0.3.2.dist-info/WHEEL +4 -0
- agent_trust_plane-0.3.2.dist-info/entry_points.txt +3 -0
- agent_trust_plane-0.3.2.dist-info/licenses/LICENSE +21 -0
- atp_adapter_http/__init__.py +19 -0
- atp_adapter_http/client.py +334 -0
- atp_adapter_http/py.typed +0 -0
- atp_adapter_mcp/__init__.py +30 -0
- atp_adapter_mcp/__main__.py +6 -0
- atp_adapter_mcp/config.py +187 -0
- atp_adapter_mcp/discovery.py +189 -0
- atp_adapter_mcp/mapping.py +138 -0
- atp_adapter_mcp/proxy.py +389 -0
- atp_adapter_mcp/py.typed +0 -0
- atp_audit/__init__.py +22 -0
- atp_audit/events.py +92 -0
- atp_audit/py.typed +0 -0
- atp_audit/store.py +269 -0
- atp_cli/__init__.py +1 -0
- atp_cli/demos.py +409 -0
- atp_cli/doctor.py +136 -0
- atp_cli/home.py +138 -0
- atp_cli/main.py +755 -0
- atp_cli/mcp_cmds.py +220 -0
- atp_cli/py.typed +0 -0
- atp_core/__init__.py +64 -0
- atp_core/authority.py +56 -0
- atp_core/canonical.py +48 -0
- atp_core/decision.py +126 -0
- atp_core/envelope.py +145 -0
- atp_core/errors.py +28 -0
- atp_core/files.py +18 -0
- atp_core/ids.py +14 -0
- atp_core/money.py +60 -0
- atp_core/principals.py +23 -0
- atp_core/py.typed +0 -0
- atp_core/reasons.py +93 -0
- atp_core/resources.py +14 -0
- atp_core/sqlite.py +57 -0
- atp_core/timeutil.py +18 -0
- atp_evals/__init__.py +20 -0
- atp_evals/__main__.py +62 -0
- atp_evals/analysis.py +454 -0
- atp_evals/harness.py +80 -0
- atp_evals/model.py +69 -0
- atp_evals/py.typed +0 -0
- atp_evals/regression/__init__.py +43 -0
- atp_evals/regression/format.py +198 -0
- atp_evals/regression/record.py +156 -0
- atp_evals/regression/runner.py +300 -0
- atp_evals/runner.py +108 -0
- atp_evals/scenarios.py +557 -0
- atp_gateway/__init__.py +25 -0
- atp_gateway/__main__.py +66 -0
- atp_gateway/api.py +330 -0
- atp_gateway/app.py +99 -0
- atp_gateway/auth.py +83 -0
- atp_gateway/evidence.py +115 -0
- atp_gateway/grants.py +277 -0
- atp_gateway/local.py +169 -0
- atp_gateway/py.typed +0 -0
- atp_gateway/reports.py +66 -0
- atp_gateway/schemas.py +116 -0
- atp_gateway/service.py +753 -0
- atp_gateway/settings.py +90 -0
- atp_gateway/tools.py +177 -0
- atp_gateway/wiring.py +140 -0
- atp_identity/__init__.py +40 -0
- atp_identity/credentials.py +254 -0
- atp_identity/grants.py +91 -0
- atp_identity/provider.py +68 -0
- atp_identity/py.typed +0 -0
- atp_identity/scope.py +121 -0
- atp_identity/service.py +299 -0
- atp_identity/store.py +127 -0
- atp_policy/__init__.py +45 -0
- atp_policy/analysis.py +182 -0
- atp_policy/base.py +111 -0
- atp_policy/declarative.py +417 -0
- atp_policy/directory.py +45 -0
- atp_policy/engine.py +66 -0
- atp_policy/explain.py +256 -0
- atp_policy/policies/__init__.py +27 -0
- atp_policy/policies/authority.py +89 -0
- atp_policy/policies/payments.py +255 -0
- atp_policy/py.typed +0 -0
- atp_policy/registry.py +119 -0
- finance_agent/__init__.py +44 -0
- finance_agent/__main__.py +86 -0
- finance_agent/agent.py +155 -0
- finance_agent/invoices.py +89 -0
- finance_agent/py.typed +0 -0
- finance_agent/runner.py +172 -0
- finance_agent/scenario.py +157 -0
- notes_mcp_server/__init__.py +81 -0
- notes_mcp_server/__main__.py +4 -0
- 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
|
+

|
|
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,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
|