Moss. DeviceId
(moss v1.1.0)
Copy Markdown
Stable, per-device identifier sourcing for MOS-14 "better tracking" parity.
This module's ONLY job is to source a stable, persisted, per-device id and
hand it to the closed core, which owns the actual /telemetry POST + buffer
- 3s flush. The SDK never touches telemetry transport.
Contract (see the canonical MOS-14 device-id spec):
- Elixir is a server/CLI platform with no OS-blessed vendor id, so the id
is a generated UUIDv4 persisted on first use (spec R1.2). There is no
Apple
identifierForVendorequivalent here. - The id is an opaque string handed through unchanged (R1.3). A blank persisted value is treated as absent and regenerated (R1.4).
- Persistence (R2.2, file-platform class): a plaintext file named exactly
.moss-device-id. When a client cache dir is known it lives at<cachePath>/.moss-device-id; otherwise it falls back to a single per-user dir so a device counts once toward Monthly Active Devices (R2.3). The per-user dir mirrors the naming intentdev.moss.sdk/ accountdevice_idfrom the Keychain platforms by using a stablemossuser-cache dir plus a.mossfallback. - The store is device-scoped and MUST NOT sync/migrate to another device (R2). A user-cache / home dir is non-synced by construction.
- Persistence failure must never break the client: on any error we fall back to a fresh ephemeral (non-persisted) UUID (R2.4).
MOSS_DISABLE_TELEMETRY(truthy set, trimmed + lowercased) is honored, checked before the memo fast-path, at runtime: when disabled we source no id, do no store I/O, and hand nothing to the core (R4).
Memoization (R3) and "apply once" tracking are held per-client in the GenServer/struct state that calls this module; this module is a pure resolver plus a best-effort apply helper.
In moss-sdks-internal the native setter EXISTS: the core exposes
IndexManager::set_device_id and SessionIndex::set_device_id, and the
Elixir NIF surfaces them as MossCore.Nif.manager_set_device_id/2 and
session_set_device_id/2. So callers pass a REAL apply_fun closure and
the id is actually handed to the core (fully functional, not inert).
apply/2 still degrades to a no-op success when telemetry is disabled or no
setter is wired (R5.4), but that is the fallback path, not the norm.
Summary
Types
Per-client device-id memo state: the resolved id and whether it was applied to the core.
Functions
Best-effort push of id to the core via apply_fun, which should call the
binding's setter NIF (setter mechanism, R5.2). Never raises (R5.3).
Resolve the device id (once, shared via state) and push it to the core via
apply_fun. No-op once applied or when telemetry is disabled (R3.2).
Per-user fallback directory for the device-id file, used when no cache_path
is available (e.g. the session API, which has no cache dir of its own).
A fresh, empty per-client memo state.
Resolve the stable per-device id persisted at <dir>/.moss-device-id.
Resolve the client's device id ONCE and memoize it on state, so every
telemetry surface a client touches (the IndexManager, ManageClient, and any
sessions) reports the same id — one device, one id (R3.1, R5.5).
True when usage telemetry is disabled via MOSS_DISABLE_TELEMETRY.
Types
Functions
Best-effort push of id to the core via apply_fun, which should call the
binding's setter NIF (setter mechanism, R5.2). Never raises (R5.3).
Returns true when the id is now settled — on success, OR when the setter is
not available in this build (telemetry disabled / no setter wired: terminal,
nothing to retry, R5.4). Returns false only when the apply raised, so the
caller may retry later (R3.3).
apply_fun is an arity-1 function returning one of:
:ok/{:ok, _}-> success:unsupported-> setter not present in this build (terminal success){:error, _}/ raises -> transient failure (retry)
In moss-sdks-internal the callers pass a REAL closure that invokes
MossCore.Nif.manager_set_device_id/2 / session_set_device_id/2, so this
actually hands the id to the core. Passing nil (no setter wired) is still
accepted and treated as :unsupported for the degrade path (R5.4).
Resolve the device id (once, shared via state) and push it to the core via
apply_fun. No-op once applied or when telemetry is disabled (R3.2).
On a transient apply failure state.applied stays false so the next call
retries rather than permanently suppressing the id (R3.3). Returns the new
state.
@spec default_dir() :: String.t()
Per-user fallback directory for the device-id file, used when no cache_path
is available (e.g. the session API, which has no cache dir of its own).
Resolves to a stable, per-user, non-synced location so a device's id is the
same across processes and surfaces — it counts once toward Monthly Active
Devices (R2.3). Uses $XDG_CACHE_HOME/moss when that env var is set,
falling back to <home>/.moss where home = $HOME -> %USERPROFILE% ->
OS home, with blank values skipped so a blank $HOME does not resolve into
the CWD (R2.2).
@spec new_state() :: state()
A fresh, empty per-client memo state.
Resolve the stable per-device id persisted at <dir>/.moss-device-id.
Reads an existing non-blank UUID, or generates and writes one. Returns nil
when telemetry is disabled (no store I/O). On any filesystem error, returns a
fresh ephemeral UUID (NOT persisted) so telemetry can still attribute within
this run — device-id persistence must never break a real operation (R2.4).
Resolve the client's device id ONCE and memoize it on state, so every
telemetry surface a client touches (the IndexManager, ManageClient, and any
sessions) reports the same id — one device, one id (R3.1, R5.5).
Persists under cache_path when a non-blank one is given, otherwise under
default_dir/0. Returns {id_or_nil, new_state}. Returns nil (without
memoizing) when telemetry is disabled, and — because the disabled-check runs
before the memo fast-path — a runtime opt-out takes effect immediately even
after an id was memoized (R3.3, R4.2).
@spec telemetry_disabled?() :: boolean()
True when usage telemetry is disabled via MOSS_DISABLE_TELEMETRY.
Truthy set (trimmed + lowercased): ["1", "true", "yes", "on"]. Checked at runtime
so toggling the env var mid-process takes effect immediately (R4.2).