QVF governance¶
QVF (the Quantum Visualization Format) is an open standard for quantum-chemistry visualization and analysis data. This document describes how the format is stewarded, versioned, and evolved, so that adopters can rely on it as a stable, multi-vendor interchange format rather than a single code’s captive output.
Origin and stewardship¶
QVF was created by the vibe-qc project and is stewarded by it. That origin is a matter of record, not something the format hides: vibe-qc designed the container model, the section-kind vocabulary, and the reference implementations, and it maintains the specification and the JSON Schema.
Stewardship here means the vibe-qc maintainer is the current custodian of the spec and registry — merging changes, cutting versions, and arbitrating the vendor→canonical promotion process. It does not mean the format is vibe-qc-specific:
The specification and reference implementations are Apache-2.0 (this toolkit), so anyone can implement a producer or consumer, including in a proprietary code, with no obligation back to vibe-qc.
The format is producer-neutral by design:
source.programnames any code, and no canonical section kind is vibe-qc-specific. vibe-qc’s own non-standard data lives under itsx_vibeqc.*vendor namespace, exactly like any other adopter’s.Governance decisions are made in the open (spec + schema + registry are public and versioned), and the process below is the same for everyone.
As independent adopters ship QVF, stewardship is intended to broaden — see “Evolving the governance” below.
QVF in the vibe-qc family¶
Within the family of programs the steward maintains — vibe-qc (the calculations), vibe-view (the viewer), vibe-sketch (the structure and reaction sketcher), vibe-queue (the job queue) and this toolkit — QVF is the standard format. Each program reads and writes it natively, and the programs exchange data through it: what one saves, the others open, each for what it does. vibe-view shows what vibe-sketch and vibe-qc save, and vibe-sketch draws the molecules of a vibe-view or vibe-qc archive. Other formats (XYZ, PDB, CIF, SDF, cube, Molden, KET, program inputs and outputs) remain welcome as imports and exports beside it, never as the format the programs hand each other.
The family has no privilege in the format: its programs follow the same
specification and consumer rules (spec § 8) as every other producer and
consumer, put their own data in their vendor namespaces (x_vibeqc,
x_vibesketch), and bring a question the format does not answer to the
specification instead of agreeing on it privately. A gap one program meets in
another’s archive is a bug of the program or a gap of the specification, and
is fixed where it belongs.
This repository is the single source of the format (decided 2026-10-15,
decision).
The specification, the JSON Schema, the registry and the reference
validators change only here. Each program of the family pins
one qvf release tag, in one place. Any copy it vendors, such as the schema or
spec text, is byte-identical to that tag’s file, and the program’s CI checks
it against a clone of the tag. No program keeps a derived or edited schema;
that includes the withdrawn qvf_version 2, which consumers read by spec
§ 3.1 rather than with a v2 schema of their own. A field a program needs is
proposed here first and released before the program writes it.
What is under governance¶
The specification (
spec/qvf-format-spec.md) and the machine-readable JSON Schema (spec/qvf_manifest.schema.json), which is the single source of truth for the manifest contract.The registry (
registry.json) of canonical section kinds and registered vendor namespaces.The versioning policy and the vendor→canonical promotion process.
Versioning policy¶
qvf_versionis an integer in every manifest. v1 =1. It changes only on a breaking change (a removed kind, a required-member change, an incompatible unit change). A consumer that only understandsqvf_version = 1must refuse a file with a higher version.The format grows additively within a major version: new canonical kinds and new optional members may be added without bumping
qvf_version. Consumers detect capability by supported-kind checking and theextensionsblock, not by a minor-version number.The schema
$idcarries the major version (https://vibe-qc.org/spec/qvf/1/manifest.schema.json).Changes to the format are recorded in the toolkit’s
CHANGELOG.mdand reflected inregistry.json.
Version history and decisions¶
Governance decisions that affect the on-disk contract are recorded here, in the open. Recording a mistake is part of the process working, not evidence against it.
2026-10-16 — Toolkit 0.5.0 compatibility¶
Toolkit 0.5.0 packages the additive C++/C/Python APIs, optional Fortran
bindings, CLI and converters recorded below. Format version 1, schema identity,
canonical kinds and validator finding codes remain unchanged. The advisory
x_ascent namespace is included. The distribution remains qvf-writer with
legacy imports available; NumPy array APIs use the explicit numpy extra.
Consumers continue to pin a release and vendor exact files from that tag.
This release does not authorize media-type registration or schema hosting changes.
2026-10-15 — Optional Fortran binding (WP-45)¶
The Unreleased toolkit adds an optional Fortran layer over existing ABI 1. Generated raw interfaces preserve the C signatures and ownership rules; convenience writers copy column-major inputs into the existing canonical row-major layouts without changing units or scientific meaning. This is a source/API addition. Format version 1, schema, registry, validators, finding codes and generated contract tables are unchanged. Compiler/platform release coverage remains separate from the focused development checks.
2026-10-15 — Native library adoption (WP-49)¶
The Unreleased toolkit adds pkg-config metadata and explicit CMake test/example options. Embedded builds default to the library alone; standalone development retains its existing checks. Installation metadata propagates existing language and shared-library requirements and supports relocation with relative install paths. This is a packaging change: ABI 1, format version 1, validators and generated contract tables are unchanged. Package registry publication remains separate from this source change.
2026-10-15 — Initial text converters (WP-48)¶
The Python toolkit gains explicit cube and XYZ/extXYZ conversions using existing canonical structure, trajectory and scalar volume members. This is an API addition, not an on-disk contract change. Structure/trajectory lengths are angstrom; cube atoms convert from bohr while grid vectors remain bohr. Periodic cube imports require an explicit user choice and use the existing endpoint span rule; grid values are not resampled. No physical quantity, charge, multiplicity, job or scientific result is inferred. Unsupported input properties are refused; known conversion omissions are reported. Exports select an unambiguous section and preserve the original archive. The broader converter package remains in progress; these APIs are Unreleased.
2026-10-15 — ASCENT dataset namespace (#13)¶
Register x_ascent for ASCENT’s non-critical x_ascent.dataset sections.
Registration is advisory under spec § 7.3 and records an existing producer;
it does not promote a kind or change the schema, writers or finding codes.
The root extensions declaration carries version 1. Ordered records
batches and metadata retain ASCENT’s task/reference semantics, original
units and provenance. Canonical structure sections carry geometries and
molecular identity; task spin labels and dataset-wide properties are not
reinterpreted as molecular multiplicities or an archive SCF energy.
Generic consumers report the private section as unsupported and can still
open its structures. The private payload remains the producer’s contract.
2026-10-15 — Complete additive C ABI (WP-44)¶
The C binding exposes the established writer, reader, validator and editor
facades through opaque handles. ABI 1 preserves every existing C signature;
new constructors return status and an output handle. Fixed-layout tensor
inputs carry explicit dtype, shape, byte order and byte length. Nested
payloads remain JSON strings, and generated named entries cover every
canonical registry kind. Periodic structures use the existing row-vector
lattice and per-axis pbc contract. No format field or finding code changes.
Errors use fixed handle-owned storage; constructor/path errors use thread-local
storage. C++ exceptions are caught at the boundary. Borrowed strings, arrays,
callbacks and separately owned editor writer handles have documented lifetimes.
Shared builds export the API with ABI version 1; the toolkit version continues
to follow VERSION. Incompatible layouts require new types/functions or a
future ABI version. This completes the binding direction already specified in
WP-44, with no new maintainer-owned format decision.
2026-10-15 — Typed C++ writing and editing (WP-42)¶
The additive Writer facade shares the existing QvfWriter streaming
backend; the compatibility API remains supported. Canonical kind classes
and nested payload records derive from the normative schema and registry.
They use the Reader’s from_section protocol, preserve open payload fields,
and distinguish absent fields from explicit null. TensorView borrows
contiguous producer storage and converts endian/dtype in bounded chunks;
typed reads return owning tensors. Schema checking at write time complements
the complete archive validator rather than asserting cross-section semantics.
Editor append/removal follows the established update lifecycle. Atomic replacement is an explicit option: work in a private sibling directory and rename only a completed archive, preserving the original on abort or rename failure. Repack streams live members to remove superseded entries. These are library API additions; the on-disk format, schema, registry and finding-code contract are unchanged. Native verification covers every canonical kind’s typed round trip, callback/memory member equality, conversion boundaries, update rollback and Python validation of edited/repacked output.
2026-10-15 — C++ Reader and validation facades (WP-43)¶
The additive qvf::Reader API uses the existing lossless manifest model,
Tensor / DType and ZIP container. Payload access is lazy; streaming
callbacks receive borrowed little-endian chunks and must discard partial
results on a final integrity failure. CRC-32 remains mandatory, including
when the caller disables SHA-256 verification. get<K> delegates to the
kind’s from_section decoder; canonical kind classes belong to WP-42.
Validation levels are cumulative. Container and Manifest stages avoid
reading payload bytes that belong to later stages. Full validation reuses
the existing validator and remains the default for both APIs. Reports use
the same finding codes and limits. The old QvfReader signatures remain
supported. This library API addition changes no on-disk contract, schema,
registry entry, or qvf_version.
2026-10-15 — Command-line archive interoperability (WP-47)¶
The Python distribution now exposes qvf inspection, validation, extraction,
comparison, repack, scrub, conformance and schema commands. Its validation
levels share the established reference checks and follow the C++ cumulative
stage boundaries; the default full report and existing finding codes remain
unchanged. The compatibility validator command and imports are retained.
This is a toolkit interface change, with no schema, registry or format-version change. Output commands require new destinations. Scrub is an explicit, reported transformation that retains the input, removes the deprecated hostname and author contact fields, applies only caller-selected run-record patterns, and reports skipped content. It preserves job specifications, rewrites member hashes, drops orphan data and records a new archive UUID with derivation. Its scope does not change publication-scanner policy or claim complete privacy certification. NumPy and JSON Schema dependencies remain optional as before.
2026-10-15 — Python package and archive facades (WP-46)¶
The Python implementation now ships the qvf package in the existing
qvf-writer distribution, as the maintainer decided. The package API adds
streaming writes, lazy typed reads with a standard-library buffer path,
append editing, optional atomic commits and repacking. The top-level
qvf_writer and qvf_reader imports preserve their implementation module
identity and command-line behavior while emitting DeprecationWarning.
NumPy is an explicit optional extra for numeric convenience operations;
JSON Schema validation remains the validate extra. Schema and generated
kind tables are package data/source derived from the same normative inputs.
This is an additive toolkit API change. It introduces no archive fields, finding codes, version rule or schema identity. Editors preserve job specifications verbatim; binary sinks reject incomplete or oversized members before a new manifest is committed. All existing semantic validation is shared with the compatibility reader, avoiding a second set of format rules.
2026-10-15 — Distribution, schema identity and media-type preparation¶
The maintainer decided to keep the Python distribution name qvf-writer
for WP-46 and to preserve the top-level qvf_writer and qvf_reader
imports. The schema $id stays
https://vibe-qc.org/spec/qvf/1/manifest.schema.json pending hosting;
this decision does not claim that the URL is served. An IANA registration
draft for application/vnd.vibe-qc.qvf+zip
is prepared for maintainer review. Submission is not authorized and the media type remains unregistered.
2026-10-15 — Unrestricted natural transition orbitals (#12)¶
The maintainer adopted the two-section proposal for an unrestricted state’s
NTOs: one wavefunction.gto hole section and one particle section, each with
spin: "unrestricted", alpha / beta metadata and corresponding
coefficient members. Rows pair within each spin, with equal counts and
equal nonnegative, descending squared singular-value weights. Alpha and
beta may have different counts; an empty spin channel is valid. The state’s
nto_ref and the unique particle lookup are unchanged. No NTO convention
for restricted-open references is decided here.
Martin, J. Chem. Phys. 118, 4775 (2003), doi:10.1063/1.1558471, equations 4–9, describes the corresponding-orbital transformation and separately diagonal spin components. For a normalized state, the weights of both spins together sum to one. As Martin notes, full TDDFT/RPA de-excitation terms can change that sum; the existing normalized-state qualification remains. There is no normalization flag and validators do not demand a unit sum.
This is a clarification within v1 of the previously unspecified unrestricted
NTO case, using existing fields, not a new kind or version. The schema now
requires the canonical NTO state fields and transition-weight semantics
when nto_role is present. Deprecated metadata without nto_role retains
its v0.3.0 acceptance and is exempt from the new channel/pair checks; no
role is inferred from section order. Writers always emit canonical fields. Writers check channel row counts, finite nonnegative ordered
weights before mutation; validators resolve forward state/partner
references and check pair consistency. Missing or ambiguous partners and invalid
state references use E_REF_UNRESOLVED; spin, row and weight inconsistencies
use E_MEMBER_MALFORMED, at the NTO section’s members/mo_metadata.
Restricted and deprecated legacy NTO shapes keep their existing acceptance.
All three implementations share positive and negative corpus cases, including
unequal spin counts, empty beta, a normalized combined sum, non-unit TDDFT
weights and malformed pairs. No registry kind is promoted.
2026-10-15 — One source for the format; what the family already writes and reads (#11)¶
Background. By 2026-10-14 the programs of the vibe-qc family held four
schema copies that disagreed: vibe-qc vendored the schema of v0.1.0-docs.1 and
a qvf_version 2 schema derived from it, and vibe-view vendored the schema of
v0.2.0 and a frozen v2 schema. vibe-qc wrote four kinds of data that the 0.2.0
validator rejects: run against 2,000 archives written by vibe-qc 0.15–0.17,
validate_qvf failed 463 of them (23 %), for a job.spec functional of
null (336 archives), a job.spec basis of "" (143), the mo_metadata
keys excitation_index and excitation_energy_ev of natural transition
orbitals (116), and iao_charges and localization_reference_basis of
localized orbitals (60); an archive may have several. vibe-view meanwhile read
three viewer_defaults hints the specification did not record (#6), and the
format could not say which structure a standalone bonds section belongs to
(#4). qvf’s own text also disagreed about version 2: spec § 3.1 and the three
validators read a v2 archive as the current v1, while spec/legacy/README.md
and registry.json called the frozen v2 schema “a strict superset of v1”,
which holds only against v0.1.0. Toolkit 0.3.0 merges the drift into the
format, and the family takes the format from here only. Every change is
additive under v1.
qvf is the single source (see “QVF in the vibe-qc family” above). The specification, schema, registry and reference validators change only in this repository. A family program (vibe-qc, vibe-view, vibe-sketch, vibe-queue) pins one qvf release tag in one place; any copy it vendors is byte-identical to that tag’s file and checked in its CI against a clone of the tag; no program keeps a derived or edited schema, the v2 rule included. A field a program needs is proposed here first and released before the program writes it.
localization_reference_basis(spec § 5.3), a new optionalMoMetadataPayloadfield: the non-empty name of the minimal reference basis of free-atom AOs the intrinsic atomic orbitals were built from. IAOs are constructed from the occupied orbitals and such a minimal basis, and the IAOs, IAO charges and IBOs depend on the choice (G. Knizia, J. Chem. Theory Comput. 2013, 9, 4834, which compares MINAO with Huzinaga’s MINI and ANO-RCC functions); a producer names it besidelocalization_method, as vibe-qc does with"mini".Three deprecated
mo_metadatakeys (spec § 5.3), valid so that existing archives keep validating, annotated"deprecated": truein the schema asprovenance.hostnameand the legacy unit spellings were:iao_charges(canonical carrier: theatom_propertiesmemberiao_charge),excitation_index(canonical:state_indexwithstate_ref) andexcitation_energy_ev(canonical: theexcitation_energyof that state in theexcited_statessection). Producers SHOULD NOT write them.MoMetadataPayloadstays strict for every other key.nullfor ajob.specfunctionalorbasis(spec § 5.9): the method takes none (Hartree–Fock has no functional, a semiempirical or tight-binding method no basis set), distinct from an absent key, which leaves the choice to the engine like an absentmethod. The empty string is a deprecated spelling ofnull(vibe-qc 0.15.136–0.17.15 wrotebasis: ""); the schema’sanyOfmarks that branchdeprecated.The
viewer_defaultshintsenergy_window,cameraandcrossfade(spec § 3.4, #6), settled as conservatively asrenderandtransfer_functionwere (#5, 2026-10-07): recorded in the specification with the shapes and rules of vibe-view’s parser, and not typed by the schema, so that a malformed hint never fails schema conformance and no consumer refuses a file over one.energy_windowis a per-section key ofbandsanddos.*sections:[min, max]in eV relative to the Fermi level,max > min; consumers MAY accept vibe-view’s object forms.cameraandcrossfadesit at the top level, in the key space of section ids, so both are reserved as section ids besideauto_openandbookmarks(E_RESERVED_ID), and the schema lists them asviewer_defaultsmembers without constraining their values.camerahas the shape of$defs/Camera, the camera of a bookmark, which is what vibe-view applies (it also tolerates a camera with less);crossfadeis{a, b, blend}withblendfrom 0 (aalone) to 1 (balone), default 0.5, and a consumer reads a missing or malformedblendas 0.5. Validators report no finding for any of the three hints.structure_refon a standalonebondssection (spec § 5.1, #4), the generic section field of § 4.1 given its meaning forbonds: present, the indices address the atoms of thestructuresection it names; absent, the bonds belong to the archive’s first structure (§ 4.1, 2026-10-14). Producers that write several structures SHOULD set it. Validators resolve it like every section-levelstructure_ref(E_REF_UNRESOLVED) and check each pair’siandjagainst the atom count of the named structure (E_MEMBER_MALFORMED). A section withoutstructure_ref, and a structure’s ownbondsmember, are not counted, so no archive that validated before fails now unless it already named a structure its bonds do not fit. The Python and C++ writers’add_bondstakestructure_ref.x_vibesketchinregistry.jsondescribes what vibe-sketch writes since it moved its picture to the canonicalpreviewkind: the JSON membersdocument,speciesandsdf, and no vendorprevieworthumbnail.One rule for the withdrawn
qvf_version2. Spec § 3.1 stands: a v2 archive is validated against the current v1 schema and reported asW_VERSION_WITHDRAWN.spec/legacy/README.mdand the registry’sversion_decisionsnow say what the frozen file is: qvf 0.1.0’s v1 schema withqvf_version1 or 2, kept so that its$idresolves, used by no validator and no superset of the current v1 schema. Its bytes are unchanged.
Warnings. A deprecated key or spelling produces no finding, as
provenance.hostname and the legacy unit spellings do not: all three
validators accept it silently, and the schema’s deprecated annotation and
the specification carry the advice to producers.
Rationale for no bump. Every change adds an optional field, accepts a
value that was rejected, records a hint, or reserves two ids. No member, kind,
default or unit changes. The verdicts that change: the 463 archives above, and
any archive with null or deprecated spellings, become valid; an archive
with a section whose id is camera or crossfade, or with a standalone
bonds section whose structure_ref names a structure its indices do not
fit, becomes invalid, as the new meaning requires. Neither occurs among the
2,000 archives.
Compatibility. Producers: additive; vibe-qc moves to the canonical
carriers (iao_charge, state_index, null) and keeps
localization_reference_basis. Consumers: read null as “none”, "" as
null, prefer the canonical carrier of a deprecated key, and apply
bonds.structure_ref. The family re-vendors the 0.3.0 schema and drops its
own v2 schemas. Validator authors: Python, C++ and JavaScript implement the
rules with the codes and locations of Appendix B. Conformance: five archives
join the corpus (wavefunction_localized.qvf, job_set_methods.qvf,
deprecated_spellings.qvf, bonds_structure_ref.qvf, viewer_hints.qvf)
and six the negative corpus.
2026-10-14 — Section ids carry no meaning; the first structure¶
Decision. A section id names a section and nothing more; structure is a
conventional id, not a role, and a consumer MUST find sections by kind and
reference (spec § 4.1). When a consumer shows one structure, or needs one for
an operation that names none, it uses the archive’s first structure: the
first structure section that viewer_defaults.auto_open lists, else the
first structure section in sections order; it SHOULD offer the others.
Spec § 8.2 adds the consumer rule. No qvf_version bump: a
clarification.
Background. The specification never said which of several structures a
consumer shows, and a consumer took the section named structure. A
vibe-sketch document names its species structure_<species id>, as the
conformance archives multi_structure.qvf, reaction_scheme.qvf and
job_set.qvf name theirs, so that consumer showed nothing. Choosing by kind
and order needs nothing new from producers, and auto_open already existed for
a producer that wants another structure first.
Compatibility. Every archive keeps its meaning. A producer that relied on
the id structure being shown first keeps that behaviour when that section
comes first or is listed in auto_open. Consumers that looked a structure up
by id must change.
2026-10-14 — Sketch documents: chemical identity, reaction.scheme, preview, job sets¶
Background. vibe-sketch, the structure and reaction sketcher of the
vibe-qc family, saves its documents as QVF. A document holds several species,
the reactions that relate them, the calculations to run on them and a picture;
the drawing itself is Ketcher’s KET. Until now the format had a place for
none of it except the 3-D structures, so the sketcher wrote everything into a
vendor section, x_vibesketch.document, and a reader that does not know
vibe-sketch saw a heap of unrelated molecules. An earlier question, whether a
result archive may carry the sketch that produced it as an attachment, is
superseded: the sketch is now the document, and results join it. The
decisions below give the portable parts of a sketch canonical carriers. Each
is additive under v1.
x_vibesketchis registered; there is no canonical kind for 2-D drawings.registry.jsonlists the namespace (owner vibe-sketch). An editable drawing has no producer-neutral representation (KET, CDXML and molfile 2-D coordinates are each one program’s model) and one producer, so a canonical kind would freeze one program’s format. The drawing stays in the vendor section; what it means goes into the canonical carriers of decisions 2, 3 and 5, so that every reader gets the chemistry and the picture and only the editing program needs the drawing. Spec § 9 records the gap.Chemical identity on
structuresections (spec § 5.1). Optional section fieldscharge,multiplicity,name,smiles,inchiandinchikey; the genericlabelis the species’ compound label ("1","3a"). They sit on the section object, beside the biomolecule metadata, so a program lists the molecules of an archive, or searches archives by InChIKey, from the manifest alone. Absent means not stated, never neutral; a single-structure archive may still state charge and multiplicity inprovenance, as before, and then the two MUST agree (E_ROOT_INCONSISTENT). The job’s own charge stays thejob.spec’s, authoritative for the calculation and allowed to differ (a vertical ionization). Validators check that a stated charge and multiplicity are possible for the atoms — a non-negative electron count, and unpaired electrons no more than it and of its parity — with the new errorE_CHARGE_MULTIPLICITY. They do not compare line notations with atoms, which needs a cheminformatics toolkit; the spec says the identifiers describe the atoms and imply no atom order. Only the new fields are checked, so no existing archive changes verdict (ajob.specwith an impossible default multiplicity, for example, stays unchecked).reaction.scheme, a canonical kind (spec § 5.14), added directly by maintainer decision (Change process: a clear ecosystem need and a reference consumer). One strict JSON payload:stepsin reading order, each with anid,reactantsandproducts(at least one each), optionalreagents(species drawn at the arrow),arrow(forward, the default,equilibrium,retrosynthetic,resonance),conditions(the text at the arrow, one entry per line),yield_percent(0–100) andyield_text(as printed). A participant is{structure_ref, coefficient?}naming astructuresection; the coefficient makes the step a balanced equation, which reaction energies will need. Roles are chemical, not positional:reactantsare consumed in the forward direction whatever the arrow, so the target of a retrosynthetic step is among itsproductsand a producer that records the drawing by arrow side swaps the two for such a step; a quantity of a step is then always products minus reactants. The step keyenergiesis reserved for reaction energies, to be specified when a producer computes them; until then it is a schema error and producers usex_keys. Conditions stay text: structured temperature, time or solvent fields are additive later. A species without a 3-D structure cannot be a participant in v1. Validators resolve everystructure_ref(E_REF_UNRESOLVED) and reject duplicate step ids and a structure repeated within a role (E_MEMBER_MALFORMED).Job sets (spec § 5.8, § 5.9). An archive MAY carry several
job.specsections; each is an independent job.structure_refonjob.specalready existed twice — the generic section field of § 4.1 and the payload field of § 5.9 — and a job without one in an archive of several structures was already ambiguous and refused; the section-level field is now the preferred carrier, because a consumer reads it from the manifest, and the reference writer writes both. What was missing is the link from a run to its job:run.recordgainsjob_ref(resolved to ajob.specby validators) andrun_status(running,converged,failed). A job is pending until a run record names it, result sections carryrun_refandstructure_ref, andprovenance.run_statussummarises the set (pending,running, thenconvergedorfailed). Jobs do not depend on each other; a sequence within one job stays itstaskslist. A single-job archive reads exactly as before.preview, a canonical kind, and the member fieldmedia_type(spec § 4.2, § 5.15), added directly by maintainer decision. Every member spec may carrymedia_type, the IANA media type of its bytes; it describes and never changes how a member is read. Apreviewsection has a required rasterthumbnail(image/pngorimage/jpeg) and an optionalimage(image/svg+xml, PNG or JPEG), both opaque members whosemedia_typeis required, so a file manager reads the type from the manifest and never sniffs. The archive’s picture is the thumbnail of the firstpreviewsection; its longest side should be 256–1024 px and the member at most 1 MiB. SVG must be self-contained and inert (no external resources, no scripts), and consumers must render it without running scripts or fetching anything. A fixed entry name such as an office document’sThumbnails/thumbnail.pngwas rejected: QVF names every member from the manifest (§ 2), and a thumbnail extractor reads a stored manifest as cheaply as a fixed entry. Vendor sections may now carry opaque members too, which is how vibe-sketch can store a PNG without pretending it is auint8array. Validators check the bytes against the media type (E_MEMBER_MALFORMED,E_UTF8).One media type and one macOS UTI (spec § 2.4).
.qvfisapplication/vnd.vibe-qc.qvf+zip— the IANA vendor tree, named for the steward, with the registered+zipsuffix — andcom.vibe-qc.qvfon macOS, conforming topublic.zip-archiveandpublic.content.registry.jsoncarries the values underfile_type, andspec/file-type/the declarations every application copies unchanged: the UTI dictionary forInfo.plistand the freedesktop.org shared-mime-info XML. One application of the family exports the UTI and the others import the identical declaration. The media type is not yet registered with IANA; registration is a follow-up for the steward and changes nothing in the files.Reference writer API (informative; no format change). Python
add_*methods all takelabel=;add_structuretakes the identity fields;add_reaction_scheme,add_previewandopaque_members=are new.remove_sectionremoves a section, and removing and adding under the same id replaces it;open_for_updatetakes an archive’s path or bytes, and a member whose default path the archive already holds gets a fresh one, since an update never rewrites an entry (§ 2.3).repack=Trueonwriteandto_bytesdrops superseded entries, which a program that saves the same document repeatedly needs.Three inconsistencies. (a) The
Extensionsschema description said keys come “without x_” while the pattern required the prefix; it was corrected on 2026-10-07 (keys carryx_, as spec § 7.4 and the corpus do), after the 0.1.0 release that vibe-sketch pins. (b) The schema called itself “(v1.1)” whileqvf_versionisconst 1; the label was removed in the same revision, since v1 has no minor version. (c) The validators rejectedqvf_version: 2withE_VERSIONwhile spec § 3.1 andregistry.json(readable_versions: [1, 2]) told consumers to accept it; the conformance README recorded this as a decision pending. Decided: a validator reads2as1for every check, the JSON Schema check included, and reports the new warningW_VERSION_WITHDRAWN, so such an archive is valid and the warning still records that its producer wrote a version producers must not write. A validator answers whether an archive can be read correctly, and this one can. The schema keepsqvf_version: const 1, the producer contract;E_VERSIONremains for every other value; a tool that updates such an archive writes1. The legacy corpus expects the warning.
Rationale for no bump. Every change adds an optional field, a canonical
kind or a warning, or relaxes a recommendation (SHOULD carry exactly one job.spec). No existing member, kind, default or unit changes. Every archive
that validated before validates unchanged; the one archive whose verdict
changes, the legacy qvf_version: 2 archive, goes from invalid to valid with
a warning.
Compatibility. Producers: additive. vibe-sketch can keep its vendor
section and add the canonical carriers: identity on each structure_<species id> section, one reaction.scheme, one preview (its PNG and SVG move from
the vendor preview member), and job sets with job_ref when it writes
calculations; the one-species archives it writes for vibe-view keep charge
and multiplicity in provenance and may add them to the structure, equal.
Consumers that do not know the new kinds list them as unsupported, as for
any kind; a consumer that reads charge and multiplicity should prefer the
structure’s and fall back to provenance for a single-structure archive.
Validator authors: the rules above; Python, C++ and JavaScript implement them
with the codes of Appendix B. Conformance: reaction_scheme.qvf and
job_set.qvf join the corpus, ten archives the negative corpus.
2026-10-08 — wavefunction.bloch: k-resolved Bloch orbitals, promoted provisionally¶
Decision. A canonical kind wavefunction.bloch (spec § 5.3.1)
carries the k-resolved crystalline orbitals of a periodic system over an
atom-centred Gaussian basis. Its members: basis, the wavefunction.gto
payload ($defs/BasisPayload, unchanged); kpoints, strict
$defs/BlochKpointsPayload (kpoints in fractional reciprocal-lattice
coordinates, required; optional weights summing to 1 within 1e-6, labels,
mesh, shift, time_reversal, units); mo_metadata, strict
$defs/BlochMoMetadataPayload (spin restricted or unrestricted,
n_spin, n_mo, n_ao, complex (always true) and k_point_convention
(fractional) required; orbital_kind, n_mo_per_k, fermi_energy and
units optional); mo_coefficients, float64 [n_k, n_spin, n_mo, n_ao, 2]
with the last axis (real, imaginary); energies, float64 [n_k, n_spin, n_mo] in hartree; occupations, float64 [n_k, n_spin, n_mo], electrons per
orbital at each k-point. The spec states the Bloch sum
φ_μk(r) = N^(−1/2) Σ_T e^{+ik·T} χ_μ(r − T), with k fractional
(k·T = 2π k·n), the phase e^{+ik·T} without the atomic position, and the AO
order and primitive normalization of Appendix A. The three reference
validators check that the member shapes agree with each other and with
mo_metadata and basis.n_ao, that the weights sum to 1 and the labels
and n_mo_per_k have one entry per k-point, and that the section’s structure
(the section-level structure_ref, else basis.structure_ref) is a
structure section with at least one periodic axis, reporting
E_MEMBER_MALFORMED and, for an unresolved basis.structure_ref,
E_REF_UNRESOLVED; no finding code is added. When an archive also carries
bands at the same k-points, the band energies SHOULD agree with
energies; nothing checks that. The conformance corpus gains bloch.qvf, the
negative corpus bloch_weights_sum.qvf,
bloch_coefficients_trailing_axis.qvf, bloch_occupations_shape.qvf and
bloch_structure_molecular.qvf. No qvf_version bump.
Promoted ahead of the threshold, and provisional. The kind is the shape of
the vendor section x_vibeqc.bloch_wavefunction, which one producer
(vibe-qc) writes. The promotion rules ask for two independent producers; the
maintainer promoted it before a second one ships (format review P15, decision
5), as spectra.epr was, so that periodic Gaussian codes (CP2K, CRYSTAL, a
PySCF-PBC adapter) have a canonical target instead of each inventing a vendor
layout. Its registry.json promotions entry says
"from": "x_vibeqc.bloch_wavefunction" and "status": "provisional". A
provisional kind is canonical in every respect: producers may emit it,
validators check it, consumers may rely on it, and any revision it needs
before it becomes final is additive, as for every canonical kind. It becomes
final, and the status is removed, when a second independent producer ships
it. The other open promotion criterion is a reference consumer: vibe-view
renders wavefunction.gto orbitals at Γ with a lattice sum and refuses other
k-points because the older metadata had no declared k convention, which this
kind now declares.
Vendor mapping. The vendor section’s restricted coefficient payload
(schema_version 1.0) maps onto the kind with the same numbers and the same
conventions, so the producer can switch kinds without a data change:
|
|
|---|---|
member |
member |
binary member |
|
|
|
|
the position along axis 0, and its length |
per-k members |
|
per-k members |
|
per-k members |
|
|
|
|
|
|
|
|
not needed; a producer may keep them as |
The vendor writer’s Bloch phase is the e^{+ik·T} of the spec, its k-points are
fractional (wrapped to [−1/2, 1/2)), and it divides engine coefficients by
N_i as for wavefunction.gto. New in the canonical kind: a second spin
channel, labels, mesh, shift, time_reversal and fermi_energy. Not
carried: the vendor’s spin-density restart payload (schema_version 1.1,
restart_kind: "spin_density_bloch_kpoints", members
density_alpha_k<k> and density_beta_k<k>, float64 [n_ao, n_ao, 2]),
which holds AO density matrices and no orbitals; it stays in the vendor
namespace until k-resolved density matrices get a canonical form. Two choices
go beyond the sketch of P15: n_mo_per_k, without which the vendor’s
per-k-point orbital counts (canonical orthogonalisation removes a different
number of near-linear-dependent combinations at each k) could not be carried
without loss, and the k-point axis ahead of the spin axis, which keeps the
vendor’s per-k blocks contiguous and differs from the leading spin axis of
§ 5.6 on purpose.
Rationale for no bump. A new canonical kind with new payload definitions is the additive case of “Change process”: no existing member, kind, default or unit changes, and every archive that validated before validates unchanged.
Compatibility. Producers: purely additive. The reference writers gain
Python add_wavefunction_bloch (which shares the shell normalization of
add_wavefunction_gto through a helper), C++ WavefunctionBloch and
add_wavefunction_bloch, and the C ABI qvf_add_wavefunction_bloch; the C
ABI still writes molecular structures only, so a C caller cannot yet write
the periodic structure the kind needs. Consumers that do not support the kind
treat it per the unsupported-kind rules. A consumer that evaluates the
orbitals MUST use the stated Bloch sum and MUST NOT assume a gauge.
Validator authors: the rules above, with existing codes.
2026-10-08 — Property kinds: properties.electric, properties.elastic, properties.dielectric¶
Decision. Three canonical kinds, added directly (spec § 5.13, “Property
kinds”), each with one required JSON member properties whose payload is
strict (only the listed keys and x_ keys, in every nested object).
(1) properties.electric ($defs/ElectricPropertiesPayload): origin (a
conventional name or a 3-vector in bohr, as dipole_moment.origin), dipole
(vector, total, units; debye), quadrupole (matrix, traceless
and convention buckingham or cartesian, all required; debye·Å),
octupole (tensor, 27 numbers in row-major [3, 3, 3], optional
convention; debye·Å²), polarizability (matrix, frequency_ev,
isotropic; atomic units), hyperpolarizability (tensor,
frequencies_ev; atomic units) and units; at least one of the five
blocks is required. The root dipole_moment stays the canonical place for a
bare dipole. When both it and an electric dipole are present they MUST
agree within 1e-6 debye (vector with vector_debye, total with
total_debye); a validator reports a disagreement as the existing code
E_ROOT_INCONSISTENT, located at the section’s properties member.
(2) properties.elastic ($defs/ElasticPropertiesPayload): stiffness
(6 × 6, Voigt order xx yy zz yz xz xy, GPa, required), compliance
(6 × 6, GPa⁻¹), bulk_modulus_gpa, shear_modulus_gpa,
youngs_modulus_gpa, poisson_ratio, average (voigt, reuss, hill)
and units. Both matrices MUST be symmetric: entries [I][J] and [J][I]
differ by at most 1e-6 times the largest absolute entry. (3)
properties.dielectric ($defs/DielectricPropertiesPayload):
electronic (3 × 3, required), ionic, static (3 × 3, dimensionless),
born_charges (one 3 × 3 per atom, e), piezoelectric (3 × 6, C/m²)
and units. When electronic, ionic and static are all present, static
MUST equal electronic + ionic entry by entry within 1e-6, and
born_charges MUST have one tensor per atom of the section’s structure
(the section-level structure_ref, else the archive’s only structure) when
that resolves. The elastic and dielectric violations are
E_MEMBER_MALFORMED. Spec § 4.3 gains unit rows for the fields, § 6 and
Appendix B gain the rules, and the schema gains the three section branches,
the three payload definitions and the shape helpers Matrix3x3, Matrix3x6,
Matrix6x6 and Tensor3x3x3. The conformance corpus gains
properties_molecular.qvf and properties_periodic.qvf, the negative corpus
elastic_not_symmetric.qvf, dielectric_static_mismatch.qvf,
dielectric_born_atom_count.qvf and electric_dipole_inconsistent.qvf. No
qvf_version bump.
Background. Format review P14 named properties.elastic and
properties.dielectric as demand-driven kinds and P17 properties.electric,
so that a periodic-systems code had no schema-valid place for its stiffness
matrix, dielectric response or Born charges, and no code could carry a
quadrupole or a polarizability beside the dipole of the root block.
Rationale. All three kinds are added directly by maintainer decision under
the change process (“clear ecosystem need exists and a reference consumer will
render it”), as forces and hessian were, and are noted in registry.json.
A new kind with a payload that is strict from the start is the additive case:
nothing that was valid becomes invalid, because no valid archive carried these
kinds before. Strictness can be loosened later without a break; the reverse
could not.
Units that the vocabulary cannot name. The units object of each payload
declares only the fields whose unit has a name in § 4.3 (the dipole, origin,
the stiffness and moduli, Poisson’s ratio, the dielectric tensors and the Born
charges), and a declaration only restates the fixed unit. The quadrupole
(debye·Å), octupole (debye·Å²), polarizabilities (atomic units), compliance
(GPa⁻¹) and piezoelectric tensor (C/m²) are in compound units that the
vocabulary does not name (spec § 9, item 7), so they are fixed by § 5.13 alone
and cannot be declared. This follows the earlier record on the unit
vocabulary.
Readings to confirm. (a) E_ROOT_INCONSISTENT, defined for two root
blocks, now also covers a section that restates a root block; the code is not
renamed and Appendix B says so. (b) The tolerances differ by quantity: matrix
symmetry is relative to the largest entry (so a zero beside a 1e-9 entry is
not an error), while static against electronic + ionic and the dipole are
absolute (1e-6, a dimensionless number and debye). (c) P17 names a
quadrupole convention and a traceless flag but not their definitions; § 5.13
defines buckingham as ½ Σ q (3 r_a r_b − r² δ_ab), which is always
traceless, and cartesian as Σ q r_a r_b, with its trace removed when
traceless is true. The octupole gets the same convention pair, an
addition to P17’s sketch, with cartesian as its default, and its unit
(debye·Å²) is the one P17 left open. (d) average is optional and says “not
stated” when absent; a producer SHOULD give it whenever it writes a modulus.
(e) The piezoelectric tensor is the relaxed-ion stress tensor. (f) Two
consistency rules are not checked: polarizability.isotropic against
one third of the trace, and dipole.total against the length of
dipole.vector. (g) The section is numbered § 5.13 so that § 5.12 stays free
for work in parallel; a later renumbering is a documentation change.
Compatibility. Producers: purely additive. The reference writers gain
Python add_electric_properties(properties), add_elastic_properties(stiffness, compliance=, moduli=, average=, units=) and add_dielectric_properties( electronic, ionic=, static=, born_charges=, piezoelectric=, units=), C++
add_electric_properties, add_elastic_properties and
add_dielectric_properties (each takes the payload as JSON and checks it), and
the C ABI qvf_add_electric_properties_json,
qvf_add_elastic_properties_json and qvf_add_dielectric_properties_json.
Consumers that do not support the kinds treat them per the unsupported-kind
rules. Validator authors: add the three kinds, the three payload definitions
and the rules of § 6; no code is added.
2026-10-08 — volume.vector, volume.complex, volume.series and volume.laplacian; periodic grid semantics¶
Decision. Two additions under v1. (1) The volume grid payload
($defs/VolumeGridPayload, spec § 5.2) gains three optional fields:
periodic (three booleans, absent [false, false, false]),
includes_endpoint (boolean, absent true) and structure_ref. Along
an axis a that periodic marks, the grid samples exactly one period of the
cell of its structure (the section-level structure_ref, else the grid’s,
else the archive’s only structure): with includes_endpoint true, the
cube-file convention, the last plane repeats the first and
voxel_vectors[a] × (shape[a] − 1) is lattice vector a; with it false, the
CHGCAR convention, voxel_vectors[a] × shape[a] is. A consumer that tiles or
wraps a periodic grid MUST drop the duplicated plane when
includes_endpoint is true. Spec § 5.2 is rewritten around a precise
statement of the point positions and a one-axis, four-point worked example of
both conventions. All three reference validators check, with
E_MEMBER_MALFORMED at the grid member, that a periodic axis is periodic in
the structure (pbc[a]) and that its span equals the lattice row converted
to bohr within 10⁻⁶ relative, and report a grid structure_ref that names no
structure section as E_REF_UNRESOLVED. (2) Four canonical kinds:
volume.vector (data float32/64 [nx, ny, nz, 3], Cartesian
components, $defs/Volume4DVectorBinary), volume.complex
([nx, ny, nz, 2], last axis [re, im], $defs/Volume4DComplexBinary),
volume.series ([n, nx, ny, nz] with a required strict axis member,
$defs/VolumeSeriesAxisPayload: kind time / frame / state / custom,
values one per slab, units, labels, trajectory_ref) and
volume.laplacian (∇²ρ, [nx, ny, nz], in e/bohr⁵, a unit § 4.3 now
fixes in prose because the vocabulary has no name for it). Validators check
the rank and trailing axis of each new kind and a series axis against its
slabs (E_MEMBER_MALFORMED), and resolve axis.trajectory_ref to a
trajectory or reaction.path section (E_REF_UNRESOLVED); a series axis
units that is not a unit name is W_UNIT_UNKNOWN. The frame_volumes +
volume_grid members of reaction.path are documented (§ 5.5) as the older
form of a volume.series with a frame axis naming the path; producers
SHOULD prefer volume.series, and both stay valid. The conformance corpus
gains volume_periodic.qvf, volume_vector.qvf, volume_complex.qvf,
volume_series.qvf and volume_laplacian.qvf; the negative corpus
volume_periodic_span.qvf, volume_endpoint_count.qvf,
volume_series_axis_length.qvf and volume_vector_trailing_axis.qvf. No
qvf_version bump.
Background: a silent interoperability bug. Format review P11. A grid that
samples a periodic cell is written in one of two conventions that agree on
every point position and disagree on what a period is. Gaussian cube files
include the endpoint: n points with the last one the image of the first, so
n − 1 steps per period. VASP CHGCAR files do not: n distinct points per
period. Both are in wide use, and nothing in the numbers of a grid says which
one it is, because the voxel vectors give the positions either way. A
consumer that guesses wrong renders without an error message: it tiles a
cube-style grid with a doubled plane at every cell boundary and a period one
step too long, or drops a real plane of a CHGCAR-style grid; a converter that
reads one convention and writes the other shifts every point. QVF v1 said
nothing about it. includes_endpoint makes the convention explicit, and the
span check, which needs nothing but the grid and the structure’s cell, turns
a mislabelled grid from a silently wrong picture into a validation error.
The new kinds give a canonical home to fields that producers wrote as
volume.generic or not at all: density gradients and other vector fields,
complex orbitals and Bloch functions, and fields along a trajectory, in time
or by state, of which reaction.path frame_volumes covered only the first.
Rationale. The four kinds are added directly by maintainer decision
under the change process (“clear ecosystem need exists and a reference
consumer will render it”), as run.record, job.spec, forces and
hessian were, and are noted in registry.json. New optional payload
fields, new canonical kinds and validator rules that only constrain the new
fields are the additive case: every archive that was valid stays valid,
because no grid declared periodic before this record and every rule of
spec § 5.2 is conditional on it or on a new kind. includes_endpoint
defaults to true, the cube-file convention, as P11 specifies; it matters
only on a periodic axis, and no grid declared one before this record, so the
default changes the meaning of no existing archive.
Readings to confirm. The brief (P11) leaves three details open, settled
here and open to revision by the maintainer before the next release. First,
the span check is applied to every grid payload, the volume_grid of a
reaction.path included, whenever the grid declares a periodic axis and its
structure resolves; a variable-cell path whose volumes cannot share one cell
should therefore not declare periodic. Second, a series trajectory_ref is
resolved whatever the axis kind, not only for frame axes, because an
unresolvable reference is an error wherever QVF allows one; and labels is
held to one entry per slab like values. Third, the span is compared as a
vector (the norm of the difference against 10⁻⁶ of the lattice vector’s
length), not as a length, so a step of the right length in the wrong
direction is an error.
Compatibility. Producers: purely additive. Writers gain Python
add_volume(..., periodic=, includes_endpoint=, structure_ref=, axis=),
which takes 4-D data for volume.vector, volume.complex (or a complex 3-D
array) and volume.series and checks the rank per kind; C++ Grid::periodic,
Grid::includes_endpoint and Grid::structure_ref, add_volume for the new
kinds and add_volume_series(grid, data, axis, ...); and the C ABI’s first
generic volume entry points, qvf_add_volume(w, kind, grid_json, dtype, rank, shape, data, label) and qvf_add_volume_series. The writers do not check
the span; the validators do. Consumers that do not support a new kind treat
it per the unsupported-kind rules; a consumer that tiles periodic grids
MUST honour includes_endpoint. Validator authors: no new finding code;
the new rules report E_MEMBER_MALFORMED, E_REF_UNRESOLVED and
W_UNIT_UNKNOWN, and the lattice conversion is the factor
f = 1.8897261246 of spec § 4.3.
2026-10-08 — Wavefunction extensions: ECPs, two-component layout, overlap and density matrices; normalization checkable¶
Decision. wavefunction.gto (spec § 5.3) gains, under v1: (1) a full
specification of the effective core potentials whose shape the payload
definitions reserved. basis.ecp[] replaces n_core_electrons electrons of
one atom; each radial function of projectors is
U(r) = Σ_i c_i r^(n_i − 2) exp(−α_i r²) (r in bohr, U in hartree, the
conventional semi-local form, powers holding the n_i), and the new
optional local flag (default false) marks the local part, which acts on
every angular momentum and carries the highest l of its centre; every other
radial function multiplies the projector onto its l, holding
U_l − U_L as the Gaussian, Molpro and NWChem tabulations do. (2)
basis.n_electrons is the number of electrons the wavefunction describes,
Σ Z − Σ n_core_electrons − charge; it MUST equal the sum of the
occupations over both spin channels (within 1e-6, unless
occupation_semantics is transition_weight) and provenance.n_electrons
when both are present. (3) The spin layouts: restricted_open uses the
single-set shape; generalized uses it too, with the AO axis of
mo_coefficients doubled in the spinor layout [α AOs…, β AOs…], and
requires complex: true; complex: true means that every coefficient member
has a trailing [re, im] axis of 2, and its absence that none has.
symmetry_labels has one entry per MO. (4) Optional binary members
overlap (the AO overlap matrix of the AOs as basis defines them) and
density_matrix, or density_matrix_alpha + density_matrix_beta
(one-particle density matrices in the AO basis), all float64
[n_ao, n_ao] ($defs/WavefunctionAoMatrix); the density members follow the
coefficient members. (5) Normalization becomes checkable: when overlap
is present, a validator recomputes from the basis payload the self-overlap of each
AO (Appendix A.1, Verifying with overlap: S_axial of its shell for a
spherical component, S_axial · (2l_x−1)!!(2l_y−1)!!(2l_z−1)!! / (2l−1)!! for
a Cartesian one) and compares it with the stored diagonal at a relative
tolerance of 1e-6; a disagreement is the new finding code
E_NORMALIZATION. Producers SHOULD write overlap. The other rules
are reported as E_MEMBER_MALFORMED, and the provenance disagreement as
E_ROOT_INCONSISTENT. All three reference validators (Python, C++,
JavaScript) implement every check. The conformance corpus gains
wavefunction_ecp.qvf and wavefunction_overlap.qvf, the negative corpus
wavefunction_normalization.qvf, wavefunction_n_electrons.qvf and
wavefunction_complex_rank2.qvf. No qvf_version bump.
Background. Format review P09. The schema allowed [n_mo, n_ao, 2]
coefficients and the payload definitions named ecp, n_electrons, the
grown spin enum and complex, but the record of 2026-10-07 left the radial
convention of powers, the layout of a generalized wavefunction and the
electron count unspecified, so an ECP archive could not be evaluated and its
electron count was ambiguous. Independently, the primitive normalization of
Appendix A.1 was the format’s most error-prone contract and could not be
checked from an archive: the reference producer’s own readers once applied a
per-component Cartesian correction that nothing caught (record of
2026-08-05, spec § 9). The overlap matrix, which every producer’s integral
code already has, turns that contract into a number a validator compares.
Rationale for no bump. The additions are optional members and an optional
payload key; the reserved fields keep their shapes. Two rules can fail an
archive that validated before: a generalized mo_metadata without
complex: true (the schema now requires it, E_PAYLOAD_SCHEMA) and a
coefficient member whose rank contradicts complex. Neither layout was
specified before, so no conforming archive could rely on them, and no
reference writer or corpus archive emits either; the same holds for a
symmetry_labels of the wrong length and an n_electrons that contradicts
the occupations. This is the additive case of “Change process”.
Readings to confirm. n_ao for the shape checks is counted from the
shells, falling back to the declared n_ao only when the shells cannot be
read; the electron-count tolerance is absolute (1e-6 electrons) and the
normalization tolerance relative (1e-6). The normalization check reads the
diagonal only, so it cannot see a permutation of m within a spherical shell;
an off-diagonal check would need the real-solid-harmonic phase convention,
which Appendix A.2 does not yet fix. generalized requires complex: true as
P09 proposed, so a real two-component wavefunction is written with a zero
imaginary part. The density members are real; for complex orbitals they hold
the real part of the Hermitian density matrix, all the electron density
needs. All are open to revision by the maintainer before the next release.
Compatibility. Producers: purely additive. Python add_wavefunction_gto
gains spin, ecp, n_electrons, symmetry_labels, overlap,
density_matrix, density_matrix_alpha, density_matrix_beta and
complex_coefficients, and now refuses coefficient matrices whose AO extent
is not the basis’s; C++ WavefunctionGTO gains the matching fields and
QvfWriter set_wavefunction_overlap and set_wavefunction_ecp; the C ABI
gains qvf_add_wavefunction_gto_unrestricted,
qvf_set_wavefunction_overlap and qvf_set_wavefunction_ecp_json.
Consumers: a consumer that evaluates ECP integrals may now do so; a consumer
that counts electrons uses Z − n_core_electrons for an ECP atom. Validator
authors: E_NORMALIZATION is a new code; codes are added, never renamed.
2026-10-08 — excited_states and volume.transition_density canonical kinds; natural transition orbitals; spectra name states¶
Decision. Four additions under v1. (1) A canonical kind
excited_states (spec § 5.12): one JSON member states
($defs/ExcitedStatesPayload, strict) holding the states of one excited-state
calculation in ascending order of a unique index (integer ≥ 1). Each state
($defs/ExcitedState) requires index and excitation_energy, a
{value, units} object that producers SHOULD write in ev, and may carry
total_energy (SHOULD be hartree), multiplicity, symmetry,
oscillator_strength (dimensionless), rotatory_strength_cgs (10⁻⁴⁰ esu²·cm²),
transition_dipole and transition_magnetic_dipole (atomic units),
contributions ({from_mo, to_mo, spin?, weight}, rows of the reference
wavefunction’s coefficient member), nto_ref and density_ref; an optional
strict meta carries method, reference_state, n_states, tda,
spin_flip and a units declaration. The section may carry the field
wavefunction_ref, the wavefunction.gto of the reference state. (2) A
canonical kind volume.transition_density (spec § 5.2): the transition
density of one excitation in e/bohr³ with the members of volume.density and
the optional section fields excited_states_ref and state_index (which
requires excited_states_ref). (3) Natural transition orbitals (spec
§ 5.3) are two wavefunction.gto sections per state, hole and particle, with
orbital_kind: "nto", occupation_semantics: "transition_weight", the
excited_states id in state_ref, and two new optional mo_metadata fields,
state_index and nto_role ("hole" or "particle"); row k of the two
sections is the k-th pair and occupations are the pair weights. A state’s
nto_ref names the hole section. (4) A one-dimensional spectrum payload may
carry states_ref, the excited_states section whose index values its
peaks’ assignment.state_index are. Validators report repeated or
descending indices, an n_states that differs from the number of states and
a contribution whose from_mo or to_mo is not a row of the reference
wavefunction (read from the manifest shape) as E_MEMBER_MALFORMED, and a
wavefunction_ref, nto_ref, density_ref, states_ref or
excited_states_ref that names no section of the right kind, or a
state_index that names no state, as E_REF_UNRESOLVED; meta.units joins
the unit-name rule (W_UNIT_UNKNOWN). The conformance corpus gains
tddft.qvf, the negative corpus excited_states_index_duplicate.qvf,
excited_states_nto_ref_wrong_kind.qvf and
excited_states_to_mo_out_of_range.qvf. No qvf_version bump.
Background. Format review P08, and the NTO and transition-density parts
of P09 and P11: an archive could carry a UV-Vis spectrum but not the states it
is made of, so a viewer could neither list the transitions nor draw the
orbitals and densities that explain them. MoMetadataPayload already had
orbital_kind: "nto" and a state_ref whose meaning was left open, and
SpectrumPayload an assignment.state_index numbered “as the producer
numbers its states”; this record gives both a target.
Rationale. Both kinds are added directly by maintainer decision under the
change process (“clear ecosystem need exists and a reference consumer will
render it”), as forces and hessian were, and are noted in registry.json.
New kinds, new optional fields of mo_metadata, of a spectrum payload and of
a section, and new rules about them are the additive case: no member or unit
changes and nothing that was valid becomes invalid, except a typed-open
spectrum payload that already carried a key named states_ref naming no
excited_states section, which no known producer wrote. Finding codes are
reused, none is added.
Choices made here, open to revision by the maintainer before the next
release. Hole and particle NTOs are two sections, not two halves of one
coefficient matrix, so each is an ordinary wavefunction.gto that existing
consumers render; the price is that nto_ref, a single string, names the hole
section and the particle section is found through its mo_metadata.
occupations of an NTO section are the pair weights λₖ, the eigenvalues of
T Tᵀ (the squares of the singular values of the transition density matrix),
which sum to 1 for a normalized state and are what programs print as NTO
occupations. The transition density’s integer field is state_index, not
state_ref: every other *_ref in QVF is a string section id, and
mo_metadata.state_ref is the excited_states id, so the integer takes the
name the peaks and mo_metadata already use. Units with no unit name: the
vocabulary of spec § 4.3 has no name for the 10⁻⁴⁰ cgs rotatory strength nor
for atomic units of the dipoles, so those fields have fixed units, carried in
the field name for the rotatory strength, and are not declared in
meta.units.
Compatibility. Producers: purely additive. The reference writers gain
Python add_excited_states(states, *, meta=, wavefunction_ref=, section_id=),
add_volume("volume.transition_density", ..., excited_states_ref=, state_index=) and add_wavefunction_gto(..., state_ref=, state_index=, nto_role=); C++ add_excited_states(states, meta, wavefunction_ref, section_id), add_volume_transition_density and the WavefunctionGTO fields
state_ref, state_index and nto_role; and the C ABI
qvf_add_excited_states_json. add_spectrum refuses a states_ref that
names no excited_states section already added. Consumers that do not
support the kinds treat them per the unsupported-kind rules. Validator
authors: the new rules use the existing codes E_MEMBER_MALFORMED and
E_REF_UNRESOLVED.
2026-10-08 — Typed provenance fields; typed job.spec fields; structure_ref resolved¶
Decision. Two additions under v1, both optional. (1) The root
provenance block (spec § 3.2) stays open and gains seven typed fields:
software (an array of $defs/ProvenanceSoftware, strict: name required,
version, url, and x_ vendor keys), git_commit (7 to 64 hexadecimal
characters, the producer’s own revision), n_threads and n_mpi_ranks
(integers ≥ 1), n_gpus (an integer ≥ 0), and started_utc and
finished_utc (ISO 8601 UTC, the pattern of the metadata time stamps).
wall_seconds keeps its name; no walltime_s is added to provenance.
Spec § 3.2 replaces the prose list of recognised fields by a table with type,
unit and meaning for every field, and states that provenance.n_electrons,
when present, MUST agree with basis.n_electrons of a wavefunction.gto
section. (2) $defs/JobSpecPayload (spec § 5.9) gains structure_ref (the
structure section to run), dispersion and relativistic (strings) and
four objects, each a strict $defs entry with x_ vendor keys allowed:
solvation (model required, solvent, epsilon > 0), scf
(convergence > 0, max_iterations ≥ 1, guess, damping), geometry
(convergence > 0, max_steps ≥ 1, coordinates of cartesian,
internal or redundant) and resources (n_cores ≥ 1, memory_mb ≥ 1,
walltime_s > 0). The payload root stays open and engine keywords stay under
options. resources are hints, not requirements; what a run used is
recorded in provenance. Validators resolve the payload’s structure_ref to
a section of kind structure and report any other value as the existing
E_REF_UNRESOLVED (no new finding code); all three reference validators do.
The conformance corpus gains job_spec_full.qvf and provenance_full.qvf,
and the negative corpus job_spec_structure_ref_wrong_kind.qvf,
provenance_git_commit_not_hex.qvf and provenance_started_utc_not_utc.qvf.
The frozen job_spec.qvf is untouched. No qvf_version bump.
Background. Format review P18. Provenance said which method produced an
energy but not which code, which revision, on how many threads or ranks, or
when, so two archives of the same calculation could not be told apart or
reproduced; and a job.spec could request a method and a basis but not the
things a runner needs next (a dispersion correction, solvation, SCF and
optimisation settings, resources), which then went untyped into options
where no two codes agreed on the key names. The hostname part of P18 was
settled earlier (2026-10-07).
Rationale for no bump. Every addition is an optional field of an open
object (provenance, the job.spec payload root) or a new strict object
that nothing carried before, so every archive that validated before still
validates; the only archives newly rejected are those that already carried
one of the new key names with a value outside the new type, and no reference
writer and no corpus archive did. Nothing becomes required, no unit or default
changes, and no kind is added: the additive case of “Change process”.
Readings to confirm. The brief fixes types and ranges but not meaning, so
the spec states these readings, which the maintainer may revise before the
next release. scf.convergence is the convergence threshold of the SCF
energy change in hartree, and geometry.convergence the threshold of the
largest Cartesian gradient component in hartree/bohr; an engine that
converges on another measure maps them to its nearest criterion. Without a
unit a bare number would not be portable. n_threads counts threads per
process, so the total is n_mpi_ranks × n_threads.
provenance.n_electrons is defined as the number of electrons the
calculation treats explicitly, the same quantity as basis.n_electrons
(valence electrons only when an ECP replaces the core), which makes the
agreement rule hold for ECP calculations too. A job.spec without
structure_ref runs on the archive’s only structure; with several and none
named the job is ambiguous and a runner MUST refuse it. A section-level
structure_ref on the job.spec section takes precedence over the payload’s,
as for the basis payload (spec § 4.1). The typed blocks are strict rather
than open so that an engine keyword cannot hide in them. The provenance of a
pending archive, when it has any of the new fields, describes the step that
prepared the job. git_commit accepts either case of hexadecimal digit, as
git prints lowercase but does not require it of a producer’s copy.
Compatibility. Producers: additive, except that an archive which already
carries one of the new key names with a value outside its type, such as a
software string or n_threads: 0, no longer validates. Python set_provenance(**fields)
and C++ set_provenance(Json) (and so qvf_set_provenance_json) check the
typed fields and refuse a wrong value before anything changes, where they
accepted anything before; the C++ call now also rejects a provenance that is
not a JSON object, and parses a raw fragment even with allow_hostname.
Python add_job_spec gains structure_ref, solvation, dispersion,
relativistic, scf, geometry and resources; C++ add_job_spec(Json)
checks the same fields; the C ABI gains qvf_add_job_spec_json(w, spec_json)
for the whole payload and qvf_add_job_spec is unchanged. A writer accepts a
structure_ref to a section added later and refuses one that already names a
section of another kind. Consumers that ignore the new fields are unaffected.
Validator authors: a job.spec payload’s structure_ref is checked with
E_REF_UNRESOLVED at the spec member, and the typed fields are checked by
the JSON Schema (E_SCHEMA for provenance, E_PAYLOAD_SCHEMA for the
payload), so a validator without a schema engine does not see them; Appendix B
lists the new case.
2026-10-08 — Member size; updating an archive in place; the last manifest.json wins; root byte_order¶
Decision. Three container rules under v1. (1) Every member spec
($defs/JsonMember, BinaryMember, OpaqueBinaryMember,
CitationsReferences) gains an optional size ($defs/MemberSize, an
integer ≥ 0): the uncompressed byte count of the stored member, which MUST
equal the uncompressed size the ZIP central directory records for its entry
(spec § 4.2). Producers SHOULD write it, and both reference writers now
write it for every member. Validators compare it with the central directory
before reading the member and report a disagreement with the existing code
E_BINARY_SIZE, whose Appendix B meaning is widened to cover it; a size
that is not a non-negative integer is E_MEMBER_MALFORMED. (2) A new spec
§ 2.3, “Updating an archive in place”, fixes how an archive is appended
to: an update MAY append entries, MUST write a new manifest.json as
the last entry, and MUST NOT reuse the name of an existing entry other
than manifest.json. The previous manifest and every entry the new manifest
no longer references are superseded orphans, which consumers MUST ignore
and validators report as W_ORPHAN_MEMBER. A consumer MUST read the
manifest.json whose central-directory record comes last. Repacking removes
the orphans; metadata.modified_utc SHOULD be set; run.record.sequence
orders the run records of successive updates; provenance.run_status moves
per § 3.2. (3) An optional root byte_order with const: "little" (spec
§ 2.2, § 3.1) declares what every v1 archive already is; any other value
fails the JSON Schema (E_SCHEMA), and consumers SHOULD refuse it. The
reference writers emit "byte_order": "little". The conformance corpus gains
updated_in_place.qvf (two manifest.json entries, one orphaned member,
modified_utc; it validates with two warnings) and size_declared.qvf, the
negative corpus member_size_mismatch.qvf and byte_order_big.qvf. No
qvf_version bump.
Background. Format review P19 and P20. Spec § 2.1 already called orphans
“the expected by-product of updating an archive in place” (2026-10-07
record), but nothing said how an update is made, which manifest a consumer
reads afterwards, or what becomes of the previous one, and the job.spec
lifecycle (§ 5.9) and live checkpoints rely on exactly that. Appending is the
standard way to change a ZIP archive without rewriting it: the new entries go
where the old central directory began, and a new central directory lists
everything. The manifest is the only entry an update has to replace under the
same name, because consumers find it by name. Member sizes were available only
from the ZIP central directory, so a consumer that holds just the manifest
could not plan a read, and nothing cross-checked the manifest against the
container. The byte order was stated only in prose.
The manifest.json duplicate exception clarifies the 2026-10-07 rule.
That record made duplicate entry names an error because readers disagree on
which copy wins. For manifest.json this record removes the disagreement by
fixing the winner: the last central-directory record, which is also what
Python’s zipfile and the C++ and JavaScript reference readers already
resolve a duplicated name to, and which a reader that walks the local headers
in order also meets last, because an update writes its manifest last.
Validators therefore report E_ZIP_DUPLICATE_ENTRY for any duplicated name
except manifest.json, and W_ORPHAN_MEMBER for each superseded copy of it.
Every other duplicate stays an error; an update writes a changed member under
a new name instead.
Rationale for no bump. All three are the additive case of “Change
process”: size and byte_order are optional fields that no archive
carried, and § 2.3 specifies a use of the container the format already
tolerated (orphans have been valid since the 2026-10-07 record). The only
archives whose verdict changes are those with several manifest.json
entries, which were rejected before and are now valid with warnings; none of
the reference writers produced one, and every frozen corpus archive validates
unchanged. Moving the byte order into the dtype names (<f8), which would
describe each member, changes the dtype vocabulary every consumer validates
and stays a v2 candidate (format review V2-01).
Compatibility. Producers: write size when you can (the reference
writers do), write byte_order as "little" or not at all, and follow § 2.3
when you append; repack to drop the orphans before publishing an archive if
you prefer it without them. Consumers: read the last manifest.json, ignore
entries the manifest does not reference, and, if you rely on size, check it
against the central directory first. The Python, C++ and JavaScript readers
now open an archive with superseded manifests (they used to refuse it as
ambiguous), refuse a byte_order other than "little" and a member whose
size disagrees with its entry. Validator authors: no new code; the meanings
of E_ZIP_DUPLICATE_ENTRY, W_ORPHAN_MEMBER, E_BINARY_SIZE and
E_MEMBER_MALFORMED are widened in Appendix B. A conformance runner must
accept a positive archive that validates with warnings.
2026-10-08 — Trajectory extensions: a cell in the structure convention, velocities, gradient, times, binary energies, float32 MD coordinates¶
Decision. A trajectory section gains seven optional binary members,
each float64 and in the frame order of coords (spec § 5.5): lattice,
[3, 3] (one cell) or [n_frames, 3, 3] (one cell per frame), in the
structure convention, rows in Å; velocities, [n_frames, n_atoms, 3]
in Å/fs; gradient, [n_frames, n_atoms, 3], the energy gradient dE/dR in
hartree/bohr, so the forces are its negative, as for the forces kind;
times, [n_frames] in fs; energies, [n_frames] in hartree, the
binary form of metadata.energies; cell_volumes, [n_frames] in ų; and
temperatures, [n_frames] in kelvin. A reaction.path gains the four
shared members velocities, gradient, times and energies and keeps its
own lattice (columns, bohr). coords MAY be float32 when
metadata.precision is "float32": the schema’s TrajectoryCoords dtype
widens from float64 to float64 | float32, and precision, which already
had to agree with the dtype, now has a consequence. The metadata gains
ensemble (nve, nvt, npt, nph, custom) and timestep_fs (frame k
is at k × timestep_fs when times is absent); kind keeps its enum, with
scan covering relaxed scans; and pbc becomes REQUIRED when a trajectory
carries lattice, a true entry requiring lattice, mirroring § 5.1. The
fixed units of the new members are encoded in the metadata units
definitions (times and timestep_fs fs; for a trajectory lattice
angstrom, cell_volumes a3, temperatures kelvin). Validators (Python,
C++, JavaScript) check, with E_MEMBER_MALFORMED: the extents of every
per-frame member against coords, float32 coords without precision
"float32" (or a precision naming the other dtype), binary energies that
differ from metadata.energies by more than 1e-10 hartree, and a trajectory
lattice without pbc or a periodic pbc without lattice. The writers gain
Python add_trajectory(..., lattice=, velocities=, gradient=, times=, energies=, cell_volumes=, temperatures=, precision=, units=) and
add_reaction_path(..., velocities=, gradient=, times=, energies=), C++
TrajectoryExtras with the add_trajectory and add_reaction_path
overloads that take it and the Lattice / std::vector<Lattice> trajectory
overloads, and the C ABI qvf_add_trajectory, the generic trajectory entry
it lacked. The conformance corpus gains md_periodic.qvf, and the negative
corpus trajectory_lattice_without_pbc.qvf,
trajectory_float32_without_precision.qvf,
trajectory_velocities_frame_count.qvf and
trajectory_energies_disagree.qvf. No qvf_version bump.
Background. Format review P12 and D06: trajectory carried coordinates
and a JSON list of energies, so a periodic molecular-dynamics run had nowhere
to put its cell, velocities, forces or time axis, and long runs paid for
float64 positions and JSON energies they did not need. reaction.path already
had a cell, but in a convention of its own.
trajectory.lattice adopts the structure convention. Two conventions for
a periodic cell existed (spec § 4.3, “Lattice conventions”):
structure.lattice_vectors (rows, Å) and reaction.path.lattice (columns,
bohr). Units are frozen within v1, so the second cannot change, but a new
carrier may choose: trajectory.lattice takes the structure convention, so
that the frame-0 cell of a trajectory equals the lattice_vectors of its
structure element for element, and new carriers converge on one layout as
D06 proposed. § 4.3 now states the three carriers side by side and is the one
place that states both conventions; a validator reads each lattice in its own
convention and never compares or converts one against another. Unifying
reaction.path.lattice remains a v2 ledger item (V2-05).
The gradient, not the forces. P12 sketches a per-frame forces member in
Eh/bohr. Codes disagree on the sign of what they call forces, and the
forces kind (2026-10-08 record above) already settled the question by
storing the gradient. The per-frame member follows it and is named
gradient, for what it stores, so that the sign cannot be misread.
Rationale for no bump. Every member is optional, the new metadata fields
are optional, and widening the dtype of coords accepts more. The two rules
that can turn a previously accepted archive into a rejected one restate what
the format already meant: precision was already required to agree with the
dtype of coords, and a trajectory had no cell before this record, so no
archive carried lattice without pbc. No reference writer and no frozen
corpus archive is affected: all of them validate unchanged.
Compatibility. Producers: purely additive; a producer that writes float32
coordinates must set metadata.precision, which the reference writers do.
The Python add_reaction_path and C++ add_reaction_path keep float64
coordinates and refuse a metadata precision of "float32"; the reference
writers check every rule above before writing. Consumers: read
trajectory.lattice as rows in Å (never as reaction.path.lattice), negate
gradient to draw forces, and take the time of frame k from times, else
k × timestep_fs; a consumer that does not understand a member ignores it.
Schema users: the trajectory and reaction-path metadata definitions now share
$defs/TrajectoryMetadataCommon, so that each can fix the unit of its own
lattice; TrajectoryMetadataPayload keeps its name and meaning. Validator
authors: no new finding code; E_MEMBER_MALFORMED covers the new rules
(Appendix B).
Two readings to confirm. The brief left the code of the pbc rule open;
it is reported as E_MEMBER_MALFORMED at the metadata member, like the
other trajectory rules, rather than as E_STRUCTURE_PERIODICITY, whose name
and rule (§ 5.1) belong to the structure payload. Second, the precision rule
applies to reaction.path too, because the two kinds share TrajectoryCoords
and the metadata precision field. Both readings are open to revision by the
maintainer before the next release.
2026-10-08 — A unit vocabulary; typed units declarations; legacy spellings accepted¶
Decision. QVF names units with a controlled vocabulary of 25 unit
names (spec § 4.3, “Unit names”): angstrom, bohr, hartree, ev,
kcal_per_mol, kj_per_mol, cm-1, nm, debye, mhz, hz, ppm,
km_per_mol, a3, bohr3, gpa, fs, ps, kelvin, atm, e, amu,
radian, degree and dimensionless, each with its quantity and its
definition (CODATA 2018 where a measured constant enters). The schema carries
the list as $defs/UnitName, the single source of truth that the spec table,
the Python qvf_units module, the C++ header cpp/include/qvf/units.hpp
and the JavaScript reader repeat and are tested against. Every payload
units object (the
volume grid, which gains one, the trajectory, reaction-path and scan
metadata, kpath, DOS meta, Fermi mesh, qpath, phonon meta and
QTAIM), the 1-D spectrum’s x_units, y_units and broadening.units, and
the units of a {value, units} object are typed as unit names. A
declaration of a unit that § 4.3 fixes may only restate it, which the
payload definitions encode as const values (a grid units.origin is
bohr, a trajectory units.coords is angstrom). Spec § 4.3, “Declared
units”, states the semantics: producers MAY declare units, a declaration is
not a conversion, consumers MUST honour a declared unit, a contradiction of a
fixed unit is an error, and an unknown unit string is the new warning
W_UNIT_UNKNOWN, reported by all three reference validators. The conformance
corpus gains units_declared.qvf, the negative corpus unit_unknown.qvf and
unit_contradicts_fixed.qvf. No qvf_version bump.
Background. ValueWithUnits.units was free text, so "Eh", "hartree"
and "Hartree" were all valid and all different to a consumer
(docs/format_review.md D12), and the optional units objects added by the
payload definitions said in their descriptions that their vocabulary was not
yet fixed (P02). The list names units the format already uses; it changes no
unit of § 4.3, so a payload that declares nothing means what it meant.
Legacy spellings. The reference writers wrote "Eh" and "eV" in
provenance.scf_energy and provenance.fermi_energy, and frozen corpus
archives carry them. ValueWithUnits.units therefore accepts one of two
disjoint enums, oneOf UnitName and $defs/LegacyUnitName (Eh, eV,
Hartree, annotated deprecated), and only there: consumers MUST read a
legacy spelling as hartree or ev, producers SHOULD write the unit name,
and the reference writers now do. hartree itself is a unit name, not a
legacy spelling, so the two enums stay disjoint as oneOf requires. The
older free-text keys (unit of a scan axis, reaction_coordinate_unit,
energy_unit, …) stay free text, because frozen archives carry spellings
such as "rad" there.
Unknown units: a warning that the schema makes an error. The unit-name
rule is a semantic check every validator runs, with or without a JSON Schema
engine, and on its own it is a warning, so a validator without one (the C++
and JavaScript references) accepts such an archive with W_UNIT_UNKNOWN. The
same strings are typed by the schema, so a validator that runs it also
reports E_SCHEMA or E_PAYLOAD_SCHEMA and rejects the archive; the negative
corpus records both, and the runners hold the schema-only code only against
a validator that ran the schema, as for the other payload archives.
Rationale for no bump. Typing a field that was free text, and naming the units the format already uses, is additive in the sense of “Change process”: no member, kind or default unit changes, nothing becomes required, and every archive the reference writers emitted before and every frozen corpus archive validates unchanged.
Compatibility. Producers: a payload units value outside the vocabulary,
which the schema used to accept, now fails validation with
E_PAYLOAD_SCHEMA (in provenance, E_SCHEMA); no reference writer and no
corpus archive wrote one. The vocabulary has no name yet for compound units
such as e·bohr⁻³, Å⁻¹ or states·eV⁻¹, so fields in those units (QTAIM rho,
for example) cannot be declared until a later additive revision adds names
(spec § 9). Writers: Python add_volume, add_trajectory and
add_scan_surface gain units=, and add_topology_qtaim(units=) and
add_spectrum(x_units=, y_units=) now check their names; C++ gains
Grid::units and Spectrum::x_units / y_units, and checks a units
object in JSON metadata; all of them refuse an unknown name or a
contradicted fixed unit and convert nothing. set_provenance(scf_energy_eh=, fermi_energy_ev=) writes hartree and ev. Consumers: read declared units,
and map a legacy spelling to its unit name. Validator authors: add
W_UNIT_UNKNOWN (Appendix B); codes are added, never renamed.
2026-10-08 — Root energies block; forces and hessian canonical kinds¶
Decision. Three additions under v1. (1) An optional root block
energies (spec § 3.6, $defs/Energies, strict): total (required, the
energy of the archive’s primary result), method, components (twelve named
contributions such as nuclear_repulsion, exchange_correlation,
dispersion and zpve, plus x_ keys) and by_method (the energy by
further methods), every energy a {value, units} object that producers
SHOULD write in hartree. Where the manifest states one energy twice the
statements MUST agree within 1e-8 hartree: an energy of energies stated
for the method provenance.method names agrees with provenance.scf_energy,
and components.zpve with thermochemistry.zpve_eh. Validators report a
disagreement with the new finding code E_ROOT_INCONSISTENT (“two root
blocks state different values for the same quantity”). (2) A canonical kind
forces (spec § 5.10): gradient, float64 [n_atoms, 3] in hartree/bohr,
which is the energy gradient, so the forces are its negative; an optional
stress, float64 [3, 3] in hartree/bohr³ for periodic systems; an optional
strict meta ($defs/ForcesMetaPayload). (3) A canonical kind hessian
(spec § 5.11): matrix, the symmetric Cartesian Hessian, float64 [3N, 3N]
in hartree/bohr² with index 3·atom + xyz, and an optional strict meta
($defs/HessianMetaPayload: mass_weighted, projected, atom_masses_amu,
which a mass-weighted matrix requires). vibrations gains the optional
section field hessian_ref. Validators check the gradient, stress and matrix
shapes, and the atom count against the section’s structure (the section-level
structure_ref, else meta.structure_ref, else the archive’s only
structure) with E_MEMBER_MALFORMED, and resolve meta.structure_ref and
hessian_ref with E_REF_UNRESOLVED. The conformance corpus gains
forces_hessian.qvf, and the negative corpus forces_atom_count.qvf,
hessian_not_square.qvf and energies_inconsistent.qvf. No
qvf_version bump.
Background. Format review P05, P06 and P07: an archive could carry a
wavefunction and a spectrum but not the energy of the calculation beyond
provenance.scf_energy, nor the gradient or the Hessian, which every
optimisation and frequency calculation produces and which a viewer draws as
force arrows or turns into normal modes. Gradient sign conventions differ
between codes; fixing the stored quantity as the gradient, with the force its
negative, removes the ambiguity rather than leaving it to a flag.
Rationale. Both kinds are added directly by maintainer decision under the
change process (“clear ecosystem need exists and a reference consumer will
render it”), as run.record and job.spec were, and are noted in
registry.json. A new root block, new canonical kinds, a new optional section
field and a new finding code are all the additive case: nothing that was valid
becomes invalid, except an archive that carries the new energies block
inconsistently, which no archive did before this record.
Two readings to confirm. P05 compares provenance.scf_energy with “the
by_method entry of provenance.method”, which presumes that
provenance.method names the method of scf_energy. Spec § 3.6 states that
reading, so for a calculation beyond SCF provenance.method names the SCF
reference and the final method is named by the new optional
energies.method. That field, not in P05’s sketch, is what makes “total
is the SCF energy” checkable: total is compared only when energies.method
is the provenance method. Second, when neither the section nor meta names a
structure, a forces or hessian section belongs to the archive’s only
structure section; with several structures and no reference, no atom count
is checked. Both readings are open to revision by the maintainer before the
next release.
Compatibility. Producers: purely additive. The reference writers gain
Python set_energies, add_forces, add_hessian and add_vibrations(..., hessian_ref=), C++ set_energies, add_forces, add_hessian and a trailing
hessian_ref argument of add_vibrations, and the C ABI
qvf_set_energies_json, qvf_add_forces and qvf_add_hessian. Consumers
that do not support the kinds treat them per the unsupported-kind rules; a
consumer drawing forces MUST negate the stored gradient. Validator
authors: E_ROOT_INCONSISTENT is a new code; the comparison converts
hartree (and Eh, Ha), ev, kcal_per_mol and kj_per_mol to hartree
and skips other units. The forces and Hessian units objects may only
restate hartree and bohr.
2026-10-07 — atom_properties: more scalar roles, vector roles, and custom roles with a meta description¶
Decision. atom_properties keeps one role list, held by
$defs/SectionAtomProperties and mirrored verbatim by the spec’s § 5 table,
a new role table in § 5.7, both reference writers and the C ABI. The list
grows from five roles to 24. Seventeen are new scalar roles, float64
[n_atoms]: the charges cm5_charge, chelpg_charge, resp_charge,
esp_charge, nbo_charge, mbis_charge, bader_charge and ddec_charge,
and valence, bonded_valence, fukui_plus, fukui_minus, fukui_zero,
dual_descriptor, atomic_energy_eh, atomic_volume_bohr3 and
nmr_shielding_iso_ppm. Two are vector roles, float64 [n_atoms, 3]
($defs/AtomVectorBinary): magnetic_moment and force_eh_bohr. The § 5.7
table gives each role’s quantity, unit and convention: a *_charge is a net
charge in e, positive for electron loss; spin_population is an electron
count, α minus β; the Fukui functions are dimensionless condensed
differences of atomic populations and dual_descriptor is fukui_plus −
fukui_minus; magnetic_moment is in μB, a collinear producer putting the
moment on the quantisation axis; force_eh_bohr is the energy gradient in
hartree/bohr, not the force (force = −gradient), and the forces kind is
authoritative when both are present. A role named custom.<name> (<name>
is [A-Za-z0-9_]+) is also valid, scalar or vector, and only together with a
JSON member meta ($defs/AtomPropertiesMetaPayload, strict) that maps each
custom role to {units, description, kind}, kind being scalar or
vector. The schema requires meta whenever a custom role is present and a
role besides meta; validators report a custom.* member without an entry
in meta as E_MEMBER_MALFORMED, the existing code, in Python, C++ and
JavaScript, with or without a JSON Schema engine. Python
add_atom_properties takes every role by keyword plus custom= and meta=.
C++ gains add_atom_vector_property and set_atom_properties_meta, and its
map overload and add_atom_property take the enlarged scalar list and scalar
custom roles; the C ABI gains qvf_add_atom_vector_property and
qvf_set_atom_properties_meta_json, and qvf_add_atom_property takes the
enlarged scalar list and scalar custom roles. The conformance corpus gains
atom_properties_extended.qvf and four negative archives. No
qvf_version bump.
Background. Five roles covered three charge schemes and the spin
population, so a producer with CM5, CHELPG, RESP or Bader charges, reactivity
indices, NMR shieldings or magnetic moments had no schema-valid place for
them, and no per-atom vector existed at all (docs/format_review.md P10).
Each addition would have needed a revision of the format. The custom.<name>
escape lets a producer ship a quantity at once, and the required meta entry
keeps it self-describing: a consumer that does not know the role can still
show its description and units.
Rationale for no bump. New optional members are the additive case in
“Change process”. The five roles keep their names, shapes and meaning, and
every archive that was valid stays valid: a member the closed members
object did not allow could not appear in a conforming archive before, so no
conforming archive changes meaning. A consumer that does not know a new role
ignores it, as it does any member it does not understand.
Compatibility. Producers: nothing changes for a valid archive; a writer
that adds a role the list lacks uses custom.<name> and a meta entry.
Validator authors: a custom.* member without an object entry named like it
in meta is now an error, and so is a section with a custom role and no
meta member (a schema error as well); a vector role of another shape, and a
role outside the list that is not custom.<name>, were schema errors already
and stay so. The writers check what they write: both reject roles of
different lengths, vector roles that are not [n_atoms, 3], and custom roles
without a well-formed entry, before anything is written; the Python writer
derives kind from the array and rejects one that disagrees. Two points are
left open for the maintainers. First, the spec requires kind to agree with
the member’s shape, but no validator checks it. Second, the role
force_eh_bohr carries a gradient although its name says force; the name is
the one P10 fixed, and § 5.7 states the sign convention instead.
2026-10-07 — Root metadata block; section structure_ref, run_ref and group¶
Decision. The manifest gains two optional additions. First, a root
object metadata ($defs/Metadata, new spec § 3.5) that describes
the archive itself: title, description, created_utc, modified_utc,
uuid, authors ($defs/MetadataAuthor: a required name, and orcid,
affiliation, email), license (an SPDX identifier recommended), doi,
keywords, derived_from (uuids or DOIs) and tags. The block and the
author object are strict: another key is an error unless it begins with x_.
The two time stamps must be ISO 8601 UTC ending in Z, and uuid an RFC 4122
UUID in lowercase 8-4-4-4-12 form; the schema checks both (E_SCHEMA).
email and affiliation are personal data, which producers SHOULD NOT emit
by default. Second, every section object may carry structure_ref (the
structure section it describes), run_ref (the run.record of the run
that produced it) and group (a free display label) (spec § 4.1). Both
reference validators report a structure_ref that does not name a
structure section, or a run_ref that does not name a run.record, as
E_REF_UNRESOLVED, and now also report a reaction.waypoints
trajectory_ref that names a section of another kind, which spec § 6 already
forbade but no validator checked. No finding code is added. The kind-specific
references (operand_a / operand_b, trajectory_ref, and the
structure_ref of a wavefunction.gto basis payload) stay. The conformance
corpus gains multi_structure.qvf, and the negative corpus
structure_ref_wrong_kind.qvf, run_ref_unresolved.qvf,
trajectory_ref_wrong_kind.qvf and metadata_uuid_invalid.qvf. No
qvf_version bump.
Background. Several structures in one archive were legal but ambiguous
(format review P04): only the basis payload of a wavefunction named its
structure, so a consumer guessed from section order which geometry a volume
or spectrum belonged to, and a multi-step archive could not say which run
produced which result. An archive also had no title, licence, authorship or
identity of its own (P03), which publication, citation and provenance
tracking (derived_from) need.
Precedence rule. When a section carries a structure_ref and one of its
payloads carries one too, the section’s takes precedence: a consumer reads
the payload’s atom indices against the structure the section names (spec
§ 4.1, § 8.2). The section object is in the manifest, so the rule needs no
member to be opened, and it is one rule for every kind, including payload
references that later revisions may add. Producers SHOULD keep the two equal;
a disagreement is not a validation finding.
Automatic metadata in the reference writers. The Python writer
(QvfWriter(..., auto_metadata=True)), the C++ writer (the public field
QvfWriter::auto_metadata, default true) and therefore the C ABI add
created_utc and a random version-4 uuid to every archive unless the caller
set them, as spec § 3.5 recommends; both values are made once per writer. The
manifests the writers produce therefore differ from run to run. The positive
conformance generator turns the default off, so the frozen archives keep their
manifests, and multi_structure.qvf sets fixed values explicitly; no test
compares regenerated archives byte for byte.
Rationale for no bump. An optional root member and optional section fields
are the additive case in “Change process”. No valid archive carried a root
metadata key, because the root object is closed. The section object was
open, so an archive could already carry a structure_ref, run_ref or
group key with a meaning of its own; none of the reference writers, the
conformance corpus or the reference producer does. A trajectory_ref that
names a non-trajectory section was already invalid by § 6.
Compatibility. Producers: nothing changes for an archive that does not
use the new fields. Consumers MAY ignore metadata and group, and MUST
apply the precedence rule. A consumer that validates manifests against a
vendored copy of the schema must update that copy: the root object is closed,
so an older copy rejects a metadata block with E_SCHEMA, and archives from
the updated reference writers carry one by default. A producer that must stay
readable by such a consumer in the meantime can turn the automatic metadata
off. Writers: Python set_metadata(**fields) checks every field and
annotate_section(section_id, structure_ref=, run_ref=, group=, label=)
refuses a reference to a missing section or one of the wrong kind; C++
set_metadata(Json), set_section_field(id, key, value) and
annotate_section(id, structure_ref, run_ref, group); C ABI
qvf_set_metadata_json and qvf_annotate_section. No add_* signature
changed.
2026-10-07 — Payload schemas for the spectra and analysis members; provisional QTAIM position unit¶
Decision. The JSON payloads of the spectra and analysis kinds are
specified by schema definitions, and validators check each member against the
definition for its section kind and role: $defs/SpectrumPayload (the
spectrum member of spectra.ir, .raman, .uvvis, .ecd, .vcd and
.generic), $defs/NmrSpectrumPayload and $defs/EprSpectrumPayload
(spectra.nmr, spectra.epr), $defs/BondsPayload (the bonds section and
the optional bonds member of structure), $defs/BondOrdersPayload,
$defs/ScfHistoryPayload (scf_history.iterations), $defs/QtaimPayload
(topology.qtaim.critical_points) and $defs/SymmetryPayload
(structure.symmetry.data). Each has a strictness level, defined in spec
§ 5.4: strict for bonds, bond_orders and scf_history (no unlisted
keys except x_-prefixed extensions), typed-open for the 1-D spectrum,
and permissive for NMR, EPR, QTAIM and symmetry. Spec § 5.4 and § 5.7 now
describe each payload in a table. The 1-D spectrum gains optional x_units,
y_units, an x alias of frequencies, peaks, broadening,
temperature_k, and the raman and uvvis blocks; frequencies keeps its
name and carries the x axis in the kind’s § 4.3 unit unless x_units
declares another, and frequencies, intensities and x must have one
length. topology.qtaim gains an optional units object: positions are in
the unit units.position declares, and in ångström when it is absent. The
conformance corpus gains spectra_all.qvf, nmr.qvf, qtaim.qvf and
symmetry.qvf, and the negative corpus three payload archives. No
qvf_version bump.
Background. Only the structure and job.spec payloads had definitions;
every other JSON member payload was prose (format review D07), so two
producers could diverge without a validator noticing. The x values of a
spectrum are called frequencies even where they are energies (D18), and
QTAIM positions had no declared unit (D05). The typed fields are the ones the
reference writers, the conformance corpus, and the reference producer and
consumer already use. Where the reference producer writes a field under a
second name, that name is typed as well: atom_pair on a QTAIM point and
atoms / path on a bond path, and space_group_number /
space_group_symbol beside spacegroup_number / international in a
symmetry payload, where both spellings must agree when both are present.
Choosing one spelling is left to the revision that tightens the permissive
payloads.
QTAIM position unit: provisional. The unit is maintainer decision 2 in
docs/format_review.md and has not been taken. Until it is, the default is
ångström: the unit of the structure whose atoms the points overlay, and the
unit the reference producer already writes. Changing a default unit later
would be an incompatible unit change under the versioning policy above, so
producers SHOULD declare units.position now; an archive that does means
the same thing whichever default the decision fixes.
Rationale for no bump. Typing content that was already allowed, and adding optional fields, is the additive case in “Change process”. Nothing becomes required that the documented shapes did not already carry, and every archive the reference writers emit and every corpus archive validates unchanged.
Compatibility. Producers: archives that follow the documented shapes are
unaffected. Payloads that were malformed but accepted now fail validation
with E_PAYLOAD_SCHEMA: a QTAIM point whose type is not bcp, rcp, ccp
or ncp, a spectrum whose arrays differ in length, a non-integer atom index,
an unlisted key in a strict payload. Extension keys in a strict payload need
the x_ prefix. Consumers MUST NOT read spectrum values in the kind’s
default unit when x_units declares another, and MUST read QTAIM
positions in a declared units.position. Validator authors: the equal-length
rule of a 1-D spectrum cannot be expressed in JSON Schema and is checked
directly; the reference Python validator does so with or without a schema
engine. The C++ reference validator has no schema engine and, as before,
reports W_SCHEMA_SKIPPED instead of E_PAYLOAD_SCHEMA; the conformance
runners therefore require the code only from a validator that ran the schema
check. The Python writer’s add_spectrum refuses arrays of different lengths
and accepts x_units, y_units, peaks and broadening;
add_topology_qtaim accepts units.
2026-10-07 — Payload definitions for the geometry and wavefunction members¶
Decision. The schema gains payload definitions for the JSON members that
carry geometry and wavefunction data, and spec § 6 now requires every JSON
member whose section kind and role have a payload definition to conform to
it. The new definitions are VolumeGridPayload (the grid member of the
eight volume.* kinds and of basis.ao, and the volume_grid member of
reaction.path), BasisPayload and MoMetadataPayload (wavefunction.gto),
TrajectoryMetadataPayload, ReactionPathMetadataPayload,
ReactionWaypointsPayload, ScanSurfaceMetadataPayload and
VibrationsMetadataPayload, with the item definitions they use. Spec § 5.2,
§ 5.3 and § 5.5 describe each payload in a table, and § 5 names two
strictness levels. Strict payloads (grid, basis, mo_metadata,
waypoints) accept only their listed keys plus keys beginning with x_;
typed-open payloads (the trajectory, reaction, scan and vibration
metadata) type their listed keys and accept any other. The Python reference
validator reports a violation as E_PAYLOAD_SCHEMA; the C++ validator has no
JSON Schema engine and is unchanged. No qvf_version bump.
Background. Only the structure and job.spec payloads had definitions.
Every other JSON member was described in prose, so two producers could
disagree on a key’s name, type or unit without any validator noticing. That
was the largest gap between “documented” and “checkable” in v1
(docs/format_review.md D07, proposal P01).
What was accepted, and why. Every payload the reference writers emit
conforms unchanged, and so does every existing corpus archive. Keys that producers and the vibe-view consumer already use are
accepted as optional typed fields rather than by loosening a definition: the
atoms object list beside symbols, flat scan-axis labels
(axis_a_label, coordinate_a_label, coordinate_a_unit, …), and in
mo_metadata the occupation_semantics, coefficient_encoding /
coefficient_components, per-channel symmetry_labels, and the
localized-orbital descriptors. mo_metadata.n_mo is optional because a
producer in use omits it. Fields that later proposals need are named now,
with their shapes: basis.ecp and basis.n_electrons, the grown spin and
orbital_kind enums, complex, symmetry_labels and state_ref (P09), and
trajectory kind, pbc and precision (P12). The radial convention of the
ECP powers, the coefficient layout of a generalized wavefunction, and the
target of state_ref are not yet specified. New per-mode vibration fields
carry fixed units: km·mol⁻¹ for ir_intensities, Å⁴·amu⁻¹ for
raman_activities, amu for reduced_masses and mdyn·Å⁻¹ for
force_constants. An imaginary mode is written as a negative frequency.
Rationale for no bump. Adding definitions for members that already exist is additive: no member, kind or unit changes, and nothing becomes required that the spec did not already describe as carried. The reference writers’ output and every archive in the corpus are valid before and after.
Compatibility. Validator authors: the payloads were previously
unconstrained, so a malformed value that used to pass validation now fails
it. Examples are a grid shape with two entries, a negative basis exponent,
an unrestricted mo_metadata without alpha and beta, a waypoint kind
outside the five listed values, and an unknown key in a strict payload. The
equal length of a shell’s exponents and coefficients is required but is a
semantic rule the schema cannot check. Producers: a strict payload may carry
private additions only under keys beginning with x_. A single-set
mo_metadata must carry energies and occupations, which the spec always
listed as carried; the reference writers write zeros when no orbital energies
are known. Consumers: nothing changes; payloads that validated before mean
what they meant.
2026-10-07 — Phonon optional members, one atom_properties role list, DOS section fields named¶
Decision. Three places where the schema, the spec and the reference
writers disagreed now agree, additively. phonon_bands gains an optional
binary member eigenvectors, float64 [n_q, n_modes, n_atoms, 3] when real or
[n_q, n_modes, n_atoms, 3, 2] when complex (last axis [re, im], as for
complex wavefunction.gto coefficients), and phonon_dos an optional
binary member projected, float64 [n_channels, n_points], whose rows a
channels array in the meta member labels (spec § 5.6). atom_properties
has one role list, mulliken_charge, loewdin_charge, hirshfeld_charge,
iao_charge and spin_population, held by $defs/SectionAtomProperties and
mirrored verbatim by the spec’s § 5 table and § 5.7, both reference writers
and the C ABI. The fields dos.total and dos.projected carry on the section
object, n_spin, smearing, smearing_type, fermi_energy_ev,
n_electrons and, on dos.projected, channels, are named as optional typed
properties of $defs/SectionDOSTotal and $defs/SectionDOSProjected and
documented in § 5.6. The conformance corpus gains phonons.qvf and
atom_properties_all.qvf. No qvf_version bump.
Background. add_phonon_bands(eigenvectors=…) and
add_phonon_dos(projected=…) in both reference writers wrote members that the
closed members objects of the two phonon kinds did not allow, so every
archive that used them failed schema validation (docs/format_review.md D03).
The lists of atom_properties roles disagreed: three in § 5.7, four in the
§ 5 table, five in the schema, and three in the writers and the C ABI (D02). Both
writers emitted the DOS fields, but they rode the open section object unnamed,
so their types were not part of the contract (D08).
Rationale for no bump. New optional members and newly named optional
fields are the additive case in “Change process”. The two phonon members could
not appear in a schema-valid archive before, so no conforming archive changes
meaning, and hirshfeld_charge and iao_charge were already in the schema;
the spec and the writers now say what the schema already accepted.
The section object stays open. Naming the DOS fields does not close it: an
unnamed field on a dos.* section still validates. Whether each Section
branch becomes closed once its peer fields are named is decision 3 in
docs/format_review.md and remains open.
Compatibility. Producers: nothing changes for an archive that was valid.
Validator authors: the DOS fields were unconstrained, so a malformed value
that used to pass now fails, such as n_spin: 3, a negative smearing, or a
channels item whose spin is not 0 or 1. Neither reference writer emitted
one. The writers now check what they write: both reject phonon eigenvectors or
a projected phonon DOS of the wrong rank, trailing axes, dtype or extent, and
atom_properties roles of different lengths, before anything is written.
Python add_atom_properties gains hirshfeld_charge= and iao_charge=; the
C++ writer gains an overload taking a map from role to values and
add_atom_property; the C ABI gains qvf_add_atom_property. v1 does not fix
the normalization or mass weighting of phonon eigenvectors; the member stores
them as the producer computed them.
2026-10-07 — Payload definitions for the periodic kinds and the run-record files index; spin axis on projections¶
Decision. The schema defines the payloads of eight JSON members, and a
validator with a JSON Schema engine checks each member against its definition
(E_PAYLOAD_SCHEMA): bands.kpath ($defs/KpathPayload, with
$defs/KpathSegment), fermi_surface.mesh ($defs/FermiMeshPayload),
phonon_bands.qpath ($defs/QpathPayload), equation_of_state.fit
($defs/EosFitPayload, whose model is one of birch_murnaghan,
murnaghan, vinet, poirier_tarantola, polynomial) and run.record.files
($defs/RunRecordFilesPayload) are strict: only the listed fields plus
vendor keys beginning x_. The meta of dos.coop / dos.cohp
($defs/DosMetaPayload) and of phonon_dos ($defs/PhononMetaPayload) are
typed-open: the listed fields are typed and further keys are allowed.
Projection channels share $defs/DosChannel (atom, l, m, spin, and a
required label; open). Spin-resolved projections carry spin as a leading
axis: bands.projections may be [n_spin, n_k, n_bands, n_channels] and
dos.projected.projections [n_spin, n_channels, n_points]; the existing
lower-rank shapes stay legal and mean n_spin = 1. bands gains an optional
occupations member, float64 [n_spin, n_k, n_bands]. Both reference
validators report a bands section whose projections or occupations
disagree in shape with its eigenvalues as E_MEMBER_MALFORMED. Spec § 5.6
is rewritten around per-member payload tables with a Spin axis rule, and
§ 5.8 gains a table for the files index. No qvf_version bump.
Background. These payloads were prose only (D07 in the format review), so
two producers could name the same field differently and no validator would
notice. The reference consumer already read keys the format never named:
kpath.labels, segments, channels and title, the Fermi mesh’s
lattice_vectors and band_indices, the COOP/COHP pair_labels, and the
files entries’ description and truncated. Band and DOS projections had
no spin axis (D04): eigenvalues were [n_spin, n_k, n_bands], but fat-band
weights could not say which spin they belonged to, and producers already wrote
[n_spin, n_channels, n_points] DOS projections that the format did not
describe.
Rationale for no bump. Every change is additive: a new optional member,
new optional fields, definitions for members that already existed, and new
legal shapes beside the old ones. The definitions were written against what
the reference writers, the reference producer and the conformance corpus
emit, so all of those archives validate with their bytes unchanged. Where
producers write one quantity under two names (fermi and fermi_energy_ev
in kpath, shape and nk1/nk2/nk3 in the Fermi mesh), both names are
recorded with the rule that they agree, rather than one being made invalid.
No field becomes required except model in an EOS fit and label in a
channel object, which every known producer writes.
Compatibility. Producers: nothing changes for an archive that already
matches the tables; new archives SHOULD use the spin axis whenever
eigenvalues have two spin channels. Consumers: read a lower-rank projection
as n_spin = 1, and a string in kpath.channels as a channel with that
label. Validator authors: the eight members were unconstrained, so a
malformed payload that used to pass now fails, as in the 2026-07-26 decision
below — the realistic cases are an EOS model outside the five forms, a key
outside a strict table, and a files entry with a field other than
filename, description, media_type and truncated. bands.projections
must now be float64, as the reference writers always wrote it. A validator
without a JSON Schema engine (the C++ reference validator) checks the shapes
but not the payloads, so a negative-corpus expectation of E_SCHEMA or
E_PAYLOAD_SCHEMA binds only a validator that runs the schema. The reference
writers (Python and C++ add_bands, add_dos_projected) accept both ranks
and an occupations argument and refuse shapes that disagree; the C ABI is
unchanged. The channel object is open, so the atom_index and symbol keys
some producers write on dos.projected channels stay valid; whether
atom_index or atom is the name to standardise is left to the record that
types those channels.
2026-10-09 — Constant arrays and ZIP integrity¶
Decision. Remove compression ratio from the default validity limits in
§ 6.1. Keep the absolute per-entry, total, JSON-size and JSON-depth caps, and
reject streams that do not inflate to their recorded size or match their ZIP
CRC-32. Python, C++ and JavaScript agree. The reference APIs retain an explicit
positive max_ratio as a stricter application policy; its default is now 0
(disabled), and E_COMPRESSION_RATIO keeps that meaning for callers opting in.
The negative corpus’s compression_ratio case tests only that opt-in policy;
entry_size_understated, member_too_large, total_too_large, and the two CRC
cases retain the mandatory malformed-input and resource-limit coverage.
Correction to the 2026-10-07 rationale. Its assertion that no reference-writer
archive is affected was incorrect: a valid 64³ float64 zero spin-density grid
compresses beyond 1000:1 and was rejected. Raising the ratio threshold does not
bound decompression better than the absolute and declared-size limits. The
C++ container reader already checks CRC-32 for both STORE and DEFLATE members;
new corpus controls cover members without an optional SHA-256. No
qvf_version bump: valid archives become readable while existing corrupt-input
rejection remains enforced.
2026-10-07 — Validation hardened for untrusted archives; stable finding codes¶
Decision. Spec § 2.1 and § 6 now state the rules a validator needs to read
untrusted input safely, and both reference validators enforce them. These are
errors: duplicate ZIP entry names; member paths (manifest path values
and ZIP entry names) containing a backslash or NUL, beginning with / or a
drive letter, or with a .. segment; archives over the resource caps
(per-member and total uncompressed size, compression ratio, JSON size, and
JSON nesting depth); and a section whose id is auto_open or bookmarks,
the two reserved viewer_defaults keys. ZIP entries that no member references
(“orphans”) are warnings. Every finding carries a stable code listed in the
new informative Appendix B (E_… errors, W_… warnings), shared by the
Python and C++ reference validators, and the conformance suite gains a
negative corpus (conformance/negative/) with one must-fail archive per rule.
No qvf_version bump.
Background. The reference validators parsed untrusted archives without
checking for duplicate entry names (ZIP readers disagree on which copy wins),
Windows-style or absolute paths that escape an extraction directory, JSON
nested deeply enough to exhaust a recursive parser, or archives that inflate
far beyond their size on disk. viewer_defaults keys its per-section hints by
section id in the same object that holds auto_open and bookmarks, so a
section with either id was ambiguous. Findings were free-text strings that
tools could only match by substring, and the two reference validators did not
share a vocabulary.
Rationale for no bump. These rules clarify and harden validation semantics; they do not change the format. The only archives newly rejected are malformed ones: an archive with duplicate entries or an escaping path never had a single well-defined reading, a reserved-id section was already ambiguous, and the reference caps sit far above what producers write (the 8 GiB per-member cap was already the § 2.1 recommendation). Orphans stay valid, so an archive updated in place keeps validating. The schema is unchanged, and no archive the reference writers produce is affected; the writers now also refuse the reserved ids and the newly forbidden paths.
Resource caps are SHOULD values. A validator reports an exceeded cap as an error, and SHOULD use the reference values: 8 GiB per entry, 64 GiB in total, a ratio of 1000 (STORE entries and entries under 1 MiB exempt), 256 MiB per JSON document, and nesting depth 64. Producers SHOULD stay within them so their archives validate everywhere.
Compatibility. Producers: nothing changes for a conforming archive.
Consumers SHOULD refuse duplicate entry names and apply the caps before
decompressing or parsing. Validator authors SHOULD emit the Appendix B
codes; a code is never renamed or reused. The Python validate_qvf() keeps its
errors / warnings string lists (each now prefixed CODE: ) and adds a
findings list; the C++ reader keeps validate() and adds
validate_findings(). qvf_version: 2 is still reported as an error
(E_VERSION): producers MUST NOT emit it, while consumers may still open
such archives per the 2026-07-10 decision below.
2026-10-07 — viewer_defaults hints render and transfer_function recorded (spec only)¶
Decision. Spec § 3.4 describes viewer_defaults: the members
auto_open and bookmarks, which are reserved and may not be used as section
ids, and the per-section hint object with its keys. It records two keys a
consumer already read but the format never named: render
("isosurface" or "volume") and transfer_function (a range, color
and opacity nodes, optional preset and colormap), with the rules a
consumer cleans a function by. It
states the consumer rule for every hint: a consumer MAY ignore a hint, MUST
ignore one it does not understand, and MUST NOT refuse a file because a hint
is malformed. The change is spec only; $defs/ViewerHint is unchanged.
No qvf_version bump.
Background. vibe-view’s volume mode reads both keys, and its manual
documented them “ahead of the specification”, so a producer had only a
consumer’s manual to go by and a second consumer could not learn the keys from
the format. The recorded shapes and rules are those of vibe-view’s shipped
parser. Where the proposal’s prose differed, the parser was followed: a
transfer_function whose colormap is not "custom", including one with no
colormap, takes its palette from the section’s colormap hint.
Rationale for spec only. ViewerHint is open
(additionalProperties: true), so both keys already validate and no archive
changes. Typing them in the schema would turn a malformed hint into a schema
error, and a schema error is fatal in the Python reference validator and in a
consumer that validates the manifest before opening, as vibe-view does. That is
the opposite of the consumer rule, and stricter than the parser the keys come
from, which forgives a capitalised render and drops a bad node rather than
the function. An unchanged schema also means no copy of it has to be
re-vendored downstream.
Rationale for no bump. Recording optional keys of an open object is the additive case in “Change process”; nothing becomes required and no existing archive changes meaning.
Compatibility. Purely additive. The four schema-typed keys (isovalue,
colormap, opacity, replication) keep their types, so a malformed value of
one of them still fails schema conformance and remains a producer error; under
the consumer rule a consumer nonetheless opens such a file and ignores the
hint. The hint object is defined independently of where it is keyed, so its
keys keep their meaning if a later major version moves per-section hints under
a sub-object of their own. Three further keys the same consumer reads, a
per-section energy_window and a top-level camera and crossfade, remain
unrecorded and are left to a follow-up change (recorded on 2026-10-15).
2026-10-07 — provenance.hostname deprecated; sharing and sanitisation guidance¶
Decision. The recognized provenance field hostname is deprecated
(spec § 3.2): producers SHOULD NOT emit it, consumers MUST ignore it for any
purpose other than display and MUST NOT rely on it, and it may be removed in a
future major version. The schema keeps the property, now annotated
"deprecated": true, and the provenance object stays open. The reference
writers drop a hostname key by default — Python set_provenance(..., allow_hostname=True) and C++ set_provenance(prov, /*allow_hostname=*/true)
opt back in; the C ABI has no opt-in. Spec § 5.8 gains a “Sharing and
sanitisation” paragraph: the verbatim members of a run.record commonly carry
absolute paths, user names, and host names, sanitising them before an archive
is shared is the producer’s or user’s responsibility, and producers SHOULD
offer a way to suppress or redact such content. No qvf_version bump.
Rationale. QVF archives are meant to be shared, and a recognized
hostname field invited producers to write private operational detail into
every file, against the format’s purpose and this repository’s own
public-safety policy. The run.record guidance replaces a one-sentence
reminder with an explicit allocation of responsibility and names the intended
toolkit mechanism, a scrub operation with a reviewable report, which is
planned separately and changes nothing in the format.
Compatibility. Deprecating an optional field is additive: nothing becomes
invalid. Archives that already carry hostname still validate, and the field
remains listed so they stay interpretable. Producer-visible changes: the
reference writers now omit the key unless asked, and because
qvf_set_provenance_json must parse its string to find the key, malformed JSON
passed to it is now reported as an error instead of being written verbatim.
2026-08-05 — basis.ao Cartesian components named by cartesian_powers¶
Decision. ao_metadata gains an optional cartesian_powers
([l_x, l_y, l_z]), present if and only if the AO is a component of a
Cartesian (pure: false) shell. angular_momentum keeps its shape and stays
REQUIRED; its second element is a magnetic quantum number only when
cartesian_powers is absent. Specified in spec Appendix A.3.
No qvf_version bump.
Background. basis.ao names one component of one shell, and the two
shell kinds of Appendix A.2 need different names. Only the spherical one was
specified. The reference producer derived m as in-shell index − l,
which is the A.2 spherical ordering and is simply undefined for a Cartesian
shell: it has (l+1)(l+2)/2 components, not 2l+1, so the subtraction ran
past +l and a Cartesian d shell was labelled m = −2 … +3 across its six
components. Every value was schema-valid, so nothing caught it.
Rationale for no bump. A new optional member is the additive case in
“Change process” above. angular_momentum’s required-ness and shape are
unchanged, so no required-member change occurs and the compatibility
commitment below is not engaged. Element 0 (l) was and remains meaningful
for both shell kinds, so a v1 consumer reading only l is unaffected. No
conforming archive changes: cartesian_powers can appear only where the old
contract had no correct value to emit.
Rationale for a separate member over widening angular_momentum.
[l, l_x, l_y, l_z] would have overloaded one field with two meanings
distinguished by length, so an un-updated consumer reading element 1 as m
would keep getting a plausible-looking number ([2,0,0] → m = 2). A
distinct key makes the Cartesian case detectable rather than
indistinguishable, which is the property the old contract lacked.
Compatibility. Purely additive for producers; a spherical-only producer
emits exactly the bytes it did before. Consumers MUST NOT read
angular_momentum[1] as m when cartesian_powers is present. Validator
authors: the member is optional, so no existing archive is invalidated.
2026-07-26 — biomolecule metadata schema’d on structure (no v3)¶
Decision. The optional biomolecule fields on the structure section object
— chains, residues, secondary_structure, and the new b_factors — are
schema’d as optional properties of $defs/SectionStructure, with
$defs/BiomoleculeResidue and $defs/BiomoleculeSecondaryStructure giving
their item shapes, and $defs/StructureAtom gaining an optional per-atom
b_factor. Spec § 5.1 gains a normative precedence rule: a consumer that can
also derive residues / chains / secondary structure itself MUST prefer the
producer-supplied values, and per-atom b_factor wins over the section-level
b_factors array. Added under v1 — every field is optional, which is the
additive case, so there is no qvf_version bump.
Rationale. vibe-view’s cross-repo Ask 1 requested a
a separate qvf_version: 3 manifest schema for these fields (and for the streaming
run_status / checkpoint / partial fields, which landed additively on
2026-07-02 and 2026-07-25). That request predates the versioning policy above
and the 2026-07-10 withdrawal of qvf_version: 2 below, which settled exactly
this question: an optional member must not bump the major version, because the
specification requires a v1-only consumer to refuse a higher version, so a bump
would break conforming third-party consumers for a field they were free to
ignore. The three earlier fields already shipped unschema’d, riding the open
Section object; naming them in the schema is what turns “the writer happens to
emit this” into a contract two implementations can be held to, and it is what
Ask 1 actually needed.
Compatibility. Purely additive for producers. Note for validator authors:
the fields were previously unconstrained, so a malformed value that used to
pass validation now fails it — a secondary_structure range with a type
outside helix/sheet/coil being the realistic case. No known producer
emitted one; the reference producer now drops such a range rather than writing
it.
2026-07-25 — job.spec canonical kind + run_status lifecycle (executable archives)¶
Decision. A new canonical kind job.spec (spec § 5.9) carries a
declarative specification of the calculation an archive requests — job_type,
method, basis, functional, charge, multiplicity, kpoints, tasks,
and open engine-specific options — as a JSON member schema’d by
$defs/JobSpecPayload. In the same decision, provenance.run_status is
formalized in spec § 3.2 with the value set pending | running | converged | failed, adding pending for archives that describe a job not yet run. Both
are added under v1 — a new canonical kind and a newly recognized optional
provenance field are the additive case, so there is no qvf_version bump.
Rationale. With run.record (2026-07-24) an archive records what ran;
without a request counterpart it cannot describe what should run, so a
calculation’s lifecycle could not live in one file. job.spec completes the
container model: structure + job.spec + run_status: "pending" in, results +
run.record + run_status: "converged"/"failed" out — the same archive updated
in place. The payload is deliberately declarative data, never code: a runner
reconstructs the job from typed fields, and the spec explicitly forbids
executing run.record.input as a side effect of opening or running an
archive, keeping “open a file” and “run untrusted code” apart. Added directly
per the change process (“clear ecosystem need exists and a reference consumer
will render it”) as a maintainer decision, noted in registry.json.
Compatibility. Purely additive. Consumers that do not support the kind
treat it per the standard unsupported-kind rules. run_status values
running / converged / failed were already emitted by the reference
producer’s live checkpointer under the open provenance object; pending is
new, and the schema now names the recognized value set.
2026-07-24 — run.record canonical kind added (self-contained archives)¶
Decision. A new canonical kind run.record (spec § 5.8) carries the
verbatim input of a program invocation, its full log/output, and auxiliary
attachments, plus a required program section field naming the code that ran.
Added under v1 — a new canonical kind is the additive case, so there is
no qvf_version bump.
Rationale. A QVF archive already carries a calculation’s results; without
the input and log it is not a self-contained record of the calculation. The
kind is producer-neutral by construction: program is independent of the root
source.program, so a converter (e.g. an ORCA-output packer) can record
faithfully which code the input belongs to. Added directly per the change
process (“clear ecosystem need exists and a reference consumer will render
it”) as a maintainer decision, noted in registry.json.
Compatibility. Purely additive. Consumers that do not support the kind
treat it per the standard unsupported-kind rules; the opaque-bytes member
shape (no dtype/shape) follows the existing citations.references
precedent, so generic member handling is unaffected.
2026-07-10 — qvf_version: 2 withdrawn (periodic reaction.path)¶
Decision. The qvf_version: 2 bump is withdrawn. reaction.path gains an
optional lattice member under v1; a periodic reaction path is detected by
that member’s presence, not by a version number.
Background. vibe-qc v0.10.0–v0.15.x stamped qvf_version: 2 on any archive
containing a periodic reaction.path. The entire schema-visible difference from
v1 was one optional member (lattice); a companion dim field lives inside
a JsonMember payload and is not schema-visible at all.
Rationale. Under the versioning policy above, an optional member is the
additive case and must not bump qvf_version. The bump also created a real
interoperability break: the specification requires a v1-only consumer to refuse
any archive with a higher major version, so conforming third-party consumers were
obliged to reject archives the reference producer routinely wrote. Finally, a
major version is the format’s scarcest signal — qvf_version: 2 must mean “a v1
consumer genuinely cannot read this” — and spending the format’s first bump on a
backward-compatible optional field would have permanently muddied that meaning.
Compatibility. Archives stamped qvf_version: 2 exist in the wild (shipped
from v0.10.0 onward). They are v1-compatible. Therefore:
Producers MUST NOT emit
qvf_version: 2.Consumers SHOULD accept
qvf_version: 2and treat it as1, for at least one minor line. This is a deprecation, not a deletion.spec/legacy/qvf_manifest_v2.schema.jsonis retained as a frozen generated artifact so anything resolving its$idcontinues to validate (it was then a strict superset of v1; it is not of the v1 schema as it has grown since, and no validator uses it: see the 2026-10-15 decision).
2026-07-10 — structure payload schema’d; pbc required when periodic¶
Decision. The structure member’s JSON payload is now specified, as
$defs/StructurePayload. pbc is REQUIRED whenever lattice_vectors is
non-null, and dimensionality is optional and derived, with the invariant
dimensionality == sum(pbc). No qvf_version bump.
Background. pbc, lattice_vectors, and dimensionality were prose-only,
and the prose made dimensionality a MAY on the section object while the
reference producer wrote it into the payload. A consumer could not assert that
a structure was a 2-D slab, only infer it — and inferring wrong renders a slab
inside a vacuum box.
Rationale for no bump. Absent pbc had no defined meaning; a periodic
archive that omitted it was already unrenderable. Every known producer emits it,
and every archive in the conformance corpus validates unchanged. This is the
clarification case in “Change process” above, not a required-member change.
The periodic axes are deliberately not required to be the leading ones.
pbc = [true, false, true] — a slab whose normal lies along y — is
conforming. Ordering the periodic axes first is an internal convention of one
producer (vibe-qc’s periodic-system code); making it normative would have forced other
producers to permute their lattice, against the producer neutrality asserted at
the top of this document. It survives in the spec as an informative note
describing the legacy dimensionality-without-pbc fallback.
Compatibility. dimensionality moved from the section object to the
payload. Archives carrying it in the old position remain fully interpretable,
because pbc — which every such archive also carries — is what a consumer
reads. A consumer MUST NOT read dimensionality off the section object.
The registry¶
registry.json is the machine-readable list of:
Canonical kinds — the section kinds defined in the spec + schema. A drift test keeps this list in lock-step with the schema’s
Sectionbranches, so the registry can never claim a kind the schema doesn’t define (or vice versa).Reserved names — planning names not yet portable contracts.
Vendor namespaces — registered
x_<vendor>.*prefixes, their owner, and purpose. Registration is advisory (any code may use its ownx_<vendor>.*without asking), but registering avoids collisions and signals intent to standardize.Promotions — vendor kinds that have become canonical.
Change process¶
Anyone may propose a change by opening an issue or merge request against the repository:
Register a vendor namespace — add a row to
registry.json(vendor_namespaces). Lightweight; no design review needed, it just records who is using which prefix.Extend a canonical kind (a new optional member) — a spec + schema change, reviewed for backward compatibility. No
qvf_versionbump.Add a new canonical kind — via the promotion process below, or directly when a clear ecosystem need exists and a reference consumer will render it.
Report a spec issue / ambiguity — an issue; clarifications land in the spec without a version bump.
The maintainer reviews and merges. Decisions that affect the contract are
recorded in CHANGELOG.md and the registry.
Vendor → canonical promotion¶
The path from an experimental x_<vendor>.* section to a standard kind
(spec § 5.7 / § 7.5):
The kind is used in production by at least two independent producers for at least one release cycle.
Its member contract is stable across those producers.
A spec + schema + validator change adds the canonical kind, and at least one reference consumer renders it.
This threshold is what makes a promotion earned by real interoperability rather
than declared. (The maintainer may promote ahead of the two-producer threshold
when a canonical target is needed to seed adoption — as was done for
spectra.epr and wavefunction.bloch — but such promotions are noted as
maintainer decisions in registry.json. A kind promoted before a second
producer ships it carries "status": "provisional" in its promotions entry
until one does; it is canonical in every respect, and any revision it needs
meanwhile is additive.)
Compatibility commitments¶
No canonical kind is removed or has its required members changed without a major
qvf_versionbump.A consumer may always safely ignore vendor (
x_<vendor>.*) sections it does not understand, unless they are flaggedcritical: true(in which case a consumer that cannot support them must refuse to open the file, by design).Units and numeric conventions (spec § 3) are stable within a major version.
Getting involved / becoming a supported producer¶
Emit valid QVF for your code’s outputs and self-certify against the conformance suite.
Register your vendor namespace in
registry.jsonif you ship non-standard data.Reach out — once your code emits conforming archives, it can be listed as a supported producer and its native format considered for direct vibe-view support. See docs/integration_guide.md.
Evolving the governance¶
The current model is maintainer-led because there is, so far, one reference producer/consumer ecosystem. It is explicitly designed to broaden as the format is adopted:
As independent producers ship QVF, they earn a say in the promotion process (a promotion requires their real-world use, per the threshold above).
If and when adoption warrants it, the spec + registry can move to a vendor-neutral hosting home with a small steering group of adopters. The standard is open regardless of where it is hosted — the Apache-2.0 license, the public schema, and this governance document travel with it — and vibe-qc’s origin and stewardship remain acknowledged.
That is a decision for the maintainer and the adopter community to make together, not a prerequisite for adopting QVF today.