modelvet release policy and procedure

August 4, 2026 ยท View on GitHub

This document is the versioning contract embedders rely on and the checklist a release actually follows. The ABI rules here are permanent; the procedure evolves.

Versioning policy

modelvet uses semantic versioning. The version lives in one place, include/modelvet.h (MVET_VERSION_MAJOR/MINOR/PATCH); the amalgamation banner, the CLI, make dist, and the Python binding all derive from it.

Permanent ABI, regardless of version:

  • Violation-code numbers are append-only. A MVET_V_* value is never renumbered and never reused. A check that is retired leaves a hole in the numbering. New codes append inside their group gaps or in new groups. A consumer may persist numeric codes forever.
  • The report struct layout is stable. mvet_report_t is fixed-width fields only; an all-zero report reads as fail-closed REJECT.
  • Status and verdict values are stable. MVET_OK = 0, MVET_ERR_ARG = -1, MVET_ERR_ARENA = -2, MVET_VERDICT_REJECT = 0, MVET_VERDICT_ACCEPT = 1.
  • CLI exit codes are stable. 0 verified ACCEPT, 1 verified REJECT, 2 no verdict. JSON report fields are append-only: existing fields keep their names, types, and order; new fields append before version.

Pre-1.0 rules (current):

  • MINOR releases may add public API, add violation codes, and change cap DEFAULTS. The arena bounds move with cap defaults; the closed-form formulas are the contract, not the numeric values.
  • Pre-1.0 signature or behavior changes to existing public API are permitted only deliberately, with a CHANGELOG entry and a migration note. There is no silent breakage tier.
  • PATCH releases change no public API and no default cap.

Post-1.0, MAJOR gates any signature or behavior break of existing public API; everything above still holds.

Verdict-strictness changes are behavior: a release that makes the validator stricter (new REJECT on previously accepted shapes) or looser documents each change in the CHANGELOG with its violation code and rationale, the way "policy" codes are marked in the public header.

Release artifacts

  • modelvet-X.Y.Z.tar.gz: reproducible tarball from make dist holding the amalgamated modelvet.c + modelvet.h, LICENSE, README.md, CHANGELOG.md, INTEGRATION.md, and the modelvet.1 man page. Built with pinned timestamps (SOURCE_DATE_EPOCH, default 0), sorted entries, and numeric zero ownership: the same tree produces the same bytes.
  • modelvet-X.Y.Z.tar.gz.sha256: emitted by make dist.
  • modelvet-X.Y.Z.tar.gz.sig (or .asc): detached signature, made by the release manager with the same key that signs the tag. 0.1.0 used an SSH signature (ssh-keygen -Y sign -n file); a GPG detached signature is equally acceptable. The release notes must state which, plus the verification command.

Release procedure

  1. Verify the tree on the full matrix (both compilers):

    make clean && make check CC=gcc
    make clean && make check CC=clang
    make test-profile-64k CC=clang
    make test-no-assert CC=clang
    make clean && make test CC=clang \
      EXTRA_CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all"
    make fuzz && ./build/smoke_fuzz -runs=100000
    make check-ggml GGML_DIR=/path/to/pinned/llama.cpp
    make check-python
    
  2. Bump the version in include/modelvet.h, finish the CHANGELOG section (date it), and land that change through CI.

  3. Confirm branch protection: the corpus, parity, and gate jobs must be required status checks so the release commit cannot bypass them.

  4. Build and sign (SSH shown, as used for 0.1.0; GPG works the same way with gpg --detach-sign --armor):

    make dist
    ssh-keygen -Y sign -f ~/.ssh/<key> -n file \
      build/dist/modelvet-X.Y.Z.tar.gz
    
  5. Tag and publish:

    git tag -s vX.Y.Z -m "modelvet X.Y.Z"
    git push origin vX.Y.Z
    gh release create vX.Y.Z build/dist/modelvet-X.Y.Z.tar.gz \
      build/dist/modelvet-X.Y.Z.tar.gz.sha256 \
      build/dist/modelvet-X.Y.Z.tar.gz.asc \
      --title "modelvet X.Y.Z" --notes-file <notes>
    
  6. Python package: make python-vendor, then build and upload the sdist from bindings/python/ (reserves the PyPI name at first release).

  7. Post-release: reserve/confirm the npm name, update integration consumers, and open the next CHANGELOG section.

Steps 4-7 require the release manager's keys and registry accounts; they are never performed by automation or an agent session.