crprotocol 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crp/__init__.py +126 -0
- crp/__main__.py +8 -0
- crp/_typing.py +27 -0
- crp/_version.py +5 -0
- crp/adapters.py +31 -0
- crp/advanced/__init__.py +40 -0
- crp/advanced/auto_ingest.py +400 -0
- crp/advanced/cqs.py +235 -0
- crp/advanced/cross_window.py +477 -0
- crp/advanced/curator.py +265 -0
- crp/advanced/feedback.py +146 -0
- crp/advanced/hierarchical.py +211 -0
- crp/advanced/meta_learning.py +401 -0
- crp/advanced/parallel.py +98 -0
- crp/advanced/review_cycle.py +329 -0
- crp/advanced/scale_mode.py +129 -0
- crp/advanced/source_grounding.py +207 -0
- crp/ckf/__init__.py +35 -0
- crp/ckf/community.py +377 -0
- crp/ckf/fabric.py +445 -0
- crp/ckf/gc.py +175 -0
- crp/ckf/graph_walk.py +87 -0
- crp/ckf/merge.py +133 -0
- crp/ckf/pattern_query.py +122 -0
- crp/ckf/pubsub.py +128 -0
- crp/ckf/semantic.py +207 -0
- crp/cli/__init__.py +7 -0
- crp/cli/main.py +329 -0
- crp/cli/sidecar.py +929 -0
- crp/cli/startup.py +272 -0
- crp/continuation/__init__.py +103 -0
- crp/continuation/completion.py +348 -0
- crp/continuation/degradation.py +157 -0
- crp/continuation/document_map.py +160 -0
- crp/continuation/flow.py +109 -0
- crp/continuation/gap.py +419 -0
- crp/continuation/manager.py +484 -0
- crp/continuation/quality_monitor.py +179 -0
- crp/continuation/stitch.py +419 -0
- crp/continuation/trigger.py +142 -0
- crp/continuation/voice.py +157 -0
- crp/core/__init__.py +69 -0
- crp/core/batch.py +77 -0
- crp/core/circuit_breaker.py +116 -0
- crp/core/config.py +377 -0
- crp/core/context_tools.py +540 -0
- crp/core/dispatch_router.py +3977 -0
- crp/core/errors.py +128 -0
- crp/core/extraction_facade.py +384 -0
- crp/core/facilitator.py +713 -0
- crp/core/idempotency.py +215 -0
- crp/core/orchestrator.py +1435 -0
- crp/core/relay_strategies.py +613 -0
- crp/core/security_manager.py +140 -0
- crp/core/session.py +134 -0
- crp/core/task_intent.py +36 -0
- crp/core/window.py +363 -0
- crp/envelope/__init__.py +30 -0
- crp/envelope/builder.py +288 -0
- crp/envelope/decomposer.py +236 -0
- crp/envelope/formatter.py +168 -0
- crp/envelope/packer.py +211 -0
- crp/envelope/reranker.py +209 -0
- crp/envelope/scoring.py +310 -0
- crp/extraction/__init__.py +45 -0
- crp/extraction/complexity.py +96 -0
- crp/extraction/contradiction.py +132 -0
- crp/extraction/pipeline.py +360 -0
- crp/extraction/quality_gate.py +237 -0
- crp/extraction/stage1_regex.py +173 -0
- crp/extraction/stage2_statistical.py +244 -0
- crp/extraction/stage3_gliner.py +210 -0
- crp/extraction/stage4_uie.py +183 -0
- crp/extraction/stage5_discourse.py +175 -0
- crp/extraction/stage6_llm.py +178 -0
- crp/extraction/structured_output.py +219 -0
- crp/extraction/types.py +299 -0
- crp/license_guard.py +722 -0
- crp/observability/__init__.py +30 -0
- crp/observability/audit.py +118 -0
- crp/observability/events.py +233 -0
- crp/observability/metrics.py +264 -0
- crp/observability/quality.py +135 -0
- crp/observability/structured_logging.py +81 -0
- crp/observability/telemetry.py +117 -0
- crp/provenance/__init__.py +314 -0
- crp/provenance/_embeddings.py +97 -0
- crp/provenance/_types.py +378 -0
- crp/provenance/attribution_scorer.py +252 -0
- crp/provenance/claim_detector.py +229 -0
- crp/provenance/contradiction_detector.py +243 -0
- crp/provenance/distortion_detector.py +397 -0
- crp/provenance/entailment_verifier.py +358 -0
- crp/provenance/fabrication_detector.py +203 -0
- crp/provenance/hallucination_scorer.py +320 -0
- crp/provenance/omission_analyzer.py +106 -0
- crp/provenance/provenance_chain.py +205 -0
- crp/provenance/report_generator.py +440 -0
- crp/providers/__init__.py +43 -0
- crp/providers/anthropic.py +270 -0
- crp/providers/base.py +135 -0
- crp/providers/custom.py +63 -0
- crp/providers/diagnostic.py +251 -0
- crp/providers/llamacpp.py +224 -0
- crp/providers/manager.py +139 -0
- crp/providers/ollama.py +243 -0
- crp/providers/openai.py +628 -0
- crp/providers/tokenizers.py +48 -0
- crp/py.typed +0 -0
- crp/resources/__init__.py +53 -0
- crp/resources/adaptive_allocator.py +525 -0
- crp/resources/cost_model.py +388 -0
- crp/resources/overhead_manager.py +217 -0
- crp/resources/resource_manager.py +262 -0
- crp/schemas/__init__.py +20 -0
- crp/schemas/cost-estimate.json +33 -0
- crp/schemas/crp-error.json +43 -0
- crp/schemas/envelope-preview.json +40 -0
- crp/schemas/persisted-state-header.json +27 -0
- crp/schemas/quality-report.json +94 -0
- crp/schemas/session-handle.json +33 -0
- crp/schemas/session-status.json +57 -0
- crp/schemas/stream-event.json +18 -0
- crp/schemas/task-intent.json +42 -0
- crp/security/__init__.py +93 -0
- crp/security/audit_trail.py +392 -0
- crp/security/binding.py +192 -0
- crp/security/compliance.py +813 -0
- crp/security/consent.py +593 -0
- crp/security/embedding_defense.py +161 -0
- crp/security/encryption.py +202 -0
- crp/security/injection.py +335 -0
- crp/security/integrity.py +267 -0
- crp/security/privacy.py +662 -0
- crp/security/quarantine.py +249 -0
- crp/security/rbac.py +221 -0
- crp/security/validation.py +164 -0
- crp/state/__init__.py +31 -0
- crp/state/cold_storage.py +258 -0
- crp/state/compaction.py +263 -0
- crp/state/critical_state.py +104 -0
- crp/state/event_log.py +313 -0
- crp/state/fact.py +189 -0
- crp/state/serialization.py +189 -0
- crp/state/session_cleanup.py +77 -0
- crp/state/snapshot.py +290 -0
- crp/state/warm_store.py +346 -0
- crprotocol-2.0.0.dist-info/METADATA +1295 -0
- crprotocol-2.0.0.dist-info/RECORD +153 -0
- crprotocol-2.0.0.dist-info/WHEEL +4 -0
- crprotocol-2.0.0.dist-info/entry_points.txt +2 -0
- crprotocol-2.0.0.dist-info/licenses/LICENSE.md +170 -0
- crprotocol-2.0.0.dist-info/licenses/NOTICE +18 -0
crp/core/idempotency.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
|
|
2
|
+
# Licensed under Elastic License 2.0 — see LICENSE.md for details.
|
|
3
|
+
"""Request deduplication and idempotency keys (§23).
|
|
4
|
+
|
|
5
|
+
Same key within TTL returns cached result — no re-dispatch.
|
|
6
|
+
Concurrency model: session-level write lock for dispatch/ingest/configure,
|
|
7
|
+
read lock for session_status/estimate/preview.
|
|
8
|
+
Lock timeout: 30 seconds → CRPError(code=1003).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Constants
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
DEFAULT_TTL = 300.0 # 5 minutes
|
|
24
|
+
LOCK_TIMEOUT = 30.0 # seconds
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Data types
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CachedResult:
|
|
34
|
+
"""Cached dispatch result for idempotency."""
|
|
35
|
+
|
|
36
|
+
idempotency_key: str = ""
|
|
37
|
+
output: str = ""
|
|
38
|
+
report: Any = None
|
|
39
|
+
created_at: float = field(default_factory=time.time)
|
|
40
|
+
ttl: float = DEFAULT_TTL
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_expired(self) -> bool:
|
|
44
|
+
return time.time() > (self.created_at + self.ttl)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class DeduplicationStats:
|
|
49
|
+
"""Statistics for request deduplication."""
|
|
50
|
+
|
|
51
|
+
total_requests: int = 0
|
|
52
|
+
cache_hits: int = 0
|
|
53
|
+
cache_misses: int = 0
|
|
54
|
+
evictions: int = 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# RequestDeduplicator
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RequestDeduplicator:
|
|
63
|
+
"""Idempotency key manager with TTL-based cache.
|
|
64
|
+
|
|
65
|
+
Concurrency: session-level write lock for mutations,
|
|
66
|
+
reads are lock-free.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
ttl: float = DEFAULT_TTL,
|
|
72
|
+
max_cache_size: int = 1000,
|
|
73
|
+
) -> None:
|
|
74
|
+
self._ttl = ttl
|
|
75
|
+
self._max_cache_size = max_cache_size
|
|
76
|
+
self._cache: dict[str, CachedResult] = {}
|
|
77
|
+
self._stats = DeduplicationStats()
|
|
78
|
+
self._write_lock = threading.Lock()
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def stats(self) -> DeduplicationStats:
|
|
82
|
+
return self._stats
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def cache_size(self) -> int:
|
|
86
|
+
return len(self._cache)
|
|
87
|
+
|
|
88
|
+
def compute_key(
|
|
89
|
+
self,
|
|
90
|
+
system_prompt: str,
|
|
91
|
+
task_input: str,
|
|
92
|
+
**kwargs: Any,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Compute idempotency key from request parameters."""
|
|
95
|
+
content = f"{system_prompt}|{task_input}|{sorted(kwargs.items())}"
|
|
96
|
+
return hashlib.sha256(content.encode()).hexdigest()[:32]
|
|
97
|
+
|
|
98
|
+
def check(self, key: str) -> CachedResult | None:
|
|
99
|
+
"""Check if a non-expired result exists for this key.
|
|
100
|
+
|
|
101
|
+
Returns cached result or None.
|
|
102
|
+
"""
|
|
103
|
+
self._stats.total_requests += 1
|
|
104
|
+
cached = self._cache.get(key)
|
|
105
|
+
if cached is None:
|
|
106
|
+
self._stats.cache_misses += 1
|
|
107
|
+
return None
|
|
108
|
+
if cached.is_expired:
|
|
109
|
+
del self._cache[key]
|
|
110
|
+
self._stats.cache_misses += 1
|
|
111
|
+
self._stats.evictions += 1
|
|
112
|
+
return None
|
|
113
|
+
self._stats.cache_hits += 1
|
|
114
|
+
return cached
|
|
115
|
+
|
|
116
|
+
def store(
|
|
117
|
+
self,
|
|
118
|
+
key: str,
|
|
119
|
+
output: str,
|
|
120
|
+
report: Any = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Store a dispatch result in the cache."""
|
|
123
|
+
acquired = self._write_lock.acquire(timeout=LOCK_TIMEOUT)
|
|
124
|
+
if not acquired:
|
|
125
|
+
return # Could not acquire lock, skip caching
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# Evict expired entries if at capacity
|
|
129
|
+
if len(self._cache) >= self._max_cache_size:
|
|
130
|
+
self._evict_expired()
|
|
131
|
+
# If still at capacity, evict oldest
|
|
132
|
+
if len(self._cache) >= self._max_cache_size:
|
|
133
|
+
oldest_key = min(self._cache, key=lambda k: self._cache[k].created_at)
|
|
134
|
+
del self._cache[oldest_key]
|
|
135
|
+
self._stats.evictions += 1
|
|
136
|
+
|
|
137
|
+
self._cache[key] = CachedResult(
|
|
138
|
+
idempotency_key=key,
|
|
139
|
+
output=output,
|
|
140
|
+
report=report,
|
|
141
|
+
ttl=self._ttl,
|
|
142
|
+
)
|
|
143
|
+
finally:
|
|
144
|
+
self._write_lock.release()
|
|
145
|
+
|
|
146
|
+
def invalidate(self, key: str) -> bool:
|
|
147
|
+
"""Remove a cached entry. Returns True if found and removed."""
|
|
148
|
+
acquired = self._write_lock.acquire(timeout=LOCK_TIMEOUT)
|
|
149
|
+
if not acquired:
|
|
150
|
+
return False
|
|
151
|
+
try:
|
|
152
|
+
if key in self._cache:
|
|
153
|
+
del self._cache[key]
|
|
154
|
+
return True
|
|
155
|
+
return False
|
|
156
|
+
finally:
|
|
157
|
+
self._write_lock.release()
|
|
158
|
+
|
|
159
|
+
def clear(self) -> int:
|
|
160
|
+
"""Clear all cached entries. Returns count of entries cleared."""
|
|
161
|
+
acquired = self._write_lock.acquire(timeout=LOCK_TIMEOUT)
|
|
162
|
+
if not acquired:
|
|
163
|
+
return 0
|
|
164
|
+
try:
|
|
165
|
+
count = len(self._cache)
|
|
166
|
+
self._cache.clear()
|
|
167
|
+
return count
|
|
168
|
+
finally:
|
|
169
|
+
self._write_lock.release()
|
|
170
|
+
|
|
171
|
+
def _evict_expired(self) -> int:
|
|
172
|
+
"""Remove all expired entries. Returns count evicted."""
|
|
173
|
+
expired = [k for k, v in self._cache.items() if v.is_expired]
|
|
174
|
+
for k in expired:
|
|
175
|
+
del self._cache[k]
|
|
176
|
+
self._stats.evictions += len(expired)
|
|
177
|
+
return len(expired)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Session-level locking (§23)
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class SessionLock:
|
|
186
|
+
"""Read-write lock with ordering for session operations.
|
|
187
|
+
|
|
188
|
+
Lock ordering (deadlock prevention):
|
|
189
|
+
1. Cold Storage Lock
|
|
190
|
+
2. Model Registry Lock
|
|
191
|
+
3. Session Write Lock
|
|
192
|
+
|
|
193
|
+
Write lock for: dispatch, ingest, configure, reset, close
|
|
194
|
+
Read lock (no-op) for: session_status, estimate, preview
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(self, timeout: float = LOCK_TIMEOUT) -> None:
|
|
198
|
+
self._lock = threading.RLock()
|
|
199
|
+
self._timeout = timeout
|
|
200
|
+
self._readers = 0
|
|
201
|
+
self._readers_lock = threading.Lock()
|
|
202
|
+
|
|
203
|
+
def acquire_write(self) -> bool:
|
|
204
|
+
"""Acquire write lock. Returns True if acquired within timeout."""
|
|
205
|
+
return self._lock.acquire(timeout=self._timeout)
|
|
206
|
+
|
|
207
|
+
def release_write(self) -> None:
|
|
208
|
+
self._lock.release()
|
|
209
|
+
|
|
210
|
+
def acquire_read(self) -> bool:
|
|
211
|
+
"""Acquire read lock (currently a no-op for reads)."""
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
def release_read(self) -> None:
|
|
215
|
+
pass
|