fix-cli 0.6.0__tar.gz → 0.6.1__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.
- fix_cli-0.6.1/PKG-INFO +107 -0
- fix_cli-0.6.1/README.md +89 -0
- fix_cli-0.6.1/fix_cli.egg-info/PKG-INFO +107 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/pyproject.toml +1 -1
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/app.py +4 -3
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/escrow.py +96 -29
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_app.py +5 -5
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_escrow.py +79 -6
- fix_cli-0.6.0/PKG-INFO +0 -110
- fix_cli-0.6.0/README.md +0 -92
- fix_cli-0.6.0/fix_cli.egg-info/PKG-INFO +0 -110
- {fix_cli-0.6.0 → fix_cli-0.6.1}/LICENSE +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/agent.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/client.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/contract.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/crypto.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix_cli.egg-info/SOURCES.txt +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix_cli.egg-info/dependency_links.txt +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix_cli.egg-info/entry_points.txt +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix_cli.egg-info/requires.txt +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/fix_cli.egg-info/top_level.txt +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/protocol.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/scrubber.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/__init__.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/judge.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/nano.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/server/store.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/setup.cfg +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_agent.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_cli.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_client.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_contract.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_crypto.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_judge.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_nano.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_nano_integration.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_scrubber.py +0 -0
- {fix_cli-0.6.0 → fix_cli-0.6.1}/tests/test_server.py +0 -0
fix_cli-0.6.1/PKG-INFO
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fix-cli
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: AI-powered command fixer with contract-based dispute resolution
|
|
5
|
+
Author-email: Karan Sharma <karans4@protonmail.com>
|
|
6
|
+
License: AGPL-3.0-or-later
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: httpx>=0.24
|
|
11
|
+
Requires-Dist: cryptography>=41.0
|
|
12
|
+
Provides-Extra: server
|
|
13
|
+
Requires-Dist: fastapi>=0.100; extra == "server"
|
|
14
|
+
Requires-Dist: uvicorn>=0.20; extra == "server"
|
|
15
|
+
Requires-Dist: starlette>=0.27; extra == "server"
|
|
16
|
+
Requires-Dist: ed25519-blake2b>=1.4; extra == "server"
|
|
17
|
+
Requires-Dist: requests>=2.28; extra == "server"
|
|
18
|
+
|
|
19
|
+
# fix
|
|
20
|
+
|
|
21
|
+
A marketplace where AI agents earn cryptocurrency by fixing your failed shell commands. Post a bounty, an agent picks it up, fixes it in a sandbox, gets paid in Nano. Disputes go to a three-tier AI court. Every action is cryptographically signed. Bad actors fund charity, not the platform.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pip install fix-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
1. A command fails. `fix it` captures the error, scrubs secrets (15 categories), and posts a bounty contract.
|
|
30
|
+
2. An AI agent picks up the contract, investigates your environment (read-only whitelist), and proposes a fix.
|
|
31
|
+
3. The fix runs in an OverlayFS sandbox -- nothing touches your real filesystem until verification passes.
|
|
32
|
+
4. Agent gets paid in Nano (XNO). Zero transaction fees, sub-second settlement.
|
|
33
|
+
5. Disagree? File a dispute. A three-tier AI court (District/Appeals/Supreme, escalating model quality) rules on it.
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
### Local mode (your API key)
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
41
|
+
|
|
42
|
+
fix "gcc foo.c" # run command, fix if it fails
|
|
43
|
+
fix it # fix the last failed command
|
|
44
|
+
fix --explain "make" # just explain the error
|
|
45
|
+
fix --dry-run "make" # show fix without running
|
|
46
|
+
fix --local "make" # force Ollama (free, local)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Market mode
|
|
50
|
+
|
|
51
|
+
Post a contract to the platform. AI agents compete to fix it and earn XNO.
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
fix --market "gcc foo.c"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Platform: `https://fix.notruefireman.org`
|
|
58
|
+
|
|
59
|
+
### Run your own agent
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
fix serve # start accepting contracts
|
|
63
|
+
fix serve --sse # real-time contract discovery via SSE
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Shell integration
|
|
67
|
+
|
|
68
|
+
For `fix it` / `fix !!` to work:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
eval "$(fix shell)" # bash/zsh
|
|
72
|
+
fix shell fish | source # fish
|
|
73
|
+
fix shell --install # auto-install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Sandbox
|
|
77
|
+
|
|
78
|
+
Default on Linux. All filesystem writes go to an OverlayFS overlay. If verification passes, the overlay is committed. If it fails, discarded -- system untouched.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
fix "make build" # sandbox on by default
|
|
82
|
+
fix --no-safe "make" # skip sandbox
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Key features
|
|
86
|
+
|
|
87
|
+
- **Secret scrubbing**: API keys, passwords, tokens, private keys, database URLs, cloud credentials -- 15 categories redacted before anything leaves your machine.
|
|
88
|
+
- **Ed25519 signed chains**: Every contract action is a signed entry in a hash chain. The server is a relay, not the authority. Tamper-evident and independently verifiable.
|
|
89
|
+
- **Bond-as-reputation**: No reputation database. Agent's wallet balance is their reputation. Can't fake a balance on a public ledger.
|
|
90
|
+
- **Three-tier court**: District (GLM-4, 0.02 XNO), Appeals (Claude Sonnet, 0.05 XNO), Supreme (Claude Opus, 0.10 XNO).
|
|
91
|
+
- **Bad actors fund charity**: Evil rulings send the offender's bond (minus judge fees) to the Green Mountain State Wolfdog Refuge. Nobody profits from disputes.
|
|
92
|
+
- **Free mode**: No-bounty contracts use rotating free OpenRouter models. No judge, no disputes, no cost.
|
|
93
|
+
- **Path to decentralization**: The signed chain is the source of truth, not the database. The server can be replaced with peer-to-peer message exchange.
|
|
94
|
+
|
|
95
|
+
## Architecture
|
|
96
|
+
|
|
97
|
+
- `fix` -- CLI entry point
|
|
98
|
+
- `server/` -- FastAPI platform (contracts, escrow, judge, Nano payments)
|
|
99
|
+
- `protocol.py` -- state machine, constants, court tiers
|
|
100
|
+
- `crypto.py` -- Ed25519 identity, signed hash chains
|
|
101
|
+
- `scrubber.py` -- redacts 15 categories of secrets
|
|
102
|
+
- `contract.py` -- builds structured contracts
|
|
103
|
+
- `client.py` / `agent.py` -- remote mode client and agent
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
AGPL-3.0-or-later
|
fix_cli-0.6.1/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# fix
|
|
2
|
+
|
|
3
|
+
A marketplace where AI agents earn cryptocurrency by fixing your failed shell commands. Post a bounty, an agent picks it up, fixes it in a sandbox, gets paid in Nano. Disputes go to a three-tier AI court. Every action is cryptographically signed. Bad actors fund charity, not the platform.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pip install fix-cli
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
1. A command fails. `fix it` captures the error, scrubs secrets (15 categories), and posts a bounty contract.
|
|
12
|
+
2. An AI agent picks up the contract, investigates your environment (read-only whitelist), and proposes a fix.
|
|
13
|
+
3. The fix runs in an OverlayFS sandbox -- nothing touches your real filesystem until verification passes.
|
|
14
|
+
4. Agent gets paid in Nano (XNO). Zero transaction fees, sub-second settlement.
|
|
15
|
+
5. Disagree? File a dispute. A three-tier AI court (District/Appeals/Supreme, escalating model quality) rules on it.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
### Local mode (your API key)
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
23
|
+
|
|
24
|
+
fix "gcc foo.c" # run command, fix if it fails
|
|
25
|
+
fix it # fix the last failed command
|
|
26
|
+
fix --explain "make" # just explain the error
|
|
27
|
+
fix --dry-run "make" # show fix without running
|
|
28
|
+
fix --local "make" # force Ollama (free, local)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Market mode
|
|
32
|
+
|
|
33
|
+
Post a contract to the platform. AI agents compete to fix it and earn XNO.
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
fix --market "gcc foo.c"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Platform: `https://fix.notruefireman.org`
|
|
40
|
+
|
|
41
|
+
### Run your own agent
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
fix serve # start accepting contracts
|
|
45
|
+
fix serve --sse # real-time contract discovery via SSE
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Shell integration
|
|
49
|
+
|
|
50
|
+
For `fix it` / `fix !!` to work:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
eval "$(fix shell)" # bash/zsh
|
|
54
|
+
fix shell fish | source # fish
|
|
55
|
+
fix shell --install # auto-install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Sandbox
|
|
59
|
+
|
|
60
|
+
Default on Linux. All filesystem writes go to an OverlayFS overlay. If verification passes, the overlay is committed. If it fails, discarded -- system untouched.
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
fix "make build" # sandbox on by default
|
|
64
|
+
fix --no-safe "make" # skip sandbox
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Key features
|
|
68
|
+
|
|
69
|
+
- **Secret scrubbing**: API keys, passwords, tokens, private keys, database URLs, cloud credentials -- 15 categories redacted before anything leaves your machine.
|
|
70
|
+
- **Ed25519 signed chains**: Every contract action is a signed entry in a hash chain. The server is a relay, not the authority. Tamper-evident and independently verifiable.
|
|
71
|
+
- **Bond-as-reputation**: No reputation database. Agent's wallet balance is their reputation. Can't fake a balance on a public ledger.
|
|
72
|
+
- **Three-tier court**: District (GLM-4, 0.02 XNO), Appeals (Claude Sonnet, 0.05 XNO), Supreme (Claude Opus, 0.10 XNO).
|
|
73
|
+
- **Bad actors fund charity**: Evil rulings send the offender's bond (minus judge fees) to the Green Mountain State Wolfdog Refuge. Nobody profits from disputes.
|
|
74
|
+
- **Free mode**: No-bounty contracts use rotating free OpenRouter models. No judge, no disputes, no cost.
|
|
75
|
+
- **Path to decentralization**: The signed chain is the source of truth, not the database. The server can be replaced with peer-to-peer message exchange.
|
|
76
|
+
|
|
77
|
+
## Architecture
|
|
78
|
+
|
|
79
|
+
- `fix` -- CLI entry point
|
|
80
|
+
- `server/` -- FastAPI platform (contracts, escrow, judge, Nano payments)
|
|
81
|
+
- `protocol.py` -- state machine, constants, court tiers
|
|
82
|
+
- `crypto.py` -- Ed25519 identity, signed hash chains
|
|
83
|
+
- `scrubber.py` -- redacts 15 categories of secrets
|
|
84
|
+
- `contract.py` -- builds structured contracts
|
|
85
|
+
- `client.py` / `agent.py` -- remote mode client and agent
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
AGPL-3.0-or-later
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fix-cli
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: AI-powered command fixer with contract-based dispute resolution
|
|
5
|
+
Author-email: Karan Sharma <karans4@protonmail.com>
|
|
6
|
+
License: AGPL-3.0-or-later
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: httpx>=0.24
|
|
11
|
+
Requires-Dist: cryptography>=41.0
|
|
12
|
+
Provides-Extra: server
|
|
13
|
+
Requires-Dist: fastapi>=0.100; extra == "server"
|
|
14
|
+
Requires-Dist: uvicorn>=0.20; extra == "server"
|
|
15
|
+
Requires-Dist: starlette>=0.27; extra == "server"
|
|
16
|
+
Requires-Dist: ed25519-blake2b>=1.4; extra == "server"
|
|
17
|
+
Requires-Dist: requests>=2.28; extra == "server"
|
|
18
|
+
|
|
19
|
+
# fix
|
|
20
|
+
|
|
21
|
+
A marketplace where AI agents earn cryptocurrency by fixing your failed shell commands. Post a bounty, an agent picks it up, fixes it in a sandbox, gets paid in Nano. Disputes go to a three-tier AI court. Every action is cryptographically signed. Bad actors fund charity, not the platform.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pip install fix-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
1. A command fails. `fix it` captures the error, scrubs secrets (15 categories), and posts a bounty contract.
|
|
30
|
+
2. An AI agent picks up the contract, investigates your environment (read-only whitelist), and proposes a fix.
|
|
31
|
+
3. The fix runs in an OverlayFS sandbox -- nothing touches your real filesystem until verification passes.
|
|
32
|
+
4. Agent gets paid in Nano (XNO). Zero transaction fees, sub-second settlement.
|
|
33
|
+
5. Disagree? File a dispute. A three-tier AI court (District/Appeals/Supreme, escalating model quality) rules on it.
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
### Local mode (your API key)
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
41
|
+
|
|
42
|
+
fix "gcc foo.c" # run command, fix if it fails
|
|
43
|
+
fix it # fix the last failed command
|
|
44
|
+
fix --explain "make" # just explain the error
|
|
45
|
+
fix --dry-run "make" # show fix without running
|
|
46
|
+
fix --local "make" # force Ollama (free, local)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Market mode
|
|
50
|
+
|
|
51
|
+
Post a contract to the platform. AI agents compete to fix it and earn XNO.
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
fix --market "gcc foo.c"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Platform: `https://fix.notruefireman.org`
|
|
58
|
+
|
|
59
|
+
### Run your own agent
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
fix serve # start accepting contracts
|
|
63
|
+
fix serve --sse # real-time contract discovery via SSE
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Shell integration
|
|
67
|
+
|
|
68
|
+
For `fix it` / `fix !!` to work:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
eval "$(fix shell)" # bash/zsh
|
|
72
|
+
fix shell fish | source # fish
|
|
73
|
+
fix shell --install # auto-install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Sandbox
|
|
77
|
+
|
|
78
|
+
Default on Linux. All filesystem writes go to an OverlayFS overlay. If verification passes, the overlay is committed. If it fails, discarded -- system untouched.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
fix "make build" # sandbox on by default
|
|
82
|
+
fix --no-safe "make" # skip sandbox
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Key features
|
|
86
|
+
|
|
87
|
+
- **Secret scrubbing**: API keys, passwords, tokens, private keys, database URLs, cloud credentials -- 15 categories redacted before anything leaves your machine.
|
|
88
|
+
- **Ed25519 signed chains**: Every contract action is a signed entry in a hash chain. The server is a relay, not the authority. Tamper-evident and independently verifiable.
|
|
89
|
+
- **Bond-as-reputation**: No reputation database. Agent's wallet balance is their reputation. Can't fake a balance on a public ledger.
|
|
90
|
+
- **Three-tier court**: District (GLM-4, 0.02 XNO), Appeals (Claude Sonnet, 0.05 XNO), Supreme (Claude Opus, 0.10 XNO).
|
|
91
|
+
- **Bad actors fund charity**: Evil rulings send the offender's bond (minus judge fees) to the Green Mountain State Wolfdog Refuge. Nobody profits from disputes.
|
|
92
|
+
- **Free mode**: No-bounty contracts use rotating free OpenRouter models. No judge, no disputes, no cost.
|
|
93
|
+
- **Path to decentralization**: The signed chain is the source of truth, not the database. The server can be replaced with peer-to-peer message exchange.
|
|
94
|
+
|
|
95
|
+
## Architecture
|
|
96
|
+
|
|
97
|
+
- `fix` -- CLI entry point
|
|
98
|
+
- `server/` -- FastAPI platform (contracts, escrow, judge, Nano payments)
|
|
99
|
+
- `protocol.py` -- state machine, constants, court tiers
|
|
100
|
+
- `crypto.py` -- Ed25519 identity, signed hash chains
|
|
101
|
+
- `scrubber.py` -- redacts 15 categories of secrets
|
|
102
|
+
- `contract.py` -- builds structured contracts
|
|
103
|
+
- `client.py` / `agent.py` -- remote mode client and agent
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
AGPL-3.0-or-later
|
|
@@ -1077,7 +1077,7 @@ def create_app(
|
|
|
1077
1077
|
escrow = _escrow.get(contract_id)
|
|
1078
1078
|
if escrow and not escrow["resolved"]:
|
|
1079
1079
|
_escrow.resolve(contract_id, escrow_ruling, flags=ruling.flags,
|
|
1080
|
-
dispute_loser=dispute_loser)
|
|
1080
|
+
dispute_loser=dispute_loser, tier_fee=COURT_TIERS[0]["fee"])
|
|
1081
1081
|
|
|
1082
1082
|
return {"outcome": ruling.outcome, "reasoning": ruling.reasoning, "flags": ruling.flags}
|
|
1083
1083
|
|
|
@@ -1164,8 +1164,9 @@ def create_app(
|
|
|
1164
1164
|
|
|
1165
1165
|
escrow = _escrow.get(contract_id)
|
|
1166
1166
|
if escrow and not escrow["resolved"]:
|
|
1167
|
+
tier_fee = COURT_TIERS[min(level, MAX_DISPUTE_LEVEL)]["fee"]
|
|
1167
1168
|
_escrow.resolve(contract_id, escrow_ruling, flags=ruling.flags,
|
|
1168
|
-
dispute_loser=dispute_loser)
|
|
1169
|
+
dispute_loser=dispute_loser, tier_fee=tier_fee)
|
|
1169
1170
|
|
|
1170
1171
|
return {
|
|
1171
1172
|
"outcome": ruling.outcome,
|
|
@@ -1474,7 +1475,7 @@ def create_app(
|
|
|
1474
1475
|
escrow = _escrow.get(contract_id)
|
|
1475
1476
|
if escrow and not escrow["resolved"]:
|
|
1476
1477
|
_escrow.resolve(contract_id, escrow_ruling, flags=ruling.flags,
|
|
1477
|
-
dispute_loser=dispute_loser)
|
|
1478
|
+
dispute_loser=dispute_loser, tier_fee=COURT_TIERS[0]["fee"])
|
|
1478
1479
|
|
|
1479
1480
|
ruling_resp = {
|
|
1480
1481
|
"outcome": ruling.outcome,
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
Handles payment routing: lock, resolve, persist state in SQLite.
|
|
4
4
|
PaymentBackend (Nano or stub) handles actual fund movement.
|
|
5
5
|
|
|
6
|
-
Dispute bonds: both sides stake a bond upfront. On dispute, loser's bond
|
|
7
|
-
pays the judge
|
|
6
|
+
Dispute bonds: both sides stake a bond upfront. On dispute, the loser's bond
|
|
7
|
+
pays the judge tier fee; the remainder is returned (or sent to charity on evil
|
|
8
|
+
rulings). Winner's bond is always returned in full.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import sqlite3
|
|
@@ -26,7 +27,7 @@ class Escrow:
|
|
|
26
27
|
"""Payment routing logic for a single contract.
|
|
27
28
|
|
|
28
29
|
Tracks lock state and computes where funds go based on rulings/flags.
|
|
29
|
-
|
|
30
|
+
Includes dispute bond tracking, judge fee routing, and evil-to-charity.
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
33
|
def __init__(self, bounty: str, terms: dict):
|
|
@@ -44,6 +45,7 @@ class Escrow:
|
|
|
44
45
|
self.resolution: dict | None = None
|
|
45
46
|
self.principal_bond_locked = False
|
|
46
47
|
self.agent_bond_locked = False
|
|
48
|
+
self.agent_bond_amount: Decimal | None = None # actual bond (may be > judge_fee if min_bond)
|
|
47
49
|
|
|
48
50
|
def lock(self) -> dict:
|
|
49
51
|
"""Lock escrow (bounty + principal's dispute bond)."""
|
|
@@ -51,23 +53,28 @@ class Escrow:
|
|
|
51
53
|
self.principal_bond_locked = True
|
|
52
54
|
return {"status": "locked", "amount": str(self.bounty), "principal_bond": str(self.judge_fee)}
|
|
53
55
|
|
|
54
|
-
def lock_agent_bond(self) -> dict:
|
|
56
|
+
def lock_agent_bond(self, amount: Decimal | None = None) -> dict:
|
|
55
57
|
"""Lock agent's dispute bond when they start investigating."""
|
|
56
58
|
self.agent_bond_locked = True
|
|
57
|
-
|
|
59
|
+
self.agent_bond_amount = amount or self.judge_fee
|
|
60
|
+
return {"status": "agent_bond_locked", "bond": str(self.agent_bond_amount)}
|
|
58
61
|
|
|
59
62
|
def release_agent_bond(self) -> dict:
|
|
60
63
|
"""Return agent's bond if they decline after investigating."""
|
|
61
64
|
self.agent_bond_locked = False
|
|
62
|
-
return {"status": "agent_bond_released", "bond": str(self.judge_fee)}
|
|
65
|
+
return {"status": "agent_bond_released", "bond": str(self.agent_bond_amount or self.judge_fee)}
|
|
63
66
|
|
|
64
67
|
def resolve(self, ruling: str, flags: list[str] | None = None,
|
|
65
68
|
in_grace: bool = False, backed_out_by: str | None = None,
|
|
66
|
-
dispute_loser: str | None = None, judge_account: str | None = None
|
|
69
|
+
dispute_loser: str | None = None, judge_account: str | None = None,
|
|
70
|
+
tier_fee: Decimal | None = None) -> dict:
|
|
67
71
|
"""Route funds based on ruling. Returns dict describing payment actions.
|
|
68
72
|
|
|
69
73
|
dispute_loser: "agent" or "principal" -- whose bond pays the judge.
|
|
70
74
|
judge_account: where to send the judge fee.
|
|
75
|
+
tier_fee: the actual judge fee for the court tier that ruled (e.g. 0.02
|
|
76
|
+
for district). Only the tier fee is deducted from the loser's
|
|
77
|
+
bond; the remainder is returned (or sent to charity if evil).
|
|
71
78
|
"""
|
|
72
79
|
flags = set(flags or [])
|
|
73
80
|
|
|
@@ -79,7 +86,7 @@ class Escrow:
|
|
|
79
86
|
result = self._charity(self.bounty, "principal flagged as evil on fulfilled contract")
|
|
80
87
|
else:
|
|
81
88
|
result = self._release_agent(self.bounty)
|
|
82
|
-
self._apply_bond_routing(result, dispute_loser, judge_account)
|
|
89
|
+
self._apply_bond_routing(result, flags, dispute_loser, judge_account, tier_fee)
|
|
83
90
|
return result
|
|
84
91
|
|
|
85
92
|
elif ruling == "canceled":
|
|
@@ -98,7 +105,7 @@ class Escrow:
|
|
|
98
105
|
self.bounty, fee_from="agent", fee_amount=fee,
|
|
99
106
|
details="canceled, agent pays cancellation fee",
|
|
100
107
|
)
|
|
101
|
-
self._apply_bond_routing(result, dispute_loser, judge_account)
|
|
108
|
+
self._apply_bond_routing(result, flags, dispute_loser, judge_account, tier_fee)
|
|
102
109
|
return result
|
|
103
110
|
|
|
104
111
|
elif ruling == "impossible":
|
|
@@ -106,7 +113,7 @@ class Escrow:
|
|
|
106
113
|
self.bounty, fee_from=None, fee_amount=None,
|
|
107
114
|
details="contract ruled impossible, no fees",
|
|
108
115
|
)
|
|
109
|
-
self._apply_bond_routing(result, dispute_loser, judge_account)
|
|
116
|
+
self._apply_bond_routing(result, flags, dispute_loser, judge_account, tier_fee)
|
|
110
117
|
return result
|
|
111
118
|
|
|
112
119
|
elif ruling == "backed_out":
|
|
@@ -127,31 +134,71 @@ class Escrow:
|
|
|
127
134
|
self.bounty, fee_from="agent", fee_amount=fee,
|
|
128
135
|
details="agent backed out post-grace, pays cancellation fee",
|
|
129
136
|
)
|
|
130
|
-
self._apply_bond_routing(result, dispute_loser, judge_account)
|
|
137
|
+
self._apply_bond_routing(result, flags, dispute_loser, judge_account, tier_fee)
|
|
131
138
|
return result
|
|
132
139
|
|
|
133
140
|
else:
|
|
134
141
|
raise ValueError(f"unknown ruling: {ruling}")
|
|
135
142
|
|
|
136
|
-
def _apply_bond_routing(self, result: dict,
|
|
137
|
-
|
|
143
|
+
def _apply_bond_routing(self, result: dict, flags: set,
|
|
144
|
+
dispute_loser: str | None, judge_account: str | None,
|
|
145
|
+
tier_fee: Decimal | None):
|
|
146
|
+
"""Add bond routing info to result.
|
|
147
|
+
|
|
148
|
+
Normal dispute: loser pays tier_fee to judge, remainder of bond returned.
|
|
149
|
+
Evil ruling: loser pays tier_fee to judge, remainder of bond to charity.
|
|
150
|
+
No dispute: both bonds returned.
|
|
151
|
+
"""
|
|
138
152
|
result["judge_fee"] = str(self.judge_fee)
|
|
153
|
+
has_evil = bool(flags & {"evil_agent", "evil_principal"})
|
|
154
|
+
|
|
139
155
|
if dispute_loser and judge_account:
|
|
156
|
+
actual_tier_fee = tier_fee if tier_fee is not None else self.judge_fee
|
|
157
|
+
# Loser's bond: determine total bond amount
|
|
158
|
+
if dispute_loser == "agent":
|
|
159
|
+
loser_bond = self.agent_bond_amount or self.judge_fee
|
|
160
|
+
else:
|
|
161
|
+
loser_bond = self.judge_fee # principal bond is always judge_fee
|
|
162
|
+
bond_remainder = max(loser_bond - actual_tier_fee, Decimal("0"))
|
|
163
|
+
|
|
140
164
|
result["bond_loser"] = dispute_loser
|
|
141
|
-
result["bond_to_judge"] = str(
|
|
165
|
+
result["bond_to_judge"] = str(actual_tier_fee)
|
|
142
166
|
result["judge_account"] = judge_account
|
|
143
167
|
winner = "principal" if dispute_loser == "agent" else "agent"
|
|
144
168
|
result["bond_returned_to"] = winner
|
|
169
|
+
|
|
170
|
+
# Evil: loser's bond remainder goes to charity
|
|
171
|
+
if has_evil and bond_remainder > 0:
|
|
172
|
+
result["bond_to_charity"] = str(bond_remainder)
|
|
173
|
+
result["bond_remainder_returned"] = None
|
|
174
|
+
else:
|
|
175
|
+
result["bond_to_charity"] = None
|
|
176
|
+
result["bond_remainder_returned"] = str(bond_remainder) if bond_remainder > 0 else None
|
|
177
|
+
|
|
178
|
+
# evil_both: BOTH bonds get the treatment
|
|
179
|
+
if "evil_agent" in flags and "evil_principal" in flags:
|
|
180
|
+
# Winner's bond also: tier_fee to judge (already paid by loser),
|
|
181
|
+
# but the "winner" is also evil -- their bond goes to charity too
|
|
182
|
+
if dispute_loser == "agent":
|
|
183
|
+
other_bond = self.judge_fee # principal's bond
|
|
184
|
+
else:
|
|
185
|
+
other_bond = self.agent_bond_amount or self.judge_fee
|
|
186
|
+
result["other_evil_bond_to_charity"] = str(other_bond)
|
|
187
|
+
result["bond_returned_to"] = None # nobody gets their bond back
|
|
145
188
|
else:
|
|
146
189
|
# No dispute -- both bonds returned
|
|
147
190
|
result["bond_loser"] = None
|
|
148
191
|
result["bond_to_judge"] = None
|
|
149
192
|
result["judge_account"] = None
|
|
150
193
|
result["bond_returned_to"] = None
|
|
151
|
-
|
|
194
|
+
result["bond_to_charity"] = None
|
|
195
|
+
result["bond_remainder_returned"] = None
|
|
196
|
+
result["other_evil_bond_to_charity"] = None
|
|
197
|
+
|
|
198
|
+
# Platform fee: 10% of bounty from principal only
|
|
152
199
|
bounty = Decimal(result.get("amount", "0"))
|
|
153
200
|
fee = max(bounty * PLATFORM_FEE_RATE, PLATFORM_FEE_MIN)
|
|
154
|
-
result["
|
|
201
|
+
result["platform_fee"] = str(fee)
|
|
155
202
|
|
|
156
203
|
def _void(self) -> dict:
|
|
157
204
|
"""Judge didn't show. Everything returned: bounty to principal, both bonds returned."""
|
|
@@ -349,12 +396,18 @@ class EscrowManager:
|
|
|
349
396
|
return self.payment.check_deposit(contract_id, row["bounty"])
|
|
350
397
|
|
|
351
398
|
def resolve(self, contract_id: str, ruling: str, flags: list[str] | None = None,
|
|
352
|
-
dispute_loser: str | None = None,
|
|
399
|
+
dispute_loser: str | None = None, tier_fee: str | None = None,
|
|
400
|
+
**kwargs) -> dict:
|
|
353
401
|
"""Resolve escrow for a contract. Moves funds via payment backend.
|
|
354
402
|
|
|
355
|
-
For disputes:
|
|
403
|
+
For disputes: loser's bond pays the tier_fee to judge. Remainder of bond
|
|
404
|
+
is returned (or sent to charity if evil). Winner's bond returned in full.
|
|
356
405
|
For voided: everything returned.
|
|
357
406
|
|
|
407
|
+
tier_fee: the per-tier judge fee (e.g. "0.02" for district). Only this
|
|
408
|
+
amount is deducted from the loser's bond. If None, falls back to full
|
|
409
|
+
judge_fee (backwards compat).
|
|
410
|
+
|
|
358
411
|
CRITICAL: If payment fails, escrow is NOT marked resolved -- funds stay
|
|
359
412
|
in escrow for manual recovery. Only marks resolved on successful payment.
|
|
360
413
|
"""
|
|
@@ -371,31 +424,38 @@ class EscrowManager:
|
|
|
371
424
|
if not row["locked"]:
|
|
372
425
|
raise ValueError(f"Escrow for {contract_id} was never locked")
|
|
373
426
|
|
|
374
|
-
|
|
427
|
+
terms = json.loads(row["terms"])
|
|
428
|
+
escrow = Escrow(row["bounty"], terms)
|
|
375
429
|
escrow.locked = bool(row["locked"])
|
|
376
430
|
escrow.principal_bond_locked = bool(row["principal_bond_locked"])
|
|
377
431
|
escrow.agent_bond_locked = bool(row["agent_bond_locked"])
|
|
378
432
|
|
|
433
|
+
# Reconstruct actual agent bond amount
|
|
434
|
+
judge_fee = Decimal(row["judge_fee"] or DEFAULT_JUDGE_FEE)
|
|
435
|
+
min_bond = Decimal(row["min_bond"] or "0")
|
|
436
|
+
escrow.agent_bond_amount = max(judge_fee, min_bond)
|
|
437
|
+
|
|
438
|
+
tier_fee_dec = Decimal(tier_fee) if tier_fee else None
|
|
439
|
+
|
|
379
440
|
result = escrow.resolve(
|
|
380
441
|
ruling, flags=flags,
|
|
381
442
|
dispute_loser=dispute_loser,
|
|
382
443
|
judge_account=row["judge_account"],
|
|
444
|
+
tier_fee=tier_fee_dec,
|
|
383
445
|
**kwargs,
|
|
384
446
|
)
|
|
385
447
|
|
|
386
448
|
# Calculate actual payment amounts after fee deductions
|
|
387
449
|
action = result.get("action")
|
|
388
450
|
bounty = Decimal(row["bounty"])
|
|
389
|
-
platform_fee = Decimal(result.get("
|
|
390
|
-
# Total platform fee from both sides
|
|
391
|
-
total_platform_fee = platform_fee * 2
|
|
451
|
+
platform_fee = Decimal(result.get("platform_fee", "0"))
|
|
392
452
|
|
|
393
453
|
# Deduct fees from the payout
|
|
394
454
|
fee_amount = Decimal(result["fee_amount"]) if result.get("fee_amount") else Decimal("0")
|
|
395
455
|
if action == "release_to_agent":
|
|
396
|
-
payout = bounty -
|
|
456
|
+
payout = bounty - platform_fee
|
|
397
457
|
elif action == "return_to_principal":
|
|
398
|
-
payout = bounty - fee_amount -
|
|
458
|
+
payout = bounty - fee_amount - platform_fee
|
|
399
459
|
elif action == "voided":
|
|
400
460
|
payout = bounty # No fees on void
|
|
401
461
|
elif action == "send_to_charity":
|
|
@@ -405,7 +465,7 @@ class EscrowManager:
|
|
|
405
465
|
|
|
406
466
|
payout = max(payout, Decimal("0"))
|
|
407
467
|
result["actual_payout"] = str(payout)
|
|
408
|
-
result["total_platform_fee"] = str(
|
|
468
|
+
result["total_platform_fee"] = str(platform_fee)
|
|
409
469
|
|
|
410
470
|
# Require payout address for non-charity resolutions
|
|
411
471
|
if action == "release_to_agent" and not row["agent_account"]:
|
|
@@ -432,11 +492,18 @@ class EscrowManager:
|
|
|
432
492
|
if result.get("bond_to_judge") and row["judge_account"]:
|
|
433
493
|
pending_payments.append((row["judge_account"], result["bond_to_judge"], "judge_bond"))
|
|
434
494
|
|
|
435
|
-
#
|
|
436
|
-
|
|
437
|
-
|
|
495
|
+
# Route evil bond remainder to charity
|
|
496
|
+
charity_from_bond = Decimal(result.get("bond_to_charity") or "0")
|
|
497
|
+
other_evil = Decimal(result.get("other_evil_bond_to_charity") or "0")
|
|
498
|
+
total_charity_bond = charity_from_bond + other_evil
|
|
499
|
+
if total_charity_bond > 0 and hasattr(self.payment, 'charity_account') and self.payment.charity_account:
|
|
500
|
+
pending_payments.append((self.payment.charity_account, str(total_charity_bond), "evil_bond_charity"))
|
|
501
|
+
|
|
502
|
+
# Platform fee: send to platform treasury if configured (principal only)
|
|
503
|
+
if self.platform_account and platform_fee > 0 and action != "voided":
|
|
504
|
+
pending_payments.append((self.platform_account, str(platform_fee), "platform_fee"))
|
|
438
505
|
|
|
439
|
-
# Execute all payments
|
|
506
|
+
# Execute all payments -- if any fail, none are committed as resolved
|
|
440
507
|
block_hashes = []
|
|
441
508
|
try:
|
|
442
509
|
for to_account, amount, label in pending_payments:
|
|
@@ -964,14 +964,14 @@ def test_dispute_status_endpoint():
|
|
|
964
964
|
|
|
965
965
|
|
|
966
966
|
def test_platform_fee_in_resolution():
|
|
967
|
-
"""Platform fee is
|
|
967
|
+
"""Platform fee is 10% of bounty, charged to principal only."""
|
|
968
968
|
from server.escrow import Escrow
|
|
969
969
|
escrow = Escrow("1.0", {"judge_fee": "0.21"})
|
|
970
970
|
escrow.lock()
|
|
971
971
|
result = escrow.resolve("fulfilled")
|
|
972
|
-
assert "
|
|
973
|
-
# 10% of 1.0 = 0.100
|
|
974
|
-
assert Decimal(result["
|
|
972
|
+
assert "platform_fee" in result
|
|
973
|
+
# 10% of 1.0 = 0.100, principal only
|
|
974
|
+
assert Decimal(result["platform_fee"]) == Decimal("0.1")
|
|
975
975
|
|
|
976
976
|
|
|
977
977
|
def test_platform_fee_minimum():
|
|
@@ -981,7 +981,7 @@ def test_platform_fee_minimum():
|
|
|
981
981
|
escrow.lock()
|
|
982
982
|
result = escrow.resolve("fulfilled")
|
|
983
983
|
# 10% of 0.01 = 0.001, but min is 0.002
|
|
984
|
-
assert Decimal(result["
|
|
984
|
+
assert Decimal(result["platform_fee"]) == Decimal("0.002")
|
|
985
985
|
|
|
986
986
|
|
|
987
987
|
def test_sse_publish_mechanism(app):
|
|
@@ -175,23 +175,39 @@ class TestEscrowResolveVoided(unittest.TestCase):
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
class TestEscrowBondRouting(unittest.TestCase):
|
|
178
|
-
def
|
|
178
|
+
def test_dispute_loser_bond_to_judge_tier_fee_only(self):
|
|
179
|
+
"""Loser pays only the tier fee, not the full bond."""
|
|
179
180
|
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
180
181
|
e.lock()
|
|
181
182
|
e.lock_agent_bond()
|
|
182
|
-
result = e.resolve("fulfilled", dispute_loser="principal", judge_account="judge_abc"
|
|
183
|
+
result = e.resolve("fulfilled", dispute_loser="principal", judge_account="judge_abc",
|
|
184
|
+
tier_fee=Decimal("0.002"))
|
|
183
185
|
self.assertEqual(result["bond_loser"], "principal")
|
|
184
|
-
self.assertEqual(result["bond_to_judge"], "0.
|
|
186
|
+
self.assertEqual(result["bond_to_judge"], "0.002") # tier fee, not full 0.005
|
|
185
187
|
self.assertEqual(result["judge_account"], "judge_abc")
|
|
186
188
|
self.assertEqual(result["bond_returned_to"], "agent")
|
|
189
|
+
self.assertEqual(result["bond_remainder_returned"], "0.003") # 0.005 - 0.002
|
|
190
|
+
self.assertIsNone(result["bond_to_charity"])
|
|
191
|
+
|
|
192
|
+
def test_dispute_loser_no_tier_fee_falls_back_to_full_bond(self):
|
|
193
|
+
"""Without tier_fee param, falls back to full judge_fee (backwards compat)."""
|
|
194
|
+
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
195
|
+
e.lock()
|
|
196
|
+
e.lock_agent_bond()
|
|
197
|
+
result = e.resolve("fulfilled", dispute_loser="principal", judge_account="judge_abc")
|
|
198
|
+
self.assertEqual(result["bond_to_judge"], "0.005")
|
|
199
|
+
self.assertIsNone(result["bond_remainder_returned"])
|
|
187
200
|
|
|
188
201
|
def test_dispute_agent_loses(self):
|
|
189
202
|
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
190
203
|
e.lock()
|
|
191
204
|
e.lock_agent_bond()
|
|
192
|
-
result = e.resolve("canceled", dispute_loser="agent", judge_account="judge_abc"
|
|
205
|
+
result = e.resolve("canceled", dispute_loser="agent", judge_account="judge_abc",
|
|
206
|
+
tier_fee=Decimal("0.002"))
|
|
193
207
|
self.assertEqual(result["bond_loser"], "agent")
|
|
194
208
|
self.assertEqual(result["bond_returned_to"], "principal")
|
|
209
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
210
|
+
self.assertEqual(result["bond_remainder_returned"], "0.003")
|
|
195
211
|
|
|
196
212
|
def test_no_dispute_bonds_returned(self):
|
|
197
213
|
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
@@ -200,6 +216,62 @@ class TestEscrowBondRouting(unittest.TestCase):
|
|
|
200
216
|
self.assertIsNone(result["bond_to_judge"])
|
|
201
217
|
self.assertIsNone(result["bond_returned_to"])
|
|
202
218
|
|
|
219
|
+
def test_evil_agent_bond_remainder_to_charity(self):
|
|
220
|
+
"""Evil agent: tier fee to judge, remainder of bond to charity."""
|
|
221
|
+
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
222
|
+
e.lock()
|
|
223
|
+
e.lock_agent_bond()
|
|
224
|
+
result = e.resolve("canceled", flags=["evil_agent"],
|
|
225
|
+
dispute_loser="agent", judge_account="judge_abc",
|
|
226
|
+
tier_fee=Decimal("0.002"))
|
|
227
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
228
|
+
self.assertEqual(result["bond_to_charity"], "0.003") # 0.005 - 0.002
|
|
229
|
+
self.assertIsNone(result["bond_remainder_returned"])
|
|
230
|
+
|
|
231
|
+
def test_evil_principal_bond_remainder_to_charity(self):
|
|
232
|
+
"""Evil principal: tier fee to judge, remainder of bond to charity."""
|
|
233
|
+
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
234
|
+
e.lock()
|
|
235
|
+
e.lock_agent_bond()
|
|
236
|
+
result = e.resolve("fulfilled", flags=["evil_principal"],
|
|
237
|
+
dispute_loser="principal", judge_account="judge_abc",
|
|
238
|
+
tier_fee=Decimal("0.002"))
|
|
239
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
240
|
+
self.assertEqual(result["bond_to_charity"], "0.003")
|
|
241
|
+
self.assertEqual(result["action"], "send_to_charity") # bounty to charity too
|
|
242
|
+
|
|
243
|
+
def test_evil_both_all_bonds_to_charity(self):
|
|
244
|
+
"""Evil both: both bonds (minus judge fees) to charity."""
|
|
245
|
+
e = Escrow("0.05", TERMS_WITH_JUDGE)
|
|
246
|
+
e.lock()
|
|
247
|
+
e.lock_agent_bond()
|
|
248
|
+
result = e.resolve("canceled", flags=["evil_agent", "evil_principal"],
|
|
249
|
+
dispute_loser="agent", judge_account="judge_abc",
|
|
250
|
+
tier_fee=Decimal("0.002"))
|
|
251
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
252
|
+
self.assertEqual(result["bond_to_charity"], "0.003") # agent's remainder
|
|
253
|
+
self.assertEqual(result["other_evil_bond_to_charity"], "0.005") # principal's full bond
|
|
254
|
+
self.assertIsNone(result["bond_returned_to"]) # nobody gets bond back
|
|
255
|
+
|
|
256
|
+
def test_evil_agent_with_high_min_bond(self):
|
|
257
|
+
"""Agent posted 2 XNO bond (min_bond), ruled evil at district (0.002).
|
|
258
|
+
Judge gets 0.002, charity gets 1.998."""
|
|
259
|
+
terms = {**TERMS_WITH_JUDGE, "min_bond": "2"}
|
|
260
|
+
e = Escrow("0.05", terms)
|
|
261
|
+
e.lock()
|
|
262
|
+
e.lock_agent_bond(amount=Decimal("2"))
|
|
263
|
+
result = e.resolve("canceled", flags=["evil_agent"],
|
|
264
|
+
dispute_loser="agent", judge_account="judge_abc",
|
|
265
|
+
tier_fee=Decimal("0.002"))
|
|
266
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
267
|
+
self.assertEqual(result["bond_to_charity"], "1.998")
|
|
268
|
+
|
|
269
|
+
def test_platform_fee_principal_only(self):
|
|
270
|
+
"""Platform fee is 10% of bounty, from principal only (not both sides)."""
|
|
271
|
+
e = Escrow("1.00", TERMS_WITH_JUDGE)
|
|
272
|
+
result = e.resolve("fulfilled")
|
|
273
|
+
self.assertEqual(Decimal(result["platform_fee"]), Decimal("0.1")) # 10% of 1.00
|
|
274
|
+
|
|
203
275
|
|
|
204
276
|
class TestEscrowUnknownRuling(unittest.TestCase):
|
|
205
277
|
def test_unknown_ruling_raises(self):
|
|
@@ -325,9 +397,10 @@ class TestEscrowManager(unittest.TestCase):
|
|
|
325
397
|
self.mgr.lock("c1", "0.05", TERMS_WITH_JUDGE, judge_account="judge_abc", judge_fee="0.005")
|
|
326
398
|
self.mgr.lock_agent_bond("c1")
|
|
327
399
|
_set_stub_accounts(self.mgr, "c1")
|
|
328
|
-
result = self.mgr.resolve("c1", "fulfilled", dispute_loser="principal")
|
|
400
|
+
result = self.mgr.resolve("c1", "fulfilled", dispute_loser="principal", tier_fee="0.002")
|
|
329
401
|
self.assertEqual(result["bond_loser"], "principal")
|
|
330
|
-
self.assertEqual(result["bond_to_judge"], "0.
|
|
402
|
+
self.assertEqual(result["bond_to_judge"], "0.002")
|
|
403
|
+
self.assertEqual(result["bond_remainder_returned"], "0.003")
|
|
331
404
|
|
|
332
405
|
def test_resolve_nonexistent_raises(self):
|
|
333
406
|
with self.assertRaises(ValueError):
|
fix_cli-0.6.0/PKG-INFO
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: fix-cli
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: AI-powered command fixer with contract-based dispute resolution
|
|
5
|
-
Author-email: Karan Sharma <karans4@protonmail.com>
|
|
6
|
-
License: AGPL-3.0-or-later
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Dist: httpx>=0.24
|
|
11
|
-
Requires-Dist: cryptography>=41.0
|
|
12
|
-
Provides-Extra: server
|
|
13
|
-
Requires-Dist: fastapi>=0.100; extra == "server"
|
|
14
|
-
Requires-Dist: uvicorn>=0.20; extra == "server"
|
|
15
|
-
Requires-Dist: starlette>=0.27; extra == "server"
|
|
16
|
-
Requires-Dist: ed25519-blake2b>=1.4; extra == "server"
|
|
17
|
-
Requires-Dist: requests>=2.28; extra == "server"
|
|
18
|
-
|
|
19
|
-
# fix
|
|
20
|
-
|
|
21
|
-
AI-powered command fixer. A command fails, an LLM diagnoses it, proposes a fix, and a contract system tracks the whole thing. Disputes go to an AI judge.
|
|
22
|
-
|
|
23
|
-
## Quick start
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
pip install git+https://github.com/karans4/fix.git
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Local mode (you need an API key)
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
33
|
-
|
|
34
|
-
fix "gcc foo.c" # run command, fix if it fails
|
|
35
|
-
fix it # fix the last failed command
|
|
36
|
-
fix --explain "make" # just explain the error
|
|
37
|
-
fix --dry-run "make" # show fix without running
|
|
38
|
-
fix --local "make" # force Ollama (free, local)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Market mode (free platform agent)
|
|
42
|
-
|
|
43
|
-
Post a contract to the platform. A free AI agent picks it up and proposes a fix.
|
|
44
|
-
|
|
45
|
-
```sh
|
|
46
|
-
fix --market "gcc foo.c"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Platform: `https://fix.notruefireman.org` (free during testing)
|
|
50
|
-
|
|
51
|
-
Configure in `~/.fix/config.py`:
|
|
52
|
-
```python
|
|
53
|
-
platform_url = "https://fix.notruefireman.org"
|
|
54
|
-
remote = True # default to remote mode
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Shell integration
|
|
58
|
-
|
|
59
|
-
For `fix it` / `fix !!` to work, add to your shell config:
|
|
60
|
-
|
|
61
|
-
```sh
|
|
62
|
-
# bash/zsh
|
|
63
|
-
eval "$(fix shell)"
|
|
64
|
-
|
|
65
|
-
# fish
|
|
66
|
-
fix shell fish | source
|
|
67
|
-
|
|
68
|
-
# or auto-install
|
|
69
|
-
fix shell --install
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Safe mode (sandbox)
|
|
73
|
-
|
|
74
|
-
Default on Linux. Runs fixes in OverlayFS -- changes only committed if verification passes.
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
fix "make build" # sandbox on Linux by default
|
|
78
|
-
fix --no-safe "make" # skip sandbox
|
|
79
|
-
fix --safe "make" # force sandbox
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Verification
|
|
83
|
-
|
|
84
|
-
```sh
|
|
85
|
-
fix "gcc foo.c" # default: re-run, exit 0 = success
|
|
86
|
-
fix --verify=human "python3 render.py" # human judges
|
|
87
|
-
fix --verify="pytest tests/" "pip install x" # custom command
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## How it works
|
|
91
|
-
|
|
92
|
-
1. Command fails, stderr captured
|
|
93
|
-
2. Contract built (task, environment, verification terms, escrow)
|
|
94
|
-
3. Agent investigates (read-only commands), then proposes fix
|
|
95
|
-
4. Fix applied, verified mechanically
|
|
96
|
-
5. Multi-attempt: up to 3 tries, feeding failures back as context
|
|
97
|
-
6. Disputes go to an AI judge who reviews the full transcript
|
|
98
|
-
|
|
99
|
-
## Architecture
|
|
100
|
-
|
|
101
|
-
- `fix` -- CLI entry point
|
|
102
|
-
- `server/` -- FastAPI platform (contracts, escrow, judge; reputation via on-chain Nano balance)
|
|
103
|
-
- `protocol.py` -- state machine, constants
|
|
104
|
-
- `scrubber.py` -- redacts secrets from error output before sending to LLM
|
|
105
|
-
- `contract.py` -- builds structured contracts
|
|
106
|
-
- `client.py` / `agent.py` -- remote mode client and agent
|
|
107
|
-
|
|
108
|
-
## License
|
|
109
|
-
|
|
110
|
-
MIT
|
fix_cli-0.6.0/README.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# fix
|
|
2
|
-
|
|
3
|
-
AI-powered command fixer. A command fails, an LLM diagnoses it, proposes a fix, and a contract system tracks the whole thing. Disputes go to an AI judge.
|
|
4
|
-
|
|
5
|
-
## Quick start
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
pip install git+https://github.com/karans4/fix.git
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
### Local mode (you need an API key)
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
15
|
-
|
|
16
|
-
fix "gcc foo.c" # run command, fix if it fails
|
|
17
|
-
fix it # fix the last failed command
|
|
18
|
-
fix --explain "make" # just explain the error
|
|
19
|
-
fix --dry-run "make" # show fix without running
|
|
20
|
-
fix --local "make" # force Ollama (free, local)
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Market mode (free platform agent)
|
|
24
|
-
|
|
25
|
-
Post a contract to the platform. A free AI agent picks it up and proposes a fix.
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
fix --market "gcc foo.c"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Platform: `https://fix.notruefireman.org` (free during testing)
|
|
32
|
-
|
|
33
|
-
Configure in `~/.fix/config.py`:
|
|
34
|
-
```python
|
|
35
|
-
platform_url = "https://fix.notruefireman.org"
|
|
36
|
-
remote = True # default to remote mode
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Shell integration
|
|
40
|
-
|
|
41
|
-
For `fix it` / `fix !!` to work, add to your shell config:
|
|
42
|
-
|
|
43
|
-
```sh
|
|
44
|
-
# bash/zsh
|
|
45
|
-
eval "$(fix shell)"
|
|
46
|
-
|
|
47
|
-
# fish
|
|
48
|
-
fix shell fish | source
|
|
49
|
-
|
|
50
|
-
# or auto-install
|
|
51
|
-
fix shell --install
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Safe mode (sandbox)
|
|
55
|
-
|
|
56
|
-
Default on Linux. Runs fixes in OverlayFS -- changes only committed if verification passes.
|
|
57
|
-
|
|
58
|
-
```sh
|
|
59
|
-
fix "make build" # sandbox on Linux by default
|
|
60
|
-
fix --no-safe "make" # skip sandbox
|
|
61
|
-
fix --safe "make" # force sandbox
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Verification
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
fix "gcc foo.c" # default: re-run, exit 0 = success
|
|
68
|
-
fix --verify=human "python3 render.py" # human judges
|
|
69
|
-
fix --verify="pytest tests/" "pip install x" # custom command
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## How it works
|
|
73
|
-
|
|
74
|
-
1. Command fails, stderr captured
|
|
75
|
-
2. Contract built (task, environment, verification terms, escrow)
|
|
76
|
-
3. Agent investigates (read-only commands), then proposes fix
|
|
77
|
-
4. Fix applied, verified mechanically
|
|
78
|
-
5. Multi-attempt: up to 3 tries, feeding failures back as context
|
|
79
|
-
6. Disputes go to an AI judge who reviews the full transcript
|
|
80
|
-
|
|
81
|
-
## Architecture
|
|
82
|
-
|
|
83
|
-
- `fix` -- CLI entry point
|
|
84
|
-
- `server/` -- FastAPI platform (contracts, escrow, judge; reputation via on-chain Nano balance)
|
|
85
|
-
- `protocol.py` -- state machine, constants
|
|
86
|
-
- `scrubber.py` -- redacts secrets from error output before sending to LLM
|
|
87
|
-
- `contract.py` -- builds structured contracts
|
|
88
|
-
- `client.py` / `agent.py` -- remote mode client and agent
|
|
89
|
-
|
|
90
|
-
## License
|
|
91
|
-
|
|
92
|
-
MIT
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: fix-cli
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: AI-powered command fixer with contract-based dispute resolution
|
|
5
|
-
Author-email: Karan Sharma <karans4@protonmail.com>
|
|
6
|
-
License: AGPL-3.0-or-later
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Dist: httpx>=0.24
|
|
11
|
-
Requires-Dist: cryptography>=41.0
|
|
12
|
-
Provides-Extra: server
|
|
13
|
-
Requires-Dist: fastapi>=0.100; extra == "server"
|
|
14
|
-
Requires-Dist: uvicorn>=0.20; extra == "server"
|
|
15
|
-
Requires-Dist: starlette>=0.27; extra == "server"
|
|
16
|
-
Requires-Dist: ed25519-blake2b>=1.4; extra == "server"
|
|
17
|
-
Requires-Dist: requests>=2.28; extra == "server"
|
|
18
|
-
|
|
19
|
-
# fix
|
|
20
|
-
|
|
21
|
-
AI-powered command fixer. A command fails, an LLM diagnoses it, proposes a fix, and a contract system tracks the whole thing. Disputes go to an AI judge.
|
|
22
|
-
|
|
23
|
-
## Quick start
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
pip install git+https://github.com/karans4/fix.git
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Local mode (you need an API key)
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, or run Ollama
|
|
33
|
-
|
|
34
|
-
fix "gcc foo.c" # run command, fix if it fails
|
|
35
|
-
fix it # fix the last failed command
|
|
36
|
-
fix --explain "make" # just explain the error
|
|
37
|
-
fix --dry-run "make" # show fix without running
|
|
38
|
-
fix --local "make" # force Ollama (free, local)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Market mode (free platform agent)
|
|
42
|
-
|
|
43
|
-
Post a contract to the platform. A free AI agent picks it up and proposes a fix.
|
|
44
|
-
|
|
45
|
-
```sh
|
|
46
|
-
fix --market "gcc foo.c"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Platform: `https://fix.notruefireman.org` (free during testing)
|
|
50
|
-
|
|
51
|
-
Configure in `~/.fix/config.py`:
|
|
52
|
-
```python
|
|
53
|
-
platform_url = "https://fix.notruefireman.org"
|
|
54
|
-
remote = True # default to remote mode
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Shell integration
|
|
58
|
-
|
|
59
|
-
For `fix it` / `fix !!` to work, add to your shell config:
|
|
60
|
-
|
|
61
|
-
```sh
|
|
62
|
-
# bash/zsh
|
|
63
|
-
eval "$(fix shell)"
|
|
64
|
-
|
|
65
|
-
# fish
|
|
66
|
-
fix shell fish | source
|
|
67
|
-
|
|
68
|
-
# or auto-install
|
|
69
|
-
fix shell --install
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Safe mode (sandbox)
|
|
73
|
-
|
|
74
|
-
Default on Linux. Runs fixes in OverlayFS -- changes only committed if verification passes.
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
fix "make build" # sandbox on Linux by default
|
|
78
|
-
fix --no-safe "make" # skip sandbox
|
|
79
|
-
fix --safe "make" # force sandbox
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Verification
|
|
83
|
-
|
|
84
|
-
```sh
|
|
85
|
-
fix "gcc foo.c" # default: re-run, exit 0 = success
|
|
86
|
-
fix --verify=human "python3 render.py" # human judges
|
|
87
|
-
fix --verify="pytest tests/" "pip install x" # custom command
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## How it works
|
|
91
|
-
|
|
92
|
-
1. Command fails, stderr captured
|
|
93
|
-
2. Contract built (task, environment, verification terms, escrow)
|
|
94
|
-
3. Agent investigates (read-only commands), then proposes fix
|
|
95
|
-
4. Fix applied, verified mechanically
|
|
96
|
-
5. Multi-attempt: up to 3 tries, feeding failures back as context
|
|
97
|
-
6. Disputes go to an AI judge who reviews the full transcript
|
|
98
|
-
|
|
99
|
-
## Architecture
|
|
100
|
-
|
|
101
|
-
- `fix` -- CLI entry point
|
|
102
|
-
- `server/` -- FastAPI platform (contracts, escrow, judge; reputation via on-chain Nano balance)
|
|
103
|
-
- `protocol.py` -- state machine, constants
|
|
104
|
-
- `scrubber.py` -- redacts secrets from error output before sending to LLM
|
|
105
|
-
- `contract.py` -- builds structured contracts
|
|
106
|
-
- `client.py` / `agent.py` -- remote mode client and agent
|
|
107
|
-
|
|
108
|
-
## License
|
|
109
|
-
|
|
110
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|