feat(session): spec mode — the renderer stops resolving policy and stops writing settings

C2 of design/client-architecture-split.md, and the last of the session's overreach.

`--resolved-spec <path>` hands the session everything it needs already resolved —
effective settings, the host's clipboard decision, the profile's name — and in that mode it
performs ZERO store reads. It had been re-deriving all three, which meant policy was being
evaluated inside the thing that draws pixels, and that the spawner and the child could
disagree about a file either of them might have written in between. First-party spawns
(the shells, the CLI) always pass one now.

The compat path stays for hand-run `punktfunk-session --connect` and old Decky scripts —
but it calls the SAME helper, so the two modes cannot drift; it is the identical function
invoked in-process instead of by the parent. A spec that is named but unreadable fails
loudly rather than quietly falling back: a spawner that asked for exact settings must not
get store-derived ones instead.

The match-window write-back is gone too. The callback used to load-modify-save the shared
settings file from inside the renderer — one of that file's five concurrent writers, for a
value only the parent needs. It now reports `{"window":{w,h}}` on stdout and the spawner
persists it, on a real change only. A hand-run session still persists its own window,
because nobody is listening to its stdout there and the event alone would drop the value.

Verified on .21: a spec naming a profile that doesn't exist in the catalog is honoured
(proving no lookup happened), a missing spec errors instead of falling back, and the CLI's
spec file is written and cleaned up per launch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 01:16:17 +02:00
co-authored by Claude Opus 5
parent 0594056e02
commit f32c3aaa71
5 changed files with 250 additions and 24 deletions
+2
View File
@@ -571,6 +571,8 @@ punktfunk:// link takes. Exit codes: 0 ok, 2 connect, 3 trust, 4 renderer, 5 not
trust_rejected,
} => failure = Some((msg, trust_rejected)),
SessionEvent::Ended(reason) => eprintln!("{reason}"),
// Persisted by the brain on the way past; nothing to report here.
SessionEvent::Window { .. } => {}
SessionEvent::Exited(code) => {
return match failure {
Some((msg, true)) => {