# Versioning and Release Tarball — Design

**Date:** 2026-05-05
**Status:** Approved (brainstorm complete); implementation plan to follow
**Scope:** Establish a single canonical version for the zyginit suite (zyginit + zygctl + sysv-wrapper), wire `--version` flags into all three binaries, and add scripts to bump the version and produce a source release tarball for vendoring into Hammerhead.

## 1. Goal

Hammerhead consumes zyginit as a **vendored source drop** into `hammerhead-userland` (per `docs/SMF_MIGRATION.md`). To make that drop reproducible and auditable, zyginit needs:

1. A single canonical version, tracked in source.
2. A scripted bump that updates every place the version appears, in lock-step.
3. A scripted release that produces `releases/zyginit-<version>-source.tar.xz` plus a `.sha256` companion.
4. A runtime `--version` flag on every shipped binary, so an installed Hammerhead system can answer "which zyginit am I running?".

This work intentionally mirrors Coral's `scripts/make-release.sh` pattern (single tarball, sha256, `--transform`-prefixed paths) — the Hammerhead team gets a consistent shape across Zygaena projects.

## 2. Non-goals

- **No CI/CD release automation.** Bump and release are run by hand by a maintainer; no GitHub/Hg-server hooks, no signing, no upload step.
- **No reproducible-build flags** (`--mtime`, `--owner=0`, `--sort=name`). Coral doesn't bother and Hammerhead's packaging step is what produces the OS-level reproducible artifact. Adding these is a trivial follow-up if needed.
- **No multi-tarball releases.** The whole suite (zyginit + zygctl + sysv-wrapper) ships and versions together, in one tarball.
- **No `CHANGELOG.md` integration.** The bump script doesn't touch a changelog. (We can add this later; there is no changelog file today to integrate with.)
- **No GPG signing.** Hammerhead handles signing at the OS package layer.

## 3. Versioning policy

Semver: `MAJOR.MINOR.PATCH`.

| Bump | Reason |
|---|---|
| MAJOR | Incompatible socket protocol change, incompatible service-definition (TOML) schema change, removal of a published `zygctl` subcommand |
| MINOR | Backward-compatible additions: new service-definition fields with defaults, new `zygctl` subcommands, new runlevel features |
| PATCH | Bug fixes only; no API or protocol changes |

While zyginit is pre-1.0, breaking changes may land in MINOR per semver convention. Once 1.0 ships (target: when SMF removal from Hammerhead is complete and the suite has been running unmodified for one release cycle), strict semver applies.

## 4. Source of truth

The root `reef.toml` `[package].version` is **canonical**. Every other location is **derived** and regenerated by the bump script.

Today's drift inventory (rev 82, version `0.1.0`) — the bump script must keep these all consistent:

| # | Location | Form |
|---|---|---|
| 1 | `reef.toml` (root) | `version = "0.1.0"` (canonical) |
| 2 | `tools/zygctl/reef.toml` | `version = "0.1.0"` |
| 3 | `src/version.reef` (new, generated) | `pub fn VERSION(): string` returning `"0.1.0"` |
| 4 | `tools/zygctl/src/version.reef` (new, generated) | same |
| 5 | `tools/sysv-wrapper/version.h` (new, generated) | `#define ZYGINIT_VERSION "0.1.0"` |

Generated files (`version.reef` × 2 and `version.h`) **are committed** to the repository so a tarball recipient can build without rerunning the bump script. They carry a "Generated by scripts/bump-version.sh — do not edit by hand" comment.

## 5. `scripts/bump-version.sh`

**Usage:** `scripts/bump-version.sh <new-version>`

**Behavior:**

1. Validates `<new-version>` matches `^[0-9]+\.[0-9]+\.[0-9]+$`. Refuses if not.
2. Reads current version from root `reef.toml`. Refuses if `<new-version>` equals current.
3. Rewrites all five locations listed in §4.
4. Prints a summary diff (`hg diff --stat`) and a suggested commit command:
   ```
   $ hg ci -m 'Bump version to <new-version>'
   ```
