contextwall 0.1.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 (66) hide show
  1. context_firewall/__init__.py +3 -0
  2. context_firewall/analytics/__init__.py +5 -0
  3. context_firewall/analytics/engine.py +234 -0
  4. context_firewall/api/__init__.py +5 -0
  5. context_firewall/api/app.py +1020 -0
  6. context_firewall/api/models.py +54 -0
  7. context_firewall/classifier/__init__.py +5 -0
  8. context_firewall/classifier/classifier.py +165 -0
  9. context_firewall/cli/__init__.py +5 -0
  10. context_firewall/cli/main.py +513 -0
  11. context_firewall/compliance/__init__.py +1 -0
  12. context_firewall/compliance/baa.py +57 -0
  13. context_firewall/compliance/chain.py +84 -0
  14. context_firewall/compliance/control_mappings.py +92 -0
  15. context_firewall/compliance/export.py +280 -0
  16. context_firewall/compliance/keys.py +94 -0
  17. context_firewall/config.py +234 -0
  18. context_firewall/control_plane/__init__.py +0 -0
  19. context_firewall/control_plane/client.py +107 -0
  20. context_firewall/control_plane/models.py +72 -0
  21. context_firewall/control_plane/pusher.py +377 -0
  22. context_firewall/daemon/__init__.py +5 -0
  23. context_firewall/daemon/main.py +461 -0
  24. context_firewall/db/__init__.py +6 -0
  25. context_firewall/db/connection.py +30 -0
  26. context_firewall/db/migrations.py +408 -0
  27. context_firewall/entropy/__init__.py +5 -0
  28. context_firewall/entropy/engine.py +271 -0
  29. context_firewall/graph/__init__.py +5 -0
  30. context_firewall/graph/engine.py +162 -0
  31. context_firewall/lint/__init__.py +0 -0
  32. context_firewall/lint/engine.py +334 -0
  33. context_firewall/mcp/__init__.py +5 -0
  34. context_firewall/mcp/server.py +237 -0
  35. context_firewall/metrics.py +100 -0
  36. context_firewall/models.py +130 -0
  37. context_firewall/policy/__init__.py +10 -0
  38. context_firewall/policy/detectors/__init__.py +0 -0
  39. context_firewall/policy/detectors/injection.py +480 -0
  40. context_firewall/policy/dsl/__init__.py +0 -0
  41. context_firewall/policy/dsl/evaluator.py +208 -0
  42. context_firewall/policy/dsl/loader.py +150 -0
  43. context_firewall/policy/dsl/types.py +112 -0
  44. context_firewall/policy/engine.py +532 -0
  45. context_firewall/provenance/__init__.py +5 -0
  46. context_firewall/provenance/engine.py +658 -0
  47. context_firewall/provenance/models.py +91 -0
  48. context_firewall/proxy/__init__.py +1 -0
  49. context_firewall/proxy/router.py +519 -0
  50. context_firewall/proxy/scanner.py +214 -0
  51. context_firewall/proxy/tokens.py +156 -0
  52. context_firewall/runtime/__init__.py +5 -0
  53. context_firewall/runtime/engine.py +303 -0
  54. context_firewall/source/__init__.py +1 -0
  55. context_firewall/source/registry.py +299 -0
  56. context_firewall/source/types.py +12 -0
  57. context_firewall/synthesizer/__init__.py +5 -0
  58. context_firewall/synthesizer/synthesizer.py +255 -0
  59. context_firewall/trust/__init__.py +5 -0
  60. context_firewall/trust/engine.py +139 -0
  61. context_firewall/trust/signals.py +184 -0
  62. contextwall-0.1.0.dist-info/METADATA +431 -0
  63. contextwall-0.1.0.dist-info/RECORD +66 -0
  64. contextwall-0.1.0.dist-info/WHEEL +4 -0
  65. contextwall-0.1.0.dist-info/entry_points.txt +3 -0
  66. contextwall-0.1.0.dist-info/licenses/LICENSE +33 -0
