Build with Verity
Everything to use Verity in your own work: the calibrated comparison REST API, the native X3P codec for Rust, Python, and R, the data catalog, and the concepts behind the numbers. For the science narrative, see the method and why Verity exists.
Overview
Verity turns a pair of 3-D surface-topography scans into a calibrated, bounded likelihood ratio — an auditable weight of evidence — with a characterized cost (Cllr) and region-level attribution. One method spans striated marks (bullet lands, toolmarks) and impressed marks (cartridge breech faces).
There are four ways in: the hosted web app at verity.codes, the REST API below, the MCP server for AI agents, and the open-source Rust/Python/R libraries. The decision always stays behind a glass-box statistical firewall — the representation only produces a score; the reportable LR is a monotone, bounded transform of it.
Quickstart
The fastest path is the hosted API. GET /health lists the calibrated mark types; POST /compare returns the report. For bullets, upload allof each bullet’s land scans — aggregating the lands is the strong path.
# Which mark types have a calibrated reference?
curl -s https://api.verity.codes/health
# Compare two bullets — upload every land scan of each.
curl -s -X POST https://api.verity.codes/compare \
-F domain=striated \
-F mark_a=@bulletA_land1.x3p -F mark_a=@bulletA_land2.x3p \
-F mark_b=@bulletB_land1.x3p -F mark_b=@bulletB_land2.x3pNo .x3p files handy? The web app’s Compare workspace lets you pick two real specimens from a curated gallery — bullets, cartridge cases, or toolmarks — and runs a real, precomputed comparison, no file of your own needed.
Core concepts
Likelihood ratio (LR).The reportable answer is not a “match” — it is how much more probable the observed similarity is if the marks share a source than if they do not. Verbal equivalents (“moderately strong support”) map directly from the LR.
Calibration & Cllr. A raw score is not evidence until it is calibrated against a namedreference of known same-source (KM) and different-source (KNM) pairs. Cllr is the cost of the calibrated LRs (lower is better; <1 is informative); the Cllr−Cllrmin gap is the calibration loss. Verity also reports an empirical cap (ELUB-inspired) — the strongest claim the reference data can support.
The calibration model. The score → LR map is a 2-parameter logistic fit (Platt scaling) by default — a monotone, bounded transform, with an optional pool-adjacent-violators (isotonic) alternative. The near-unregularized logistic on a small anchoring set gives the ELUB-style bound. Source: decision/lr.py.
Congruent Matching Regions (CMR).Verity’s domain-general similarity principle: partition a mark into regions, register each against the other mark, and count the regions that agree on one common geometry. It generalizes Song’s Congruent Matching Cells (CMC) from 2-D cells to regions of any dimension — the set of congruent regions is the attribution map.
| Modality | Region | Transform group | Reduces to |
|---|---|---|---|
| Striated | 1-D profile window | 1-D translation | Chumbley / CMS |
| Impressed | 2-D grid cell | 2-D translation + rotation | = CMC |
| Fractured | 3-D mesh patch | 3-D rigid pose | research |
Full write-up: docs/congruent-matching-regions.md
REST API
Base URL https://api.verity.codes. The API is OpenAPI-described — an interactive reference (Scalar) lives at /scalar (Swagger UI at /docs, ReDoc at /redoc).
/health{ "status": "ok", "engine_version": "0.1.0", "domains": ["impressed", "striated", "toolmark"], "rate_limiter": { "tracked_ips": 5 } }/detectscan), from striation anisotropy. The UI pre-selects it; you confirm.curl -s -X POST https://api.verity.codes/detect -F scan=@mark.x3p
# { "domain": "striated", "coherence": 0.72 }/comparedomain (striated, impressed, or toolmark), plus repeated mark_a / mark_b file fields. For striated bullets, send every land scan of each bullet; for impressed, one breech-face scan per mark; for toolmark, one striated profile scan per mark./v1/compare/report.pdf/v1/scopeResponse — the ComparisonReport
{
"domain": "striated",
"likelihood_ratio": 146.0,
"log10_lr": 2.16,
"direction": "same source",
"verbal": "moderately strong support for same source",
"lr_bound_log10": 2.16,
"log10_lr_ci_lo": 1.74, "log10_lr_ci_hi": 2.16, // 95% credible interval (clustered bootstrap)
"lr_ci_method": "bootstrap-clustered",
"score": 0.153,
"score_kind": "bullet-contrast",
"reference": { // diagnostics are the in-sample fit;
"name": "pooled bullet-land reference (Hamby-252 & 173, …)", // source-disjoint Cllr ≈ 0.19
"n_km": 146, "n_knm": 1755, "auc": 0.984, "cllr": 0.193, "cllr_min": 0.168
},
"attribution": [{ "x_frac": 0.0, "w_frac": 0.167, "corr": 0.91, "…": "…" }],
"attribution_b": [{ "x_frac": 0.008,"w_frac": 0.167, "corr": 0.91, "…": "…" }],
"previews": { "a": "[[…]] downsampled height grid", "b": "[[…]]" },
"scope_note": "A calibrated weight of evidence on the named reference. Not a
verdict: one input to an examiner's judgment, alongside case
context. Not a claim about the error rate of examination, which
remains unknown."
}likelihood_ratio | The calibrated LR — the reportable weight of evidence (LR < 1 supports different sources). |
log10_lr · lr_bound_log10 | log₁₀ of the LR, and the empirical cap — the strongest claim the reference supports. |
verbal · direction | Verbal equivalent of the LR, and which source hypothesis it favors. |
score · score_kind | The raw similarity score and which scorer produced it. |
reference | The named calibration population and its diagnostics (n_km, n_knm, auc, cllr, cllr_min). |
attribution · attribution_b | The matched regions on Mark A and Mark B — the explanation of the score. |
previews | Downsampled height grids of the rendered surfaces, for overlaying attribution. |
scope_note | An explicit statement of what the number does and does not claim. |
CORS: the API answers the web origin (configurable via VERITY_CORS_ORIGINS). Errors return a JSON detail with a 400 status for bad uploads or an uncalibrated domain.
MCP server
Verity ships a Model Context Protocol server (“verity”) so AI agents can drive the same calibrated engine as the REST API — behind the same glass-box firewall and the same scope-note guarantees.
/mcphttps://api.verity.codes/mcp — scans go in as base64, no local install.Or run it locally over stdio against any API base:
uv run --directory services/mcp verity-mcp # env VERITY_API_URLSix tools — compare_marks, detect_mark_type, calibrate_score, list_references, scorer_config, and service_health — carry the same calibration firewall and scope-note guarantees as the HTTP API. A Claude Desktop bundle builds from services/mcp/build_mcpb.sh.
Reproducibility & the glass-box API
Every /v1/compare carries a recipe— the methods section as JSON: every pipeline step and its parameters, the engine version, the SHA-256 of each input scan, and the reference’s provenance — stamped with a content handle. Same inputs + scorer config + reference + engine produce the same handle, so verifying a published likelihood ratio is a hash-equality check, not an act of trust.
{
"handle": "sha256:632d8a8b9943fb71…", // the content address of this computation
"engine_version": "0.1.0",
"scorer_config_hash": "ea4ddd513b57…",
"inputs": { "mark_a": ["8ab45f56…"], "mark_b": ["ed232b90…"] }, // SHA-256 of each scan
"reference": {
"name": "Fadul cartridge cases", "scorer_config_hash": "ea4ddd513b57…",
// in-sample reference fit, not a validation claim — the frozen public
// cartridge benchmark reads AUC 0.922 (fold mean; see /benchmark)
"diagnostics": { "n_km": 10, "n_knm": 180, "auc": 0.997, "cllr_min": 0.07 }
},
"result": { "likelihood_ratio": 10.0, "log10_lr": 1.0, "verbal": "moderate support…" },
"steps": [
{ "step": "decode", "code": "verity_x3p.read_x3p" },
{ "step": "preprocess", "code": "verity.preprocess", "params": { "lambda_s": 4e-6, "lambda_c": 250e-6 } },
{ "step": "areal-signature", "code": "verity.areal.areal_signature" },
{ "step": "compare", "code": "verity.cmr.cmr_count" },
{ "step": "calibrate", "code": "verity.decision.lr.ScoreLRModel", "params": { "lr_bound": "auto" } },
{ "step": "uncertainty", "code": "verity.decision.uncertainty.lr_credible_interval" }
]
}Every step is addressable. Upload a scan for a content handle, then chain the pipeline — each intermediate is fetchable and links to its inputs, a content-addressed graph from scan to score.
/v1/artifacts/v1/steps/{preprocess,signature,areal-signature,align,features}/v1/artifacts/{handle}/data for the raw .npy (ETag = handle) or /preview for a downsampled view.The calibration firewall. A likelihood ratio is valid only when the score was produced under the same scorer config as the reference. /v1/steps/calibrate — and a scorer_config override on /v1/compare — refuse to emit a calibrated LR when the config hashes disagree, returning the raw score with calibrated: false rather than a mis-scaled number.
/v1/scorer-config/v1/references/v1/steps/calibrateClients. Thin Python and R clients wrap the API; reproduce() re-runs a comparison and checks the handle matches.
from verity_client import VerityClient # clients/python
v = VerityClient("https://api.verity.codes")
r = v.compare("impressed", "breech_a.x3p", "breech_b.x3p")
print(r["likelihood_ratio"], r["handle"]) # the content address
# verify a published LR — a one-line hash-equality check
assert v.reproduce("impressed", "breech_a.x3p", "breech_b.x3p",
expect_handle=r["handle"])Clients: clients/
X3P codec
Verity reads and writes X3P (ISO 25178-72) — the open container for 3-D surface topography — through a single native Rust core, verity-x3p, with thin per-language bindings. A file written from any binding reads back bit-identically in every other. Heights are an (ny, nx) matrix of f64 (invalid points are NaN) with a parallel validity mask and the X3P axis/provenance metadata.
use verity_x3p::{read_x3p, write_x3p, WriteOptions};
let surface = read_x3p("scan.x3p")?; // verifies the stored MD5
println!("{} x {} points", surface.nx(), surface.ny());
write_x3p(&surface, "copy.x3p", &WriteOptions::default())?;Install: pip install verity-x3p (PyPI, abi3 wheels, Python 3.9+) or cargo add verity-x3p(crates.io) — both v0.2.0. The R binding isn’t on CRAN: R CMD INSTALL bindings/r/verityx3p from a clone, which needs a Rust toolchain. TypeScript / Swift / Java bindings are planned.
For examiners
Verity accepts 3-D surface-topography scans in X3P (ISO 25178-72). To resolve the individualizing roughness band the calibration relies on, the lateral pitch must be fine enough — ≤ 2 µm (Nyquist for the λs= 4 µm short cutoff); a scan coarser than 4 µm is refused rather than scored. Every comparison is checked against its reference’s validated domain (resolution, mark type, coverage, signal) before an LR is emitted. The result is a calibrated weight of evidence — one input to an examiner’s judgment, not a verdict, and not a claim about the error rate of examination.
Data catalog
verity-catalogis a normalized catalog + content-addressed (SHA-256) store + manifest-driven ingestion for forensic X3P scans harvested from NIST NBTRD (U.S. public domain), CSAFE-ISU’s cartridge-case scans on Figshare (CC BY 4.0), and the tmaRks toolmark set (MIT). It is local-first (SQLite + a blob directory, no external services) and scales to Postgres + object storage by setting VERITY_CATALOG_* env vars — no code change. Same-source (KM/KNM) labels fall out of the Study → Firearm → Bullet → Land → Scan hierarchy, and every scan is validated with verity-x3p on ingest.
uv run verity-catalog init-db
uv run verity-catalog ingest hamby252-barrel1-sample --limit 2 # ~2.9 MB scans from NBTRD
uv run verity-catalog infoArchitecture
Verity is a polyglot monorepo: one Rust codec core, thin language bindings, and the Python science + service stack on top.
| Package | Lang | Role |
|---|---|---|
| crates/verity-x3p | Rust | Native X3P reader/writer — the format’s single source of truth. |
| bindings/python · bindings/r | PyO3 · extendr | Thin wrappers over the core for bit-identical I/O. |
| services/engine | Python | Metrology preprocessing, registration, CMR, the calibrated-LR decision layer. |
| services/api | FastAPI | The comparison HTTP API serving the ComparisonReport. |
| services/catalog | Python | Catalog + content-addressed store + ingestion. |
| services/web | Next.js | This site and the interactive comparison UI. |
| services/mcp | Python | MCP server (“verity”) — local stdio plus the hosted endpoint at api.verity.codes/mcp. |
The firewall. A learned or hand-engineered representation produces a score; a transparent, empirically-capped calibration turns that score into the reportable LR. The report is interpretable regardless of how the score was computed — the defense against the black box.
Status & roadmap
Verity is a research preview — the engine and API are version 0.1.0. The method, the validation protocols, and the code are open and reproducible, but Verity is not a production forensic system, and its output should not be the basis of a casework or courtroom decision.
What ships today. Three calibrated mark types (striated bullet lands, impressed breech faces, striated toolmarks), each scoped to a named reference population; the monotone, empirically-capped LR firewall; the glass-box step API for auditing every intermediate; and the open benchmark with frozen, source-disjoint splits. The verity-x3p codec is published at v0.2.0 on crates.io and PyPI. Every number on this site is labeled with the protocol that produced it.
What’s ahead. Broader reference populations and cross-instrument validation, external replication through the open benchmark, versioned releases of the engine, and independent review. Follow progress on GitHub.
The pre-registered external test landed — H1 supported. The one-shot validation on untouched data was pre-registered on OSF (2026-07-01, before any data access) and run once as registered: on the independent Weller et al. (2012) cartridge-case set the frozen pipeline reached pooled Cllr 0.163 (95% CI 0.147–0.189), AUC 0.9995 — the pre-registered H1 (pooled Cllr ≤ 0.45) is supported. Protocol: docs/weller-preregistration.md.
Reference
- Interactive API reference (Scalar)
- Source on GitHub
- How the method works
- Why Verity exists
- CMR write-up
- Try a comparison
Cite Verity
Use GitHub’s “Cite this repository” button, backed by CITATION.cff. The preferred citation is the Verity whitepaper.
Licensed MIT / Apache-2.0. Verity reports a calibrated weight of evidence; it never makes the decision, and makes no claim about the error rate of forensic examination, which remains unknown.