Skip to content

Known limitations

Every limitation here is documented. Each item names its class — "deliberate" (ruled out by design), "deferred" (in the roadmap), or "gap" (real bug we know about but haven't fixed).

The v2.0 release contract is to ship an honest, public list of what doesn't work yet. This page IS that list.

Deploy

Systemd unit directory is hardcoded

backend = "systemd" writes units to /etc/systemd/system/<svc>.service. Hardcoded.

  • Class: deferred — Stage 8.
  • Impact: non-root SSH deploys can't write /etc/systemd/system/. Workaround: run the deploy CLI on the remote as root, or use the services = [] (config-files-only) pattern.
  • Fix: --unit-dir flag, defaults to /etc/systemd/system/; overridable to ~/.config/systemd/user/ for user-mode systemd. Stage 8.

verify_pinned_paths checks the deploy-side store

punix service rollback STACK --target ssh://... runs the verify step on the local store (Store(store_root)), not the remote via Transport. For shared-fs fixtures this passes incidentally. For a real cross-host deploy where the target's store was GC'd independently, the verify misses it.

  • Class: gap — fix is straightforward.
  • Impact: Stage ⅚ demo: yes (low — shared fs). Production: a silent failure mode that surfaces at service start, not at rollback time.
  • Fix: route the existence check through Transport.exists. Stage 8.

Forward roll is "redeploy", not a command

There's no punix service set-current STACK N. To roll FORWARD after a rollback, re-run punix service deploy (which creates a new generation). The historical generations remain on disk.

  • Class: deferred — Stage 8 polish.
  • Impact: "oops, I rolled back to the wrong gen, take me back to gen-002" requires either re-deploying or manually editing the current symlink.
  • Fix: punix service set-current STACK N for any preserved gen.

Rollback doesn't re-write config files

punix service rollback STACK flips current but doesn't re-write the config files. Out-of-band mutation of /etc/myapp/app.conf between deploy and rollback isn't reverted by the rollback.

  • Class: deferred — small gap.
  • Impact: if /etc/myapp/app.conf was hand-edited between deploys, rollback leaves the hand-edit in place. The gen-NNN.json's config_files[].sha256 lets you DETECT the drift (punix store verify is on the roadmap), but rollback doesn't restore.
  • Fix: opt-in --rewrite-config-files on rollback (Stage 8).

Cross-arch deploys fail at push, not config time

Deploying an x86_64-built closure to an aarch64 target ⇒ the pushed binaries fail at service-start time with "exec format error". The deploy itself succeeds (the closure pushed cleanly); the runtime fails.

  • Class: deferred — Stage 8.
  • Impact: an x86 dev box can't directly deploy to an aarch64 server without a same-arch build farm (or running punix build on an aarch64 jumpbox first).
  • Fix: build on the target host through the transport seam — Stage 8.

Lifecycle commands not invoked

punix service deploy STACK writes the unit files + flips the symlink. It does NOT run systemctl daemon-reload or systemctl restart. The operator does that.

  • Class: deliberate — Stage 4 scope.
  • Impact: after a deploy, you need to systemctl daemon-reload && systemctl restart <svc> yourself (or via SSH).
  • Fix: Stage 8 asserts the running service end-to-end. The transport layer already supports it (transport.run(["systemctl", "daemon-reload"])); the CLI just doesn't invoke it yet.

Secrets

from_file lacks its own conformance test

The from_file resolver is wired but only from_env has a dedicated conformance test. from_file adds a race-condition surface (Vault-style writers, atomic-replace) that deserves its own property test.

  • Class: deferred — Stage 8.
  • Impact: from_file works in practice; the conformance test doesn't pin it.
  • Fix: add a from_file conformance test. Trivial.

Secret values in unit files at 0644

The default is to inject secrets via Environment= lines inside the systemd unit file. The unit file is mode 0644; anyone with read access to /etc/systemd/system/ sees the values.

  • Class: deliberate, revisitable.
  • Impact: depending on threat model, this may be too permissive.
  • Fix: opt-in EnvironmentFile= mode (a 0600 sidecar). Stage 8.

Secrets in source not yet wired

source = { url = "...", token = { from_env = "FETCH_TOKEN" } } — the parser accepts it; the realiser doesn't use it. v0.6 secrets are deploy-time only.

  • Class: deferred — Stage 8 (or later).
  • Impact: can't fetch private-archive sources with a token.
  • Fix: route secrets through the fetch layer at realise time. Requires care: realise is normally idempotent / cacheable, but a fetch authenticated by a secret can't be cached on the secret value itself — that would defeat the hash-exclusion property.

Language

No string concatenation

PCL has no ++ operator. Exec paths are composed in Python at the deploy seam — f"{package_path}/bin/{binary}" — not in PCL. The frontend has no string-composition operator.

  • Class: deliberate — may lift in a later stage.
  • Impact: common-case string composition is done by the deploy layer, not the user. For now, the only PCL strings users write are literals.
  • Fix: likely a typed format() function. Open question.

Pkg.out not exposed as a PCL member

The type checker doesn't know about out as a member of a package module. Stack PCL references packages by module name string (package = "Caddy"); compose_stack resolves the name to the store path at compose time.

  • Class: deliberate.
  • Impact: stack PCL is slightly less self-documenting (you write "Caddy" instead of Caddy.out).
  • Fix: add out as a typed member of every package module. Requires a frontend type-system change.