Skip to content

punix verify

Report config-file and store drift against a stack's current generation.

punix verify STACK [--target ssh://…] [--transport-root PATH]

What it answers

Is this host still what I declared?: the question Chef and Puppet earn their agents with, and the one Ansible cannot answer at all because it keeps no record of the declaration.

Punix already records a sha256 for every config file in every generation, and the store is content-addressed. So this is a read-only command over data the deploy already wrote: no agent, no daemon, no convergence loop, nothing installed on the target beyond what a deploy already needed.

It never repairs. A drifted file is still drifted after verify returns. Putting it back is punix service rollback; an audit command that mutates the host it audits is not an audit command.

Example

$ punix verify WebStack
ok  /etc/systemd/system/api.service
MODIFIED    /etc/nginx/conf.d/app.conf  (recorded 3f2a91cc4b07… now 8e14bd0a72f1…)
missing /etc/app/app.conf  (declared, not on the target)
skipped /etc/punix/env/knot.env  (artifact-bearing (recorded by reference))
ok  /store/a1b2…-caddy-2.9.1
missing /store/c3d4…-postgres-17.2  (pinned store path)
WebStack gen-003: 6 checked, 3 clean, 1 MODIFIED, 2 missing, 1 unverifiable
$ echo $?
1

Exit 0 when the host matches the declaration, 1 when anything drifted, 2 on a usage error.

What each state means

state meaning what to do
ok the bytes on the target hash to what the generation recorded nothing
MODIFIED the file exists but its contents changed out of band service rollback, or redeploy, or accept and redeploy to re-record
missing a declared config file, or a pinned store path whose built marker is gone redeploy; a missing store path means the generation cannot actually run
unreadable the transport could not read it (permissions, a broken remote) an operational problem, not drift
skipped the file cannot be verified by re-hashing; see below nothing; this is expected

The two things it cannot check, and why

Artifact-bearing files are skipped. A file consuming a cross-host artifact records its sha256 in reference form (the bytes with each digest rendered as a stable {artifact:host/name} token) so that a producer renewing its certificate does not change the consumer's identity (ADR-028 ARTIFACT-REF-HASH). The bytes on disk carry the resolved digest, so re-hashing them would report drift on every healthy host. They are unverifiable by construction; guessing would make the command useless.

Files Punix never wrote are invisible. verify checks the declaration, not the whole filesystem. A stray /etc/nginx/conf.d/rogue.conf that no generation ever mentioned is not drift by this definition: Punix has no opinion about files it does not own.

What it does check that you might not expect

Secret-bearing files are verified. A file marked secret = true keeps its payload out of the generation (§20.6) but its hash is still recorded. So drift on exactly the files an operator most wants watched (credentials, keys, env files) is detectable, without the manifest ever having held the secret.

Store paths are checked on the target, through the transport. Validity is the built marker, the same predicate Store.is_valid uses. This is stricter than punix service rollback, which still verifies pinned paths against the deploy-side store; a known gap. verify asks the host.

Options

  • --deployments-root PATH: where gen-NNN.json lives (default ~/.punix/deployments).
  • --target ssh://user@host[:port]: verify a remote host. Mutually exclusive with --transport-root.
  • --key / --known-hosts: SSH credentials, as for service deploy.
  • --transport-root PATH: rebase the filesystem surface (hermetic runs and tests).