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.
- spanforge/__init__.py +815 -0
- spanforge/_ansi.py +93 -0
- spanforge/_batch_exporter.py +409 -0
- spanforge/_cli.py +2094 -0
- spanforge/_cli_audit.py +639 -0
- spanforge/_cli_compliance.py +711 -0
- spanforge/_cli_cost.py +243 -0
- spanforge/_cli_ops.py +791 -0
- spanforge/_cli_phase11.py +356 -0
- spanforge/_hooks.py +337 -0
- spanforge/_server.py +1708 -0
- spanforge/_span.py +1036 -0
- spanforge/_store.py +288 -0
- spanforge/_stream.py +664 -0
- spanforge/_trace.py +335 -0
- spanforge/_tracer.py +254 -0
- spanforge/actor.py +141 -0
- spanforge/alerts.py +469 -0
- spanforge/auto.py +464 -0
- spanforge/baseline.py +335 -0
- spanforge/cache.py +635 -0
- spanforge/compliance.py +325 -0
- spanforge/config.py +532 -0
- spanforge/consent.py +228 -0
- spanforge/consumer.py +377 -0
- spanforge/core/__init__.py +5 -0
- spanforge/core/compliance_mapping.py +1254 -0
- spanforge/cost.py +600 -0
- spanforge/debug.py +548 -0
- spanforge/deprecations.py +205 -0
- spanforge/drift.py +482 -0
- spanforge/egress.py +58 -0
- spanforge/eval.py +648 -0
- spanforge/event.py +1064 -0
- spanforge/exceptions.py +240 -0
- spanforge/explain.py +178 -0
- spanforge/export/__init__.py +69 -0
- spanforge/export/append_only.py +337 -0
- spanforge/export/cloud.py +357 -0
- spanforge/export/datadog.py +497 -0
- spanforge/export/grafana.py +320 -0
- spanforge/export/jsonl.py +195 -0
- spanforge/export/openinference.py +158 -0
- spanforge/export/otel_bridge.py +294 -0
- spanforge/export/otlp.py +811 -0
- spanforge/export/otlp_bridge.py +233 -0
- spanforge/export/redis_backend.py +282 -0
- spanforge/export/siem_schema.py +98 -0
- spanforge/export/siem_splunk.py +264 -0
- spanforge/export/siem_syslog.py +212 -0
- spanforge/export/webhook.py +299 -0
- spanforge/exporters/__init__.py +30 -0
- spanforge/exporters/console.py +271 -0
- spanforge/exporters/jsonl.py +144 -0
- spanforge/exporters/sqlite.py +142 -0
- spanforge/gate.py +1150 -0
- spanforge/governance.py +181 -0
- spanforge/hitl.py +295 -0
- spanforge/http.py +187 -0
- spanforge/inspect.py +427 -0
- spanforge/integrations/__init__.py +45 -0
- spanforge/integrations/_pricing.py +280 -0
- spanforge/integrations/anthropic.py +388 -0
- spanforge/integrations/azure_openai.py +133 -0
- spanforge/integrations/bedrock.py +292 -0
- spanforge/integrations/crewai.py +251 -0
- spanforge/integrations/gemini.py +351 -0
- spanforge/integrations/groq.py +442 -0
- spanforge/integrations/langchain.py +349 -0
- spanforge/integrations/langgraph.py +306 -0
- spanforge/integrations/llamaindex.py +373 -0
- spanforge/integrations/ollama.py +287 -0
- spanforge/integrations/openai.py +368 -0
- spanforge/integrations/together.py +483 -0
- spanforge/io.py +214 -0
- spanforge/lint.py +322 -0
- spanforge/metrics.py +417 -0
- spanforge/metrics_export.py +343 -0
- spanforge/migrate.py +402 -0
- spanforge/model_registry.py +278 -0
- spanforge/models.py +389 -0
- spanforge/namespaces/__init__.py +254 -0
- spanforge/namespaces/audit.py +256 -0
- spanforge/namespaces/cache.py +237 -0
- spanforge/namespaces/chain.py +77 -0
- spanforge/namespaces/confidence.py +72 -0
- spanforge/namespaces/consent.py +92 -0
- spanforge/namespaces/cost.py +179 -0
- spanforge/namespaces/decision.py +143 -0
- spanforge/namespaces/diff.py +157 -0
- spanforge/namespaces/drift.py +80 -0
- spanforge/namespaces/eval_.py +251 -0
- spanforge/namespaces/feedback.py +241 -0
- spanforge/namespaces/fence.py +193 -0
- spanforge/namespaces/guard.py +105 -0
- spanforge/namespaces/hitl.py +91 -0
- spanforge/namespaces/latency.py +72 -0
- spanforge/namespaces/prompt.py +190 -0
- spanforge/namespaces/redact.py +173 -0
- spanforge/namespaces/retrieval.py +379 -0
- spanforge/namespaces/runtime_governance.py +494 -0
- spanforge/namespaces/template.py +208 -0
- spanforge/namespaces/tool_call.py +77 -0
- spanforge/namespaces/trace.py +1029 -0
- spanforge/normalizer.py +171 -0
- spanforge/plugins.py +82 -0
- spanforge/presidio_backend.py +349 -0
- spanforge/processor.py +258 -0
- spanforge/prompt_registry.py +418 -0
- spanforge/py.typed +0 -0
- spanforge/redact.py +914 -0
- spanforge/regression.py +192 -0
- spanforge/runtime_policy.py +159 -0
- spanforge/sampling.py +511 -0
- spanforge/schema.py +183 -0
- spanforge/schemas/v1.0/schema.json +170 -0
- spanforge/schemas/v2.0/schema.json +536 -0
- spanforge/sdk/__init__.py +625 -0
- spanforge/sdk/_base.py +584 -0
- spanforge/sdk/_base.pyi +71 -0
- spanforge/sdk/_exceptions.py +1096 -0
- spanforge/sdk/_types.py +2184 -0
- spanforge/sdk/alert.py +1514 -0
- spanforge/sdk/alert.pyi +56 -0
- spanforge/sdk/audit.py +1196 -0
- spanforge/sdk/audit.pyi +67 -0
- spanforge/sdk/cec.py +1215 -0
- spanforge/sdk/cec.pyi +37 -0
- spanforge/sdk/config.py +641 -0
- spanforge/sdk/config.pyi +55 -0
- spanforge/sdk/enterprise.py +714 -0
- spanforge/sdk/enterprise.pyi +79 -0
- spanforge/sdk/explain.py +170 -0
- spanforge/sdk/fallback.py +432 -0
- spanforge/sdk/feedback.py +351 -0
- spanforge/sdk/gate.py +874 -0
- spanforge/sdk/gate.pyi +51 -0
- spanforge/sdk/identity.py +2114 -0
- spanforge/sdk/identity.pyi +47 -0
- spanforge/sdk/lineage.py +175 -0
- spanforge/sdk/observe.py +1065 -0
- spanforge/sdk/observe.pyi +50 -0
- spanforge/sdk/operator.py +338 -0
- spanforge/sdk/pii.py +1473 -0
- spanforge/sdk/pii.pyi +119 -0
- spanforge/sdk/pipelines.py +458 -0
- spanforge/sdk/pipelines.pyi +39 -0
- spanforge/sdk/policy.py +930 -0
- spanforge/sdk/rag.py +594 -0
- spanforge/sdk/rbac.py +280 -0
- spanforge/sdk/registry.py +430 -0
- spanforge/sdk/registry.pyi +46 -0
- spanforge/sdk/scope.py +279 -0
- spanforge/sdk/secrets.py +293 -0
- spanforge/sdk/secrets.pyi +25 -0
- spanforge/sdk/security.py +560 -0
- spanforge/sdk/security.pyi +57 -0
- spanforge/sdk/trust.py +472 -0
- spanforge/sdk/trust.pyi +41 -0
- spanforge/secrets.py +799 -0
- spanforge/signing.py +1179 -0
- spanforge/stats.py +100 -0
- spanforge/stream.py +560 -0
- spanforge/testing.py +378 -0
- spanforge/testing_mocks.py +1052 -0
- spanforge/trace.py +199 -0
- spanforge/types.py +696 -0
- spanforge/ulid.py +300 -0
- spanforge/validate.py +379 -0
- spanforge-1.0.0.dist-info/METADATA +1509 -0
- spanforge-1.0.0.dist-info/RECORD +174 -0
- spanforge-1.0.0.dist-info/WHEEL +4 -0
- spanforge-1.0.0.dist-info/entry_points.txt +5 -0
- 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]: ...
|
spanforge/sdk/lineage.py
ADDED
|
@@ -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
|