seenrelay 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vlad Belciug
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this client wrapper software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including without
8
+ limitation the rights to use, copy, modify, merge, publish, distribute,
9
+ sublicense, and/or sell copies of the Software, and to permit persons to whom
10
+ the Software is 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,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: seenrelay
3
+ Version: 0.1.0
4
+ Summary: Deterministic zero-dependency SeenRelay client for repeated source-backed validation workflows.
5
+ Author: Vlad Belciug
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://seenrelay.com/clients
8
+ Project-URL: Repository, https://github.com/ovladon/seenrelay
9
+ Project-URL: Issues, https://github.com/ovladon/seenrelay/issues
10
+ Keywords: ai-agents,validation,freshness,cache,mcp
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # SeenRelay Python client
19
+
20
+ Deterministic, standard-library-only client for placing SeenRelay CHECK directly in front of repeated source-backed validation.
21
+
22
+ ## Install
23
+
24
+ Package metadata is prepared and continuously validated for registry publication. Until the package is published, use the repository copy from `clients/python/`.
25
+
26
+ ## Smallest integration: bind once, one line per revalidation
27
+
28
+ ```python
29
+ from seenrelay import SeenRelayClient
30
+ from seenrelay_easy import protect_validation
31
+
32
+ relay = SeenRelayClient()
33
+
34
+ validate_price = protect_validation(
35
+ relay,
36
+ fact=fact,
37
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
38
+ )
39
+
40
+ value = validate_price(known_value)
41
+ ```
42
+
43
+ That is strict shadow mode by default: SeenRelay CHECK runs, your original validation still runs, and the independently obtained result is OBSERVEd best-effort. Nothing is skipped merely because SeenRelay is installed.
44
+
45
+ Only after measurement and policy approval should you add an explicit reuse policy:
46
+
47
+ ```python
48
+ from seenrelay import reuse_known_on_same_observed
49
+
50
+ validate_price = protect_validation(
51
+ relay,
52
+ fact=fact,
53
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
54
+ reuse=reuse_known_on_same_observed,
55
+ )
56
+ ```
57
+
58
+ ## Direct client form
59
+
60
+ The same behavior is available without the convenience binder:
61
+
62
+ ```python
63
+ value = relay.guard(
64
+ fact=fact,
65
+ known_value=known_value,
66
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
67
+ )
68
+ ```
69
+
70
+ Without an explicit reuse policy, validation is never skipped.
71
+
72
+ ## Prove value before enabling reuse
73
+
74
+ ```python
75
+ from seenrelay import SeenRelayClient
76
+ from seenrelay_shadow import SeenRelayShadowProof
77
+
78
+ proof = SeenRelayShadowProof(SeenRelayClient())
79
+
80
+ value = proof.guard(
81
+ fact=fact,
82
+ known_value=known_value,
83
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
84
+ )
85
+
86
+ print(proof.report(
87
+ avoided_validation_cost=0.01, # use your own invoice/cost unit
88
+ ))
89
+ ```
90
+
91
+ Shadow Proof always keeps the original validation. It measures CHECK status distribution, validation time and SeenRelay request latency locally. Potential savings count only `SAME_OBSERVED` calls and subtract caller-supplied request costs. Savings from conditional ETag / Last-Modified requests are deliberately excluded unless measured separately by the application.
92
+
93
+ Use SeenRelay around repeated validation that is materially more expensive than the preflight: paid search, scraping/proxy work, browser or extraction calls, rate-limited APIs, model-assisted parsing, or multi-step validation. It is generally a poor fit for a cheap one-off GET.
94
+
95
+ For fleet economics and current public-price illustrations, see `https://seenrelay.com/economics` and `docs/ECONOMICS_LAB.md` in the repository.
96
+
97
+ ## License
98
+
99
+ The client package is MIT licensed. The hosted SeenRelay service implementation remains governed by the repository root license.
@@ -0,0 +1,82 @@
1
+ # SeenRelay Python client
2
+
3
+ Deterministic, standard-library-only client for placing SeenRelay CHECK directly in front of repeated source-backed validation.
4
+
5
+ ## Install
6
+
7
+ Package metadata is prepared and continuously validated for registry publication. Until the package is published, use the repository copy from `clients/python/`.
8
+
9
+ ## Smallest integration: bind once, one line per revalidation
10
+
11
+ ```python
12
+ from seenrelay import SeenRelayClient
13
+ from seenrelay_easy import protect_validation
14
+
15
+ relay = SeenRelayClient()
16
+
17
+ validate_price = protect_validation(
18
+ relay,
19
+ fact=fact,
20
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
21
+ )
22
+
23
+ value = validate_price(known_value)
24
+ ```
25
+
26
+ That is strict shadow mode by default: SeenRelay CHECK runs, your original validation still runs, and the independently obtained result is OBSERVEd best-effort. Nothing is skipped merely because SeenRelay is installed.
27
+
28
+ Only after measurement and policy approval should you add an explicit reuse policy:
29
+
30
+ ```python
31
+ from seenrelay import reuse_known_on_same_observed
32
+
33
+ validate_price = protect_validation(
34
+ relay,
35
+ fact=fact,
36
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
37
+ reuse=reuse_known_on_same_observed,
38
+ )
39
+ ```
40
+
41
+ ## Direct client form
42
+
43
+ The same behavior is available without the convenience binder:
44
+
45
+ ```python
46
+ value = relay.guard(
47
+ fact=fact,
48
+ known_value=known_value,
49
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
50
+ )
51
+ ```
52
+
53
+ Without an explicit reuse policy, validation is never skipped.
54
+
55
+ ## Prove value before enabling reuse
56
+
57
+ ```python
58
+ from seenrelay import SeenRelayClient
59
+ from seenrelay_shadow import SeenRelayShadowProof
60
+
61
+ proof = SeenRelayShadowProof(SeenRelayClient())
62
+
63
+ value = proof.guard(
64
+ fact=fact,
65
+ known_value=known_value,
66
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
67
+ )
68
+
69
+ print(proof.report(
70
+ avoided_validation_cost=0.01, # use your own invoice/cost unit
71
+ ))
72
+ ```
73
+
74
+ Shadow Proof always keeps the original validation. It measures CHECK status distribution, validation time and SeenRelay request latency locally. Potential savings count only `SAME_OBSERVED` calls and subtract caller-supplied request costs. Savings from conditional ETag / Last-Modified requests are deliberately excluded unless measured separately by the application.
75
+
76
+ Use SeenRelay around repeated validation that is materially more expensive than the preflight: paid search, scraping/proxy work, browser or extraction calls, rate-limited APIs, model-assisted parsing, or multi-step validation. It is generally a poor fit for a cheap one-off GET.
77
+
78
+ For fleet economics and current public-price illustrations, see `https://seenrelay.com/economics` and `docs/ECONOMICS_LAB.md` in the repository.
79
+
80
+ ## License
81
+
82
+ The client package is MIT licensed. The hosted SeenRelay service implementation remains governed by the repository root license.
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "seenrelay"
7
+ version = "0.1.0"
8
+ description = "Deterministic zero-dependency SeenRelay client for repeated source-backed validation workflows."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Vlad Belciug" }
14
+ ]
15
+ keywords = ["ai-agents", "validation", "freshness", "cache", "mcp"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent"
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.urls]
23
+ Homepage = "https://seenrelay.com/clients"
24
+ Repository = "https://github.com/ovladon/seenrelay"
25
+ Issues = "https://github.com/ovladon/seenrelay/issues"
26
+
27
+ [tool.setuptools]
28
+ py-modules = ["seenrelay", "seenrelay_shadow", "seenrelay_easy"]
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: seenrelay
3
+ Version: 0.1.0
4
+ Summary: Deterministic zero-dependency SeenRelay client for repeated source-backed validation workflows.
5
+ Author: Vlad Belciug
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://seenrelay.com/clients
8
+ Project-URL: Repository, https://github.com/ovladon/seenrelay
9
+ Project-URL: Issues, https://github.com/ovladon/seenrelay/issues
10
+ Keywords: ai-agents,validation,freshness,cache,mcp
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # SeenRelay Python client
19
+
20
+ Deterministic, standard-library-only client for placing SeenRelay CHECK directly in front of repeated source-backed validation.
21
+
22
+ ## Install
23
+
24
+ Package metadata is prepared and continuously validated for registry publication. Until the package is published, use the repository copy from `clients/python/`.
25
+
26
+ ## Smallest integration: bind once, one line per revalidation
27
+
28
+ ```python
29
+ from seenrelay import SeenRelayClient
30
+ from seenrelay_easy import protect_validation
31
+
32
+ relay = SeenRelayClient()
33
+
34
+ validate_price = protect_validation(
35
+ relay,
36
+ fact=fact,
37
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
38
+ )
39
+
40
+ value = validate_price(known_value)
41
+ ```
42
+
43
+ That is strict shadow mode by default: SeenRelay CHECK runs, your original validation still runs, and the independently obtained result is OBSERVEd best-effort. Nothing is skipped merely because SeenRelay is installed.
44
+
45
+ Only after measurement and policy approval should you add an explicit reuse policy:
46
+
47
+ ```python
48
+ from seenrelay import reuse_known_on_same_observed
49
+
50
+ validate_price = protect_validation(
51
+ relay,
52
+ fact=fact,
53
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
54
+ reuse=reuse_known_on_same_observed,
55
+ )
56
+ ```
57
+
58
+ ## Direct client form
59
+
60
+ The same behavior is available without the convenience binder:
61
+
62
+ ```python
63
+ value = relay.guard(
64
+ fact=fact,
65
+ known_value=known_value,
66
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
67
+ )
68
+ ```
69
+
70
+ Without an explicit reuse policy, validation is never skipped.
71
+
72
+ ## Prove value before enabling reuse
73
+
74
+ ```python
75
+ from seenrelay import SeenRelayClient
76
+ from seenrelay_shadow import SeenRelayShadowProof
77
+
78
+ proof = SeenRelayShadowProof(SeenRelayClient())
79
+
80
+ value = proof.guard(
81
+ fact=fact,
82
+ known_value=known_value,
83
+ validate=lambda ctx: expensive_validation(ctx.conditional_headers),
84
+ )
85
+
86
+ print(proof.report(
87
+ avoided_validation_cost=0.01, # use your own invoice/cost unit
88
+ ))
89
+ ```
90
+
91
+ Shadow Proof always keeps the original validation. It measures CHECK status distribution, validation time and SeenRelay request latency locally. Potential savings count only `SAME_OBSERVED` calls and subtract caller-supplied request costs. Savings from conditional ETag / Last-Modified requests are deliberately excluded unless measured separately by the application.
92
+
93
+ Use SeenRelay around repeated validation that is materially more expensive than the preflight: paid search, scraping/proxy work, browser or extraction calls, rate-limited APIs, model-assisted parsing, or multi-step validation. It is generally a poor fit for a cheap one-off GET.
94
+
95
+ For fleet economics and current public-price illustrations, see `https://seenrelay.com/economics` and `docs/ECONOMICS_LAB.md` in the repository.
96
+
97
+ ## License
98
+
99
+ The client package is MIT licensed. The hosted SeenRelay service implementation remains governed by the repository root license.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ seenrelay.py
5
+ seenrelay_easy.py
6
+ seenrelay_shadow.py
7
+ seenrelay.egg-info/PKG-INFO
8
+ seenrelay.egg-info/SOURCES.txt
9
+ seenrelay.egg-info/dependency_links.txt
10
+ seenrelay.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ seenrelay
2
+ seenrelay_easy
3
+ seenrelay_shadow
@@ -0,0 +1,383 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ import json
6
+ import threading
7
+ import time
8
+ import uuid
9
+ from typing import Any, Callable, Generic, Mapping, MutableMapping, Optional, TypeVar
10
+ from urllib import request as urllib_request
11
+ from urllib.error import HTTPError
12
+
13
+ T = TypeVar("T")
14
+ JsonValue = Any
15
+
16
+ @dataclass(frozen=True)
17
+ class TransportResponse:
18
+ status: int
19
+ headers: Mapping[str, str]
20
+ body: Any
21
+
22
+ @dataclass(frozen=True)
23
+ class ValidationContext:
24
+ check: Optional[Mapping[str, Any]]
25
+ conditional_headers: Mapping[str, str]
26
+
27
+ @dataclass(frozen=True)
28
+ class ReuseDecision(Generic[T]):
29
+ reuse: bool
30
+ value: Optional[T] = None
31
+
32
+ @dataclass(frozen=True)
33
+ class GuardDetailedResult(Generic[T]):
34
+ value: T
35
+ path: str
36
+ check: Optional[Mapping[str, Any]]
37
+ check_ok: bool
38
+ observe_ok: Optional[bool]
39
+ observe_deferred: bool = False
40
+ check_error: Optional[str] = None
41
+ observe_error: Optional[str] = None
42
+
43
+ @dataclass(frozen=True)
44
+ class TelemetrySnapshot:
45
+ guard_calls: int
46
+ check_calls: int
47
+ check_successes: int
48
+ check_failures: int
49
+ check_timeouts: int
50
+ check_network_requests: int
51
+ check_coalesced: int
52
+ check_network_latency_ms_total: float
53
+ check_network_latency_ms_max: float
54
+ check_network_latency_ms_average: float
55
+ reuse_hits: int
56
+ validation_calls: int
57
+ conditional_hint_validations: int
58
+ observe_attempts: int
59
+ observe_scheduled: int
60
+ observe_schedule_failures: int
61
+ observe_successes: int
62
+ observe_failures: int
63
+ observe_timeouts: int
64
+ observe_network_requests: int
65
+ observe_network_latency_ms_total: float
66
+ observe_network_latency_ms_max: float
67
+ observe_network_latency_ms_average: float
68
+
69
+ @dataclass(frozen=True)
70
+ class ReuseEconomicsEstimate:
71
+ gross_avoided_validation_cost: float
72
+ relay_request_cost: float
73
+ net_estimated_savings: float
74
+ excludes_conditional_request_savings: bool = True
75
+
76
+ Transport = Callable[[str, str, Mapping[str, str], Any, float], TransportResponse]
77
+ ReusePolicy = Callable[[Mapping[str, Any], T], ReuseDecision[T]]
78
+ ValidatorMetadata = Mapping[str, Any]
79
+ ObservationFactory = Callable[[T, ValidationContext], Optional[ValidatorMetadata]]
80
+ Validator = Callable[[ValidationContext], T]
81
+
82
+ class _InflightCheck:
83
+ __slots__ = ("event", "result", "error")
84
+ def __init__(self) -> None:
85
+ self.event = threading.Event()
86
+ self.result: Optional[Mapping[str, Any]] = None
87
+ self.error: Optional[Exception] = None
88
+
89
+ def _empty_metrics() -> MutableMapping[str, float | int]:
90
+ return {
91
+ "guard_calls": 0, "check_calls": 0, "check_successes": 0, "check_failures": 0,
92
+ "check_timeouts": 0, "check_network_requests": 0, "check_coalesced": 0,
93
+ "check_network_latency_ms_total": 0.0, "check_network_latency_ms_max": 0.0,
94
+ "reuse_hits": 0, "validation_calls": 0, "conditional_hint_validations": 0,
95
+ "observe_attempts": 0, "observe_scheduled": 0, "observe_schedule_failures": 0,
96
+ "observe_successes": 0, "observe_failures": 0,
97
+ "observe_timeouts": 0, "observe_network_requests": 0,
98
+ "observe_network_latency_ms_total": 0.0, "observe_network_latency_ms_max": 0.0,
99
+ }
100
+
101
+ def reuse_known_on_same_observed(check: Mapping[str, Any], known_value: T) -> ReuseDecision[T]:
102
+ if check.get("status") == "SAME_OBSERVED":
103
+ return ReuseDecision(reuse=True, value=known_value)
104
+ return ReuseDecision(reuse=False)
105
+
106
+ def _default_transport(method: str, url: str, headers: Mapping[str, str], body: Any, timeout: float) -> TransportResponse:
107
+ data = json.dumps(body, separators=(",", ":"), allow_nan=False).encode("utf-8")
108
+ req = urllib_request.Request(url, data=data, method=method, headers=dict(headers))
109
+ try:
110
+ with urllib_request.urlopen(req, timeout=timeout) as response:
111
+ parsed = json.loads(response.read().decode("utf-8"))
112
+ return TransportResponse(status=response.status, headers={k.lower(): v for k, v in response.headers.items()}, body=parsed)
113
+ except HTTPError as exc:
114
+ raw = exc.read().decode("utf-8", errors="replace")
115
+ try:
116
+ parsed = json.loads(raw)
117
+ except json.JSONDecodeError:
118
+ parsed = {"error": raw}
119
+ return TransportResponse(status=exc.code, headers={k.lower(): v for k, v in exc.headers.items()}, body=parsed)
120
+
121
+ def _safe_conditional_headers(check: Optional[Mapping[str, Any]]) -> Mapping[str, str]:
122
+ if not check:
123
+ return {}
124
+ hint = check.get("conditional_request_hint")
125
+ if not isinstance(hint, Mapping):
126
+ return {}
127
+ name = hint.get("request_header")
128
+ value = hint.get("header_value")
129
+ if name not in {"If-None-Match", "If-Modified-Since"}:
130
+ return {}
131
+ if not isinstance(value, str) or not value or "\r" in value or "\n" in value:
132
+ return {}
133
+ return {str(name): value}
134
+
135
+ def _positive_finite(value: float, name: str) -> float:
136
+ number = float(value)
137
+ if not (number > 0.0 and number < float("inf")):
138
+ raise ValueError(f"{name} must be a positive finite number")
139
+ return number
140
+
141
+ def _non_negative_finite(value: float, name: str) -> float:
142
+ number = float(value)
143
+ if not (number >= 0.0 and number < float("inf")):
144
+ raise ValueError(f"{name} must be a non-negative finite number")
145
+ return number
146
+
147
+ def _coalescing_key(fact: Mapping[str, Any], known_value: Any, max_age_seconds: Optional[int]) -> Optional[str]:
148
+ payload: MutableMapping[str, Any] = {"fact": dict(fact), "known_value": known_value}
149
+ if max_age_seconds is not None:
150
+ payload["max_age_seconds"] = max_age_seconds
151
+ try:
152
+ return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
153
+ except (TypeError, ValueError):
154
+ return None
155
+
156
+ class SeenRelayClient:
157
+ def __init__(self, *, base_url: str = "https://seenrelay.com", client_hint: Optional[str] = None,
158
+ initial_lease: Optional[str] = None, on_lease: Optional[Callable[[str], None]] = None,
159
+ check_timeout_seconds: float = 1.0, observe_timeout_seconds: float = 0.75,
160
+ coalesce_checks: bool = True,
161
+ schedule_observe: Optional[Callable[[Callable[[], None]], None]] = None,
162
+ on_deferred_observe_error: Optional[Callable[[Exception], None]] = None,
163
+ transport: Transport = _default_transport) -> None:
164
+ self.base_url = base_url.rstrip("/")
165
+ self.client_hint = client_hint.strip() if client_hint and client_hint.strip() else None
166
+ self.lease = initial_lease.strip() if initial_lease and initial_lease.strip() else None
167
+ self.on_lease = on_lease
168
+ self.check_timeout_seconds = _positive_finite(check_timeout_seconds, "check_timeout_seconds")
169
+ self.observe_timeout_seconds = _positive_finite(observe_timeout_seconds, "observe_timeout_seconds")
170
+ self.coalesce_checks = bool(coalesce_checks)
171
+ self.schedule_observe = schedule_observe
172
+ self.on_deferred_observe_error = on_deferred_observe_error
173
+ self.transport = transport
174
+ self._state_lock = threading.Lock()
175
+ self._inflight_checks: MutableMapping[str, _InflightCheck] = {}
176
+ self._metrics: MutableMapping[str, float | int] = _empty_metrics()
177
+
178
+ def _metric_add(self, name: str, amount: float | int = 1) -> None:
179
+ with self._state_lock:
180
+ self._metrics[name] = self._metrics[name] + amount
181
+
182
+ def _metric_latency(self, prefix: str, elapsed_ms: float) -> None:
183
+ with self._state_lock:
184
+ total = f"{prefix}_latency_ms_total"
185
+ maximum = f"{prefix}_latency_ms_max"
186
+ self._metrics[total] = float(self._metrics[total]) + elapsed_ms
187
+ self._metrics[maximum] = max(float(self._metrics[maximum]), elapsed_ms)
188
+
189
+ def get_telemetry(self) -> TelemetrySnapshot:
190
+ with self._state_lock:
191
+ m = dict(self._metrics)
192
+ check_requests = int(m["check_network_requests"])
193
+ observe_requests = int(m["observe_network_requests"])
194
+ return TelemetrySnapshot(
195
+ guard_calls=int(m["guard_calls"]), check_calls=int(m["check_calls"]), check_successes=int(m["check_successes"]),
196
+ check_failures=int(m["check_failures"]), check_timeouts=int(m["check_timeouts"]), check_network_requests=check_requests,
197
+ check_coalesced=int(m["check_coalesced"]), check_network_latency_ms_total=float(m["check_network_latency_ms_total"]),
198
+ check_network_latency_ms_max=float(m["check_network_latency_ms_max"]),
199
+ check_network_latency_ms_average=(float(m["check_network_latency_ms_total"]) / check_requests if check_requests else 0.0),
200
+ reuse_hits=int(m["reuse_hits"]), validation_calls=int(m["validation_calls"]),
201
+ conditional_hint_validations=int(m["conditional_hint_validations"]), observe_attempts=int(m["observe_attempts"]),
202
+ observe_scheduled=int(m["observe_scheduled"]), observe_schedule_failures=int(m["observe_schedule_failures"]),
203
+ observe_successes=int(m["observe_successes"]), observe_failures=int(m["observe_failures"]),
204
+ observe_timeouts=int(m["observe_timeouts"]), observe_network_requests=observe_requests,
205
+ observe_network_latency_ms_total=float(m["observe_network_latency_ms_total"]),
206
+ observe_network_latency_ms_max=float(m["observe_network_latency_ms_max"]),
207
+ observe_network_latency_ms_average=(float(m["observe_network_latency_ms_total"]) / observe_requests if observe_requests else 0.0),
208
+ )
209
+
210
+ def reset_telemetry(self) -> None:
211
+ with self._state_lock:
212
+ self._metrics = _empty_metrics()
213
+
214
+ def estimate_reuse_economics(self, *, avoided_validation_cost: float, check_request_cost: float = 0.0,
215
+ observe_request_cost: float = 0.0) -> ReuseEconomicsEstimate:
216
+ avoided = _positive_finite(avoided_validation_cost, "avoided_validation_cost")
217
+ check_cost = _non_negative_finite(check_request_cost, "check_request_cost")
218
+ observe_cost = _non_negative_finite(observe_request_cost, "observe_request_cost")
219
+ m = self.get_telemetry()
220
+ gross = m.reuse_hits * avoided
221
+ relay = m.check_network_requests * check_cost + m.observe_network_requests * observe_cost
222
+ return ReuseEconomicsEstimate(gross_avoided_validation_cost=gross, relay_request_cost=relay, net_estimated_savings=gross - relay)
223
+
224
+ def guard(self, *, fact: Mapping[str, Any], known_value: T, validate: Validator[T], reuse: Optional[ReusePolicy[T]] = None,
225
+ max_age_seconds: Optional[int] = None, observation: Optional[ObservationFactory[T]] = None) -> T:
226
+ return self.guard_detailed(fact=fact, known_value=known_value, validate=validate, reuse=reuse,
227
+ max_age_seconds=max_age_seconds, observation=observation).value
228
+
229
+ def guard_detailed(self, *, fact: Mapping[str, Any], known_value: T, validate: Validator[T],
230
+ reuse: Optional[ReusePolicy[T]] = None, max_age_seconds: Optional[int] = None,
231
+ observation: Optional[ObservationFactory[T]] = None) -> GuardDetailedResult[T]:
232
+ self._metric_add("guard_calls")
233
+ self._metric_add("check_calls")
234
+ check: Optional[Mapping[str, Any]] = None
235
+ check_ok = False
236
+ check_error: Optional[str] = None
237
+ try:
238
+ check = self._check(fact, known_value, max_age_seconds)
239
+ check_ok = True
240
+ self._metric_add("check_successes")
241
+ except Exception as exc:
242
+ self._metric_add("check_failures")
243
+ if isinstance(exc, TimeoutError): self._metric_add("check_timeouts")
244
+ check_error = str(exc)
245
+ if check is not None and reuse is not None:
246
+ decision = reuse(check, known_value)
247
+ if decision.reuse:
248
+ self._metric_add("reuse_hits")
249
+ return GuardDetailedResult(value=decision.value, path="reused", check=check, check_ok=check_ok,
250
+ observe_ok=None, check_error=check_error) # type: ignore[arg-type]
251
+ conditional_headers = _safe_conditional_headers(check)
252
+ context = ValidationContext(check=check, conditional_headers=conditional_headers)
253
+ self._metric_add("validation_calls")
254
+ if conditional_headers: self._metric_add("conditional_hint_validations")
255
+ value = validate(context)
256
+ observe_ok: Optional[bool] = None
257
+ observe_error: Optional[str] = None
258
+ observe_deferred = False
259
+ self._metric_add("observe_attempts")
260
+
261
+ def perform_observe(*, deferred: bool) -> tuple[bool, Optional[str]]:
262
+ try:
263
+ metadata = observation(value, context) if observation else None
264
+ self._observe(fact, value, metadata)
265
+ self._metric_add("observe_successes")
266
+ return True, None
267
+ except Exception as exc:
268
+ self._metric_add("observe_failures")
269
+ if isinstance(exc, TimeoutError): self._metric_add("observe_timeouts")
270
+ if deferred and self.on_deferred_observe_error is not None:
271
+ try:
272
+ self.on_deferred_observe_error(exc)
273
+ except Exception:
274
+ pass
275
+ return False, str(exc)
276
+
277
+ if self.schedule_observe is not None:
278
+ observe_deferred = True
279
+ try:
280
+ self.schedule_observe(lambda: perform_observe(deferred=True))
281
+ self._metric_add("observe_scheduled")
282
+ except Exception as exc:
283
+ self._metric_add("observe_schedule_failures")
284
+ observe_ok = False
285
+ observe_error = str(exc)
286
+ else:
287
+ observe_ok, observe_error = perform_observe(deferred=False)
288
+
289
+ return GuardDetailedResult(value=value, path="validated", check=check, check_ok=check_ok, observe_ok=observe_ok,
290
+ observe_deferred=observe_deferred, check_error=check_error, observe_error=observe_error)
291
+
292
+ def _headers(self) -> MutableMapping[str, str]:
293
+ with self._state_lock:
294
+ lease = self.lease
295
+ headers: MutableMapping[str, str] = {"content-type": "application/json"}
296
+ if lease: headers["x-seenrelay-lease"] = lease
297
+ if self.client_hint: headers["x-seenrelay-client"] = self.client_hint
298
+ return headers
299
+
300
+ def _update_lease(self, response: TransportResponse) -> None:
301
+ header_lease = response.headers.get("x-seenrelay-lease") or response.headers.get("X-SeenRelay-Lease")
302
+ body_lease = None
303
+ if isinstance(response.body, Mapping):
304
+ hive = response.body.get("hive")
305
+ if isinstance(hive, Mapping) and isinstance(hive.get("lease"), str): body_lease = hive.get("lease")
306
+ next_lease = str(header_lease or body_lease or "").strip()
307
+ if not next_lease: return
308
+ callback: Optional[Callable[[str], None]] = None
309
+ with self._state_lock:
310
+ if next_lease == self.lease: return
311
+ self.lease = next_lease
312
+ callback = self.on_lease
313
+ if callback: callback(next_lease)
314
+
315
+ def _post(self, path: str, body: Any, timeout: float) -> Any:
316
+ response = self.transport("POST", f"{self.base_url}{path}", self._headers(), body, timeout)
317
+ self._update_lease(response)
318
+ if response.status < 200 or response.status >= 300:
319
+ raise RuntimeError(f"SeenRelay {path} returned HTTP {response.status}")
320
+ return response.body
321
+
322
+ def _check(self, fact: Mapping[str, Any], known_value: T, max_age_seconds: Optional[int]) -> Mapping[str, Any]:
323
+ key = _coalescing_key(fact, known_value, max_age_seconds) if self.coalesce_checks else None
324
+ if key is None: return self._check_network(fact, known_value, max_age_seconds)
325
+ with self._state_lock:
326
+ inflight = self._inflight_checks.get(key)
327
+ if inflight is None:
328
+ inflight = _InflightCheck(); self._inflight_checks[key] = inflight; leader = True
329
+ else:
330
+ leader = False; self._metrics["check_coalesced"] = self._metrics["check_coalesced"] + 1
331
+ if not leader:
332
+ inflight.event.wait()
333
+ if inflight.error is not None: raise RuntimeError(str(inflight.error)) from inflight.error
334
+ if inflight.result is None: raise RuntimeError("coalesced SeenRelay CHECK completed without a result")
335
+ return deepcopy(inflight.result)
336
+ try:
337
+ result = self._check_network(fact, known_value, max_age_seconds); inflight.result = result; return deepcopy(result)
338
+ except Exception as exc:
339
+ inflight.error = exc; raise
340
+ finally:
341
+ inflight.event.set()
342
+ with self._state_lock:
343
+ if self._inflight_checks.get(key) is inflight: del self._inflight_checks[key]
344
+
345
+ def _check_network(self, fact: Mapping[str, Any], known_value: T, max_age_seconds: Optional[int]) -> Mapping[str, Any]:
346
+ self._metric_add("check_network_requests")
347
+ started = time.monotonic()
348
+ try:
349
+ payload: MutableMapping[str, Any] = {"fact": dict(fact), "known_value": known_value}
350
+ if max_age_seconds is not None: payload["max_age_seconds"] = max_age_seconds
351
+ body = self._post("/v1/check", payload, self.check_timeout_seconds)
352
+ if not isinstance(body, Mapping): raise RuntimeError("SeenRelay CHECK response is not an object")
353
+ if body.get("status") not in {"SAME_OBSERVED", "CHANGED_OBSERVED", "CONTESTED", "STALE", "UNKNOWN"}:
354
+ raise RuntimeError("SeenRelay CHECK response has an invalid status")
355
+ return body
356
+ finally:
357
+ self._metric_latency("check_network", max(0.0, (time.monotonic() - started) * 1000.0))
358
+
359
+ def _observe(self, fact: Mapping[str, Any], value: T, metadata: Optional[Mapping[str, Any]]) -> None:
360
+ meta = dict(metadata or {})
361
+ source_validator = meta.pop("source_validator", None)
362
+ if source_validator is not None:
363
+ if not isinstance(source_validator, Mapping): raise ValueError("source_validator must be an object")
364
+ validator_value = source_validator.get("value")
365
+ if not isinstance(validator_value, str) or "\r" in validator_value or "\n" in validator_value:
366
+ raise ValueError("source_validator.value must not contain CR or LF")
367
+ payload: MutableMapping[str, Any] = {
368
+ "fact": dict(fact), "value": value,
369
+ "observed_at": meta.pop("observed_at", None) or __import__("datetime").datetime.now(__import__("datetime").timezone.utc).isoformat().replace("+00:00", "Z"),
370
+ "idempotency_key": meta.pop("idempotency_key", None) or str(uuid.uuid4()),
371
+ }
372
+ observer_id = meta.pop("observer_id", None)
373
+ evidence_fingerprint = meta.pop("evidence_fingerprint", None)
374
+ if observer_id: payload["observer_id"] = observer_id
375
+ if evidence_fingerprint: payload["evidence_fingerprint"] = evidence_fingerprint
376
+ if source_validator is not None: payload["source_validator"] = dict(source_validator)
377
+ if meta: raise ValueError(f"unsupported observation metadata: {', '.join(sorted(meta))}")
378
+ self._metric_add("observe_network_requests")
379
+ started = time.monotonic()
380
+ try:
381
+ self._post("/v1/observe", payload, self.observe_timeout_seconds)
382
+ finally:
383
+ self._metric_latency("observe_network", max(0.0, (time.monotonic() - started) * 1000.0))
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Mapping, Optional, TypeVar
4
+
5
+ from seenrelay import ObservationFactory, ReusePolicy, SeenRelayClient, ValidationContext
6
+
7
+ T = TypeVar("T")
8
+ Validator = Callable[[ValidationContext], T]
9
+
10
+
11
+ def protect_validation(
12
+ client: SeenRelayClient,
13
+ *,
14
+ fact: Mapping[str, Any],
15
+ validate: Validator[T],
16
+ reuse: Optional[ReusePolicy[T]] = None,
17
+ max_age_seconds: Optional[int] = None,
18
+ observation: Optional[ObservationFactory[T]] = None,
19
+ ) -> Callable[[T], T]:
20
+ """Bind SeenRelay around one existing validator.
21
+
22
+ Without an explicit reuse policy this remains strict shadow mode: CHECK runs,
23
+ the original validation still runs, and the independently obtained result is
24
+ OBSERVEd best-effort. The returned callable accepts only the caller's known
25
+ value, making each later protected revalidation a one-line call.
26
+ """
27
+
28
+ def protected(known_value: T) -> T:
29
+ return client.guard(
30
+ fact=fact,
31
+ known_value=known_value,
32
+ validate=validate,
33
+ reuse=reuse,
34
+ max_age_seconds=max_age_seconds,
35
+ observation=observation,
36
+ )
37
+
38
+ return protected
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from math import isfinite
5
+ import time
6
+ from typing import Any, Callable, Mapping, Optional, TypeVar
7
+
8
+ from seenrelay import SeenRelayClient, ValidationContext
9
+
10
+ T = TypeVar("T")
11
+ _STATUSES = ("SAME_OBSERVED", "CHANGED_OBSERVED", "CONTESTED", "STALE", "UNKNOWN")
12
+
13
+
14
+ def _non_negative_finite(value: float, name: str) -> float:
15
+ number = float(value)
16
+ if not isfinite(number) or number < 0:
17
+ raise ValueError(f"{name} must be a non-negative finite number")
18
+ return number
19
+
20
+
21
+ def _empty_metrics() -> dict[str, Any]:
22
+ return {
23
+ "calls": 0,
24
+ "checks_without_usable_response": 0,
25
+ "conditional_hints_seen": 0,
26
+ "validation_ms_total": 0.0,
27
+ "same_observed_validation_ms": 0.0,
28
+ "statuses": {status: 0 for status in _STATUSES},
29
+ }
30
+
31
+
32
+ class SeenRelayShadowProof:
33
+ """Measure SeenRelay in strict shadow mode without suppressing validation."""
34
+
35
+ def __init__(self, client: SeenRelayClient) -> None:
36
+ if not hasattr(client, "guard_detailed") or not hasattr(client, "get_telemetry"):
37
+ raise TypeError("client must be a SeenRelayClient-compatible instance")
38
+ self.client = client
39
+ self._metrics = _empty_metrics()
40
+
41
+ def reset(self) -> None:
42
+ self._metrics = _empty_metrics()
43
+ if hasattr(self.client, "reset_telemetry"):
44
+ self.client.reset_telemetry()
45
+
46
+ def snapshot(self) -> Mapping[str, Any]:
47
+ metrics = deepcopy(self._metrics)
48
+ calls = int(metrics["calls"])
49
+ metrics["validation_ms_average"] = float(metrics["validation_ms_total"]) / calls if calls else 0.0
50
+ return metrics
51
+
52
+ def guard(
53
+ self,
54
+ *,
55
+ fact: Mapping[str, Any],
56
+ known_value: T,
57
+ validate: Callable[[ValidationContext], T],
58
+ max_age_seconds: Optional[int] = None,
59
+ observation: Optional[Callable[[T, ValidationContext], Optional[Mapping[str, Any]]]] = None,
60
+ ) -> T:
61
+ validation_ms = 0.0
62
+
63
+ def measured_validate(context: ValidationContext) -> T:
64
+ nonlocal validation_ms
65
+ started = time.monotonic()
66
+ try:
67
+ return validate(context)
68
+ finally:
69
+ validation_ms += max(0.0, (time.monotonic() - started) * 1000.0)
70
+
71
+ result = self.client.guard_detailed(
72
+ fact=fact,
73
+ known_value=known_value,
74
+ validate=measured_validate,
75
+ reuse=None,
76
+ max_age_seconds=max_age_seconds,
77
+ observation=observation,
78
+ )
79
+
80
+ self._metrics["calls"] += 1
81
+ self._metrics["validation_ms_total"] += validation_ms
82
+ check = result.check
83
+ status = check.get("status") if isinstance(check, Mapping) else None
84
+ if status in _STATUSES:
85
+ self._metrics["statuses"][status] += 1
86
+ if status == "SAME_OBSERVED":
87
+ self._metrics["same_observed_validation_ms"] += validation_ms
88
+ else:
89
+ self._metrics["checks_without_usable_response"] += 1
90
+
91
+ hint = check.get("conditional_request_hint") if isinstance(check, Mapping) else None
92
+ if isinstance(hint, Mapping):
93
+ self._metrics["conditional_hints_seen"] += 1
94
+
95
+ return result.value
96
+
97
+ def report(
98
+ self,
99
+ *,
100
+ avoided_validation_cost: float = 0.0,
101
+ check_request_cost: float = 0.0,
102
+ observe_request_cost: float = 0.0,
103
+ observe_off_critical_path: bool = False,
104
+ ) -> Mapping[str, Any]:
105
+ avoided = _non_negative_finite(avoided_validation_cost, "avoided_validation_cost")
106
+ check_cost = _non_negative_finite(check_request_cost, "check_request_cost")
107
+ observe_cost = _non_negative_finite(observe_request_cost, "observe_request_cost")
108
+ proof = self.snapshot()
109
+ relay = self.client.get_telemetry()
110
+
111
+ calls = int(proof["calls"])
112
+ same = int(proof["statuses"]["SAME_OBSERVED"])
113
+ observed_same_rate = same / calls if calls else 0.0
114
+ prospective_observe_requests = max(0, int(relay.observe_network_requests) - same)
115
+
116
+ gross_potential_savings = same * avoided
117
+ prospective_relay_request_cost = (
118
+ int(relay.check_network_requests) * check_cost
119
+ + prospective_observe_requests * observe_cost
120
+ )
121
+ net_potential_savings = gross_potential_savings - prospective_relay_request_cost
122
+
123
+ check_average_ms = float(relay.check_network_latency_ms_average)
124
+ observe_average_ms = float(relay.observe_network_latency_ms_average)
125
+ validation_average_ms = float(proof["validation_ms_average"])
126
+ off_critical_path = bool(observe_off_critical_path)
127
+ prospective_relay_latency_ms = (
128
+ float(relay.check_network_latency_ms_total)
129
+ + (0.0 if off_critical_path else prospective_observe_requests * observe_average_ms)
130
+ )
131
+ potential_net_time_saved_ms = float(proof["same_observed_validation_ms"]) - prospective_relay_latency_ms
132
+
133
+ if off_critical_path:
134
+ break_even_reuse_rate_by_time = check_average_ms / validation_average_ms if validation_average_ms > 0 else None
135
+ else:
136
+ time_denominator = validation_average_ms + observe_average_ms
137
+ break_even_reuse_rate_by_time = (
138
+ (check_average_ms + observe_average_ms) / time_denominator
139
+ if time_denominator > 0
140
+ else None
141
+ )
142
+ cost_denominator = avoided + observe_cost
143
+ break_even_reuse_rate_by_cost = (
144
+ (check_cost + observe_cost) / cost_denominator
145
+ if cost_denominator > 0
146
+ else None
147
+ )
148
+
149
+ return {
150
+ "mode": "shadow-proof",
151
+ "calls": calls,
152
+ "status_counts": deepcopy(proof["statuses"]),
153
+ "observed_same_rate": observed_same_rate,
154
+ "conditional_hints_seen": int(proof["conditional_hints_seen"]),
155
+ "validation_ms_average": validation_average_ms,
156
+ "check_network_latency_ms_average": check_average_ms,
157
+ "observe_network_latency_ms_average": observe_average_ms,
158
+ "potential_validation_calls_avoided": same,
159
+ "gross_potential_savings": gross_potential_savings,
160
+ "prospective_relay_request_cost": prospective_relay_request_cost,
161
+ "net_potential_savings": net_potential_savings,
162
+ "same_observed_validation_ms": float(proof["same_observed_validation_ms"]),
163
+ "prospective_relay_latency_ms": prospective_relay_latency_ms,
164
+ "potential_net_time_saved_ms": potential_net_time_saved_ms,
165
+ "break_even_reuse_rate_by_time": break_even_reuse_rate_by_time,
166
+ "break_even_reuse_rate_by_cost": break_even_reuse_rate_by_cost,
167
+ "assumptions": {
168
+ "direct_reuse_only": True,
169
+ "conditional_request_savings_excluded": True,
170
+ "active_mode_would_not_observe_direct_reuse_hits": True,
171
+ "caller_supplied_cost_units": True,
172
+ "no_savings_claim_when_same_observed_is_zero": True,
173
+ "observe_off_critical_path": off_critical_path,
174
+ },
175
+ }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+