spanforge 1.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 (174) hide show
  1. spanforge/__init__.py +815 -0
  2. spanforge/_ansi.py +93 -0
  3. spanforge/_batch_exporter.py +409 -0
  4. spanforge/_cli.py +2094 -0
  5. spanforge/_cli_audit.py +639 -0
  6. spanforge/_cli_compliance.py +711 -0
  7. spanforge/_cli_cost.py +243 -0
  8. spanforge/_cli_ops.py +791 -0
  9. spanforge/_cli_phase11.py +356 -0
  10. spanforge/_hooks.py +337 -0
  11. spanforge/_server.py +1708 -0
  12. spanforge/_span.py +1036 -0
  13. spanforge/_store.py +288 -0
  14. spanforge/_stream.py +664 -0
  15. spanforge/_trace.py +335 -0
  16. spanforge/_tracer.py +254 -0
  17. spanforge/actor.py +141 -0
  18. spanforge/alerts.py +469 -0
  19. spanforge/auto.py +464 -0
  20. spanforge/baseline.py +335 -0
  21. spanforge/cache.py +635 -0
  22. spanforge/compliance.py +325 -0
  23. spanforge/config.py +532 -0
  24. spanforge/consent.py +228 -0
  25. spanforge/consumer.py +377 -0
  26. spanforge/core/__init__.py +5 -0
  27. spanforge/core/compliance_mapping.py +1254 -0
  28. spanforge/cost.py +600 -0
  29. spanforge/debug.py +548 -0
  30. spanforge/deprecations.py +205 -0
  31. spanforge/drift.py +482 -0
  32. spanforge/egress.py +58 -0
  33. spanforge/eval.py +648 -0
  34. spanforge/event.py +1064 -0
  35. spanforge/exceptions.py +240 -0
  36. spanforge/explain.py +178 -0
  37. spanforge/export/__init__.py +69 -0
  38. spanforge/export/append_only.py +337 -0
  39. spanforge/export/cloud.py +357 -0
  40. spanforge/export/datadog.py +497 -0
  41. spanforge/export/grafana.py +320 -0
  42. spanforge/export/jsonl.py +195 -0
  43. spanforge/export/openinference.py +158 -0
  44. spanforge/export/otel_bridge.py +294 -0
  45. spanforge/export/otlp.py +811 -0
  46. spanforge/export/otlp_bridge.py +233 -0
  47. spanforge/export/redis_backend.py +282 -0
  48. spanforge/export/siem_schema.py +98 -0
  49. spanforge/export/siem_splunk.py +264 -0
  50. spanforge/export/siem_syslog.py +212 -0
  51. spanforge/export/webhook.py +299 -0
  52. spanforge/exporters/__init__.py +30 -0
  53. spanforge/exporters/console.py +271 -0
  54. spanforge/exporters/jsonl.py +144 -0
  55. spanforge/exporters/sqlite.py +142 -0
  56. spanforge/gate.py +1150 -0
  57. spanforge/governance.py +181 -0
  58. spanforge/hitl.py +295 -0
  59. spanforge/http.py +187 -0
  60. spanforge/inspect.py +427 -0
  61. spanforge/integrations/__init__.py +45 -0
  62. spanforge/integrations/_pricing.py +280 -0
  63. spanforge/integrations/anthropic.py +388 -0
  64. spanforge/integrations/azure_openai.py +133 -0
  65. spanforge/integrations/bedrock.py +292 -0
  66. spanforge/integrations/crewai.py +251 -0
  67. spanforge/integrations/gemini.py +351 -0
  68. spanforge/integrations/groq.py +442 -0
  69. spanforge/integrations/langchain.py +349 -0
  70. spanforge/integrations/langgraph.py +306 -0
  71. spanforge/integrations/llamaindex.py +373 -0
  72. spanforge/integrations/ollama.py +287 -0
  73. spanforge/integrations/openai.py +368 -0
  74. spanforge/integrations/together.py +483 -0
  75. spanforge/io.py +214 -0
  76. spanforge/lint.py +322 -0
  77. spanforge/metrics.py +417 -0
  78. spanforge/metrics_export.py +343 -0
  79. spanforge/migrate.py +402 -0
  80. spanforge/model_registry.py +278 -0
  81. spanforge/models.py +389 -0
  82. spanforge/namespaces/__init__.py +254 -0
  83. spanforge/namespaces/audit.py +256 -0
  84. spanforge/namespaces/cache.py +237 -0
  85. spanforge/namespaces/chain.py +77 -0
  86. spanforge/namespaces/confidence.py +72 -0
  87. spanforge/namespaces/consent.py +92 -0
  88. spanforge/namespaces/cost.py +179 -0
  89. spanforge/namespaces/decision.py +143 -0
  90. spanforge/namespaces/diff.py +157 -0
  91. spanforge/namespaces/drift.py +80 -0
  92. spanforge/namespaces/eval_.py +251 -0
  93. spanforge/namespaces/feedback.py +241 -0
  94. spanforge/namespaces/fence.py +193 -0
  95. spanforge/namespaces/guard.py +105 -0
  96. spanforge/namespaces/hitl.py +91 -0
  97. spanforge/namespaces/latency.py +72 -0
  98. spanforge/namespaces/prompt.py +190 -0
  99. spanforge/namespaces/redact.py +173 -0
  100. spanforge/namespaces/retrieval.py +379 -0
  101. spanforge/namespaces/runtime_governance.py +494 -0
  102. spanforge/namespaces/template.py +208 -0
  103. spanforge/namespaces/tool_call.py +77 -0
  104. spanforge/namespaces/trace.py +1029 -0
  105. spanforge/normalizer.py +171 -0
  106. spanforge/plugins.py +82 -0
  107. spanforge/presidio_backend.py +349 -0
  108. spanforge/processor.py +258 -0
  109. spanforge/prompt_registry.py +418 -0
  110. spanforge/py.typed +0 -0
  111. spanforge/redact.py +914 -0
  112. spanforge/regression.py +192 -0
  113. spanforge/runtime_policy.py +159 -0
  114. spanforge/sampling.py +511 -0
  115. spanforge/schema.py +183 -0
  116. spanforge/schemas/v1.0/schema.json +170 -0
  117. spanforge/schemas/v2.0/schema.json +536 -0
  118. spanforge/sdk/__init__.py +625 -0
  119. spanforge/sdk/_base.py +584 -0
  120. spanforge/sdk/_base.pyi +71 -0
  121. spanforge/sdk/_exceptions.py +1096 -0
  122. spanforge/sdk/_types.py +2184 -0
  123. spanforge/sdk/alert.py +1514 -0
  124. spanforge/sdk/alert.pyi +56 -0
  125. spanforge/sdk/audit.py +1196 -0
  126. spanforge/sdk/audit.pyi +67 -0
  127. spanforge/sdk/cec.py +1215 -0
  128. spanforge/sdk/cec.pyi +37 -0
  129. spanforge/sdk/config.py +641 -0
  130. spanforge/sdk/config.pyi +55 -0
  131. spanforge/sdk/enterprise.py +714 -0
  132. spanforge/sdk/enterprise.pyi +79 -0
  133. spanforge/sdk/explain.py +170 -0
  134. spanforge/sdk/fallback.py +432 -0
  135. spanforge/sdk/feedback.py +351 -0
  136. spanforge/sdk/gate.py +874 -0
  137. spanforge/sdk/gate.pyi +51 -0
  138. spanforge/sdk/identity.py +2114 -0
  139. spanforge/sdk/identity.pyi +47 -0
  140. spanforge/sdk/lineage.py +175 -0
  141. spanforge/sdk/observe.py +1065 -0
  142. spanforge/sdk/observe.pyi +50 -0
  143. spanforge/sdk/operator.py +338 -0
  144. spanforge/sdk/pii.py +1473 -0
  145. spanforge/sdk/pii.pyi +119 -0
  146. spanforge/sdk/pipelines.py +458 -0
  147. spanforge/sdk/pipelines.pyi +39 -0
  148. spanforge/sdk/policy.py +930 -0
  149. spanforge/sdk/rag.py +594 -0
  150. spanforge/sdk/rbac.py +280 -0
  151. spanforge/sdk/registry.py +430 -0
  152. spanforge/sdk/registry.pyi +46 -0
  153. spanforge/sdk/scope.py +279 -0
  154. spanforge/sdk/secrets.py +293 -0
  155. spanforge/sdk/secrets.pyi +25 -0
  156. spanforge/sdk/security.py +560 -0
  157. spanforge/sdk/security.pyi +57 -0
  158. spanforge/sdk/trust.py +472 -0
  159. spanforge/sdk/trust.pyi +41 -0
  160. spanforge/secrets.py +799 -0
  161. spanforge/signing.py +1179 -0
  162. spanforge/stats.py +100 -0
  163. spanforge/stream.py +560 -0
  164. spanforge/testing.py +378 -0
  165. spanforge/testing_mocks.py +1052 -0
  166. spanforge/trace.py +199 -0
  167. spanforge/types.py +696 -0
  168. spanforge/ulid.py +300 -0
  169. spanforge/validate.py +379 -0
  170. spanforge-1.0.0.dist-info/METADATA +1509 -0
  171. spanforge-1.0.0.dist-info/RECORD +174 -0
  172. spanforge-1.0.0.dist-info/WHEEL +4 -0
  173. spanforge-1.0.0.dist-info/entry_points.txt +5 -0
  174. spanforge-1.0.0.dist-info/licenses/LICENSE +128 -0
