thin-client Intermediate

Telemetry and Privacy

Event types, sampling, the bulk endpoint, envelope fields, privacy levels, and client-side inspection — the pyvorin-native 1.0.9 telemetry system, precisely.

Published Aug 7, 2026

pyvorin-native 1.0.9 records seven kinds of event, sends them in batches to a single endpoint, and carries nothing in those batches but metadata. This page describes that system down to the field level: what each event is, how sampling decides what gets recorded, what the wire format looks like, which privacy levels exist, and exactly how to inspect the queue from your own process. Every behaviour quoted here was verified against the installed build, either by reading the telemetry core or by calling it live.

Two corrections to earlier documentation come first, because they change compliance decisions. Telemetry was never "raw source unless enabled" — no privacy level, flag or setting has ever caused source code to be collected by this package, and the phrase described an architecture that does not exist in it. And there is no pyvorin telemetry off command in either CLI; the opt-out is an in-process API call, described below.

The transport: batched, fire-and-forget

Telemetry uses a ring buffer capped at 1,000 events. There is no background thread. Events accumulate in memory until one of three triggers fires a flush: the buffer reaches 50 events, 300 seconds have passed since the last flush, or the process exits (an atexit hook drains whatever remains). Each flush sends one POST to https://api.pyvorin.com/v1/usage/events/bulk with a 5-second timeout. Failures are silently ignored — telemetry is explicitly designed so that losing it changes nothing about your program. Recording an event costs roughly a microsecond: an in-memory append under a lock, nothing more.

These properties are deliberate and worth understanding as a set. Fire-and-forget means the telemetry path can never raise into your code. No background thread means no scheduling surprises in latency-sensitive loops. Silent failure means a telemetry outage can never become your outage. The cost of that design is that delivery is not guaranteed: a process killed without atexit, or a network partition longer than the flush window, simply loses events. For a usage-analytics stream that is the right trade; for an audit log it would be disqualifying. Telemetry is not an audit log, and nothing on this page should be read as suggesting it can serve as one.

Event types and sampling

The event vocabulary is fixed and small. Verified from the telemetry core:

EventRecordedTypical parameters
compile1% sampledTiming, such as {"time_ms": 1.0}
execute1% sampledTiming
feature_used1% sampledFeature name
activationAlwaysActivation outcome
validation_failedAlwaysFailure context
tamper_detectedAlwaysTamper context
revokedAlwaysRevocation context

The sampling rule is applied at record time, per event, in the core itself: when an event named compile, execute or feature_used arrives, a hash of the event name plus the current time is taken modulo 100, and only one call in a hundred appends to the buffer. The security events bypass sampling entirely — they are the four that licensing and integrity subsystems emit when something is wrong, and a sampling scheme that could drop a tamper event would defeat their purpose. Dev mode records everything unsampled, which is one of several reasons PYVORIN_DEV_MODE=1 belongs nowhere near production.

The envelope: what a batch looks like

A captured batch, quoted verbatim from the runtime interception during verification:

{"v":"1.0","session":"9ad3dbb7-…","privacy":"standard","abuse_score":0,
 "events":[{"t":…,"e":"compile","p":{"time_ms":1.0},"s":…}]}

Field by field, from the code that builds it:

FieldContent
vEnvelope version, currently "1.0"
sessionFirst 16 characters of a UUID4, generated once per process
privacyThe active privacy level at flush time, as a string
abuse_scoreInteger computed locally; see below
events[].tUnix timestamp of the event
events[].eEvent name, from the vocabulary above
events[].pParameter dict — timings, feature names, outcome context
events[].sThe session id, repeated per event

What is not in the envelope deserves the same precision: no source code, no function names drawn from your codebase, no file paths, no argument or return values, no environment variables, no stack traces. The p field carries measurements and labels, not payloads. The session id correlates events within one process lifetime and nothing beyond it — a new process gets a new session, and there is no persistent cross-process identifier in the stream.

The abuse score is worth explaining because it sounds worse than it is. Three local counters feed it: validation_failures (incremented per licence-validation failure; contributes 1 once it exceeds 10), tamper_events (incremented per detected tamper; contributes 5 each), and compile_count (contributes 1 beyond 100,000 compiles in one process — a heuristic against abnormal hammering). All computation happens locally; the batch carries only the resulting integer. It is an anti-fraud tripwire, not a behavioural profile.

Privacy levels

Three levels exist, set per process:

LevelBehaviour
standardDefault. Sampled compile/execute/feature events plus all security events.
minimalSecurity events only: activation, validation_failed, tamper_detected, revoked.
disabledNothing is recorded; every event is dropped at the door.

The control surface is exactly one function, and it is the only opt-out that exists:

from pyvorin import _telemetry

_telemetry.set_privacy("minimal")    # security events only
_telemetry.set_privacy("disabled")   # no events recorded
print(_telemetry.get_privacy())      # confirm the active level

Verified live: the default in a fresh process is standard; after set_privacy("minimal") a recorded validation_failed event still queues while a compile event does not; after set_privacy("disabled") nothing queues at all. There is no environment variable, no config file, and no CLI subcommand for any of this. PYVORIN_TELEMETRY_URL overrides the destination URL, not the collection behaviour — setting it does not reduce what is recorded, only where it would go. One subtlety the live check exposed: set_privacy ignores unknown level strings rather than raising, so a typo in a level name silently leaves you at standard. Confirm with get_privacy() when it matters.

Where to put the call is a judgement call with a definite right answer. Set the privacy level in your application entrypoint, at start-up, before other pyvorin work; do not set it inside libraries, because a library that silently overrides the application's telemetry choice is making a policy decision it has no standing to make. If your requirement is fleet-wide silence, the code call above plus an egress block on the bulk endpoint gives you defence in depth with a verifiable backstop.

Inspecting the queue yourself

You do not have to take the envelope description on faith — the telemetry core exposes its own state, verified by calling it:

from pyvorin import _telemetry

print(_telemetry.get_diagnostics())
{'session_id': 'a68299d4-167c-48', 'privacy': 'standard',
 'queued_events': 0, 'abuse_score': 0, 'abuse_counters': {}}

get_diagnostics() returns the session id, the active privacy level, the number of queued events, the current abuse score and the local abuse counters. It is a summary, not a payload dump: it tells you what would go, not the full content of each queued event. If you need the wire format itself, the honest client-side route is to redirect the flush to a sink you control with PYVORIN_TELEMETRY_URL and read the captured POST body — that is precisely how the envelope quoted above was obtained during verification. There is no telemetry inspect or telemetry preview command in either CLI, and we would rather tell you that than invent one.

Enterprise arrangements

The default configuration sends telemetry to Pyvorin's own endpoint under the standard privacy level. If your organisation needs a different arrangement — minimal or disabled as the enforced default across your fleet, a different destination, or questions about what the platform retains server-side — that is a conversation, not a setting. Contact us through the licensing dashboard at app.pyvorin.com, identify your licence key, and state the requirement. We do not publish retention figures we cannot evidence, and we will not claim a configuration exists when it does not; what we can do is agree arrangements explicitly, in writing, for your account.

Where to go next

Last reviewed 7 August 2026 against pyvorin-native 1.0.9 installed at /root/pvfinal. Event vocabulary, sampling logic, envelope fields, privacy levels and get_diagnostics were read from the telemetry core and exercised with live calls; the quoted batch was captured by runtime interception. Server-side retention practices are outside what the artefact can prove and are not claimed here.