BKPT LabsDOCS/VIEWALYZER CLI/LICENSING VIEWALYZER · HEADLESS CLI
VIEWALYZER · CLI & AUTOMATION

Licensing

How ViewAlyzer decides what an install may do, where that state lives, and how to work on it. The implementation is the va-license crate; both fronts (the GUI and viewalyzer-cli) consume it through one LicenseManager and share the same per-user state directory, so a license, an OEM file or a cooldown seen by one is seen by the other.

Four sources of rights, in precedence order

SOURCE WHERE IT COMES FROM VERIFIED HOW NETWORK
OEM filea partner ships our standard binary plus a signed viewalyzer-oem.licenseEd25519 signature against the OEM root key compiled into the binary; expires / grace_until datesnone, ever (see below)
Retail grantactivating a license key against our license Worker (https://license.bkptlabs.com) returns a signed, machine-bound grant kept in the local vaultEd25519 signature against the retail key compiled into the binary, machine_id must be this machine, expires (about 90 days, refreshed by every successful validate)only on explicit activate / validate / deactivate, plus a silent validate at GUI launch when a key is stored
Trial granta one-time key minted in the BKPT Release Console starts a 30-day evaluation on first activationtype: trial, signed by the existing retail key, bound to this machine, and capped by the immutable trial_ends_atsame activation/validation calls as retail; never contacts Lemon Squeezy
Free tiereverything elsenone

An OEM file that grants full features outranks a retail grant. Rights are decided by the signed tokens and nothing else: the vault around the retail grant is display metadata (obfuscated, not protected); editing it changes nothing.

Trials are evaluations, not purchases

A trial has the full feature policy through the end date in its signed grant. After that date its identity remains trial, but licensed becomes false and the ordinary free-tier caps apply. Validation may re-sign the same end date; it can never extend it. A retail purchase remains perpetual and is unchanged: its rolling grant expiry is an online validation horizon, not the end of its right to run the version it owns.

The Worker permanently records the first stable machine id to activate a trial. A trial key cannot move to a second machine, and a machine that used one trial cannot activate a different trial key after reinstalling or deactivating. This is a hashed OS-install identity, not a MAC address or email. The Release Console ledger shows the admin-entered handoff note, key suffix, machine id/name, app version, activation/end dates, and last check-in. There is no anonymous install telemetry: the record begins when a user activates a key.

The free tier

One binary, no separate build: "free" is an install nothing licenses. The caps are the constants in va_license::policy::free_tier and are the ONLY place to tune them:

CAP FREE TIER APPLIES TO
capture length5 scapture, poll, the GUI capsule (auto-stop)
cooldown between captures5 sshared clock file, both fronts
task / ISR / user-function lanes10 (the earliest to appear)every recording opened or captured; user traces are never capped
symbol polling4 symbols at 100 Hzpoll, the Symbols / Registers panels

Every cap is named in the log it applied to ([headless] Free mode ... on the CLI, the Log sidebar in the GUI) and in --get-license (policy). A capture refused by the cooldown answers {"error":"cooldown_active","retry_after_s":N}.

The OEM promise

When a valid OEM file is present (any state, even expired), the app makes no unprompted network call: the GUI's startup validation is skipped and "Check for Updates" is answered locally ("Updates for this installation come from <partner>") before any URL is built, unless the signed payload carries allow_update_check: true. Absent means forbidden. The Support section of the sidebar shows what the payload's support object says, or nothing at all, because the partner's customers are not ours.

Expiry is loud at every stage: active (through expires), grace (through grace_until, full features plus a renewal notice naming the partner), expired (free-tier caps, the status line says who to contact). Discovery: $VA_LICENSE_FILE when set is the only file considered; otherwise <state dir>/viewalyzer-oem.license (what "Install License File" writes) and viewalyzer-oem.license beside the executable (on macOS also Contents/Resources and beside the .app), the latest grace_until winning.

State on disk

All under the per-user state directory (%APPDATA%\ViewAlyzer-GPUI, ~/Library/Application Support/ViewAlyzer-GPUI, $XDG_CONFIG_HOME/ViewAlyzer-GPUI; VIEWALYZER_STATE_DIR moves the whole tree):

FILE PURPOSE
license-vault.datonline retail or trial activation (key, signed grant, display fields), XOR-obfuscated JSON keyed from install-id
install-idstable per-install id; keys the vault, never hardware-derived so a transient read failure cannot lose a license
viewalyzer-oem.licensethe installed OEM file
last-capture.jsonepoch-ms of the last capture end (the cooldown clock; the length comes from the policy, so a licensed build is never throttled by a stale file)
clock-hwmthe latest time this install has seen; time-limited licenses evaluate against max(now, mark) so turning the clock back does not extend them (a mark more than 90 days ahead is treated as a broken clock and reset)

The machine id an activation binds to is a hash of the OS install id (Windows MachineGuid, macOS IOPlatformUUID, Linux /etc/machine-id); never a hostname or MAC. --get-license prints it as machine_id.

CLI

viewalyzer-cli license                       # local state + effective caps (JSON), never online
viewalyzer-cli license activate <key>        # bind this machine to a key (online)
viewalyzer-cli license validate              # refresh the grant (online)
viewalyzer-cli license deactivate            # release this machine's seat (online)
viewalyzer-cli license install <file>        # verify and install an OEM file (local)

The --headless flag spellings (--get-license, --activate-license KEY, --validate-license, --deactivate-license, --install-license FILE) are the frozen contract Studio and the SDK use. version and doctor carry the same license summary ({type, tier, licensed, partner?, expires?}); licensed is the one field to branch on.

The lifecycle verbs answer {schema_version, activated, status, licensed, tier?, error?, definitive?, support_email?, machine_id, seats_in_use?} and exit 1 when error is set. definitive: true means the server answered and said no (a human has to sort it out: support_email is set); false means nobody answered (offline: retry later, nothing was changed).

Override the server for benches with VA_LICENSE_SERVER (the signed-grant check makes a spoofed server pointless).

Developing with mock licenses

Release builds trust only the two production keys. A build with the dev-keys feature additionally trusts the key named by VA_LICENSE_DEV_PUBKEY, and --get-license then reports dev_key_active: true so a mock can never be mistaken for a real license in a support log.

cargo run -p va-license --example mint -- keygen dev.key
export VA_LICENSE_DEV_PUBKEY=$(cargo run -q -p va-license --example mint -- pubkey dev.key)

# an OEM file, active for a year with a month of grace
cargo run -p va-license --example mint -- oem dev.key --partner acme --partner-name "Acme Robotics" \
    --expires 2027-12-31 --grace-until 2028-01-31 --support-url https://acme.example/renew \
    --support-email help@acme.example --out viewalyzer-oem.license
cargo run -p va-cli --features dev-keys -- license install viewalyzer-oem.license

# a retail grant for this machine (what /v1/activate would mint)
cargo run -p va-cli --features dev-keys -- license            # read machine_id
cargo run -p va-license --example mint -- retail dev.key --machine-id <hex> --tier team3

python build_app.py run -p va-ui --features dev-keys runs the GUI with the same trust. Use VIEWALYZER_STATE_DIR to keep experiments out of your own state. Expired / grace states are one --expires away; a policy object (--policy '{"max_recording_seconds":60}') exercises partner caps.

The CLI test suites run under a mock OEM license from va_license::devkit (crates/va-cli/tests/common), so the fixture manifests describe full recordings; crates/va-cli/tests/license.rs runs in a separate process with no license and covers the free-tier gates.