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.
Files changed (153) hide show
  1. crp/__init__.py +126 -0
  2. crp/__main__.py +8 -0
  3. crp/_typing.py +27 -0
  4. crp/_version.py +5 -0
  5. crp/adapters.py +31 -0
  6. crp/advanced/__init__.py +40 -0
  7. crp/advanced/auto_ingest.py +400 -0
  8. crp/advanced/cqs.py +235 -0
  9. crp/advanced/cross_window.py +477 -0
  10. crp/advanced/curator.py +265 -0
  11. crp/advanced/feedback.py +146 -0
  12. crp/advanced/hierarchical.py +211 -0
  13. crp/advanced/meta_learning.py +401 -0
  14. crp/advanced/parallel.py +98 -0
  15. crp/advanced/review_cycle.py +329 -0
  16. crp/advanced/scale_mode.py +129 -0
  17. crp/advanced/source_grounding.py +207 -0
  18. crp/ckf/__init__.py +35 -0
  19. crp/ckf/community.py +377 -0
  20. crp/ckf/fabric.py +445 -0
  21. crp/ckf/gc.py +175 -0
  22. crp/ckf/graph_walk.py +87 -0
  23. crp/ckf/merge.py +133 -0
  24. crp/ckf/pattern_query.py +122 -0
  25. crp/ckf/pubsub.py +128 -0
  26. crp/ckf/semantic.py +207 -0
  27. crp/cli/__init__.py +7 -0
  28. crp/cli/main.py +329 -0
  29. crp/cli/sidecar.py +929 -0
  30. crp/cli/startup.py +272 -0
  31. crp/continuation/__init__.py +103 -0
  32. crp/continuation/completion.py +348 -0
  33. crp/continuation/degradation.py +157 -0
  34. crp/continuation/document_map.py +160 -0
  35. crp/continuation/flow.py +109 -0
  36. crp/continuation/gap.py +419 -0
  37. crp/continuation/manager.py +484 -0
  38. crp/continuation/quality_monitor.py +179 -0
  39. crp/continuation/stitch.py +419 -0
  40. crp/continuation/trigger.py +142 -0
  41. crp/continuation/voice.py +157 -0
  42. crp/core/__init__.py +69 -0
  43. crp/core/batch.py +77 -0
  44. crp/core/circuit_breaker.py +116 -0
  45. crp/core/config.py +377 -0
  46. crp/core/context_tools.py +540 -0
  47. crp/core/dispatch_router.py +3977 -0
  48. crp/core/errors.py +128 -0
  49. crp/core/extraction_facade.py +384 -0
  50. crp/core/facilitator.py +713 -0
  51. crp/core/idempotency.py +215 -0
  52. crp/core/orchestrator.py +1435 -0
  53. crp/core/relay_strategies.py +613 -0
  54. crp/core/security_manager.py +140 -0
  55. crp/core/session.py +134 -0
  56. crp/core/task_intent.py +36 -0
  57. crp/core/window.py +363 -0
  58. crp/envelope/__init__.py +30 -0
  59. crp/envelope/builder.py +288 -0
  60. crp/envelope/decomposer.py +236 -0
  61. crp/envelope/formatter.py +168 -0
  62. crp/envelope/packer.py +211 -0
  63. crp/envelope/reranker.py +209 -0
  64. crp/envelope/scoring.py +310 -0
  65. crp/extraction/__init__.py +45 -0
  66. crp/extraction/complexity.py +96 -0
  67. crp/extraction/contradiction.py +132 -0
  68. crp/extraction/pipeline.py +360 -0
  69. crp/extraction/quality_gate.py +237 -0
  70. crp/extraction/stage1_regex.py +173 -0
  71. crp/extraction/stage2_statistical.py +244 -0
  72. crp/extraction/stage3_gliner.py +210 -0
  73. crp/extraction/stage4_uie.py +183 -0
  74. crp/extraction/stage5_discourse.py +175 -0
  75. crp/extraction/stage6_llm.py +178 -0
  76. crp/extraction/structured_output.py +219 -0
  77. crp/extraction/types.py +299 -0
  78. crp/license_guard.py +722 -0
  79. crp/observability/__init__.py +30 -0
  80. crp/observability/audit.py +118 -0
  81. crp/observability/events.py +233 -0
  82. crp/observability/metrics.py +264 -0
  83. crp/observability/quality.py +135 -0
  84. crp/observability/structured_logging.py +81 -0
  85. crp/observability/telemetry.py +117 -0
  86. crp/provenance/__init__.py +314 -0
  87. crp/provenance/_embeddings.py +97 -0
  88. crp/provenance/_types.py +378 -0
  89. crp/provenance/attribution_scorer.py +252 -0
  90. crp/provenance/claim_detector.py +229 -0
  91. crp/provenance/contradiction_detector.py +243 -0
  92. crp/provenance/distortion_detector.py +397 -0
  93. crp/provenance/entailment_verifier.py +358 -0
  94. crp/provenance/fabrication_detector.py +203 -0
  95. crp/provenance/hallucination_scorer.py +320 -0
  96. crp/provenance/omission_analyzer.py +106 -0
  97. crp/provenance/provenance_chain.py +205 -0
  98. crp/provenance/report_generator.py +440 -0
  99. crp/providers/__init__.py +43 -0
  100. crp/providers/anthropic.py +270 -0
  101. crp/providers/base.py +135 -0
  102. crp/providers/custom.py +63 -0
  103. crp/providers/diagnostic.py +251 -0
  104. crp/providers/llamacpp.py +224 -0
  105. crp/providers/manager.py +139 -0
  106. crp/providers/ollama.py +243 -0
  107. crp/providers/openai.py +628 -0
  108. crp/providers/tokenizers.py +48 -0
  109. crp/py.typed +0 -0
  110. crp/resources/__init__.py +53 -0
  111. crp/resources/adaptive_allocator.py +525 -0
  112. crp/resources/cost_model.py +388 -0
  113. crp/resources/overhead_manager.py +217 -0
  114. crp/resources/resource_manager.py +262 -0
  115. crp/schemas/__init__.py +20 -0
  116. crp/schemas/cost-estimate.json +33 -0
  117. crp/schemas/crp-error.json +43 -0
  118. crp/schemas/envelope-preview.json +40 -0
  119. crp/schemas/persisted-state-header.json +27 -0
  120. crp/schemas/quality-report.json +94 -0
  121. crp/schemas/session-handle.json +33 -0
  122. crp/schemas/session-status.json +57 -0
  123. crp/schemas/stream-event.json +18 -0
  124. crp/schemas/task-intent.json +42 -0
  125. crp/security/__init__.py +93 -0
  126. crp/security/audit_trail.py +392 -0
  127. crp/security/binding.py +192 -0
  128. crp/security/compliance.py +813 -0
  129. crp/security/consent.py +593 -0
  130. crp/security/embedding_defense.py +161 -0
  131. crp/security/encryption.py +202 -0
  132. crp/security/injection.py +335 -0
  133. crp/security/integrity.py +267 -0
  134. crp/security/privacy.py +662 -0
  135. crp/security/quarantine.py +249 -0
  136. crp/security/rbac.py +221 -0
  137. crp/security/validation.py +164 -0
  138. crp/state/__init__.py +31 -0
  139. crp/state/cold_storage.py +258 -0
  140. crp/state/compaction.py +263 -0
  141. crp/state/critical_state.py +104 -0
  142. crp/state/event_log.py +313 -0
  143. crp/state/fact.py +189 -0
  144. crp/state/serialization.py +189 -0
  145. crp/state/session_cleanup.py +77 -0
  146. crp/state/snapshot.py +290 -0
  147. crp/state/warm_store.py +346 -0
  148. crprotocol-2.0.0.dist-info/METADATA +1295 -0
  149. crprotocol-2.0.0.dist-info/RECORD +153 -0
  150. crprotocol-2.0.0.dist-info/WHEEL +4 -0
  151. crprotocol-2.0.0.dist-info/entry_points.txt +2 -0
  152. crprotocol-2.0.0.dist-info/licenses/LICENSE.md +170 -0
  153. crprotocol-2.0.0.dist-info/licenses/NOTICE +18 -0
@@ -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