archytan-lite 2.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,7 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.py[cod]
7
+ .pytest_cache/
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 High ArchyTech Solutions
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.
22
+
23
+ ---
24
+
25
+ This license applies only to the client library in this directory
26
+ (clients/python). The Archytan Lite gate itself — the code in this
27
+ repository's cmd/ and internal/ directories — is licensed separately; see
28
+ /LICENSE at the repository root.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: archytan-lite
3
+ Version: 2.0.0
4
+ Summary: Fail-closed client for the Archytan Lite authorization gate. Verifies the gate's Ed25519 signature and that the receipt matches the request, and redeems single-use capability tokens before acting; treats every other outcome as denied.
5
+ Project-URL: Homepage, https://github.com/High-ArchyTech-Solutions/archytan-lite
6
+ Project-URL: Repository, https://github.com/High-ArchyTech-Solutions/archytan-lite
7
+ Project-URL: Issues, https://github.com/High-ArchyTech-Solutions/archytan-lite/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-agent,archytan,audit-log,authorization,capability,ed25519,fail-closed,langchain,tamper-evident,zero-trust
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Security
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: cryptography>=42.0
18
+ Requires-Dist: httpx>=0.27
19
+ Provides-Extra: dev
20
+ Requires-Dist: langchain-core>=0.3; extra == 'dev'
21
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
22
+ Requires-Dist: pytest>=8.0; extra == 'dev'
23
+ Provides-Extra: langchain
24
+ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # archytan-lite (Python)
28
+
29
+ Fail-closed Python client for the [Archytan Lite](https://github.com/High-ArchyTech-Solutions/archytan-lite)
30
+ authorization gate.
31
+
32
+ ```sh
33
+ pip install archytan-lite
34
+ ```
35
+
36
+ ## The rule this client enforces
37
+
38
+ Only an HTTP 200 whose body has `decision == "ALLOW"`, **and** a receipt whose
39
+ Ed25519 signature verifies against the gate's public key, **and** a receipt that
40
+ describes *this* request counts as authorized.
41
+
42
+ Everything else is denied: a non-200, a timeout, a refused connection, a
43
+ malformed body, a signature that doesn't verify, a receipt for some other
44
+ action, the gate process being dead.
45
+
46
+ **Nothing here raises for a denial.** A denial arrives as `allowed=False`, never
47
+ as an exception — an exception is something a caller can forget to catch, and a
48
+ forgotten `except` around an authorization check fails open.
49
+
50
+ ## Use
51
+
52
+ ```python
53
+ from uuid import uuid4
54
+ from archytan_lite import authorize, redeem_capability
55
+
56
+ result = authorize(
57
+ url="http://archytan-lite:8421",
58
+ token=CALLER_TOKEN,
59
+ gate_public_key_hex=GATE_PUBLIC_KEY, # the hex `keygen` printed
60
+ action="invoice.delete",
61
+ actor={"uid": "user_1"}, # see "Roles" below
62
+ resource={"type": "invoice", "id": "inv_7"},
63
+ idempotency_key=str(uuid4()),
64
+ )
65
+ if not result.allowed:
66
+ raise PermissionError(result.reason)
67
+ ```
68
+
69
+ `authorize_async` and `redeem_capability_async` are the async forms. They share
70
+ their decision logic with the sync versions rather than reimplementing it — two
71
+ copies is how a sync path and an async path end up disagreeing about what
72
+ "authorized" means, with only one of them audited.
73
+
74
+ ## Roles
75
+
76
+ **Omit `actor["role"]` when the gate runs credential-bound roles.** In that mode
77
+ your role is a property of your credential, resolved server-side, and a role
78
+ asserted in the request that disagrees with it is treated as an escalation
79
+ attempt and refused. The receipt comes back carrying the role the gate
80
+ resolved.
81
+
82
+ Send `actor["role"]` only against a gate configured with a single shared token,
83
+ which has no other way to learn one. When you do send it, this client checks the
84
+ receipt agrees with it.
85
+
86
+ ## Capabilities
87
+
88
+ When the gate has capabilities enabled, an ALLOW carries a single-use grant
89
+ scoped to exactly one action on one resource, expiring in seconds. Holding it is
90
+ not permission to act — **spend it immediately before acting**:
91
+
92
+ ```python
93
+ spent = redeem_capability(
94
+ url="http://archytan-lite:8421",
95
+ token=CALLER_TOKEN,
96
+ capability_token=result.capability.token,
97
+ action="invoice.delete",
98
+ resource={"type": "invoice", "id": "inv_7"},
99
+ )
100
+ if not spent.redeemed:
101
+ raise PermissionError(spent.reason) # expired, out of scope, or already spent
102
+
103
+ delete_invoice("inv_7")
104
+ ```
105
+
106
+ A second redemption of the same capability is refused, so an actuator cannot be
107
+ driven to act twice on one authorization.
108
+
109
+ ## AI agent frameworks
110
+
111
+ ```sh
112
+ pip install "archytan-lite[langchain]"
113
+ ```
114
+
115
+ `ArchytanGuardedTool` wraps a function so an agent cannot reach it without the
116
+ gate authorizing the call and the capability being spent first:
117
+
118
+ ```python
119
+ from archytan_lite.integrations.langchain import ArchytanGuardedTool
120
+
121
+ delete_invoice_tool = ArchytanGuardedTool(
122
+ name="delete_invoice",
123
+ description="Permanently delete an invoice by id.",
124
+ action="invoice.delete",
125
+ resource_type="invoice",
126
+ func=really_delete_invoice, # reached only after ALLOW + redemption
127
+ gate_url="http://archytan-lite:8421",
128
+ caller_token=AGENT_TOKEN,
129
+ gate_public_key_hex=GATE_PUBLIC_KEY,
130
+ actor_uid="agent-invoices",
131
+ )
132
+ ```
133
+
134
+ It is a LangChain `BaseTool`, which LangGraph and CrewAI both accept, so one
135
+ integration covers all three.
136
+
137
+ A denial returns an explanation to the agent rather than raising, because a
138
+ raised exception inside an agent loop is usually swallowed and retried — the
139
+ agent should be *told* it was refused, in text it can reason about, so it stops
140
+ rather than loops.
141
+
142
+ **What this cannot do for you:** nothing stops your code from importing the
143
+ underlying function and calling it directly. The wrapper makes the guarded path
144
+ the easy one and the unguarded path a deliberate act; it is not a sandbox.
145
+
146
+ ## Contract siblings
147
+
148
+ This client, [`clients/node/index.js`](../node/index.js), and the `authorize()`
149
+ helper in `testing/failclosed/failclosed_test.go` are the same contract in three
150
+ languages. If you change the decision logic in one, change it in the others.
151
+
152
+ ## What it does not do
153
+
154
+ It verifies the signature over the `intent_hash` the gate returned. It does not
155
+ recompute that hash from the receipt's fields — doing so would require
156
+ byte-for-byte replication of Go's `encoding/json` output, including its
157
+ HTML-escaping of `<`, `>` and `&`, and getting that subtly wrong would silently
158
+ reject legitimate receipts. Confirming a stored receipt still matches its hash
159
+ is `--verify-chain`'s job, server-side, where the canonical encoding lives.
@@ -0,0 +1,133 @@
1
+ # archytan-lite (Python)
2
+
3
+ Fail-closed Python client for the [Archytan Lite](https://github.com/High-ArchyTech-Solutions/archytan-lite)
4
+ authorization gate.
5
+
6
+ ```sh
7
+ pip install archytan-lite
8
+ ```
9
+
10
+ ## The rule this client enforces
11
+
12
+ Only an HTTP 200 whose body has `decision == "ALLOW"`, **and** a receipt whose
13
+ Ed25519 signature verifies against the gate's public key, **and** a receipt that
14
+ describes *this* request counts as authorized.
15
+
16
+ Everything else is denied: a non-200, a timeout, a refused connection, a
17
+ malformed body, a signature that doesn't verify, a receipt for some other
18
+ action, the gate process being dead.
19
+
20
+ **Nothing here raises for a denial.** A denial arrives as `allowed=False`, never
21
+ as an exception — an exception is something a caller can forget to catch, and a
22
+ forgotten `except` around an authorization check fails open.
23
+
24
+ ## Use
25
+
26
+ ```python
27
+ from uuid import uuid4
28
+ from archytan_lite import authorize, redeem_capability
29
+
30
+ result = authorize(
31
+ url="http://archytan-lite:8421",
32
+ token=CALLER_TOKEN,
33
+ gate_public_key_hex=GATE_PUBLIC_KEY, # the hex `keygen` printed
34
+ action="invoice.delete",
35
+ actor={"uid": "user_1"}, # see "Roles" below
36
+ resource={"type": "invoice", "id": "inv_7"},
37
+ idempotency_key=str(uuid4()),
38
+ )
39
+ if not result.allowed:
40
+ raise PermissionError(result.reason)
41
+ ```
42
+
43
+ `authorize_async` and `redeem_capability_async` are the async forms. They share
44
+ their decision logic with the sync versions rather than reimplementing it — two
45
+ copies is how a sync path and an async path end up disagreeing about what
46
+ "authorized" means, with only one of them audited.
47
+
48
+ ## Roles
49
+
50
+ **Omit `actor["role"]` when the gate runs credential-bound roles.** In that mode
51
+ your role is a property of your credential, resolved server-side, and a role
52
+ asserted in the request that disagrees with it is treated as an escalation
53
+ attempt and refused. The receipt comes back carrying the role the gate
54
+ resolved.
55
+
56
+ Send `actor["role"]` only against a gate configured with a single shared token,
57
+ which has no other way to learn one. When you do send it, this client checks the
58
+ receipt agrees with it.
59
+
60
+ ## Capabilities
61
+
62
+ When the gate has capabilities enabled, an ALLOW carries a single-use grant
63
+ scoped to exactly one action on one resource, expiring in seconds. Holding it is
64
+ not permission to act — **spend it immediately before acting**:
65
+
66
+ ```python
67
+ spent = redeem_capability(
68
+ url="http://archytan-lite:8421",
69
+ token=CALLER_TOKEN,
70
+ capability_token=result.capability.token,
71
+ action="invoice.delete",
72
+ resource={"type": "invoice", "id": "inv_7"},
73
+ )
74
+ if not spent.redeemed:
75
+ raise PermissionError(spent.reason) # expired, out of scope, or already spent
76
+
77
+ delete_invoice("inv_7")
78
+ ```
79
+
80
+ A second redemption of the same capability is refused, so an actuator cannot be
81
+ driven to act twice on one authorization.
82
+
83
+ ## AI agent frameworks
84
+
85
+ ```sh
86
+ pip install "archytan-lite[langchain]"
87
+ ```
88
+
89
+ `ArchytanGuardedTool` wraps a function so an agent cannot reach it without the
90
+ gate authorizing the call and the capability being spent first:
91
+
92
+ ```python
93
+ from archytan_lite.integrations.langchain import ArchytanGuardedTool
94
+
95
+ delete_invoice_tool = ArchytanGuardedTool(
96
+ name="delete_invoice",
97
+ description="Permanently delete an invoice by id.",
98
+ action="invoice.delete",
99
+ resource_type="invoice",
100
+ func=really_delete_invoice, # reached only after ALLOW + redemption
101
+ gate_url="http://archytan-lite:8421",
102
+ caller_token=AGENT_TOKEN,
103
+ gate_public_key_hex=GATE_PUBLIC_KEY,
104
+ actor_uid="agent-invoices",
105
+ )
106
+ ```
107
+
108
+ It is a LangChain `BaseTool`, which LangGraph and CrewAI both accept, so one
109
+ integration covers all three.
110
+
111
+ A denial returns an explanation to the agent rather than raising, because a
112
+ raised exception inside an agent loop is usually swallowed and retried — the
113
+ agent should be *told* it was refused, in text it can reason about, so it stops
114
+ rather than loops.
115
+
116
+ **What this cannot do for you:** nothing stops your code from importing the
117
+ underlying function and calling it directly. The wrapper makes the guarded path
118
+ the easy one and the unguarded path a deliberate act; it is not a sandbox.
119
+
120
+ ## Contract siblings
121
+
122
+ This client, [`clients/node/index.js`](../node/index.js), and the `authorize()`
123
+ helper in `testing/failclosed/failclosed_test.go` are the same contract in three
124
+ languages. If you change the decision logic in one, change it in the others.
125
+
126
+ ## What it does not do
127
+
128
+ It verifies the signature over the `intent_hash` the gate returned. It does not
129
+ recompute that hash from the receipt's fields — doing so would require
130
+ byte-for-byte replication of Go's `encoding/json` output, including its
131
+ HTML-escaping of `<`, `>` and `&`, and getting that subtly wrong would silently
132
+ reject legitimate receipts. Confirming a stored receipt still matches its hash
133
+ is `--verify-chain`'s job, server-side, where the canonical encoding lives.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "archytan-lite"
7
+ version = "2.0.0"
8
+ description = "Fail-closed client for the Archytan Lite authorization gate. Verifies the gate's Ed25519 signature and that the receipt matches the request, and redeems single-use capability tokens before acting; treats every other outcome as denied."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ keywords = [
13
+ "authorization",
14
+ "fail-closed",
15
+ "ed25519",
16
+ "audit-log",
17
+ "tamper-evident",
18
+ "zero-trust",
19
+ "capability",
20
+ "ai-agent",
21
+ "langchain",
22
+ "archytan",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 5 - Production/Stable",
26
+ "Intended Audience :: Developers",
27
+ "Programming Language :: Python :: 3",
28
+ "Topic :: Security",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ ]
31
+
32
+ # Two runtime dependencies, both load-bearing. cryptography because Python has
33
+ # no Ed25519 in the standard library and hand-rolling one for a security client
34
+ # would be indefensible; httpx because it provides sync and async transports
35
+ # through a single API, and the agent frameworks this targets are async-heavy.
36
+ dependencies = [
37
+ "cryptography>=42.0",
38
+ "httpx>=0.27",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ # The agent-framework integration is an extra so the core client stays at two
43
+ # dependencies. A team calling the gate from ordinary application code should
44
+ # not inherit an agent framework to do it.
45
+ langchain = ["langchain-core>=0.3"]
46
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "langchain-core>=0.3"]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/High-ArchyTech-Solutions/archytan-lite"
50
+ Repository = "https://github.com/High-ArchyTech-Solutions/archytan-lite"
51
+ Issues = "https://github.com/High-ArchyTech-Solutions/archytan-lite/issues"
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/archytan_lite"]
55
+
56
+ [tool.pytest.ini_options]
57
+ asyncio_mode = "auto"
58
+ testpaths = ["tests"]
@@ -0,0 +1,61 @@
1
+ """archytan-lite — fail-closed Python client for the Archytan Lite authorization gate.
2
+
3
+ from archytan_lite import authorize, redeem_capability
4
+
5
+ result = authorize(
6
+ url="http://archytan-lite:8421",
7
+ token=CALLER_TOKEN,
8
+ gate_public_key_hex=GATE_PUBLIC_KEY,
9
+ action="invoice.delete",
10
+ actor={"uid": "user_1"}, # omit "role" against a bound-mode gate
11
+ resource={"type": "invoice", "id": "inv_7"},
12
+ idempotency_key=str(uuid4()),
13
+ )
14
+ if not result.allowed:
15
+ raise PermissionError(result.reason)
16
+
17
+ spent = redeem_capability(
18
+ url="http://archytan-lite:8421",
19
+ token=CALLER_TOKEN,
20
+ capability_token=result.capability.token,
21
+ action="invoice.delete",
22
+ resource={"type": "invoice", "id": "inv_7"},
23
+ )
24
+ if not spent.redeemed:
25
+ raise PermissionError(spent.reason)
26
+
27
+ delete_invoice("inv_7")
28
+
29
+ For agent frameworks, ``archytan_lite.integrations.langchain.ArchytanGuardedTool``
30
+ wraps a function so that sequence runs on every call and the function cannot be
31
+ reached without it.
32
+ """
33
+
34
+ from .client import (
35
+ RECEIPT_TAG,
36
+ AuthorizeResult,
37
+ Capability,
38
+ RedeemResult,
39
+ authorize,
40
+ authorize_async,
41
+ receipt_mismatch,
42
+ redeem_capability,
43
+ redeem_capability_async,
44
+ verify_receipt_signature,
45
+ )
46
+
47
+ __version__ = "2.0.0"
48
+
49
+ __all__ = [
50
+ "AuthorizeResult",
51
+ "Capability",
52
+ "RedeemResult",
53
+ "authorize",
54
+ "authorize_async",
55
+ "redeem_capability",
56
+ "redeem_capability_async",
57
+ "verify_receipt_signature",
58
+ "receipt_mismatch",
59
+ "RECEIPT_TAG",
60
+ "__version__",
61
+ ]