taskfence 0.1.0__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.
- taskfence/__init__.py +11 -0
- taskfence/_hooks.py +32 -0
- taskfence/fence.py +200 -0
- taskfence/outcomes.py +93 -0
- taskfence/stores/__init__.py +3 -0
- taskfence/stores/postgres.py +328 -0
- taskfence/stores/sqlite.py +310 -0
- taskfence/testing/__init__.py +8 -0
- taskfence/testing/audit.py +81 -0
- taskfence/testing/latches.py +138 -0
- taskfence/testing/plugin.py +15 -0
- taskfence/tokens.py +120 -0
- taskfence-0.1.0.dist-info/METADATA +169 -0
- taskfence-0.1.0.dist-info/RECORD +18 -0
- taskfence-0.1.0.dist-info/WHEEL +5 -0
- taskfence-0.1.0.dist-info/entry_points.txt +2 -0
- taskfence-0.1.0.dist-info/licenses/LICENSE +21 -0
- taskfence-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taskfence
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Revocation-safe side effects for long-running AI agent tasks: commit-time authorization in your own database transaction.
|
|
5
|
+
Author: Muzeeb Mohammad
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mohdmuzeeb/taskfence
|
|
8
|
+
Project-URL: Documentation, https://github.com/mohdmuzeeb/taskfence/tree/main/docs
|
|
9
|
+
Project-URL: Issues, https://github.com/mohdmuzeeb/taskfence/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/mohdmuzeeb/taskfence/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai-agents,mcp,model-context-protocol,authorization,revocation,fencing-token
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Framework :: Pytest
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
25
|
+
Provides-Extra: postgres
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
|
|
27
|
+
Provides-Extra: examples
|
|
28
|
+
Requires-Dist: mcp>=2.3; extra == "examples"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TaskFence
|
|
32
|
+
|
|
33
|
+
[](https://github.com/mohdmuzeeb/taskfence/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/taskfence/)
|
|
35
|
+
[](https://github.com/mohdmuzeeb/taskfence/blob/main/LICENSE)
|
|
36
|
+
|
|
37
|
+
**Revocation-safe side effects for long-running AI agent tasks.**
|
|
38
|
+
|
|
39
|
+
If a user revokes permission while an agent task is running, TaskFence guarantees the task cannot commit its side effect afterwards. The check runs **inside your own database transaction**, at commit time, not before it.
|
|
40
|
+
|
|
41
|
+
## The problem
|
|
42
|
+
|
|
43
|
+
1. An agent task is admitted: the user has granted `refund.issue`.
|
|
44
|
+
2. The task works for 10 minutes (LLM calls, tool calls, waiting for input).
|
|
45
|
+
3. At minute 4, the user revokes the grant.
|
|
46
|
+
4. At minute 10, the task writes the refund. **Most servers allow this**, because they checked permission when the task started, or just before the write in a separate step.
|
|
47
|
+
|
|
48
|
+
The TaskFence paper measured this window. In one randomized run, designs that check at start or in a separate pre-commit step let 317 provable commits through after revocation. Checking inside the commit transaction let none through.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install taskfence # SQLite; core has no dependencies
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "taskfence[postgres]" # PostgreSQL via psycopg 3
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requires Python 3.12+. PostgreSQL 15, 16 and 17 are tested in CI.
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from taskfence import Denied, Fence, KeyRing
|
|
66
|
+
from taskfence.stores.sqlite import connect
|
|
67
|
+
|
|
68
|
+
fence = Fence(KeyRing.from_env("TASKFENCE_KEYS")) # or KeyRing.generate() to try it out
|
|
69
|
+
conn = connect("app.sqlite")
|
|
70
|
+
fence.migrate(conn)
|
|
71
|
+
conn.executescript("CREATE TABLE IF NOT EXISTS orders (id TEXT PRIMARY KEY, refunded INTEGER DEFAULT 0);"
|
|
72
|
+
"INSERT OR IGNORE INTO orders VALUES ('order:9001', 0);")
|
|
73
|
+
|
|
74
|
+
# The user grants the agent permission; a task is admitted with a signed token.
|
|
75
|
+
g = fence.grant(conn, principal="user:42", operation="refund.issue", resources=["order:9001"])
|
|
76
|
+
token = fence.issue(conn, g.grant_id, task_id="task-1")
|
|
77
|
+
|
|
78
|
+
# ... the task works for minutes; meanwhile the user may call fence.revoke(conn, g.grant_id) ...
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
with fence.transaction(conn): # do slow work BEFORE opening the transaction
|
|
82
|
+
fence.guard(conn, token, operation="refund.issue", resource="order:9001",
|
|
83
|
+
operation_id="task-1:refund")
|
|
84
|
+
conn.execute("UPDATE orders SET refunded = 1 WHERE id = 'order:9001'")
|
|
85
|
+
except Denied as e:
|
|
86
|
+
print("blocked:", e.code) # STALE_EPOCH if access was revoked meanwhile
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
[examples/01_quickstart_sqlite.py](https://github.com/mohdmuzeeb/taskfence/blob/main/examples/01_quickstart_sqlite.py) runs this with a revocation in the middle and prints `refund blocked: STALE_EPOCH`.
|
|
90
|
+
|
|
91
|
+
For PostgreSQL:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from taskfence.stores.postgres import PostgresStore, connect
|
|
95
|
+
fence = Fence(KeyRing.from_env(), PostgresStore()) # fair shared locking per grant (default)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
- **Grants have epochs.** Revoking a grant raises a fence (`min_epoch`) in your database. Regranting starts a new epoch.
|
|
101
|
+
- **Tasks carry a signed token** naming the epoch they were admitted under.
|
|
102
|
+
- **`guard()` checks the token in the same transaction as the effect.** The commit either sees the revocation, or is strictly ordered before it.
|
|
103
|
+
- **The same check also:**
|
|
104
|
+
- fences cancelled tasks
|
|
105
|
+
- makes each `operation_id` commit at most once
|
|
106
|
+
- enforces resource scope and an optional application predicate
|
|
107
|
+
- can write an outbox entry for external calls
|
|
108
|
+
|
|
109
|
+
Details: [docs/DESIGN.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/DESIGN.md) and [docs/THREAT_MODEL.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/THREAT_MODEL.md).
|
|
110
|
+
|
|
111
|
+
## What it guarantees, and what it does not
|
|
112
|
+
|
|
113
|
+
- ✅ No effect protected by `guard()` commits after revocation, or after task cancellation, has taken effect.
|
|
114
|
+
- ✅ A revocation waits for at most the guarded transactions already in flight, never for a whole task.
|
|
115
|
+
- ❌ Not retroactive: effects that committed before the revocation stay committed.
|
|
116
|
+
- ❌ External calls (payments, e-mail) are covered only as far as the outbox entry. Your downstream must be idempotent.
|
|
117
|
+
- ❌ The effect must be in the same database as TaskFence's tables. Federated multi-service mode is planned for v0.2.
|
|
118
|
+
|
|
119
|
+
Read [docs/LIMITATIONS.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/LIMITATIONS.md) before relying on it.
|
|
120
|
+
|
|
121
|
+
## Testing your own code for revocation races
|
|
122
|
+
|
|
123
|
+
`taskfence.testing` stages races deterministically with latches, and checks the database with an independent auditor:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from taskfence.testing import assert_no_violations, hook, run_in_thread
|
|
127
|
+
|
|
128
|
+
# `taskfence_latches` is a fixture that ships with taskfence; `fence`, `conn` and `grant` are yours.
|
|
129
|
+
def test_revoke_while_refund_runs(taskfence_latches, fence, conn, grant):
|
|
130
|
+
token = fence.issue(conn, grant.grant_id, task_id="t1")
|
|
131
|
+
pause = taskfence_latches.pause_at("app.before_txn") # your code calls hook("app.before_txn")
|
|
132
|
+
worker = run_in_thread(lambda: my_refund(token))
|
|
133
|
+
pause.wait_reached()
|
|
134
|
+
fence.revoke(conn, grant.grant_id) # revoke while the task is mid-flight
|
|
135
|
+
pause.release()
|
|
136
|
+
worker.join()
|
|
137
|
+
assert_no_violations(conn) # no commit after the revocation
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Using with MCP servers
|
|
141
|
+
|
|
142
|
+
[examples/03_mcp_server](https://github.com/mohdmuzeeb/taskfence/tree/main/examples/03_mcp_server) is a refund-desk MCP server whose background refunds are fenced by TaskFence. A demo drives it with the official MCP client and shows four cases:
|
|
143
|
+
- a refund revoked mid-task is blocked
|
|
144
|
+
- a refund cancelled mid-task is blocked
|
|
145
|
+
- a refund on another customer's order is blocked
|
|
146
|
+
- a refund after a fresh grant commits
|
|
147
|
+
|
|
148
|
+
The Python MCP SDK does not include the MCP Tasks extension yet, so the example uses start-then-poll tools.
|
|
149
|
+
|
|
150
|
+
## Roadmap
|
|
151
|
+
|
|
152
|
+
- **v0.2:** a federated grant authority (signed service-to-service RPC), TypeScript middleware for the MCP Tasks extension, and an async API
|
|
153
|
+
- **v0.3:** `taskfence scan`, a revocation-race checker for MCP servers
|
|
154
|
+
|
|
155
|
+
## Research and citation
|
|
156
|
+
|
|
157
|
+
TaskFence is based on peer-reviewed research:
|
|
158
|
+
|
|
159
|
+
> M. Mohammad, "TaskFence: Commit-Time Authorization and Revocation Safety for Long-Running MCP Agent Workflows," *International Conference on Intelligent Cybernetics Technology & Applications (ICICyTA 2026)*.
|
|
160
|
+
|
|
161
|
+
If you use TaskFence, please cite the paper. GitHub's "Cite this repository" button uses [CITATION.cff](https://github.com/mohdmuzeeb/taskfence/blob/main/CITATION.cff). The archived research artifact (code, preregistration, wire traces, raw databases) will be linked here once it has a DOI.
|
|
162
|
+
|
|
163
|
+
## Contributing and security
|
|
164
|
+
|
|
165
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/mohdmuzeeb/taskfence/blob/main/CONTRIBUTING.md). Report vulnerabilities privately as described in [SECURITY.md](https://github.com/mohdmuzeeb/taskfence/blob/main/SECURITY.md).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
taskfence/__init__.py,sha256=3VK2XnN0yhFV88VwFiGutf9rGhyVGiwWJ_71hSA1paE,481
|
|
2
|
+
taskfence/_hooks.py,sha256=pH3wqDChRp76UMSrVTsFMJYO20VSZnL3xWbdQBoyO0Q,844
|
|
3
|
+
taskfence/fence.py,sha256=xfFnT10YTtGy5cjqIp8l1LfI543wpwfXvM-eSOYYFlo,10315
|
|
4
|
+
taskfence/outcomes.py,sha256=yW1tEKB0DalQ1WWCTJ0J0aIJUXWScFJTBf4xAqnEaI0,2627
|
|
5
|
+
taskfence/tokens.py,sha256=79kBF07u_x6lgsg4bWBJnFsGgG3eOxLR5YyYWWNJ4fo,3873
|
|
6
|
+
taskfence/stores/__init__.py,sha256=w0J6BqRsYZmpPyIYA8SdSdO4rZ3iOqJkwuIYIj6FNuE,79
|
|
7
|
+
taskfence/stores/postgres.py,sha256=Xq8_Ssb4ffsE23bOR2ubfW9zgU7FzFxt-V_7UeMj6-M,17012
|
|
8
|
+
taskfence/stores/sqlite.py,sha256=8zMmWD2Bg02s4gM2MkFg32OpGADJAmD1XdlzrHTaiC0,15295
|
|
9
|
+
taskfence/testing/__init__.py,sha256=sPpvfMaAzfleWVjPCE-YjUIFeKeabR45v0SU_5BaoTk,410
|
|
10
|
+
taskfence/testing/audit.py,sha256=FT_cpHBwVHH7sUE0_QYp5yrfh7wV4T5KhY5oLYbiB-o,3213
|
|
11
|
+
taskfence/testing/latches.py,sha256=nilSP8VAiUC8AeS4zrkU8UbGz8iZkSsHzPFCVLvaZBE,4569
|
|
12
|
+
taskfence/testing/plugin.py,sha256=swPoFIP50NPoOW42EscodowbYiUOMND1FCgyYNAr2sI,307
|
|
13
|
+
taskfence-0.1.0.dist-info/licenses/LICENSE,sha256=htAZdhshqoCQDSpdWYVFuofP6-oq6Aa-mvDSjlmfgO4,1072
|
|
14
|
+
taskfence-0.1.0.dist-info/METADATA,sha256=wdNisgPoCqXszaCmOl51trFN0z_7o5kLbugMmyySnes,8406
|
|
15
|
+
taskfence-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
16
|
+
taskfence-0.1.0.dist-info/entry_points.txt,sha256=Rpi9VgUPq0RZkgp4EB5FPgc_zbuzexzuLeU7swJ092M,48
|
|
17
|
+
taskfence-0.1.0.dist-info/top_level.txt,sha256=S82N3a-i8vd8TaZJDA4wBXBnHpynrwOTXtKqR19JM-w,10
|
|
18
|
+
taskfence-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muzeeb Mohammad
|
|
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 @@
|
|
|
1
|
+
taskfence
|