Skip to content

Known limitations

Every project has this list. Most keep it in a bug tracker nobody reads. The v2.0 release contract is to publish it.

Each item names its class:

  • deliberate: ruled out by design. It isn't coming, and the entry says why.
  • deferred: we know how to do it and it's on the roadmap.
  • gap: a real bug we know about and haven't fixed yet.

If you hit something that isn't on this page, it's a bug report we want.

Deploy

scope = "user" is local-only

A stack declaring scope = "user" writes under your home directory as you (units to ~/.config/systemd/user/, activated with systemctl --user, no root). But the home directory is resolved on the machine running punix, so deploying a user-scope stack to a remote host with --target is refused rather than writing your local path onto someone else's machine.

  • Class: gap: the resolution needs to move to the target.
  • Impact: "manage my own machine" works; "manage my account on ten machines" does not. Run the deploy on the machine you want configured.
  • Fix: ask the Transport for the target's home rather than resolving locally.

See manage your dotfiles for what user scope does today.

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. punix verify already does exactly this, so the shape is settled: rollback just has not adopted it. Until it does, punix verify is the command that catches a target-side collection.

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.

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.

The PCL service vocabulary is systemd-shaped

serviceConfig and timer are [Section] directive passthroughs, so a stack that uses them is written against systemd's vocabulary rather than a neutral one. The seam itself is systemd-agnostic: a backend is a renderer plus an activator, both dispatched from registries. The fields a user writes are systemd-shaped.

  • Class: deferred: de-leaking it is a real design slice.
  • Impact: a second backend can render and activate, but a stack authored for systemd won't port to it unchanged.
  • Fix: a neutral service vocabulary that each backend lowers, with the passthrough kept as an escape hatch.

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.

EnvironmentFile is chosen for you, and you can't opt back out

A service whose environment contains a resolved secret is rendered with a 0600 EnvironmentFile= sidecar rather than inline Environment= lines, whether or not you asked (B3c / ADR-027). A secret must not land in a world-readable unit. Setting environmentFile = true opts a service in for non-secret values too; there is no way to opt a secret-bearing service out.

  • Class: deliberate.
  • Impact: none we know of. Noted because "where does my secret end up on disk" deserves a straight answer, and because the mode is not yours to choose (the kernel owns file modes).

Secrets in source not yet wired

source = { url = "...", token = { from_env = "FETCH_TOKEN" } }: the parser accepts it; the realiser doesn't use it. 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.