# zyginit Release Notes

## 0.2.7 — 2026-07-21

**Bug-fix release.** Fixes bug 004 — running the `zyginit` binary as a
non-PID-1 process (classically `zyginit -v` to check the version) fell through
into supervisor mode and **clobbered the live PID-1 control socket**, silently
breaking `zygctl` control of the running system until reboot. Three defenses:

- **Version flags exit safely.** `-v`, `-V`, and `--version` print the version
  and exit — but only when not running as PID 1. The Hammerhead kernel passes
  `-v` (verbose) as a boot-arg to init, so that path must never short-circuit,
  or PID 1 would return from `main()` and be re-exec'd in a loop.
- **The control socket is guarded.** `create_socket` now `connect()`-probes the
  path and refuses to unlink/rebind a socket a live server is already listening
  on; a non-PID-1 instance that cannot own the socket exits cleanly. PID 1
  keeps running without a control socket rather than halting.
- **Non-PID-1 supervisor mode is opt-in.** A non-PID-1 run now requires
  `--supervisor-test`; a bare `zyginit ...` on a host where zyginit already
  owns PID 1 refuses and exits before any setup. The integration harnesses and
  the CLAUDE.md dev-run invocation pass the flag.

Builds clean and passes the full integration suite (104/104) on **Reef 0.7.7**;
no source changes were required for the 0.7.7 toolchain (its newly-rejected
program shapes are all Active-Object-related, which zyginit does not use).

## 0.2.6 — 2026-07-21

**Bug-fix release.** Fixes bug 003 — spurious `exit=-1` service failures in
first-boot reports on Hammerhead. Root-cause analysis found `exit=-1` was
never an exec-failure code (a real exec failure exits 127); it was the
"could not resolve an exit code" sentinel, produced by two distinct defects:

- **Exec robustness / no silent failure.** Under first-boot I/O contention
  (devfsadm + ZFS pool-finalize) the child's exec of a valid start command
  could transiently fail, and the child then exited silently with an empty
  per-service log. The child now retries `process_exec` (5×, 100 ms backoff)
  and, on final failure, records the reason to both the per-service
  `<name>.log` (fd 2, survives a privilege drop) and a new central
  `/var/log/zyginit/zyginit.log`, then exits 127.
- **Reap-race reconciliation** (the actual source of `-1`). The
  contract-empty event on the bundle fd can beat the child's exit-status
  posting; `handle_contract_event` used to commit the `-1` sentinel and tear
  down tracking, after which the catch-all reaper discarded the real code. It
  now defers when there is no hint, no resolvable code yet, and a child is
  still tracked (`rt.pid > 0`), so the unconditional `reap_children`
  re-dispatches with the true code within ~1 s. This fixes both the 127→-1
  masking and the independent false-positive where an exit-0 oneshot that
  lost the race was reported FAILED.

New central operational log: `/var/log/zyginit/zyginit.log` (distinct from
the per-service logs). Adds integration Suite 9b (spawn-failure capture);
97/97 tests pass. The reap-race half rides the Hammerhead contract path
(not exercised by the Linux stubs) and awaits an hh-alpha first boot to
confirm in situ.

## 0.2.5 — 2026-07-17

**Reef 0.7.6 migration; no init-engine behavior change.** Migrated the suite
to Reef 0.7.6: `Result`/`Option` idioms, TOML inline arrays, and the
`socket` → `ctlsocket` module rename (avoids a clash with the stdlib
`net.socket`, which `net.unix` now imports). Internal/tooling release — no
source tarball was cut for 0.2.5.

## 0.2.4 — 2026-06-13

**Service mutual exclusion.** Adds the `[dependencies].conflicts` array:
mutually-exclusive services both fail if both are enabled, and a start is
refused if a conflicting peer is already running. Boot-time resolution fails
both sides and surfaces the reason in `zygctl status`. A one-sided
declaration is enforced symmetrically (both directions).

## 0.2.3 — 2026-06-02

**Boot/shutdown progress gauge relocated into the header divider.** The gauge
previously lived on the volatile last screen row, where interleaved
kernel/daemon writes would scroll it off. It now paints into the header's
horizontal rule. `fmt_progress_bar` and `PROGRESS_WIDTH` were removed and
`fmt_divider_gauge` added in `src/ui.reef`; two screen rows are reclaimed for
the rolling tape.

## 0.2.2 — 2026-05-21

**Repo content reshape; no behavioral change.** zyginit is now
strictly engine + examples + schema docs. The in-repo `services/hammerhead/`
tree (22 production service TOMLs) has been removed — distros own all
service-definition policy in their own overlay trees. The Hammerhead
team has absorbed the prior set into `base/usr/src/zyginit/overrides/`.

The `examples/` directory is slimmed to four heavily-commented
canonical examples:

- `daemon-with-restart/` — long-running daemon with `[restart]` policy
- `oneshot-setup/` — type="oneshot" with `[dependencies].requires`
- `task-with-condition/` — type="task" with `[condition].exists_file`
- `with-stop-script/` — `[exec].stop` + companion `stop.sh`

