Getting started

InvarLock evaluates one pinned baseline and one pinned subject on the same deterministically prepared schedule, applies a release-regression policy to the selected paired interval, publishes signed evidence, and lets a separate verifier replay the result.

For a first useful result without a model or container engine, run the five-minute signed-evidence check. It installs the core wheel, verifies retained evidence against independent anchors, issues a new verifier-signed receipt, and renders HTML on a regular CPU. Continue here when you are ready to produce evidence from your own model artifacts.

invarlock evaluate request.yaml
invarlock verify evidence/ ... --receipt verification.receipt.json
invarlock report evidence/

User guide

Outcome: Prepare and run a real paired Hugging Face comparison through the OCI-backed evaluate -> verify -> report path.

Audience: First-time operators, release decision owners, and engineers integrating a model build with an evidence gate.

Prerequisites: Python 3.12 or newer; Docker or Podman; a digest-addressed local InvarLock runtime image; two local SafeTensors snapshots; a pinned JSONL evaluation source; and separate Ed25519 evidence-signer and verifier keys.

To check results from an existing evaluator without preparing an OCI runtime, start with Captured results. Native requests support exact_match, normalized_nll_per_utf8_byte, and judge; this tutorial uses normalized NLL. For rubric-based grading of native answers, use the judge request workflow. The same commands and SDK support multiple metrics and slices with explicit captured assurance. This page walks through native execution and its independent receipt. For periodic or incident-triggered hosted evaluation, follow Requalify a hosted service; the capture harness supplies fresh executions and the core verifies their retained evidence.

Matching wheels and examples

For a released wheel, install invarlock and obtain its version with importlib.metadata.version("invarlock"). The root README and quickstart download the GitHub tag archive v${INVARLOCK_VERSION} and extract invarlock-${INVARLOCK_VERSION}/examples/quickstart and invarlock-${INVARLOCK_VERSION}/examples/acceptance-handoff/golden. Stop if that matching archive is unavailable; never substitute main or another version.

For a locally built wheel, use examples from the exact checkout used for the build. A package version does not identify a local commit or uncommitted source. Copy examples/quickstart/run.py and examples/acceptance-handoff/golden to an empty directory outside the checkout, clear PYTHONPATH, and run with the interpreter that installed the wheel. make quickstart-wheel-smoke automates the isolated local-build checks, including captured evaluation/recipient commands before optional add-ins are installed. Never regenerate retained evidence to make a recipe pass.

Diagram
The getting-started journey closes a request, runs the pair, publishes signed evidence, verifies it independently, renders a report, and retains the evidence and receipt without rewriting prior bytes.
The getting-started journey closes a request, runs the pair, publishes signed evidence, verifies it independently, renders a report, and retains the evidence and receipt without rewriting prior bytes.The getting-started journey closes a request, runs the pair, publishes signed evidence, verifies it independently, renders a report, and retains the evidence and receipt without rewriting prior bytes.

What each command owns

Transaction
evaluate
Reads
Closed request, JSONL source, local artifacts, caller-owned OCI/runtime inputs, evidence-signing key
Writes
One no-clobber signed evidence directory
Result
Paired comparison, interval, optional sample and side-accuracy qualification, and evaluation-time policy verdict
Transaction
verify
Reads
Untrusted evidence plus independent artifact, schedule, policy, runtime, and signer anchors; verifier identity; and verifier key
Writes
One no-clobber signed receipt outside the bundle
Result
Independent replay and acceptance/rejection record
Transaction
report
Reads
Signature-authenticated evidence
Writes
Console text and optional no-clobber HTML
Result
Summary of the canonical comparison

Run mode is the primary path. Import mode is available when another controlled execution already produced the complete provider sidecars; the final section uses the repository fixture to exercise that secondary path without model inference.

1. Install the host CLI

python -m pip install "invarlock==0.16.0"
invarlock --version
invarlock evaluate --help

The core includes the HF provider. Model execution dependencies are supplied by the runtime image, including the source-derived hardened Accelerate wheel. For host-side model preparation from a source checkout, use the repository runtime setup.

Build or obtain the runtime image from the same release you intend to run. It must be available locally under a digest-bearing reference or exact image ID. The host launcher uses --pull=never, so evaluation never substitutes an image download for caller-controlled preparation.

For a source checkout, choose the image that matches the worker device:

