Skip to content
BitExactdocs
Documentation
On this page

Licensing and license provenance#

BitExact licenses are JSON documents (bitexact-license) signed with the vendor's hybrid Ed25519 + ML-DSA key (see Post-quantum below) and validated entirely offline — no network, no phone-home. BitExact is licensed use only: without a valid license the product records nothing. There is no evaluation mode. A witness is run by an auditor or a regulator, who holds no license; a key is made before there can be a license naming it.

Without a valid license only bitexact capture keygen, bitexact keygen, bitexact license, bitexact pki, bitexact recorder-key, bitexact version and bitexact witness answer. bitexact collector, bitexact mcp-proxy, bitexact recorder and bitexact serve start in any license state and check the license on each call: they pass the agents' traffic through, or declare a gap, rather than record.

bitexact mcp-proxy --replay is not on the data path: a replay is a test mode, licensed as the SDK's replay is, so it starts only under a valid license that grants replay, and exits 1 otherwise.

An expired license is different from a missing or invalid one: past expires plus grace_days the deployment is read-only. Your agents keep running: the recorder forwards their calls unrecorded, marked x-bitexact-recording: off, and an SDK run executes unrecorded - a lapse stops the evidence, never the pipeline. Read-only has one deliberate exception: the statutory acts - placing and releasing a legal hold, and redaction with its dual-control request and decision - keep working, because each is an obligation owed to a court or a data subject and a late renewal must never be why one is missed. Event delivery keeps running too: alerts, holds, redactions, refusals and purges still reach their destinations - license-expired is itself an alert, and someone must receive it - and the outbox can still be checked, listed and retried; only events test, which sends an event no fact produced, waits for renewal. Delivery after expiry reaches only the destinations the license covered: while the license is valid, each delivery round stores the names of the configured destinations, and once it has expired a destination not among them - one added after expiry - is refused. Its events are kept as dead letters reading "destination NAME was added after the license expired; it is sent to after renewal (bitexact events retry)", and bitexact events retry sends them once the license is renewed. A license that is missing or does not load delivers nothing at all: no row is sent or settled, and every one waits for a license that loads, so removing an expired license never reopens a destination it had closed. The viewer refuses its other state-changing routes, and the commands split as follows (with the entitlements the license granted):

  • Keep working: cost, coverage, events check, events deliver, events list, events reconcile, events retry, events run, events secret, events status, events verify-delivery, evidence-pack, export, forks, health, hold, list, log audit, log consistency, log heads, log status, log tail, otel-export, provenance-bundle, quickstart, receipts, redact, redaction, release-hold, retention, roles, show, strict, verify, verify-receipts, verify-report and view.
  • Refuse: anchor, archive, capture backfill, capture journal, capture policy, capture test, case, db-grant-viewer, db-transfer, drift, events test, fork-matrix, fork-matrix-compare, lint, log backfill, log checkpoint, purge, qualify, regress, reindex, repair, seal, seed, suite and workflow.
  • Unaffected in any license state: capture keygen, collector, keygen, license, mcp-proxy, pki, recorder, recorder-key, serve, version and witness.

roles keeps working on purpose: governing who may do what is not a capability a license sells, and an expired deployment must keep the ability to withdraw somebody's access - the opposite would make a lapsed renewal a security event. Evidence already recorded is never out of its owner's reach. expires plus grace_days is the end of it. A license that names no grace_days gets 14, so a lapse shows up as an hourly license_grace warning in the recorder's log, and as a license-in-grace alert in bitexact health, before recording stops; a license can name its own grace, including 0. Model traffic never stops.

Keep your last license file after it expires: the read-only state runs on it. If it is lost or damaged, or a later release can no longer validate it, ask for a read-only license - it is issued without fee.

A license naming recorder_keys binds to those keys: the deployment records only when it signs with one of them, and disabling signing does not slip the binding. One deployment per key. Copying both the license and the key to a second stack cannot be refused offline, but every sealed run commits a per-deployment fingerprint, so one license_id emitting under two fingerprints is evident in the evidence itself, and the license renews only for the deployment fingerprint it was last renewed from: a renewal request from another one is refused until you confirm with BitExact that the deployment moved.

