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.
- seenrelay-0.1.0/LICENSE +21 -0
- seenrelay-0.1.0/PKG-INFO +99 -0
- seenrelay-0.1.0/README.md +82 -0
- seenrelay-0.1.0/pyproject.toml +28 -0
- seenrelay-0.1.0/seenrelay.egg-info/PKG-INFO +99 -0
- seenrelay-0.1.0/seenrelay.egg-info/SOURCES.txt +10 -0
- seenrelay-0.1.0/seenrelay.egg-info/dependency_links.txt +1 -0
- seenrelay-0.1.0/seenrelay.egg-info/top_level.txt +3 -0
- seenrelay-0.1.0/seenrelay.py +383 -0
- seenrelay-0.1.0/seenrelay_easy.py +38 -0
- seenrelay-0.1.0/seenrelay_shadow.py +175 -0
- seenrelay-0.1.0/setup.cfg +4 -0
seenrelay-0.1.0/LICENSE
ADDED
|
@@ -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.
|
seenrelay-0.1.0/PKG-INFO
ADDED
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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
|
+
}
|