First create and authenticate the exact Git source archive using the runtime-image procedure. Pass its commit, path, and digest to the build target shown below.

Data table with columns: Device, Dockerfile, Build and smoke
DeviceDockerfileBuild and smoke
CPU, including the matching Apple Silicon closureruntime/Dockerfilemake runtime-image && make runtime-smoke
x86_64 NVIDIA CUDA 12.6runtime/Dockerfile.cudamake runtime-image-cuda && make runtime-smoke-cuda

Scroll horizontally to see every column.

The CUDA device flag only exposes a GPU to a CUDA-capable image. It does not add CUDA libraries to the CPU image.

For a complete journey that compares a pinned Qwen3.5-0.8B checkpoint with an explicit behavioral derivative and derives all verifier inputs independently, run the checked-in Hugging Face example. The remaining sections explain the same transaction field by field for a real release workspace.

Record the resulting sha256:... identity. A local image ID authenticates local bytes but is not a fetchable reference for another operator; use a pinned registry-manifest reference when the evidence must be reproduced elsewhere.

2. Prepare the request workspace

Use a new directory for one comparison:

The evidence destination declared in request.yaml is created beneath this directory by the host transaction. Keep the evidence-signing private key outside the tree. It remains host-only and is never mounted into a model worker.

Pin the evaluation source

Each selected JSONL line must be a JSON object. For example:

{"case_id":"release/001","prompt":"Return the capital of France.","expected":"Paris"}
{"case_id":"release/002","prompt":"Complete: 2 + 2 =","expected":"4"}

Hash the exact file bytes:

python -c 'import hashlib, pathlib; print(hashlib.sha256(pathlib.Path("release-check/inputs/release-regression.jsonl").read_bytes()).hexdigest())'

Do not sort, serialize again, or edit the file after recording the digest. Run mode preserves source order, maps the declared top-level fields, and constructs canonical invarlock/runtime-behavioral-schedule-v1 bytes. If id_field is omitted, IDs are deterministically generated as record/00000000, record/00000001, and so on. Stable source IDs are preferable for review.

Pin both HF snapshots

Derive checkpoint_tree_sha256 and tokenizer_metadata_sha256 from each exact local snapshot with the public helpers shown in Runtime providers. The tree digest rejects links, special files, and changes during traversal. The tokenizer digest binds the tokenizer contract used to interpret target text.

Normalized NLL remains comparable across authenticated tokenizer contracts because the target loss is normalized by expected-output UTF-8 bytes. When the two tokenizer digests match and each pair also has the same positive target token count, the report can add a verifier-derived token-weighted perplexity ratio as a likelihood interpretation.

Write a one-metric policy

This example permits a normalized expected-continuation NLL ratio up to 1.05:

{"resolved_policy":{"metrics":{"normalized_nll_per_utf8_byte":{"ratio_max":1.05}}}}

The verdict uses the interval upper bound, not only the point ratio. See Schedule and policy before selecting a production threshold.

3. Write the run request

Replace every illustrative digest with the value derived from the exact input. Digest fields inside HF provider settings use the exact form returned by their public helper; runtime-image identities always use the sha256: prefix.

format_version: invarlock/evaluation-request-v1
comparison:
  baseline:
    artifact:
      path: artifacts/baseline
      model_id: acme/baseline
      locator: hf://acme/baseline@0123456789abcdef0123456789abcdef01234567
    runtime:
      provider: hf_transformers
      settings:
        batch_size: 1
        checkpoint_tree_sha256: "1111111111111111111111111111111111111111111111111111111111111111"
        context_length: 2048
        immutable_revision: 0123456789abcdef0123456789abcdef01234567
        max_output_tokens: 64
        offline: true
        seed: 7
        timeout_seconds: 300
        tokenizer_metadata_sha256: "3333333333333333333333333333333333333333333333333333333333333333"
  subject:
    artifact:
      path: artifacts/subject
      model_id: acme/subject
      locator: hf://acme/subject@fedcba9876543210fedcba9876543210fedcba98
    runtime:
      provider: hf_transformers
      settings:
        batch_size: 1
        checkpoint_tree_sha256: "2222222222222222222222222222222222222222222222222222222222222222"
        context_length: 2048
        immutable_revision: fedcba9876543210fedcba9876543210fedcba98
        max_output_tokens: 64
        offline: true
        seed: 7
        timeout_seconds: 300
        tokenizer_metadata_sha256: "3333333333333333333333333333333333333333333333333333333333333333"
  dataset:
    path: inputs/release-regression.jsonl
    sha256: "4444444444444444444444444444444444444444444444444444444444444444"
    format: jsonl
    name: release-regression
    split: validation
    input_field: prompt
    expected_output_field: expected
    id_field: case_id
  policy: policy/acceptance.json
  task: text_causal
  metric: normalized_nll_per_utf8_byte
