projmux 0.14.2 → 0.15.0

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.
@@ -332,6 +332,28 @@ support archives remain count-only for that file. The common journal is the
332
332
  safe correlated source for watcher lifecycle and anomalous ingest
333
333
  classification, while the bounded file retains detailed normal-state rows.
334
334
 
335
+ ## Detached process SIGQUIT artifacts
336
+
337
+ The detached Codex lifecycle watcher and endpoint broker keep provider hooks
338
+ quiet and non-blocking: hook stdout and stderr are still discarded and hook
339
+ failure still cannot block the provider. For process-local deadlock diagnosis,
340
+ sending `SIGQUIT` to the exact projmux-owned `codex-broker-watch` or
341
+ `codex-broker serve` PID writes one artifact before retaining SIGQUIT's fatal,
342
+ non-zero exit behavior:
343
+
344
+ ```text
345
+ ${XDG_STATE_HOME:-$HOME/.local/state}/projmux/crash/codex-broker-watch-<pid>.sigquit.txt
346
+ ${XDG_STATE_HOME:-$HOME/.local/state}/projmux/crash/codex-broker-serve-<pid>.sigquit.txt
347
+ ```
348
+
349
+ The `crash` directory is mode `0700`, each file is mode `0600`, and publication
350
+ is bounded to 1 MiB and atomic. A dump that reaches the bound ends with an
351
+ explicit truncation marker. Normal startup creates no crash artifact or
352
+ append-style crash log. These files are local-only and are deliberately not
353
+ included in `diagnostics report`: goroutine stacks can contain absolute source
354
+ paths and other sensitive local data. Inspect and share them only as carefully
355
+ as any other local crash dump.
356
+
335
357
  `PROJMUX_FOCUS_DEBUG` remains available with its existing one-line byte
336
358
  contract. Focus diagnostics share its request classification seam, but do not
337
359
  copy the debug line's raw target, session/window/pane, socket, client, source,
@@ -353,3 +375,106 @@ JSON schema version 2 and the bounded operations decoder are reused rather than
353
375
  duplicated. AI ingest contributes count-only allowlisted source/result rows,
354
376
  never raw legacy lines. Existing output files survive collisions and partial
355
377
  temporary archives are removed.
