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,346 @@
1
+ # Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
2
+ # Licensed under Elastic License 2.0 — see LICENSE.md for details.
3
+ """Warm state store — in-memory Tier 2 with optional SQLite WAL persist (§3.0, §3.1).
4
+
5
+ The warm store holds all active facts, their graph, and provides the ranked
6
+ retrieval interface used by the envelope builder.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import logging
13
+ import threading
14
+ from collections.abc import Callable
15
+ from dataclasses import dataclass
16
+ from typing import Any
17
+
18
+ from crp.extraction.types import Fact, FactEdge, FactGraph
19
+
20
+ from .critical_state import CriticalState, StructuralState
21
+ from .fact import StateFact
22
+
23
+ logger = logging.getLogger("crp.state.warm_store")
24
+
25
+ # Schema version for session file format (§audit L6 / L7)
26
+ SCHEMA_VERSION = 2
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # WarmStateStore
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ @dataclass
34
+ class WarmStoreConfig:
35
+ """Configuration for the warm state store."""
36
+
37
+ max_facts: int = 10_000
38
+ persist_enabled: bool = False
39
+ persist_path: str = ""
40
+ compact_threshold: int = 5000
41
+ compact_latency_ms: float = 500.0
42
+
43
+
44
+ class WarmStateStore:
45
+ """In-memory warm state (Tier 2) with thread-safe access.
46
+
47
+ Provides:
48
+ - add_facts / get_facts / get_ranked_facts / mark_seen / supersede
49
+ - Critical state & structural state management
50
+ - Optional async SQLite WAL persistence (Phase 4E)
51
+ """
52
+
53
+ def __init__(self, config: WarmStoreConfig | None = None) -> None:
54
+ self._config = config or WarmStoreConfig()
55
+ self._lock = threading.Lock()
56
+
57
+ # Fact storage
58
+ self._facts: dict[str, StateFact] = {}
59
+ self._fact_text_hashes: set[str] = set()
60
+ self._graph = FactGraph()
61
+
62
+ # Critical & structural state
63
+ self._critical = CriticalState()
64
+ self._structural = StructuralState()
65
+
66
+ # Event callbacks
67
+ self._on_fact_added: list[Callable[[StateFact], None]] = []
68
+ self._on_fact_superseded: list[Callable[[StateFact, str], None]] = []
69
+
70
+ # Window tracking
71
+ self._current_window_id: str = ""
72
+ self._window_count: int = 0
73
+
74
+ # --- Properties -----------------------------------------------------------
75
+
76
+ @property
77
+ def fact_count(self) -> int:
78
+ return len(self._facts)
79
+
80
+ @property
81
+ def graph(self) -> FactGraph:
82
+ return self._graph
83
+
84
+ @property
85
+ def critical_state(self) -> CriticalState:
86
+ return self._critical
87
+
88
+ @property
89
+ def structural_state(self) -> StructuralState:
90
+ return self._structural
91
+
92
+ @property
93
+ def window_count(self) -> int:
94
+ return self._window_count
95
+
96
+ # --- Fact operations ------------------------------------------------------
97
+
98
+ def add_facts(self, facts: list[Fact], edges: list[FactEdge] | None = None) -> list[StateFact]:
99
+ """Add extraction facts to the warm store, wrapping them as StateFacts."""
100
+ added: list[StateFact] = []
101
+ with self._lock:
102
+ for fact in facts:
103
+ if len(self._facts) >= self._config.max_facts: # §audit3 H3: enforce cap
104
+ logger.warning("Warm store fact cap reached (%d), dropping new facts", self._config.max_facts)
105
+ break
106
+ if fact.id in self._facts:
107
+ continue # deduplicate by ID
108
+ text_hash = hashlib.sha256(fact.text.encode()).hexdigest()
109
+ if text_hash in self._fact_text_hashes:
110
+ continue # deduplicate by content
111
+ sf = StateFact.from_fact(fact)
112
+ self._facts[fact.id] = sf
113
+ self._fact_text_hashes.add(text_hash)
114
+ self._graph.add_fact(fact)
115
+ added.append(sf)
116
+ if edges:
117
+ for edge in edges:
118
+ self._graph.add_edge(edge)
119
+ # Fire callbacks outside lock
120
+ for sf in added:
121
+ for cb in self._on_fact_added:
122
+ try:
123
+ cb(sf)
124
+ except Exception: # noqa: BLE001
125
+ logger.exception("Fact-added callback failed for fact %s", sf.id)
126
+ return added
127
+
128
+ def get_facts(self, *, include_superseded: bool = False) -> list[StateFact]:
129
+ """Return all active facts (or all including superseded)."""
130
+ with self._lock:
131
+ if include_superseded:
132
+ return list(self._facts.values())
133
+ return [sf for sf in self._facts.values() if not sf.is_superseded]
134
+
135
+ def get_fact(self, fact_id: str) -> StateFact | None:
136
+ """Get a specific fact by ID."""
137
+ return self._facts.get(fact_id)
138
+
139
+ def get_active_facts_as_extraction(self) -> list[Fact]:
140
+ """Return active facts as extraction Fact objects (for envelope builder)."""
141
+ with self._lock:
142
+ return [sf.fact for sf in self._facts.values() if not sf.is_superseded]
143
+
144
+ def get_ranked_facts(self, *, top_k: int | None = None, limit: int | None = None) -> list[StateFact]:
145
+ """Return active facts sorted by relevance heuristic (confidence × recency)."""
146
+ k = top_k or limit
147
+ with self._lock:
148
+ active = [sf for sf in self._facts.values() if not sf.is_superseded]
149
+ # Simple ranking: confidence × inverse age
150
+ active.sort(
151
+ key=lambda sf: sf.confidence * (1.0 / (1.0 + sf.age_in_windows)),
152
+ reverse=True,
153
+ )
154
+ if k is not None:
155
+ return active[:k]
156
+ return active
157
+
158
+ def mark_seen(self, fact_ids: list[str], window_id: str) -> None:
159
+ """Record that facts were included in an envelope for *window_id*."""
160
+ with self._lock:
161
+ for fid in fact_ids:
162
+ sf = self._facts.get(fid)
163
+ if sf:
164
+ sf.mark_seen(window_id)
165
+
166
+ def supersede(self, old_fact_id: str, new_fact_id: str, confidence: float = 1.0) -> None:
167
+ """Mark *old_fact_id* as superseded by *new_fact_id*."""
168
+ with self._lock:
169
+ old = self._facts.get(old_fact_id)
170
+ if old:
171
+ old.supersede(new_fact_id, confidence)
172
+ # Fire callback outside lock
173
+ if old:
174
+ for cb in self._on_fact_superseded:
175
+ try:
176
+ cb(old, new_fact_id)
177
+ except Exception: # noqa: BLE001
178
+ logger.exception("Fact-superseded callback failed for fact %s", old_fact_id)
179
+
180
+ def remove_fact(self, fact_id: str) -> StateFact | None:
181
+ """Remove a fact (for compaction/archival)."""
182
+ with self._lock:
183
+ sf = self._facts.pop(fact_id, None)
184
+ if sf is not None:
185
+ # Clean content-dedup hash (§audit2 STATE-H1)
186
+ text_hash = hashlib.sha256(sf.fact.text.encode()).hexdigest()
187
+ self._fact_text_hashes.discard(text_hash)
188
+ # Remove from graph (§audit2 STATE-H2)
189
+ self._graph.remove_fact(fact_id)
190
+ return sf
191
+
192
+ def boost_confidence(self, fact_id: str, delta: float) -> None:
193
+ """Increase a fact's confidence by *delta*, capped at 1.0 (§22 curation)."""
194
+ with self._lock:
195
+ sf = self._facts.get(fact_id)
196
+ if sf:
197
+ sf.fact.confidence = min(1.0, sf.fact.confidence + delta)
198
+
199
+ def reduce_confidence(self, fact_id: str, delta: float) -> None:
200
+ """Decrease a fact's confidence by *delta*, floored at 0.0 (§22 curation)."""
201
+ with self._lock:
202
+ sf = self._facts.get(fact_id)
203
+ if sf:
204
+ sf.fact.confidence = max(0.0, sf.fact.confidence - delta)
205
+
206
+ # --- Window lifecycle -----------------------------------------------------
207
+
208
+ def advance_window(self, window_id: str) -> None:
209
+ """Called when a new window begins — age all facts, update window tracking."""
210
+ with self._lock:
211
+ self._current_window_id = window_id
212
+ self._window_count += 1
213
+ for sf in self._facts.values():
214
+ sf.increment_age()
215
+
216
+ # --- Critical state -------------------------------------------------------
217
+
218
+ def get_critical_state(self) -> CriticalState:
219
+ return self._critical
220
+
221
+ def update_critical_state(self, **kwargs: Any) -> None:
222
+ """Update critical state fields (goal, phase, blockers, constraints)."""
223
+ with self._lock:
224
+ self._critical.update(**kwargs)
225
+ self._critical.window_id = self._current_window_id
226
+
227
+ def update_phase(self, phase: str) -> None:
228
+ with self._lock:
229
+ self._critical.phase = phase
230
+ self._critical.window_id = self._current_window_id
231
+
232
+ # --- Structural state -----------------------------------------------------
233
+
234
+ def get_structural_state(self) -> StructuralState:
235
+ return self._structural
236
+
237
+ def update_structural_state(self, **kwargs: Any) -> None:
238
+ with self._lock:
239
+ for key, value in kwargs.items():
240
+ if hasattr(self._structural, key):
241
+ setattr(self._structural, key, value)
242
+
243
+ # --- Scoring helpers (for envelope builder) -------------------------------
244
+
245
+ def get_seen_counts(self) -> dict[str, int]:
246
+ """Return {fact_id: seen_count} for all facts."""
247
+ with self._lock:
248
+ return {fid: sf.seen_count for fid, sf in self._facts.items()}
249
+
250
+ def get_fact_window_indices(self) -> dict[str, int]:
251
+ """Return {fact_id: creation_window_index} for scoring recency."""
252
+ with self._lock:
253
+ return {
254
+ fid: self._window_count - sf.age_in_windows
255
+ for fid, sf in self._facts.items()
256
+ }
257
+
258
+ # --- Event subscriptions --------------------------------------------------
259
+
260
+ def on_fact_added(self, callback: Callable[[StateFact], None]) -> None:
261
+ self._on_fact_added.append(callback)
262
+
263
+ def on_fact_superseded(self, callback: Callable[[StateFact, str], None]) -> None:
264
+ self._on_fact_superseded.append(callback)
265
+
266
+ # --- Compaction check -----------------------------------------------------
267
+
268
+ def needs_compaction(self) -> bool:
269
+ """Check if warm store exceeds compaction thresholds."""
270
+ return self.fact_count > self._config.compact_threshold
271
+
272
+ # --- Serialization --------------------------------------------------------
273
+
274
+ def to_dict(self) -> dict[str, Any]:
275
+ """Serialize entire warm store state."""
276
+ with self._lock:
277
+ return {
278
+ "schema_version": SCHEMA_VERSION,
279
+ "facts": {fid: sf.to_dict() for fid, sf in self._facts.items()},
280
+ "edges": [
281
+ {
282
+ "id": e.id,
283
+ "source_id": e.source_id,
284
+ "target_id": e.target_id,
285
+ "relation_type": e.relation_type.value if hasattr(e.relation_type, "value") else str(e.relation_type),
286
+ "confidence": e.confidence,
287
+ "source_stage": e.source_stage,
288
+ }
289
+ for e in self._graph.edges
290
+ ],
291
+ "critical_state": self._critical.to_dict(),
292
+ "structural_state": self._structural.to_dict(),
293
+ "window_count": self._window_count,
294
+ "current_window_id": self._current_window_id,
295
+ }
296
+
297
+ def load_from_dict(self, data: dict[str, Any]) -> None:
298
+ """Restore warm store from serialized dict."""
299
+ file_version = data.get("schema_version", 1)
300
+ if file_version > SCHEMA_VERSION:
301
+ raise ValueError(
302
+ f"Session file schema v{file_version} is newer than "
303
+ f"supported v{SCHEMA_VERSION}. Upgrade CRP."
304
+ )
305
+ if file_version < SCHEMA_VERSION:
306
+ data = self._migrate(data, file_version)
307
+ with self._lock:
308
+ self._facts.clear()
309
+ self._graph = FactGraph()
310
+ self._fact_text_hashes.clear()
311
+
312
+ for _fid, fdata in data.get("facts", {}).items():
313
+ sf = StateFact.from_dict(fdata)
314
+ self._facts[sf.id] = sf
315
+ self._graph.add_fact(sf.fact)
316
+ # Rebuild content-dedup hashes (§audit2 STATE-C1)
317
+ text_hash = hashlib.sha256(sf.fact.text.encode()).hexdigest()
318
+ self._fact_text_hashes.add(text_hash)
319
+
320
+ for edata in data.get("edges", []):
321
+ edge = FactEdge(
322
+ id=edata.get("id", ""),
323
+ source_id=edata.get("source_id", ""),
324
+ target_id=edata.get("target_id", ""),
325
+ relation_type=edata.get("relation_type", "RELATED"),
326
+ confidence=edata.get("confidence", 0.0),
327
+ source_stage=edata.get("source_stage", 0),
328
+ )
329
+ self._graph.add_edge(edge)
330
+
331
+ self._critical = CriticalState.from_dict(data.get("critical_state", {}))
332
+ self._structural = StructuralState.from_dict(data.get("structural_state", {}))
333
+ self._window_count = data.get("window_count", 0)
334
+ self._current_window_id = data.get("current_window_id", "")
335
+
336
+ @staticmethod
337
+ def _migrate(data: dict[str, Any], from_version: int) -> dict[str, Any]:
338
+ """Incrementally migrate session data from *from_version* to current (§audit L7)."""
339
+ d = dict(data)
340
+ if from_version < 2:
341
+ # v1 → v2: add schema_version, ensure edges have source_stage
342
+ d.setdefault("schema_version", 2)
343
+ for edata in d.get("edges", []):
344
+ edata.setdefault("source_stage", 0)
345
+ logger.info("Migrated session data v%d → v%d", from_version, SCHEMA_VERSION)
346
+ return d