execution:
  mode: run
output:
  evidence: evidence/release-001

The request contains comparison intent, not host authorization. It cannot select an OCI engine, grant network access, name arbitrary executables, or embed private-key bytes.

4. Evaluate through OCI

From the directory containing release-check/, run:

invarlock evaluate release-check/request.yaml \
  --signing-key evidence-signer.pem \
  --baseline-runtime-image registry.example/invarlock-runtime-cuda@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  --baseline-runtime-image-digest sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  --subject-runtime-image registry.example/invarlock-runtime-cuda@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \
  --subject-runtime-image-digest sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \
  --container-engine docker \
  --baseline-runtime-device cuda:0 \
  --subject-runtime-device cuda:1

evaluate performs preflight before it starts either worker. Append --preflight --json when you want to stop at that boundary and inspect the result. It validates the request, referenced inputs, schedule, policy, runtime capabilities, signing key, output destination, OCI engine, and both local pinned images without starting a container or creating output. Rerun the same command without --preflight only when you deliberately want to continue into the evaluation.

For repeated run requests, put the image, device and resource settings in an explicit runtime profile, then use:

invarlock evaluate release-check/request.yaml \
  --signing-key evidence-signer.pem \
  --runtime-profile runtime.json --preflight

Create runtime.json using that reference and your actual pinned images before running this command. Preflight text output shows the effective settings and where they came from. Explicit CLI overrides take precedence over profile values. The profile does not contain signing keys or acceptance policy and is not accepted for import requests. Remove --preflight to execute the same resolved setup.

Use --container-engine podman when appropriate. Device values are cpu, cuda, or cuda:<index>. Shared --runtime-image, --runtime-image-digest, --runtime-device, and --runtime-entrypoint options provide defaults for both sides. Per-side options override those defaults. Workers that share a generic or identical CUDA device run sequentially; explicitly different CUDA indexes can run in parallel.

When invoked from the host, evaluate authenticates the source and prepares the canonical schedule, then launches a constrained worker for each side. Each worker receives only its artifact and support resources read-only, an isolated writable output directory, a read-only container root, and the selected CPU/CUDA resources. Networking is disabled and capabilities are dropped. The host validates both outputs, atomically publishes release-check/evidence/release-001, and signs the bundle with its host-only key.

A successful publication exits 0, prints Evidence created, and shows the recorded policy result and evidence directory. A policy failure can still be published successfully; use independent verify to gate acceptance. The bundle contains the prepared canonical schedule, both provider observations, paired records, the comparison report, runtime bindings, checksums, manifest, and evidence signature.

5. Verify with independent anchors

The verifier must obtain these values through its own trusted channel:

  • the exact policy file;
  • expected baseline and subject artifact-identity digests;
  • the expected canonical schedule digest;
  • expected baseline and subject runtime-image digests;
  • expected evidence-signer fingerprint;
  • expected normalized-request digest when either side uses llama_cpp;
  • its verifier identity and private key.
invarlock verify release-check/evidence/release-001/ \
  --policy release-check/policy/acceptance.json \
  --expected-baseline-artifact sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc \
  --expected-subject-artifact sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee \
  --expected-schedule sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
  --expected-baseline-runtime sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  --expected-subject-runtime sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \
  --expected-signer sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
  --receipt release-check/verification.receipt.json \
  --verifier-signing-key verifier.pem \
  --verifier-identity release-verifier \
  --json

Acceptance requires exit status 0, ok: true, integrity_ok: true, a passing policy verdict, the expected signer authenticity, and a valid signed receipt. An integrity-valid policy rejection may still produce a signed rejection receipt and then exit nonzero.

6. Render the report

invarlock report release-check/evidence/release-001/
invarlock report release-check/evidence/release-001/ \
  --html release-check/evidence.html \
  --explain

