Skip to content

Commit 409350b

Browse files
committed
Preserve full trust-score precision in the Python SDK
Remove legacy 3-decimal rounding from exported trust helpers so Python no longer truncates trust values below Rust-authoritative precision. Add regression tests that prove the deprecated compatibility helpers preserve full precision instead of silently rounding. Constraint: Root and child repo guidance explicitly require no round() on trust scores for Rust parity Constraint: The deprecated helpers remain part of the exported SDK surface, so parity fixes must cover them too Rejected: Leave the helpers unchanged because they are deprecated | still leaks rounded values to callers and breaks parity expectations Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep any future compatibility helpers precision-preserving unless Rust itself changes the wire/math contract Tested: python -m pytest trustchain-py/tests/test_trust_legacy_precision.py -q; python -m pytest trustchain-py/tests/ -x -q Not-tested: Cross-language end-to-end parity harness beyond the Python SDK suite
1 parent 921f650 commit 409350b

2 files changed

Lines changed: 85 additions & 4 deletions

File tree

src/trustchain/trust.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ def compute_trust(pubkey: str, store: RecordStore) -> float:
11031103
+ weights["age"] * age_score
11041104
+ weights["entropy"] * normalized_entropy
11051105
)
1106-
return round(min(max(trust, 0.0), 1.0), 3)
1106+
return min(max(trust, 0.0), 1.0)
11071107

11081108

11091109
def is_sybil_cluster(pubkeys: Set[str], store: RecordStore) -> bool:
@@ -1150,7 +1150,7 @@ def compute_transitive_trust(pubkey: str, store: RecordStore, alpha: float = 0.8
11501150
max_pr = max(pr.values())
11511151
if max_pr == 0:
11521152
return 0.0
1153-
return round(pr.get(pubkey, 0.0) / max_pr, 3)
1153+
return pr.get(pubkey, 0.0) / max_pr
11541154

11551155

11561156
def compute_chain_trust(
@@ -1176,7 +1176,7 @@ def compute_chain_trust(
11761176
penalized = base_trust * penalty
11771177
if integrity == 0.0:
11781178
penalized = min(penalized, 0.1)
1179-
return round(min(max(penalized, 0.0), 1.0), 3)
1179+
return min(max(penalized, 0.0), 1.0)
11801180

11811181
return base_trust
11821182

@@ -1249,4 +1249,4 @@ def compute_trust_with_decay(
12491249
+ weights["age"] * age_score
12501250
+ weights["entropy"] * normalized_entropy
12511251
)
1252-
return round(min(max(trust, 0.0), 1.0), 3)
1252+
return min(max(trust, 0.0), 1.0)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import networkx as nx
2+
import pytest
3+
4+
from trustchain.block import GENESIS_HASH
5+
from trustchain.record import create_record
6+
from trustchain.store import RecordStore
7+
from trustchain.trust import (
8+
compute_chain_trust,
9+
compute_transitive_trust,
10+
compute_trust,
11+
compute_trust_with_decay,
12+
)
13+
14+
15+
def _single_completed_record(identity_a, identity_b) -> tuple[RecordStore, int]:
16+
store = RecordStore()
17+
record = create_record(
18+
identity_a=identity_a,
19+
identity_b=identity_b,
20+
seq_a=0,
21+
seq_b=0,
22+
prev_hash_a=GENESIS_HASH,
23+
prev_hash_b=GENESIS_HASH,
24+
interaction_type="service",
25+
outcome="completed",
26+
)
27+
store.add_record(record)
28+
return store, record.timestamp
29+
30+
31+
def test_compute_trust_preserves_full_precision(identity_a, identity_b):
32+
store, _ = _single_completed_record(identity_a, identity_b)
33+
34+
trust = compute_trust(identity_a.pubkey_hex, store)
35+
36+
assert trust == pytest.approx(0.3025, abs=1e-12)
37+
assert trust != pytest.approx(0.302, abs=1e-12)
38+
39+
40+
def test_compute_trust_with_decay_preserves_full_precision(identity_a, identity_b):
41+
store, timestamp = _single_completed_record(identity_a, identity_b)
42+
43+
trust = compute_trust_with_decay(identity_a.pubkey_hex, store, now=timestamp)
44+
45+
assert trust == pytest.approx(0.3025, abs=1e-12)
46+
assert trust != pytest.approx(0.302, abs=1e-12)
47+
48+
49+
def test_compute_chain_trust_preserves_full_precision(identity_a, identity_b, monkeypatch):
50+
store, _ = _single_completed_record(identity_a, identity_b)
51+
52+
monkeypatch.setattr("trustchain.chain.compute_chain_integrity", lambda pubkey, records: 0.5)
53+
54+
trust = compute_chain_trust(identity_a.pubkey_hex, store)
55+
56+
expected = 0.3025 * (1.0 - 0.15 * (1.0 - 0.5))
57+
assert trust == pytest.approx(expected, abs=1e-12)
58+
assert trust != pytest.approx(round(expected, 3), abs=1e-12)
59+
60+
61+
class _DummyStore:
62+
def __init__(self, graph: nx.DiGraph):
63+
self._graph = graph
64+
65+
def get_interaction_graph(self) -> nx.DiGraph:
66+
return self._graph
67+
68+
69+
def test_compute_transitive_trust_preserves_full_precision(monkeypatch):
70+
graph = nx.DiGraph()
71+
graph.add_edge("agent-a", "agent-b", weight=1.0)
72+
store = _DummyStore(graph)
73+
74+
pagerank = {"agent-a": 0.1234567, "agent-b": 0.9876543}
75+
monkeypatch.setattr("trustchain.trust.nx.pagerank", lambda *_args, **_kwargs: pagerank)
76+
77+
trust = compute_transitive_trust("agent-a", store)
78+
expected = pagerank["agent-a"] / pagerank["agent-b"]
79+
80+
assert trust == pytest.approx(expected, abs=1e-12)
81+
assert trust != pytest.approx(round(expected, 3), abs=1e-12)

0 commit comments

Comments
 (0)