Zester v0.4.2
Fleet-wide state-module convergence audit. After the pkg.latest
stale-index bug shipped in the field, every state module (44 states, 36
files) was audited against the Check/Apply contract; 48 adversarially
verified defects across 25 states are fixed in this release. Three defect
classes dominated: Check blind to a facet only Apply enforces (drift never
converges, reported compliant forever), destructive or dishonest Revert
paths, and read errors conflated with "file absent" (data-loss paths).
Fixed
pkg.latestrefreshes the package cache BEFORE checking upgradability
(Salt parity). Refresh (default on) ran only in Apply, but Check consulted
the stale index and short-circuited "already at latest" — so Apply, and
with it the refresh, never executed: any release published after the box's
last cache refresh was invisible (field symptom: the first fleet-wide
pkg.latest zester-peelafter the 0.4.1 repo publish was a silent
changed: 0no-op on every apt host). Check and Apply now each run their
own refresh independently. A FAILED refresh (one rotted third-party repo
failsapt-get updatewhile reachable repos still updated) warns and
proceeds in both phases instead of failing the state;--testdry runs
refresh the index too (metadata-only).pkg.installedhonors a declaredversion:pin in Check. Any
installed version used to satisfy a pinned state, so version drift was
compliant forever. Check now compares the installed version against the
pin (got/want diff). Pinned downgrades converge on every provider: apt
passes--allow-downgrades, yum verifies the pin landed and falls back
toyum downgrade(plainyum install pkg-<older>silently no-ops),
dnf handles explicit version downgrades natively. Undeclaredversion:
is unchanged.pkg.purgedtreats a removed-but-not-purged Debian package (dpkgrc
state) as needing a purge — converged only when no package record
exists at all; previouslyrcreported converged and leftover conffiles
were never removed. The apt probes also fail loudly when the probe never
ran (spawn failure/context death) instead of reporting "not installed",
and parse multi-arch dpkg output correctly.cron.present,sysctl.present, andmount.mountednow actually work
on real peels. Their exec providers (crontab/procfs/fstab) were never
wired into provider detection — every real-peel run failed with "no
cron/sysctl/mount provider available" (unit tests wired providers
manually, masking it).file.managedenforces themode:facet on pre-existing files.
Apply wrote content via a create-time-perm-only write and never chmodded,
so mode drift on an existing file was reported by Check forever but never
fixed — permanent churn firingwatchdependents every highstate. Apply
now chmods after writing; Revert restores the CAPTURED prior mode, not
the desired one. Octal modes with setuid/setgid/sticky digits
("4755", "1777") are translated to the Go FileMode flags — previously the
special bit was silently dropped by chmod and the state re-applied forever
without ever setting it — and ownership is applied BEFORE mode (chown on
an executable clears setuid). Applies to file.managed, file.directory,
and file.recurse modes.- Debian
rc-state packages (removed, conffiles remain) no longer count
as installed. The apt probe requires dpkg statusinstalled
(dpkg-query -W -f='${db:Status-Status}') instead of thedpkg -sexit
code —pkg.installedcould never reinstall a previously-removed
conffile-bearing package, andpkg.removedre-applied (and firedwatch
dependents) on every highstate after its own successful removal. pkgrepo.managedverifies the Debian signing key. Thekey_urlkey
now lands persistently at/etc/apt/keyrings/zester-<name>.gpg(was a
volatile/tmpdownload) and Check reports drift when it is missing —
previously a never-imported/deleted key leftapt-get updatefailing
NO_PUBKEY while the state reported converged. Presence-only (in-place key
rotation at the same URL is not detected); Revert removes the artifact.- File states detect ownership drift.
file.managed,file.directory,
andfile.recursecompare on-disk owner/group against declared
user:/group:in Check (declared facets only — undeclared ownership
never churns).file.recurseCheck also flagsclean: trueextra files
(previously the clean feature could never fire once the managed set
converged) anddir_modedrift on every managed dir, and all its
filesystem walks now go through the injected file provider
(newFileExec.Walk). service.running/service.deadcompare a declaredenable:facet in
Check — running-but-disabled (enable: true) and stopped-but-enabled
(enable: false, resurrects at reboot) were compliant forever.
service.runningwithenable: falsenow actually disables; an Apply
reached only for enable drift never restarts the running service.user.presentconverges password and name-based primary group. A
declaredpassword:hash is compared against the shadow hash (new
UserExec.PasswordHash); a name-basedgid:/primary_group:is compared
and enforced on existing users viausermod -g— both facets were
silently unenforced outside user creation.mount.mountedfstab comparison now includesdump/pass, Apply
no-ops honestly when converged, and Revert no longer claims an unmount it
never performed. (Comparing the LIVE mount — a wrong device/options
serving the mountpoint — is deferred: it needs Salt-style
option/fstype/device normalization to avoid remount churn on
kernel-normalized values; that audit finding stays open on the backlog.)sysctl.presentwithpersist: trueverifies the drop-in file entry,
not just the runtime value — a manualsysctl -wmatch silently died at
the next reboot.cron.presententries are keyed on the label (identifier comment,
Salt semantics) instead of the exact command string — editing a state's
command replaces the old line instead of orphaning it to run forever.
Managed entries carry a# ZESTER_CRON_ID: <label>marker; pre-existing
same-command lines are adopted (a human descriptive comment neither
blocks adoption nor duplicates the job) and stamped on the next apply.
Converged Apply is a no-op, so watch-forced runs no longer rewrite the
crontab. Crontab edits are LINE-PRESERVING: Set/Remove splice only
the targeted entry and its marker — MAILTO=/PATH= environment lines,
human comments, blank lines, and @reboot/@daily nickname entries survive
untouched (the previous whole-crontab reconstruction silently destroyed
them), and a Remove that matches nothing does not rewrite the crontab at
all. Commands are compared whitespace-normalized, so a declared command
with consecutive spaces converges instead of rewriting every run.git.cloned/git.latestwith a symbolicrev:(tag) converge —
the rev was compared against the HEAD sha by string prefix, which never
matches a tag name, so every run re-applied (needless fetches,watch
cascades, service restarts every highstate). Symbolic revs resolve locally
viagit rev-parse --verify <rev>^{commit}; sha prefixes still match
directly.locale.presentverifies the/etc/locale.genenabling line that
Apply writes, not justlocale -amembership — an out-of-band-generated
locale was compliant until the nextlocalespackage upgrade silently
dropped it. The facet applies only where/etc/locale.genexists
(Debian-family); RHEL/musl systems stay satisfied bylocale -a, no
straylocale.genis created, and charmap spellings are normalized
(en_US.utf8==en_US.UTF-8).- Read errors are no longer conflated with "file absent" anywhere.
host.present,ssh_auth.present, every text-editing file state
(file.line/append/blockreplace/comment/keyvalue/replace),
file.managed/copy/recurse,cmd.run'screatesprobe,
locale.present, and the archive marker all treated ANY read error as
"missing" — a transient EIO/EACCES/ESTALE followed by a successful write
could truncate/etc/hostsor a user'sauthorized_keysdown to the one
managed line, or clobber file content that was never captured. Only
fs.ErrNotExistselects the absent path now; anything else fails the
phase without writing. - Revert contract: a fresh instance never destroys state. Revert
consuming in-instance memos (backups, created flags, saved originals)
treated "memo unset" as "file was new" and DELETED the target — and the
runner builds FRESH instances for revert, so a revert run would have wiped
/etc/hosts,authorized_keys, or any managed file. Fresh-instance
Revert is now an explicit clean no-op across ALL modules
("nothing to revert (no apply recorded in this run)"); same-instance
Apply→Revert still restores backups (with canonical permissions —
authorized_keysrestores 0600) and still removes files the same
instance created. (tolerating an already-externally-removed file). Related honesty fixes:sysctl.presentRevert no
longer writes an EMPTY value into the kernel and persist file;
mount.mountedRevert no longer claims an unmount it never performed;
group.presentRevert really restores membership (it only restored the
GID);service.runningRevert reports enable-reverts. - Watch-forced applies are safe on guarded/absent states.
cmd.run's
createsguard now also gates Apply (a watch trigger used to re-run
creates-guarded one-shots likeinitdb);user.absent/group.absent
Apply no-op cleanly when already absent instead of failinguserdel/
groupdel;service.dead/mount.mounted/sysctl.present/user.present
Apply can now honestly reportchanged: falsewhen converged.
archive.extractedApply re-evaluates itsif_missing/source_hash
guard itself, so a watch trigger no longer re-downloads and re-extracts a
converged archive.
Added
archive.extractedgainssource_hash: recorded in a marker after a
successful extraction and compared by Check, so bumping
source/source_hashre-extracts instead of no-oping forever. Opaque
string comparison (not byte verification); the marker is only written
after success, so a failed extract no longer latches amakedirs-created
dir as done. Withoutsource_hash, existing marker semantics are
unchanged and now documented.- Exec-layer convergence probes (internal API): status-aware apt
installed-probe,PackageExec.InstalledVersion,FileExec.Owner/Walk,
UserExec.PasswordHash,UserModifyOpts.PrimaryGroup; test fakes wrap
fs.ErrNotExistfor missing files and support read-error injection.
Changed
pkg.removedRevert is an explicit clean no-op — it previously
reinstalled the repo's latest candidate driven by a never-populated memo.
Reinstall explicitly withpkg.installed.pkgrepo.managedDebian key artifact moved from
/tmp/zester-repo-<name>.gpgto/etc/apt/keyrings/zester-<name>.gpg;
existing fleets show ONE pending change per keyed repo on the next
highstate (idempotent re-import).