The report leads with Policy satisfied or Policy not met and identifies the baseline and subject. It shows both side means, the point comparison, the selected paired interval, and every configured metric, count, precision and side-accuracy check. HTML details expand to show exact values and bindings. report checks the embedded evidence signature and bundle integrity before rendering, but it does not use the independent verifier anchors or replace the signed receipt. Successful rendering exits 0 even when the recorded policy failed. Use --json for a machine-readable rendering status and HTML path; do not parse the terminal layout to decide acceptance.

Interpret the result

Native exact-match and normalized-NLL invarlock/comparison-report-v3 reports record:

  • a point estimate over all authenticated records;
  • a paired Newcombe 95% interval plus regression/improvement counts and an exact McNemar probability for exact match, or the fixed 2,048-replicate paired_percentile_bootstrap_sha256_v1 interval for normalized NLL;
  • the selected policy limit; and
  • optional record-count and interval-width qualification when the policy binds those coupled requirements;
  • optional baseline and subject accuracy qualification for exact match when the policy binds minimum_side_accuracy; and
  • a verdict controlled by the conservative interval bound and any configured sample and side-accuracy qualification.

For exact match, the lower bound must meet or exceed the percentage-point floor. For byte-normalized NLL, the upper bound must be at or below the ratio ceiling. Its interval resamples paired schedule positions, so each baseline observation stays coupled to its subject observation.

Preflight can establish that the authenticated schedule meets a configured minimum count, but interval width remains pending until execution. Strict verification also preserves v2 reports without side-accuracy qualification and v1 evidence with its original exact-match method.

Normalized NLL is teacher-forced expected-continuation likelihood regression, not a general model-quality measure. A displayed token-weighted perplexity ratio is derived interpretation only; it has no policy, interval, or verdict authority.

This describes sensitivity to paired resampling of the fixed schedule. It is not a population confidence interval, statistical-power calculation, safety claim, or proof that the schedule represents production traffic.

Secondary offline import smoke

The repository's examples/ directory contains complete synthetic provider sidecars. It makes no network calls and performs no model inference, but exercises the same evidence, signature, interval, verifier, receipt, and rendering contracts:

cd examples
python generate_keys.py --output-dir .keys
invarlock evaluate request.yaml --signing-key .keys/evidence-signer.pem

EVIDENCE_SIGNER_FINGERPRINT="$(tr -d '\n' < .keys/evidence-signer.fingerprint)"
BASELINE_ARTIFACT_DIGEST="$(python -c 'import json; print(json.load(open("trusted-inputs/input-digests.json"))["baseline_artifact"])')"
SUBJECT_ARTIFACT_DIGEST="$(python -c 'import json; print(json.load(open("trusted-inputs/input-digests.json"))["subject_artifact"])')"
SCHEDULE_DIGEST="$(python -c 'import json; print(json.load(open("trusted-inputs/input-digests.json"))["canonical_schedule"])')"
invarlock verify artifacts/evidence/ \
  --policy policy/acceptance.json \
  --expected-baseline-artifact "$BASELINE_ARTIFACT_DIGEST" \
  --expected-subject-artifact "$SUBJECT_ARTIFACT_DIGEST" \
  --expected-schedule "$SCHEDULE_DIGEST" \
  --expected-baseline-runtime sha256:1111111111111111111111111111111111111111111111111111111111111111 \
  --expected-subject-runtime sha256:2222222222222222222222222222222222222222222222222222222222222222 \
  --expected-signer "$EVIDENCE_SIGNER_FINGERPRINT" \
  --receipt verification.receipt.json \
  --verifier-signing-key .keys/verifier.pem \
  --verifier-identity local-example-verifier

invarlock report artifacts/evidence/ --html evidence.html --explain

Use this fixture to test key handling and artifact transport, not as evidence that a runtime-backed model comparison works.

Apply the transaction to a release change

The model-change workflow guide maps fine-tuned, pruned, quantized, GGUF, TensorRT-LLM, multimodal, harness, endpoint, and evidence handoff inputs to the same transaction. The runnable examples execute the maintained Hugging Face, PEFT, TorchAO, GGUF/llama.cpp, LM Evaluation Harness, and TensorRT-LLM journeys. A separate evidence-handoff fixture exercises acceptance, policy rejection, and tamper rejection without a model runtime. Artifact creation remains in the system that owns it.

Recovery and next steps

Evidence, receipt, and HTML destinations are no-clobber. Do not repair a published bundle in place. Retain a failed attempt when review policy requires it, fix the source boundary, and use a new output name.

Continue with: