Skip to content

geno_lewm.cli.verify

verify

geno-lewm-verify CLI — checksum-mode receipt verifier (RFC-0011).

The CLI takes a receipt JSON path and validates the receipt against a local manifest. Three checks compose the v1 protocol:

  1. Receipt schemaread_receipt already raises :class:ReceiptSchemaError on a malformed receipt.
  2. Manifest hash — recompute model_id from the manifest and compare against the receipt's model_id. Mismatch raises :class:ManifestHashMismatchError.
  3. Input commitment — if the caller passes --input-window plus the edit / pool / dtype flags, recompute the commitment and compare. Mismatch raises :class:InputCommitmentMismatchError.
  4. Output commitment — recompute the output commitment from the receipt's output block and compare against the stored output_commitment. Mismatch raises :class:OutputCommitmentMismatchError.

  5. Bit-exact re-inference (--rerun) — load the deploy runtime from --model-dir, re-score the receipt's input (passed via --input-window + --edit-{chrom,pos,ref,alt}), recompute the output commitment, and require it to match the receipt bit-for-bit. Mismatch raises :class:OutputCommitmentMismatchError. This proves the receipt describes output the model actually produces, not merely that the receipt is internally self-consistent.

Exit codes follow RFC-0012 / docs/spec/04-error-model.md:

  • 0 — verification passed.
  • 2 — InputError (bad CLI args).
  • 3 — ConfigError (e.g. manifest schema mismatch).
  • 8 — any ProvenanceError subclass (the main failure mode).
  • 1 — uncategorized.

verify

verify(args: Namespace, *, stream: IO[str] | None = None) -> None

Run the verification protocol; raise on any failure.

Side effect: writes a human-readable progress line per check to stream (defaults to sys.stdout resolved at call time, not at import time — so a pytest-captured stdout works without snapshot churn).

Source code in geno_lewm/cli/verify.py
def verify(args: argparse.Namespace, *, stream: IO[str] | None = None) -> None:
    """Run the verification protocol; raise on any failure.

    Side effect: writes a human-readable progress line per check to
    ``stream`` (defaults to ``sys.stdout`` resolved at call time, not
    at import time — so a pytest-captured stdout works without
    snapshot churn).
    """
    if stream is None:
        stream = sys.stdout
    print(f"reading receipt:  {args.receipt}", file=stream)
    receipt = read_receipt(args.receipt)
    print(
        f"  schema_version={receipt.schema_version} provenance.kind={receipt.provenance.kind}",
        file=stream,
    )

    print(f"reading manifest: {args.manifest}", file=stream)
    manifest = load_manifest(args.manifest)
    recomputed_model_id = manifest.model_id()
    if recomputed_model_id != receipt.model_id:
        raise ManifestHashMismatchError(
            "manifest hash does not match receipt.model_id",
            details={
                "receipt_model_id": receipt.model_id,
                "manifest_model_id": recomputed_model_id,
            },
            remediation="check that you are using the manifest the receipt was produced against",
        )
    print(f"  model_id ok ({recomputed_model_id[:23]}…)", file=stream)

    maybe_input = _maybe_recompute_input_commitment(args)
    if maybe_input is not None:
        if maybe_input != receipt.input_commitment:
            raise InputCommitmentMismatchError(
                "recomputed input commitment does not match the receipt",
                details={
                    "receipt": receipt.input_commitment,
                    "recomputed": maybe_input,
                },
            )
        print(f"  input_commitment ok ({receipt.input_commitment[:23]}…)", file=stream)
    else:
        print("  input_commitment: skipped (no input flags supplied)", file=stream)

    recomputed_output = compute_output_commitment(receipt.output)
    if recomputed_output != receipt.output_commitment:
        raise OutputCommitmentMismatchError(
            "recomputed output commitment does not match the receipt",
            details={
                "receipt": receipt.output_commitment,
                "recomputed": recomputed_output,
            },
        )
    print(f"  output_commitment ok ({recomputed_output[:23]}…)", file=stream)

    if args.rerun:
        rerun_commitment = _rerun_output_commitment(args)
        if rerun_commitment != receipt.output_commitment:
            raise OutputCommitmentMismatchError(
                "re-run inference output commitment does not match the receipt",
                details={
                    "receipt": receipt.output_commitment,
                    "rerun": rerun_commitment,
                },
                remediation=(
                    "ensure --model-dir is the exact model that produced the receipt; "
                    "a mismatch means the receipt does not describe this model's output"
                ),
            )
        print(f"  rerun output_commitment ok ({rerun_commitment[:23]}…)", file=stream)

    print("ok", file=stream)