The verifier is never part of this: it is open source, requires no license, and integrity verification never depends on any license. Licensing gates what the deployment will do; it never touches the bytes that get sealed or how they verify.

Editions and the licensed unit#

BitExact is licensed in editions - Pilot, Department and Enterprise - on an annual contract, with the license file renewed every 90 days. Each edition is sized by a band of production agent identities, never by seats, runs, events or gigabytes. Agents outside production are free, and a band changes only at renewal.

A production agent identity is a distinct agent seen recording in a production run within the trailing 90 days, the renewal period. It is counted in one place (license_usage in bitexact.license), which every surface reads, on either store:

  • The agent is the run's agent_id when it states one, else its principal, read from the run's own chain. A run that rebinds its identity counts every identity it stated.
  • Production is a run whose environment is prod or production (trimmed, any case). Runs in any other environment are listed by environment and not counted; runs that state no environment are counted apart as unlabeled.
  • The window holds every run that wrote anything in the last 90 days.
  • Unattributed runs, production runs stating neither agent_id nor principal, are reported as their own figure, and runs whose chain cannot be read are reported too, so leaving a field out moves a run to another figure and never out of the report.

bitexact license usage prints the count beside the licensed band, the window, each identity counted and every run left out and why. bitexact health and the inspector's License settings show the count beside the band. The band is the license's scope.production_agents. Over the band is a notice in those reports, never an alert and never a refusal: the count is reported, and nothing in the product reads it to stop, slow or degrade recording. The figures leave the deployment only inside the renewal request (below), never the identities themselves.

The provenance chain (anti-replay)#

Beyond casual entitlement, a license can be bound to the recorder key(s) a deployment records with, so a leaked license can't be reused and a forged one can't be made:

vendor (HSM key)  ──signs──▶  license {recorder_keys: [K], scope, expires}
                                        │ names
recorder key K    ──signs──▶  the run's checkpoints / WORM anchors
  • The license lists the customer's recorder_keys and, optionally, a scope (org / environments, and production_agents, the licensed band).

  • Every exported bundle carries the vendor-signed license as an informational license_cert sidecar (never part of the bundle signature).

  • A relying party runs the open verifier with the vendor's public key:

    bitexact-verifier run.bundle.json --vendor-key <vendor ed25519 pubkey>

    It prints an advisory line — e.g. "license LIC-9 for 'Acme Corp', scope {...} — vendor signature OK; recorder key abc123… is licensed (bound)" — and never changes the exit code. It walks vendor → licensed key → the run's own recorder signature.

