# Immutable Overdeck releases

## Owner-visible evidence for this phase

After a successful `packaging/deploy-local.sh --now`, a normal user write anywhere in
the deploy working tree fails with `Permission denied`. The next serialized deploy makes
that tree owner-writable only for its own checkout/build/install window and makes it
read-only again before reporting `deployed` or `deployed-docs-only`.

Generated wrappers, caches, authentication, and runtime state are not release source.
Installers must write them below `${XDG_STATE_HOME:-$HOME/.local/state}/overdeck/` (or
`$XDG_CACHE_HOME` for disposable cache), then expose a stable command with a symlink from
the configured command directory to that state-owned artifact. Configuration belongs in
`${XDG_CONFIG_HOME:-$HOME/.config}/overdeck/`. A symlink inside the release tree is skipped
by the permission walk, so the walk never changes an external state target.

The in-repo `install-seat-guard` generated-wrapper installer now writes releases under
`$XDG_STATE_HOME/overdeck/seat-guard` and exposes its established
`~/.local/share/overdeck/seat-guard` runtime path as a symlink to state.

The repository and `origin/main` contained no `install-quietlsp` on 2026-08-16. The only
references are in `docs/plans/2026-08-15-quietlsp-diagnostics-filter.md`, which records an
external, contested QuietLSP repository. The `modules/` audit found deploy-path references
used as runtime sources, but no installer that emits generated files into the deploy tree.
Therefore this phase mechanically stops that external offender at its own write; rerouting
its wrapper cannot be committed here until its source is brought into scope. Its required
layout is `$XDG_STATE_HOME/overdeck/quietlsp/wrappers/<name>` with the live command symlinked
to that artifact.

## Follow-up design: content-addressed releases

Move from one mutable checkout window to immutable `releases/<full-sha>/` directories.
A deploy stages and verifies a new SHA directory without touching the active release, then
atomically replaces a `current` symlink. Services and command indirections resolve through
`current`. Keep at least the prior verified SHA; rollback is one atomic symlink flip back
followed by the bounded service restart/health proof. A failed candidate remains dark and
cannot modify either the active release or shared runtime state.

The follow-up must define ownership and garbage collection for release directories, preserve
the standing `deploy-requested`/`--now` queue semantics, and migrate existing absolute paths
without allowing services to observe a partially populated SHA directory. This phase does
not implement that layout.

## Phase 2 verdict — sol review 2026-08-16: ON HOLD

Independent review conclusion: given phase 1's landed locking, phase 2's remaining risk
reduction does NOT justify implementation until five findings carry explicit designs and
fixtures: (1) dirty/pin failure paths it does not actually eliminate; (2) the ~/.claude
current-symlink hop vs realpath resolvers; (5) no atomic current+previous pair update —
rollback target must come from one durable activation record; (6) whole-repo pruning and
disk accounting absent (deploys stage multi-GiB; the prune fixture is web-only and would
never prune releases/<sha>); (7) rollback is not "flip back" — installed units, wrappers,
root files, migrated config, and running processes sit outside `current`. Version skew
across one agent turn (hooks resolving release A then B mid-turn) is the real tearing
class, not filesystem tears. Do not build phase 2 until these are designed; phase 1 + the
main-checkout ff-sync already close the incident classes that hurt.
