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-reportandview. - 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,suiteandworkflow. - Unaffected in any license state:
capture keygen,collector,keygen,license,mcp-proxy,pki,recorder,recorder-key,serve,versionandwitness.
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_idwhen it states one, else itsprincipal, 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
prodorproduction(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_idnorprincipal, 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_keysand, optionally, ascope(org/environments, andproduction_agents, the licensed band).Every exported bundle carries the vendor-signed license as an informational
license_certsidecar (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.
| Condition | State | Records |
|---|---|---|
| Licensed, renewal current | ok | yes |
Within 14 days of renews_until | amber | yes |
| Renewal date passed, contract current | red | yes |
| Contract expired, inside its grace | amber | yes |
| Contract expired, past its grace | red | read-only |
| No, forged or unbound license, or another deployment's key | red | no |
| This build's files differ from the release's | red | no |
"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.