5. Does **not** auto-commit. The maintainer reviews and commits by hand.

**Generated file templates:**

`src/version.reef` (and identical-content `tools/zygctl/src/version.reef`):
```reef
/* SRCHEADER... */

module version

// Generated by scripts/bump-version.sh — do not edit by hand.
pub fn VERSION(): string
    return "0.1.0"
end VERSION

end module
```

`tools/sysv-wrapper/version.h`:
```c
/* SRCHEADER... */
/* Generated by scripts/bump-version.sh — do not edit by hand. */
#ifndef ZYGINIT_VERSION_H
#define ZYGINIT_VERSION_H
#define ZYGINIT_VERSION "0.1.0"
#endif
```

Both templates use the project's standard `SRCHEADER.txt` header. The bump script fills in `${project}` from each subproject's own `reef.toml` `[package].name` (i.e., `zyginit` for `src/version.reef`, `zygctl` for `tools/zygctl/src/version.reef`), `${file.name}` from the filename, and `${file.description}` with a fixed string ("Generated version constant").

## 6. `scripts/make-release.sh`

**Usage:** `scripts/make-release.sh [version-override]`

**Behavior** (mirrors Coral's `make-release.sh`):

1. Reads version from root `reef.toml`, or uses `[version-override]` if provided.
2. Creates `releases/` if missing.
3. Produces `releases/zyginit-<version>-source.tar.xz` via `tar --transform "s,^,zyginit-<version>-source/," -cJf ...`.
4. Generates `releases/zyginit-<version>-source.tar.xz.sha256`.
5. Prints final paths and the sha256.

**Tarball contents** (top-level paths, `--transform`-prefixed into `zyginit-<version>-source/`):

| Include | Why |
|---|---|
| `reef.toml` | Build manifest (carries the canonical version) |
| `README.md` | Project orientation |
| `ROADMAP.md` | Forward-looking roadmap |
| `CLAUDE.md` | Project context (useful even outside Claude — accurate in-tree docs) |
| `SRCHEADER.txt` | Source-header template (referenced by all source files) |
| `src/` | Main daemon Reef source |
| `docs/` | Architecture docs (`DESIGN.md`, `SMF_MIGRATION.md`, etc.) |
| `tests/` | Unit + integration tests |
| `services/` | Example + Hammerhead service TOMLs (the v2 BSD-pivot tree) |
| `tools/` | `zygctl/`, `sysv-wrapper/` (subprojects) |
| `utils/` | `contract_probe` and any other utility tools |
| `scripts/` | `bump-version.sh`, `make-release.sh`, `install-to-be.sh` |

**Tarball excludes:**

| Exclude | Why |
|---|---|
| `build/` (root and any `tools/*/build/`) | Build artifacts |
| `releases/` | Output of this very script — don't ship past tarballs |
| `.hg/`, `.hgignore`, `.hgtags` | VCS metadata |
| `*.o`, `*.a` | Compiled object files |
| `*.swp` | Editor cruft |
| `resume/` | Local scratch / resume notes (per user instruction) |
| `ss/` | Local scratch (per user instruction) |
| `stub/` | Old Rust-like design archeology, kept for reference only (per `CLAUDE.md`) |

`tar`'s `--exclude` patterns are passed in for each item. The exclusion of `tools/*/build/` is critical — the global `build/` exclude only covers the repo-root build dir.

## 7. Code changes (one-time)

These ride along with the introduction of the bump/release scripts:

| File | Change |
|---|---|
| `src/main.reef:67-69` | Drop the inline `fn VERSION(): string` definition; `import version` and use `version.VERSION()` |
| `src/socket.reef:248` | Replace literal `"zyginit 0.1.0\n"` with `"zyginit " + version.VERSION() + "\n"` |
| `tools/zygctl/src/main.reef:54-57` | Replace literal `"zygctl 0.1.0"` with `"zygctl " + version.VERSION()` |
| `tools/zygctl/src/main.reef` | Add `import version` |
| `tools/sysv-wrapper/wrapper.c` | Add `--version` / `-V` handling: prints `"<argv[0]-basename> <ZYGINIT_VERSION>\n"` and exits 0. `#include "version.h"`. |
| `tools/sysv-wrapper/Makefile` | Add `version.h` to the dependency list of `wrapper.o` |

The argv[0]-basename trick in sysv-wrapper means `halt --version` prints `halt 0.1.0`, `reboot --version` prints `reboot 0.1.0`, etc. — consistent with how each personality identifies itself in normal output.

## 8. Output format

A single line, `<binary> <version>`, exit 0. Matches Coral and the GNU/BSD common case:

```
$ zyginit --version
zyginit 0.1.0
$ zygctl --version
zygctl 0.1.0
$ halt --version
halt 0.1.0
```

`zygctl version` (no dashes — already a subcommand) continues to work and prints the same line. Both `zygctl --version` and `zygctl version` are **strictly local** — they print zygctl's compiled-in version and never round-trip the socket. This is the standard Unix convention and keeps the answer trustworthy when the daemon is wedged.

Querying the running daemon's version (useful during a partial upgrade) is an existing socket command (`socket.reef:247`) but is currently unreachable from zygctl. Wiring up a separate `zygctl daemon-version` subcommand to surface it is **out of scope** for this design — see §10.

## 9. End-to-end workflow

Maintainer cuts release `0.2.0`:

```
$ scripts/bump-version.sh 0.2.0
zyginit: bumping 0.1.0 -> 0.2.0
  rewriting reef.toml
  rewriting tools/zygctl/reef.toml
  regenerating src/version.reef
  regenerating tools/zygctl/src/version.reef
  regenerating tools/sysv-wrapper/version.h
done. review with `hg diff` and commit:
  hg ci -m 'Bump version to 0.2.0'

$ hg diff --stat
 reef.toml                          | 2 +-
 tools/zygctl/reef.toml             | 2 +-
 src/version.reef                   | 2 +-
 tools/zygctl/src/version.reef      | 2 +-
 tools/sysv-wrapper/version.h       | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

$ hg ci -m 'Bump version to 0.2.0'

$ scripts/make-release.sh
Creating release: zyginit-0.2.0-source
Created: releases/zyginit-0.2.0-source.tar.xz
-rw-r--r-- 1 ctusa ctusa 1234567 May  5 15:30 releases/zyginit-0.2.0-source.tar.xz
Checksum: releases/zyginit-0.2.0-source.tar.xz.sha256
abc123...  releases/zyginit-0.2.0-source.tar.xz
```

Hammerhead then consumes the tarball + sha256 into `hammerhead-userland`, per the integration steps in `docs/SMF_MIGRATION.md`.

## 10. Open questions / future work

- **`scripts/install-to-be.sh`** already exists. Out of scope for this design — it's an in-place dev install, not a release artifact path.
- **Reproducible tarballs** (`--mtime`, `--sort=name`, `--owner=0`, `--group=0`). Add when the Hammerhead build needs byte-identical tarballs across maintainer machines.
- **`CHANGELOG.md`** — add when there's enough release history to warrant one. The first one or two release notes can live in commit messages.
- **GPG signing** — Hammerhead handles this at the OS package layer; no zyginit-side signing planned.
- **Pre-release versions** (e.g., `0.2.0-rc1`). Not supported by the regex in §5. Add when needed.
- **`zygctl daemon-version` subcommand.** The socket already exposes a `version` command (`socket.reef:247`) that returns the running daemon's version, but no client surfaces it. Add a `zygctl daemon-version` (or similar) subcommand to make partial-upgrade diagnosis easy. Until then, `socket.reef`'s `version` handler stays as accessible-but-unused (an `echo version | nc -U /var/run/zyginit.sock` still works).