@@ -0,0 +1,3 @@
1
+ """Context Firewall - Context Reliability Engine."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Analytics Engine - DuckDB-powered OLAP queries over ContextWall SQLite data."""
2
+
3
+ from context_firewall.analytics.engine import AnalyticsEngine
4
+
5
+ __all__ = ["AnalyticsEngine"]
@@ -0,0 +1,234 @@
1
+ """Analytics Engine - DuckDB in-process queries with snapshot pre-computation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from datetime import datetime, timezone
10
+
11
+ import aiosqlite
12
+ import duckdb
13
+
14
+ from context_firewall.config import Config
15
+ from context_firewall.models import SubsystemHealth
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="cre-analytics")
20
+
21
+
22
+ def _get_duckdb_conn(sqlite_path: str) -> duckdb.DuckDBPyConnection:
23
+ conn = duckdb.connect(":memory:", read_only=False)
24
+ conn.execute(f"ATTACH '{sqlite_path}' AS cre (TYPE SQLITE, READ_ONLY TRUE)")
25
+ return conn
26
+
27
+
28
+ def _compute_retrieval_metrics_sync(sqlite_path: str) -> list[dict]:
29
+ with _get_duckdb_conn(sqlite_path) as conn:
30
+ rows = conn.execute("""
31
+ SELECT
32
+ json_extract(payload, '$.file_path') AS file_path,
33
+ COUNT(*) FILTER (WHERE event_type = 'slice_included') AS inclusion_count,
34
+ COUNT(*) FILTER (WHERE event_type = 'slice_excluded') AS exclusion_count,
35
+ AVG(json_extract(payload, '$.trust_score')::FLOAT) AS avg_trust_score
36
+ FROM cre.provenance_events
37
+ WHERE event_type IN ('slice_included', 'slice_excluded')
38
+ GROUP BY file_path
39
+ ORDER BY inclusion_count DESC
40
+ LIMIT 100
41
+ """).fetchall()
42
+ return [
43
+ {
44
+ "file_path": r[0],
45
+ "inclusion_count": r[1],
46
+ "exclusion_count": r[2],
47
+ "avg_trust_score": float(r[3]) if r[3] is not None else None,
48
+ }
49
+ for r in rows
50
+ ]
51
+
52
+
53
+ def _compute_trust_degradation_sync(sqlite_path: str, threshold: float, window_days: int) -> list[dict]:
54
+ with _get_duckdb_conn(sqlite_path) as conn:
55
+ rows = conn.execute(f"""
56
+ WITH ranked AS (
57
+ SELECT
58
+ node_id,
59
+ file_path,
60
+ trust_score,
61
+ snapshot_at,
62
+ ROW_NUMBER() OVER (PARTITION BY node_id ORDER BY snapshot_at DESC) AS rn,
63
+ ROW_NUMBER() OVER (PARTITION BY node_id ORDER BY snapshot_at ASC) AS rn_asc
64
+ FROM cre.trust_score_snapshots
65
+ WHERE snapshot_at > (CURRENT_TIMESTAMP - INTERVAL '{window_days} days')
66
+ ),
67
+ first_last AS (
68
+ SELECT
69
+ node_id,
70
+ file_path,
71
+ MAX(CASE WHEN rn_asc = 1 THEN trust_score END) AS initial_score,
72
+ MAX(CASE WHEN rn = 1 THEN trust_score END) AS latest_score
73
+ FROM ranked
74
+ GROUP BY node_id, file_path
75
+ )
76
+ SELECT node_id, file_path, initial_score, latest_score,
77
+ initial_score - latest_score AS degradation
78
+ FROM first_last
79
+ WHERE initial_score - latest_score > {threshold}
80
+ ORDER BY degradation DESC
81
+ LIMIT 50
82
+ """).fetchall()
83
+ return [
84
+ {
85
+ "node_id": r[0],
86
+ "file_path": r[1],
87
+ "initial_score": float(r[2]) if r[2] is not None else None,
88
+ "latest_score": float(r[3]) if r[3] is not None else None,
89
+ "degradation": float(r[4]) if r[4] is not None else None,
90
+ }
91
+ for r in rows
92
+ ]
93
+
94
+
95
+ class AnalyticsEngine:
96
+ name = "analytics_engine"
97
+ critical = False
98
+
99
+ def __init__(self) -> None:
100
+ self._config: Config | None = None
101
+ self._db: aiosqlite.Connection | None = None
102
+ self._sqlite_path: str = ".ctxfw/cre.db"
103
+
104
+ async def init(self, config: Config) -> None:
105
+ self._config = config
106
+ self._sqlite_path = config.storage.db_path
107
+ from context_firewall.db.connection import get_db
108
+ self._db = await get_db()
109
+ logger.info("AnalyticsEngine initialized")
110
+
111
+ def health_check(self) -> SubsystemHealth:
112
+ return SubsystemHealth(name=self.name, healthy=True)
113
+
114
+ async def shutdown(self) -> None:
115
+ self._db = None
116
+
117
+ async def get_retrieval_metrics(self) -> dict:
118
+ snapshot = await self._latest_snapshot("retrieval_metrics")
119
+ if snapshot:
120
+ return {"data": snapshot, "source": "snapshot"}
121
+ try:
122
+ loop = asyncio.get_event_loop()
123
+ data = await loop.run_in_executor(
124
+ _executor,
125
+ _compute_retrieval_metrics_sync,
126
+ self._sqlite_path,
127
+ )
128
+ return {"data": data, "source": "live"}
129
+ except Exception as e:
130
+ logger.error("retrieval metrics failed", extra={"error": str(e)})
131
+ return {"data": [], "error": str(e)}
132
+
133
+ async def get_trust_degradation(self) -> dict:
134
+ cfg = self._config.analytics if self._config else None
135
+ threshold = cfg.degradation_threshold if cfg else 0.15
136
+ window_days = cfg.degradation_window_days if cfg else 30
137
+ try:
138
+ loop = asyncio.get_event_loop()
139
+ data = await loop.run_in_executor(
140
+ _executor,
141
+ _compute_trust_degradation_sync,
142
+ self._sqlite_path,
143
+ threshold,
144
+ window_days,
145
+ )
146
+ return {"data": data, "threshold": threshold, "window_days": window_days}
147
+ except Exception as e:
148
+ logger.error("trust degradation failed", extra={"error": str(e)})
149
+ return {"data": [], "error": str(e)}
150
+
151
+ async def get_entropy_trends(self) -> dict:
152
+ if self._db is None:
153
+ return {"data": []}
154
+ async with self._db.execute(
155
+ """
156
+ SELECT node_id, file_path, entropy_score, snapshot_at
157
+ FROM entropy_snapshots
158
+ ORDER BY snapshot_at DESC
159
+ LIMIT 500
160
+ """
161
+ ) as cursor:
162
+ rows = await cursor.fetchall()
163
+ return {
164
+ "data": [
165
+ {"node_id": r["node_id"], "file_path": r["file_path"],
166
+ "entropy_score": r["entropy_score"], "snapshot_at": r["snapshot_at"]}
167
+ for r in rows
168
+ ]
169
+ }
170
+
171
+ async def get_budget_utilization(self) -> dict:
172
+ snapshot = await self._latest_snapshot("budget_utilization")
173
+ if snapshot:
174
+ return {"data": snapshot, "source": "snapshot"}
175
+ if self._db is None:
176
+ return {"data": []}
177
+ async with self._db.execute(
178
+ """
179
+ SELECT
180
+ json_extract(payload, '$.task_type') AS task_type,
181
+ AVG(CAST(json_extract(payload, '$.total_tokens') AS REAL)
182
+ / CAST(json_extract(payload, '$.token_budget') AS REAL)) AS avg_utilization,
183
+ COUNT(*) AS bundle_count
184
+ FROM provenance_events
185
+ WHERE event_type = 'context_request'
186
+ GROUP BY task_type
187
+ """
188
+ ) as cursor:
189
+ rows = await cursor.fetchall()
190
+ return {
191
+ "data": [
192
+ {"task_type": r["task_type"], "avg_utilization": r["avg_utilization"], "bundle_count": r["bundle_count"]}
193
+ for r in rows
194
+ ],
195
+ "source": "live",
196
+ }
197
+
198
+ async def _latest_snapshot(self, metric_type: str) -> list | None:
199
+ if self._db is None:
200
+ return None
201
+ async with self._db.execute(
202
+ """
203
+ SELECT payload FROM analytics_snapshots
204
+ WHERE metric_type = ?
205
+ ORDER BY computed_at DESC
206
+ LIMIT 1
207
+ """,
208
+ (metric_type,),
209
+ ) as cursor:
210
+ row = await cursor.fetchone()
211
+ if row:
212
+ return json.loads(row["payload"])
213
+ return None
214
+
215
+ async def compute_and_store_snapshots(self) -> None:
216
+ """Called by the offline job scheduler."""
217
+ now = datetime.now(timezone.utc)
218
+ try:
219
+ loop = asyncio.get_event_loop()
220
+ retrieval_data = await loop.run_in_executor(
221
+ _executor, _compute_retrieval_metrics_sync, self._sqlite_path
222
+ )
223
+ if self._db:
224
+ await self._db.execute(
225
+ """
226
+ INSERT INTO analytics_snapshots
227
+ (metric_type, granularity, key, payload, computed_at)
228
+ VALUES (?, ?, ?, ?, ?)
229
+ """,
230
+ ("retrieval_metrics", "hourly", "all", json.dumps(retrieval_data), now.isoformat()),
231
+ )
232
+ await self._db.commit()
233
+ except Exception as e:
234
+ logger.error("snapshot computation failed", extra={"error": str(e)})
@@ -0,0 +1,5 @@
1
+ """FastAPI REST API."""
2
+
3
+ from context_firewall.api.app import create_app
4
+
5
+ __all__ = ["create_app"]