claude-smart 0.2.46 → 0.2.47
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +1 -1
- package/plugin/vendor/reflexio/reflexio/README.md +3 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +23 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +16 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +176 -29
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +45 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +129 -111
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +14 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +5 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +262 -107
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +19 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Recency adjustment helpers for unified search ranking."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import math
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
|
|
11
|
+
from reflexio.server.env_utils import env_str, env_truthy
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
SECONDS_PER_DAY = 24 * 60 * 60
|
|
16
|
+
PLAYBOOK_HALF_LIFE_SECONDS = 90 * SECONDS_PER_DAY
|
|
17
|
+
_PROFILE_TTL_HALF_LIFE_FRACTION = 0.5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ScoredItem[T]:
|
|
22
|
+
item: T
|
|
23
|
+
score: float | None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class RecencyConfig:
|
|
28
|
+
enabled: bool = True
|
|
29
|
+
max_penalty_frac: float = 0.15
|
|
30
|
+
max_penalty_logit: float = 0.2
|
|
31
|
+
pool_size: int = 20
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_env(cls, *, env: Mapping[str, str] | None = None) -> RecencyConfig:
|
|
35
|
+
return cls(
|
|
36
|
+
enabled=env_truthy(
|
|
37
|
+
env_str("REFLEXIO_SEARCH_RECENCY_ENABLED", "true", env=env)
|
|
38
|
+
),
|
|
39
|
+
max_penalty_frac=_float_env(
|
|
40
|
+
"REFLEXIO_SEARCH_RECENCY_MAX_PENALTY_FRAC",
|
|
41
|
+
cls.max_penalty_frac,
|
|
42
|
+
env=env,
|
|
43
|
+
minimum=0.0,
|
|
44
|
+
maximum=1.0,
|
|
45
|
+
),
|
|
46
|
+
max_penalty_logit=_float_env(
|
|
47
|
+
"REFLEXIO_SEARCH_RECENCY_MAX_PENALTY_LOGIT",
|
|
48
|
+
cls.max_penalty_logit,
|
|
49
|
+
env=env,
|
|
50
|
+
minimum=0.0,
|
|
51
|
+
),
|
|
52
|
+
pool_size=_int_env(
|
|
53
|
+
"REFLEXIO_SEARCH_RECENCY_POOL_SIZE",
|
|
54
|
+
cls.pool_size,
|
|
55
|
+
env=env,
|
|
56
|
+
minimum=1,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def with_overrides(self, values: Mapping[str, object] | None) -> RecencyConfig:
|
|
61
|
+
if not values:
|
|
62
|
+
return self
|
|
63
|
+
updates: dict[str, object] = {}
|
|
64
|
+
if "recency_enabled" in values:
|
|
65
|
+
updates["enabled"] = _bool_value(values["recency_enabled"], self.enabled)
|
|
66
|
+
if "recency_max_penalty_frac" in values:
|
|
67
|
+
updates["max_penalty_frac"] = _float_value(
|
|
68
|
+
values["recency_max_penalty_frac"],
|
|
69
|
+
self.max_penalty_frac,
|
|
70
|
+
minimum=0.0,
|
|
71
|
+
maximum=1.0,
|
|
72
|
+
)
|
|
73
|
+
if "recency_max_penalty_logit" in values:
|
|
74
|
+
updates["max_penalty_logit"] = _float_value(
|
|
75
|
+
values["recency_max_penalty_logit"],
|
|
76
|
+
self.max_penalty_logit,
|
|
77
|
+
minimum=0.0,
|
|
78
|
+
)
|
|
79
|
+
if "recency_pool_size" in values:
|
|
80
|
+
updates["pool_size"] = _int_value(
|
|
81
|
+
values["recency_pool_size"], self.pool_size, minimum=1
|
|
82
|
+
)
|
|
83
|
+
return replace(self, **updates)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def decay_for_item(
|
|
87
|
+
item: object,
|
|
88
|
+
*,
|
|
89
|
+
entity_type: str,
|
|
90
|
+
now: int | None = None,
|
|
91
|
+
) -> float:
|
|
92
|
+
"""Return a freshness decay in [0, 1], where 1 means no penalty."""
|
|
93
|
+
now = now if now is not None else int(datetime.now(UTC).timestamp())
|
|
94
|
+
if entity_type == "profiles":
|
|
95
|
+
return _profile_decay(item, now=now)
|
|
96
|
+
return _timestamp_decay(
|
|
97
|
+
getattr(item, "created_at", None),
|
|
98
|
+
half_life_seconds=PLAYBOOK_HALF_LIFE_SECONDS,
|
|
99
|
+
now=now,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def decay(age_seconds: float, half_life_seconds: float) -> float:
|
|
104
|
+
if half_life_seconds <= 0:
|
|
105
|
+
return 1.0
|
|
106
|
+
age_seconds = max(0.0, age_seconds)
|
|
107
|
+
return math.exp(-age_seconds / (half_life_seconds / math.log(2)))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def multiplicative_factor(decay_value: float, max_penalty_frac: float) -> float:
|
|
111
|
+
return 1.0 - _clamp(max_penalty_frac, 0.0, 1.0) * (1.0 - _decay(decay_value))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def additive_penalty(decay_value: float, max_penalty_logit: float) -> float:
|
|
115
|
+
return max(0.0, max_penalty_logit) * (1.0 - _decay(decay_value))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _profile_decay(item: object, *, now: int) -> float:
|
|
119
|
+
modified_at = _int_or_none(getattr(item, "last_modified_timestamp", None))
|
|
120
|
+
expires_at = _int_or_none(getattr(item, "expiration_timestamp", None))
|
|
121
|
+
if modified_at is None or expires_at is None or expires_at <= modified_at:
|
|
122
|
+
return 1.0
|
|
123
|
+
half_life = (expires_at - modified_at) * _PROFILE_TTL_HALF_LIFE_FRACTION
|
|
124
|
+
return decay(now - modified_at, half_life)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _timestamp_decay(value: object, *, half_life_seconds: int, now: int) -> float:
|
|
128
|
+
timestamp = _int_or_none(value)
|
|
129
|
+
if timestamp is None or timestamp <= 0:
|
|
130
|
+
return 1.0
|
|
131
|
+
return decay(now - timestamp, half_life_seconds)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _float_env(
|
|
135
|
+
name: str,
|
|
136
|
+
default: float,
|
|
137
|
+
*,
|
|
138
|
+
env: Mapping[str, str] | None,
|
|
139
|
+
minimum: float,
|
|
140
|
+
maximum: float | None = None,
|
|
141
|
+
) -> float:
|
|
142
|
+
return _float_value(
|
|
143
|
+
env_str(name, str(default), env=env),
|
|
144
|
+
default,
|
|
145
|
+
minimum=minimum,
|
|
146
|
+
maximum=maximum,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _int_env(
|
|
151
|
+
name: str,
|
|
152
|
+
default: int,
|
|
153
|
+
*,
|
|
154
|
+
env: Mapping[str, str] | None,
|
|
155
|
+
minimum: int,
|
|
156
|
+
) -> int:
|
|
157
|
+
return _int_value(env_str(name, str(default), env=env), default, minimum=minimum)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _bool_value(value: object, default: bool) -> bool:
|
|
161
|
+
if isinstance(value, bool):
|
|
162
|
+
return value
|
|
163
|
+
if isinstance(value, str) and value.strip():
|
|
164
|
+
return env_truthy(value)
|
|
165
|
+
return default
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _float_value(
|
|
169
|
+
value: object,
|
|
170
|
+
default: float,
|
|
171
|
+
*,
|
|
172
|
+
minimum: float,
|
|
173
|
+
maximum: float | None = None,
|
|
174
|
+
) -> float:
|
|
175
|
+
try:
|
|
176
|
+
if not isinstance(value, str | int | float):
|
|
177
|
+
raise TypeError
|
|
178
|
+
parsed = float(value)
|
|
179
|
+
except (TypeError, ValueError):
|
|
180
|
+
logger.warning("Invalid recency float override %r; using %.3f", value, default)
|
|
181
|
+
return default
|
|
182
|
+
parsed = max(minimum, parsed)
|
|
183
|
+
if maximum is not None:
|
|
184
|
+
parsed = min(maximum, parsed)
|
|
185
|
+
return parsed
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _int_value(value: object, default: int, *, minimum: int) -> int:
|
|
189
|
+
try:
|
|
190
|
+
if not isinstance(value, str | int):
|
|
191
|
+
raise TypeError
|
|
192
|
+
parsed = int(value)
|
|
193
|
+
except (TypeError, ValueError):
|
|
194
|
+
logger.warning("Invalid recency integer override %r; using %d", value, default)
|
|
195
|
+
return default
|
|
196
|
+
return max(minimum, parsed)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _int_or_none(value: object) -> int | None:
|
|
200
|
+
try:
|
|
201
|
+
return int(value) # type: ignore[arg-type]
|
|
202
|
+
except (TypeError, ValueError):
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _decay(value: float) -> float:
|
|
207
|
+
return _clamp(value, 0.0, 1.0)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _clamp(value: float, low: float, high: float) -> float:
|
|
211
|
+
return min(high, max(low, value))
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
12
|
from collections.abc import Callable, Sequence
|
|
13
|
+
from dataclasses import dataclass
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
15
16
|
from reflexio.server.llm.rerank import score_pairs
|
|
@@ -21,12 +22,20 @@ from reflexio.server.tracing import profile_step
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class RelevanceFloorResult:
|
|
27
|
+
items: list[Any]
|
|
28
|
+
scores: list[float] | None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _floor_and_sort[T](
|
|
32
|
+
items: list[T], scores: list[float], floor: float
|
|
33
|
+
) -> list[tuple[T, float]]:
|
|
34
|
+
"""Return ``(item, score)`` pairs scoring >= ``floor``, sorted descending."""
|
|
26
35
|
ranked = sorted(
|
|
27
36
|
zip(items, scores, strict=True), key=lambda pair: pair[1], reverse=True
|
|
28
37
|
)
|
|
29
|
-
return [item for item, score in ranked if score >= floor]
|
|
38
|
+
return [(item, score) for item, score in ranked if score >= floor]
|
|
30
39
|
|
|
31
40
|
|
|
32
41
|
def apply_relevance_floor[T](
|
|
@@ -71,7 +80,7 @@ def apply_relevance_floor[T](
|
|
|
71
80
|
return items[:top_k]
|
|
72
81
|
span.set_data("available", True)
|
|
73
82
|
|
|
74
|
-
survivors = _floor_and_sort(items, scores, floor)
|
|
83
|
+
survivors = [item for item, _score in _floor_and_sort(items, scores, floor)]
|
|
75
84
|
dropped = len(items) - len(survivors)
|
|
76
85
|
span.set_data("kept", len(survivors))
|
|
77
86
|
span.set_data("dropped", dropped)
|
|
@@ -92,7 +101,7 @@ def apply_relevance_floors(
|
|
|
92
101
|
top_k: int,
|
|
93
102
|
*,
|
|
94
103
|
content_of: Callable[[Any], str] = lambda item: item.content,
|
|
95
|
-
) -> list[
|
|
104
|
+
) -> list[RelevanceFloorResult]:
|
|
96
105
|
"""Floor every arm with a single cross-encoder batch.
|
|
97
106
|
|
|
98
107
|
CPU cross-encoder inference does not parallelize across threads, so
|
|
@@ -108,12 +117,13 @@ def apply_relevance_floors(
|
|
|
108
117
|
content_of: Extracts the text to score for an item.
|
|
109
118
|
|
|
110
119
|
Returns:
|
|
111
|
-
One
|
|
112
|
-
descending and
|
|
113
|
-
every arm returns
|
|
120
|
+
One result per arm, in input order. Available reranker results are
|
|
121
|
+
sorted by score descending and left uncapped with the paired raw logits.
|
|
122
|
+
On reranker unavailability, every arm returns the original full item
|
|
123
|
+
pool with a ``None`` score sentinel (logged).
|
|
114
124
|
"""
|
|
115
125
|
if not any(items for _, items, _ in arms):
|
|
116
|
-
return [[] for _ in arms]
|
|
126
|
+
return [RelevanceFloorResult([], []) for _ in arms]
|
|
117
127
|
arm_names = [name for name, _, _ in arms]
|
|
118
128
|
contents: list[str] = []
|
|
119
129
|
for _, items, _ in arms:
|
|
@@ -123,20 +133,21 @@ def apply_relevance_floors(
|
|
|
123
133
|
arm="all",
|
|
124
134
|
arms=arm_names,
|
|
125
135
|
items=len(contents),
|
|
136
|
+
top_k=top_k,
|
|
126
137
|
) as span:
|
|
127
138
|
try:
|
|
128
139
|
scores = score_pairs(query, contents)
|
|
129
140
|
except CrossEncoderUnavailableError:
|
|
130
141
|
span.set_data("available", False)
|
|
131
142
|
logger.warning(
|
|
132
|
-
"event=relevance_floor_unavailable arms=%s items=%d (returning unfiltered
|
|
143
|
+
"event=relevance_floor_unavailable arms=%s items=%d (returning unfiltered pool)",
|
|
133
144
|
arm_names,
|
|
134
145
|
len(contents),
|
|
135
146
|
)
|
|
136
|
-
return [items
|
|
147
|
+
return [RelevanceFloorResult(list(items), None) for _, items, _ in arms]
|
|
137
148
|
span.set_data("available", True)
|
|
138
149
|
|
|
139
|
-
results: list[
|
|
150
|
+
results: list[RelevanceFloorResult] = []
|
|
140
151
|
offset = 0
|
|
141
152
|
for name, items, floor in arms:
|
|
142
153
|
arm_scores = scores[offset : offset + len(items)]
|
|
@@ -153,5 +164,10 @@ def apply_relevance_floors(
|
|
|
153
164
|
dropped,
|
|
154
165
|
floor,
|
|
155
166
|
)
|
|
156
|
-
results.append(
|
|
167
|
+
results.append(
|
|
168
|
+
RelevanceFloorResult(
|
|
169
|
+
[item for item, _score in survivors],
|
|
170
|
+
[score for _item, score in survivors],
|
|
171
|
+
)
|
|
172
|
+
)
|
|
157
173
|
return results
|
|
@@ -14,6 +14,10 @@ class StorageError(Exception):
|
|
|
14
14
|
return f"StorageError: {self.message}"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class SubjectWriteBarrierError(StorageError):
|
|
18
|
+
"""Raised when a write targets a subject with an active erasure barrier."""
|
|
19
|
+
|
|
20
|
+
|
|
17
21
|
def require_non_empty_session_id(value: Any) -> str:
|
|
18
22
|
"""Return a stripped, non-empty request ``session_id`` or raise ``StorageError``.
|
|
19
23
|
|