`scripts/install-to-be.sh` was deleted; its "deploy our services to
a BE" purpose evaporates in an engine-only world. (Distros can ship
their own equivalent.)

`docs/SMF_MIGRATION.md` has been rewritten as a methodology guide
(how to convert an SMF manifest to a zyginit TOML) rather than a
catalog of our specific conversions.

Tarball: ~22 KB smaller than 0.2.1. No `clang` / `reefc` / API changes.

## 0.2.1 — 2026-05-15

Patch release with two operational improvements:

- `type = "transient"` in 0.1.x-style TOMLs no longer silently demotes
  to `daemon`. The parser now treats it as `task` (the renamed type) and
  emits a deprecation warning. This prevents a service that should run
  as a task from hanging in STATE_STARTING after an incomplete upgrade.
- Missing `/etc/zyginit/services/` directory at boot now produces a
  distinct, actionable log message instead of silently entering the
  idle loop. The operator is told to run the migration tool from a
  recovery medium.

## 0.2.0 — 2026-05-15

**Breaking change.** The filesystem layout under `/etc/zyginit/` is
restructured. Operators upgrading from 0.1.x MUST run the migration
tool before installing 0.2.0 binaries:

    /path/to/scripts/migrate-layout.sh --dry-run    # preview
    /path/to/scripts/migrate-layout.sh --apply      # apply

The tool is idempotent (safe to re-run) and refuses to proceed if
both old and new layouts coexist for the same service.

### New: per-service directory layout

Each service is self-contained:

    /etc/zyginit/services/<name>/
    ├── service.toml
    ├── start.sh          (optional)
    └── stop.sh           (optional)

`enabled.d/<name>` symlinks target `../services/<name>/` (the
directory), not `../<name>.toml` (the file).

### New: `type = "task"` service type

For services that may run for a while but are expected to eventually
exit cleanly — platform probes, one-time setup work, etc. Replaces
the previously-unused `type = "transient"`.

    [service]
    name = "acpihpd"
    type = "task"

Semantics:
- Exit 0 → STATE_STOPPED (counts as online in the boot banner)
- Exit ≠ 0 → STATE_FAILED (counts as failed)
- Never restarts
- Skips the daemonize handshake (tasks don't double-fork)

### New: `[condition]` block

Declarative pre-flight gating. Services whose conditions fail at
startup go into a new SKIPPED state — they don't fork, don't fail,
don't loop. Supported keys:

    [condition]
    exists_file     = "/dev/acpihp"
    exists_file_any = ["/dev/acpihp", "/dev/acpi"]
    command         = "/usr/sbin/acpi_probe"     # 5-second timeout

All specified keys must pass (AND). Absent keys are no-ops. The
command form is bounded by a 5-second timeout.

### New: SKIPPED state in `zygctl status`

Skipped services render with the `·` (dim) glyph and the skip reason
in the NOTES column. The boot banner and final card include a
"skipped" counter alongside online/failed.

### Hammerhead vendoring

The Hammerhead team runs the migration tool against their override
tree in `base/usr/src/zyginit/overrides/` as part of consuming this
source drop. Follow the same dry-run → apply pattern.

### Other changes

- Renamed `SERVICE_TYPE_TRANSIENT` → `SERVICE_TYPE_TASK` in the API.
- `type = "transient"` in TOML produces a deprecation warning and is treated as `type = "task"` (see 0.2.1).

## 0.1.8 — 2026-05-14

Late-failure race fix. A 1-second settle period after the last tier
catches daemons that fork successfully but die immediately (e.g.,
acpihpd on Hammerhead without ACPI hardware).

## 0.1.7 — 2026-05-14

Boot screen no longer scrolls the `[Z]` banner off the top of the
framebuffer. Drop the trailing newline that was advancing the cursor
past the last row.

## 0.1.6 — 2026-05-14

illumos build fix: `<sys/termios.h>` now explicitly included for
`struct winsize` and `TIOCGWINSZ`. Linux's `<sys/ioctl.h>` pulls
those in transitively; illumos doesn't.

## 0.1.5 — 2026-05-14

(Broken on illumos: missing `<sys/termios.h>` for TIOCGWINSZ — use
0.1.6+.) Spinner-in-tape redesign (option B): each tape row tracks
its own `active` flag, lifecycle procs update rows in place,
parallel-startup ready. TIOCGWINSZ for dynamic screen size. Oneshot
single-count fix. Dynamic SERVICE column in `zygctl status`.

## 0.1.4 — 2026-05-14

Framebuffer-first mode detection (PID 1 + isatty + no TERM defaults
to truecolor). Sets TERM=sun-color in zyginit env so children
inherit it. Plain-mode emit no longer mislabels event types.

## 0.1.3 — 2026-05-12

Initial visual pass: `[Z]` sigil, palette, rolling tape, progress
bar, spinner, final cards, mirrored shutdown UI, `zygctl status`
banner+table.
