projmux 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -208,6 +208,21 @@ real-provider observation is opt-in through
208
208
  `scripts/agent-dialogue-live-canary.sh`; each version-stress row must qualify
209
209
  independently.
210
210
 
211
+ `TestClaudeDialogueStreamReplaysObservedFrameCorpus` pins the reply-only
212
+ validator's verdict on preserved real frame shapes. Neither it nor L20 proves
213
+ installed-provider compatibility, value drift (the corpus keeps top-level key
214
+ shape with placeholder values), or acceptance of unknown frames and fields (the
215
+ closed vocabulary rejects them by design). The L20 fixture emits none of the
216
+ corpus's dropped side frames. Two recorded real shapes are rejected today:
217
+ `result-success-25-keys` (the result allowlist lacks `origin`) and
218
+ `system-init-allowlist-diff` (the init allowlist lacks `memory_paths` and
219
+ `terminal_slash_commands`). Shapes without preserved evidence are gaps, not
220
+ items: `command_lifecycle` keys and values, a non-null assistant
221
+ `context_management`, `system` `thinking_tokens`, hook and user `tool_result`
222
+ frames, the exact real init key set, every nested value, `rate_limit_event`
223
+ envelope keys beyond `type` and `rate_limit_info`, and the lost earlier shape
224
+ inventories.
225
+
211
226
  Provider sources: [SessionStart and Stop hooks](https://code.claude.com/docs/en/hooks)
212
227
  and [cross-session messaging](https://code.claude.com/docs/en/cross-session-messaging).
213
228
 
package/docs/cli-guide.md CHANGED
@@ -376,10 +376,16 @@ The generated `MouseDown3Pane` menu treats its Horizontal Split, Vertical
376
376
  Split, and Kill entries as Projmux resource actions. Both splits pass the exact
377
377
  clicked pane through the popup-origin anchor above and reach the same canonical
378
378
  `create pane` materializer as the CLI, so the new pane receives a Registry uid.
379
- Kill resolves that anchor's mirrored uid and reaches canonical `delete pane`,
380
- including its printed delete result. The menu never falls back to a raw tmux
381
- mutation when either route refuses. The reason is displayed on the exact client
382
- that opened the menu instead of being lost as a `run-shell` exit code.
379
+ Kill on a pane with `@projmux_pane_uid` resolves that anchor's mirrored uid and
380
+ reaches canonical `delete pane`, including its printed delete result; a pane
381
+ without the mirror runs tmux's own `kill-pane` instead of being refused. The
382
+ menu never falls back to a raw tmux mutation when either route refuses. The
383
+ reason is displayed on the exact client that opened the menu instead of being
384
+ lost as a `run-shell` exit code. The app config's `prefix >` and
385
+ `M-MouseDown3Pane` Pane menus follow the same rules, and its `prefix <`,
386
+ `MouseDown3Status`, and `M-MouseDown3Status` Window menus route Kill on a
387
+ mirrored Window to canonical `delete window`; see
388
+ [Managed menus](keybindings.md#managed-menus).
383
389
 
384
390
  tmux Respawn has no equivalent in the current resource model: it preserves the
385
391
  same pane handle, layout, Registry uid, and original command, while canonical
@@ -389,6 +395,23 @@ menu therefore omits Respawn entirely and does not expose a refusal handler or
389
395
  invent a replace operation. User-authored tmux bindings remain outside this
390
396
  managed-menu contract.
391
397
 
398
+ #### Managed close keys
399
+
400
+ The generated app config routes tmux's `prefix x` and `prefix &` through the
401
+ same resource routes whenever the target carries a Registry identity mirror.
402
+ `prefix x` on a Pane with `@projmux_pane_uid` confirms on the exact client, then
403
+ runs the Pane menu Kill route above: canonical `delete pane` with an
404
+ `intentional` termination receipt. The Agent that owned the Pane stays in the
405
+ Registry as Offline with a `deleted` exit, which Continue never replays.
406
+ `prefix &` on a Window with
407
+ `@projmux_window_uid` confirms, then runs `internal tmux window-delete`, which is
408
+ canonical `delete window` on that exact Window; a Project's last Window leaves a
409
+ zero-Window Project. A refusal is shown on that client and never falls back to
410
+ a raw kill. A target without the mirror keeps tmux's stock `confirm-before`
411
+ prompt and kill, with no Registry write. See
412
+ [Managed close keys](keybindings.md#managed-close-keys) for changing or
413
+ disabling the keys.
414
+
392
415
  ### Rename and rebind live convergence
393
416
 
394
417
  `rename project|window|pane` commits the selected Registry `metadata.name` and
@@ -537,7 +560,9 @@ generated `pane-exited` hook. The supervisor must have durably journaled a
537
560
  same-generation `normal` receipt, the hook must name the exact `%N` Pane and
538
561
  the owner Window must carry its exact last-positive `$N/@N` binding on the same
539
562
  socket, and fresh preflight plus locked observations must still resolve the
540
- same Registry owner chain. A non-last Pane is removed while its directly owning
563
+ same Registry owner chain. This includes Windows re-materialized by Project
564
+ start/Continue topology replay: the replay records each Window it materializes
565
+ with its live `$N/@N` in the same commit. A non-last Pane is removed while its directly owning
541
566
  Agent is retained Offline with its conversation identity. For a last Pane, that evidence is retained until a
542
567
  matching `window-unlinked` hook removes the Window; a final Project Window also
543
568
  removes its Window descendants while retaining the exact Project uid, root,
@@ -549,6 +574,14 @@ content, prompt, history, and transcript are never parsed. `abnormal`,
549
574
  `killed`, `unknown`, stale/resumed bindings, empty or unavailable inventory,
550
575
  permission failure, and foreign-host/window observations keep their diagnostic
551
576
  rows and produce no automatic delete plan.
577
+ Every teardown decision on this hook path appends one
578
+ `topology.teardown.decision` journal record (`projmux diagnostics log
579
+ --component topology`) with its `delete-window`/`delete-pane-agent`/`retain`/
580
+ `refuse` decision, `topology.teardown.<reason>` code, Window/Pane UID, and
581
+ termination classification. An unpaired `window-unlinked` records `retain`
582
+ with `topology.teardown.awaiting-pane-exit` when it first waits and again when
583
+ its bounded pair wait is exhausted; `kill-window` closes surface this way,
584
+ because tmux fires no `pane-exited` for them.
552
585
 
553
586
  For `pane-exited`, tmux supplies `%N` as `#{hook_pane}`. Its current-context
554
587
  session/window formats may already name a surviving client Window, so the owner
@@ -760,6 +793,14 @@ Registry-only result reports that no tmux Pane was killed, preserves the owning
760
793
  Window/root/socket and all siblings, and repeating the exact apply returns the
761
794
  ordinary no-match result.
762
795
 
796
+ A name, scope, or active-target delete of a target that carries that evidence
797
+ still refuses with the live-mirror refusal, but the refusal then names the
798
+ typed resource's exact form to run instead —
799
+ `projmux delete pane|agent uid:<uid> --socket-path <server> --dry-run`, then the
800
+ same command with `--yes` — and says which evidence qualifies it. A target the
801
+ `uid:` form would refuse too, such as a Pane without `MissingRuntime` or a
802
+ `Running` or `Failed` Agent, gets no such pointer.
803
+
763
804
  `delete window|pane|agent` names the server its live half addresses the same
764
805
  way `reconcile resources` does: `--socket <name>`, `--socket-path <absolute>`,
765
806
  or the inherited absolute `$TMUX`. Outside tmux with neither flag it refuses
@@ -1144,7 +1185,9 @@ autosave-session-state`, `window record`, and
1144
1185
  successful `diagnostics log` views do not produce an event. Errors from those
1145
1186
  automatic hook/poll paths still produce one safe `error` outcome. Successful
1146
1187
  direct command help and explicit `--dry-run` preview modes also remain
1147
- read-only and do not produce an event. Journal failures are a best-effort side
1188
+ read-only and do not produce an event. The automatic `internal tmux converge`
1189
+ route still appends one typed `topology.teardown.decision` record per Window or
1190
+ Pane teardown decision it consumes. Journal failures are a best-effort side
1148
1191
  channel and never change command output or exit status. See
1149
1192
  [operational-diagnostics.md](operational-diagnostics.md) for the file,
1150
1193
  retention, concurrency, and privacy contracts.
package/docs/cli.md CHANGED
@@ -130,7 +130,7 @@ projmux agent app-server upgrade resume|abort --operation <ref>
130
130
  projmux agent app-server handover plan|apply --request <absolute-json>
131
131
  projmux agent app-server handover resume|abort --operation <ref>
132
132
  projmux agent capabilities [<agent-ref> | --provider <codex|claude|antigravity>] [-o json]
133
- projmux agent message send <agent-ref> [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
133
+ projmux agent message send <agent-ref> [--source <agent-ref>] [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
134
134
  projmux agent message status <message-ref> [-o json]
135
135
  projmux agent message qualify <claude-agent-ref> --evidence <absolute-private-json> --confirm-isolated-provider-push -o json
136
136
  projmux agent wait <agent-ref> [--until idle] [--timeout <duration>] [-o json]
@@ -150,7 +150,7 @@ Subcommands:
150
150
  | [`projmux agent usage`](#projmux-agent-usage) | Read provider account usage quota snapshots |
151
151
  | [`projmux agent app-server`](#projmux-agent-app-server) | Manage explicitly requested private Codex app-server generation operations |
152
152
  | [`projmux agent capabilities`](#projmux-agent-capabilities) | Read static provider support or one exact Agent's Registry-backed runtime eligibility |
153
- | [`projmux agent message`](#projmux-agent-message) | Exchange bounded untrusted coordination messages through exact Agent activations |
153
+ | [`projmux agent message`](#projmux-agent-message) | Exchange bounded untrusted coordination messages; --source selects a source Agent anchor, not caller authentication (default: active Pane) |
154
154
  | [`projmux agent wait`](#projmux-agent-wait) | Wait read-only for one exact Agent's Registry-backed idle observation |
155
155
 
156
156
  Canonical spelling: `projmux agent status`, `projmux agent topic`, `projmux agent resume`, `projmux agent turn start`, `projmux agent turn steer`, `projmux agent turn interrupt`, `projmux agent approval review`, `projmux agent review`, `projmux agent integrate`, `projmux agent usage`, `projmux agent app-server upgrade qualify`, `projmux agent app-server upgrade plan`, `projmux agent app-server upgrade apply`, `projmux agent app-server upgrade resume`, `projmux agent app-server upgrade abort`, `projmux agent app-server handover plan`, `projmux agent app-server handover apply`, `projmux agent app-server handover resume`, `projmux agent app-server handover abort`, `projmux agent capabilities`, `projmux agent message send`, `projmux agent message status`, `projmux agent message qualify`, `projmux agent wait`
@@ -736,7 +736,7 @@ Output modes (`-o`): `json`
736
736
 
737
737
  ### `projmux agent message`
738
738
 
739
- Exchange bounded untrusted coordination messages through exact Agent activations
739
+ Exchange bounded untrusted coordination messages; --source selects a source Agent anchor, not caller authentication (default: active Pane)
740
740
 
741
741
  Selectorless authority: `refusal` — there is no safe selectorless action; refuse before output or mutation.
742
742
 
@@ -752,7 +752,7 @@ Allowed effects:
752
752
  - `domain-effect=agent-delivery`
753
753
 
754
754
  ```
755
- projmux agent message send <agent-ref> [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
755
+ projmux agent message send <agent-ref> [--source <agent-ref>] [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
756
756
  projmux agent message status <message-ref> [-o json]
757
757
  projmux agent message qualify <claude-agent-ref> --evidence <absolute-private-json> --confirm-isolated-provider-push -o json
758
758
  ```
@@ -761,7 +761,7 @@ Subcommands:
761
761
 
762
762
  | Route | Summary |
763
763
  | --- | --- |
764
- | [`projmux agent message send`](#projmux-agent-message-send) | Submit a bounded coordination message from the current exact Agent |
764
+ | [`projmux agent message send`](#projmux-agent-message-send) | Submit bounded peer coordination; --source selects a source Agent anchor, not caller authentication (default: active Pane) |
765
765
  | [`projmux agent message status`](#projmux-agent-message-status) | Read a payload-free broker delivery receipt |
766
766
  | [`projmux agent message qualify`](#projmux-agent-message-qualify) | Explicitly qualify one exact Claude target using owned current-version isolation evidence and one marker push |
767
767
 
@@ -769,7 +769,7 @@ Canonical spelling: `projmux agent message send`, `projmux agent message status`
769
769
 
770
770
  #### `projmux agent message send`
771
771
 
772
- Submit a bounded coordination message from the current exact Agent
772
+ Submit bounded peer coordination; --source selects a source Agent anchor, not caller authentication (default: active Pane)
773
773
 
774
774
  Selectorless authority: `explicit-target` — the route or caller must name the exact target.
775
775
 
@@ -785,7 +785,7 @@ Allowed effects:
785
785
  - `domain-effect=agent-delivery`
786
786
 
787
787
  ```
788
- projmux agent message send <agent-ref> [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
788
+ projmux agent message send <agent-ref> [--source <agent-ref>] [--message-ref <ref>] [--reply-to <ref>] [--ttl <duration>] -- <text>
789
789
  ```
790
790
 
791
791
  #### `projmux agent message status`
@@ -2003,10 +2003,10 @@ Subcommands:
2003
2003
 
2004
2004
  | Route | Summary |
2005
2005
  | --- | --- |
2006
- | [`projmux get projects`](#projmux-get-projects) | List Project resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context |
2007
- | [`projmux get windows`](#projmux-get-windows) | List Window resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2008
- | [`projmux get panes`](#projmux-get-panes) | List Pane resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2009
- | [`projmux get agents`](#projmux-get-agents) | List Agent resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2006
+ | [`projmux get projects`](#projmux-get-projects) | List Project resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context |
2007
+ | [`projmux get windows`](#projmux-get-windows) | List Window resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2008
+ | [`projmux get panes`](#projmux-get-panes) | List Pane resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2009
+ | [`projmux get agents`](#projmux-get-agents) | List Agent resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry |
2010
2010
  | [`projmux get runtime`](#projmux-get-runtime) | List every tmux Session, Window, and Pane on one exact server with its attribution |
2011
2011
  | [`projmux get notifications`](#projmux-get-notifications) | List pending notification rows |
2012
2012
  | [`projmux get snapshots`](#projmux-get-snapshots) | List saved session snapshots |
@@ -2016,7 +2016,7 @@ Canonical spelling: `projmux get projects`, `projmux get windows`, `projmux get
2016
2016
 
2017
2017
  ### `projmux get projects`
2018
2018
 
2019
- List Project resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context
2019
+ List Project resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context
2020
2020
 
2021
2021
  Selectorless authority: `explicit-fan-out` — the route spelling is an intentional global or whole-set opt-in.
2022
2022
 
@@ -2041,7 +2041,7 @@ Output modes (`-o`): `uid`, `name`, `ref`, `metadata`, `json`, `none`, `wide`
2041
2041
 
2042
2042
  ### `projmux get windows`
2043
2043
 
2044
- List Window resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2044
+ List Window resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2045
2045
 
2046
2046
  Selectorless authority: `natural-omitted` — omission resolves one predictable current resource or documented contextual read/scope; any selector replaces it.
2047
2047
 
@@ -2066,7 +2066,7 @@ Output modes (`-o`): `uid`, `name`, `ref`, `metadata`, `json`, `none`, `wide`
2066
2066
 
2067
2067
  ### `projmux get panes`
2068
2068
 
2069
- List Pane resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2069
+ List Pane resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2070
2070
 
2071
2071
  Selectorless authority: `natural-omitted` — omission resolves one predictable current resource or documented contextual read/scope; any selector replaces it.
2072
2072
 
@@ -2089,7 +2089,7 @@ Output modes (`-o`): `uid`, `name`, `ref`, `metadata`, `json`, `none`, `wide`
2089
2089
 
2090
2090
  ### `projmux get agents`
2091
2091
 
2092
- List Agent resources as NAME STATUS ACTIONS; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2092
+ List Agent resources as NAME STATUS ACTIONS AGE; route-implied KIND is omitted, shifting stdout positions; -o wide retains KIND and diagnostics, and -o json retains kind and invocation context; inside tmux defaults to the active managed root, and --all-projects lists the whole Registry
2093
2093
 
2094
2094
  Selectorless authority: `natural-omitted` — omission resolves one predictable current resource or documented contextual read/scope; any selector replaces it.
2095
2095
 
@@ -217,6 +217,11 @@ handover resume refuses before it drives any effect. A receipt qualifies the one
217
217
  pair it names and no other, and the refusal for a pair with no stored receipt
218
218
  names the two commands that produce and install one.
219
219
 
220
+ `projmux doctor --section integrations` reports the saved version pairs and
221
+ their verdicts even without a generation journal. Text, JSON, and support
222
+ `doctor.json` distinguish an empty store from damaged or misfiled receipts;
223
+ see [Stored Codex version-pair qualification](codex-stored-qualification.md).
224
+
220
225
  The same `receipt.json` also goes verbatim into the `qualification` field of an
221
226
  `agent app-server upgrade plan|apply --request <absolute>.json` document. The
222
227
  upgrade request requires that receipt's version pair to match the exact current
@@ -0,0 +1,45 @@
1
+ # Stored Codex version-pair qualification
2
+
3
+ `projmux agent app-server upgrade qualify --receipt <absolute-json>` installs
4
+ one measured receipt for its exact old/new Codex version pair. Inspect the
5
+ saved receipts with:
6
+
7
+ ```sh
8
+ projmux doctor --section integrations
9
+ projmux doctor --section integrations --json
10
+ ```
11
+
12
+ The default Doctor report includes the same inventory. Its
13
+ `codex_stored_qualification` JSON field also appears in support `doctor.json`,
14
+ with version pairs, statuses, verdicts, reasons, and readiness preserved.
15
+ The read does not depend on a generation-pool journal or a readable Registry.
16
+
17
+ ```text
18
+ Codex stored version-pair qualification
19
+ Store: stored
20
+ 0.153.2 -> 0.153.4: stored; verdict: yes; reason: qualified; qualification ready: true
21
+ ```
22
+
23
+ | Store status | Meaning |
24
+ | --- | --- |
25
+ | `stored` | All enumerated receipts decoded and matched their filenames. |
26
+ | `absent` | No saved receipt exists; the directory may be absent or empty. |
27
+ | `damaged` | At least one receipt could not be read as its filename's pair. Other receipts remain visible. |
28
+ | `unavailable` | The store path could not be resolved or the directory could not be read. |
29
+
30
+ Each `version_pairs` row reports `stored`, `damaged`, or
31
+ `version-pair-mismatch`. A mismatch means a valid receipt names a different pair
32
+ from its filename; its verdict is not reported for the filename's pair. A
33
+ malformed `.json` filename produces a damaged row without exposing that name.
34
+ Non-regular receipt files are damaged; temporary save files are ignored.
35
+
36
+ For a stored row, `verdict` and `reason` come from the existing strict receipt
37
+ decoder. `qualification_ready` comes from the existing qualification gate:
38
+ a saved `no` or a self-consistent `yes` with unbacked evidence counters remains
39
+ not ready. This field describes only the pair's qualification, and does not
40
+ assert that the generation pool or an upgrade operation is ready.
41
+
42
+ Schema v2 has no timestamps or expiry policy. A receipt stays reportable until
43
+ it is replaced or removed. Doctor reads it without running qualification,
44
+ writing receipts or journals, restoring upgrade state, or executing lifecycle
45
+ commands.
@@ -1,13 +1,18 @@
1
1
  # Column profiles
2
2
 
3
3
  Plural Registry reads (`get projects|windows|panes|agents`) now default to
4
- `NAME STATUS ACTIONS`. The route already selects the resource kind. Runtime
4
+ `NAME STATUS ACTIONS AGE`. The route already selects the resource kind. Runtime
5
5
  reads default to their identity, containment, and classification columns. Use `-o wide` (or `--output wide`) to print the full
6
6
  column profile. Terminal width and data length never select a profile.
7
7
 
8
- Removing KIND changes these four default tables from four columns to three and
9
- moves NAME, STATUS and ACTIONS one position left. Existing positional parsers
10
- can break; request `-o wide` or recover each resource's `kind` from `-o json`.
8
+ Adding AGE changes these four default tables from three columns to four, keeping
9
+ NAME, STATUS and ACTIONS in their existing positions. AGE uses the same stored
10
+ creation timestamp and formatter as wide; unavailable ages remain empty cells.
11
+ The AGE header and a two-cell gap add five display cells when ages fit within
12
+ three cells; longer ages widen the table further. Resource CLI output is not
13
+ clipped at COLUMNS 40/80/112, so narrow terminals can wrap earlier.
14
+ Positional parsers must account for the added field; each resource's `kind`
15
+ remains available in `-o wide` and `-o json`.
11
16
  Mixed Registry/Runtime pickers keep KIND in both profiles.
12
17
 
13
18
  Scripts that need diagnostic columns must request `-o wide` or a structured projection explicitly. Registry JSON items
@@ -18,15 +23,15 @@ projections retain their contracts, as do singular reads and selector cardinalit
18
23
 
19
24
  | Surface / kind | Default | Wide |
20
25
  | --- | --- | --- |
21
- | Registry Project | NAME STATUS ACTIONS | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED AGE |
22
- | Registry Window | NAME STATUS ACTIONS | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED PROJECT AGE |
23
- | Registry Pane | NAME STATUS ACTIONS | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED PROJECT WINDOW AGENT TERMINATION AGE |
24
- | Registry Agent | NAME STATUS ACTIONS | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED INTERACTION PROJECT WINDOW SESSION TERMINATION AGE |
26
+ | Registry Project | NAME STATUS ACTIONS AGE | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED AGE |
27
+ | Registry Window | NAME STATUS ACTIONS AGE | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED PROJECT AGE |
28
+ | Registry Pane | NAME STATUS ACTIONS AGE | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED PROJECT WINDOW AGENT TERMINATION AGE |
29
+ | Registry Agent | NAME STATUS ACTIONS AGE | KIND NAME STATUS ACTIONS CONTEXT SOURCE OBSERVED INTERACTION PROJECT WINDOW SESSION TERMINATION AGE |
25
30
  | Runtime Session | SESSION NAME CLASS | SESSION NAME CLASS UID RESOURCE REASON |
26
31
  | Runtime Window | WINDOW SESSION NAME CLASS | WINDOW SESSION NAME CLASS UID RESOURCE REASON |
27
32
  | Runtime Pane | PANE WINDOW TITLE CLASS | PANE WINDOW TITLE CLASS UID RESOURCE REASON |
28
33
 
29
- Registry defaults omit KIND, CONTEXT, SOURCE, OBSERVED, AGE and each kind's owner-chain,
34
+ Registry defaults omit KIND, CONTEXT, SOURCE, OBSERVED and each kind's owner-chain,
30
35
  INTERACTION, SESSION and TERMINATION diagnostics shown in the wide column above.
31
36
  Runtime defaults omit UID, RESOURCE and REASON. Wide retains their full values;
32
37
  stdout is unbounded and copyable, including long names, context, provider session
@@ -337,3 +337,26 @@ The selectorless E2E remains the single deterministic L20 scenario. Actual
337
337
  active-tool/human overlap, multiple ordinary requests, same-UID recovery and
338
338
  installed smoke remain separate unverified evidence. They are not run by this
339
339
  qualification-plus-one-idle transaction and are not inferred from its result.
340
+
341
+ `TestClaudeDialogueStreamReplaysObservedFrameCorpus` replays the preserved
342
+ Claude Code 2.1.263 frame shapes through the reply-only validator and fails when
343
+ a recorded verdict moves. Neither it nor L20 proves:
344
+
345
+ - compatibility with the installed provider, which only this canary observes;
346
+ - value drift, because the corpus keeps top-level key shape and fills values
347
+ with placeholders;
348
+ - acceptance of unknown frames or fields, which the closed vocabulary rejects
349
+ by design.
350
+
351
+ The L20 fixture emits none of the corpus's dropped side frames. Recorded real
352
+ shapes the validator rejects today:
353
+
354
+ - `result-success-25-keys`: the result allowlist lacks `origin`;
355
+ - `system-init-allowlist-diff`: the init allowlist lacks `memory_paths` and
356
+ `terminal_slash_commands`.
357
+
358
+ Shapes without preserved evidence are gaps, not corpus items: `command_lifecycle`
359
+ keys and values; a non-null assistant `context_management` value; `system`
360
+ `thinking_tokens` frames; hook and user `tool_result` frames; the exact real init
361
+ key set; nested values of every frame; `rate_limit_event` envelope keys beyond
362
+ `type` and `rate_limit_info`; and earlier shape inventories, which were lost.
@@ -217,6 +217,91 @@ without storing or replacing the transport default. Rename actions no longer
217
217
  have a built-in terminal fallback; use tmux's prefix rename flow or configure
218
218
  an explicit safe key where the action is editable.
219
219
 
220
+ ## Managed close keys
221
+
222
+ The generated app config replaces tmux's stock `prefix x` (kill Pane) and
223
+ `prefix &` (kill Window) with two catalog actions, `pane.delete` (v0 id
224
+ `delete-pane`) and `window.delete` (v0 id `delete-window`). Each binding
225
+ branches on the target's Registry identity mirror:
226
+
227
+ | Key | Target carries the mirror | Target has no mirror |
228
+ | --- | --- | --- |
229
+ | `prefix x` | `@projmux_pane_uid`: confirm, then the Pane menu Kill route (canonical `delete pane`) | tmux stock `confirm-before -p "kill-pane #P? (y/n)" kill-pane` |
230
+ | `prefix &` | `@projmux_window_uid`: confirm, then canonical `delete window` | tmux stock `confirm-before -p "kill-window #W? (y/n)" kill-window` |
231
+
232
+ A managed target is always confirmed first. The prompt is localized (en-US and
233
+ ko-KR) when the key is pressed. It says the resource is deleted from the
234
+ Registry and what happens to Agents: deleting a Pane leaves its owning Agent in
235
+ the Registry as Offline with a `deleted` exit, and deleting a Window deletes the
236
+ Agents in it. Neither comes back on Continue. Answering `y` deletes the target
237
+ with an `intentional` termination receipt, so a later Project start does not
238
+ re-materialize it. Deleting a Project's last Window follows `delete window` and
239
+ leaves a zero-Window Project. When the canonical route refuses, the reason is
240
+ shown on the client that pressed the key and nothing falls back to a raw tmux
241
+ kill.
242
+
243
+ A target without the mirror gets tmux's own stock prompt and kill, unchanged,
244
+ and projmux writes nothing to the Registry. The projmux process never issues
245
+ `kill-pane` or `kill-window` for either key: the stock body is tmux's binding,
246
+ run by tmux. The standalone `~/.tmux.conf` snippet does not carry these
247
+ actions, so a plain tmux server keeps both keys stock.
248
+
249
+ Both actions follow the ordinary keymap rules. In `keymap.toml`,
250
+ `prefix = "<key>"` moves the managed binding, `prefix = ""` disables it, and
251
+ `keys` adds root-table keys that run the same branching binding:
252
+
253
+ ```toml
254
+ [bindings."pane.delete"]
255
+ prefix = "X"
256
+
257
+ [bindings."window.delete"]
258
+ prefix = ""
259
+ ```
260
+
261
+ When an action no longer owns its stock key, because it was disabled or moved,
262
+ the generated config unbinds that key and binds tmux's stock body back onto it.
263
+ `prefix x` and `prefix &` therefore return to tmux's own behavior on the next
264
+ apply, including on a server that is already running, instead of being left
265
+ with no binding at all. Two managed actions on one prefix key are rejected.
266
+ Settings > Keybindings adds and removes root-table keys for these actions; their
267
+ prefix key is changed in `keymap.toml`.
268
+
269
+ ## Managed menus
270
+
271
+ The generated app config also replaces tmux's stock Window and Pane menus. Each
272
+ generated menu keeps tmux's title, position, item names, key shortcuts, and dim
273
+ conditions, and changes only the items that alter topology or identity:
274
+
275
+ | Menu | Bindings | Kill on a target with the mirror | Kill on a target without it | Typed items | Removed |
276
+ | --- | --- | --- | --- | --- | --- |
277
+ | Window | `prefix <`, `MouseDown3Status`, `M-MouseDown3Status` | `@projmux_window_uid`: `internal tmux window-delete`, canonical `delete window` | tmux stock `kill-window` | Rename: `internal tmux window-rename`; New At End: `internal tmux window-create` | Respawn, New After |
278
+ | Pane | `prefix >`, `M-MouseDown3Pane` | `@projmux_pane_uid`: the Pane menu Kill route, canonical `delete pane` | tmux stock `kill-pane` | Horizontal Split / Vertical Split: `internal tmux pane-menu split-right` / `split-down` | Respawn |
279
+ | Pane context | `MouseDown3Pane` | same as the Pane menus | tmux stock `kill-pane` | Horizontal Split / Vertical Split, as above | Respawn |
280
+
281
+ Swap Left, Swap Right, Swap Marked, Swap Up, Swap Down, Mark, Zoom, and the
282
+ copy-mode items (Go To Top, Go To Bottom, Search For, Type, Copy, Copy Line)
283
+ are presentation and run tmux's own commands. `MouseDown3Pane` also keeps its
284
+ AI Resume Picker entry.
285
+
286
+ Selecting Kill is the confirmation, as it is in tmux's own menus, so a managed
287
+ Kill asks nothing more. It deletes the target with an `intentional` termination
288
+ receipt: a Pane's owning Agent stays in the Registry as Offline with a `deleted`
289
+ exit, the Agents of a deleted Window are deleted with it, and deleting a
290
+ Project's last Window leaves a zero-Window Project. When the canonical route
291
+ refuses, for example because the mirror names no Registry resource, the reason
292
+ is shown on the client that opened the menu and nothing falls back to a raw
293
+ tmux kill. A target without the mirror gets tmux's own kill with no Registry
294
+ write; the projmux process never issues `kill-pane` or `kill-window`.
295
+
296
+ A mouse menu acts on what was clicked, not on what is focused: Kill, Rename, and
297
+ New At End in the status-line Window menu act on the clicked Window, and the
298
+ Rename prompt starts with that Window's name.
299
+
300
+ The standalone `~/.tmux.conf` snippet keeps tmux's stock `prefix <`, `prefix >`,
301
+ `MouseDown3Status`, `M-MouseDown3Status`, and `M-MouseDown3Pane` menus; it
302
+ carries only the `MouseDown3Pane` menu. A `keymap.toml` that assigns a managed
303
+ action to `prefix <` or `prefix >` keeps that key.
304
+
220
305
  ## Product Requirements
221
306
 
222
307
  Settings > Keybindings stays a discovery surface. It must continue to expose
@@ -44,6 +44,20 @@ UIDs and stage labels are bounded control data; root paths, pane content,
44
44
  history, prompts, transcripts, and snapshot contents are never identity or
45
45
  intent authority.
46
46
 
47
+ Automatic Window teardown decisions use `component=topology` and
48
+ `event=topology.teardown.decision`: one `info`/`success` record per decision
49
+ the `internal tmux converge` hook path consumes. An unpaired `window-unlinked`
50
+ records `retain` when its own hook first waits and again when its bounded pair
51
+ wait is exhausted, never for the carried retries between; its Window/Pane UID
52
+ is resolved read-only from the exact `$N/@N` handles and omitted when
53
+ ambiguous. The record adds only
54
+ a closed `decision` (`retain`, `delete-pane-agent`, `delete-window`, `refuse`),
55
+ a `code` from the closed `topology.teardown.<reason>` set that mirrors every
56
+ core teardown reason, an optional closed termination `classification`, and
57
+ optional opaque `window_uid` (`win-…`) and `pane_uid` (`pane-…`) Registry UIDs.
58
+ tmux `%N`/`@N`/`$N` handles, socket paths, session names, cwd, argv, and free
59
+ text are never recorded, and every other event family rejects these fields.
60
+
47
61
  Session State mutations use one outcome-only `session-state.outcome` record
48
62
  per selected attempt. The closed operations are `session-state.save`,
49
63
  `session-state.autosave`, `session-state.restore`, and `session-state.delete`;
@@ -105,7 +105,9 @@ holds the forgery refusal. For the `L2` guarantee above,
105
105
  `TestBrokerRuntimeDrainsWhenItsOwnImageWasReplaced` holds the vintage entry
106
106
  condition and that a runtime on the installed image is not drained by it,
107
107
  `TestReplacementRolePoliciesMatchTheContractDocument` and
108
- `TestReplacementSessionClientIsNeverADrainTarget` hold the policy table, and
108
+ `TestReplacementSessionClientIsNeverADrainTarget` hold the policy table,
109
+ `TestReplacementRoutesNameARunnableAction` holds that every role's action is a
110
+ runnable `projmux` command or a closed lifecycle event, and
109
111
  `TestInstallReplacementPassOutcomesAreFixedByFleetAndRequest` fixes every
110
112
  outcome of the install pass.
111
113
 
@@ -321,18 +323,46 @@ measurement, and they are fixed here rather than at a call site.
321
323
  when it actually goes. `report-only` means no install touches it; the route that
322
324
  does replace it is named instead, so a reader of the row knows what would.
323
325
 
324
- `TestReplacementRolePoliciesMatchTheContractDocument` holds this table and the
325
- code's policy map equal, in both directions.
326
+ `TestReplacementRolePoliciesMatchTheContractDocument` holds this table — its
327
+ operator actions included — and the lifecycle event table below equal to the
328
+ code's policy map and event set, in both directions.
326
329
 
327
- | Role | Disposition | Replacement route |
328
- | --- | --- | --- |
329
- | `broker-runtime` | `drain` | `broker-drain` |
330
- | `lifecycle-observer` | `report-only` | `pane-relaunch` |
331
- | `supervisor` | `report-only` | `pane-relaunch` |
332
- | `session-client` | `report-only` | `operator-reattach` |
333
- | `agent-endpoint` | `report-only` | `pane-relaunch` |
334
- | `usage-watcher` | `report-only` | `lease-expiry` |
335
- | `other` | `report-only` | `process-exit` |
330
+ | Role | Disposition | Replacement route | Operator action |
331
+ | --- | --- | --- | --- |
332
+ | `broker-runtime` | `drain` | `broker-drain` | event `install-replacement-pass` |
333
+ | `lifecycle-observer` | `report-only` | `pane-relaunch` | `projmux stop project <project-ref>`, then `projmux start project <project-ref>` |
334
+ | `supervisor` | `report-only` | `pane-relaunch` | `projmux stop project <project-ref>`, then `projmux start project <project-ref>` |
335
+ | `session-client` | `report-only` | `operator-reattach` | event `shell-wrapper-exit`, then `projmux shell` |
336
+ | `agent-endpoint` | `report-only` | `pane-relaunch` | `projmux stop project <project-ref>`, then `projmux start project <project-ref>` |
337
+ | `usage-watcher` | `report-only` | `lease-expiry` | event `usage-demand-lapse` |
338
+ | `other` | `report-only` | `process-exit` | event `invocation-exit` |
339
+
340
+ **The operator action is the route spelled as something that can happen.** A
341
+ route name says which kind of replacement applies; the action says what carries
342
+ it out, step by step. A step is either a `projmux` command the shipped CLI
343
+ catalog resolves to a runnable verb, or one of the lifecycle events below, which
344
+ replace a role with no command at all.
345
+ `TestReplacementRoutesNameARunnableAction` fails when a command names a route
346
+ the catalog does not have, stops at a verb that refuses without its child, or
347
+ reaches into `internal`, and when an event is outside this list.
348
+
349
+ | Event | What happens |
350
+ | --- | --- |
351
+ | `install-replacement-pass` | `make install` runs `projmux internal install-replace`, which dials the superseded broker runtime. The runtime drains, closes when its last binding goes, and the next binding starts one on the installed image |
352
+ | `shell-wrapper-exit` | the terminal command that ran `projmux shell` returns. The wrapper waits on its `tmux attach-session` child, so it ends only when that client does |
353
+ | `usage-demand-lapse` | no usage render has refreshed the watcher's demand marker within its 15-second TTL, so the watcher cancels itself and releases its lease; the next render starts one on the installed image. A status line still rendering the usage segment keeps that demand fresh |
354
+ | `invocation-exit` | a short-lived invocation returns on its own, and the next one runs the installed image |
355
+
356
+ **`pane-relaunch` is a Project runtime cycle.** Each role it covers lives as
357
+ long as one managed Pane: the supervisor is that Pane's own process, the
358
+ lifecycle observer ends once its Pane binding is gone, and the endpoint helper
359
+ ends once its provider process does. `projmux stop project` ends the Project's
360
+ exact tmux session and preserves its Registry graph; `projmux start project`
361
+ materializes that graph again from the installed image, and its Continue
362
+ launches or resumes the Project's declared Agents under the same eligibility
363
+ every Continue uses. It ends every Pane of that Project, which is exactly why
364
+ no install runs it on the operator's behalf. A Pane under the Home control
365
+ session belongs to no Project, and no Project verb reaches it.
336
366
 
337
367
  **Exactly one role drains, and that is a fact about what exists rather than a
338
368
  first instalment.** A drain is a protocol: the runtime has to be able to hear
@@ -350,6 +380,17 @@ typed into. It is report-only and no cutoff reaches it.
350
380
  `TestReplacementSessionClientIsNeverADrainTarget` holds that as a property of
351
381
  the table rather than of any call site.
352
382
 
383
+ **Detaching and reattaching does not by itself finish that replacement.** The
384
+ census counts the `projmux shell` wrapper, not the tmux client. The wrapper
385
+ starts `tmux attach-session` as a child process and waits on it, so the
386
+ wrapper — the process still on the pre-install image — keeps running until that
387
+ client exits and the terminal command that ran `projmux shell` returns. A
388
+ reattach that leaves that command running, such as switching sessions or
389
+ attaching a second client, leaves the wrapper exactly where it was. The required
390
+ action is to end the wrapper by exiting the terminal session running
391
+ `projmux shell`, then start `projmux shell` again, which runs the installed
392
+ image.
393
+
353
394
  **No disposition severs work.** The one hard kill this repository has measured —
354
395
  2026-09-05, four panes — brought three of the four back automatically and left
355
396
  the fourth wedged on `backlog-overflow`. A recovery asymmetry that large is what