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 theservices = [](config-files-only) pattern. - Fix:
--unit-dirflag, 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
currentsymlink. - Fix:
punix service set-current STACK Nfor 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.confwas hand-edited between deploys, rollback leaves the hand-edit in place. The gen-NNN.json'sconfig_files[].sha256lets you DETECT the drift (punix store verifyis on the roadmap), but rollback doesn't restore. - Fix: opt-in
--rewrite-config-fileson 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 buildon 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_fileworks in practice; the conformance test doesn't pin it. - Fix: add a
from_fileconformance 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 ofCaddy.out). - Fix: add
outas a typed member of every package module. Requires a frontend type-system change.
Related¶
- Roadmap — Stages 7, 8, 9, 10.
- Reference: decisions — design pins for some of these limitations.