cascade-mcp 0.1.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.
- cascade_mcp-0.1.0/.gitignore +34 -0
- cascade_mcp-0.1.0/LICENSE +21 -0
- cascade_mcp-0.1.0/PKG-INFO +152 -0
- cascade_mcp-0.1.0/README.md +114 -0
- cascade_mcp-0.1.0/cascade/__init__.py +7 -0
- cascade_mcp-0.1.0/cascade/cascade_routing.py +164 -0
- cascade_mcp-0.1.0/cascade/cascade_sim.py +331 -0
- cascade_mcp-0.1.0/cascade/server.py +386 -0
- cascade_mcp-0.1.0/pyproject.toml +49 -0
- cascade_mcp-0.1.0/scripts/audit_cherrypick.py +91 -0
- cascade_mcp-0.1.0/scripts/gen_agent_logs.py +234 -0
- cascade_mcp-0.1.0/scripts/validate_logs.py +47 -0
- cascade_mcp-0.1.0/tests/__init__.py +0 -0
- cascade_mcp-0.1.0/tests/test_agent_logs.py +255 -0
- cascade_mcp-0.1.0/tests/test_mcp_wrapper.py +352 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Large simulation outputs (regenerable — run gen_agent_logs.py / test_mcp_wrapper.py)
|
|
2
|
+
agent_logs.csv
|
|
3
|
+
agent_logs_mcp.csv
|
|
4
|
+
|
|
5
|
+
# Logs
|
|
6
|
+
*.log
|
|
7
|
+
gen_progress.log
|
|
8
|
+
|
|
9
|
+
# Python
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.eggs/
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
|
|
17
|
+
# Virtual environments
|
|
18
|
+
.venv/
|
|
19
|
+
venv/
|
|
20
|
+
env/
|
|
21
|
+
|
|
22
|
+
# IDE / editor
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
*.swp
|
|
26
|
+
*.swo
|
|
27
|
+
*~
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
Thumbs.db
|
|
31
|
+
.DS_Store
|
|
32
|
+
|
|
33
|
+
# Claude / agent settings (local-only)
|
|
34
|
+
.claude/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pedro Clemente-Turrubiates
|
|
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,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cascade-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cascade-resolution routing for concurrent multi-agent writes, exposed as an MCP server.
|
|
5
|
+
Project-URL: Homepage, https://github.com/clemente-turrubiates/cascade-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/clemente-turrubiates/cascade-mcp
|
|
7
|
+
Author: Pedro Clemente-Turrubiates
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Pedro Clemente-Turrubiates
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: agents,conflict-resolution,mcp,model-context-protocol,occ,routing
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Requires-Dist: mcp>=1.0
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# cascade-mcp
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/cascade-mcp/)
|
|
42
|
+
[](https://pypi.org/project/cascade-mcp/)
|
|
43
|
+
[](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
Cascade-resolution routing for concurrent multi-agent writes — a resolution
|
|
47
|
+
router that decides, per conflict, whether a write **wins**, **forks** to a
|
|
48
|
+
human, or must be **recomputed**, plus an MCP server that exposes the router as
|
|
49
|
+
tools and a stress-test suite that proves the behavior can't be cherry-picked.
|
|
50
|
+
|
|
51
|
+
## What's here
|
|
52
|
+
|
|
53
|
+
The core question: when many agents write to the same field over a dependency
|
|
54
|
+
DAG, how do you resolve conflicts without either silently committing wrong
|
|
55
|
+
values (pure cascade) or overpaying in wasted re-runs (pure OCC)? The **hybrid**
|
|
56
|
+
policy routes zero-tolerance fields to OCC and tolerant fields to a
|
|
57
|
+
provenance-weighted cascade. Every conflict lands in one of a few arms:
|
|
58
|
+
|
|
59
|
+
- **WINNER** — a live (non-stale) write wins on authority → confidence. No
|
|
60
|
+
re-run, no human. This is the win over OCC.
|
|
61
|
+
- **FORK** — two+ fresh writes tie; defer to a human/high-tier agent instead of
|
|
62
|
+
silently dropping one.
|
|
63
|
+
- **RECOMPUTE** — every competing write is premise-stale; there's no correct
|
|
64
|
+
value to pick, so re-run. Here you're no better than OCC.
|
|
65
|
+
|
|
66
|
+
## Layout
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
cascade/ importable package
|
|
70
|
+
cascade_routing.py core resolution router (OCC vs cascade vs hybrid)
|
|
71
|
+
server.py MCP stdio server wrapping the router as tools
|
|
72
|
+
cascade_sim.py standalone go/no-go regime simulator
|
|
73
|
+
scripts/ data-generation / audit utilities
|
|
74
|
+
gen_agent_logs.py emit agent_logs.csv across the regime × policy grid
|
|
75
|
+
audit_cherrypick.py adversarial read of agent_logs.csv
|
|
76
|
+
validate_logs.py quick sanity checks on a generated CSV
|
|
77
|
+
tests/ verification suite
|
|
78
|
+
test_agent_logs.py 43-check self-consistency + usability suite over the CSV
|
|
79
|
+
test_mcp_wrapper.py routes the regime grid through the MCP wrapper and
|
|
80
|
+
re-runs the suite to prove the wrapper preserves behavior
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Large simulation outputs (`agent_logs.csv`, `agent_logs_mcp.csv`, ~900 MB each)
|
|
84
|
+
are regenerable and are gitignored.
|
|
85
|
+
|
|
86
|
+
## Requirements
|
|
87
|
+
|
|
88
|
+
- Python ≥ 3.10 (developed on 3.13)
|
|
89
|
+
- [`mcp`](https://pypi.org/project/mcp/) — installed automatically as a dependency
|
|
90
|
+
|
|
91
|
+
## Install & attach to an MCP client
|
|
92
|
+
|
|
93
|
+
Once published to PyPI, no clone or virtualenv is needed — [`uvx`](https://docs.astral.sh/uv/)
|
|
94
|
+
runs the server in an ephemeral environment:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
uvx cascade-mcp
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To attach the router to **Claude Desktop** or **Cursor**, add this to your
|
|
101
|
+
`claude_desktop_config.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"cascade": {
|
|
107
|
+
"command": "uvx",
|
|
108
|
+
"args": ["cascade-mcp"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The MCP server exposes five tools: `configure`, `read_state`, `propose_update`,
|
|
115
|
+
`churn`, `get_field`.
|
|
116
|
+
|
|
117
|
+
## Usage (from source)
|
|
118
|
+
|
|
119
|
+
Clone the repo and run everything **from the repo root**.
|
|
120
|
+
|
|
121
|
+
Run the MCP server (stdio):
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
python -m cascade.server
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Run the standalone simulator:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
python -m cascade.cascade_sim
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Generate the stress-test CSV (writes UTF-8 — pipe via a POSIX shell, **not**
|
|
134
|
+
PowerShell `>`, which re-encodes to UTF-16 and corrupts the file):
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
python scripts/gen_agent_logs.py > agent_logs.csv
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Verify the generated CSV:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
python -m tests.test_agent_logs # 43-check suite
|
|
144
|
+
python scripts/audit_cherrypick.py # adversarial cross-checks
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Verify the MCP wrapper preserves the router's behavior end-to-end (wire-protocol
|
|
148
|
+
smoke test → regime grid through the wrapper → re-run the suite):
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
python -m tests.test_mcp_wrapper
|
|
152
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# cascade-mcp
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/cascade-mcp/)
|
|
4
|
+
[](https://pypi.org/project/cascade-mcp/)
|
|
5
|
+
[](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Cascade-resolution routing for concurrent multi-agent writes — a resolution
|
|
9
|
+
router that decides, per conflict, whether a write **wins**, **forks** to a
|
|
10
|
+
human, or must be **recomputed**, plus an MCP server that exposes the router as
|
|
11
|
+
tools and a stress-test suite that proves the behavior can't be cherry-picked.
|
|
12
|
+
|
|
13
|
+
## What's here
|
|
14
|
+
|
|
15
|
+
The core question: when many agents write to the same field over a dependency
|
|
16
|
+
DAG, how do you resolve conflicts without either silently committing wrong
|
|
17
|
+
values (pure cascade) or overpaying in wasted re-runs (pure OCC)? The **hybrid**
|
|
18
|
+
policy routes zero-tolerance fields to OCC and tolerant fields to a
|
|
19
|
+
provenance-weighted cascade. Every conflict lands in one of a few arms:
|
|
20
|
+
|
|
21
|
+
- **WINNER** — a live (non-stale) write wins on authority → confidence. No
|
|
22
|
+
re-run, no human. This is the win over OCC.
|
|
23
|
+
- **FORK** — two+ fresh writes tie; defer to a human/high-tier agent instead of
|
|
24
|
+
silently dropping one.
|
|
25
|
+
- **RECOMPUTE** — every competing write is premise-stale; there's no correct
|
|
26
|
+
value to pick, so re-run. Here you're no better than OCC.
|
|
27
|
+
|
|
28
|
+
## Layout
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
cascade/ importable package
|
|
32
|
+
cascade_routing.py core resolution router (OCC vs cascade vs hybrid)
|
|
33
|
+
server.py MCP stdio server wrapping the router as tools
|
|
34
|
+
cascade_sim.py standalone go/no-go regime simulator
|
|
35
|
+
scripts/ data-generation / audit utilities
|
|
36
|
+
gen_agent_logs.py emit agent_logs.csv across the regime × policy grid
|
|
37
|
+
audit_cherrypick.py adversarial read of agent_logs.csv
|
|
38
|
+
validate_logs.py quick sanity checks on a generated CSV
|
|
39
|
+
tests/ verification suite
|
|
40
|
+
test_agent_logs.py 43-check self-consistency + usability suite over the CSV
|
|
41
|
+
test_mcp_wrapper.py routes the regime grid through the MCP wrapper and
|
|
42
|
+
re-runs the suite to prove the wrapper preserves behavior
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Large simulation outputs (`agent_logs.csv`, `agent_logs_mcp.csv`, ~900 MB each)
|
|
46
|
+
are regenerable and are gitignored.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Python ≥ 3.10 (developed on 3.13)
|
|
51
|
+
- [`mcp`](https://pypi.org/project/mcp/) — installed automatically as a dependency
|
|
52
|
+
|
|
53
|
+
## Install & attach to an MCP client
|
|
54
|
+
|
|
55
|
+
Once published to PyPI, no clone or virtualenv is needed — [`uvx`](https://docs.astral.sh/uv/)
|
|
56
|
+
runs the server in an ephemeral environment:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
uvx cascade-mcp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
To attach the router to **Claude Desktop** or **Cursor**, add this to your
|
|
63
|
+
`claude_desktop_config.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"cascade": {
|
|
69
|
+
"command": "uvx",
|
|
70
|
+
"args": ["cascade-mcp"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The MCP server exposes five tools: `configure`, `read_state`, `propose_update`,
|
|
77
|
+
`churn`, `get_field`.
|
|
78
|
+
|
|
79
|
+
## Usage (from source)
|
|
80
|
+
|
|
81
|
+
Clone the repo and run everything **from the repo root**.
|
|
82
|
+
|
|
83
|
+
Run the MCP server (stdio):
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
python -m cascade.server
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Run the standalone simulator:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
python -m cascade.cascade_sim
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Generate the stress-test CSV (writes UTF-8 — pipe via a POSIX shell, **not**
|
|
96
|
+
PowerShell `>`, which re-encodes to UTF-16 and corrupts the file):
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
python scripts/gen_agent_logs.py > agent_logs.csv
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Verify the generated CSV:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
python -m tests.test_agent_logs # 43-check suite
|
|
106
|
+
python scripts/audit_cherrypick.py # adversarial cross-checks
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Verify the MCP wrapper preserves the router's behavior end-to-end (wire-protocol
|
|
110
|
+
smoke test → regime grid through the wrapper → re-run the suite):
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
python -m tests.test_mcp_wrapper
|
|
114
|
+
```
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
cascade_routing.py — pure OCC vs pure cascade vs per-field HYBRID.
|
|
4
|
+
Fair head-to-head: forked/committed fields keep contending (no freeze), so
|
|
5
|
+
conflict volumes are comparable across policies. Tracks BOTH costs:
|
|
6
|
+
recomputes wasted expensive re-runs (OCC overpays these under churn)
|
|
7
|
+
silent_errors committed-but-actually-wrong values (cascade risks these)
|
|
8
|
+
|
|
9
|
+
Each field has a TRUE tolerance (drift its answer can absorb). Hybrid routes
|
|
10
|
+
zero-tolerance fields to OCC (safe) and tolerant fields to the semantic cascade
|
|
11
|
+
with materiality = measured tolerance. tol_safety>1 models OVER-estimating it.
|
|
12
|
+
fresh_loser_redo_prob models the assumption that a fresh loser re-runs anyway.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import math
|
|
16
|
+
import random
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from collections import Counter
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Field:
|
|
22
|
+
id: str; level: int; deps: list
|
|
23
|
+
rev: int = 0; value: float = 1.0
|
|
24
|
+
true_tol: float = 0.25; policy: str = "cascade"; materiality: float = 0.20
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Write:
|
|
28
|
+
tier: int; conf: float; read_rev: dict; read_val: dict
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Config:
|
|
32
|
+
n_levels: int = 3; fields_per_level: int = 6; deps_per_field: int = 3
|
|
33
|
+
conf_levels: tuple = (0.5, 0.7, 0.85, 0.95, 0.99)
|
|
34
|
+
rounds: int = 12000
|
|
35
|
+
source_write_prob: float = 0.25; contention_prob: float = 0.30
|
|
36
|
+
width: tuple = (2, 3); lag: int = 4; value_drift: float = 0.08
|
|
37
|
+
frac_zero_tol: float = 0.30; zero_tol: float = 0.01; gen_tol: float = 0.25
|
|
38
|
+
policy: str = "hybrid"; global_materiality: float = 0.20
|
|
39
|
+
route_threshold: float = 0.05; tol_safety: float = 1.0
|
|
40
|
+
# multiplicative log-normal noise on the per-field tolerance ESTIMATE.
|
|
41
|
+
# tol_safety is systematic bias; tol_est_noise is the honest "you measured
|
|
42
|
+
# it, but imperfectly" spread. At >0 the hybrid over-estimates on ~half its
|
|
43
|
+
# fields even with tol_safety=1 -> silent errors appear (the safety=1 zero
|
|
44
|
+
# is a perfect-knowledge artifact, not a property of the design).
|
|
45
|
+
tol_est_noise: float = 0.0
|
|
46
|
+
fresh_loser_redo_prob: float = 0.0; seed: int = 0
|
|
47
|
+
|
|
48
|
+
def drift(w, F):
|
|
49
|
+
return max((abs(F[d].value/v0 - 1.0) if v0 else 0.0 for d, v0 in w.read_val.items()), default=0.0)
|
|
50
|
+
def rev_stale(w, F):
|
|
51
|
+
return any(F[d].rev > s for d, s in w.read_rev.items())
|
|
52
|
+
def bump(f, rng, cfg):
|
|
53
|
+
f.rev += 1; f.value *= (1.0 + rng.gauss(0.0, cfg.value_drift))
|
|
54
|
+
|
|
55
|
+
def build(cfg, rng):
|
|
56
|
+
F, by = {}, []
|
|
57
|
+
for lvl in range(cfg.n_levels):
|
|
58
|
+
ids = [f"L{lvl}_{i}" for i in range(cfg.fields_per_level)]; by.append(ids)
|
|
59
|
+
for fid in ids:
|
|
60
|
+
deps = [] if lvl == 0 else rng.sample(
|
|
61
|
+
[x for p in by[:lvl] for x in p], min(cfg.deps_per_field, cfg.fields_per_level*lvl))
|
|
62
|
+
f = Field(fid, lvl, deps)
|
|
63
|
+
if lvl > 0:
|
|
64
|
+
f.true_tol = cfg.zero_tol if rng.random() < cfg.frac_zero_tol else cfg.gen_tol
|
|
65
|
+
measured = f.true_tol * cfg.tol_safety
|
|
66
|
+
if cfg.tol_est_noise > 0.0:
|
|
67
|
+
measured *= math.exp(rng.gauss(0.0, cfg.tol_est_noise))
|
|
68
|
+
if cfg.policy == "occ": f.policy = "occ"
|
|
69
|
+
# occ_value: OCC decision rule (commit-any-fresh, all losers
|
|
70
|
+
# rerun, no fork/authority) on the VALUE predicate. Isolates the
|
|
71
|
+
# staleness-predicate win from the routing/arbitration win.
|
|
72
|
+
elif cfg.policy == "occ_value":
|
|
73
|
+
f.policy, f.materiality = "occ_value", cfg.global_materiality
|
|
74
|
+
elif cfg.policy == "cascade": f.policy, f.materiality = "cascade", cfg.global_materiality
|
|
75
|
+
else:
|
|
76
|
+
if measured < cfg.route_threshold: f.policy = "occ"
|
|
77
|
+
else: f.policy, f.materiality = "cascade", measured
|
|
78
|
+
F[fid] = f
|
|
79
|
+
return F
|
|
80
|
+
|
|
81
|
+
def resolve(f, grp, F, cfg, rng):
|
|
82
|
+
if f.policy == "occ":
|
|
83
|
+
fresh = [w for w in grp if not rev_stale(w, F)]
|
|
84
|
+
if fresh: return True, len(grp) - 1, 0, "OCC_COMMIT"
|
|
85
|
+
return False, len(grp), 0, "OCC_ALLABORT"
|
|
86
|
+
if f.policy == "occ_value":
|
|
87
|
+
# value predicate + OCC accounting: commit one fresh, ALL losers rerun
|
|
88
|
+
# (no free adoption), no fork/authority. Can leak, because a flat global
|
|
89
|
+
# materiality has no idea of this field's true tolerance.
|
|
90
|
+
fresh = [w for w in grp if drift(w, F) <= f.materiality]
|
|
91
|
+
if not fresh: return False, len(grp), 0, "OCC_ALLABORT"
|
|
92
|
+
silent = 1 if drift(fresh[0], F) > f.true_tol else 0
|
|
93
|
+
return True, len(grp) - 1, silent, "OCC_COMMIT"
|
|
94
|
+
m = f.materiality
|
|
95
|
+
fresh = [w for w in grp if drift(w, F) <= m]; n_stale = len(grp) - len(fresh)
|
|
96
|
+
if not fresh: return False, len(grp), 0, "RECOMPUTE"
|
|
97
|
+
redo = n_stale + sum(1 for _ in range(len(fresh) - 1) if rng.random() < cfg.fresh_loser_redo_prob)
|
|
98
|
+
bt = min(w.tier for w in fresh); top = [w for w in fresh if w.tier == bt]
|
|
99
|
+
if len(top) > 1:
|
|
100
|
+
bc = max(w.conf for w in top); top = [w for w in top if w.conf == bc]
|
|
101
|
+
if len(top) > 1: return True, redo, 0, "FORK"
|
|
102
|
+
silent = 1 if drift(top[0], F) > f.true_tol else 0
|
|
103
|
+
return True, redo, silent, "WINNER"
|
|
104
|
+
|
|
105
|
+
def run(cfg):
|
|
106
|
+
rng = random.Random(cfg.seed); F = build(cfg, rng)
|
|
107
|
+
src = [f for f in F.values() if f.level == 0]; der = [f for f in F.values() if f.level > 0]
|
|
108
|
+
topo = sorted(F.values(), key=lambda f: f.level); pending, since, M = {}, {}, Counter()
|
|
109
|
+
for r in range(cfg.rounds):
|
|
110
|
+
for f in src:
|
|
111
|
+
if rng.random() < cfg.source_write_prob: bump(f, rng, cfg)
|
|
112
|
+
for f in der:
|
|
113
|
+
if f.id in pending: continue
|
|
114
|
+
if rng.random() < cfg.contention_prob:
|
|
115
|
+
grp = [Write(2, rng.choice(cfg.conf_levels),
|
|
116
|
+
{d: F[d].rev for d in f.deps}, {d: F[d].value for d in f.deps})
|
|
117
|
+
for _ in range(rng.randint(*cfg.width))]
|
|
118
|
+
pending[f.id] = grp; since[f.id] = r
|
|
119
|
+
for f in topo:
|
|
120
|
+
g = pending.get(f.id)
|
|
121
|
+
if g is None or r - since[f.id] < cfg.lag: continue
|
|
122
|
+
committed, redo, silent, arm = resolve(f, g, F, cfg, rng)
|
|
123
|
+
M["conflicts"] += 1; M["recomputes"] += redo; M["silent_errors"] += silent; M[arm] += 1
|
|
124
|
+
if committed: M["commits"] += 1; bump(f, rng, cfg) # keeps contending; no freeze
|
|
125
|
+
del pending[f.id]; del since[f.id]
|
|
126
|
+
return M
|
|
127
|
+
|
|
128
|
+
def rep(label, M):
|
|
129
|
+
c = M["conflicts"] or 1; cm = M["commits"] or 1
|
|
130
|
+
print(f" {label:<28} {M['recomputes']/c:4.2f} recompute/conflict "
|
|
131
|
+
f"{M['recomputes']/cm:5.2f} recompute/commit "
|
|
132
|
+
f"silent_err {M['silent_errors']:>4} conflicts {M['conflicts']:>6}")
|
|
133
|
+
|
|
134
|
+
def main():
|
|
135
|
+
print("=" * 104)
|
|
136
|
+
print("POLICY HEAD-TO-HEAD (lag=4; 30% price-like tol~0.01, 70% estimate-like tol~0.25)")
|
|
137
|
+
print("=" * 104)
|
|
138
|
+
print("\n[10] pure OCC(rev) vs OCC(value) vs pure CASCADE(0.20) vs HYBRID(routed)")
|
|
139
|
+
rep("pure OCC (rev-staleness)", run(Config(policy="occ")))
|
|
140
|
+
rep("OCC (value-staleness 0.20)", run(Config(policy="occ_value", global_materiality=0.20)))
|
|
141
|
+
rep("pure CASCADE (global 0.20)", run(Config(policy="cascade", global_materiality=0.20)))
|
|
142
|
+
rep("HYBRID (measured tol)", run(Config(policy="hybrid")))
|
|
143
|
+
print(" OCC(value) vs OCC(rev): the throughput gain that is JUST the predicate,")
|
|
144
|
+
print(" not the routing. OCC(value) silent_err > 0: the predicate alone is unsafe.")
|
|
145
|
+
|
|
146
|
+
print("\n[10b] HYBRID under NOISY (imperfect) tolerance measurement, tol_safety=1")
|
|
147
|
+
print(" the safety=1 zero-leak is a perfect-knowledge artifact; noise leaks:")
|
|
148
|
+
for nz in (0.0, 0.5, 1.0):
|
|
149
|
+
rep(f"hybrid tol_est_noise={nz:.1f}", run(Config(policy="hybrid", tol_est_noise=nz)))
|
|
150
|
+
|
|
151
|
+
print("\n[11] survives the 'fresh loser adopts winner free' assumption being switched off?")
|
|
152
|
+
for p in (0.0, 0.5, 1.0):
|
|
153
|
+
rep(f"hybrid redo_prob={p:.1f}", run(Config(policy="hybrid", fresh_loser_redo_prob=p)))
|
|
154
|
+
|
|
155
|
+
print("\n[12] over-estimating tolerance: PURE CASCADE (bites hard) vs HYBRID (only cascade-routed fields)")
|
|
156
|
+
for s in (1.0, 2.0, 5.0):
|
|
157
|
+
rep(f"cascade tol_safety={s:.0f}", run(Config(policy="cascade", global_materiality=0.20 * s)))
|
|
158
|
+
for s in (1.0, 2.0, 5.0):
|
|
159
|
+
rep(f"hybrid tol_safety={s:.0f}", run(Config(policy="hybrid", tol_safety=s)))
|
|
160
|
+
print(" Measure tolerance conservatively (safety<=1) -> silent_err stays 0.")
|
|
161
|
+
print("=" * 104)
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
main()
|