@@ -0,0 +1,240 @@
1
+ """Typed exception hierarchy for spanforge.
2
+
3
+ All exceptions raised by spanforge inherit from :class:`LLMSchemaError` so
4
+ callers can catch the whole family with a single ``except LLMSchemaError``.
5
+
6
+ Design rules
7
+ ------------
8
+ * Exceptions carry enough context to be actionable — field name, received value,
9
+ and an explanation of what was expected.
10
+ * HMAC keys and PII-tagged content are **never** embedded in exception messages
11
+ or ``__cause__`` chains.
12
+ * No bare ``raise`` — every raise site uses a typed subclass.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ __all__ = [
18
+ "AuditStorageError",
19
+ "DeserializationError",
20
+ "EgressViolationError",
21
+ "EventTypeError",
22
+ "ExportError",
23
+ "LLMSchemaError",
24
+ "SchemaValidationError",
25
+ "SchemaVersionError",
26
+ "SerializationError",
27
+ "SigningError",
28
+ "ULIDError",
29
+ "VerificationError",
30
+ ]
31
+
32
+
33
+ class LLMSchemaError(Exception):
34
+ """Base class for all spanforge exceptions.
35
+
36
+ All public-facing exceptions derive from this class, enabling callers to
37
+ write a single broad ``except LLMSchemaError`` guard as a safety net while
38
+ still being able to catch specific sub-types for targeted handling.
39
+ """
40
+
41
+
42
+ class SchemaValidationError(LLMSchemaError):
43
+ """Raised when an :class:`~spanforge.event.Event` fails validation.
44
+
45
+ Attributes:
46
+ field: The dotted field path that failed (e.g. ``"event_id"``).
47
+ received: The actual value that was provided (redacted if sensitive).
48
+ reason: Human-readable explanation of the constraint that was violated.
49
+
50
+ Example::
51
+
52
+ try:
53
+ event.validate()
54
+ except SchemaValidationError as exc:
55
+ logger.error("Invalid event field=%s reason=%s", exc.field, exc.reason)
56
+ """
57
+
58
+ def __init__(self, field: str, received: object, reason: str) -> None:
59
+ self.field = field
60
+ self.received = received
61
+ self.reason = reason
62
+ super().__init__(
63
+ f"Validation failed for field '{field}': {reason} "
64
+ f"(received type={type(received).__name__!r})"
65
+ )
66
+
67
+
68
+ class ULIDError(LLMSchemaError):
69
+ """Raised when ULID generation or parsing fails.
70
+
71
+ Attributes:
72
+ detail: Human-readable description of the failure.
73
+
74
+ This exception is intentionally opaque about internal state to avoid
75
+ leaking timing information that could aid side-channel attacks.
76
+ """
77
+
78
+ def __init__(self, detail: str) -> None:
79
+ self.detail = detail
80
+ super().__init__(f"ULID error: {detail}")
81
+
82
+
83
+ class SerializationError(LLMSchemaError):
84
+ """Raised when an :class:`~spanforge.event.Event` cannot be serialized.
85
+
86
+ Attributes:
87
+ event_id: The ULID of the event that failed (safe to log).
88
+ reason: Human-readable description of the failure.
89
+ """
90
+
91
+ def __init__(self, event_id: str, reason: str) -> None:
92
+ self.event_id = event_id
93
+ self.reason = reason
94
+ super().__init__(f"Serialization failed for event '{event_id}': {reason}")
95
+
96
+
97
+ class DeserializationError(LLMSchemaError):
98
+ """Raised when a JSON blob cannot be deserialized into an Event.
99
+
100
+ Attributes:
101
+ reason: Human-readable description of the failure.
102
+ source_hint: A short, non-PII hint about the source (e.g. filename).
103
+ """
104
+
105
+ def __init__(self, reason: str, source_hint: str = "<unknown>") -> None:
106
+ self.reason = reason
107
+ self.source_hint = source_hint
108
+ super().__init__(f"Deserialization failed (source={source_hint!r}): {reason}")
109
+
110
+
111
+ class EventTypeError(LLMSchemaError):
112
+ """Raised when an unknown or malformed event type string is encountered.
113
+
114
+ Attributes:
115
+ event_type: The offending event type string.
116
+ reason: Human-readable description of the failure.
117
+ """
118
+
119
+ def __init__(self, event_type: str, reason: str) -> None:
120
+ self.event_type = event_type
121
+ self.reason = reason
122
+ super().__init__(f"Invalid event type '{event_type}': {reason}")
123
+
124
+
125
+ class SigningError(LLMSchemaError):
126
+ """Raised when HMAC event signing fails.
127
+
128
+ Security: the ``org_secret`` value is **never** included in the message.
129
+
130
+ Attributes:
131
+ reason: Human-readable description of why signing failed.
132
+ """
133
+
134
+ def __init__(self, reason: str) -> None:
135
+ self.reason = reason
136
+ super().__init__(f"Signing failed: {reason}")
137
+
138
+
139
+ class VerificationError(LLMSchemaError):
140
+ """Raised when an event fails cryptographic verification.
141
+
142
+ Raised by :func:`~spanforge.signing.assert_verified` on verification failure.
143
+
144
+ Attributes:
145
+ event_id: The ULID of the event that failed (safe to log).
146
+ """
147
+
148
+ def __init__(self, event_id: str) -> None:
149
+ self.event_id = event_id
150
+ super().__init__(
151
+ f"Event '{event_id}' failed cryptographic verification. "
152
+ "The event may have been tampered with or the wrong key was used."
153
+ )
154
+
155
+
156
+ class ExportError(LLMSchemaError):
157
+ """Raised when exporting events to an external backend fails.
158
+
159
+ Attributes:
160
+ backend: Short identifier for the backend (e.g. ``"otlp"``,
161
+ ``"webhook"``, ``"jsonl"``).
162
+ reason: Human-readable description of the failure.
163
+ event_id: The ULID of the event that failed, or ``""`` for batch
164
+ failures where no single event is responsible.
165
+
166
+ Security: HMAC secrets and PII-tagged payloads are **never** embedded in
167
+ the message or ``__cause__``.
168
+
169
+ Example::
170
+
171
+ try:
172
+ await exporter.export(event)
173
+ except ExportError as exc:
174
+ logger.error("backend=%s reason=%s", exc.backend, exc.reason)
175
+ """
176
+
177
+ def __init__(self, backend: str, reason: str, event_id: str = "") -> None:
178
+ self.backend = backend
179
+ self.reason = reason
180
+ self.event_id = event_id
181
+ msg = f"Export to '{backend}' failed: {reason}"
182
+ if event_id:
183
+ msg += f" (event_id={event_id!r})"
184
+ super().__init__(msg)
185
+
186
+
187
+ class SchemaVersionError(LLMSchemaError):
188
+ """Raised when an event carries an unsupported ``schema_version`` value.
189
+
190
+ RFC-0001 §15.5 specifies that v2.0 consumers MUST accept events with
191
+ ``schema_version`` ``"1.0"`` or ``"2.0"`` and MUST raise this error
192
+ for any other value.
193
+
194
+ Attributes:
195
+ version: The unsupported schema version string that was encountered.
196
+
197
+ Example::
198
+
199
+ try:
200
+ consumer.ingest(event)
201
+ except SchemaVersionError as exc:
202
+ logger.warning("Skipping event with unknown version %s", exc.version)
203
+ """
204
+
205
+ def __init__(self, version: str) -> None:
206
+ self.version = version
207
+ super().__init__(
208
+ f"Unsupported schema_version {version!r}. "
209
+ "Accepted values: '1.0', '2.0' (RFC-0001 §15.5)."
210
+ )
211
+
212
+
213
+ class EgressViolationError(LLMSchemaError):
214
+ """Raised when an exporter attempts a network call in no-egress mode.
215
+
216
+ Attributes:
217
+ backend: Short identifier for the backend that violated the policy.
218
+ endpoint: The endpoint URL that was blocked (may be ``""`` if unknown).
219
+ """
220
+
221
+ def __init__(self, backend: str, endpoint: str = "") -> None:
222
+ self.backend = backend
223
+ self.endpoint = endpoint
224
+ msg = f"Egress violation: exporter '{backend}' attempted a network call"
225
+ if endpoint:
226
+ msg += f" to {endpoint!r}"
227
+ msg += " but no_egress mode is active"
228
+ super().__init__(msg)
229
+
230
+
231
+ class AuditStorageError(LLMSchemaError):
232
+ """Raised when the audit log storage layer detects a violation.
233
+
234
+ Attributes:
235
+ reason: Human-readable description of the storage violation.
236
+ """
237
+
238
+ def __init__(self, reason: str) -> None:
239
+ self.reason = reason
240
+ super().__init__(f"Audit storage error: {reason}")
spanforge/explain.py ADDED
@@ -0,0 +1,178 @@
1
+ """spanforge.explain — Explainability record generation for AI compliance.
2
+
3
+ Aggregates decision drivers from span payloads into human-readable
4
+ explanations compliant with EU AI Act transparency requirements
5
+ (Articles 13-14) and the T.R.U.S.T. Framework Transparency pillar.
6
+
7
+ Emits ``explanation.generated`` events into the HMAC audit chain.
8
+
9
+ Usage::
10
+
11
+ from spanforge.explain import ExplainabilityRecord
12
+
13
+ record = ExplainabilityRecord(
14
+ trace_id="01HQZF...",
15
+ agent_id="support-agent@1.0",
16
+ decision_id="01HQZF...",
17
+ factors=[
18
+ {"factor_name": "user_intent", "weight": 0.6,
19
+ "contribution": 0.42, "evidence": "keyword match",
20
+ "confidence": 0.85},
21
+ ],
22
+ summary="Routed to billing team based on keyword match.",
23
+ )
24
+ print(record.to_text())
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import contextlib
30
+ import json
31
+ from dataclasses import dataclass, field
32
+ from typing import Any
33
+
34
+ __all__ = [
35
+ "ExplainabilityRecord",
36
+ "generate_explanation",
37
+ ]
38
+
39
+
40
+ @dataclass
41
+ class ExplainabilityRecord:
42
+ """A human-readable explanation of one or more AI decisions.
43
+
44
+ Designed to satisfy EU AI Act Art. 13 transparency obligations.
45
+ Each record can be serialised to plain text, JSON, or dict for
46
+ audit trail inclusion.
47
+ """
48
+
49
+ trace_id: str
50
+ agent_id: str
51
+ decision_id: str
52
+ factors: list[dict[str, Any]]
53
+ summary: str
54
+ model_id: str | None = None
55
+ confidence: float | None = None
56
+ risk_tier: str | None = None
57
+ metadata: dict[str, Any] = field(default_factory=dict)
58
+
59
+ def __post_init__(self) -> None:
60
+ if not self.trace_id:
61
+ raise ValueError("ExplainabilityRecord.trace_id must be non-empty")
62
+ if not self.agent_id:
63
+ raise ValueError("ExplainabilityRecord.agent_id must be non-empty")
64
+ if not self.decision_id:
65
+ raise ValueError("ExplainabilityRecord.decision_id must be non-empty")
66
+ if not self.summary:
67
+ raise ValueError("ExplainabilityRecord.summary must be non-empty")
68
+ if self.confidence is not None and not (0.0 <= self.confidence <= 1.0):
69
+ raise ValueError("ExplainabilityRecord.confidence must be in [0.0, 1.0]")
70
+
71
+ def to_dict(self) -> dict[str, Any]:
72
+ """Serialise to a plain dict."""
73
+ d: dict[str, Any] = {
74
+ "trace_id": self.trace_id,
75
+ "agent_id": self.agent_id,
76
+ "decision_id": self.decision_id,
77
+ "factors": self.factors,
78
+ "summary": self.summary,
79
+ }
80
+ if self.model_id is not None:
81
+ d["model_id"] = self.model_id
82
+ if self.confidence is not None:
83
+ d["confidence"] = self.confidence
84
+ if self.risk_tier is not None:
85
+ d["risk_tier"] = self.risk_tier
86
+ if self.metadata:
87
+ d["metadata"] = self.metadata
88
+ return d
89
+
90
+ @classmethod
91
+ def from_dict(cls, data: dict[str, Any]) -> ExplainabilityRecord:
92
+ """Reconstruct from a plain dict."""
93
+ return cls(
94
+ trace_id=data["trace_id"],
95
+ agent_id=data["agent_id"],
96
+ decision_id=data["decision_id"],
97
+ factors=data.get("factors", []),
98
+ summary=data["summary"],
99
+ model_id=data.get("model_id"),
100
+ confidence=data.get("confidence"),
101
+ risk_tier=data.get("risk_tier"),
102
+ metadata=data.get("metadata", {}),
103
+ )
104
+
105
+ def to_json(self) -> str:
106
+ """Serialise to JSON string."""
107
+ return json.dumps(self.to_dict(), default=str)
108
+
109
+ def to_text(self) -> str:
110
+ """Generate a human-readable explanation text.
111
+
112
+ Returns a multi-line string suitable for end-user display or
113
+ compliance documentation.
114
+ """
115
+ lines: list[str] = []
116
+ lines.append(f"Explanation for decision {self.decision_id}")
117
+ lines.append(f" Agent: {self.agent_id}")
118
+ lines.append(f" Trace: {self.trace_id}")
119
+ if self.model_id:
120
+ lines.append(f" Model: {self.model_id}")
121
+ if self.confidence is not None:
122
+ lines.append(f" Confidence: {self.confidence:.2%}")
123
+ if self.risk_tier:
124
+ lines.append(f" Risk tier: {self.risk_tier}")
125
+ lines.append(f" Summary: {self.summary}")
126
+ if self.factors:
127
+ lines.append(" Contributing factors:")
128
+ for f in self.factors:
129
+ name = f.get("factor_name", "unknown")
130
+ weight = f.get("weight", 0)
131
+ evidence = f.get("evidence", "")
132
+ lines.append(f" - {name} (weight={weight:.2f}): {evidence}")
133
+ return "\n".join(lines)
134
+
135
+
136
+ def generate_explanation(
137
+ trace_id: str,
138
+ agent_id: str,
139
+ decision_id: str,
140
+ factors: list[dict[str, Any]],
141
+ summary: str,
142
+ *,
143
+ model_id: str | None = None,
144
+ confidence: float | None = None,
145
+ risk_tier: str | None = None,
146
+ auto_emit: bool = True,
147
+ metadata: dict[str, Any] | None = None,
148
+ ) -> ExplainabilityRecord:
149
+ """Create an :class:`ExplainabilityRecord` and optionally emit an event.
150
+
151
+ This is the primary convenience function for the explainability module.
152
+ """
153
+ record = ExplainabilityRecord(
154
+ trace_id=trace_id,
155
+ agent_id=agent_id,
156
+ decision_id=decision_id,
157
+ factors=factors,
158
+ summary=summary,
159
+ model_id=model_id,
160
+ confidence=confidence,
161
+ risk_tier=risk_tier,
162
+ metadata=metadata or {},
163
+ )
164
+ if auto_emit:
165
+ _emit_explanation(record)
166
+ return record
167
+
168
+
169
+ def _emit_explanation(record: ExplainabilityRecord) -> None:
170
+ """Emit an explanation.generated event into the HMAC audit chain."""
171
+ try:
172
+ from spanforge._stream import emit_rfc_event
173
+ from spanforge.types import EventType
174
+
175
+ with contextlib.suppress(Exception):
176
+ emit_rfc_event(EventType.EXPLANATION_GENERATED, record.to_dict())
177
+ except ImportError:
178
+ pass
@@ -0,0 +1,69 @@
1
+ """RFC-0001 export backends for spanforge SDK events.
2
+
3
+ All exporters are **opt-in** — importing this package does not open any network
4
+ connections or file handles. Instantiate an exporter explicitly to activate it.
5
+
6
+ Core exporters (RFC-0001 §14)
7
+ ------------------------------
8
+ * :class:`~spanforge.export.otlp.OTLPExporter` — OTLP/JSON HTTP exporter
9
+ (zero dependencies; builds OTLP wire format from stdlib).
10
+ * :class:`~spanforge.export.otel_bridge.OTelBridgeExporter` — OTel SDK bridge
11
+ that emits real OTel spans via a configured ``TracerProvider``.
12
+ Requires ``pip install "spanforge[otel]"``.
13
+ * :class:`~spanforge.export.webhook.WebhookExporter` — HTTP webhook with
14
+ HMAC-SHA256 request signing.
15
+ * :class:`~spanforge.export.jsonl.JSONLExporter` — NDJSON for local development
16
+ and audit trail persistence.
17
+
18
+ SIEM exporters
19
+ --------------
20
+ * :class:`~spanforge.export.siem_splunk.SplunkHECExporter` — Splunk HTTP Event
21
+ Collector (HEC) exporter.
22
+ * :class:`~spanforge.export.siem_syslog.SyslogExporter` — Syslog / CEF exporter
23
+ for QRadar, Sentinel, Chronicle, and other syslog-compatible SIEMs.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from spanforge.export.append_only import (
29
+ AppendOnlyJSONLExporter,
30
+ WORMBackend,
31
+ WORMUploadResult,
32
+ )
33
+ from spanforge.export.datadog import DatadogExporter, DatadogResourceAttributes
34
+ from spanforge.export.grafana import GrafanaLokiExporter
35
+ from spanforge.export.jsonl import JSONLExporter
36
+ from spanforge.export.openinference import OpenInferenceSpanBridge, span_to_openinference_dict
37
+ from spanforge.export.otlp import OTLPExporter, ResourceAttributes
38
+ from spanforge.export.siem_schema import event_to_siem_record, severity_from_event
39
+ from spanforge.export.siem_splunk import SplunkHECError, SplunkHECExporter
40
+ from spanforge.export.siem_syslog import SyslogExporter, SyslogExporterError
41
+ from spanforge.export.webhook import WebhookExporter
42
+
43
+ # OTelBridgeExporter is an optional import — requires opentelemetry-sdk
44
+ try:
45
+ from spanforge.export.otel_bridge import OTelBridgeExporter
46
+ except ImportError:
47
+ OTelBridgeExporter = None # type: ignore[assignment,misc]
48
+
49
+ __all__ = [
50
+ "AppendOnlyJSONLExporter",
51
+ "DatadogExporter",
52
+ "DatadogResourceAttributes",
53
+ "GrafanaLokiExporter",
54
+ "JSONLExporter",
55
+ "OTLPExporter",
56
+ "OTelBridgeExporter",
57
+ "OpenInferenceSpanBridge",
58
+ "ResourceAttributes",
59
+ "SplunkHECError",
60
+ "SplunkHECExporter",
61
+ "SyslogExporter",
62
+ "SyslogExporterError",
63
+ "WORMBackend",
64
+ "WORMUploadResult",
65
+ "WebhookExporter",
66
+ "event_to_siem_record",
67
+ "severity_from_event",
68
+ "span_to_openinference_dict",
69
+ ]