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,47 @@
1
+ """Type stubs for spanforge.sdk.identity (DX-001)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from spanforge.sdk._base import SFClientConfig, SFServiceClient
8
+ from spanforge.sdk._types import (
9
+ APIKeyBundle,
10
+ JWTClaims,
11
+ MagicLinkResult,
12
+ RateLimitInfo,
13
+ TokenIntrospectionResult,
14
+ TOTPEnrollResult,
15
+ )
16
+
17
+ class SFIdentityClient(SFServiceClient):
18
+ def __init__(self, config: SFClientConfig | None = None) -> None: ...
19
+ def refresh_token(self) -> str: ...
20
+ def issue_api_key(
21
+ self,
22
+ *,
23
+ scopes: list[str] | None = None,
24
+ project_id: str = "",
25
+ expires_in_days: int = 365,
26
+ ip_allowlist: list[str] | None = None,
27
+ test_mode: bool = False,
28
+ ) -> APIKeyBundle: ...
29
+ def issue_magic_link(self, email: str) -> MagicLinkResult: ...
30
+ def rotate_key(self, key_id: str) -> APIKeyBundle: ...
31
+ def revoke_key(self, key_id: str) -> None: ...
32
+ def create_session(self, api_key: str) -> str: ...
33
+ def verify_token(self, jwt: str) -> JWTClaims: ...
34
+ def introspect(self, token: str) -> TokenIntrospectionResult: ...
35
+ def enroll_totp(self, key_id: str) -> TOTPEnrollResult: ...
36
+ def verify_backup_code(self, key_id: str, code: str) -> bool: ...
37
+ def saml_metadata(self) -> str: ...
38
+ def check_rate_limit(self, key_id: str) -> RateLimitInfo: ...
39
+ def record_request(self, key_id: str) -> bool: ...
40
+ def check_ip_allowlist(self, key_id: str, ip: str) -> None: ...
41
+ def get_jwks(self) -> dict[str, Any]: ...
42
+ def require_scope(self, claims: JWTClaims, scope: str) -> None: ...
43
+ def set_mfa_policy(self, project_id: str, mfa_required: bool) -> None: ...
44
+ def get_mfa_policy(self, project_id: str) -> bool: ...
45
+ def set_key_tier(self, key_id: str, tier: str) -> None: ...
46
+ def consume_quota(self, key_id: str) -> bool: ...
47
+ def get_quota_usage(self, key_id: str) -> dict[str, Any]: ...
@@ -0,0 +1,175 @@
1
+ """spanforge.sdk.lineage - SpanForge sf-lineage client.
2
+
3
+ Phase 1 implementation for GA runtime provenance and decision lineage. The
4
+ client records canonical lineage payloads and emits signed evidence via
5
+ sf-audit.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import threading
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from spanforge.namespaces.runtime_governance import LineagePayload
15
+ from spanforge.sdk._base import SFClientConfig, SFServiceClient
16
+
17
+ __all__ = ["LineageStatusInfo", "SFLineageClient"]
18
+
19
+
20
+ @dataclass
21
+ class LineageStatusInfo:
22
+ """sf-lineage service status."""
23
+
24
+ status: str
25
+ total_recorded: int
26
+ traces_tracked: int
27
+
28
+
29
+ class SFLineageClient(SFServiceClient):
30
+ """SpanForge runtime lineage service client."""
31
+
32
+ def __init__(self, config: SFClientConfig) -> None:
33
+ super().__init__(config, service_name="lineage")
34
+ self._lock = threading.Lock()
35
+ self._records: dict[str, LineagePayload] = {}
36
+ self._by_trace: dict[str, list[str]] = {}
37
+ self._by_subject: dict[str, list[str]] = {}
38
+ self._total_recorded = 0
39
+
40
+ def record(
41
+ self,
42
+ *,
43
+ trace_id: str,
44
+ decision_id: str,
45
+ subject_type: str,
46
+ subject_id: str,
47
+ operation: str,
48
+ recorded_at: str,
49
+ lineage_id: str | None = None,
50
+ input_refs: list[str] | None = None,
51
+ output_refs: list[str] | None = None,
52
+ parent_lineage_ids: list[str] | None = None,
53
+ metadata: dict[str, Any] | None = None,
54
+ ) -> LineagePayload:
55
+ """Create and persist a canonical lineage record."""
56
+ from spanforge.ulid import generate as _ulid
57
+
58
+ payload = LineagePayload(
59
+ lineage_id=lineage_id or _ulid(),
60
+ trace_id=trace_id,
61
+ decision_id=decision_id,
62
+ subject_type=subject_type,
63
+ subject_id=subject_id,
64
+ operation=operation,
65
+ recorded_at=recorded_at,
66
+ input_refs=list(input_refs or []),
67
+ output_refs=list(output_refs or []),
68
+ parent_lineage_ids=list(parent_lineage_ids or []),
69
+ metadata=metadata or {},
70
+ )
71
+
72
+ subject_key = self._subject_key(subject_type, subject_id)
73
+ with self._lock:
74
+ self._records[payload.lineage_id] = payload
75
+ self._by_trace.setdefault(trace_id, []).append(payload.lineage_id)
76
+ self._by_subject.setdefault(subject_key, []).append(payload.lineage_id)
77
+ self._total_recorded += 1
78
+
79
+ self._emit_signed_record(payload)
80
+ return payload
81
+
82
+ def record_with_policy(
83
+ self,
84
+ *,
85
+ environment: str,
86
+ trace_id: str,
87
+ decision_id: str,
88
+ subject_type: str,
89
+ subject_id: str,
90
+ operation: str,
91
+ recorded_at: str,
92
+ policy_client: Any | None = None,
93
+ control: str = "lineage_capture",
94
+ lineage_id: str | None = None,
95
+ input_refs: list[str] | None = None,
96
+ output_refs: list[str] | None = None,
97
+ parent_lineage_ids: list[str] | None = None,
98
+ metadata: dict[str, Any] | None = None,
99
+ ) -> LineagePayload:
100
+ """Record lineage using the active runtime policy decision metadata."""
101
+ engine = policy_client or self._default_policy_client()
102
+ decision = engine.evaluate(
103
+ environment=environment,
104
+ trace_id=trace_id,
105
+ service="sf_lineage",
106
+ control=control,
107
+ evaluated_at=recorded_at,
108
+ metadata={"subject_type": subject_type, "subject_id": subject_id},
109
+ )
110
+ merged_metadata = dict(metadata or {})
111
+ merged_metadata.setdefault("policy_id", decision.policy_id)
112
+ merged_metadata.setdefault("policy_action", decision.action)
113
+ return self.record(
114
+ trace_id=trace_id,
115
+ decision_id=decision_id,
116
+ subject_type=subject_type,
117
+ subject_id=subject_id,
118
+ operation=operation,
119
+ recorded_at=recorded_at,
120
+ lineage_id=lineage_id,
121
+ input_refs=input_refs,
122
+ output_refs=output_refs,
123
+ parent_lineage_ids=parent_lineage_ids,
124
+ metadata=merged_metadata,
125
+ )
126
+
127
+ async def record_async(self, **kwargs: Any) -> LineagePayload:
128
+ """Async wrapper around :meth:`record`."""
129
+ import asyncio
130
+
131
+ loop = asyncio.get_event_loop()
132
+ return await loop.run_in_executor(None, lambda: self.record(**kwargs))
133
+
134
+ def get(self, lineage_id: str) -> LineagePayload | None:
135
+ """Return a previously emitted lineage record."""
136
+ with self._lock:
137
+ return self._records.get(lineage_id)
138
+
139
+ def list_for_trace(self, trace_id: str) -> list[LineagePayload]:
140
+ """Return all lineage records emitted for a trace."""
141
+ with self._lock:
142
+ ids = list(self._by_trace.get(trace_id, []))
143
+ return [self._records[item] for item in ids if item in self._records]
144
+
145
+ def list_for_subject(self, *, subject_type: str, subject_id: str) -> list[LineagePayload]:
146
+ """Return all lineage records for one subject."""
147
+ subject_key = self._subject_key(subject_type, subject_id)
148
+ with self._lock:
149
+ ids = list(self._by_subject.get(subject_key, []))
150
+ return [self._records[item] for item in ids if item in self._records]
151
+
152
+ def get_status(self) -> LineageStatusInfo:
153
+ """Return service health and lineage counters."""
154
+ with self._lock:
155
+ return LineageStatusInfo(
156
+ status="ok",
157
+ total_recorded=self._total_recorded,
158
+ traces_tracked=len(self._by_trace),
159
+ )
160
+
161
+ @staticmethod
162
+ def _subject_key(subject_type: str, subject_id: str) -> str:
163
+ return f"{subject_type}:{subject_id}"
164
+
165
+ def _emit_signed_record(self, payload: LineagePayload) -> None:
166
+ """Write the lineage payload into sf-audit."""
167
+ from spanforge.sdk import sf_audit
168
+
169
+ sf_audit.append(payload.to_dict(), "spanforge.lineage.v1")
170
+
171
+ @staticmethod
172
+ def _default_policy_client() -> Any:
173
+ from spanforge.sdk import sf_policy
174
+
175
+ return sf_policy