aethermesh 2.0.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.
@@ -0,0 +1,154 @@
1
+ """
2
+ AetherMesh — Semantic compatibility matrix (Pillar 3, Section 3.3.3).
3
+
4
+ A compatibility matrix C assigns to each pair of operation kinds a
5
+ verdict on whether concurrent deltas of those kinds that touch
6
+ overlapping targets may coexist.
7
+
8
+ The matrix is evaluated locally and cheaply, which is what makes the
9
+ admission layer lightweight. It is also coarse: it classifies operation
10
+ kinds, not program behaviour, and it cannot detect semantic breakage
11
+ that depends on program meaning.
12
+
13
+ This module provides:
14
+
15
+ - SemanticVerdict: enum (ok, resolve, conflict)
16
+ - CompatibilityMatrix: the matrix itself
17
+ - DEFAULT_MATRIX: the illustrative matrix from Table 3.1
18
+
19
+ Reference:
20
+ Baladi, S. (2026). AetherMesh v1.0.0. Section 3.3.3 and Table 3.1.
21
+ Zenodo. https://doi.org/10.5281/zenodo.24038038
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from dataclasses import dataclass, field
27
+ from enum import Enum
28
+ from typing import Dict, FrozenSet, Optional, Tuple
29
+
30
+ from aethermesh.models import DeltaKind
31
+
32
+
33
+ # =============================================================================
34
+ # SemanticVerdict
35
+ # =============================================================================
36
+
37
+ class SemanticVerdict(str, Enum):
38
+ """The three possible verdicts of the semantic compatibility matrix."""
39
+ OK = "ok"
40
+ RESOLVE = "resolve" # deterministic resolution required
41
+ CONFLICT = "conflict" # reject or quarantine
42
+
43
+
44
+ # =============================================================================
45
+ # CompatibilityMatrix
46
+ # =============================================================================
47
+
48
+ @dataclass
49
+ class CompatibilityMatrix:
50
+ """
51
+ A compatibility matrix over operation kinds.
52
+
53
+ The matrix is symmetric: C[a][b] == C[b][a] for all a, b.
54
+
55
+ Reference: Table 3.1 of the paper.
56
+ """
57
+ entries: Dict[Tuple[DeltaKind, DeltaKind], SemanticVerdict] = field(
58
+ default_factory=dict
59
+ )
60
+
61
+ def set(
62
+ self,
63
+ a: DeltaKind,
64
+ b: DeltaKind,
65
+ verdict: SemanticVerdict,
66
+ ) -> None:
67
+ """Set a matrix entry (both directions)."""
68
+ self.entries[(a, b)] = verdict
69
+ self.entries[(b, a)] = verdict
70
+
71
+ def get(
72
+ self,
73
+ a: DeltaKind,
74
+ b: DeltaKind,
75
+ ) -> SemanticVerdict:
76
+ """
77
+ Get the verdict for a pair of kinds. Defaults to OK if unset.
78
+ """
79
+ return self.entries.get((a, b), SemanticVerdict.OK)
80
+
81
+ def check(self, deltas: list) -> Tuple[SemanticVerdict, str]:
82
+ """
83
+ Check a set of deltas for semantic compatibility.
84
+
85
+ Returns the worst verdict among all pairs:
86
+ - If any pair is CONFLICT, returns CONFLICT.
87
+ - Else if any pair is RESOLVE, returns RESOLVE.
88
+ - Else returns OK.
89
+
90
+ The reason string lists the conflicting pair (if any).
91
+ """
92
+ worst = SemanticVerdict.OK
93
+ worst_pair: Optional[Tuple[str, str]] = None
94
+
95
+ for i, d_i in enumerate(deltas):
96
+ for d_j in deltas[i + 1:]:
97
+ v = self.get(d_i.kind, d_j.kind)
98
+ if v == SemanticVerdict.CONFLICT:
99
+ if worst != SemanticVerdict.CONFLICT:
100
+ worst = SemanticVerdict.CONFLICT
101
+ worst_pair = (d_i.kind.value, d_j.kind.value)
102
+ elif v == SemanticVerdict.RESOLVE:
103
+ if worst == SemanticVerdict.OK:
104
+ worst = SemanticVerdict.RESOLVE
105
+ worst_pair = (d_i.kind.value, d_j.kind.value)
106
+
107
+ if worst == SemanticVerdict.OK:
108
+ return worst, "ok"
109
+ if worst_pair:
110
+ return worst, f"{worst.value}: {worst_pair[0]} vs {worst_pair[1]}"
111
+ return worst, worst.value
112
+
113
+
114
+ # =============================================================================
115
+ # Default matrix (Table 3.1 of the paper)
116
+ # =============================================================================
117
+
118
+ def _build_default_matrix() -> CompatibilityMatrix:
119
+ """Build the illustrative matrix from Table 3.1 of the paper."""
120
+ m = CompatibilityMatrix()
121
+ OK = SemanticVerdict.OK
122
+ RESOLVE = SemanticVerdict.RESOLVE
123
+ CONFLICT = SemanticVerdict.CONFLICT
124
+
125
+ # Edit body
126
+ m.set(DeltaKind.EDIT_BODY, DeltaKind.EDIT_BODY, OK)
127
+ m.set(DeltaKind.EDIT_BODY, DeltaKind.RENAME_SYMBOL, OK)
128
+ m.set(DeltaKind.EDIT_BODY, DeltaKind.DELETE_SYMBOL, CONFLICT)
129
+ m.set(DeltaKind.EDIT_BODY, DeltaKind.CHANGE_SIGNATURE, OK)
130
+ m.set(DeltaKind.EDIT_BODY, DeltaKind.EDIT_METADATA, OK)
131
+
132
+ # Rename symbol
133
+ m.set(DeltaKind.RENAME_SYMBOL, DeltaKind.RENAME_SYMBOL, RESOLVE)
134
+ m.set(DeltaKind.RENAME_SYMBOL, DeltaKind.DELETE_SYMBOL, CONFLICT)
135
+ m.set(DeltaKind.RENAME_SYMBOL, DeltaKind.CHANGE_SIGNATURE, CONFLICT)
136
+ m.set(DeltaKind.RENAME_SYMBOL, DeltaKind.EDIT_METADATA, OK)
137
+
138
+ # Delete symbol
139
+ m.set(DeltaKind.DELETE_SYMBOL, DeltaKind.DELETE_SYMBOL, OK) # idempotent
140
+ m.set(DeltaKind.DELETE_SYMBOL, DeltaKind.CHANGE_SIGNATURE, CONFLICT)
141
+ m.set(DeltaKind.DELETE_SYMBOL, DeltaKind.EDIT_METADATA, OK)
142
+
143
+ # Change signature
144
+ m.set(DeltaKind.CHANGE_SIGNATURE, DeltaKind.CHANGE_SIGNATURE, RESOLVE)
145
+ m.set(DeltaKind.CHANGE_SIGNATURE, DeltaKind.EDIT_METADATA, OK)
146
+
147
+ # Edit metadata
148
+ m.set(DeltaKind.EDIT_METADATA, DeltaKind.EDIT_METADATA, RESOLVE) # LWW
149
+
150
+ return m
151
+
152
+
153
+ DEFAULT_MATRIX: CompatibilityMatrix = _build_default_matrix()
154
+ """The illustrative compatibility matrix from Table 3.1 of the paper."""
aethermesh/cli.py ADDED
@@ -0,0 +1,270 @@
1
+ """
2
+ AetherMesh — Command-line interface.
3
+
4
+ Provides three subcommands:
5
+
6
+ aethermesh simulate Run a simulated P2P testbed and measure the
7
+ three metrics from Section 5 of the paper.
8
+
9
+ aethermesh info Print version, DOI, and record information.
10
+
11
+ aethermesh verify Run a quick self-check (join laws, signature
12
+ verification, admission pipeline) without
13
+ a full simulation.
14
+
15
+ Usage:
16
+ python -m aethermesh.cli simulate --nodes 50 --fanout 5
17
+ python -m aethermesh.cli info
18
+ python -m aethermesh.cli verify
19
+
20
+ Reference:
21
+ Baladi, S. (2026). AetherMesh v1.0.0. Section 5.
22
+ Zenodo. https://doi.org/10.5281/zenodo.24038038
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import sys
30
+ from typing import Dict, List, Optional
31
+
32
+ from aethermesh.__version__ import (
33
+ __version__,
34
+ __author__,
35
+ __email__,
36
+ __doi__,
37
+ __license__,
38
+ __paper_version__,
39
+ __record_id__,
40
+ )
41
+
42
+
43
+ # =============================================================================
44
+ # Info
45
+ # =============================================================================
46
+
47
+ def cmd_info(args: argparse.Namespace) -> int:
48
+ """Print version, DOI, and record information."""
49
+ print("=" * 60)
50
+ print(f"AetherMesh v{__version__}")
51
+ print("=" * 60)
52
+ print(f"Author: {__author__}")
53
+ print(f"Email: {__email__}")
54
+ print(f"License: {__license__}")
55
+ print(f"Paper version: {__paper_version__}")
56
+ print(f"Record ID: {__record_id__}")
57
+ print(f"DOI: {__doi__}")
58
+ print(f"DOI URL: https://doi.org/{__doi__}")
59
+ print()
60
+ print("AetherMesh is the decentralization layer of the Aether family:")
61
+ print(" AetherGit (storage) — 10.5281/zenodo.23966896")
62
+ print(" AetherFlow (integration) — 10.5281/zenodo.23979129")
63
+ print(" AetherProof (verification) — 10.5281/zenodo.24013124")
64
+ print(" AetherMesh (decentralization) — 10.5281/zenodo.24038038")
65
+ print()
66
+ print("Status: Reference implementation. Preliminary results.")
67
+ print(" Not a production system. No real-world deployment.")
68
+ print("=" * 60)
69
+ return 0
70
+
71
+
72
+ # =============================================================================
73
+ # Simulate
74
+ # =============================================================================
75
+
76
+ def cmd_simulate(args: argparse.Namespace) -> int:
77
+ """Run the full testbed and report all three metrics."""
78
+ from aethermesh.simulation.testbed import Testbed, TestbedConfig
79
+
80
+ print("=" * 60)
81
+ print(f"AetherMesh v{__version__} — simulated testbed")
82
+ print("=" * 60)
83
+ print(f"Nodes: {args.nodes}")
84
+ print(f"Fanout: {args.fanout}")
85
+ print(f"Rounds: {args.max_rounds}")
86
+ print(f"Seed: {args.seed}")
87
+ print()
88
+
89
+ config = TestbedConfig(
90
+ n_nodes=args.nodes,
91
+ fanout=args.fanout,
92
+ max_rounds=args.max_rounds,
93
+ seed=args.seed,
94
+ )
95
+
96
+ # Metric 1: convergence
97
+ print("[1/3] Convergence latency")
98
+ tb1 = Testbed(config)
99
+ m1 = tb1.measure_convergence_latency(initial_delta_count=1)
100
+ print(f" Converged at time: {m1['converged_at_time']}")
101
+ print(f" Converged at round: {m1['converged_at_round']}")
102
+ print(f" Final fraction: {m1['final_fraction']:.2%}")
103
+ print()
104
+
105
+ # Metric 2: conflict overhead
106
+ print("[2/3] Conflict resolution overhead")
107
+ tb2 = Testbed(config)
108
+ m2 = tb2.measure_conflict_overhead(n_conflicting_deltas=10)
109
+ print(f" Conflicts flagged: {m2['conflicts_flagged']}")
110
+ print(f" Deadlocks: {m2['deadlocks']}")
111
+ print(f" Throughput overhead: {m2['throughput_overhead_pct']}%")
112
+ print()
113
+
114
+ # Metric 3: fault resilience
115
+ print("[3/3] Fault resilience")
116
+ tb3 = Testbed(config)
117
+ m3 = tb3.measure_fault_resilience(n_malformed=args.n_malformed)
118
+ print(f" Injected: {m3['injected']}")
119
+ print(f" Blocked: {m3['blocked']}")
120
+ print(f" Block rate: {m3['block_rate']:.2%}")
121
+ print()
122
+
123
+ # Summary
124
+ results = {
125
+ "version": __version__,
126
+ "config": {
127
+ "nodes": args.nodes,
128
+ "fanout": args.fanout,
129
+ "max_rounds": args.max_rounds,
130
+ "seed": args.seed,
131
+ },
132
+ "metric_1_convergence": m1,
133
+ "metric_2_conflict_overhead": m2,
134
+ "metric_3_fault_resilience": m3,
135
+ }
136
+
137
+ if args.output:
138
+ with open(args.output, "w") as f:
139
+ json.dump(results, f, indent=2)
140
+ print(f"Results written to {args.output}")
141
+ else:
142
+ print(json.dumps(results, indent=2))
143
+
144
+ return 0
145
+
146
+
147
+ # =============================================================================
148
+ # Verify
149
+ # =============================================================================
150
+
151
+ def cmd_verify(args: argparse.Namespace) -> int:
152
+ """Run a self-check without a full simulation."""
153
+ print("=" * 60)
154
+ print(f"AetherMesh v{__version__} — self-check")
155
+ print("=" * 60)
156
+
157
+ ok = True
158
+
159
+ # 1. Join laws on the Figure 4 example
160
+ print("[1/4] Join laws (Figure 4 example)")
161
+ from aethermesh.crdt.semilattice import build_figure4_example, verify_join_laws
162
+ lattice, elements = build_figure4_example()
163
+ laws_ok, violations = verify_join_laws(lattice, elements)
164
+ if laws_ok:
165
+ print(f" ✅ 7 elements, all 3 laws hold")
166
+ else:
167
+ print(f" ❌ {len(violations)} violations")
168
+ ok = False
169
+
170
+ # 2. Signature verification
171
+ print("[2/4] Ed25519 signature verification")
172
+ from aethermesh.bft import generate_keypair, sign_delta, verify_delta_signature
173
+ from aethermesh.models import Delta, DeltaKind
174
+ priv, pub = generate_keypair()
175
+ d = Delta.create(body={"op": "test"}, origin="test-agent", kind=DeltaKind.EDIT_BODY)
176
+ d_signed = sign_delta(d, priv, "key-1")
177
+ sig_ok = verify_delta_signature(d_signed, pub)
178
+ if sig_ok:
179
+ print(f" ✅ Valid signature verified")
180
+ else:
181
+ print(f" ❌ Signature verification failed")
182
+ ok = False
183
+
184
+ # 3. Admission pipeline
185
+ print("[3/4] Admission pipeline")
186
+ from aethermesh.bft import KeyRegistry
187
+ from aethermesh.admission.pipeline import AdmissionPipeline
188
+ from aethermesh.crdt.delta_state import DeltaStateCRDT
189
+ registry = KeyRegistry()
190
+ registry.register("test-agent", "key-1", pub)
191
+ pipeline = AdmissionPipeline(registry=registry)
192
+ crdt = DeltaStateCRDT(origin="test")
193
+ verdict = pipeline.admit(d_signed, crdt.replica)
194
+ if verdict.accepted:
195
+ print(f" ✅ Valid delta accepted")
196
+ else:
197
+ print(f" ❌ Valid delta rejected: {verdict.reason}")
198
+ ok = False
199
+
200
+ # 4. Reject unsigned
201
+ d_unsigned = Delta.create(body={"op": "test2"}, origin="test-agent", kind=DeltaKind.EDIT_BODY)
202
+ verdict2 = pipeline.admit(d_unsigned, crdt.replica)
203
+ if verdict2.verdict.value == "reject":
204
+ print(f" ✅ Unsigned delta rejected")
205
+ else:
206
+ print(f" ❌ Unsigned delta not rejected: {verdict2.verdict.value}")
207
+ ok = False
208
+
209
+ print("[4/4] Summary")
210
+ if ok:
211
+ print(" ✅ All checks passed")
212
+ print("=" * 60)
213
+ return 0
214
+ else:
215
+ print(" ❌ Some checks failed")
216
+ print("=" * 60)
217
+ return 1
218
+
219
+
220
+ # =============================================================================
221
+ # Entry point
222
+ # =============================================================================
223
+
224
+ def build_parser() -> argparse.ArgumentParser:
225
+ """Build the argument parser."""
226
+ parser = argparse.ArgumentParser(
227
+ prog="aethermesh",
228
+ description=(
229
+ f"AetherMesh v{__version__} — decentralized P2P agent synchronization"
230
+ ),
231
+ formatter_class=argparse.RawDescriptionHelpFormatter,
232
+ )
233
+ parser.add_argument(
234
+ "--version",
235
+ action="version",
236
+ version=f"aethermesh {__version__}",
237
+ )
238
+
239
+ sub = parser.add_subparsers(dest="command", required=True)
240
+
241
+ # info
242
+ p_info = sub.add_parser("info", help="Print version, DOI, and record information")
243
+ p_info.set_defaults(func=cmd_info)
244
+
245
+ # verify
246
+ p_verify = sub.add_parser("verify", help="Run a self-check (no simulation)")
247
+ p_verify.set_defaults(func=cmd_verify)
248
+
249
+ # simulate
250
+ p_sim = sub.add_parser("simulate", help="Run the full simulated testbed")
251
+ p_sim.add_argument("--nodes", type=int, default=50, help="Number of nodes (default: 50)")
252
+ p_sim.add_argument("--fanout", type=int, default=5, help="Gossip fanout (default: 5)")
253
+ p_sim.add_argument("--max-rounds", type=int, default=200, help="Max gossip rounds (default: 200)")
254
+ p_sim.add_argument("--seed", type=int, default=42, help="Random seed (default: 42)")
255
+ p_sim.add_argument("--n-malformed", type=int, default=100, help="Malformed deltas to inject (default: 100)")
256
+ p_sim.add_argument("--output", type=str, default=None, help="Write results JSON to this file")
257
+ p_sim.set_defaults(func=cmd_simulate)
258
+
259
+ return parser
260
+
261
+
262
+ def main(argv: Optional[List[str]] = None) -> int:
263
+ """Main entry point."""
264
+ parser = build_parser()
265
+ args = parser.parse_args(argv)
266
+ return args.func(args)
267
+
268
+
269
+ if __name__ == "__main__":
270
+ sys.exit(main())
@@ -0,0 +1,17 @@
1
+ """AetherMesh — CRDT module (Pillar 1)."""
2
+
3
+ from aethermesh.crdt.semilattice import (
4
+ JoinSemilattice,
5
+ DeltaSetLattice,
6
+ JoinLawError,
7
+ verify_join_laws,
8
+ )
9
+ from aethermesh.crdt.delta_state import DeltaStateCRDT
10
+
11
+ __all__ = [
12
+ "JoinSemilattice",
13
+ "DeltaSetLattice",
14
+ "JoinLawError",
15
+ "verify_join_laws",
16
+ "DeltaStateCRDT",
17
+ ]
@@ -0,0 +1,195 @@
1
+ """
2
+ AetherMesh — Delta-state CRDT (Pillar 1).
3
+
4
+ Implements delta-state replication (Section 3.1.3 of the paper).
5
+
6
+ AetherMesh uses delta-state replication as its default: nodes ship
7
+ deltas that are themselves lattice elements, so duplication and
8
+ reordering remain harmless, and fall back to full-state exchange only
9
+ when a digest comparison indicates that incremental repair would cost
10
+ more than a state transfer.
11
+
12
+ This module provides:
13
+
14
+ - DeltaStateCRDT: the main CRDT class, operating on a Replica
15
+
16
+ Reference:
17
+ Baladi, S. (2026). AetherMesh v1.0.0. Sections 3.1.1 and 3.1.3.
18
+ Zenodo. https://doi.org/10.5281/zenodo.24038038
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Dict, FrozenSet, Iterable, List, Optional, Set, Tuple
24
+
25
+ from aethermesh.models import Delta, Replica
26
+ from aethermesh.crdt.semilattice import DeltaSetLattice
27
+
28
+
29
+ class DeltaStateCRDT:
30
+ """
31
+ A delta-state CRDT over a `Replica`.
32
+
33
+ The CRDT wraps a `Replica` (which holds the admitted delta set)
34
+ and provides delta-based operations:
35
+
36
+ - apply(delta): join a single delta (idempotent)
37
+ - apply_many(deltas): join multiple deltas
38
+ - merge(other): join another CRDT's replica
39
+ - delta_since(digest): the deltas the peer is missing
40
+ - digest(): compact summary of local state
41
+ - full_state(): the entire admitted set (fallback)
42
+
43
+ The delta set lattice is the grow-only-set CRDT lifted to deltas
44
+ (Section 3.1.1), with set union as join.
45
+ """
46
+
47
+ def __init__(self, origin: str = "anonymous"):
48
+ self.replica = Replica(origin=origin)
49
+ self._lattice = DeltaSetLattice()
50
+
51
+ # -------------------------------------------------------------------------
52
+ # Core operations
53
+ # -------------------------------------------------------------------------
54
+
55
+ def apply(self, delta: Delta) -> bool:
56
+ """
57
+ Join a single delta into the local replica.
58
+
59
+ Returns True if the delta was newly admitted, False if it was
60
+ already present (idempotence) or deferred (missing parents).
61
+
62
+ A delta with missing parents is deferred (Section 3.3.2) — it is
63
+ not rejected.
64
+ """
65
+ if delta.id in self.replica:
66
+ return False
67
+
68
+ if not self.replica.has_parents(delta):
69
+ self.replica.deferred[delta.id] = delta
70
+ return False
71
+
72
+ self.replica.join(delta)
73
+ self._try_drain_deferred()
74
+ return True
75
+
76
+ def apply_many(self, deltas: Iterable[Delta]) -> int:
77
+ """Apply multiple deltas. Returns the count of newly admitted."""
78
+ count = 0
79
+ for d in deltas:
80
+ if self.apply(d):
81
+ count += 1
82
+ return count
83
+
84
+ def merge(self, other: "DeltaStateCRDT") -> int:
85
+ """
86
+ Merge another CRDT's replica into this one (set union).
87
+
88
+ Returns the count of newly admitted deltas.
89
+ """
90
+ count = 0
91
+ for did in other.replica.ids() - self.replica.ids():
92
+ delta = other.replica.admitted[did]
93
+ if self.apply(delta):
94
+ count += 1
95
+ return count
96
+
97
+ # -------------------------------------------------------------------------
98
+ # Delta exchange (Section 3.2.1)
99
+ # -------------------------------------------------------------------------
100
+
101
+ def digest(self) -> FrozenSet[str]:
102
+ """
103
+ A compact summary of local state: the set of admitted delta ids.
104
+
105
+ In production, this would be compressed into a version vector,
106
+ Merkle tree, or Bloom filter (Section 3.2.1). For the reference
107
+ implementation we use the raw id set, which is exact.
108
+ """
109
+ return self.replica.ids()
110
+
111
+ def delta_since(self, peer_digest: FrozenSet[str]) -> List[Delta]:
112
+ """
113
+ The deltas this replica has that the peer does not.
114
+
115
+ Used in step 2 of the anti-entropy exchange (Figure 5): the
116
+ responder returns "digest + list of deltas A appears to lack".
117
+ """
118
+ missing = self.replica.ids() - peer_digest
119
+ return [self.replica.admitted[did] for did in missing]
120
+
121
+ def full_state(self) -> List[Delta]:
122
+ """
123
+ The entire admitted set (state-based fallback).
124
+
125
+ Used when a digest comparison indicates that incremental repair
126
+ would cost more than a state transfer (Section 3.1.3).
127
+ """
128
+ return list(self.replica.admitted.values())
129
+
130
+ # -------------------------------------------------------------------------
131
+ # Deferred deltas (Section 3.3.2)
132
+ # -------------------------------------------------------------------------
133
+
134
+ def _try_drain_deferred(self) -> None:
135
+ """
136
+ Retry deferred deltas whose parents may now be present.
137
+
138
+ Called after each successful join.
139
+ """
140
+ progress = True
141
+ while progress:
142
+ progress = False
143
+ ready = [
144
+ d for d in self.replica.deferred.values()
145
+ if self.replica.has_parents(d)
146
+ ]
147
+ for d in ready:
148
+ del self.replica.deferred[d.id]
149
+ self.replica.join(d)
150
+ progress = True
151
+
152
+ def deferred_count(self) -> int:
153
+ """Number of deltas currently deferred (missing parents)."""
154
+ return len(self.replica.deferred)
155
+
156
+ # -------------------------------------------------------------------------
157
+ # Materialization
158
+ # -------------------------------------------------------------------------
159
+
160
+ def materialize(self) -> List[Delta]:
161
+ """Deterministic linearization of the admitted delta set."""
162
+ return self.replica.materialize()
163
+
164
+ # -------------------------------------------------------------------------
165
+ # Introspection
166
+ # -------------------------------------------------------------------------
167
+
168
+ def __len__(self) -> int:
169
+ return len(self.replica)
170
+
171
+ def __contains__(self, delta_id: str) -> bool:
172
+ return delta_id in self.replica
173
+
174
+ def ids(self) -> FrozenSet[str]:
175
+ return self.replica.ids()
176
+
177
+ def state_size(self) -> int:
178
+ """Number of admitted deltas."""
179
+ return len(self.replica)
180
+
181
+
182
+ # =============================================================================
183
+ # Sanity check on the join laws (Figure 4 example)
184
+ # =============================================================================
185
+
186
+ if __name__ == "__main__":
187
+ from aethermesh.crdt.semilattice import build_figure4_example, verify_join_laws
188
+
189
+ lattice, elements = build_figure4_example()
190
+ ok, violations = verify_join_laws(lattice, elements)
191
+ print(f"Lattice elements: {len(elements)}")
192
+ print(f"Join laws hold: {ok}")
193
+ if violations:
194
+ for v in violations[:5]:
195
+ print(f" - {v}")