QVF conformance suite¶
A language-agnostic way to prove a QVF implementation is correct — for both consumers (can you read QVF right?) and producers (do you write conforming QVF?). This is what turns “two implementations that happen to agree” into “you can certify against the standard.”
What’s here¶
corpus/— a set of frozen reference archives (*.qvf) paired with*.expected.jsonfiles. Each archive exercises a part of the format (structure, the 2-D slab periodicity contract, wavefunction with the normalization contract, spectra, volumes, bands with and without a spin axis, the DOS family, Fermi surface, equation of state, phonons with eigenvectors and a projected DOS, analysis with everyatom_propertiesrole, trajectories, a periodic molecular-dynamics run, reaction paths, relaxed scans, vibrations, vendor extensions, root metadata, declared units, the typed provenance fields, a job request with every optional field, a sketch document with a reaction scheme and a preview, and a job set:provenance_full.qvf,job_spec_full.qvf,reaction_scheme.qvf,job_set.qvf; and what producers and viewers of the vibe-qc family already write or read: localized orbitals with their IAO reference basis,job.specfunctionals and bases that arenull, the deprecated spellings,bondssections that name their structure, and the viewer hintscamera,crossfadeandenergy_window, in Family data). Every canonical kind ofregistry.jsonappears in at least one of them (Coverage), and a set of edge cases — unrestricted and Cartesian wavefunctions, a 1-D wire, a variable-cell reaction path, ZIP64 and data-descriptor containers — has an archive each (Edge cases). The archives are fixed test vectors: they define the format, not any one writer, so they are committed and only change when the corpus is intentionally extended.structure_slab_2d.qvfis the one to read first if you consume periodic data. Its third lattice row is a 15.875 Å synthesized normal — the same length as a typical vacuum gap — andpbc[2]is the only thing that says it is not a cell edge. A consumer that infers periodicity from lattice geometry passes every other archive in this corpus and fails that one.negative/— the must-fail corpus: one small hand-built archive per validation rule of spec § 6 (duplicate ZIP entries, illegal paths, resource caps, reserved section ids, orphans, bad sha256, …), each paired with an*.expected.jsonnaming the finding code it must produce (spec Appendix B). See The negative corpus below.legacy/— archives the format no longer produces but that exist in the wild: today one archive stampedqvf_version: 2. They use the negative format; the one archive is valid with a warning. See The legacy corpus below.run_conformance.py— the reference runner. For each archive it validates it (schema + sha256 + semantic checks), confirms the section kinds, and decodes the members named by each check, comparing to the expected value. It then runs the negative corpus whennegative/sits beside the corpus directory, and the legacy corpus whenlegacy/does.../cpp/examples/run_conformance.cpp— the same runner for the C++ reader (CTestqvf_conformance); see The C++ runner. The JavaScript reader has its own,../js/scripts/conformance.mjs.generate_corpus.py— how the corpus was built, using the reference Python writer. Run it only to (re)generate or extend the corpus: into a scratch directory, copying only the new files, so that no frozen vector is rewritten. It writeslegacy/next to the corpus directory it is given.negative/generate_negative.py— how the negative corpus was built, withzipfileand raw bytes (the reference writer refuses to produce these archives). Run it only to (re)generate or extend the negative corpus.
Certify a consumer¶
Point the runner at the corpus:
python run_conformance.py # exits non-zero on any failure
A conforming consumer must, for every archive: open it, verify every member’s
sha256, decode the members, and reproduce the values in expected.json.
Every positive archive validates without errors; warnings are allowed and do
not fail it. updated_in_place.qvf is one that has them: it reports
W_ORPHAN_MEMBER twice by design (see Edge cases). The
expected.json check format is deliberately simple and language-agnostic, so
a C++/Rust/Julia consumer can implement the same loop:
{
"archive": "wavefunction_rhf.qvf",
"kinds": ["structure", "wavefunction.gto"],
"checks": [
{"manifest_pointer": ["source", "program"], "equals": "qvf-conformance"},
{"section": "wf", "member": "basis",
"json_pointer": ["shells", 0, "coefficients", 0], "equals": 0.318, "tol": 1e-9},
{"section": "wf", "member": "mo_coefficients",
"binary_index": [0, 0], "equals": 1.0, "tol": 1e-12}
]
}
manifest_pointer— walkmanifest.jsonby the given keys/indices.section+member+json_pointer— resolve the member’s path through the manifest, parse the JSON, then walk it.section+member+binary_index— read the binary member as a little-endian array of its declared dtype/shape and index into it.section+member+decode: "utf8"— read an opaque binary member (no dtype/shape:citations.references,run.recordinput/log) and decode the raw bytes as UTF-8 text. Compare withequals(whole text) orcontains(substring must occur).tol— compare as floats within tolerance; otherwise compare exactly. Withouttol, arrays and objects compare by value.
Coverage¶
Every kind in registry.json canonical_kinds appears in the kinds list
of at least one archive in corpus/. python/tests/test_corpus_coverage.py
enforces it, reading the kinds from the registry at test time: a kind added to
the registry fails the test until an archive exercises it, and it also checks
each kinds list against the archive’s manifest, so a typo cannot count as
coverage. A consumer that passes the corpus has therefore been shown every
kind the format defines.
An archive that exists for one kind is named for it. The kinds that were without an archive when the rule was introduced:
Archive |
Kind |
What its checks touch |
|---|---|---|
|
|
the standalone connectivity section: a double bond, a single bond, and a pair that omits |
|
|
a signed amplitude on an anisotropic |
|
|
signed spin density stored as float64 (the opt-in dtype) |
|
|
a dimensionless field in |
|
|
hartree / e, positive and negative |
|
|
dimensionless, non-negative |
|
|
the escape hatch, identified by its section |
|
|
|
|
|
Cartesian components named by |
|
|
the lower-rank shapes ( |
|
|
an opaque binary member that decodes as UTF-8, including a non-ASCII author name |
Kinds added later bring their archive with them:
Archive |
Kind |
What its checks touch |
|---|---|---|
|
|
∇²ρ in e/bohr⁵, negative near the nuclei and positive farther out; no |
|
|
a density gradient, float32 |
|
|
the hydrogen 2p orbital with m = +1, float64 |
|
|
three densities at frames 0, 2 and 4 of a five-frame trajectory: |
|
|
a sketch saved as QVF: eight species with their chemical identity on the |
The property kinds of spec § 5.13 have two archives of their own, each built from a model so that every number is derived rather than typed in:
Archive |
Kinds |
What its checks touch |
|---|---|---|
|
|
water as three point charges: the root |
|
|
a cubic CsCl-type cell: the cubic Voigt stiffness (C11, C12, C44), its compliance and the Voigt moduli; |
The negative corpus holds one archive per rule of that section:
elastic_not_symmetric.qvf, dielectric_static_mismatch.qvf and
dielectric_born_atom_count.qvf (E_MEMBER_MALFORMED) and
electric_dipole_inconsistent.qvf (E_ROOT_INCONSISTENT). Every validator,
with or without a JSON Schema engine, rejects all four.
Edge cases¶
Archives that exist because a consumer can pass every other archive and fail
one of these. Each also has expected.json checks, and
python/tests/test_corpus_edge_cases.py verifies the property independently of
the generator, by parsing the archive itself.
Archive |
What it proves |
|---|---|
|
An unrestricted |
|
The Appendix A.1 normalization contract in numbers. A |
|
The normalization contract, checked by every validator. CO with spherical s, p and d shells on C and a spherical s and a Cartesian d shell on O, carrying the AO |
|
An effective core potential and the electron count. NaH with a 10-electron ECP on Na (a local part and s and p semi-local channels with radial powers), so |
|
A 1-D periodic wire: |
|
A variable-cell periodic |
|
A periodic NPT molecular-dynamics |
|
ZIP64 structures on small members. Every ZIP64 structure is present: a ZIP64 extra field in each local header and each central-directory record (sizes and local-header offset saturated to |
|
The endpoint rule of a periodic grid (spec § 5.2). One periodic density on a hexagonal cell (non-orthogonal a and b), sampled CHGCAR style ( |
|
|
|
Streamed members. Every local header carries the data-descriptor flag (bit 3) with zero CRC and zero sizes, a descriptor follows each member’s data, and only the central directory knows the sizes; a reader must use the central directory. All members are DEFLATEd, |
|
A pending job with every optional |
|
A settled archive that uses every typed provenance field: |
|
An archive updated in place (spec § 2.3). A pending |
|
A job set halfway through (spec § 5.9). Two structures (water, and the hydroxyl radical with multiplicity 2), a |
|
|
Family data¶
Archives for the toolkit 0.3.0 decisions (GOVERNANCE.md, 2026-10-15): data
that programs of the vibe-qc family already wrote or read before the format
named it. python/tests/test_family_drift.py checks each property by parsing
the archive itself, and that the archive validates with no finding at all.
Archive |
What it proves |
|---|---|
|
Localized orbitals and their IAO reference basis (spec § 5.3). Two H₂ molecules 3 Å apart with one intrinsic bond orbital each ( |
|
|
|
The deprecated spellings are valid (spec § 5.3, § 5.9). Localized orbitals whose |
|
Bonds name their structure (spec § 5.1). Water, then H₂, and two standalone |
|
The hints |
zip64.qvf and data_descriptor.qvf hold the same calculation (water:
structure with bonds, atom_properties, an IR spectrum) with the same member
bytes as an ordinary archive; only the container differs. The properties of
updated_in_place.qvf and size_declared.qvf are verified independently by
python/tests/test_container_rules.py.
The negative corpus¶
A validator is only as good as what it rejects. Every archive in negative/
is a minimal valid archive with exactly one defect, and must be rejected with
the stable finding code (spec Appendix B) its expected.json names:
{
"archive": "compression_ratio.qvf",
"description": "A 64 KiB run of zeros deflated to well under 1 KiB ...",
"limits": {"max_ratio": 100, "ratio_exempt_bytes": 4096}, // optional
"expect": [{"code": "E_COMPRESSION_RATIO", "severity": "error"}]
}
expect— the findings the validator must report, bycodeandseverity. It may report more (a validator that runs the JSON Schema also reportsE_SCHEMAfor several of these archives), but never fewer. An expectation of a code that only the JSON Schema check reports (E_SCHEMA,E_PAYLOAD_SCHEMA) binds only a validator that runs the schema; a validator that does not (it reportsW_SCHEMA_SKIPPED) is held to the other expectations alone, soeos_model_unknown.qvf, whose only defect is a payload the schema rejects, is valid to it. The Python validator runs the schema throughjsonschemawhen it is installed; the C++ and JavaScript validators always run it, through a checker generated from the schema (scripts/gen_kinds.py), unless it is disabled.An archive passes when every expected finding is present and the verdict matches: invalid (
okfalse) when any expected finding is an error; valid (oktrue) when all of them are warnings —orphan_member.qvfis the one warning-only archive, because orphans do not invalidate an archive.member_size_mismatch.qvfdeclares a membersizethat disagrees with its ZIP entry (E_BINARY_SIZE);byte_order_big.qvfdeclaresbyte_order"big", which only the JSON Schema rejects (E_SCHEMA), so a validator without a schema engine accepts it. A duplicated entry name other thanmanifest.jsoniszip_duplicate_entry.qvf; a duplicatedmanifest.jsonis an archive updated in place and lives in the positive corpus (updated_in_place.qvf).unit_unknown.qvfexpects the warningW_UNIT_UNKNOWNand the schema-only errorE_PAYLOAD_SCHEMA: a validator without a JSON Schema engine is held to the warning alone, so the archive is valid to it.job_spec_structure_ref_wrong_kind.qvfexpectsE_REF_UNRESOLVEDfrom every validator (ajob.specpayload’sstructure_refnames abondssection), and so doesrun_record_job_ref_wrong_kind.qvf(arun.recordjob_refnames astructuresection);provenance_git_commit_not_hex.qvfandprovenance_started_utc_not_utc.qvfexpect the schema-onlyE_SCHEMA(agit_commitwith a non-hexadecimal character, astarted_utcwith a+02:00offset instead ofZ), so a validator without a JSON Schema engine accepts them, likemetadata_uuid_invalid.qvf.limits— optional overrides of the validator’s resource caps (spec § 6), keyed like the referenceLimits(max_member_bytes,max_total_bytes,max_ratio,ratio_exempt_bytes,max_json_bytes,max_json_depth). The cap archives use deliberately lowered limits so that they stay tiny: under the default limits they are valid, which proves that the cap, not the archive, is what fails. A runner passes the overrides to its validator.
run_conformance.py runs it after the positive corpus; --negative DIR
points it elsewhere and --no-negative skips it (and legacy/, below):
python run_conformance.py # Python validator
ctest --test-dir <build> -R qvf_negative_corpus # C++ validator
The reference validators report, for every archive, exactly the codes in
expect apart from the JSON Schema codes E_SCHEMA, E_PAYLOAD_SCHEMA and
W_SCHEMA_SKIPPED (the schema check also reports E_SCHEMA for several
archives that name another code), and every expected schema code is present,
so they agree code for code; python/tests/test_schema_checker_parity.py
also requires the C++ and JavaScript validators to report the same full code
set as the Python one, the schema findings at the same locations. A
third-party validator certifies the same way: run its validator over
negative/ and check expect.
The sketch-document rules have their own archives:
structure_charge_impossible.qvf (water with multiplicity 2,
E_CHARGE_MULTIPLICITY), structure_charge_provenance.qvf (the only
structure disagrees with provenance, E_ROOT_INCONSISTENT),
reaction_scheme_ref_wrong_kind.qvf (E_REF_UNRESOLVED),
reaction_scheme_step_id_duplicate.qvf and
reaction_scheme_participant_repeated.qvf (E_MEMBER_MALFORMED),
preview_signature_mismatch.qvf (JPEG bytes declared image/png,
E_MEMBER_MALFORMED) and preview_svg_not_utf8.qvf (E_UTF8), which every
validator rejects, and the schema-only reaction_scheme_energies_reserved.qvf
(a step carrying the reserved key energies, E_PAYLOAD_SCHEMA) and
preview_media_type_missing.qvf (E_SCHEMA).
The family-data rules (spec § 3.4, § 5.1, § 5.3) have their own archives:
bonds_structure_ref_unresolved.qvf and bonds_structure_ref_wrong_kind.qvf
(a standalone bonds section whose structure_ref names nothing, or a
job.spec, E_REF_UNRESOLVED), bonds_atom_out_of_range.qvf (a pair that
joins atom 2 of the two-atom structure its structure_ref names, although the
first structure has that atom, E_MEMBER_MALFORMED), reserved_id_camera.qvf
and reserved_id_crossfade.qvf (E_RESERVED_ID), which every validator
rejects, and the schema-only payload_localization_reference_basis_empty.qvf
(E_PAYLOAD_SCHEMA).
The wavefunction rules of spec § 5.3 have three archives:
wavefunction_normalization.qvf (an overlap whose d_xy diagonal is 1, three
times the Appendix A.1 value, E_NORMALIZATION), wavefunction_n_electrons.qvf
(basis.n_electrons disagreeing with the occupations) and
wavefunction_complex_rank2.qvf (complex: true with rank-2 coefficients),
both E_MEMBER_MALFORMED. All three validators implement the checks.
Archives that break only a member payload’s $defs definition (the
payload_*.qvf archives, spectrum_length_mismatch.qvf,
qtaim_point_type.qvf, scf_history_unknown_key.qvf,
unit_contradicts_fixed.qvf; spec § 5) expect nothing but
E_PAYLOAD_SCHEMA. A validator that does not run the JSON Schema reports
W_SCHEMA_SKIPPED instead and cannot produce that code (the equal-length rule
of spectrum_length_mismatch.qvf is a semantic check that reports it anyway),
so every runner (Python, C++, JavaScript) drops the schema-only expectations
for it and judges the verdict on the other codes; the archive must then
validate with no other error at all. The C++ and JavaScript runners show this
behaviour with --no-schema-check (CTest qvf_conformance_no_schema).
The legacy corpus¶
legacy/ holds archives that producers no longer write but that exist in the
wild. It uses the negative format (expect codes, optional limits) and every
runner treats it like negative/: run_conformance.py picks up a legacy/
beside the corpus directory (--legacy DIR points it elsewhere, --no-legacy
skips it) and reports it on its own line, legacy conformance: N/M archives judged as expected. An archive whose expectations are all warnings must
validate (ok true) with them.
Today it holds reaction_path_v2.qvf: a periodic reaction.path stamped
qvf_version: 2, as vibe-qc v0.10 – v0.15 wrote it before the 2026-07-10
decision withdrew that version (GOVERNANCE.md). Apart from the version and
the v2 schema_uri it is a valid v1 archive (a test restamps it as version 1
and validates it clean).
Its expectation is the warning W_VERSION_WITHDRAWN (decided 2026-10-14,
GOVERNANCE.md): spec § 3.1 and registry.json (readable_versions: [1, 2])
say consumers SHOULD read qvf_version: 2 as 1, so the validators check
the archive as version 1, the JSON Schema check included, and accept it with
the warning, which records that producers must not write that version. Any
other version is still E_VERSION (negative/version_unsupported.qvf).
Certify a producer¶
Emit the same logical calculations with your writer, then run the checks
against your archives. In practice: reproduce the inputs in generate_corpus.py
with your code, write the archives, and run run_conformance.py over your
output using the committed expected.json files. If your archives decode to the
expected values, your writer is conforming. (A conforming producer need not
reproduce the reference bytes — only the decoded contract.)
The C++ runner¶
cpp/examples/run_conformance.cpp implements the whole check format above for
the C++ reader: manifest_pointer; section + member with json_pointer,
binary_index (decoded from the member’s little-endian dtype) or decode: "utf8" with equals or contains; tol; and the negative format (expect
codes, limits), with the rules of the C++ negative-corpus test: the
validator runs the generated JSON Schema check, so every expectation binds;
--no-schema-check disables the check and drops the schema-only
expectations. It prints the same lines as the Python runner and exits
non-zero on any failure:
run_conformance CORPUS_DIR [--negative DIR] [--legacy DIR] [--no-schema-check]
ctest --test-dir <build> -R qvf_conformance # all three corpora
./<build>/run_conformance conformance/corpus --negative conformance/negative \
--legacy conformance/legacy # with the report
It exits 77 (a skipped CTest) when CORPUS_DIR is absent, as in a standalone
copy of cpp/.
In CI¶
The toolkit’s own test suite runs the reference runner over the committed
corpus and over a freshly regenerated corpus (proving the reference writer
stays a conforming producer), and runs the negative and legacy corpora through
both the Python validator and the C++ validator (CTest qvf_negative_corpus).
The C++ jobs run the C++ runner over all three corpora (CTest qvf_conformance,
and the binary itself after ctest), and the sanitizer jobs read every
positive archive with read_qvf; the JavaScript job runs its runner over them
too. Every reader opens zip64.qvf and data_descriptor.qvf. See
python/tests/test_conformance.py, test_corpus_coverage.py and
test_corpus_edge_cases.py.
The volume_spin_zero.qvf positive case contains a reference-writer 64³
float64 zero spin-density grid: a ratio over 1000 is valid. compression_ratio
is an opt-in application-policy test (max_ratio is disabled by default).
entry_size_understated and the absolute size-cap negatives guard bounded
decompression; crc_mismatch_stored and crc_mismatch_deflated reject corrupt
checksums even without an optional member SHA-256.