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
Transportfor 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 verifyalready does exactly this, so the shape is settled: rollback just has not adopted it. Until it does,punix verifyis 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
currentsymlink. - Fix:
punix service set-current STACK Nfor 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 buildon 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_fileworks in practice; the conformance test doesn't pin it. - Fix: add a
from_fileconformance 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 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.