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
|
@@ -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
|