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
crp/core/config.py ADDED
@@ -0,0 +1,377 @@
1
+ # Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
2
+ # Licensed under Elastic License 2.0 — see LICENSE.md for details.
3
+ """ConfigurationResolver — 5-layer hierarchy, feature flags (§06 §6.12).
4
+
5
+ Resolution order (later overrides prior):
6
+ Layer 1: Hardcoded defaults (this file)
7
+ Layer 2: Environment variables (CRP_* prefix)
8
+ Layer 3: Config file (YAML/.env) — future
9
+ Layer 4: Client() init kwargs
10
+ Layer 5: Runtime configure() call
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import logging
17
+ import os
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ logger = logging.getLogger("crp.core.config")
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Defaults (Layer 1)
26
+ # ---------------------------------------------------------------------------
27
+
28
+ _DEFAULTS: dict[str, Any] = {
29
+ # Master switch — True by default for standalone SDK usage.
30
+ # Set CRP_ENABLED=false to disable when embedded in a host framework.
31
+ "enabled": True,
32
+ # Limits
33
+ "max_continuations": 50,
34
+ "max_dispatch_rate": 60,
35
+ "session_timeout": 86400,
36
+ "ingest_quarantine": 1,
37
+ # Resource
38
+ "max_ram_mb": 512,
39
+ "max_model_ram_mb": 300,
40
+ "max_threads": 2,
41
+ "process_priority": "below_normal",
42
+ "memory_budget_mb": 512,
43
+ "idle_model_timeout_s": 300.0,
44
+ # Behaviour
45
+ "log_envelopes": False,
46
+ "encrypt_cold_state": True,
47
+ "default_role": "OPERATOR",
48
+ # Security
49
+ "binding_secret": "",
50
+ # Budget caps (immutable) — 0 means unlimited
51
+ "max_windows_per_session": 0,
52
+ "max_total_input_tokens": 0,
53
+ "max_total_output_tokens": 0,
54
+ # Latency
55
+ "max_envelope_latency_ms": 500,
56
+ "max_extraction_latency_ms": 200,
57
+ "max_windows_per_minute": 0,
58
+ # Observability
59
+ "telemetry_path": "",
60
+ # Overhead & parallelism
61
+ "overhead_cap_pct": 15.0,
62
+ "parallel_max_concurrent": 4,
63
+ # Extraction stages — which stages are enabled at init
64
+ "enable_stage_3": True, # GLiNER zero-shot NER
65
+ "enable_stage_4": True, # UIE relation extraction
66
+ "enable_stage_5": True, # Discourse markers
67
+ "enable_stage_6": False, # LLM-assisted (costly, rare)
68
+ # Dispatch timeout (seconds) — wall-time cap per continuation loop
69
+ "dispatch_timeout": 3600,
70
+ }
71
+
72
+ # Fields set at init that CANNOT change after session start
73
+ _IMMUTABLE_FIELDS: frozenset[str] = frozenset({
74
+ "max_windows_per_session",
75
+ "max_total_input_tokens",
76
+ "max_total_output_tokens",
77
+ "binding_secret",
78
+ "session_timeout",
79
+ })
80
+
81
+ # Environment variable prefix
82
+ _ENV_PREFIX = "CRP_"
83
+
84
+ # Mapping from env-var suffix to config key
85
+ _ENV_MAP: dict[str, str] = {
86
+ "ENABLED": "enabled",
87
+ "LOG_ENVELOPES": "log_envelopes",
88
+ "MAX_CONTINUATIONS": "max_continuations",
89
+ "MAX_RAM_MB": "max_ram_mb",
90
+ "MAX_MODEL_RAM_MB": "max_model_ram_mb",
91
+ "MAX_THREADS": "max_threads",
92
+ "PROCESS_PRIORITY": "process_priority",
93
+ "BINDING_SECRET": "binding_secret",
94
+ "SESSION_TIMEOUT": "session_timeout",
95
+ "DEFAULT_ROLE": "default_role",
96
+ "ENCRYPT_COLD_STATE": "encrypt_cold_state",
97
+ "MAX_DISPATCH_RATE": "max_dispatch_rate",
98
+ "INGEST_QUARANTINE": "ingest_quarantine",
99
+ "ENABLE_STAGE_3": "enable_stage_3",
100
+ "ENABLE_STAGE_4": "enable_stage_4",
101
+ "ENABLE_STAGE_5": "enable_stage_5",
102
+ "ENABLE_STAGE_6": "enable_stage_6",
103
+ "DISPATCH_TIMEOUT": "dispatch_timeout",
104
+ }
105
+
106
+ _BOOL_KEYS = frozenset({"enabled", "log_envelopes", "encrypt_cold_state",
107
+ "enable_stage_3", "enable_stage_4", "enable_stage_5",
108
+ "enable_stage_6"})
109
+ _INT_KEYS = frozenset({
110
+ "max_continuations", "max_dispatch_rate", "session_timeout",
111
+ "ingest_quarantine", "max_ram_mb", "max_model_ram_mb", "max_threads",
112
+ "max_windows_per_session", "max_total_input_tokens",
113
+ "max_total_output_tokens", "max_envelope_latency_ms",
114
+ "max_extraction_latency_ms", "max_windows_per_minute",
115
+ "dispatch_timeout",
116
+ })
117
+
118
+
119
+ def _parse_env_value(key: str, raw: str) -> Any:
120
+ """Parse a raw environment variable string to the correct type."""
121
+ if key in _BOOL_KEYS:
122
+ return raw.lower() in ("1", "true", "yes")
123
+ if key in _INT_KEYS:
124
+ try:
125
+ return int(raw)
126
+ except ValueError:
127
+ logger.warning("Invalid integer for %s: %r — using default", key, raw)
128
+ return None # Will be filtered out, falling through to default
129
+ return raw
130
+
131
+
132
+ @dataclass
133
+ class CRPConfig:
134
+ """Resolved CRP configuration.
135
+
136
+ Constructed via :class:`ConfigurationResolver`.
137
+ """
138
+
139
+ _values: dict[str, Any] = field(default_factory=dict)
140
+ _locked: bool = False
141
+
142
+ def get(self, key: str, default: Any = None) -> Any:
143
+ return self._values.get(key, default)
144
+
145
+ def __getitem__(self, key: str) -> Any:
146
+ return self._values[key]
147
+
148
+ # Convenience typed accessors for common flags
149
+ @property
150
+ def enabled(self) -> bool:
151
+ return bool(self._values.get("enabled", False))
152
+
153
+ @property
154
+ def max_continuations(self) -> int:
155
+ return int(self._values.get("max_continuations", 50))
156
+
157
+ @property
158
+ def log_envelopes(self) -> bool:
159
+ return bool(self._values.get("log_envelopes", False))
160
+
161
+ @property
162
+ def max_dispatch_rate(self) -> int:
163
+ return int(self._values.get("max_dispatch_rate", 60))
164
+
165
+ @property
166
+ def session_timeout(self) -> int:
167
+ return int(self._values.get("session_timeout", 86400))
168
+
169
+ @property
170
+ def max_windows_per_session(self) -> int:
171
+ return int(self._values.get("max_windows_per_session", 0))
172
+
173
+ @property
174
+ def max_total_input_tokens(self) -> int:
175
+ return int(self._values.get("max_total_input_tokens", 0))
176
+
177
+ @property
178
+ def max_total_output_tokens(self) -> int:
179
+ return int(self._values.get("max_total_output_tokens", 0))
180
+
181
+ def lock(self) -> None:
182
+ """Lock immutable fields — called after session starts."""
183
+ self._locked = True
184
+
185
+ def update(self, overrides: dict[str, Any]) -> None:
186
+ """Apply mutable-only overrides (Layer 5: runtime configure).
187
+
188
+ Raises ValueError if attempting to change an immutable field
189
+ after lock().
190
+ """
191
+ for key, value in overrides.items():
192
+ if self._locked and key in _IMMUTABLE_FIELDS:
193
+ msg = f"Cannot change immutable config '{key}' after session start"
194
+ raise ValueError(msg)
195
+ self._values[key] = value
196
+
197
+
198
+ _VALID_PROCESS_PRIORITIES = frozenset({
199
+ "idle", "below_normal", "normal", "above_normal", "high",
200
+ })
201
+ _VALID_ROLES = frozenset({
202
+ "OBSERVER", "OPERATOR", "ADMIN",
203
+ })
204
+
205
+ # Validation bounds: (min, max) inclusive. None means unbounded.
206
+ _INT_BOUNDS: dict[str, tuple[int | None, int | None]] = {
207
+ "max_continuations": (0, 10_000),
208
+ "max_dispatch_rate": (1, 10_000),
209
+ "session_timeout": (0, 604_800), # 0 s – 7 days (0 = immediate expiry)
210
+ "ingest_quarantine": (0, 60),
211
+ "max_ram_mb": (1, 65_536),
212
+ "max_model_ram_mb": (1, 65_536),
213
+ "max_threads": (1, 64),
214
+ "max_windows_per_session": (0, 1_000_000), # 0 = unlimited
215
+ "max_total_input_tokens": (0, 2**31),
216
+ "max_total_output_tokens": (0, 2**31),
217
+ "max_envelope_latency_ms": (0, 60_000),
218
+ "max_extraction_latency_ms": (0, 60_000),
219
+ "max_windows_per_minute": (0, 10_000),
220
+ "parallel_max_concurrent": (1, 128),
221
+ "dispatch_timeout": (10, 86_400), # 10s – 24h
222
+ "memory_budget_mb": (1, 65_536),
223
+ }
224
+ _FLOAT_BOUNDS: dict[str, tuple[float | None, float | None]] = {
225
+ "overhead_cap_pct": (0.0, 100.0),
226
+ "idle_model_timeout_s": (0.0, 86_400.0),
227
+ }
228
+
229
+
230
+ def _validate_config(values: dict[str, Any]) -> None:
231
+ """Validate resolved configuration values. Raises ValueError on bad input."""
232
+ errors: list[str] = []
233
+ for key, (lo, hi) in _INT_BOUNDS.items():
234
+ val = values.get(key)
235
+ if val is None:
236
+ continue
237
+ try:
238
+ val = int(val)
239
+ except (TypeError, ValueError):
240
+ errors.append(f"{key}: expected int, got {type(val).__name__}")
241
+ continue
242
+ if lo is not None and val < lo:
243
+ errors.append(f"{key}: {val} < minimum {lo}")
244
+ if hi is not None and val > hi:
245
+ errors.append(f"{key}: {val} > maximum {hi}")
246
+
247
+ for key, (lo, hi) in _FLOAT_BOUNDS.items():
248
+ val = values.get(key)
249
+ if val is None:
250
+ continue
251
+ try:
252
+ val = float(val)
253
+ except (TypeError, ValueError):
254
+ errors.append(f"{key}: expected number, got {type(val).__name__}")
255
+ continue
256
+ if lo is not None and val < lo:
257
+ errors.append(f"{key}: {val} < minimum {lo}")
258
+ if hi is not None and val > hi:
259
+ errors.append(f"{key}: {val} > maximum {hi}")
260
+
261
+ pp = values.get("process_priority")
262
+ if pp is not None and pp not in _VALID_PROCESS_PRIORITIES:
263
+ errors.append(
264
+ f"process_priority: '{pp}' not in {sorted(_VALID_PROCESS_PRIORITIES)}"
265
+ )
266
+
267
+ role = values.get("default_role")
268
+ if role is not None and role not in _VALID_ROLES:
269
+ errors.append(f"default_role: '{role}' not in {sorted(_VALID_ROLES)}")
270
+
271
+ if errors:
272
+ raise ValueError(
273
+ "CRP configuration validation failed:\n - " + "\n - ".join(errors)
274
+ )
275
+
276
+
277
+ class ConfigurationResolver:
278
+ """Resolve configuration through the 5-layer hierarchy."""
279
+
280
+ @staticmethod
281
+ def _read_config_file() -> dict[str, Any]:
282
+ """Layer 3: Read config from file (YAML/JSON).
283
+
284
+ Search order:
285
+ 1. ``CRP_CONFIG_FILE`` env var (explicit path)
286
+ 2. ``~/.crp/config.yaml`` / ``~/.crp/config.json``
287
+ 3. ``.crp.yaml`` / ``.crp.json`` in CWD
288
+ """
289
+ explicit = os.environ.get("CRP_CONFIG_FILE")
290
+ if explicit:
291
+ p = Path(explicit)
292
+ if not p.is_file():
293
+ logger.warning("CRP_CONFIG_FILE=%s does not exist — skipping", explicit)
294
+ return {}
295
+ return ConfigurationResolver._parse_config_path(p)
296
+
297
+ candidates = [
298
+ Path.home() / ".crp" / "config.yaml",
299
+ Path.home() / ".crp" / "config.yml",
300
+ Path.home() / ".crp" / "config.json",
301
+ Path.cwd() / ".crp.yaml",
302
+ Path.cwd() / ".crp.yml",
303
+ Path.cwd() / ".crp.json",
304
+ ]
305
+ for candidate in candidates:
306
+ if candidate.is_file():
307
+ return ConfigurationResolver._parse_config_path(candidate)
308
+ return {}
309
+
310
+ @staticmethod
311
+ def _parse_config_path(path: Path) -> dict[str, Any]:
312
+ """Parse a YAML or JSON config file, returning only known keys."""
313
+ raw_text = path.read_text(encoding="utf-8")
314
+ suffix = path.suffix.lower()
315
+ try:
316
+ if suffix in (".yaml", ".yml"):
317
+ try:
318
+ import yaml # type: ignore[import-untyped]
319
+ data = yaml.safe_load(raw_text) or {}
320
+ except ImportError:
321
+ logger.warning("PyYAML not installed — cannot read %s", path)
322
+ return {}
323
+ elif suffix == ".json":
324
+ data = json.loads(raw_text)
325
+ else:
326
+ logger.warning("Unsupported config file format: %s", path)
327
+ return {}
328
+ except Exception:
329
+ logger.exception("Failed to parse config file %s", path)
330
+ return {}
331
+
332
+ if not isinstance(data, dict):
333
+ logger.warning("Config file %s did not parse as a dict — ignoring", path)
334
+ return {}
335
+
336
+ # Only accept known config keys to prevent injection of arbitrary values
337
+ known_keys = set(_DEFAULTS)
338
+ result: dict[str, Any] = {}
339
+ for key, value in data.items():
340
+ if key in known_keys:
341
+ result[key] = _parse_env_value(key, str(value)) if isinstance(value, str) else value
342
+ return result
343
+
344
+ def resolve(self, **init_kwargs: Any) -> CRPConfig:
345
+ """Build a CRPConfig from layers 1-4.
346
+
347
+ Args:
348
+ **init_kwargs: Layer 4 overrides from Client() constructor.
349
+
350
+ Raises:
351
+ ValueError: If any resolved value is out of bounds.
352
+ """
353
+ # Layer 1: Hardcoded defaults
354
+ values = dict(_DEFAULTS)
355
+
356
+ # Layer 2: Environment variables
357
+ for env_suffix, config_key in _ENV_MAP.items():
358
+ env_var = f"{_ENV_PREFIX}{env_suffix}"
359
+ raw = os.environ.get(env_var)
360
+ if raw is not None:
361
+ values[config_key] = _parse_env_value(config_key, raw)
362
+
363
+ # Layer 3: Config file (YAML/JSON)
364
+ file_values = self._read_config_file()
365
+ values.update(file_values)
366
+
367
+ # Layer 4: Client() init kwargs
368
+ unknown_keys = [k for k in init_kwargs if k not in values]
369
+ if unknown_keys:
370
+ logger.warning("Unknown config keys ignored (§audit L3): %s", unknown_keys)
371
+ for key, value in init_kwargs.items():
372
+ if key in values:
373
+ values[key] = value
374
+
375
+ _validate_config(values)
376
+
377
+ return CRPConfig(_values=values)