cluebase-backend-sdk 0.0.1__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.
- cluebase_backend_sdk/__init__.py +484 -0
- cluebase_backend_sdk/_cluebase_authored_attributes.py +37 -0
- cluebase_backend_sdk/_generated/__init__.py +11 -0
- cluebase_backend_sdk/_generated/correlation_attribute_keys.py +37 -0
- cluebase_backend_sdk/_generated/identity_pii_policy.py +62 -0
- cluebase_backend_sdk/_generated/ingest_privacy_tables.py +727 -0
- cluebase_backend_sdk/_generated/otel_resource_keys.py +62 -0
- cluebase_backend_sdk/_generated/sdk_hint_vocabulary.py +54 -0
- cluebase_backend_sdk/_generated/usage_governor_tables.py +52 -0
- cluebase_backend_sdk/_generated/wire_credential_policy.py +24 -0
- cluebase_backend_sdk/_init_options.py +156 -0
- cluebase_backend_sdk/_integrations/__init__.py +10 -0
- cluebase_backend_sdk/_integrations/ai/__init__.py +10 -0
- cluebase_backend_sdk/_integrations/ai/_response_parsing.py +144 -0
- cluebase_backend_sdk/_integrations/ai/_span_attributes.py +249 -0
- cluebase_backend_sdk/_integrations/ai/_version.py +5 -0
- cluebase_backend_sdk/_integrations/ai/instrumentation/__init__.py +1 -0
- cluebase_backend_sdk/_integrations/ai/instrumentation/anthropic.py +526 -0
- cluebase_backend_sdk/_integrations/ai/instrumentation/base.py +543 -0
- cluebase_backend_sdk/_integrations/ai/instrumentation/openai.py +250 -0
- cluebase_backend_sdk/_integrations/ai/observer.py +774 -0
- cluebase_backend_sdk/_integrations/crewai/__init__.py +29 -0
- cluebase_backend_sdk/_integrations/crewai/observer.py +393 -0
- cluebase_backend_sdk/_integrations/django/__init__.py +43 -0
- cluebase_backend_sdk/_integrations/django/django.py +85 -0
- cluebase_backend_sdk/_integrations/django/install.py +154 -0
- cluebase_backend_sdk/_integrations/fastapi/__init__.py +35 -0
- cluebase_backend_sdk/_integrations/fastapi/fastapi.py +432 -0
- cluebase_backend_sdk/_integrations/langchain/__init__.py +28 -0
- cluebase_backend_sdk/_integrations/langchain/_run_tracker.py +127 -0
- cluebase_backend_sdk/_integrations/langchain/callback.py +505 -0
- cluebase_backend_sdk/_integrations/langchain/instrument.py +123 -0
- cluebase_backend_sdk/_integrations/llamaindex/__init__.py +28 -0
- cluebase_backend_sdk/_integrations/llamaindex/_run_tracker.py +124 -0
- cluebase_backend_sdk/_integrations/llamaindex/event_handler.py +320 -0
- cluebase_backend_sdk/_integrations/llamaindex/instrument.py +93 -0
- cluebase_backend_sdk/_integrations/mcp/__init__.py +14 -0
- cluebase_backend_sdk/_integrations/mcp/_method_observations.py +569 -0
- cluebase_backend_sdk/_integrations/mcp/_observer_helpers.py +530 -0
- cluebase_backend_sdk/_integrations/mcp/_tool_observation.py +346 -0
- cluebase_backend_sdk/_integrations/mcp/middleware.py +103 -0
- cluebase_backend_sdk/_integrations/mcp/observer.py +660 -0
- cluebase_backend_sdk/_integrations/mcp/session.py +105 -0
- cluebase_backend_sdk/_internal_log.py +51 -0
- cluebase_backend_sdk/_safe_logger.py +32 -0
- cluebase_backend_sdk/_version.py +5 -0
- cluebase_backend_sdk/context/__init__.py +9 -0
- cluebase_backend_sdk/context/parameter_snapshot.py +183 -0
- cluebase_backend_sdk/exporter/__init__.py +10 -0
- cluebase_backend_sdk/exporter/client.py +574 -0
- cluebase_backend_sdk/exporter/collection_gate.py +217 -0
- cluebase_backend_sdk/exporter/contracts.py +577 -0
- cluebase_backend_sdk/exporter/deadline.py +36 -0
- cluebase_backend_sdk/exporter/delivery.py +509 -0
- cluebase_backend_sdk/exporter/delivery_runner.py +148 -0
- cluebase_backend_sdk/exporter/diagnostics.py +216 -0
- cluebase_backend_sdk/exporter/envelope.py +60 -0
- cluebase_backend_sdk/exporter/event_id.py +74 -0
- cluebase_backend_sdk/exporter/event_size.py +98 -0
- cluebase_backend_sdk/exporter/http_transport.py +262 -0
- cluebase_backend_sdk/exporter/lifecycle.py +91 -0
- cluebase_backend_sdk/exporter/observation_source_event_exporter.py +213 -0
- cluebase_backend_sdk/exporter/otel_bridge.py +192 -0
- cluebase_backend_sdk/exporter/pending.py +394 -0
- cluebase_backend_sdk/exporter/privacy.py +313 -0
- cluebase_backend_sdk/exporter/profile.py +149 -0
- cluebase_backend_sdk/exporter/sanitize.py +39 -0
- cluebase_backend_sdk/exporter/source_signal.py +299 -0
- cluebase_backend_sdk/exporter/source_signal_context.py +53 -0
- cluebase_backend_sdk/exporter/source_signal_privacy.py +88 -0
- cluebase_backend_sdk/exporter/source_signal_record_privacy.py +193 -0
- cluebase_backend_sdk/exporter/wire_credentials.py +33 -0
- cluebase_backend_sdk/instrumentation/__init__.py +10 -0
- cluebase_backend_sdk/instrumentation/_orm_django.py +188 -0
- cluebase_backend_sdk/instrumentation/_orm_spans.py +271 -0
- cluebase_backend_sdk/instrumentation/_orm_sqlalchemy.py +337 -0
- cluebase_backend_sdk/instrumentation/_orm_state.py +100 -0
- cluebase_backend_sdk/instrumentation/celery.py +539 -0
- cluebase_backend_sdk/instrumentation/http_extraction.py +158 -0
- cluebase_backend_sdk/instrumentation/httpx.py +101 -0
- cluebase_backend_sdk/instrumentation/orm.py +44 -0
- cluebase_backend_sdk/instrumentation/requests.py +254 -0
- cluebase_backend_sdk/instrumentation/server_request.py +104 -0
- cluebase_backend_sdk/instrumentation/span_attributes.py +71 -0
- cluebase_backend_sdk/provider/__init__.py +10 -0
- cluebase_backend_sdk/provider/_pipeline.py +286 -0
- cluebase_backend_sdk/provider/_runtime_state.py +691 -0
- cluebase_backend_sdk/provider/_runtime_telemetry.py +873 -0
- cluebase_backend_sdk/provider/bootstrap.py +179 -0
- cluebase_backend_sdk/provider/consent_span_processor.py +103 -0
- cluebase_backend_sdk/provider/privacy_policy_store.py +115 -0
- cluebase_backend_sdk/provider/resources.py +96 -0
- cluebase_backend_sdk/provider/runtime.py +258 -0
- cluebase_backend_sdk/provider/runtime_context.py +43 -0
- cluebase_backend_sdk/provider/runtime_diagnostics.py +66 -0
- cluebase_backend_sdk/provider/runtime_lifecycle.py +74 -0
- cluebase_backend_sdk-0.0.1.dist-info/METADATA +193 -0
- cluebase_backend_sdk-0.0.1.dist-info/RECORD +100 -0
- cluebase_backend_sdk-0.0.1.dist-info/WHEEL +5 -0
- cluebase_backend_sdk-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""Cluebase Backend SDK (Python) public API.
|
|
2
|
+
|
|
3
|
+
The package exposes a single ``cluebase`` namespace object with ten methods:
|
|
4
|
+
``init`` / ``identify`` / ``group`` / ``reset`` / ``track`` / ``set_consent`` /
|
|
5
|
+
``link`` / ``flush`` / ``close`` / ``get_diagnostics``. Python options use
|
|
6
|
+
snake_case names.
|
|
7
|
+
|
|
8
|
+
Idiomatic usage::
|
|
9
|
+
|
|
10
|
+
from cluebase_backend_sdk import cluebase
|
|
11
|
+
|
|
12
|
+
cluebase.init({
|
|
13
|
+
"endpoint": "https://api.example.com",
|
|
14
|
+
"project_key": "pk_prod_...",
|
|
15
|
+
"api_key": "cluebasesk_...",
|
|
16
|
+
"service_key": "backend-api",
|
|
17
|
+
})
|
|
18
|
+
cluebase.identify("user_123", {"name": "Alice", "email": "alice@example.com"})
|
|
19
|
+
cluebase.group("organization", "org_42", {"name": "Acme"})
|
|
20
|
+
cluebase.track("checkout_completed", {"plan": "pro"})
|
|
21
|
+
cluebase.set_consent("granted")
|
|
22
|
+
await cluebase.flush()
|
|
23
|
+
await cluebase.close()
|
|
24
|
+
|
|
25
|
+
Framework integrations import runtime helpers from their owning modules; the
|
|
26
|
+
package root is the customer-facing facade plus stable setup helpers.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
33
|
+
from typing import Literal, TypeAlias, TypedDict
|
|
34
|
+
|
|
35
|
+
from ._init_options import (
|
|
36
|
+
CluebaseInitInput,
|
|
37
|
+
CluebaseInitOptions,
|
|
38
|
+
CluebaseSdkCollectionMode,
|
|
39
|
+
_resolve_init_options,
|
|
40
|
+
_validate_init_options,
|
|
41
|
+
)
|
|
42
|
+
from ._internal_log import get_logger
|
|
43
|
+
from ._version import __version__
|
|
44
|
+
from .exporter.client import CluebasePythonClient
|
|
45
|
+
from .exporter.diagnostics import DiagnosticSnapshot
|
|
46
|
+
from .exporter.privacy import (
|
|
47
|
+
DEFAULT_DENIED_KEYS,
|
|
48
|
+
JsonValue,
|
|
49
|
+
build_mask_token,
|
|
50
|
+
safe_sanitize_object,
|
|
51
|
+
)
|
|
52
|
+
from .instrumentation.celery import instrument_celery_signals
|
|
53
|
+
from .instrumentation.httpx import instrument_httpx, reset_httpx_instrumentation
|
|
54
|
+
from .instrumentation.orm import instrument_django_orm, instrument_sqlalchemy_orm
|
|
55
|
+
from .instrumentation.requests import (
|
|
56
|
+
instrument_requests,
|
|
57
|
+
reset_requests_instrumentation,
|
|
58
|
+
)
|
|
59
|
+
from .provider.bootstrap import (
|
|
60
|
+
CluebasePythonBootstrapConfig,
|
|
61
|
+
build_otel_environment,
|
|
62
|
+
configure_opentelemetry_environment,
|
|
63
|
+
default_traces_endpoint_from_ingest_endpoint,
|
|
64
|
+
initialize_opentelemetry,
|
|
65
|
+
load_bootstrap_config,
|
|
66
|
+
require_opentelemetry,
|
|
67
|
+
)
|
|
68
|
+
from .provider.resources import (
|
|
69
|
+
CluebasePythonResourceConfig,
|
|
70
|
+
build_resource_attributes,
|
|
71
|
+
serialize_resource_attributes,
|
|
72
|
+
)
|
|
73
|
+
from .provider.runtime import (
|
|
74
|
+
CluebaseCommand,
|
|
75
|
+
CluebasePythonSettings,
|
|
76
|
+
CluebaseStateTransition,
|
|
77
|
+
CluebaseSetOperationDiscriminator,
|
|
78
|
+
CluebaseToolCall,
|
|
79
|
+
annotate_current_otel_span,
|
|
80
|
+
build_settings,
|
|
81
|
+
clear_configured_settings,
|
|
82
|
+
close_shared_clients,
|
|
83
|
+
configure_settings,
|
|
84
|
+
create_client,
|
|
85
|
+
create_request_context,
|
|
86
|
+
emit_sdk_initialized,
|
|
87
|
+
enqueue_client_events,
|
|
88
|
+
flush_shared_clients,
|
|
89
|
+
get_current_client,
|
|
90
|
+
get_current_consent,
|
|
91
|
+
get_current_context,
|
|
92
|
+
load_settings,
|
|
93
|
+
reset_current_state,
|
|
94
|
+
set_current_state,
|
|
95
|
+
)
|
|
96
|
+
from .provider.runtime import (
|
|
97
|
+
CluebaseIdentify as _internal_identify,
|
|
98
|
+
)
|
|
99
|
+
from .provider.runtime import (
|
|
100
|
+
CluebaseLink as _internal_link,
|
|
101
|
+
)
|
|
102
|
+
from .provider.runtime import (
|
|
103
|
+
CluebaseLogout as _internal_logout,
|
|
104
|
+
)
|
|
105
|
+
from .provider.runtime import (
|
|
106
|
+
CluebaseSetOrganization as _internal_set_organization,
|
|
107
|
+
)
|
|
108
|
+
from .provider.runtime import (
|
|
109
|
+
CluebaseTrack as _internal_track,
|
|
110
|
+
)
|
|
111
|
+
from .provider.runtime import (
|
|
112
|
+
clear_current_state as _clear_current_state,
|
|
113
|
+
)
|
|
114
|
+
from .provider.runtime import (
|
|
115
|
+
get_diagnostics as _get_runtime_diagnostics,
|
|
116
|
+
)
|
|
117
|
+
from .provider.runtime import (
|
|
118
|
+
set_consent as _set_runtime_consent,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
_logger = get_logger("public")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# Public namespace types (= mirror the Frontend SDK's `CluebaseGroupType` /
|
|
126
|
+
# `CluebaseGroupTraits` / `CluebaseConsentState`).
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
CluebaseGroupType = Literal["organization"]
|
|
131
|
+
"""MVP group taxonomy. Customer company grouping is represented as
|
|
132
|
+
``organization``. ``account`` / ``workspace`` are future concepts and are not
|
|
133
|
+
public ``cluebase.group`` inputs for MVP."""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
CluebaseConsentState = Literal["granted", "denied", "unknown"]
|
|
137
|
+
"""Process-wide collection consent state."""
|
|
138
|
+
|
|
139
|
+
"""SDK collection mode accepted by ``cluebase.init``."""
|
|
140
|
+
|
|
141
|
+
CluebasePublicTraits: TypeAlias = Mapping[str, object]
|
|
142
|
+
"""Free-form customer traits accepted by ``cluebase.identify`` and ``cluebase.group``."""
|
|
143
|
+
|
|
144
|
+
CluebasePublicProperties: TypeAlias = Mapping[str, object]
|
|
145
|
+
"""Free-form customer event properties accepted by ``cluebase.track``."""
|
|
146
|
+
|
|
147
|
+
CluebasePublicMetrics: TypeAlias = Mapping[str, object]
|
|
148
|
+
"""Free-form customer event metrics accepted by ``cluebase.track``."""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class CluebaseGroupTraits(TypedDict, total=False):
|
|
152
|
+
"""Optional traits attached to ``cluebase.group(...)``. ``name`` is recommended
|
|
153
|
+
(human-readable label used downstream by analysis surfaces); other keys are
|
|
154
|
+
free-form and forwarded as event properties subject to ``denied_keys``."""
|
|
155
|
+
|
|
156
|
+
name: str
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class CluebaseLinkSubject(TypedDict):
|
|
160
|
+
type: CluebaseGroupType
|
|
161
|
+
id: str
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class CluebaseExternalIdentity(TypedDict):
|
|
165
|
+
namespace: str
|
|
166
|
+
id: str
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class CluebaseLinkInput(TypedDict):
|
|
170
|
+
subject: CluebaseLinkSubject
|
|
171
|
+
external: CluebaseExternalIdentity
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class CluebaseSubjectTraits(TypedDict, total=False):
|
|
175
|
+
"""Optional traits attached to ``cluebase.identify(user_id, traits)``. The
|
|
176
|
+
``name`` field is *strongly* recommended because analysis surfaces must not
|
|
177
|
+
display the raw user_id as a person label. ``email`` is converted to
|
|
178
|
+
privacy-safe contact-derived fields before transport.
|
|
179
|
+
|
|
180
|
+
Field naming exception: keys are **camelCase** even on the Python SDK
|
|
181
|
+
(``avatarUrl``), not the usual Python ``snake_case``.
|
|
182
|
+
This honours the cross-SDK customer-facing contract documented in
|
|
183
|
+
``docs/contracts/sdk/public-api.md`` §1.6 — every SDK
|
|
184
|
+
(Browser / Node / Python) accepts the same trait keys so customers can
|
|
185
|
+
copy idiomatic examples between languages without renaming fields.
|
|
186
|
+
The ingest core normalizes these values after receiving SDK source signals."""
|
|
187
|
+
|
|
188
|
+
name: str
|
|
189
|
+
email: str
|
|
190
|
+
emailAddress: str
|
|
191
|
+
displayName: str
|
|
192
|
+
avatarUrl: str
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
"""Runtime input accepted by ``cluebase.init``; mapping form preserves public SDK flexibility."""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
# Safe-call helpers — the SDK guarantees never to crash the host application,
|
|
202
|
+
# even if a hot path raises. We log the swallowed exception via the SDK's
|
|
203
|
+
# internal diagnostic logger so operators can opt-in to debug output.
|
|
204
|
+
# ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _report_public_sdk_error(operation: str, error: BaseException) -> None:
|
|
208
|
+
_ = error
|
|
209
|
+
# `_logger` は `SafeLogger` で、記録に失敗しても自分で飲み込みます
|
|
210
|
+
# (`_safe_logger.py`)。ここで受け口を重ねても、通ることがありません
|
|
211
|
+
_logger.warning(
|
|
212
|
+
"cluebase.sdk public operation failed operation=%s code=public_operation_failed",
|
|
213
|
+
operation,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _safe_call(operation: str, callback: Callable[[], object]) -> None:
|
|
218
|
+
try:
|
|
219
|
+
callback()
|
|
220
|
+
except BaseException as exc: # noqa: BLE001 — last-resort host protection
|
|
221
|
+
_report_public_sdk_error(operation, exc)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def _safe_async_call(
|
|
225
|
+
operation: str, callback: Callable[[], Awaitable[object]]
|
|
226
|
+
) -> None:
|
|
227
|
+
try:
|
|
228
|
+
await callback()
|
|
229
|
+
except BaseException as exc: # noqa: BLE001 — last-resort host protection
|
|
230
|
+
_report_public_sdk_error(operation, exc)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# `cluebase.init` maps Python setup options onto runtime bootstrap settings.
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ``allowed_value_paths`` used to let the caller name the request/response
|
|
247
|
+
# fields that may leave the process in clear text. That choice now belongs to
|
|
248
|
+
# the Cluebase privacy screen and reaches the SDK through the delivered privacy
|
|
249
|
+
# policy, so the option has nothing left to apply to.
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# `cluebase` namespace — module-level singleton facade. The object is module-level
|
|
256
|
+
# so multiple imports in the same process share state, exactly like the
|
|
257
|
+
# Frontend SDK's ``globalThis.__cluebase_frontend_sdk_singleton__`` pattern.
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class _Cluebase:
|
|
262
|
+
"""Cluebase Backend SDK public namespace.
|
|
263
|
+
|
|
264
|
+
Ten methods mirror the cross-SDK contract documented in
|
|
265
|
+
``docs/contracts/sdk/public-api.md`` §1.6. Every method
|
|
266
|
+
swallows exceptions and reports them via the SDK internal logger; the SDK
|
|
267
|
+
is contractually forbidden from crashing the host application.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
__slots__ = ()
|
|
271
|
+
|
|
272
|
+
def _allows_collection(self) -> bool:
|
|
273
|
+
return get_current_consent() != "denied"
|
|
274
|
+
|
|
275
|
+
def init(self, options: CluebaseInitInput) -> None:
|
|
276
|
+
"""Initialise the SDK. ``endpoint``, ``project_key``, ``api_key``, and
|
|
277
|
+
``service_key`` are required; a missing one raises ``ValueError`` rather
|
|
278
|
+
than leaving the SDK collecting nothing. Calling ``init`` twice is a
|
|
279
|
+
no-op (= the bootstrap layer guards against duplicate TracerProvider
|
|
280
|
+
wiring)."""
|
|
281
|
+
|
|
282
|
+
# Raised before ``_safe_call`` so the caller actually sees it; see
|
|
283
|
+
# ``_validate_init_options``.
|
|
284
|
+
_validate_init_options(options)
|
|
285
|
+
|
|
286
|
+
def _do_init() -> None:
|
|
287
|
+
bootstrap_config, settings = _resolve_init_options(options)
|
|
288
|
+
require_opentelemetry(bootstrap_config)
|
|
289
|
+
configure_settings(settings)
|
|
290
|
+
emit_sdk_initialized(settings)
|
|
291
|
+
|
|
292
|
+
_safe_call("init", _do_init)
|
|
293
|
+
|
|
294
|
+
def identify(
|
|
295
|
+
self,
|
|
296
|
+
user_id: str,
|
|
297
|
+
traits: CluebasePublicTraits | None = None,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Bind the caller-supplied subject identity to subsequent events.
|
|
300
|
+
``traits['name']`` is strongly recommended because analysis surfaces must
|
|
301
|
+
not display the raw user_id as a person label. ``traits['email']`` is
|
|
302
|
+
converted to privacy-safe contact-derived fields before transport."""
|
|
303
|
+
|
|
304
|
+
if not self._allows_collection():
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
_safe_call("identify", lambda: _internal_identify(user_id, traits or {}))
|
|
308
|
+
|
|
309
|
+
def group(
|
|
310
|
+
self,
|
|
311
|
+
group_type: str,
|
|
312
|
+
group_key: str,
|
|
313
|
+
traits: CluebasePublicTraits | None = None,
|
|
314
|
+
) -> None:
|
|
315
|
+
"""Associate the caller with a customer company."""
|
|
316
|
+
|
|
317
|
+
def _dispatch() -> None:
|
|
318
|
+
resolved_traits = traits or {}
|
|
319
|
+
if group_type != "organization":
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"cluebase.group: unsupported group_type {group_type!r}; "
|
|
322
|
+
"expected 'organization'"
|
|
323
|
+
)
|
|
324
|
+
if not self._allows_collection():
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
_internal_set_organization(group_key, resolved_traits)
|
|
328
|
+
|
|
329
|
+
_safe_call("group", _dispatch)
|
|
330
|
+
|
|
331
|
+
def reset(self) -> None:
|
|
332
|
+
"""Clear identity state. ``logout`` and the in-memory reset are wrapped
|
|
333
|
+
in independent ``_safe_call`` invocations so one failing does not
|
|
334
|
+
short-circuit the other (= state must be cleared as best-effort)."""
|
|
335
|
+
|
|
336
|
+
if self._allows_collection():
|
|
337
|
+
_safe_call("reset", lambda: _internal_logout())
|
|
338
|
+
_safe_call("reset", _clear_current_state)
|
|
339
|
+
|
|
340
|
+
def link(self, link_input: CluebaseLinkInput | Mapping[str, object]) -> None:
|
|
341
|
+
"""Record a canonical organization to external identifier fact."""
|
|
342
|
+
|
|
343
|
+
if not self._allows_collection():
|
|
344
|
+
return
|
|
345
|
+
_safe_call("link", lambda: _internal_link(link_input))
|
|
346
|
+
|
|
347
|
+
def track(
|
|
348
|
+
self,
|
|
349
|
+
event_name: str,
|
|
350
|
+
properties: CluebasePublicProperties | None = None,
|
|
351
|
+
metrics: CluebasePublicMetrics | None = None,
|
|
352
|
+
) -> None:
|
|
353
|
+
"""Emit a custom event with the same
|
|
354
|
+
``track(event_name, properties=None, metrics=None)`` public contract as
|
|
355
|
+
the Browser and Node SDKs."""
|
|
356
|
+
|
|
357
|
+
if not self._allows_collection():
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
_safe_call("track", lambda: _internal_track(event_name, properties, metrics))
|
|
361
|
+
|
|
362
|
+
def set_operation_discriminator(self, value: str) -> None:
|
|
363
|
+
"""Annotate the active backend operation with a mechanical branch key."""
|
|
364
|
+
|
|
365
|
+
_safe_call(
|
|
366
|
+
"set_operation_discriminator",
|
|
367
|
+
lambda: CluebaseSetOperationDiscriminator(value),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def set_consent(self, consent: str) -> None:
|
|
371
|
+
"""Record the caller's analytics consent state. Accepted values
|
|
372
|
+
(Literal): ``"granted"`` / ``"denied"`` / ``"unknown"``. ``denied``
|
|
373
|
+
makes later collection calls no-op; changing back to ``granted`` or
|
|
374
|
+
``unknown`` resumes collection."""
|
|
375
|
+
|
|
376
|
+
def _set_state() -> None:
|
|
377
|
+
if consent not in ("granted", "denied", "unknown"):
|
|
378
|
+
raise ValueError(
|
|
379
|
+
f"cluebase.set_consent: unsupported value {consent!r}; "
|
|
380
|
+
"expected one of 'granted' / 'denied' / 'unknown'"
|
|
381
|
+
)
|
|
382
|
+
_set_runtime_consent(consent)
|
|
383
|
+
|
|
384
|
+
_safe_call("set_consent", _set_state)
|
|
385
|
+
|
|
386
|
+
async def flush(self) -> None:
|
|
387
|
+
"""Force-flush every buffered shared client. Awaited so callers can
|
|
388
|
+
``await cluebase.flush()`` before shutdown — the underlying flush is
|
|
389
|
+
synchronous but we expose it as a coroutine for cross-SDK signature
|
|
390
|
+
parity (= Frontend SDK is async)."""
|
|
391
|
+
|
|
392
|
+
async def _run() -> None:
|
|
393
|
+
await asyncio.to_thread(flush_shared_clients)
|
|
394
|
+
|
|
395
|
+
await _safe_async_call("flush", _run)
|
|
396
|
+
|
|
397
|
+
async def close(self) -> None:
|
|
398
|
+
"""Stop capture and close the canonical delivery lifecycle."""
|
|
399
|
+
|
|
400
|
+
async def _run() -> None:
|
|
401
|
+
await asyncio.to_thread(close_shared_clients)
|
|
402
|
+
|
|
403
|
+
await _safe_async_call("close", _run)
|
|
404
|
+
|
|
405
|
+
def get_diagnostics(self) -> DiagnosticSnapshot:
|
|
406
|
+
"""Return a defensive, privacy-safe runtime diagnostics snapshot."""
|
|
407
|
+
|
|
408
|
+
return _get_runtime_diagnostics()
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
cluebase = _Cluebase()
|
|
412
|
+
"""Module-level singleton. Import as ``from cluebase_backend_sdk import cluebase``."""
|
|
413
|
+
|
|
414
|
+
CluebaseIdentify = _internal_identify
|
|
415
|
+
CluebaseLink = _internal_link
|
|
416
|
+
CluebaseLogout = _internal_logout
|
|
417
|
+
CluebaseSetOrganization = _internal_set_organization
|
|
418
|
+
CluebaseTrack = _internal_track
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# ---------------------------------------------------------------------------
|
|
422
|
+
# Helper exports for framework integrations.
|
|
423
|
+
__all__ = [
|
|
424
|
+
"DEFAULT_DENIED_KEYS",
|
|
425
|
+
"CluebaseCommand",
|
|
426
|
+
"CluebaseConsentState",
|
|
427
|
+
"CluebaseGroupTraits",
|
|
428
|
+
"CluebaseGroupType",
|
|
429
|
+
"CluebaseIdentify",
|
|
430
|
+
"CluebaseExternalIdentity",
|
|
431
|
+
"CluebaseLink",
|
|
432
|
+
"CluebaseInitInput",
|
|
433
|
+
"CluebaseInitOptions",
|
|
434
|
+
"CluebaseLinkInput",
|
|
435
|
+
"CluebaseLinkSubject",
|
|
436
|
+
"CluebaseLogout",
|
|
437
|
+
"CluebasePublicMetrics",
|
|
438
|
+
"CluebasePublicProperties",
|
|
439
|
+
"CluebasePublicTraits",
|
|
440
|
+
"CluebasePythonBootstrapConfig",
|
|
441
|
+
"CluebasePythonClient",
|
|
442
|
+
"CluebasePythonResourceConfig",
|
|
443
|
+
"CluebasePythonSettings",
|
|
444
|
+
"CluebaseSdkCollectionMode",
|
|
445
|
+
"CluebaseSetOrganization",
|
|
446
|
+
"CluebaseSetOperationDiscriminator",
|
|
447
|
+
"CluebaseStateTransition",
|
|
448
|
+
"CluebaseSubjectTraits",
|
|
449
|
+
"CluebaseToolCall",
|
|
450
|
+
"CluebaseTrack",
|
|
451
|
+
"JsonValue",
|
|
452
|
+
"__version__",
|
|
453
|
+
"annotate_current_otel_span",
|
|
454
|
+
"build_mask_token",
|
|
455
|
+
"build_otel_environment",
|
|
456
|
+
"build_resource_attributes",
|
|
457
|
+
"build_settings",
|
|
458
|
+
"clear_configured_settings",
|
|
459
|
+
"cluebase",
|
|
460
|
+
"configure_opentelemetry_environment",
|
|
461
|
+
"configure_settings",
|
|
462
|
+
"create_client",
|
|
463
|
+
"create_request_context",
|
|
464
|
+
"default_traces_endpoint_from_ingest_endpoint",
|
|
465
|
+
"emit_sdk_initialized",
|
|
466
|
+
"enqueue_client_events",
|
|
467
|
+
"flush_shared_clients",
|
|
468
|
+
"get_current_client",
|
|
469
|
+
"get_current_context",
|
|
470
|
+
"initialize_opentelemetry",
|
|
471
|
+
"instrument_celery_signals",
|
|
472
|
+
"instrument_django_orm",
|
|
473
|
+
"instrument_httpx",
|
|
474
|
+
"instrument_requests",
|
|
475
|
+
"instrument_sqlalchemy_orm",
|
|
476
|
+
"load_bootstrap_config",
|
|
477
|
+
"load_settings",
|
|
478
|
+
"reset_current_state",
|
|
479
|
+
"reset_httpx_instrumentation",
|
|
480
|
+
"reset_requests_instrumentation",
|
|
481
|
+
"safe_sanitize_object",
|
|
482
|
+
"serialize_resource_attributes",
|
|
483
|
+
"set_current_state",
|
|
484
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""配る部品**自身が測った値**を封筒に載せる時の、属性の名前。
|
|
2
|
+
|
|
3
|
+
顧客が ``track`` で付けた項目は ``cluebase.properties.<綴り>`` /
|
|
4
|
+
``cluebase.metrics.<綴り>`` という封筒で運ばれます。**配る部品も同じ封筒を使う**
|
|
5
|
+
ため、封筒を開けた受け口から見ると、顧客が名付けた業務項目と、部品が測った値の
|
|
6
|
+
区別が付きません。
|
|
7
|
+
|
|
8
|
+
実際にそれが起きました。分析が ``duration_ms`` を顧客の業務項目だと解釈し、
|
|
9
|
+
利用者へ**「duration_ms を測りました」**と答えました(実測 2026-08-15)。
|
|
10
|
+
|
|
11
|
+
そこで、部品が測った値は封筒の中で**予約された名前**(``cluebase.`` で始まる)を
|
|
12
|
+
名乗ります。顧客の項目がこの接頭辞を持つことはないため、封筒を開けた時点で
|
|
13
|
+
混ざりません。**顧客が偶然 ``queue_name`` という業務項目を送ってきても、それは
|
|
14
|
+
顧客のものとして扱われます。**
|
|
15
|
+
|
|
16
|
+
**顧客が渡した項目には使いません。** 顧客の綴りをここへ通すと、その項目が
|
|
17
|
+
仕組みのものとして扱われ、分析に出なくなります。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
RESERVED_FIELD_KEY_PREFIX = "cluebase."
|
|
23
|
+
|
|
24
|
+
_PROPERTY_ENVELOPE_PREFIX = "cluebase.properties."
|
|
25
|
+
_METRIC_ENVELOPE_PREFIX = "cluebase.metrics."
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cluebase_authored_property_attribute(name: str) -> str:
|
|
29
|
+
"""部品自身が測った値(文字列など)の属性名。"""
|
|
30
|
+
|
|
31
|
+
return f"{_PROPERTY_ENVELOPE_PREFIX}{RESERVED_FIELD_KEY_PREFIX}{name}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cluebase_authored_metric_attribute(name: str) -> str:
|
|
35
|
+
"""部品自身が測った値(数値)の属性名。"""
|
|
36
|
+
|
|
37
|
+
return f"{_METRIC_ENVELOPE_PREFIX}{RESERVED_FIELD_KEY_PREFIX}{name}"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""自動生成です。**手で編集しないでください。**
|
|
2
|
+
|
|
3
|
+
正本: packages/cluebase-shared/src/ingest/ingest-privacy-policy.v1.json
|
|
4
|
+
出し直す: node scripts/generate-shared-contract-tables.mjs
|
|
5
|
+
|
|
6
|
+
正本を直したらこの file も一緒に commit してください。CI が
|
|
7
|
+
``node scripts/generate-shared-contract-tables.mjs --check`` で照合し、ずれていれば落とします。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
#: 生成物を出し直す命令。**この綴りを他所へ写さないでください。**
|
|
11
|
+
REGENERATE_COMMAND = "node scripts/generate-shared-contract-tables.mjs"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""自動生成です。**手で編集しないでください。**
|
|
2
|
+
|
|
3
|
+
正本: packages/cluebase-backend-contract/src/delivery/correlation-attribute-keys.v1.json
|
|
4
|
+
出し直す: node scripts/generate-shared-contract-tables.mjs
|
|
5
|
+
|
|
6
|
+
正本を直したらこの file も一緒に commit してください。CI が
|
|
7
|
+
``node scripts/generate-shared-contract-tables.mjs --check`` で照合し、ずれていれば落とします。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
#: 伏せる処理を通しても残す、相関のための属性名。
|
|
13
|
+
CLUEBASE_CORRELATION_ATTRIBUTE_KEYS: frozenset[str] = frozenset((
|
|
14
|
+
"cluebase.actor.anonymous_id",
|
|
15
|
+
"cluebase.actor.user_id",
|
|
16
|
+
"cluebase.interaction.id",
|
|
17
|
+
"cluebase.interaction_id",
|
|
18
|
+
"cluebase.organization.id",
|
|
19
|
+
"cluebase.organization_id",
|
|
20
|
+
"cluebase.origin_frontend_interaction.id",
|
|
21
|
+
"cluebase.parent_request_span_id",
|
|
22
|
+
"cluebase.parent_span_id",
|
|
23
|
+
"cluebase.request.id",
|
|
24
|
+
"cluebase.request_id",
|
|
25
|
+
"cluebase.request_span_id",
|
|
26
|
+
"cluebase.session.id",
|
|
27
|
+
"cluebase.session_id",
|
|
28
|
+
"cluebase.span_id",
|
|
29
|
+
"cluebase.subject.organization_id",
|
|
30
|
+
"cluebase.tab.id",
|
|
31
|
+
"cluebase.tab_id",
|
|
32
|
+
"cluebase.trace_id",
|
|
33
|
+
"enduser.id",
|
|
34
|
+
"mcp.client.name",
|
|
35
|
+
"mcp.client.version",
|
|
36
|
+
"mcp.session.id",
|
|
37
|
+
))
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""自動生成です。**手で編集しないでください。**
|
|
2
|
+
|
|
3
|
+
正本: packages/cluebase-shared/src/ingest/identity-pii-policy.v1.json
|
|
4
|
+
出し直す: node scripts/generate-shared-contract-tables.mjs
|
|
5
|
+
|
|
6
|
+
正本を直したらこの file も一緒に commit してください。CI が
|
|
7
|
+
``node scripts/generate-shared-contract-tables.mjs --check`` で照合し、ずれていれば落とします。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
#: 見る順序。先に当たった種類を返します。
|
|
13
|
+
PII_CATEGORY_ORDER: tuple[str, ...] = (
|
|
14
|
+
"email",
|
|
15
|
+
"ssn",
|
|
16
|
+
"phone",
|
|
17
|
+
"credit_card",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
#: 電子メールの形。
|
|
21
|
+
PII_EMAIL_PATTERN = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
|
|
22
|
+
|
|
23
|
+
#: 社会保障番号の形。
|
|
24
|
+
PII_SSN_PATTERN = "^\\d{3}-\\d{2}-\\d{4}$"
|
|
25
|
+
|
|
26
|
+
#: 電話とみなす桁数の範囲と、見た目・求める区切り。
|
|
27
|
+
PII_PHONE_MIN_DIGITS = 7
|
|
28
|
+
PII_PHONE_MAX_DIGITS = 15
|
|
29
|
+
PII_PHONE_SHAPE_PATTERN = "^[+()0-9.\\-\\s]+$"
|
|
30
|
+
PII_PHONE_SEPARATOR_PATTERN = "[+().\\-\\s]"
|
|
31
|
+
|
|
32
|
+
#: 支払い用の番号とみなす桁数の範囲と見た目。Luhn の検算を必ず通します。
|
|
33
|
+
PII_CREDIT_CARD_MIN_DIGITS = 13
|
|
34
|
+
PII_CREDIT_CARD_MAX_DIGITS = 19
|
|
35
|
+
PII_CREDIT_CARD_SHAPE_PATTERN = "^[0-9\\s\\-]+$"
|
|
36
|
+
|
|
37
|
+
#: 伏せた値の形。同じ入力なら配る部品 3 つとも同じ値になります。
|
|
38
|
+
PII_HASH_PREFIX = "cluebase_pii_hashed:"
|
|
39
|
+
PII_HASH_HEX_LENGTH = 32
|
|
40
|
+
|
|
41
|
+
#: どの面の規則か(読み手が取り違えないため)。
|
|
42
|
+
PII_SURFACE = "whole_value_is_pii"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def passes_luhn(digits: str) -> bool:
|
|
46
|
+
"""Luhn の検算。支払い用の番号らしさを、無作為な数字の並びと分けます。
|
|
47
|
+
|
|
48
|
+
**手順もここから出します。** 桁数と綴りだけを正本化して手順を手書きで
|
|
49
|
+
残すと、同じ算法が 4 か所に残ります。実際に残っていました。
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
total = 0
|
|
53
|
+
alt = False
|
|
54
|
+
for ch in reversed(digits):
|
|
55
|
+
n = int(ch)
|
|
56
|
+
if alt:
|
|
57
|
+
n *= 2
|
|
58
|
+
if n > 9:
|
|
59
|
+
n -= 9
|
|
60
|
+
total += n
|
|
61
|
+
alt = not alt
|
|
62
|
+
return total % 10 == 0
|