Skip to content

psmux option channel: seam-owned key namespace, sweep containment, live prune E2E (safety follow-up to #310) #313

Description

@dracic

Follow-up to #310. The option channel that lands there keys per-window state as session-scoped options (@bmad_project_@3 for window @3) and reclaims keys with two generic sweeps (kill-time, launch-time orphan reconcile). Three review rounds on that branch confirmed the design and also settled three ceilings deliberately, with eyes open — this issue is the ledger for revisiting them as one safety-core unit, not a defect report against the channel.

1. The key namespace is convention-safe, not airtight

The _@<digits> suffix keeps foreign config options out of the sweeps because no naming convention in the tmux/psmux ecosystem puts @ mid-name — but psmux accepts any @ name in the map (server/options.rs:528-529 at v3.3.7), so a hand-written @theme_@3 in a user's config would match both sweeps and be deleted when window @3 dies. A caller-name prefix guard (@bmad_*) was rejected because the backend must stay generic over option names.

The airtight shape that preserves genericity: a seam-owned marker in the key grammar — owned by the channel, not by any caller — e.g. <option>__blw@<digits>. The sweep then matches only keys the seam itself minted. Costs a one-time key-format migration (the orphan reconcile can carry a transition rule for the old suffix), which is why it should ride a planned change rather than a hotfix.

2. Destructive-sweep containment edges

  • Cleanup-before-kill: kill_window frees the window's keys, then issues a best-effort kill-window. A kill that then fails leaves a live window key-less — its project tag gone (prune retry degrades to the untagged run-dir fallback) and its return key gone (an attached client parks without a way back). The order is mandated by the accepted contract on the psmux: per-window user options are session-scoped - the ctl-window project guard cannot discriminate and the parked-return mechanism is inert #310 branch; flipping to a verify-then-clean shape (or post-kill reconcile-only) needs that contract renegotiated, which is cheap to do here together with (1).
  • Foreign-key deletion is fully closed by (1); until then it remains the documented residual risk above.

3. Elevate the cross-project prune test to live E2E

The branch adds an acceptance-level unit test (_ctl_window_candidates over the real psmux backend with the subprocess seam faked) asserting only the owning project's dead-run window is a prune candidate. What no unit layer can prove is the live half: a real psmux 3.3.7 server with two projects' windows on the shared ctl session, a prune in one, and the other's window surviving kill-window. Same shape as the existing zero-token live gates — Windows-local, skipped elsewhere.

Related, smaller: name-token window targeting (=session:<window-name>) resolves first-match on duplicate ctl-window names, so option writes/frees can land on the wrong duplicate. Inherited from the callers' name-token design (tmux's native resolution is equally first-match); the migration path is the id-based targeting the #291 seam already provides — worth folding into this unit since (1) touches every key call site anyway.

Ordering

After the #310 PR merges — (1) changes the key grammar that PR introduces, and doing both in one release avoids migrating twice.

Refs: #310, #291, #288, psmux/psmux#321

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions