Skip to content

Commit a78d8d3

Browse files
committed
add client-session t0 t1 proof
1 parent 0626be9 commit a78d8d3

3 files changed

Lines changed: 149 additions & 139 deletions

File tree

.ssot/registry.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

pkgs/tigrcorn-protocols/src/tigrcorn_protocols/client_session_coverage.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import asyncio
4+
from dataclasses import dataclass, field
35
from enum import Enum
46
from typing import Any, Mapping
57

@@ -181,6 +183,138 @@ def build_matrix_row(
181183
return row
182184

183185

186+
@dataclass
187+
class ClientSession:
188+
client_id: str
189+
connection_id: str
190+
session_id: str
191+
closed: bool = False
192+
payloads: list[str] = field(default_factory=list)
193+
194+
195+
class ClientSessionTopologyHarness:
196+
"""Runtime-facing carrier topology recorder for governed proof rows."""
197+
198+
def __init__(self, carrier: ProtocolCarrier, scope: SessionScope | None = None) -> None:
199+
self.carrier = carrier
200+
self.scope = scope
201+
self.sessions: dict[str, ClientSession] = {}
202+
self.events: list[dict[str, Any]] = []
203+
204+
def open(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
205+
self.sessions[session_id] = ClientSession(
206+
client_id=client_id,
207+
connection_id=connection_id,
208+
session_id=session_id,
209+
)
210+
self.events.append(self.record("open", client_id, connection_id, session_id, topology))
211+
212+
def send(
213+
self,
214+
client_id: str,
215+
connection_id: str,
216+
session_id: str,
217+
topology: ClientTopology,
218+
payload: str,
219+
**identifiers: Any,
220+
) -> None:
221+
session = self.session_for(client_id, connection_id, session_id)
222+
session.payloads.append(payload)
223+
self.events.append(
224+
self.record(
225+
"send",
226+
client_id,
227+
connection_id,
228+
session_id,
229+
topology,
230+
payload=payload,
231+
**identifiers,
232+
)
233+
)
234+
235+
async def send_async(
236+
self,
237+
client_id: str,
238+
connection_id: str,
239+
session_id: str,
240+
topology: ClientTopology,
241+
payload: str,
242+
delay: float = 0.0,
243+
**identifiers: Any,
244+
) -> None:
245+
await asyncio.sleep(delay)
246+
self.send(client_id, connection_id, session_id, topology, payload, **identifiers)
247+
248+
def close(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
249+
session = self.session_for(client_id, connection_id, session_id)
250+
session.closed = True
251+
self.events.append(self.record("close", client_id, connection_id, session_id, topology))
252+
253+
def record(
254+
self,
255+
subevent: str,
256+
client_id: str,
257+
connection_id: str,
258+
session_id: str,
259+
topology: ClientTopology,
260+
**extra: Any,
261+
) -> dict[str, Any]:
262+
return build_matrix_row(
263+
protocol_carrier=self.carrier,
264+
client_topology=topology,
265+
session_scope=self.scope,
266+
disposition=CoverageDisposition.COVERED,
267+
lifecycle_behavior=CoverageDisposition.COVERED,
268+
identity_isolation=CoverageDisposition.COVERED,
269+
ordering_behavior=CoverageDisposition.COVERED,
270+
pressure_mode=CoverageDisposition.REQUIRED,
271+
fault_mode=CoverageDisposition.REQUIRED,
272+
client_id=client_id,
273+
connection_id=connection_id,
274+
session_id=session_id,
275+
subevent=subevent,
276+
**extra,
277+
)
278+
279+
def session_for(self, client_id: str, connection_id: str, session_id: str) -> ClientSession:
280+
session = self.sessions[session_id]
281+
if session.client_id != client_id or session.connection_id != connection_id:
282+
raise PermissionError("cross-client or cross-connection session access rejected")
283+
if session.closed:
284+
raise RuntimeError("post-close send rejected")
285+
return session
286+
287+
288+
def sequential_pair(carrier: ProtocolCarrier, scope: SessionScope | None = None) -> ClientSessionTopologyHarness:
289+
topology = ClientTopology.SEQUENTIAL_CLIENTS
290+
harness = ClientSessionTopologyHarness(carrier, scope)
291+
harness.open("client-a", "conn-a", "session-a", topology)
292+
harness.send("client-a", "conn-a", "session-a", topology, "a-1")
293+
harness.close("client-a", "conn-a", "session-a", topology)
294+
harness.open("client-b", "conn-b", "session-b", topology)
295+
harness.send("client-b", "conn-b", "session-b", topology, "b-1")
296+
harness.close("client-b", "conn-b", "session-b", topology)
297+
return harness
298+
299+
300+
def bounded_interleaved_pair(
301+
carrier: ProtocolCarrier,
302+
scope: SessionScope | None = None,
303+
) -> ClientSessionTopologyHarness:
304+
topology = ClientTopology.BOUNDED_INTERLEAVED_CLIENTS
305+
harness = ClientSessionTopologyHarness(carrier, scope)
306+
harness.open("client-a", "conn-a", "session-a", topology)
307+
harness.open("client-b", "conn-b", "session-b", topology)
308+
for client_id, connection_id, session_id, payload in (
309+
("client-a", "conn-a", "session-a", "a-1"),
310+
("client-b", "conn-b", "session-b", "b-1"),
311+
("client-a", "conn-a", "session-a", "a-2"),
312+
("client-b", "conn-b", "session-b", "b-2"),
313+
):
314+
harness.send(client_id, connection_id, session_id, topology, payload)
315+
return harness
316+
317+
184318
__all__ = [
185319
"BEHAVIOR_AXIS_VALUES",
186320
"CLIENT_TOPOLOGY_VALUES",
@@ -192,12 +326,16 @@ def build_matrix_row(
192326
"REQUIRED_MATRIX_AXES",
193327
"SESSION_SCOPE_VALUES",
194328
"BehaviorAxis",
329+
"ClientSession",
330+
"ClientSessionTopologyHarness",
195331
"ClientTopology",
196332
"CoverageDisposition",
197333
"ProtocolCarrier",
198334
"SessionScope",
199335
"build_matrix_row",
336+
"bounded_interleaved_pair",
200337
"classify_default_session_scope",
338+
"sequential_pair",
201339
"validate_governed_identifiers",
202340
"validate_matrix_row",
203341
"validate_no_internal_lane",
Lines changed: 10 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,15 @@
11
from __future__ import annotations
22

3-
import asyncio
4-
from dataclasses import dataclass, field
5-
from typing import Any
6-
73
from tigrcorn_protocols.client_session_coverage import (
8-
ClientTopology,
9-
CoverageDisposition,
10-
ProtocolCarrier,
11-
SessionScope,
12-
build_matrix_row,
4+
ClientSession,
5+
ClientSessionTopologyHarness,
6+
bounded_interleaved_pair,
7+
sequential_pair,
138
)
149

15-
16-
@dataclass
17-
class ClientSession:
18-
client_id: str
19-
connection_id: str
20-
session_id: str
21-
closed: bool = False
22-
payloads: list[str] = field(default_factory=list)
23-
24-
25-
class ClientSessionTopologyHarness:
26-
def __init__(self, carrier: ProtocolCarrier, scope: SessionScope | None = None) -> None:
27-
self.carrier = carrier
28-
self.scope = scope
29-
self.sessions: dict[str, ClientSession] = {}
30-
self.events: list[dict[str, Any]] = []
31-
32-
def open(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
33-
self.sessions[session_id] = ClientSession(
34-
client_id=client_id,
35-
connection_id=connection_id,
36-
session_id=session_id,
37-
)
38-
self.events.append(self.record("open", client_id, connection_id, session_id, topology))
39-
40-
def send(
41-
self,
42-
client_id: str,
43-
connection_id: str,
44-
session_id: str,
45-
topology: ClientTopology,
46-
payload: str,
47-
**identifiers: Any,
48-
) -> None:
49-
session = self._session_for(client_id, connection_id, session_id)
50-
session.payloads.append(payload)
51-
self.events.append(
52-
self.record(
53-
"send",
54-
client_id,
55-
connection_id,
56-
session_id,
57-
topology,
58-
payload=payload,
59-
**identifiers,
60-
)
61-
)
62-
63-
async def send_async(
64-
self,
65-
client_id: str,
66-
connection_id: str,
67-
session_id: str,
68-
topology: ClientTopology,
69-
payload: str,
70-
delay: float = 0.0,
71-
**identifiers: Any,
72-
) -> None:
73-
await asyncio.sleep(delay)
74-
self.send(client_id, connection_id, session_id, topology, payload, **identifiers)
75-
76-
def close(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
77-
session = self._session_for(client_id, connection_id, session_id)
78-
session.closed = True
79-
self.events.append(self.record("close", client_id, connection_id, session_id, topology))
80-
81-
def record(
82-
self,
83-
subevent: str,
84-
client_id: str,
85-
connection_id: str,
86-
session_id: str,
87-
topology: ClientTopology,
88-
**extra: Any,
89-
) -> dict[str, Any]:
90-
return build_matrix_row(
91-
protocol_carrier=self.carrier,
92-
client_topology=topology,
93-
session_scope=self.scope,
94-
disposition=CoverageDisposition.COVERED,
95-
lifecycle_behavior=CoverageDisposition.COVERED,
96-
identity_isolation=CoverageDisposition.COVERED,
97-
ordering_behavior=CoverageDisposition.COVERED,
98-
pressure_mode=CoverageDisposition.REQUIRED,
99-
fault_mode=CoverageDisposition.REQUIRED,
100-
client_id=client_id,
101-
connection_id=connection_id,
102-
session_id=session_id,
103-
subevent=subevent,
104-
**extra,
105-
)
106-
107-
def _session_for(self, client_id: str, connection_id: str, session_id: str) -> ClientSession:
108-
session = self.sessions[session_id]
109-
if session.client_id != client_id or session.connection_id != connection_id:
110-
raise PermissionError("cross-client or cross-connection session access rejected")
111-
if session.closed:
112-
raise RuntimeError("post-close send rejected")
113-
return session
114-
115-
116-
def sequential_pair(carrier: ProtocolCarrier, scope: SessionScope | None = None) -> ClientSessionTopologyHarness:
117-
topology = ClientTopology.SEQUENTIAL_CLIENTS
118-
harness = ClientSessionTopologyHarness(carrier, scope)
119-
harness.open("client-a", "conn-a", "session-a", topology)
120-
harness.send("client-a", "conn-a", "session-a", topology, "a-1")
121-
harness.close("client-a", "conn-a", "session-a", topology)
122-
harness.open("client-b", "conn-b", "session-b", topology)
123-
harness.send("client-b", "conn-b", "session-b", topology, "b-1")
124-
harness.close("client-b", "conn-b", "session-b", topology)
125-
return harness
126-
127-
128-
def bounded_interleaved_pair(
129-
carrier: ProtocolCarrier,
130-
scope: SessionScope | None = None,
131-
) -> ClientSessionTopologyHarness:
132-
topology = ClientTopology.BOUNDED_INTERLEAVED_CLIENTS
133-
harness = ClientSessionTopologyHarness(carrier, scope)
134-
harness.open("client-a", "conn-a", "session-a", topology)
135-
harness.open("client-b", "conn-b", "session-b", topology)
136-
for client_id, connection_id, session_id, payload in (
137-
("client-a", "conn-a", "session-a", "a-1"),
138-
("client-b", "conn-b", "session-b", "b-1"),
139-
("client-a", "conn-a", "session-a", "a-2"),
140-
("client-b", "conn-b", "session-b", "b-2"),
141-
):
142-
harness.send(client_id, connection_id, session_id, topology, payload)
143-
return harness
10+
__all__ = [
11+
"ClientSession",
12+
"ClientSessionTopologyHarness",
13+
"bounded_interleaved_pair",
14+
"sequential_pair",
15+
]

0 commit comments

Comments
 (0)