Why this deters piracy without heavyweight DRM: BitExact evidence is meant to be handed to auditors and regulators. A pirated or over-deployed recorder either strips the watermark (its bundles report "license: none" — worthless to a regulator, who won't accept evidence from an unlicensed recorder) or carries a real license_id that traces straight to the leak source. A leaked license names someone else's recorder key, so the report reads "possible license replay". Forging a fresh license requires the vendor's signing key — which lives in an HSM.

Issuing licenses (vendor-side)#

Minting lives outside the shipped package (tools/issue_license.py); the wheel can validate a license but never create one. issue_license.py itself takes a key file (--key), which is the development path. For production the library function it wraps accepts a signer, and tools/hsm_sign.py is the PKCS#11 adapter that keeps the vendor private key off disk; drive issue() through that rather than the command line when minting real licenses.

python tools/issue_license.py --key vendor.key \
  --licensee "Acme Corp" --expires 2027-01-01 --tier enterprise \
  --recorder-key <customer recorder pubkey, 64 hex> \
  --entitlements record,replay,regress,fork,evidence \
  --scope-org Acme --scope-environments prod,staging \
  --scope-production-agents 40

A license must name its entitlements: one without the field, or with a malformed one, is refused, never read as granting everything. It must name the recorder keys it binds to: an unbound license records nothing. And it must carry its 90-day renewal (renews_until) and the nonce it was issued against (renewal_nonce); issue() writes both.

Onboarding a customer: they generate a recorder keypair (bitexact keygen), send you the public key, and record with it (BITEXACT_SIGNING_KEY). You issue a license naming that key. The vendor public keys are compiled into every release build before it is compiled (scripts/build_wheel.py, which refuses to build without them), and a license is checked against those keys and no other: there is no build that accepts a license the customer signed.

Renewal, every 90 days, by file#

A license is valid for its contract term, but carries a renewal date no more than 90 days ahead. Renewal is a file in each direction; the product never contacts BitExact:

bitexact license request --out renewal-request.json   # on the recorder host, as root
#   send the file to BitExact by any channel; BitExact returns a license
bitexact license install renewed.license

The request names the installed license, the recorder key and the deployment's fingerprint, carries the nonce the current license was renewed against and a fresh one, and a TPM 2.0 attestation over that fresh nonce (Linux: a quote of the IMA log, so the request proves which files of the product the operating system measured; Windows: boot measurements only), and the production agent usage figures (the count, the band, the window and the unattributed, unreadable, unlabeled and other-environment runs, never the identities). It is signed with the recorder key, both halves, so the figures cannot be changed after it is written. In house, tools/renew_license.py checks the signature, the license, the nonce chain, the deployment fingerprint against the one the license was last renewed from, and the attestation against the release's signed file list, keeps the usage figures in its renewal ledger and prints them, then issues the renewed license, which carries the new nonce, the enrolled attestation key's name (ak_name), and a TPM credential only that TPM can open (credential, with credential_digest, the sha256 of its secret - never the secret): the next request must return what it opened, which proves the attestation key lives in the same TPM. bitexact license install accepts a renewal only for the same license, and only one that follows the installed license's nonce, so an older license cannot be put back.

A missed renewal date does not stop recording: the deployment keeps recording, and says so (Standing, below). Deployment prerequisites for the attestation are in the deployment guide's "Trusted platform" section (docs/deployment.md, in the deployment bundle).

Standing#

One rule (bitexact.standing) answers where a deployment stands, and every surface reads it: the recording gate, bitexact health, /healthz, the recorder's readiness probe, and the run_meta every run commits, so a sealed run states the standing it was recorded under.

ConditionStateRecords
Licensed, renewal currentokyes
Within 14 days of renews_untilamberyes
Renewal date passed, contract currentredyes
Contract expired, inside its graceamberyes
Contract expired, past its graceredread-only
No, forged or unbound license, or another deployment's keyredno
This build's files differ from the release'sredno

"No" never stops the customer's traffic: the data path passes every call and declares the gap (license_lapsed or build_modified). The open verifier prints each run's recorded standing after its verdict, as the producer's statement and not as proof, and fails a run whose standing says it should not have recorded at all.

Integrity of the build#

Every release build lists every file it installs, with its sha256, in bitexact/_files.json, signed with the release key (a hybrid key of its own, never the vendor license key). At startup the product checks the signature against the release public key compiled into it, then every listed file, and that nothing unlisted sits in the package. A build whose files differ records nothing, passes traffic, and names the difference in its health. This detects tampering; against root on the host it cannot prevent it.

Post-quantum. The vendor key is hybrid: an Ed25519 key and an ML-DSA key beside it (vendor.key and vendor.pq.key, from bitexact keygen). The issuer signs every license with both halves (--pq-key, or <key>.pq.key beside --key), and every build compiles both public halves in (VENDOR_PUBKEY and VENDOR_PQ_PUBKEY, which the build requires). A deployment accepts only a license both halves signed, so a quantum forger who recovers the Ed25519 vendor key still cannot mint one. Name each recorder key's post-quantum half too, with --recorder-pq-key ED25519_KEY=PQ_KEY (the customer's keygen prints both): the deployment then records only with that half, and every verdict pins it.

What stays open, always#

The standalone verifier is Apache-2.0 and imports nothing from BitExact. It can report license provenance when handed a vendor public key, but it verifies the integrity of any bundle, from a licensed deployment or not, identically. That line is not negotiable: it is what lets an auditor trust the evidence without trusting, or paying, BitExact.