378
+
379
+ ## Install residue ledger
380
+
381
+ `make install` and `npm install` replace the executable on disk. Neither
382
+ replaces the image of a process that is already running, so every long-lived
383
+ projmux child — the Codex broker runtime, the lifecycle observers, the per-pane
384
+ supervisors — keeps executing the code it started with until it exits on its
385
+ own. The install reports success while most of the running fleet is still on
386
+ the previous build.
387
+
388
+ `projmux internal install-residue` is the census of exactly that. It is hidden
389
+ internal plumbing invoked by the installer, not a command to type: `make
390
+ install` runs it as its last step, and the npm bin wrapper runs it once on the
391
+ first interactive run after an install. It reads the process table, appends one
392
+ record, prints at most one notice, and always exits `0` — a diagnostic that can
393
+ fail the install it reports on is worse than no diagnostic.
394
+
395
+ The notice goes to **stderr** and is printed only when the census was taken and
396
+ found residual processes:
397
+
398
+ ```
399
+ >> 24 projmux processes are still running the image this install replaced
400
+ supervisor 20 oldest 3h02m median 45m
401
+ lifecycle-observer 3 oldest 2h14m median 41m
402
+ broker-runtime 1 oldest 2h14m median 2h14m
403
+ They keep executing code from before this install until each one exits.
404
+ Recreating a pane moves that pane onto the installed build; the broker
405
+ follows when its last binding goes.
406
+ Recorded to ~/.local/state/projmux/install-residue.jsonl (17 installs, last 38m ago).
407
+ ```
408
+
409
+ An install that reached the whole fleet prints nothing at all. A platform with
410
+ no readable process table (macOS, which has no `/proc/<pid>/exe`) also prints
411
+ nothing: the census cannot be taken there and the operator has no action
412
+ available, so a line at every install would be permanent noise. Both cases
413
+ still write a record.
414
+
415
+ ### Ledger format
416
+
417
+ The path is `${XDG_STATE_HOME:-$HOME/.local/state}/projmux/install-residue.jsonl`,
418
+ resolved through the same `internal/config` layout as every other state file.
419
+ It is JSON Lines, appended one object per install, created `0600` in the
420
+ private state directory, and trimmed to approximately the newest 1000 records.
421
+ Every failure — an unresolvable state directory, an unwritable file — is
422
+ silent.
423
+
424
+ ```json
425
+ {
426
+ "at": "2026-09-06T04:12:33Z",
427
+ "installer": "make",
428
+ "supported": true,
429
+ "observed": 41,
430
+ "replaced": 37,
431
+ "sinceLastInstallSeconds": 3612,
432
+ "roles": [
433
+ {"role":"supervisor","processes":22,"current":2,"replaced":20,
434
+ "replacedAgeSeconds":[610,1200,4300,8800]}
435
+ ]
436
+ }
437
+ ```
438
+
439
+ | field | meaning |
440
+ | --- | --- |
441
+ | `at` | when the census was taken, RFC3339 UTC |
442
+ | `installer` | the existing `PROJMUX_INSTALLER` value; `unknown` when unset |
443
+ | `supported` | whether this platform exposes a process table the census can be taken from |
444
+ | `observed` | projmux processes classified |
445
+ | `replaced` | how many of them run the image this install replaced |
446
+ | `sinceLastInstallSeconds` | gap to the previous record; absent on the first |
447
+ | `roles[].role` | `broker-runtime`, `lifecycle-observer`, `supervisor`, or `other` |
448
+ | `roles[].processes` / `current` / `replaced` | that role's census |
449
+ | `roles[].replacedAgeSeconds` | ascending age distribution of that role's residual processes, whole seconds |
450
+ | `roles[].replacedAgeCapped` | present when the 512-sample per-role bound was reached, so the distribution above is a prefix |
451
+
452
+ The record carries **no pid, no executable path, and no argv**, on the terminal
453
+ and in the file alike. Counts and durations are the whole of it; process
454
+ identity is exactly what the census is built not to record. A residual process
455
+ whose start time could not be read is still counted in `replaced` and
456
+ contributes no entry to `replacedAgeSeconds`, so that array can be shorter than
457
+ `replaced`.
458
+
459
+ Ages come from the modification time of `/proc/<pid>`, which the kernel stamps
460
+ at process creation and never moves. That is chosen over `/proc/<pid>/stat`
461
+ field 22 plus `/proc/stat` `btime` because it needs no `USER_HZ` assumption, no
462
+ boot-time read, and no parsing around the `comm` field, which is an executable
463
+ name in parentheses and may itself contain `)` and spaces.
464
+
465
+ ### What the ledger is for
466
+
467
+ The ledger is the deliverable; the notice is a courtesy. An automatic
468
+ replacement of residual processes cannot be designed without a termination
469
+ condition for the drain, and these three fields are what such a condition is
470
+ derived from:
471
+
472
+ | field | the question it answers |
473
+ | --- | --- |
474
+ | `roles[].replaced` (per role) | what to make a replacement target |
475
+ | `roles[].replacedAgeSeconds` (the full sorted distribution, not a mean) | does a bounded drain finish in finite time, and what cutoff `T` covers which fraction |
476
+ | `at` + `sinceLastInstallSeconds` across records | how often this happens per install, hence whether a forced cutoff is needed at all |
477
+
478
+ A mean would destroy the second answer: twenty supervisors averaging forty
479
+ minutes says nothing about the one that outlives every plausible bound. That is
480
+ why the distribution is stored whole.
@@ -92,8 +92,9 @@ Notes:
92
92
  `main` is governed by ruleset `main-protect`:
93
93
 
94
94
  - Direct push to `main` is blocked. Even repository admin must use a PR.
95
- - Required status check: the CI `Test` job. The PR cannot merge until it is
96
- green.
95
+ - Required status checks are the five CI job names `Format`, `Unit Tests`,
96
+ `NPM Packages`, `Integration Tests`, and `E2E Tests`. The aggregate `Test`
97
+ job is observed as the project-wide fan-in, but it is not ruleset-required.
97
98
  - Admin bypass is `pull_request` mode — admin can self-merge without
98
99
  approvals, but the PR itself is mandatory.
99
100
  - Linear history is enforced. The merge methods exposed are