cairo-sdk 1.0.0__tar.gz

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.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: cairo-sdk
3
+ Version: 1.0.0
4
+ Summary: CAIRO Protocol SDK — forensic AI governance telemetry, deterministic interception, and EU AI Act enforcement for agentic AI systems
5
+ Author-email: XSData Factory Private Limited <sdk@cairoprotocol.com>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://cairoprotocol.com
8
+ Project-URL: Documentation, https://cairoprotocol.com/docs
9
+ Project-URL: SDK Quickstart, https://cairoprotocol.com/docs/sdk-quickstart
10
+ Project-URL: Source Code, https://cairoprotocol.com/platform
11
+ Keywords: ai-governance,forensic-ai,eu-ai-act,cairo-protocol,agentic-ai,compliance,telemetry
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: Other/Proprietary License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: pydantic>=2.0
27
+
28
+ # CAIRO Protocol SDK
29
+
30
+ **Forensic AI governance for agentic systems** — deterministic interception, real-time telemetry, and EU AI Act 2026 enforcement.
31
+
32
+ ## Install
33
+ ```bash
34
+ pip install cairo-sdk
35
+ ```
36
+
37
+ ## Quick Start
38
+ ```python
39
+ from cairo_sdk import TelemetryStream, ValueTracker, RiskPulse
40
+
41
+ # Initialize telemetry
42
+ stream = TelemetryStream(
43
+ value_tracker=ValueTracker(
44
+ annual_turnover_eur=500_000_000,
45
+ usd_per_eur=1.08,
46
+ )
47
+ )
48
+
49
+ # Emit a risk pulse
50
+ pulse = RiskPulse(
51
+ pillar="Containment",
52
+ component="prompt_injection",
53
+ sub_component="boundary_check",
54
+ risk_level=0.7,
55
+ eu_ai_act_status="high",
56
+ )
57
+ stream.emit(pulse)
58
+
59
+ # Get dashboard summary
60
+ summary = stream.value_tracker.dashboard_summary()
61
+ print(f"Total intercepts: {summary['total_intercepts']}")
62
+ print(f"Risk exposure avoided: ${summary['total_risk_usd']:,.2f}")
63
+ ```
64
+
65
+ ## Five CAIRO Pillars
66
+
67
+ | Pillar | Purpose | Module |
68
+ |--------|---------|--------|
69
+ | **Containment** | Boundaries, scope lits, sandboxing | `cairo_sdk.containment` |
70
+ | **Attestation** | Compliance proof, audit trail | `cairo_sdk.attestation` |
71
+ | **Interception** | Pre/post request hooks, middleware | `cairo_sdk.interception` |
72
+ | **Resilience** | Fault handling, retries, fallback | `cairo_sdk.resilience` |
73
+ | **Output-Logic** | Output validation, guardrails | `cairo_sdk.output_logic` |
74
+
75
+ ## Key Features
76
+
77
+ - **ValueTracker** — real-time EU AI Act fine-avoidance calculation
78
+ - **TelemetryStream** — high-speed forensic event streaming
79
+ - **LogicDriftDetector** — behavioral drift detection across agent sessions
80
+ - **Deterministic interception** — zero-trust validation at every CAIRO pillar
81
+ - **Compliance metadata** — NIST AI RMF, ISO 42001, EU AI Act mapping
82
+
83
+ ## Requirements
84
+
85
+ - Python 3.9+
86
+ - pydantic >= 2.0
87
+
88
+ ## Documentation
89
+
90
+ - [SDK Quickstart](https://cairoprotocol.com/docs/sdk-quickstart)
91
+ - [Integration Guides](https://cairoprotocol.com/docs/integration-guides)
92
+ - [API Reference](https://cairoprotocol.com/docs/api-reference)
93
+
94
+ #
95
+ Proprietary — XSData Factory Private Limited. See [cairoprotocol.com/trust](https://cairoprotocol.com/trust) for terms.
@@ -0,0 +1,68 @@
1
+ # CAIRO Protocol SDK
2
+
3
+ **Forensic AI governance for agentic systems** — deterministic interception, real-time telemetry, and EU AI Act 2026 enforcement.
4
+
5
+ ## Install
6
+ ```bash
7
+ pip install cairo-sdk
8
+ ```
9
+
10
+ ## Quick Start
11
+ ```python
12
+ from cairo_sdk import TelemetryStream, ValueTracker, RiskPulse
13
+
14
+ # Initialize telemetry
15
+ stream = TelemetryStream(
16
+ value_tracker=ValueTracker(
17
+ annual_turnover_eur=500_000_000,
18
+ usd_per_eur=1.08,
19
+ )
20
+ )
21
+
22
+ # Emit a risk pulse
23
+ pulse = RiskPulse(
24
+ pillar="Containment",
25
+ component="prompt_injection",
26
+ sub_component="boundary_check",
27
+ risk_level=0.7,
28
+ eu_ai_act_status="high",
29
+ )
30
+ stream.emit(pulse)
31
+
32
+ # Get dashboard summary
33
+ summary = stream.value_tracker.dashboard_summary()
34
+ print(f"Total intercepts: {summary['total_intercepts']}")
35
+ print(f"Risk exposure avoided: ${summary['total_risk_usd']:,.2f}")
36
+ ```
37
+
38
+ ## Five CAIRO Pillars
39
+
40
+ | Pillar | Purpose | Module |
41
+ |--------|---------|--------|
42
+ | **Containment** | Boundaries, scope lits, sandboxing | `cairo_sdk.containment` |
43
+ | **Attestation** | Compliance proof, audit trail | `cairo_sdk.attestation` |
44
+ | **Interception** | Pre/post request hooks, middleware | `cairo_sdk.interception` |
45
+ | **Resilience** | Fault handling, retries, fallback | `cairo_sdk.resilience` |
46
+ | **Output-Logic** | Output validation, guardrails | `cairo_sdk.output_logic` |
47
+
48
+ ## Key Features
49
+
50
+ - **ValueTracker** — real-time EU AI Act fine-avoidance calculation
51
+ - **TelemetryStream** — high-speed forensic event streaming
52
+ - **LogicDriftDetector** — behavioral drift detection across agent sessions
53
+ - **Deterministic interception** — zero-trust validation at every CAIRO pillar
54
+ - **Compliance metadata** — NIST AI RMF, ISO 42001, EU AI Act mapping
55
+
56
+ ## Requirements
57
+
58
+ - Python 3.9+
59
+ - pydantic >= 2.0
60
+
61
+ ## Documentation
62
+
63
+ - [SDK Quickstart](https://cairoprotocol.com/docs/sdk-quickstart)
64
+ - [Integration Guides](https://cairoprotocol.com/docs/integration-guides)
65
+ - [API Reference](https://cairoprotocol.com/docs/api-reference)
66
+
67
+ #
68
+ Proprietary — XSData Factory Private Limited. See [cairoprotocol.com/trust](https://cairoprotocol.com/trust) for terms.
@@ -0,0 +1,37 @@
1
+ """
2
+ CAIRO SDK — telemetry, deterministic interception, EU AI Act enforcement, and ValueTracker.
3
+ 100% aligned with CAIRO Protocol Technical Architecture v1.0.
4
+ """
5
+
6
+ from cairo_sdk.telemetry import (
7
+ TelemetryStream,
8
+ RiskPulse,
9
+ ComplianceMetadata,
10
+ SafetyOverheadEvent,
11
+ LogicDriftDetector,
12
+ EUAIActStatus,
13
+ PulseSeverity,
14
+ get_global_stream,
15
+ # ValueTracker & EU AI Act 2026 cost reporting
16
+ ValueTracker,
17
+ InterceptRecord,
18
+ EnforcementTier,
19
+ EU_FINES_2026,
20
+ ACTION_CONTROL_MAP,
21
+ )
22
+
23
+ __all__ = [
24
+ "TelemetryStream",
25
+ "RiskPulse",
26
+ "ComplianceMetadata",
27
+ "SafetyOverheadEvent",
28
+ "LogicDriftDetector",
29
+ "EUAIActStatus",
30
+ "PulseSeverity",
31
+ "get_global_stream",
32
+ "ValueTracker",
33
+ "InterceptRecord",
34
+ "EnforcementTier",
35
+ "EU_FINES_2026",
36
+ "ACTION_CONTROL_MAP",
37
+ ]
@@ -0,0 +1,475 @@
1
+ """
2
+ cairo_sdk/analytics.py — CAIRO SDK: CairoAnalyticsLogger
3
+
4
+ DynamoDB-backed audit log for the CairoAnalytics table. Every ArmorMiddleware
5
+ run writes one structured event containing:
6
+
7
+ event_type "agent_response" | "compliance_event" | "armor_run"
8
+ correlation_id from AgentRequest
9
+ compliance_label EU AI Act Art. 50 declaration
10
+ pillar_results per-pillar pass/fail summary
11
+ timestamp_utc ISO-8601
12
+ regulatory_tags AXON Compliance Engine — NIST / ISO 42001 / EU AI Act controls
13
+ risk_level "Low" | "Medium" | "High"
14
+ risk_mitigation_value representative per-incident "Saved Fine" in USD
15
+
16
+ Non-blocking write path
17
+ ───────────────────────
18
+ log_request() is the production entry point for all middleware instrumentation.
19
+ After resolving regulatory tags (CPU-only, synchronous, < 1 ms), it submits the
20
+ DynamoDB put_item call to a module-level ThreadPoolExecutor so the operation is
21
+ fully fire-and-forget. The caller's async event loop is never blocked by network
22
+ I/O — CAIRO Shield adds zero latency to the user's AI experience.
23
+
24
+ Error handling
25
+ ──────────────
26
+ All DynamoDB calls catch specific botocore exceptions (ClientError, BotoCoreError)
27
+ and log warnings via the standard Python logging system rather than silently
28
+ swallowing errors or surfacing them to the caller. Analytics failure is never
29
+ allowed to affect the primary agent response path.
30
+
31
+ Public API
32
+ ──────────
33
+ log_event(event_type, data)
34
+ Low-level synchronous writer — stores exactly what you pass in.
35
+ Backward-compatible; mocked in tests via unittest.mock.MagicMock.
36
+
37
+ log_request(event_type, data, pillar, action_type)
38
+ High-level AXON entry point — non-blocking.
39
+ Resolves regulatory tags, enriches `data`, then submits the DynamoDB
40
+ write to the background thread pool.
41
+
42
+ scan_events(limit)
43
+ Read path — full table scan for the AXON dashboard.
44
+
45
+ In production, set CAIRO_DYNAMO_TABLE and CAIRO_DYNAMO_REGION env vars.
46
+ In tests, inject a unittest.mock.MagicMock in place of this class.
47
+
48
+ CAIRO Protocol Technical Architecture v1.0.
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ import atexit
54
+ import concurrent.futures
55
+ import datetime
56
+ import json
57
+ import logging
58
+ import uuid
59
+ from typing import Any
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Module-level infrastructure
63
+ # ---------------------------------------------------------------------------
64
+
65
+ _logger: logging.Logger = logging.getLogger(__name__)
66
+
67
+ # Thread pool for fire-and-forget DynamoDB writes.
68
+ # max_workers=4 handles burst writes without overwhelming DynamoDB capacity.
69
+ # The pool is shared across ALL CairoAnalyticsLogger instances (process-wide).
70
+ _EXECUTOR: concurrent.futures.ThreadPoolExecutor = (
71
+ concurrent.futures.ThreadPoolExecutor(
72
+ max_workers=4,
73
+ thread_name_prefix="cairo-analytics-writer",
74
+ )
75
+ )
76
+ # Graceful shutdown: cancel queued futures, do NOT wait for in-flight ones.
77
+ atexit.register(_EXECUTOR.shutdown, wait=False)
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # botocore exception guard
81
+ # ---------------------------------------------------------------------------
82
+ # Try to import the specific exception hierarchy so callers receive informative
83
+ # warnings. If botocore is not installed we fall back to catching Exception,
84
+ # which is equivalent behaviour but loses the structured log context.
85
+
86
+ try:
87
+ from botocore.exceptions import BotoCoreError, ClientError as _BotoClientError # type: ignore[import-untyped]
88
+ _BOTOCORE_ERRORS: tuple[type[Exception], ...] = (BotoCoreError, _BotoClientError)
89
+ except ImportError:
90
+ _BOTOCORE_ERRORS = (Exception,)
91
+
92
+
93
+ # ===========================================================================
94
+ # CairoAnalyticsLogger
95
+ # ===========================================================================
96
+
97
+ class CairoAnalyticsLogger:
98
+ """
99
+ Writes compliance events to the CairoAnalytics DynamoDB table and reads
100
+ them back for the AXON Executive Dashboard.
101
+
102
+ Interface is intentionally minimal so the class can be swapped with a
103
+ ``unittest.mock.MagicMock`` in test environments without any additional
104
+ dependencies or AWS access.
105
+
106
+ Write path — ``log_event`` (sync, low-level) and ``log_request``
107
+ (async/fire-and-forget, high-level with regulatory tags).
108
+ Read path — ``scan_events`` (full table scan, dashboard use only).
109
+
110
+ Parameters
111
+ ----------
112
+ table_name : str
113
+ DynamoDB table name. Defaults to ``"CairoAnalytics"`` — must match
114
+ the table created by ``scripts/deploy_aws.sh``.
115
+ region : str
116
+ AWS region where the table lives. Defaults to ``"us-east-1"``.
117
+ endpoint_url : str or None
118
+ Override DynamoDB endpoint for local testing (e.g.
119
+ ``"http://localhost:8000"``). Leave ``None`` in production.
120
+
121
+ Environment Variables
122
+ ---------------------
123
+ CAIRO_DYNAMO_TABLE Table name override.
124
+ CAIRO_DYNAMO_REGION Region override.
125
+ CAIRO_DYNAMO_ENDPOINT_URL Local endpoint override.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ table_name: str = "CairoAnalytics",
131
+ region: str = "us-east-1",
132
+ endpoint_url: str | None = None,
133
+ ) -> None:
134
+ self.table_name = table_name
135
+ self.region = region
136
+ self.endpoint_url = endpoint_url or None
137
+ self._table: Any = None # lazy-initialised on first use
138
+
139
+ # ------------------------------------------------------------------
140
+ # Internal: DynamoDB resource initialisation
141
+ # ------------------------------------------------------------------
142
+
143
+ def _get_table(self) -> Any:
144
+ """
145
+ Return a boto3 DynamoDB Table resource, initialising the connection
146
+ lazily on first call.
147
+
148
+ Raises
149
+ ------
150
+ RuntimeError
151
+ If ``boto3`` is not installed.
152
+ BotoCoreError / ClientError
153
+ If the AWS connection or table lookup fails. Callers that need
154
+ resilience should catch ``_BOTOCORE_ERRORS``.
155
+ """
156
+ if self._table is None:
157
+ try:
158
+ import boto3 # type: ignore[import-untyped]
159
+
160
+ kwargs: dict[str, Any] = {"region_name": self.region}
161
+ if self.endpoint_url:
162
+ kwargs["endpoint_url"] = self.endpoint_url
163
+ dynamodb = boto3.resource("dynamodb", **kwargs)
164
+ self._table = dynamodb.Table(self.table_name)
165
+ except ImportError:
166
+ raise RuntimeError(
167
+ "boto3 is required for DynamoDB logging. "
168
+ "Install it with: pip install boto3"
169
+ )
170
+ except _BOTOCORE_ERRORS as exc:
171
+ _logger.error(
172
+ "Failed to initialise DynamoDB resource "
173
+ "[table=%s region=%s]: %s: %s",
174
+ self.table_name, self.region,
175
+ type(exc).__name__, exc,
176
+ )
177
+ raise
178
+ return self._table
179
+
180
+ # ------------------------------------------------------------------
181
+ # Low-level writer (synchronous — kept for test-mock compatibility)
182
+ # ------------------------------------------------------------------
183
+
184
+ def log_event(
185
+ self,
186
+ event_type: str,
187
+ data: dict[str, Any],
188
+ ) -> None:
189
+ """
190
+ Write one compliance event to the CairoAnalytics DynamoDB table.
191
+
192
+ This is the low-level synchronous writer. It is kept synchronous so
193
+ that ``unittest.mock.MagicMock`` replacements in test suites can
194
+ directly assert ``log_event.call_args`` without threading concerns.
195
+
196
+ For production middleware instrumentation, prefer ``log_request()``
197
+ which is non-blocking and automatically attaches regulatory tags.
198
+
199
+ Parameters
200
+ ----------
201
+ event_type : str
202
+ ``"agent_response"`` | ``"compliance_event"`` | ``"armor_run"``.
203
+ data : dict
204
+ Arbitrary payload — serialised to a JSON string for DynamoDB
205
+ storage. The dict is not mutated.
206
+
207
+ Error Handling
208
+ --------------
209
+ botocore ``ClientError`` / ``BotoCoreError`` are caught and logged as
210
+ WARNING messages so a DynamoDB outage never surfaces to the caller.
211
+ Any other unexpected exception is logged at WARNING level and swallowed.
212
+ """
213
+ item: dict[str, Any] = {
214
+ "event_id": str(uuid.uuid4()),
215
+ "event_type": event_type,
216
+ "timestamp_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(
217
+ timespec="milliseconds"
218
+ ),
219
+ "data": json.dumps(data, default=str),
220
+ }
221
+ try:
222
+ table = self._get_table()
223
+ table.put_item(Item=item)
224
+ except _BOTOCORE_ERRORS as exc:
225
+ _logger.warning(
226
+ "DynamoDB put_item failed [event_type=%s table=%s]: %s: %s",
227
+ event_type, self.table_name, type(exc).__name__, exc,
228
+ )
229
+ except Exception as exc: # noqa: BLE001
230
+ _logger.warning(
231
+ "Unexpected error in DynamoDB write [event_type=%s]: %s",
232
+ event_type, exc,
233
+ )
234
+
235
+ # ------------------------------------------------------------------
236
+ # Internal: thread-pool target (runs in background thread)
237
+ # ------------------------------------------------------------------
238
+
239
+ def _write_item(self, event_type: str, data: dict[str, Any]) -> None:
240
+ """
241
+ Internal method executed in the background thread pool.
242
+
243
+ Calls ``log_event()`` and suppresses all exceptions so a thread crash
244
+ never propagates to the ``ThreadPoolExecutor`` error log.
245
+
246
+ This method is not part of the public API; it exists solely as the
247
+ callable submitted to ``_EXECUTOR``.
248
+ """
249
+ try:
250
+ self.log_event(event_type, data)
251
+ except Exception as exc: # noqa: BLE001
252
+ _logger.warning("Background DynamoDB write raised: %s", exc)
253
+
254
+ # ------------------------------------------------------------------
255
+ # High-level AXON entry point (non-blocking, fire-and-forget)
256
+ # ------------------------------------------------------------------
257
+
258
+ def log_request(
259
+ self,
260
+ event_type: str,
261
+ data: dict[str, Any],
262
+ pillar: str | None = None,
263
+ action_type: str | None = None,
264
+ ) -> None:
265
+ """
266
+ AXON Compliance Engine entry point — **non-blocking**.
267
+
268
+ Resolves the full set of regulatory tags for the given ``pillar`` /
269
+ ``action_type`` from ``governance_map.py``, merges them into a copy of
270
+ ``data``, then submits the DynamoDB ``put_item`` call to the module-level
271
+ ``ThreadPoolExecutor`` and returns immediately.
272
+
273
+ The caller's thread (and any async event loop) is never blocked by
274
+ network I/O. CAIRO Shield adds zero latency to the user's AI response.
275
+
276
+ Enrichment
277
+ ──────────
278
+ Every item written by this method contains three additional top-level
279
+ DynamoDB attributes that make the CairoAnalytics table directly
280
+ queryable for compliance reporting:
281
+
282
+ ``regulatory_tags``
283
+ Full NIST AI RMF / ISO 42001 / EU AI Act control cross-walk dict.
284
+ ``risk_level``
285
+ ``"Low"`` | ``"Medium"`` | ``"High"``
286
+ ``risk_mitigation_value``
287
+ ``{"amount_usd": float, "currency": "USD", "description": str}``
288
+
289
+ Parameters
290
+ ----------
291
+ event_type : str
292
+ ``"agent_response"`` | ``"compliance_event"`` | ``"armor_run"``.
293
+ data : dict
294
+ Payload dict. **Not mutated** — a copy is enriched before writing.
295
+ pillar : str or None
296
+ CAIRO pillar name: ``"containment"`` | ``"attestation"`` |
297
+ ``"interception"`` | ``"resilience"`` | ``"output_logic"``.
298
+ Used when ``action_type`` is not known or a pillar-level override
299
+ is needed.
300
+ action_type : str or None
301
+ Action key from ``ACTION_RISK_MAP`` (e.g. ``"prompt_injection_blocked"``,
302
+ ``"pii_blocked"``, ``"transparency_banner_added"``). When supplied,
303
+ the most granular action-level controls are used.
304
+
305
+ Resolution Precedence
306
+ ─────────────────────
307
+ ``action_type`` (most granular) → ``pillar`` (defaults) → containment fallback.
308
+
309
+ Thread Safety
310
+ ─────────────
311
+ The method is thread-safe: each call builds its own independent ``enriched``
312
+ dict before submitting to the shared executor. The ``data`` argument is
313
+ never shared between threads.
314
+
315
+ Notes
316
+ ─────
317
+ The regulatory-tag resolution (CPU-only, < 1 ms) runs synchronously on
318
+ the caller's thread so any ``ValueError`` from invalid pillar/action names
319
+ surfaces immediately. Only the DynamoDB I/O is deferred.
320
+ """
321
+ # Deferred import prevents circular dependency at module load time.
322
+ from cairo_sdk.governance_map import resolve_regulatory_tags # noqa: PLC0415
323
+
324
+ # Regulatory tag resolution is CPU-only: stays on caller's thread.
325
+ tags = resolve_regulatory_tags(pillar=pillar, action_type=action_type)
326
+
327
+ enriched: dict[str, Any] = dict(data)
328
+ enriched["regulatory_tags"] = tags.model_dump()
329
+ enriched["risk_level"] = tags.risk_level
330
+ enriched["risk_mitigation_value"] = {
331
+ "amount_usd": tags.risk_mitigation_value_usd,
332
+ "currency": "USD",
333
+ "description": (
334
+ f"Theoretical per-incident 'Saved Fine' for "
335
+ f"{action_type or pillar or 'unknown'} — represents mitigated "
336
+ f"regulatory risk. Risk level: {tags.risk_level}."
337
+ ),
338
+ }
339
+
340
+ # Fire-and-forget: DynamoDB I/O runs in background thread.
341
+ _EXECUTOR.submit(self._write_item, event_type, enriched)
342
+
343
+ # ------------------------------------------------------------------
344
+ # Internal: item normaliser (static — shared by scan_events and tests)
345
+ # ------------------------------------------------------------------
346
+
347
+ @staticmethod
348
+ def _normalize_item(item: dict[str, Any]) -> dict[str, Any] | None:
349
+ """
350
+ Flatten one raw DynamoDB item into a dashboard-ready dict.
351
+
352
+ The DynamoDB item has the shape written by ``log_event`` / ``log_request``:
353
+ ``{ event_id, event_type, timestamp_utc, data: "<JSON string>" }``
354
+
355
+ The returned dict exposes every nested field at the top level so the
356
+ dashboard and report generator can work with plain pandas DataFrames
357
+ without any further transformation.
358
+
359
+ Parameters
360
+ ----------
361
+ item : dict
362
+ Raw DynamoDB item — ``"data"`` may be a JSON string or an already-
363
+ decoded dict (both are handled transparently).
364
+
365
+ Returns
366
+ -------
367
+ dict or None
368
+ Flat normalised record, or ``None`` if parsing fails. Callers
369
+ silently skip ``None`` entries.
370
+
371
+ Returned Keys
372
+ -------------
373
+ ``event_id``, ``event_type``, ``timestamp_utc``, ``pillar``,
374
+ ``action_type``, ``risk_level``, ``risk_mitigation_value_usd``,
375
+ ``nist_categories``, ``iso_annex_controls``, ``eu_articles``,
376
+ ``eu_enforcement_tier``, ``correlation_id``.
377
+ """
378
+ try:
379
+ raw_data = item.get("data", "{}")
380
+ data: dict[str, Any] = (
381
+ json.loads(raw_data) if isinstance(raw_data, str) else raw_data
382
+ )
383
+ tags: dict[str, Any] = data.get("regulatory_tags", {})
384
+ rmv: dict[str, Any] = data.get("risk_mitigation_value", {})
385
+
386
+ return {
387
+ "event_id": item.get("event_id", ""),
388
+ "event_type": item.get("event_type", "compliance_event"),
389
+ "timestamp_utc": item.get("timestamp_utc", ""),
390
+ "pillar": tags.get("pillar") or data.get("pillar", "unknown"),
391
+ "action_type": (
392
+ tags.get("action_type") or data.get("action_type", "unknown")
393
+ ),
394
+ "risk_level": (
395
+ tags.get("risk_level") or data.get("risk_level", "Medium")
396
+ ),
397
+ "risk_mitigation_value_usd": float(
398
+ rmv.get("amount_usd")
399
+ or tags.get("risk_mitigation_value_usd", 0.0)
400
+ or 0.0
401
+ ),
402
+ "nist_categories": tags.get("nist_categories", []),
403
+ "iso_annex_controls": tags.get("iso_annex_controls", []),
404
+ "eu_articles": tags.get("eu_articles", []),
405
+ "eu_enforcement_tier": tags.get("eu_enforcement_tier", ""),
406
+ "correlation_id": data.get("correlation_id"),
407
+ }
408
+ except Exception: # noqa: BLE001
409
+ return None
410
+
411
+ # ------------------------------------------------------------------
412
+ # Read path — dashboard data fetching
413
+ # ------------------------------------------------------------------
414
+
415
+ def scan_events(self, limit: int = 500) -> list[dict[str, Any]]:
416
+ """
417
+ Scan the CairoAnalytics DynamoDB table and return normalised records.
418
+
419
+ Intended for the AXON Executive Dashboard, which calls this method at
420
+ most every 60 seconds (``@st.cache_data(ttl=60)``).
421
+
422
+ Parameters
423
+ ----------
424
+ limit : int
425
+ Maximum number of records to return after sorting. Pagination
426
+ exhausts all DynamoDB pages first, then trims to ``limit``.
427
+
428
+ Returns
429
+ -------
430
+ list[dict]
431
+ Normalised records (output of ``_normalize_item``), sorted by
432
+ ``timestamp_utc`` descending (most-recent first).
433
+ Returns ``[]`` if DynamoDB is unavailable — the dashboard then
434
+ falls back to demo data automatically.
435
+
436
+ Notes
437
+ ─────
438
+ Uses a full table scan (acceptable for compliance audit tables with
439
+ < 100 k items). For larger deployments, add a GSI on ``timestamp_utc``
440
+ and switch to a ``query`` call for O(log n) reads.
441
+
442
+ Error Handling
443
+ ──────────────
444
+ ``BotoCoreError`` / ``ClientError`` are caught and logged as WARNING.
445
+ Any other exception is also caught and logged. The method **never
446
+ raises** — a connectivity blip must not crash the dashboard.
447
+ """
448
+ results: list[dict[str, Any]] = []
449
+ try:
450
+ table = self._get_table()
451
+ scan_kwargs: dict[str, Any] = {}
452
+
453
+ while True:
454
+ response = table.scan(**scan_kwargs)
455
+ for raw_item in response.get("Items", []):
456
+ norm = self._normalize_item(raw_item)
457
+ if norm:
458
+ results.append(norm)
459
+
460
+ if "LastEvaluatedKey" not in response:
461
+ break
462
+ scan_kwargs["ExclusiveStartKey"] = response["LastEvaluatedKey"]
463
+
464
+ results.sort(key=lambda x: x.get("timestamp_utc", ""), reverse=True)
465
+ return results[:limit]
466
+
467
+ except _BOTOCORE_ERRORS as exc:
468
+ _logger.warning(
469
+ "DynamoDB scan failed [table=%s]: %s: %s",
470
+ self.table_name, type(exc).__name__, exc,
471
+ )
472
+ return []
473
+ except Exception as exc: # noqa: BLE001
474
+ _logger.warning("Unexpected error in scan_events: %s", exc)
475
+ return []
@@ -0,0 +1 @@
1
+ # CAIRO SDK — Attestation pillar (compliance proof, audit)