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,267 @@
1
+ # Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
2
+ # Licensed under Elastic License 2.0 — see LICENSE.md for details.
3
+ """Fact integrity — BLAKE3/SHA-256 hash chain + HMAC verification (§7.2, §7.7).
4
+
5
+ Provides a tamper-evident chain of fact hashes:
6
+ - Per-fact hash: BLAKE3 (~1μs) with SHA-256 fallback
7
+ - Hash chain: ordered sequence of fact hashes
8
+ - Chain signature: HMAC(session_key, hash_N ‖ ... ‖ hash_0)
9
+ - Spot-check verification: 10% sample on cold load
10
+ - Full verification on envelope construction
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import hmac as _hmac
17
+ import random
18
+ from dataclasses import dataclass
19
+ from typing import Any
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Per-fact hashing
23
+ # ---------------------------------------------------------------------------
24
+
25
+ _USE_BLAKE3: bool | None = None
26
+
27
+
28
+ def _has_blake3() -> bool:
29
+ global _USE_BLAKE3 # noqa: PLW0603
30
+ if _USE_BLAKE3 is None:
31
+ try:
32
+ import blake3 # type: ignore[import-untyped] # noqa: F401
33
+ _USE_BLAKE3 = True
34
+ except ImportError:
35
+ _USE_BLAKE3 = False
36
+ return _USE_BLAKE3
37
+
38
+
39
+ def compute_fact_hash(text: str) -> str:
40
+ """Compute integrity hash for a fact's text (§6B.2).
41
+
42
+ Uses BLAKE3 (~1μs) when available, SHA-256 fallback.
43
+ """
44
+ data = text.encode("utf-8")
45
+ if _has_blake3():
46
+ import blake3 # type: ignore[import-untyped]
47
+ return blake3.blake3(data).hexdigest()
48
+ return hashlib.sha256(data).hexdigest()
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Fact Integrity Chain
53
+ # ---------------------------------------------------------------------------
54
+
55
+
56
+ @dataclass
57
+ class ChainEntry:
58
+ """Single entry in the fact integrity chain."""
59
+
60
+ fact_id: str
61
+ fact_hash: str
62
+ index: int # position in chain
63
+
64
+
65
+ class FactIntegrityChain:
66
+ """Tamper-evident chain of fact hashes (§7.7).
67
+
68
+ Maintains an ordered hash chain. Chain signature is computed via
69
+ HMAC(session_key, hash_N ‖ ... ‖ hash_0).
70
+
71
+ Usage:
72
+ chain = FactIntegrityChain(session_key)
73
+ chain.add_fact("f1", "capital of France is Paris")
74
+ sig = chain.chain_signature()
75
+ assert chain.verify_chain(sig)
76
+ """
77
+
78
+ def __init__(self, session_key: bytes | None = None) -> None:
79
+ self._session_key = session_key or b""
80
+ self._entries: list[ChainEntry] = []
81
+ self._hash_by_id: dict[str, str] = {}
82
+
83
+ @property
84
+ def size(self) -> int:
85
+ return len(self._entries)
86
+
87
+ def add_fact(self, fact_id: str, text: str) -> ChainEntry:
88
+ """Hash a fact and append to the chain."""
89
+ h = compute_fact_hash(text)
90
+ entry = ChainEntry(
91
+ fact_id=fact_id,
92
+ fact_hash=h,
93
+ index=len(self._entries),
94
+ )
95
+ self._entries.append(entry)
96
+ self._hash_by_id[fact_id] = h
97
+ return entry
98
+
99
+ def get_hash(self, fact_id: str) -> str | None:
100
+ """Get stored hash for a fact by ID."""
101
+ return self._hash_by_id.get(fact_id)
102
+
103
+ def verify_fact(self, fact_id: str, text: str) -> bool:
104
+ """Verify a single fact's hash matches the chain."""
105
+ stored = self._hash_by_id.get(fact_id)
106
+ if stored is None:
107
+ return False
108
+ return _hmac.compare_digest(stored, compute_fact_hash(text))
109
+
110
+ # ── Chain signature (§6B.3) ────────────────────────────────
111
+
112
+ def chain_signature(self) -> str:
113
+ """Compute HMAC(session_key, hash_N ‖ ... ‖ hash_0) (§6B.3).
114
+
115
+ Hash chain is concatenated in reverse order (newest first).
116
+ """
117
+ if not self._session_key:
118
+ raise ValueError("No session key set — cannot compute chain signature")
119
+ # Concatenate hashes in reverse order
120
+ chain_data = "".join(e.fact_hash for e in reversed(self._entries))
121
+ return _hmac.new(
122
+ self._session_key, chain_data.encode("utf-8"), hashlib.sha256
123
+ ).hexdigest()
124
+
125
+ def verify_chain(self, expected_signature: str) -> bool:
126
+ """Verify the full chain signature matches."""
127
+ if not self._session_key:
128
+ return False
129
+ actual = self.chain_signature()
130
+ return _hmac.compare_digest(actual, expected_signature)
131
+
132
+ # ── Spot-check verification (§6B.4) ───────────────────────
133
+
134
+ def verify_spot_check(
135
+ self,
136
+ fact_texts: dict[str, str],
137
+ sample_ratio: float = 0.10,
138
+ ) -> tuple[int, int, list[str]]:
139
+ """Spot-check 10% of facts on cold load (§6B.4).
140
+
141
+ Args:
142
+ fact_texts: {fact_id: current_text} for verification
143
+ sample_ratio: fraction of chain to sample (default 10%)
144
+
145
+ Returns:
146
+ (checked, failures, failed_ids)
147
+ """
148
+ if not self._entries:
149
+ return 0, 0, []
150
+
151
+ sample_size = max(1, int(len(self._entries) * sample_ratio))
152
+ sample = random.sample(self._entries, min(sample_size, len(self._entries)))
153
+
154
+ failures: list[str] = []
155
+ for entry in sample:
156
+ text = fact_texts.get(entry.fact_id)
157
+ if text is None:
158
+ failures.append(entry.fact_id)
159
+ continue
160
+ if not self.verify_fact(entry.fact_id, text):
161
+ failures.append(entry.fact_id)
162
+
163
+ return len(sample), len(failures), failures
164
+
165
+ # ── Envelope verification (§6B.5) ─────────────────────────
166
+
167
+ def verify_for_envelope(
168
+ self, fact_ids: list[str], fact_texts: dict[str, str]
169
+ ) -> tuple[bool, list[str]]:
170
+ """Verify all facts before including in envelope (§6B.5).
171
+
172
+ Returns (all_valid, failed_ids).
173
+ """
174
+ failed: list[str] = []
175
+ for fid in fact_ids:
176
+ text = fact_texts.get(fid)
177
+ if text is None:
178
+ failed.append(fid)
179
+ continue
180
+ if not self.verify_fact(fid, text):
181
+ failed.append(fid)
182
+ return len(failed) == 0, failed
183
+
184
+ # ── Serialization ─────────────────────────────────────────
185
+
186
+ def to_dict(self) -> dict[str, Any]:
187
+ return {
188
+ "entries": [
189
+ {"fact_id": e.fact_id, "fact_hash": e.fact_hash, "index": e.index}
190
+ for e in self._entries
191
+ ],
192
+ }
193
+
194
+ def export_for_verification(self) -> dict[str, Any]:
195
+ """Export chain data for external verification (§audit M13).
196
+
197
+ Returns a dict containing all chain entries and (if session key is set)
198
+ the chain signature, suitable for independent audit.
199
+ """
200
+ export: dict[str, Any] = {
201
+ "chain_length": len(self._entries),
202
+ "hash_algorithm": "blake3" if _has_blake3() else "sha256",
203
+ "entries": [
204
+ {
205
+ "fact_id": e.fact_id,
206
+ "fact_hash": e.fact_hash,
207
+ "index": e.index,
208
+ }
209
+ for e in self._entries
210
+ ],
211
+ }
212
+ if self._session_key:
213
+ export["chain_signature"] = self.chain_signature()
214
+ return export
215
+
216
+ @staticmethod
217
+ def verify_external(
218
+ export_data: dict[str, Any],
219
+ fact_texts: dict[str, str],
220
+ ) -> tuple[bool, list[str]]:
221
+ """Verify an exported chain against provided fact texts (§audit M13).
222
+
223
+ This is a static method usable without access to the session key —
224
+ it re-hashes each fact and compares to the stored hash.
225
+
226
+ Returns (all_valid, failed_fact_ids).
227
+ """
228
+ # Use the algorithm recorded at export time to avoid mismatch
229
+ algo = export_data.get("hash_algorithm", "sha256")
230
+ if algo == "blake3" and not _has_blake3():
231
+ raise RuntimeError(
232
+ "Exported chain uses blake3 but blake3 is not installed. "
233
+ "Install blake3 to verify this chain."
234
+ )
235
+
236
+ def _hash_text(text: str) -> str:
237
+ data = text.encode("utf-8")
238
+ if algo == "blake3":
239
+ import blake3 as _b3 # type: ignore[import-untyped]
240
+ return _b3.blake3(data).hexdigest()
241
+ return hashlib.sha256(data).hexdigest()
242
+
243
+ failures: list[str] = []
244
+ for entry in export_data.get("entries", []):
245
+ fid = entry["fact_id"]
246
+ expected_hash = entry["fact_hash"]
247
+ text = fact_texts.get(fid)
248
+ if text is None:
249
+ failures.append(fid)
250
+ continue
251
+ actual_hash = _hash_text(text)
252
+ if not _hmac.compare_digest(actual_hash, expected_hash):
253
+ failures.append(fid)
254
+ return len(failures) == 0, failures
255
+
256
+ @classmethod
257
+ def from_dict(cls, data: dict[str, Any], session_key: bytes | None = None) -> FactIntegrityChain:
258
+ chain = cls(session_key=session_key)
259
+ for edata in data.get("entries", []):
260
+ entry = ChainEntry(
261
+ fact_id=edata["fact_id"],
262
+ fact_hash=edata["fact_hash"],
263
+ index=edata.get("index", len(chain._entries)),
264
+ )
265
+ chain._entries.append(entry)
266
+ chain._hash_by_id[entry.fact_id] = entry.fact_hash
267
+ return chain