projmux 0.15.0 → 0.15.1

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.
@@ -0,0 +1,49 @@
1
+ # Explicit reply recovery
2
+
3
+ An explicit Claude reply names the original request with `--reply-to`. The
4
+ broker preserves that request's `conversationRef`, reverses its exact source
5
+ and target routes, and retains peer, untrusted, coordination-only authority.
6
+ Both activations and the original deadline must still be current. A reply
7
+ cannot extend that deadline. Source metadata remains an unverified routing
8
+ claim; an explicit reply also requires the registered provider's descendant
9
+ caller and any existing qualification and execution guard.
10
+
11
+ When a reply fails, `agent message send` exits nonzero and reports its ref,
12
+ underlying reason, outcome uncertainty, and safe action. Inspect the durable
13
+ attempt before deciding whether to retry:
14
+
15
+ ```sh
16
+ projmux agent message status <failed-reply-ref>
17
+ projmux agent message status <failed-reply-ref> -o json
18
+ ```
19
+
20
+ Only a confirmed zero-write Claude failure permits a new manual attempt.
21
+ Examples include rejected content or frame size, invalid auth configuration,
22
+ pre-write refusal, and an actual zero-byte write. Correct the reported cause,
23
+ then send a new reply ref on the same original correlation:
24
+
25
+ ```sh
26
+ projmux agent message send uid:<original-source-agent> --reply-to <original-request-ref> --message-ref <fresh-reply-ref> -- '<corrected reply>'
27
+ ```
28
+
29
+ Omitting `--message-ref` generates a fresh ref. In the qualified reply-only
30
+ execution guard, use the existing bounded command without that flag:
31
+
32
+ ```sh
33
+ projmux agent message send uid:<original-source-agent> --reply-to <original-request-ref> -- '<corrected reply>'
34
+ ```
35
+
36
+ A same-ref call returns the original immutable receipt and never pushes again.
37
+ Changing its payload is refused with the earlier ref and cause. A fresh ref
38
+ allows one new attempt only when every previous attempt is known-zero. Failed
39
+ records and the original request remain available; recovery never deletes or
40
+ resets them. Store capacity can refuse a new attempt rather than discard its
41
+ history.
42
+
43
+ Delivered replies, partial writes, unknown outcomes, pending attempts, expired
44
+ deadlines, and stale routes must not be resent. Inspect their status and the
45
+ provider outcome. A false `outcomeUnknown` flag by itself is insufficient:
46
+ the stored state and specific zero-write reason must agree. These rules also
47
+ apply after store reload and to concurrent callers. Automatic resend is
48
+ disabled, and replies whose delivery target is Codex do not gain a new retry
49
+ policy here.
@@ -27,8 +27,18 @@
27
27
 
28
28
  ## Maintained Test List
29
29
 
30
+ - Installed recovery receipt identity: `TestInstalledRecoveryReceiptMatchesPublicControlTurnIdentity` reproduces the public handler's quoted turn receipt versus the lifecycle snapshot's unquoted ID, then requires the fixture to decode the exact receipt and record only its validated `expectedTurn` before waiting. `TestInstalledRecoveryReceiptRejectsAmbiguousOrMalformedIdentity` requires one expected action label, one exact thread and one bounded opaque turn ID, decodes double-quoted fields with `strconv.Unquote`, and refuses missing/duplicate/unknown fields, wrong threads/actions, malformed/truncated/oversized identities and extra output. No stdout payload, completion inference or input retry is added.
31
+
32
+ - Managed recovery first-input admission: `TestObserverReadyControlStartPreservesOwnedReadAdmissionAndFreshness` runs the real repo broker host, session, observer and control handler with a deterministic broker clock and owned reader. It reproduces current authority/status-ready followed by the one-second `lifecycle-retry` refusal with zero turn writes, pins the exact boundary, and requires the admitted read to observe new state before any start. Retired control still writes zero. The installed matrix waits for the same exact authority/control tuple to remain ready for that existing interval before the recovered survivor's first input, inside its unchanged 90-second authority deadline; the ledger records `waiting-read-admission`. This is fixture pacing, not a fresh-state receipt or a retry of an attempted input. `TestInstalledRecoveryAdmissionRequiresStableExactControlBeforeFirstInput` resets that wait on identity, broker/connection/binding/control, unknown/not-startable, or clock reversal changes. No product admission, freshness or timeout policy changes.
33
+
34
+ - `make test`: `TestCodexNativeObserverForeignThreadSameTurnWritesNoRegistryProgressOrDiagnostics` waits for the observer's startup-ready callback after the initial progress/diagnostic flush, validates the complete baseline, then requires exact equality after a foreign-thread event. A following accepted lifecycle marker proves the foreign event was consumed; synchronization uses no sleep or expanded timeout.
35
+
36
+ - `make test`: Stored Codex version-pair qualification diagnostics (C-3) read the qualification store independently of the generation journal. `TestDoctorStoredCodexQualificationWithoutJournal` requires the saved pair and verdict even without a journal or readable Registry. `TestDoctorStoredCodexQualificationTextJSONSupportParity` covers saved, absent, corrupt, filename/receipt pair mismatch, unreadable store, measured refusal, and unbacked acceptance fixtures across text, JSON, and support `doctor.json`; the existing gate supplies qualification readiness. `TestQualificationStoreListPreservesPairOrderAndLoadVerdicts`, `TestQualificationStoreListDistinguishesAbsentDamagedAndPairMismatch`, and `TestDoctorStoredCodexQualificationKeepsValidSiblingsVisible` pin deterministic enumeration, unchanged Load judgments, invalid filename visibility, non-regular file refusal, and valid sibling retention. `TestDoctorStoredCodexQualificationReadOnlyArgvAndState` requires zero execution argv and byte/mode/mtime-identical receipt/journal state across repeated diagnoses; `TestDoctorStoredCodexQualificationSupportRejectsNonDiagnosticStrings` keeps unvalidated strings redacted. Receipt schema v2 and its lack of expiry remain unchanged.
37
+
30
38
  - `make test`: Replacement guarantee Phase 1 makes `make install` finish the replacement it starts, for the one long-lived role that ships a drain. It adds a second entry condition to the drain the broker runtime already had -- the runtime's own image having been unlinked -- and a bounded termination condition for it. It adds no wire frame, no refusal token, and no way to end a process: the whole of the action is a socket handshake and the runtime decides. `TestBrokerRuntimeDrainsWhenItsOwnImageWasReplaced` holds the vintage trigger in both directions -- a runtime on the installed image is not drained, a superseded one is, live bindings keep delivering and keep control authority through the drain, a new bind is refused by name so the drain stays finite, the answer is read once rather than per session, and an idle superseded runtime closes as soon as it is asked. `TestReplacementRolePoliciesMatchTheContractDocument` holds the per-role disposition table and [replacement-contract.md](replacement-contract.md) equal in both directions and requires every census role to carry a stated decision. `TestReplacementSessionClientIsNeverADrainTarget` fixes the one entry that is a decision rather than an observation: the operator's attached session is report-only, and a fleet of nothing but attached sessions past the cutoff still produces no drain target. `TestReplacementDrainCutoffIsTheAdoptedValueAndBound` pins the adopted 24h cutoff -- truncation rate 9.5%, 13 of 137 residual processes by identity -- fixes the exclusive boundary, and rejects every spelling that would disable the bound. `TestInstallReplacementPassOutcomesAreFixedByFleetAndRequest` pins each fleet-and-request combination to its closed outcome token and its counters, including the refusal that says which door was closed. `TestInstallReplacementNeverFailsAnInstall` holds the property every post-publication step needs. `TestInstallReplacementOutcomeRecordCarriesNoProcessIdentity` and `TestInstallReplacementNoticeSpeaksOnlyWhenAnActionFollows` keep pid, path, and argv off the new record and the new terminal line. `TestDoctorReplacementProcessRowCarriesTheCutoffAndThePassAccount` holds the `L2` row: the bound it judged against, how many processes are past it, and what the last pass did, with an absent pass distinguished from a pass that found nothing. `TestReplacementPathEndsNoProcess` is the scope guard over the acting half -- no `os/exec`, no signal, no `Ensure` -- and `TestReplacementMeasurementPathEndsNoProcess` still holds the measuring half.
31
39
 
40
+ - `make test`: Replacement guarantee Phase 2 makes report-only routes and delete refusals point at runnable commands. `TestReplacementRoutesNameARunnableAction` requires every per-role action step to be a `projmux` command the CLI catalog resolves to a non-refusal, non-`internal` verb, or an event from the closed lifecycle set, and proves the check rejects unknown, refusal-parent, literal-operand, and `internal` commands; `TestReplacementRolePoliciesMatchTheContractDocument` now also holds the `Operator action` column and the event table equal to code in both directions. `TestPaneDeleteLiveMirrorRefusalNamesTheExactUIDRegistryOnlyForm` pins full refusal strings: a name-selected MissingRuntime Pane, Offline Agent, or zero-Pane Offline Agent names the typed resource's exact `projmux delete pane|agent uid:<uid> --socket-path <server>` `--dry-run`/`--yes` form, while exact-uid-without-evidence, evidence-less Pane, Running Agent, and Failed Agent refusals carry no pointer. `TestDeleteByNameRefusalPointsAtARunnableUIDCommand` reaches both refusals through the real route and resolver and runs the named dry-run command with zero Registry writes.
41
+
32
42
  - `make test`: Replacement completion and restorability Phase 0 adds a read-only `projmux doctor` table only. [replacement-contract.md](replacement-contract.md) fixes the two axes, the closed per-layer reason-token vocabulary, and the closed signal-key inventory; nothing on this path changes install, update, or generation-pool behavior. `TestDoctorReplacementLayerVerdictsAreFixedByInputCombination` pins every reachable evidence combination of all three layers to its replacement verdict, restoration verdict, and governing token; `TestDoctorReplacementUnknownVerdictsNameTheEvidenceGap` requires each `unknown` to name which reading was silent and rejects an unreachable gap. `TestDoctorReplacementEveryReasonTokenCarriesARecoverableDiscriminant` is the negative gate: zero rows carrying a token with no discriminant. `TestDoctorReplacementSignalValuesCarryNoPathProcessOrFreeFormText` holds the serialization rule that lets the discriminant into `--json` -- every value is a counter or a closed token, and a path-shaped value becomes `unclassified`. `TestDoctorReplacementReasonTokensMatchTheContractDocument` and `TestDoctorReplacementSignalKeysMatchTheContractDocument` hold the document and the code constants equal in both directions and reject a published token no evidence reaches. `TestDoctorReplacementDarwinReportsUnsupportedPlatformForImageAndProcessLayers` fixes the `/proc`-less platform branch to `unsupported-platform` with `unknown` on both axes, with no device. `TestDoctorReplacementQualificationMissingMakesGenerationPoolNotRestorable` fixes `qualification-missing` to `not-restorable`. `TestDoctorReplacementCensusSeparatesRegistryRunningFromLiveProviderSession` reproduces the 2026-09-08 sample as a fixture: a Claude Pane's recorded process handle and a Codex Pane's composite authority are different discriminants, a Running Agent with neither is `unobservable` rather than live, and the row stays count-only. `TestDoctorRendersReplacementTableInDefaultRunAndSectionFilter` requires the table on an unfiltered run, under `--section replacement`, and in `--json`; `TestDoctorReplacementSectionCreatesNothing` runs the section against an empty home and requires it to stay empty.
33
43
 
34
44
  - `make test`: Replacement guarantee Phase 4 names the long-lived process roles that were behind the `other` bucket and makes residual survival countable per process. It adds classification and reading only: no process is ended, started, signalled, or restarted anywhere on this path. `TestProjmuxProcessRoleNamesEveryChildIncludingTheOnesItCannotName` is the route-to-role table, including the reachable roles added here (`session-client`, `agent-endpoint`, `usage-watcher`), the `--role shell` value that must *not* read as an attached session, and the unnamed remainder. `TestProjmuxProcessRoleOrderKeepsTheUnnamedRemainderLast` holds the census shape and requires every named role to be reachable from some argv. `TestReplacementProcessRolesMatchTheContractDocument` holds the role vocabulary and [replacement-contract.md](replacement-contract.md)'s `Process role vocabulary` table equal in both directions, the same guard the reason tokens and signal keys already carry. `TestDoctorReplacementProcessRowNamesTheResidualRoles` requires the `L2` row to carry `residual.role.*` in census order and to omit a role with no residue rather than print a zero. `TestProjmuxProcessVintageRecordsTheStartInstantIdentityIsKeyedOn` pins the identity key the ledger deduplicates on and requires the count-only projection `projmux doctor` renders to stay byte-identical. `TestInstallResidueSurvivalCountsProcessesNotObservations`, `TestInstallResidueSurvivalKeepsEachProcessAtItsOldestObservedAge`, `TestInstallResidueSurvivalRecordedStartInstantsCompareExactly`, `TestInstallResidueSurvivalReconstructedStartsAbsorbTheirOwnSecondOfSlack`, and `TestInstallResidueSurvivalDoesNotChainAcrossNearSimultaneousStarts` fix `projmux internal install-residue --survival`: both bases, the one-second ambiguity of a start reconstructed from an age, and the bound that keeps that ambiguity from chaining a burst of starts into one process. `TestInstallResidueSurvivalCarriesNoProcessIdentity` is the negative audit for the new report, and `TestInstallResidueSurvivalRouteAnswersOrSaysWhyItCannot` separates the census path, which must never fail an install, from the survival path, which owes a reader its reason. `TestReplacementMeasurementPathEndsNoProcess` is the scope guard: the four files of the measurement path may not acquire process termination, signalling, or restart, because this layer is one edit away from turning a census into an unplanned drain.
@@ -40,6 +50,11 @@
40
50
  - `make test` / `make test-integration` / `make test-e2e`: Columnar profiles Phase 1 changes only the CLI default projection. Registry plural reads use `KIND NAME STATUS ACTIONS`; runtime reads use identity/containment/classification, and the seven list routes accept explicit `-o wide`. [Column profiles](column-profiles.md) records the exact matrix and diagnostic recovery. `TestColumnCatalogCompletenessBijectionAndExactProfiles` compares typed field declarations to surface capabilities, rejects orphan/duplicate IDs and fixes all nine compact/wide orders and aliases; `TestColumnConsumersHaveNoIndependentHeaderLiteralAuthority` audits all four consumers. `TestColumnProfilesExactOutputAndSnapshotParity` pins whole default/wide output, structured/scalar byte digests and both explicit picker-wide entry frames in `column-profiles.golden`; its production observation fixture includes live, offline, missing-root and ControlSession-owned resources and proves one Registry read, exactly one four-query tmux observation, zero writes and zero sibling-server calls for every mode. ACTIONS is compared directly with the existing navigation projector and fixed eligibility examples. `TestColumnActionsReuseUnavailableSnapshotAndEmptyListsStayEmpty` pins unavailable-server actions and empty list bytes. `TestColumnProfilesDoNotSelectFromWidthAndWideIsUnbounded` checks 80/120 widths and long Hangul context/session values; `TestWideOutputIsScopedToColumnarListReads` rejects wide on every other canonical route. Existing age, session reference, context, termination, selector, JSON and picker layout tests retain their assertions through the explicit wide recovery surface where necessary. The integration and E2E exit-reconciliation probes explicitly use `get panes|agents -o wide` and retain their TERMINATION/evidence and Registry-write-free assertions. Phase 2 below owns the subsequent picker default/control and bound retirement; CLI, row identity/order/search and provider semantics retain these contracts. Isolated piped CLI smoke compares default/wide/JSON/scalar bytes and read/write counters at 80/120 columns before and after installation.
41
51
 
42
52
  - `make test` / installed opt-in smoke: Codex generation-pool Phase 2 keeps a state domain bounded to two dark private endpoint generations and keys each Broker by the canonical durable `stateDomainID + endpointGenerationID`. `TestGenerationEndpointKeyRoundTripTable`, `TestBrokerEndpointIdentityValidityMatchesDurableMetadataRef`, and `TestGenerationPoolStateTableKeepsPreparingDarkAndBounded` pin key/schema vocabulary parity, third-generation zero effect, and ready-Preparing create admission at zero. `TestGenerationPoolReconnectIsGenerationLocalAndCrossRouteWritesZero`, `TestGenerationPoolBrokerRestartRestoresExactBindingsAndFencesOldRuntime`, `TestGenerationPoolBrokerRestartPreservesSparseBindingEpochLedger`, `TestGenerationPoolFailedRestartRetryKeepsLedgerExactAndFencesOldRuntime`, `TestGenerationPoolRestartFenceRejectsBindingThatMissedTheSnapshot`, and `TestGenerationPoolRestartFenceRefusesControlAndWriteUntilExactSwap` use independent fake servers and semantic barriers to pin sibling reconnect isolation, same-thread cross-route typed refusal/provider write zero, exact/idempotent including sparse binding-ledger restore, fresh broker-runtime fencing, failed-restart retry, and the bind/control/write-vs-restart races. `TestPrivateGenerationHostLaunchProofTableAndLeaseRetention`, `TestPrivateGenerationHostLivePIDSocketExecutableDriftKeepsLifecycleAtZero`, `TestEveryQualifiedBundleArtifactIsMandatoryBeforePublish`, and `TestLaunchBoundaryBundleDriftRefusesPublicationAndCleansExactChild` pin the closed server/TUI/helper bundle, immutable leased argv, Phase-2-closed lease release, distinct endpoint-host versus broker runtime authority, pre-publication revalidation, and PID/process-group/socket-change-time/executable proof with lifecycle mutation zero for every drift, including immediate socket inode reuse. `TestPrivateGenerationHostCloseWaitsForOwnedSessionDescendants`, `TestPrivateGenerationHostCloseProcessSessionBarrierStateTable`, and `TestOwnedGenerationProcessCleanupSignalsExactSessionAndWaitsForDescendantExit` pin the exact `Setsid` group signal and the leader-exit plus inherited-session-EOF semantic barriers without a fixed sleep; the private-root tests retain outside-root, symlink, and mode mutation zero. `TestInstalledPrivateGenerationHostDualListenerSmoke` is opt-in and runs exact leased 0.152.0/0.152.1 private listeners with inherited `TMUX`/`TMUX_PANE` absent, two initialized runtimes, distinct private process groups, ambient/default socket/PID identity unchanged, and descendant-complete exact owned cleanup. Deletion ledger: zero canonical test symbols deleted or renamed; single-default broker, Phase 0 qualification/bundle, and Phase 1 composite-fence/projection tests retain their unique boundaries. Scope audit: current pointer/drain, Agent create/resume, foreign adoption, Settings/background UX, consumer projection, and unrelated cleanup remain unchanged.
53
+ - `make test` / `make test-integration` / installed managed recovery: `TestRecoveredBrokerRetiredBarrierCannotRepublishAuthority`, `TestDefaultBrokerEndpointRequiresProbeWireAndRouteAgreement`, `TestRecoveredDefaultBrokerRebindsExactAuthorityAndControlConsumers`, and `TestRecoveredGenerationAuthorityRejectsStaleEndpointAndActivation` cover default socket probe/initialize/version agreement, current barrier publication, exact surviving Agent/Pane/activation/thread endpoint CAS, current control consumers, old-runtime epoch-1 refusal, old writes zero, and stale/unknown/Draining refusal. `TestRecoveredBrokerConcurrentOpenSharesOneCurrentView`, `TestRecoveredBrokerOpenWaiterHonorsCancellation`, `TestRecoveredBrokerCloseCancelsPendingRouteAndEnsure`, and `TestEnsureCancellationDuringStartupLockNeverLaunches` verify shared publication, cancellable queued Open, bounded Close during route/Ensure, and no launch after startup-lock cancellation. `TestRecoveredDefaultBrokerRefusesNewRollingJournalBeforeSameEndpointReopen` verifies typed read-only admission refusal before probing or reopening the same endpoint. Existing broker disconnect, runtime replacement, owned lifecycle cancellation, and Phase 0 usage timing tests remain required. `TestInstalledManagedCodexAuthorityRecoveryMatrix` takes only an explicit `PROJMUX_CODEX_RECOVERY_INPUT` JSON file: private root, exact installed binary/SHA/source head/tree, two read-only release mounts, protected private auth input, explicit model and ten fresh validation inputs, host namespace IDs, and evidence path. It uses real installed projmux, real tmux, official managed daemon/proxy, and the repo Go client for baseline 0.151.0, upgrade 0.154.0, and three same-version restarts; one survivor and a new control complete actual turns in every row. Its content-free ledger retains failures and submission counts; it never replays work. Before each create/turn input, the same verified selection guard used by the zero-input diagnostic records exact fixture shim/release evidence and refuses a fallback CLI before incrementing submissions. `TestInstalledRecoverySelectionRefusalKeepsSubmissionsAndProviderCallsZero` reproduces execution-denied shim PATH fallback and pins create/turn refusal with zero inputs; `TestInstalledRecoverySubmissionRecordsSelectionBeforeExactlyOneInput` pins evidence-before-input ordering and rechecks every later sample. Run `go run ./internal/testutil/codexinstalled/cmd/managed-preflight </absolute/input.json>` first to verify the same general fixture helper with official start/version/stop, PID birth/artifact/socket/executable checks, and no real inputs or auth. `TestManagedRecoveryColdStartRequiresExactAbsentState`, `TestManagedRecoveryVersionMissingClassificationIsExact`, and `TestManagedRecoveryCleanupNeverFallsBackAfterUnknownStart` cover exact initial ENOENT plus owned absent state, unknown-result refusal, and zero fallback stop/signal/removal after unresolved ownership. Both runners refuse shared namespaces, ambient managers, and missing init reapers. Each turn waits for exact control publication and a successful read-only status response as well as registry authority. The matrix's unique tmux server has a fixture-private default-name socket alias for outside-tmux public commands; cleanup verifies the exact socket and waits for normal owned process exit. Linux managed conformance is opt-in and separate from Darwin compile/CI and generation qualification.
54
+ - Managed recovery failure evidence: `TestInstalledRecoveryReadinessReachesProductionControlThroughCanonicalAlias` reproduces the readiness fixture's unsupported capability spelling before any tmux/control call and requires readiness to resolve `agent turn start`, then issue status only through the production exact control consumer. It tests baseline and later current authority through the outside-tmux default alias, canonical logical socket and exact physical socket/PID proof; the public turn-start handler consumes the same route/epoch. Empty optional Codex `SessionID` remains empty while the nonempty ThreadID stays exact. `TestInstalledRecoveryReadinessAndTurnRefuseCanonicalAliasDriftBeforeControl` requires both readiness and turn start to make zero control calls on route drift. `TestRecoveryErrorDistinguishesNoBrokerRefusalAndRedactsUnknownContent` requires `RefusalNone`/generic errors to stay unclassified without a false broker label, while known typed codes remain useful and unknown content is redacted. `TestRecoveryFailureEvidenceRetainsPartialIdentityWithoutContent`, `TestRecoveryReadinessDistinguishesRefusalsAndOnlyRequestsStatus`, and `TestRecoveryObserverEvidenceFiltersIdentityVocabularyAndContent` preserve each attempt/created UID before completion, bounded partial Registry and exact tmux/control observations, and terminal process/observer evidence without provider content. Unknown error/message/approval fields are omitted or classified; diagnostics never submit a turn. `TestCleanupEvidenceRetainsErrorsWithoutRetryOrFalseSuccess`, `TestCleanupEvidenceReadsOnlyOneStatForValidatedProcIdentity`, `TestCleanupStatProjectionRejectsMalformedOrDifferentPIDWithoutContent`, and `TestOwnedProcessObservationErrorRetainsCauseWithoutContent` cover both harnesses' cleanup evidence: closed stage/operation, numeric errno, validated proc PID/leaf and at most one bounded later stat birth/state snapshot. Failure diagnostics retain the original error; their optional later snapshot grants no cleanup authority or false success and never rereads environment/argv or retains raw errors/process contents. `TestOwnedProcessesPermissionDeniedZombieRemainsNonemptyResidual` reproduces environment EACCES followed by an exact zombie stat. Only after a verified official lifecycle and rechecking the same private PID/mount/network namespaces with an init reaper may the general fixture retain two matching bounded zombie stat observations as an explicit `unreaped-residual`, distinct from an `owned-executable` and never signal authority. `TestOwnedProcessesPermissionFailuresRemainErrorsUnlessExactPrivateZombie`, `TestResidualAdmissionRequiresRecordedPrivateLifecycleProof`, `TestOwnedProcessesResidualRequiresNormalReapingBeforeCleanup`, and `TestOwnedProcessesFailedCensusCannotClearEarlierResidual` require live/unknown permission failures, malformed/disappeared/reused stat, and changed namespace proof to remain errors; known residuals block cleanup until a successful later census proves normal reaping. Existing drain deadlines remain unchanged. `TestCleanupResidualHistoryRetainsUnreapedEvidenceAfterNormalDrain` pins both installed ledgers retaining bounded PID/birth/state/parent residual history even after the inventory becomes empty; evidence overflow refuses rather than dropping observations.
55
+ - Installed command failure evidence: `TestInstalledCommandFailureCapturesOnceWithoutOutputOrFalseOutcome` verifies one fixture command invocation, numeric exit capture, inherited tmux environment removal, unchanged successful output, and zero raw stderr/stdout/argv in failure ledgers. Both installed runners save bounded `commandFailure`; the matrix also attaches it to the current attempt without changing submissions or completion counts. `TestInstalledCommandFailureProjectionRequiresBoundedKnownCLIShape` accepts only closed turn-start response/message pairs or established CLI wrapper prefixes, validates and discards resource-selector recovery suffixes, and refuses multiline/control-byte/unknown/oversized content. Stderr capture drains at a fixed 4096-byte memory bound. `TestInstalledCommandFailureRetainsUnknownOutcomeAfterReadinessAndControl` exercises the public Cobra bridge and exact control handler: status readiness can precede a failed fresh state read, a failed wire call, or an error recording an accepted turn. These are boundary fixtures, not proof of any actual failed invocation's cause. Every failure retains `outcome=unknown`; there is no retry, partial-output success, or provider mutation inference.
56
+ - Zero-input managed connection diagnostic: `TestInstalledManagedCodexConnectionDiagnostic` accepts only an absolute `PROJMUX_CODEX_CONNECTION_INPUT` JSON file with private root, exact binary/SHA/source head/tree, two read-only releases, host namespace IDs and external evidence path. It proves the same real tmux/Registry route, then official manager ownership and production default Current/shared/owned initialization and Close for 0.151.0 and 0.154.0. There are zero Agents, threads and provider inputs; no Bind, thread read/start/resume or turn call. `TestConnectionDiagnosticInitializesAndClosesWithoutProviderOperations` and `TestConnectionDiagnosticRefusesModelAuthAndProviderInputFields` enforce those limits. `TestManagedSelectionShimAndProbeFollowCurrentLinkWithoutVersionCache` isolates the fixture environment producer and production PATH manager-probe consumer across a current-link change using explicitly synthetic manager responses: CLI, managed and manager-evidence versions follow the link, attached versions remain empty, and a running-manager/absent-wire contradiction refuses attach and lifecycle mutation with unknown ownership. `TestConnectionSelectionEvidenceDistinguishesShimReleaseAndDrift`, `TestConnectionFailureProbeCannotReplaceCurrentRefusalWithLaterHealth`, and `TestConnectionHealthEvidenceRejectsUnboundedProviderStrings` retain exact fixture shim/selected-release paths and hashes, state domain, and closed health/CLI/managed/running/wire versions. A failed connection gets one bounded later read-only probe, explicitly separate from the failed Current observation; later readiness never replaces that failure or retries Current. Unexpected selection refuses before another CLI invocation. This diagnostic does not replace the actual survivor matrix. Run with a private TMPDIR outside CODEX_HOME (for example `/artifact/tmp`) to avoid official CLI PATH-helper warnings. `TestExplicitInputIsBoundedAndCannotTraverseOrFollowSymlinks` covers the shared regular JSON input reader: absolute clean path, parent directory capability, no final symlink, maximum 64 KiB.
57
+
43
58
  - `make test` / installed opt-in smoke: Codex generation-pool Phase 3 routes prompted and payload-free native create through one exact current `stateDomainID + endpointGenerationID`, then keeps resume, message/reply/approval, lifecycle observation, and TUI argv pinned to the durable Agent ref even after admission-current changes. `TestCodexNativeCurrentChangePinsExistingAgentAndAdmitsNewCreateOnlyToNew`, `TestCodexNativeResumeRouteUsesDurableEndpointAndRefusesLegacyOrDraining`, `TestOfflineCodexResumeRechecksDrainingInsideTransactionBeforeProviderWrite`, and the Agent-control action table pin old/new cross-wire zero, locked-transaction lifecycle revalidation, typed missing/Draining refusal, and provider/Registry first-write zero. `TestGenerationRoutedBrokerEpochPublishesItsExactRuntimeAuthority`, `TestCodexNativeEndpointRouteTransportShapeIsClosed`, `TestDefaultCodexStateDomainIdentityUsesExactCanonicalCodexHome`, and the broker transport tests require real generation-keyed broker runtime authority, default/private transport XOR, and canonical content-free state-domain identity; a changed `CODEX_HOME` never reattaches a stored Agent. `TestNativeResumePickerRowsPreserveExactGenerationThreadAndSource`, `TestNativeResumePickerDuplicateVisibilityUsesKnownOwnerOrRefusesAmbiguous`, and `TestCodexResumePickerPinnedValueShapeIsClosed` freeze source/domain/generation/state in each native row value, visibly mark handover/unavailable rows, refuse duplicate visibility without a durable owner, and keep rollout rows unable to carry partial native authority. `TestInstalledIsolatedGenerationPinnedEmptyPromptCreateSmoke` is opt-in through `PROJMUX_CODEX_PHASE3_SMOKE_ROOT=/tmp/<isolated-root>` and runs the installed binary against a fixture-owned unmanaged direct endpoint plus unique Registry/tmux roots; it first bootstraps the fresh generated app config through the installed public `config apply` path with no live isolated server, then accepts only the exact tuple's closed Phase-7 result: durable-ready keeps the historical payload-free Agent↔Pane↔thread↔endpoint, zero-turn, pinned-TUI, broker, obligation, ambient-mutation-zero, and cleanup assertions, while typed not-ready preserves one exact Failed Agent/thread/endpoint with Pane/TUI/plain fallback zero and an unresolved exact-UID retry with second Agent/thread/lane zero. Installed Codex does not subscribe the broker until the first real input materializes the rollout, so the successful pre-turn row keeps native authority unavailable instead of adding dummy content; the provider-recorder integration owns first-real-input same-thread/one-turn proof without requiring ambient auth. Each observation records its exact CLI/app-server/platform/route tuple and is not a version-family guarantee. Phase 5 old-endpoint stop, successor same-thread resume, endpoint-ref CAS, Pane relaunch, retirement, lease release, and foreign adoption remain out of scope.
44
59
  - `make test` / `make test-integration` / private installed observation: Codex generation-pool Phase 4 owns explicit crash-resumable `plan|apply|resume|abort`, at-most-once admission-current, and old-generation drain. `TestRollingUpgradeStateMachineKeepsPhase5EffectsAtZeroAndReceiptsAtMostOnce`, `TestRollingUpgradeStateSpaceProperty`, and `FuzzRollingUpgradeValidStatesNeverContainPhase5Effects` pin monotonic receipts and all seven Phase-5 effects at zero, including `leaseRelease`. `TestCoordinatorExactPhase0TuplePreflightRefusesBeforeEveryMutation` re-opens both complete leases read-only and pins exact bundle/version/RoleTUI plus same-domain, direct-socket, and non-overlapping private/state/lease-root topology before any physical start. `TestCoordinatorResumeAfterLaunchBeforeProofReusesOneCandidateAndLeavesNoDuplicate`, `TestCoordinatorAbortFencePreventsStalePlannedResumeFromLaunching`, `TestCoordinatorAbortAndAdmissionRaceNeverStopsCurrent`, `TestCoordinatorEveryFailpointCrashRestartConvergesAtMostOnce`, and `TestRollingJournalAtomicWriteCrashHooksRestartWithoutDuplicateEffects` cover coordinator death after supervisor launch, all ten declared coordinator failpoints, both journal atomic-write crash hooks, exactly-once candidate-start/admission/drain/handover convergence after restart, the durable abort fence, and deterministic Abort-vs-admission schedules without fixed sleeps. `TestCoordinatorCandidateReadyAbortCleansExactCandidateAndKeepsSevenPhase5EffectsZero`, `TestCoordinatorLaunchBeforeProofAbortCleansRecoveredCandidateWithoutPhase5Effects`, `TestRemoveCandidateSocketRefusesUnknownOrReboundIdentityWithoutUnlink`, `TestCleanupDurableCandidateAfterSupervisorExitRemovesOnlyOwnedSocketAndReceipts`, and `TestCleanupDurableCandidateRecoversOnlyExactUnlockedOrphanGuard` pin exact candidate cleanup, retained bundle lease, unlocked no-socket orphan-guard recovery, and locked/foreign/rebound mutation zero. `TestExactRoleTUIArtifactRefusesWrongAbsolutePathAndPostVerifyDrift`, `TestCoordinatorWrongAbsoluteTUIPathNeverCommitsAdmission`, and `TestCoordinatorTUIDriftAfterReadyProofFailsInsideAdmissionBarrier` require the exact single RoleTUI lease artifact at publication and commit. `TestCoordinatorTwoSlotThirdUpgradeRefusesWithEveryMutationSurfaceAtZero`, `TestCoordinatorRetainedRetiredRoutesDoNotConsumeThePreparingSlot`, and `TestCoordinatorAbortedOperationRefCannotBeReplayed` close capacity/replay consistency. `TestAgentResumeDrainingGenerationFailsClosedWhenHandoverRequesterIsUnconfigured` tables both Draining and HandoverPending with an unconfigured requester, while `TestAgentResumeDrainingGenerationReusesOneGenerationWideOperationRef`, `TestCoordinatorRepeatedDrainingResumeReusesOneGenerationHandoverOperation`, and `TestDrainingOldCodexAgentKeepsExactTurnControlTUIAndPaneBinding` cover exact operation-ref reuse and direct old-generation turn/control without endpoint/TUI/Pane mutation. `TestPrivateRollingAdmissionReceiptRendersAllSevenPhase5EffectsAtZero` and opt-in `TestInstalledPrivateRollingAdmissionReceipt` cover content-free receipts, private process groups, ambient/default read-only observation, exact old Draining thread read plus successful no-turn-compatible listing, payload-free new Current create/read, assertion-safe exact target teardown, and ambient-preserving cleanup. Deletion ledger: zero canonical tests deleted or renamed and zero Phase 0–3 boundary suites merged; the canonical graph baseline only adds the four explicit upgrade leaves. Phase 5 effects remain zero in the unchanged Phase-4 receipt and are implemented only by the distinct linked handover journal.
45
60
  - `make test` / `make test-integration` / opt-in installed private observation: Codex generation-pool Phase 5 owns `agent app-server handover plan|apply|resume|abort` and the generation-wide `stop → resume → snapshot-all → endpoint CAS → same-Pane relaunch → terminal retirement → lease release` journal. `TestGenerationHandoverOrdersStopResumeSnapshotCASRelaunchTerminalLease`, `TestGenerationHandoverCannotCASUntilEverySnapshot`, and `FuzzGenerationHandoverNeverAcceptsUnsafeReceiptOrder` pin monotonic per-effect intents, the global snapshot barrier, and terminal lease ordering. `TestCoordinatorGenerationWideOrderingAndEveryFailpointResumeConvergesWithoutDuplicateEffects`, `TestCoordinatorRecoversExactSameGenerationBeforeHandoverAndCrashRetryIsIdempotent`, and `TestCoordinatorUsesQualifiedFallbackWhenExactSameGenerationRestartIsUnavailable` cover every coordinator effect gap, journal-authoritative repeated Apply, same-generation-first cold recovery, and exact-qualified fallback without a fixed sleep. `TestExternalOwnersRemainAwaitingOwnerStopWithLifecycleArgvZero`, `TestIncompleteManagedOldRouteBlocksBeforeEveryEffect`, `TestSuccessorPreloadedTargetStopsBeforeOldStop`, `TestPlanIgnoresSameGenerationIDFromAnotherStateDomain`, `TestAppliedNoTurnChoiceIsForwardOnlyAndCannotClaimAuthorityRestored`, and the abort-fence tests pin the ownership matrix, exact state-domain tuple, reversible-only abort, explicit no-turn choice, pre-stop loaded-thread refusal, and foreign lifecycle argv zero. Production-effect tests revalidate the pinned Agent/Pane/runtime/generation/thread tuple before successor absence, old stop, resume, snapshot, and same-Pane relaunch; `TestHandoverResumeReceiptSurvivesSuccessorRestartWithoutSecondResumeWire` pins the content-free operation-qualified at-most-once resume receipt. `TestInstalledPrivateJournaledGenerationHandover` is opt-in through `PROJMUX_CODEX_PHASE5_SMOKE_ROOT=/tmp/<isolated-root>` and uses exact private 0.152.0→0.152.1 binaries plus isolated XDG/tmux roots for the public handover and no-turn negative. Enforcement ledger: prompt/turn/approval resend, provider-content replay/inspection, duplicate same-thread ownership, automatic no-turn migration, foreign kill/adopt, live-old same-thread resume, Phase-6 consumers/Doctor, and unrelated cleanup remain zero. Deletion ledger: zero canonical tests deleted or renamed; Phase 0 qualification, Phase 1 lifecycle fencing, Phase 2 host/pool, Phase 3 generation-pinned routing (including the empty-prompt migration receipt), and the Phase-4 seven-effect-zero suite retain their unique symbols and boundaries; the command graph adds only four handover leaves.
@@ -52,6 +67,9 @@
52
67
  - `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 2 closes the role/live consumer table. `TestAnchorAwareCreatePaneAndAgentUseExactLiveShellAnchorDetached`, `TestAnchorAwareCreatePaneAndAgentUseExactLiveAgentAnchorDetached`, `TestAnchorAwareCreatePaneAdoptsNewShellAsEmptyDefaultWithoutReplacingShellAnchor`, the paired resume tests, and the explicit-Pane/popup precedence tests pin detached exact-Window outcomes and create-time default-shell adoption. `TestAnchorAwareCreateRefusesDeadAndCrossWindowAnchorsWithZeroWrites` and `TestRegistryTopologyMaterializationRefusesDeadAnchorWithoutAlternateLivePaneInference` prohibit alternate-live-Pane inference. `TestRegistryTopologyMaterializationAgentAnchorLazyDefaultShellAndRepeatNoop` plus the installed isolated real-tmux Agent-only Window fixture pin the plan-visible lazy shell, preserved Agent anchor/Pane uid, exact default owner/ref graph, smoke-root socket containment, and repeat no-op. `TestFocusKindOnAnOfflineTargetExitsTwoAndMaterializesNothing`, `TestFocusKindMovesTheClientToAnAlreadyLiveTarget`, and `TestControlOwnedRegistryKeepsMaterializeGreen` cover strict read/navigation-only focus and the Project/ControlSession root boundary.
53
68
  - `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 3 keeps canonical Pane/Agent delete and clean exit independent from explicit Window deletion. Registry-order sibling choice, optional-empty default shell, Offline Agent retention, and same Window UID/name remain pinned for non-last Pane deletion. Project runtime stop Phase 1 supersedes the old last-Pane replacement boundary: an exact clean last-Pane receipt waits for its matching `window-unlinked`, then deletes only the Window subtree with zero replacement allocation. Explicit `delete window --yes` uses the same root-preserving shape: a non-last primary reanchors to its existing sibling and a last Window leaves the Project uid/root/reservation with empty `primaryWindowRef`; only explicit `delete project` unregisters the root. Unknown, killed, unpaired, foreign, stale, missing-server, and empty observations retain zero Window-delete authority.
54
69
  - `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 4 closes projection and startup over final-v2 desired state. `TestSnapshotProjectionFinalV2RefsAndOpenFreshGolden`, `FuzzSnapshotProjectionIsScopedValidAndIdempotent`, and `FuzzOpenFreshKeepsOnlyCanonicalProjectShell` pin metadata-bearing and legacy snapshot projection, Agent-anchor/default-shell policy, target-subtree replacement, source-snapshot preservation, and fixed points. `TestProjectFreshStartPruneScope` preserves an existing canonical Project/Window/shell UID chain and exact transaction-level Registry bytes across repeated Open fresh, while `TestProjectFreshStartAgentAnchorCreatesExactMinimumShellAndRepeatsNoop` covers its one permitted shell allocation; `TestRestoredOfflineAgentAnchorContinueConvergesLazyShellAgentAndRepeatNoop` covers offline restore through lazy default-shell/Agent materialization. `TestAuthorshipPromotionPreservesSiblingProjectSocketAndOtherHostDesiredState` pins exact-socket positive convergence while sibling-socket and other-host-only desired refs/status plus Registry order remain byte-identical. The isolated smoke covers snapshot/Open fresh closed repeats with the exact UID chain, lifecycle Window survival, create/resume/materialize/focus, failure/repeat paths, and exact-socket sibling isolation.
70
+ - `make test`: Install handover Phase 1 keeps every Window anchor writer inside the validator's anchor predicate. `TestWindowAnchorEligibilityPredicateBranches` pins the membership, role, and managed-Agent-Pane clauses of `windowAnchorEligibility`; `TestDeletePaneReanchorSkipsReleasedAgentPaneForEligibleShell`, `TestTransitionAgentReleasingAnchorPaneMovesAnchorToEligiblePane`, `TestAnchorWritersCloseOnNewDefaultShellWhenNoPaneIsEligible`, and `TestAttachAgentPaneMovesAnchorFromPreviousBindingToNewManagedPane` cover delete reselection, termination release with an empty `paneRef`, the default-shell close with preserved ineligible rows, and rebind including the snapshot-projection Offline binding; `TestAnchorWritersPreserveValidationOverRandomWindowConfigurations` (seed 20260913, 512 iterations) requires `Validate` after every writer; `TestTopologyAgentContinueAdmitsTerminatedAnchorAgentWithEmptyPaneRef` keeps the terminated anchor Agent out of the `still records a current paneRef` refusal.
71
+ - `make test`: `TestStampProjectSnapshotSkipsUnboundPanesInRuntimeIDDuplicateCheck` pins that snapshot save's Pane runtime id duplicate check skips only a `MissingRuntime`=True/`RuntimeUnbound` Pane: its retained tmux id reused by a live Pane stamps the live Pane's UID, leaves the unbound Pane's `Status.Activation.RuntimeID` intact and absent from the snapshot, while two live Panes, a False condition, or another reason sharing the id still refuse with `share runtime id`.
72
+ - `make test`: `TestStampProjectSnapshotSkipsUnboundWindowsInRuntimeIDDuplicateCheck` pins that snapshot save's Window runtime id duplicate check skips only a `MissingRuntime`=True/`RuntimeUnbound` Window: its retained tmux id reused by a live Window stamps the live Window's UID, leaves the unbound Window's `Status.RuntimeID` intact and absent from the snapshot, while two live Windows, a False condition, or another reason sharing the id still refuse with `Registry Windows ... share runtime id`.
55
73
  - `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 3 projects a closed, content-free app-server lifecycle into Agent interaction, badges, attention, and notifications. Unit and fuzz event sequences pin exact thread/turn/item/request approval identity, waiting-plus-unresolved gating, resolution and leave-waiting clears, successful completion once, failed/interrupted non-completion, and old-epoch/foreign-identity writes at zero. Fake proxy tests pin observation-only server requests with no approval response, snapshot convergence, invalidation-before-fallback ordering, reconnect replacement, and all Codex hooks writing zero through pending/healthy/invalidating authority. Settings and delivery tests pin the independent `approval_required`/`response_complete` Notify/State only/Quiet matrix across app-server and hook-fallback epochs, explicit raw fallback-only precedence, exact non-Notify queue clearing, raw hook-byte preservation and no inference, mixed multi-Pane source/reason/epoch diagnostics, Quiet aggregate-badge suppression, and content-free `describe agent` rows. Isolated tmux integration and e2e cover exact Registry/Pane/generation/thread binding, native/fallback/reconnect behavior, queue/desktop outcomes, sibling containment, and a privacy/scope inventory that excludes prompt/control, progress/diff, catalog/session-state, provider configuration, schema, and release changes.
56
74
  - `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 7 admits only bounded scalar progress from the exact current app-server turn. The adapter drops plan explanation/step text, diff bytes and paths after a 256 KiB header-only scan, and every item body while mapping only the installed-schema discriminator into the ten-value activity set. Unit and fake-clock properties pin 99 plan steps, 999 changed-file headers, 32 opaque dedupe ids, deterministic dropped/unknown/overflow counters, identical/out-of-order write zero, and at most four non-terminal Registry writes per Agent per second. Adapter-to-reducer-to-Registry/read/UI tests pin exact activation turn attribution, KO/EN 80/100/120-cell rendering, read-time-only Window counts, and immediate zero-history clear on completed/interrupted/failed turn, generation replacement, disconnect/invalidation, Offline, and Failed; unavailable native progress leaves the existing interaction UI unchanged. Privacy inventories reject prompt, reasoning, step, command/tool/file/path/name/output, diff body/hash, model, and effort fields across the reducer input, durable Agent status, describe/Inspector projection, tmux options, and diagnostics.
57
75
  - `make test`: Codex native control plane Phase 0 keeps the app-server wire in `internal/integrations/agents/codexappserver` and pins the headerless JSON-RPC JSONL frame bound, monotonic request IDs, out-of-order response routing, initialize/initialized lifecycle, typed notification delivery, context-bounded request and initialized-notification writes, local cancellation with ignored late responses, and connection replacement with no goroutine leak. A fake stdio proxy matrix closes healthy, missing/disconnected, unsupported `-32601`, malformed, and timeout outcomes into exactly one of available/unsupported/unavailable/timeout/protocol-error, while Doctor, support-report redaction, and the Settings no-op state row expose only source/reason/version/endpoint-kind/connection-state. Negative source and argv audits keep daemon mutation, login, config writes, remote transport, all existing Agent/hook/resume/usage consumers, giant provider interfaces, and Settings source controls at zero.
@@ -134,6 +152,7 @@
134
152
  This v4 row supersedes the automatic short-name, direct-owner reservation, and stored-presentation assertions in the earlier historical Registry observation and selector entries; those entries continue to document the defects and seams that produced the current tests, not the current wire contract.
135
153
  - `make test-e2e`: Project-wide naming closure Phase 2 automatic-name witnesses against real tmux. L06 `create-materialize` registers alpha with an explicit `--name alpha` and beta with no `--name` at all, then proves the automatic Project name is the exact created uid: beta's uid is read back through the read-only `get projects -o json` projection selected by beta's exact root -- never by listing order, and never by re-issuing the create, whose reuse projection a later Phase owns -- the root-selected uid is confirmed to be the Project bound to that exact root, `describe project uid:<uid> -o name` equals the uid byte-for-byte, and the create result line is `project/<uid> created` rather than any semantic stem; every later beta reference in that scenario is therefore a `uid:` reference, because the automatic name is no longer the word `beta`. L12 `first-open` proves the same for an implicitly registered Project, then separates the two mirrored fields whose v4 values coincide: `@projmux_project_name`, `@projmux_project_uid`, and `@projmux_project_path` are each asserted on the freshly opened `work-gamma` session, and because an automatic name *is* the uid there, two explicit `rename project uid:<uid>` round-trip steps on the inherited absolute socket make the fields diverge -- the name mirror follows every address change while the uid mirror, the path mirror, and the session name stay exactly where they were, with the failure message naming which of the three drifted. L07 `binding-convergence` adds the cross-kind half: an alpha Window and an alpha Pane both named `shared` coexist in the one root and `describe window shared -o uid` / `describe pane shared -o uid` each return their own exact uid. L06 also proves the automatic Agent name is the exact Agent uid -- the payload-free `create codex` shortcut allocates exactly one new `agent-<uid>` identity rather than reusing the provider-named first Agent, a `--` payload reaches the provider and never the name, no numeric suffix exists to append, and an explicit `--name` colliding with the one human-chosen Agent name in the owning root is still exit 2 with zero writes and no new pane. L14 `registry-navigation` keeps its imported shell Pane automatically named and renders it offline, which is covered by the compact-default picker contract below.
136
154
  - `make test` / `make test-e2e`: Columnar picker Phase 2 supersedes the naming Phase 2 NAME/RESOURCE workaround. `TestPickerCompactFullUIDAndHangulFits80Columns` fixes the compact `KIND NAME STATUS ACTIONS` first frame inside 75 cells with full UID names, Hangul context and real action lists; only repeated provider/phase and role display adornments move to wide. `TestPickerCompactNativeFramePreserves76CellRegression` keeps the prior 66-cell fixture and the observed 76-cell clipping regression, proves the latter clips under the real renderer pipeline, then checks every full compact field after picker-local one-cell spacing (63/73 cells). `TestPickerWideNativeFrameViewportMatrix` runs the same native frame/scrollbar/pointer pipeline at 80/120/180/184 columns: the historical 142-cell row clips through 180 and is complete at 184; wide retains its two-cell gap. `TestPickerWideRetainsUnboundedValuesAndDetailRecovery` checks full NAME/RESOURCE/REASON/raw-target values, per-field row detail recovery and Runtime CLI semantic parity. `TestPickerProfilesUseExactCatalogFieldOrder` pins both catalog projections. `TestColumnPickerProfileSettingsAndNativeStateParity` covers the two canonical catalog actions, default Alt-W and a plain single-key Settings override, effective footer keys, query/selected-value restoration, compact-wide-compact transitions, reopen reset and zero keymap writes. `TestColumnPickerToggleDoesNotObserveOrWriteAgain` exercises both command loops, including empty filtered results, and proves unchanged row cardinality/order/search plus no extra tmux observation or runtime writes. L13 and L14 drive both toggles through attached real tmux clients and retain exact popup-process completion and zero-Registry-write checks. Installed isolated 80x24/120x30/180x40 plus 184-column smoke observes compact visibility, expected narrow wide clipping and detail/JSON recovery. The historical 142-cell Registry and 120-cell Runtime rows are representative measurements, not arbitrary-data upper bounds; current actions and free text may widen them. The former picker truncation helpers and all NAME/RESOURCE/RUNTIME/REASON bounds are removed; the source PROGRESS policy, footer rendering, provider vocabulary, CLI/JSON/scalar projections and identity authority are unchanged. [Column profiles](column-profiles.md) describes the open-lifetime control and recovery contract.
155
+ - `make test` / `make test-integration` / `make test-e2e`: Default resource AGE Phase 0 extends only the four Registry CLI compact lists to `NAME STATUS ACTIONS AGE`. `TestGetListDefaultAgeMatchesWideAtFixedClock` compares default/wide cells on the same row and injected clock for every kind, covering second/minute/hour/day boundaries, long ages, future creation stamps, missing `createdAt`, and repeated reads. `TestResourceTableDefaultAgeUnavailableIsEmpty` keeps absent resources and absent clocks as empty AGE cells in both profiles. `TestGetListDefaultProjectionIsColumnar`, `TestResourceTableColumnsAreTheCanonicalContract`, and `TestGetReadFamilyResolvesEveryKindWithListCardinality` pin the default table/header contract. `TestColumnProfilesDoNotSelectFromWidthAndWideIsUnbounded` retains AGE at COLUMNS 40/80/112/120 with byte-identical default output; resource CLI tables retain content width and can wrap on narrow terminals. `TestColumnProfilesExactOutputAndSnapshotParity` changes only four compact golden sections and freezes wide, JSON, metadata, scalar and runtime output; `TestColumnCatalogCompletenessBijectionAndExactProfiles` preserves the other three surfaces. Existing empty-list and scoped-row guards remain active. Integration and E2E compact recovery assertions accept the fourth AGE column. AGE still uses the existing `resourceAgeCell` and `i18n.FormatDuration` calculation.
137
156
  - `make test` / `make test-integration` / `make test-e2e`: Route-implied kind omission changes only the four Registry CLI compact lists to exact `NAME STATUS ACTIONS`. `TestColumnCatalogCompletenessBijectionAndExactProfiles` and `TestGetListDefaultProjectionIsColumnar` reject compact KIND reinsertion and wide/mixed KIND removal. `TestCompactResourceKindOmissionPreservesScopedRowsAndRecovery` covers all four routes with unscoped, all-projects, Project, Window and exact-Pane selectors where supported, including empty results, ordered UID sets, full NAME/STATUS/ACTIONS parity and wide/JSON kind recovery. Each fresh read runs `assertColumnReadCounters` (one Registry load, four existing tmux queries, zero Registry transactions/writes and zero sibling calls). `TestColumnProfilesExactOutputAndSnapshotParity` keeps the wide/JSON/metadata/scalar/runtime byte goldens and ACTIONS snapshot fixed; only four compact golden sections change. `TestMixedPickerProfilesRetainKindCells` verifies every mixed Registry/Runtime row retains KIND in both profiles; the existing toggle, query/selection, 80-column and detail-recovery guards remain active. Integration checks the restored Window's compact/wide/JSON cells; L06 repeats four-kind nonempty/empty default/wide/JSON recovery on real tmux and compares Registry bytes. This stdout change moves NAME, STATUS and ACTIONS one position left and can break positional parsers; wide and JSON retain kind.
138
157
  - `make test`: Resource timestamps surfacing Phase 0 timestamps in read surfaces: the registry has always stored `metadata.createdAt` on every resource, `Condition.firstObservedAt`/`lastTransitionAt` on every observed condition, and `Agent.status.lastTransitionAt` on every Agent, and `-o json` has always emitted them -- but no human-facing surface printed a single one of them, so `describe pane` carried zero time rows and the columnar `get` carried no `AGE`. Both now render, and nothing new is collected: no tmux query, no schema field, and no second relative-time implementation. The plural reads gain an `AGE` column, last on all four kinds because that is the one column whose width moves with the clock and last is the only position where growing it cannot walk an owner-chain column sideways; it is rendered through `i18n.FormatDuration`'s compact form, the same single implementation `notify list` already renders its own AGE through. `internal/app` pins the arithmetic as a boundary table asserted from both sides of every unit rollover (`59s`/`1m`, `59m`/`1h`, `23h`/`1d`, up to `900d`), plus the three cases that render an empty cell rather than a number -- a uid that has left the registry, a resource stored before `createdAt` was stamped whose zero Time would otherwise date it to year 1, and a caller that passed no clock -- and a whole-table golden spanning `9s`/`47m`/`6h`/`11d` in one read, which is also the alignment case because the widest cell sets the column. Determinism is stated as a two-sided claim rather than a single assertion, since only the pair is falsifiable: the same injected clock twice must be byte-identical (which a renderer ignoring the clock would also satisfy) **and** a clock advanced three days must produce a different table (which a renderer reading `time.Now` would fail), so only a renderer reading the passed-in clock and nothing else survives both. The clock reaches exactly one projection: `writeResourceProjection` takes it as an explicit parameter and the singular call sites (`get pane`, `describe`, `rename`, and the create fan-out, whose default projection is the `<kind>/<name> created` line and never the table) pass the zero Time, which `resourceAgeCell` renders as no age at all rather than as "created exactly now". `describe` gains absolute UTC instants instead: a `CreatedAt` row beside the identity rows on all four kinds, `PhaseSince` under an Agent's phase, and the second half of a condition's stored pair (`lastTransitionAt` beside the `firstObservedAt` that was already there), each omitted when the registry file predates the field so a pre-`createdAt` registry keeps its exact current block. The timestamp machine-consumer contract is frozen the whole way: `-o uid|name|ref|none|metadata` remain byte-identical, and the durable portion of plural `-o json` remains exact around the later Columnar Phase 0 context sibling -- both current whole documents are pinned rather than substring-matched, so a timestamp key anywhere still reddens. The Columnar track's `describe pane` byte-golden moves by exactly one row and keeps its own purpose intact: what it guards is that the columnar table is reachable only with `list` true, and a header line or a padded column in a singular route still reddens it.
139
158
  - `make test`: Settings IA re-cut Phase 0 full navigation cutover: the visible Settings tree is declared once in `internal/app/settings_nav.go` and frozen by `internal/app/testdata/settings-nav-tree.golden`, so an IA change is a golden diff rather than a shape spread across the section loops. Structural tests prove the catalog is a tree (unique ids, resolvable parents, a scope root at the top of every path), that every control hangs off exactly one owning `[View]` and that only a View owns children (row-affordance exclusivity), and that each rendered picker value maps onto exactly one navigation node whose owning loop the closed reachability table actually handles (control-owner cardinality); the drift guard runs in both directions, so a section loop cannot grow a row the golden does not show. The removed-path negative guard proves `Labs`, the separate `Theme` root, `Project recipe` and `Effective merge view` are neither rendered, nor catalogued, nor routable through `runSection` — the retired handlers, action metadata, and i18n entries are absent rather than hidden redirects — and the legacy-copy guard rejects the retired vocabulary (`AI Settings`, `Project Picker`, `Project Root`, `Workdirs`, `Enabled agents`, `Default split mode`, `Resume picker`, `Session State`, `Kill Session`, `New window`, `Hook quiet policy`, `Delivery sources`, `Labs`, `Usage HUD`, `Path icon`, `Notify icon`, …) across navigation labels, rendered rows and keymap display labels, in both locales. Display renaming is proven display-only: the keymap action IDs (`new-window`, `Sidebar:KillSession`, `ai-split-right`, …), the `sessionstate:`/`project-hooks:`/`live-resources:`/`statusbar-decoration:`/`workdir:`/`projdir:`/`switch:`/`keymap:`/`theme:` action spellings and their owner contracts are all unchanged behind the renamed rows. Keybinding navigation is category-based: an exhaustiveness test requires every catalog action to carry exactly one explicit category (never an ID-prefix inference), every surface action to sit in the sidebar/picker category with a display label for its surface, one display label per action, and each action to render exactly once across the rendered categories, while the category rows carry their members' action IDs, labels, chords and state so search still crosses the category boundary. The action detail projects the product semantics the target IA asks for — target kind, result kind, placement and anchor — and pins that every interactive right/down split declares the explicit current-Pane anchor and a right/down placement, that `Create <Provider> Agent` is always-new, and that resume names the existing `Offline`/`Failed` Agent it rebinds. Navigation is proven side-effect free (opening the root, every section and every container writes no config and runs no tmux command), every `[State]` row carries the catalogued passive value so Enter can never fall through, and the promoted rows keep their old state at the new route (Resources reads the same `live-resources` file it read from Labs, the project automation policy the same `project-hooks` file, Theme the same global `[theme]` block through `Appearance > Theme`). The pinned Project item view keeps Project identity separable (display name, unique name, uid, bound root, condition, missing-since, runtime) and offers same-uid remediation through the canonical `rebind project` route instead of re-pinning, with the unregistered-path case rendering a disabled rebind row carrying its reason. The Agent Usage HUD ownership guard keeps `agent usage` canonical: no Settings row spells a `Usage` resource or a `get usage` read, and `docs/settings-ia.md` names the owner.
@@ -252,6 +271,8 @@
252
271
  - Picker focused unit coverage includes native picker item/action mapping, direct native routing with no backend selector, stale selector env/file non-observation without lookup/delete/rewrite/propagation, native title-focused filtering, numeric selection, shared close actions including raw native custom actions, fuzzy-score reference fixtures, AI picker title chrome and stable search-key ordering, Settings title chrome and root section order, Settings Labs without picker source or hidden keybindings rows, compact multi-line metadata gutters with one-column-indented metadata, proportional native scrollbar thumb rendering, native row width clamps that preserve ANSI resets and Korean/wide-cell frame geometry with matched scrollbar/no-scrollbar row budgets including Settings > Appearance AI badge preview rows and parent long emoji preview rows, native app-background and semantic selected/muted/accent/warning/critical role coverage for fallback/effective themes across content padding, empty no-footer rows, footer rows, scrollbars, split-preview gaps, reset-to-padding continuation, and Korean Settings > Appearance AI badge style chips/prompt/emoji rows, multiline partial next/previous item row rendering with rendered-row scrollbar units, fixed split-preview and sidebar list viewports with scrollbar tracks, native Up/Down and Ctrl-N/Ctrl-P navigation wrap with empty-list safety and PageUp/PageDown/Home/End clamp regression coverage, native mouse down/follow-drag/release behavior, deferred native row updates that preserve filter/selection state and can repeat from event triggers, preview tab/control normalization before width clipping, optional native frame titlebars without same-line rule fill and with title/chip gap inheritance guards, titled Alt-1 sidebar chrome, statusbar-preserving sidebar popup height, compact project sidebar popup sizing, cheap Alt-1 first-paint rows with deferred git/window/attention/preview enrichment, fixed three-line Alt-1 sidebar row geometry across cheap/enriched paints, bulk switch existing-session classification with per-candidate fallback, and notify sidebar title/popup sizing.
253
272
  - Picker/setup lifecycle coverage includes deferred native key-reader stop and repeated reopen cleanup, plus per-probe controlling-TTY cleanup and timeout-then-next-key reads without stale goroutines stealing input.
254
273
  - Doctor focused unit coverage includes tmux required/stale behavior on every host, Windows `stty` skip behavior, and tmux bell fallback diagnostics.
274
+ - `make test`: Codex endpoint diagnostics C-1 is enforced by `TestDoctorCodexEndpointMismatchExactActivationSet` (mismatch/match/mixed/empty and installed-CLI skew), `TestDoctorCodexEndpointMismatchIncludesBoundNonRunningActivation` (current activation scope), `TestDoctorCodexEndpointMismatchSignalsEnumerationGaps` (missing/orphaned/conflicting activation, foreign/opaque endpoint and unavailable observations), and `TestDoctorCodexEndpointMismatchDoesNotGuessPrivateGenerationRouting` (version-shaped private IDs and unknown pool routing). `TestDoctorCodexEndpointMismatchTextJSONSupportParity` and `TestDoctorCodexEndpointMismatchPartialEnumerationParity` require exact Doctor UID sets, support-hashed identity parity, visible partial enumeration, 2026-09-09 n=1 observed risk with undetermined causality, and no mismatch block for a complete matching census. `TestDoctorCodexEndpointMismatchSupportAllowlistIsScoped` preserves support redaction. `TestDoctorCodexEndpointMismatchReadOnlyArgvAudit` exercises the production Doctor/support probe against an isolated failing proxy, requires visible incomplete enumeration and only `app-server proxy`/`app-server daemon version` argv, and preserves the Codex state tree; it never reaches live app-server, broker, or supervisor processes.
275
+ - `make test`: Codex endpoint diagnostics C-2 is enforced by `TestOperatorRecoveryGuidanceOwnershipMatrix`: unmanaged current, skewed, and unknown-version endpoints name `codex app-server daemon bootstrap` and the observed `pid` backend's need to bootstrap again after reboot; managed and unknown ownership omit that prescription, with readiness/refusal/recovery codes unchanged. `TestDoctorCodexRecoveryGuidanceTextJSONSettingsSupportParity` requires Doctor's bounded sentence and the existing Settings/Doctor JSON/support recovery code to agree without a schema change. `TestOperatorRecoveryGuidanceReadOnlyArgvAndState` exercises repeated Doctor/Settings/support triggers against an isolated ready unmanaged proxy, allows only `app-server proxy` and `app-server daemon version` argv, and preserves Codex state byte-for-byte; recovery execution argv is zero. `TestProductSourceHasNoSharedDaemonStopRestartOrKillArgv` also forbids bootstrap argv in the product source. The reboot observation makes no claim about other backends' boot persistence.
255
276
  - Shell focused unit coverage includes canonical typed Project/ControlSession bootstrap followed by the non-creating foreground route `tmux -L projmux -f <generated config> attach-session -t =<session> [-c <cwd>]`; disappearance after preflight fails closed instead of letting `new-session -A` provision outside the plan. Generated tmux config callbacks, native key-broker platform policy, runtime overrides, and nested-socket guards remain covered.
256
277
  - `make test` also covers npm retire/staging path canonicalization for the resolved projmux executable so generated tmux config and hooks never embed a `node_modules/.projmux-<hash>` directory, plus error-suppressed attention and rebalance-panes hooks.
257
278
  - `make test` also covers the public config spelling: `projmux config edit [--get|--set <mode>]`, `config render standalone|app`, and `config apply` forward raw argv to their manual leaf handlers; flags, unknown tokens, streams, handler errors, and side effects stay on those handlers. `make install` and every update path emit `projmux config apply`, while generated plumbing uses only the hidden `internal` namespace.
@@ -270,7 +291,8 @@
270
291
 
271
292
  - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 8 termination evidence transport binds a managed process's real exit to the Pane it ran in. `internal/core/metadata` adds the per-materialization `status.activation` generation and the optional `status.lastTermination` receipt on Pane and Agent, and its tests pin the closed classification vocabulary (exit 0 is `normal` and never `intentional`; non-zero and signal are `abnormal`), the generation guard across replaced generations, deleted Panes, a shell Pane claimed by an Agent and an Agent that no longer binds the Pane, the byte-identical duplicate no-op, intent recordable only by `source: control-action` and sticky against the signal a deliberate delete causes, the operation-id-scoped withdrawal that refuses to clear another operation's receipt, the generation-guarded runtime handle, and a registry stripped of both additive blocks that decodes, validates, re-encodes byte-identically, and rejects a receipt for a Pane that never carried a generation. `internal/app` adds the hidden `internal supervise` route: the argv builder terminates its own flags with `--` so an operator payload spelling `--pane-uid` cannot be re-read as one, a shell Pane with no command of its own is supervised over tmux's documented `default-command`/login-`default-shell` rule read from the same exact server, and every failure to construct a supervisor -- an unresolvable binary, an unreadable tmux default, a Pane with no generation -- returns the caller's original command so the pane still starts with no receipt rather than not starting. The route's tests pin the recorded wait status for clean exit, non-zero exit, and signal with the matching `128+signum` exit status, a registry that cannot be written costing the evidence and never the pane's own status, a stale receipt reported and dropped, a usage refusal that starts no child, and a launch failure reported as a launch failure rather than a termination. `runSupervisedChild` is exercised against real `sh` children for all three wait statuses and for argv/cwd/environment parity. Create, create-window, create-agent, `agent resume`, and the topology materializer each issue their own generation before the launch argv is built, and a route-level table proves every launched pane quotes the generation the Registry stored for it, that a fan-out never repeats one, and that a receipt from the process a resume replaced changes nothing. `delete window|pane|agent` gains `--socket`/`--socket-path` and now resolves exactly one server from those flags or the inherited absolute `$TMUX`, refusing outside tmux instead of falling back to `-L projmux` -- which supersedes the `-L projmux` inventory the Window and Pane/Agent deletion entries above describe -- and it commits its intentional receipt in its own transaction before the first live mutation, withdrawing it again on every refusal that leaves the processes running. The integration smoke records real shell exit 0, exit 7, and a signal death on an isolated exact socket and reads the receipts back out of an isolated Registry; the e2e smoke drives the same lifecycle against a real tmux server and proves an isolated `delete agent`/`delete pane` dry-run and apply touch only the socket they were given, leaving a sibling socket and the default socket unchanged.
272
293
  - `make test` / `make test-integration` / `make test-e2e`: Agent clean-exit and Continue replay policy Phase 0 records Project-stop provenance without changing replay. `TestProjectStopInterruptionPrewriteSelectsOnlyExactRunningAgentsAtomically` requires every and only then-Running Agent-owned Pane in the exact Project to receive matching generation/operation `interrupted/control-action` evidence in one Registry commit. `TestProjectStopFailureCompensatesOnlyWhenExactSessionIsProvedLive` pins same-operation compensation after a proved-live failure and retention after an apply error whose exact Session is proved absent; the existing external-HUP supervision rows remain `killed/supervisor`, and `TestInterruptedControlActionIsCurrentGenerationStickyAgainstLateSupervisorHUP` prevents a delayed HUP receipt from overwriting Project-stop authority. `TestCanonicalV2MigratesToV3WithoutInformationLoss` and `TestCanonicalV2ToV3PublishesOneLosslessBackupReportAndRepeatsNoop` pin the schema 2→3 lossless transform, exact versioned backup/report, one-time write, and newer-schema downgrade refusal. Describe renders classification/source/generation as separate fields and `get` includes all three in its termination cell. Continue planning, explicit resume, Open fresh, snapshot projection, provider argv, and public hooks remain unchanged in this producer-only Phase.
273
- - `make test` / `make test-integration` / `make test-e2e`: Agent clean-exit and Continue replay policy Phase 1 makes current-generation exact `control-action/interrupted` evidence the sole automatic Agent authority of retained-Project Continue. `TestTopologyAgentContinueEligibilityMatrix` closes source × classification × pre-projection-Running/projected-Offline phase × sessionRef, including nil evidence, stale generation, illegal pairings, and the supervisor normal/abnormal/killed, delete intentional, and reconcile unknown refusals. `TestRegistryTopologyContinueLaunchesInterruptedBOnceAndRetainsCleanA` and `TestClosedProjectStartupContinuesOnlyInterruptedAgents` require clean A launch 0 with UID/sessionRef/evidence retained, interrupted B exact-conversation launch 1, and repeated Continue duplicate 0 with no Registry write or split. A recorded conversation never degrades to fresh when its exact resume argv cannot be built. `TestExplicitSnapshotRestoreRetainsAgentReplayAuthority`, the session-state request assertion, and the isolated L10 topology/L11 startup smokes keep explicit snapshot restore and explicit `agent resume` authoritative. L10 requires raw Window/server HUP launch 0 while exact shell/Window UIDs converge, then uses only its test fixture utility to supply current-generation interrupted authority for the Agent-only anchor replay; L11 proves external HUP launch 0, Open fresh one canonical shell/zero Agent, exact `TMUX`/`TMUX_PANE` removal, unique contained sockets, and exact queried cleanup. Phase 0 producer/schema/vocabulary/migration, clean-Agent deletion, provider input/transcript/conversation stores, public hooks, ControlSession/quit/delete semantics, and Fresh identity remain unchanged.
294
+ - `make test` / `make test-integration` / `make test-e2e`: Continue recovery diagnostics Phase 1 is enforced by `TestTopologyRecoveryTypedReasonDecisionSites`, `TestTopologyRecoveryMixedReasonsReachJournal`, `TestTopologyRecoveryStartupCommittedSummaryAndJournal`, `TestTopologyRecoveryPublicPreviewRepeatAndLockReplan`, `TestTopologyRecoveryFailureNeverRecordsPlannedSuccess`, `TestTopologyRecoveryWriterDisplayFailureAndNoClient`, `TestTopologyRecoverySnapshotRetainsSeparateAuthority`, and `TestTopologyRecoveryReportArchiveIncludesSuccess`. The diagnostics package tests `TestTopologyRecorderCommittedCountsAndOnce`, `TestTopologyRecorderFailureAndInvalidCounts`, and `TestTopologyEventClosedShapes` hold the exact ten reason codes, same-run reason totals, count/event isolation, privacy, and writer failure ownership. Startup tests cover 0/1/9 committed resumed/skipped counts, nine long Korean names, en-US/ko-KR summaries within 220 valid UTF-8 bytes including ellipsis, retained stderr details, public log/report, and no foreign-server display without a client. `TestTopologyRecoveryCanonicalContinueReportsAfterCommit` exercises the canonical zero-Window/no-topology fallback through the real session transaction, requiring no precommit success and exactly one 0/0 success or commit-failure outcome; `TestTopologyRecoverySwitchDoesNotDuplicateMaterializerOrSnapshot` protects existing reporting ownership. L10/L11 retain verified same-run safe events and their exact bounded summary as unique recovery JSON artifacts. L10 reads public materialize results through diagnostics log after each evidence-matrix execution and repeat; L11 reads the actual startup invocation after external HUP, mixed normal/interrupted recovery, and nine missing refs, preserving explicit-resume/snapshot authority and live-open bypass with no duplicate event.
295
+ - `make test` / `make test-integration` / `make test-e2e`: Retained-Project Continue resumes exact stored conversations after unplanned stops. `TestTopologyAgentContinueEligibilityMatrix` covers interrupted/killed/abnormal/unknown/no-receipt × Running/Offline/Failed/Pending with actual exact-resume or zero-launch assertions; intentional and normal remain excluded. `TestTopologyAgentContinueRejectsInvalidActivationEvidence` and `TestTopologyAgentContinueTerminationPairingAndShape` require legal receipt provenance and wait status, matching Agent/Pane evidence, current managed generation/owner, Running's exact paneRef, and Offline/Failed's unique retained activation when evidence is absent. `TestTopologyAgentContinueResumePreparationMatrix` fixes missing/blank/malformed/mismatched refs, normalized IDs, unsupported/disabled providers, missing cwd, and prepare errors at fresh 0 with a reason. `TestRegistryTopologyMaterializationReplaysStoredAgents`, `TestRegistryTopologyContinueLaunchesInterruptedBOnceAndRetainsCleanA`, and `TestClosedProjectStartupContinuesUnplannedStops` require independent shell/Window recovery, abnormal Failed recovery, retained excluded UID/ref/evidence, and repeat duplicate/Registry-write 0. `TestRegistryTopologyContinueRechecksEvidenceUnderLock`, existing owner-guard/binder rollback tests, `TestRegistryTopologyContinueKeepsUnresumableAgentAnchorRefusal`, and `TestRegistryTopologyMaterializationAgentAnchorLazyDefaultShellAndRepeatNoop` retain current ownership and rollback boundaries. `TestTopologyAgentSnapshotKeepsFreshFallback`, `TestExplicitSnapshotRestoreRetainsAgentReplayAuthority`, and `TestExplicitSnapshotRestoredOfflineAgentAnchorConvergesLazyShellAgentAndRepeatNoop` preserve explicit snapshot fresh fallback and exact resume. L10 requires raw Window/server HUP exact resume 1 and deterministic abnormal/Failed, unknown, absent-receipt, intentional/normal, and managed-stop ref-less cases; L11 requires startup HUP resume 1, clean launch 0, managed-stop exact resume 1, repeat 0, and independent explicit resume/snapshot/Fresh parity. Both smokes use isolated HOME/XDG/TMUX_TMPDIR, remove inherited tmux routing, and clean only queried contained sockets. Receipt producers/schema/sticky intent, quit/delete/stop, provider stores/transcripts, prompt/turn/approval replay, and public hooks are unchanged.
274
296
 
275
297
  - `make test` / `make test-integration`: the delete result names the socket the invocation resolved. Phase 8 made `delete window|pane|agent` resolve one exact server from `--socket`/`--socket-path` or the inherited `$TMUX`, but left the result line printing a constant `socket=-L/projmux`, so every delete against any other server reported the wrong target -- and that line is exactly what an operator audits after a destructive command. `explicitTmuxTarget.label()` renders the flag and value the route actually used, a table pins all three resolutions (explicit name, explicit absolute path, inherited client) and fails if the default app socket appears in a result that did not name it, and the isolated integration smoke asserts the reported socket is the run-unique one -- which is the whole assertion, because a fallback would print `-L/projmux` instead, and a separate "must not contain" check would collide with the isolated socket's own `projmux` prefix.
276
298
 
@@ -316,6 +338,28 @@
316
338
  - `make test`: Codex native control plane Phase 8 capability and review coverage pins distinct localized default/advanced provider rows; default Codex launch with zero capability-session/readiness calls and zero `--model`/`model_reasoning_effort` overrides; advanced-only hidden/duplicate model removal, supported-effort and closed-modality normalization, boolean-only personality projection, catalog-localized semantic picker labels with provider model/effort/modality data unchanged, paginated `model/list`, connection/version epoch cache invalidation, stale picker refusal, live-session cleanup on pre-plan create refusal, the live picker-to-pre-create session refresh rejecting a removed option, and dynamic `model/list` -> exact launch argv. Advanced discovery failure returns its exact unavailable reason with zero create intents while a separate default launch remains usable. Review tests require one exact Running Agent -> owned Pane -> activation generation -> stored/live thread binding before `review/start`, use one bounded action context, reject incomplete or unknown initial turns, map only a valid initial response to provider-control-plane interaction state, and commit zero lifecycle writes for unavailable, timed-out, mismatched, or replaced bindings. The installed smoke is opt-in through `PROJMUX_CODEX_CAPABILITY_INSTALLED_SMOKE=1`; it closes the discovery context before refreshing the retained live connection. CI does not require a local authenticated Codex daemon or create a disposable provider thread.
317
339
 
318
340
  - `make test`: Codex native control plane Phase 5 makes `account/rateLimits/read` the authoritative Codex Usage source and consumes `account/rateLimits/updated` through one single-lease, demand-bounded read-only watcher. Bucket/malformed/sparse-event goldens pin percent, reset, label, nullable identity, unknown cadence, and row-level isolation. Manager tests require an accepted native event batch to atomically replace only Codex rows, preserve other-provider/backoff state, advance the Codex throttle timestamp, and trigger zero second collector calls in the same HUD invocation. Watcher tests pin single ownership, demand-expiry heartbeat cleanup, failure backoff, sparse-update recovery after malformed events, and a private atomic native-only sidecar that never replaces last-known-good bytes with rollout/fallback provenance. The watcher launch boundary rejects `.test` and `.test.exe` executables before starting a child; the status regression runs repeated calls from the actual Go test binary with zero child starts and all usage state confined to its isolated root. Adapter/CLI/HUD/diagnostics tests keep unavailable and unsupported accounts on the existing rollout lane, all-malformed native batches on last-known-good with a closed stale reason, source/value parity across table/JSON/HUD, and an exact request inventory containing only `account/rateLimits/read` with zero login/logout/config/token mutation.
341
+ - `TestWatchNativeRateLimitsPeriodicReadKeepsQuietConnection`,
342
+ `TestWatchNativeRateLimitsNoEOFFailureIsBounded`, and
343
+ `TestWatchNativeRateLimitsPeriodicReadRefreshesSparseBase` use Go's virtual
344
+ clock to pin the initial read, fixed 30-second same-client reads, two-second
345
+ timeout, busy-notification independence, single pending read, native-only
346
+ publication, validated sparse base and malformed-result last-good retention.
347
+ `TestWatchNativeRateLimitsParentCancelRejectsLateProbeAndEvents` prevents a
348
+ canceled connection's late results or queued events from publishing.
349
+ `TestOwnedProxyCloseIsBoundedWhenPeerDoesNotRead` and
350
+ `TestOwnedProxyCloseUnblocksConcurrentWriterAndReapsChild` fill real owned
351
+ child pipes and require local close within one second, child reap, joined
352
+ reader/writers and concurrent/idempotent Close. Close never waits for a
353
+ WebSocket close frame or its write lock. Only the exact owned proxy is killed;
354
+ its upstream daemon lifecycle is unchanged.
355
+ `TestNativeWatcherProbeFailureUsesExistingBackoff` and
356
+ `TestNativeWatcherDemandExpiryCancelsPendingProbe` distinguish a failed
357
+ two-second probe from parent cancellation. They pin failure timestamps,
358
+ heartbeat removal, lease release, the existing inclusive 30-second backoff,
359
+ next-demand relaunch, and the 15-second demand TTL plus one-second monitor.
360
+ Existing cache freshness, newer-than-store, independent collector/fallback,
361
+ request cancellation/late-response and notification channel lifetime tests
362
+ remain the parity gates; heartbeat alone supplies no connection health proof.
319
363
 
320
364
  - `make test`: Codex native control plane Phase 9 makes the compact Usage identity native-first: label goldens map healthy authoritative app-server rows to exact `Codex`, fresh rollout rows and unknown non-stale provenance to `Codex [fallback]`, and retained last-known-good rows to `Codex [stale]`; en-US/ko-KR narrow statusbar fixtures pin spacing and width. Same-snapshot HUD/table/JSON tests and operations-journal tests preserve exact percent/reset values plus typed source/fallback/stale reasons, while Claude/Antigravity labels, projection order, and Usage semantics remain unchanged.
321
365
 
@@ -472,6 +516,8 @@
472
516
  - `make test`: Codex endpoint broker diagnostics dial the endpoint the runtime published. `observeCodexBrokerRuntime` used to name `DefaultEndpointKey` up front, while a managed runtime publishes under the generation-scoped key its route derives, so `projmux doctor` reported a broker that was listening as `absent / host-unavailable` -- and an operator acted on that answer by terminating a foreign app-server process. The reader now lists the discovery records the state domain actually holds, keeps only those whose announced endpoint derives back to the record that announced it, and dials that set in one stable endpoint order under a single deadline; `Dial` stays the call, because `Ensure` may create the directory, reclaim an artifact, and start a runtime, which would make a diagnostics read a side effect. `TestBrokerDiagnosticsDialTheEndpointTheRuntimePublished` is the machine's exact shape -- a live generation-scoped runtime beside a default-key lock with no socket and no record -- and requires `running`, the generation-scoped key as the observed endpoint, the answering host's runtime identity, a byte-identical state-domain file set across the read, a runtime still serving afterwards, and a second read reaching the same runtime; it fails against the default-key dial. Its mutation audit covers the connection axis and not only the file set, because a dial is itself a connection: two reads must leave zero upstream opens, an unmoved connection epoch, zero bindings and zero upstream connections reported, and host stats -- live sessions, bindings, refusals, draining, endpoint -- back at their pre-read values. `TestBrokerDiagnosticsStayAbsentWhenOnlyAnUnpublishedArtifactRemains` keeps the resting answer and its closed token: a startup lock is not a runtime and neither is a record whose announced endpoint does not derive back to its own filename, so the verdict stays `absent / host-unavailable` with zero published endpoints and no endpoint claimed. `TestBrokerDiagnosticsSelectEveryPublishedEndpointDeterministically` publishes two generations at once and pins the selection rule the reader is allowed to have: a stable ascending endpoint order, the first runtime that answers, both hosts alive after the read, and the same endpoint on a repeat read -- which generation ought to be current stays the generation pool's judgment. `doctor` now prints `Published endpoints: N` and, when one was judged, `observed endpoint: <key>` on every state rather than only on `running`, so `absent` beside a non-zero count is visibly a runtime this reader could not reach rather than a runtime that is gone.
473
517
  - `make test`: One oversized answer no longer cycles the Codex app-server control plane. The broker multiplexes one upstream connection across every bound thread, and `codexappserver.Client.readLoop` used to treat an inbound message past the 1 MiB frame bound as a fatal connection error. `thread/read` with `includeTurns=true` returns a thread's whole transcript, so a long-lived Agent's own lifecycle snapshot crosses that bound on its own: the connection died, `Broker.revoke` suspended **every** binding at once, each observer's next epoch re-issued the same read, and the cycle sustained itself -- measured on this machine at 0.86-1.35 connection epochs per second for hours, with `bind` never dipping. The framing layer now reads an over-long message to its exact byte count and drops it, so the stream stays framed; the client answers only the requests that were waiting with `ErrPayloadTooLarge` and keeps the connection; and the broker classifies that as `indeterminate` under its own `payload-too-large` reason rather than collapsing it onto `disconnect-boundary`, because the connection survived and an operator reading the two alike looks for a broker fault that is not there. `TestClientDropsAnOversizedAnswerAndKeepsTheConnection` requires the dropped answer to reach its caller as `ErrPayloadTooLarge`, a following notification to still arrive on the same connection, and the drop to be counted. `TestClientKeepsServingRequestsAfterAnOversizedAnswer` proves the drop is request-scoped by serving the next request on that connection. `TestWebsocketStreamDropsAnOversizedMessageAndStaysFramed` reads the whole message behind a dropped one, which is what a discard that consumed the wrong byte count would break. `TestOversizedAnswerEndsOneMutationAndNotTheConnectionEpoch` holds the blast radius: five oversized answers leave the connection epoch, the disconnect count, the binding count, and a neighbour binding's live fence and stream all untouched. `TestOversizedAnswerIsToldApartFromADisconnectBoundary` keeps the two indeterminate reasons distinct. Beyond the frame bound the message is still refused, because reading it out is itself the denial of service the bound exists to stop.
474
518
 
519
+ - `make test` / `make test-integration`: Window teardown decision journal Phase 0 records each consumed lifecycle teardown decision at the app layer (never inside the pure core planner) as one closed `topology.teardown.decision` event. `TestTeardownReasonJournalMappingCoversEveryCoreReason` and `TestTeardownVocabularyMatchesCoreDecisionTable` read `internal/core/metadata` source for every `TeardownReason`, `TeardownAction`, and `TerminationClassification` constant and fail on any unmapped or unallowlisted value; the first also round-trips every code through the real store and every conflict reason through its unchanged error bytes. `TestTeardownDecisionEventClosedShapes` and `TestTeardownRecorderAppendsWithoutOutcomeOwnership` pin the closed shape, strict `win-`/`pane-` UIDs, rejection of the new fields on every other family, and best-effort appends that never own the top-level outcome. `TestTeardownJournalCleanLastPanePairRecordsOneDeleteWindow`, `TestTeardownJournalKilledLastPaneRecordsRetainAndKeepsRegistry`, `TestTeardownJournalCleanupRetryRecordsRetainOnce`, and `TestTeardownJournalAuthorityConflictRecordsOneRefusal` run each fixture beside an unjournaled twin and require identical results, errors, and Registry bytes with exactly one decision per evaluation; `TestTeardownJournalWindowUnlinkRecordsFirstWaitAndExhaustion` requires one `retain`/`awaiting-pane-exit` on the hook's first wait, zero during carried causal retries, and one at exhaustion; `TestTeardownJournalKillWindowShapeFirstWaitCarriesWindowPaneAndKilled` drives the real controller body for a `kill-window`-shaped Registry and requires that first record to carry the exact Window UID, Pane UID, and `killed` with unchanged Registry bytes, while `TestTeardownJournalAmbiguousUnlinkSubjectOmitsUIDs` and `TestLifecycleWindowUnlinkAwaitingSubjectOmitsAmbiguousPane` omit any ambiguous UID. The integration termination smoke asserts one `delete-window`/`window-teardown` record for the exact closed Window UID, the first-wait plus exactly one exhaustion record for a replayed unpaired `window-unlinked`, and one `awaiting-pane-exit` record with the exact Window UID for a real `tmux kill-window` whose Window row is retained. Its read-only status/viewer and automatic-path journal line-count windows, which follow hook-driven session stop/unregister/prune closes, exclude only the asynchronously appended `topology.teardown.decision` lines, so any record the measured command itself appends still fails.
520
+
475
521
  ## When To Update This List
476
522
 
477
523
  - A feature moves between unit, integration, and e2e coverage levels.
@@ -672,6 +718,13 @@
672
718
  now fails closed over separate core, Codex broker, and Codex app-server
673
719
  children; the core child preserves metadata, notify, and recent-window
674
720
  coverage, and `CIWorkflowContractTest` pins both Codex failure paths red.
721
+ `CIWorkflowContractTest.test_required_unit_job_runs_pinned_deadcode_without_bypass`
722
+ requires the existing `Unit Tests` job to run `make deadcode` with the module's
723
+ pinned Go tool before `make test`, preserving every required job name.
724
+ `test_deadcode_failure_fails_unit_and_test_aggregate` executes the workflow
725
+ commands with a failing scanner gate and rejects failed, skipped, or cancelled
726
+ Unit results in `Test`. `DeadcodeBaselineContractTest.test_stale_current_and_new_finding_are_rejected`
727
+ preserves rejection of new findings; neither deadcode baseline is expanded.
675
728
  Required real-tmux L06 owns only the deterministic holder/released-before-
676
729
  deadline/waiter convergence guarantee. Eight-way throughput and fairness stay
677
730
  outside required acceptance in the explicit stress command documented above.
@@ -1031,6 +1084,11 @@
1031
1084
  That token is deliberately not `binding-revoked`: the four close-cause tokens
1032
1085
  answer "who closed the stream" and come from the broker epoch, and here the
1033
1086
  stream was never closed at all.
1087
+ - `test/e2e/codex-lifecycle.sh` (C01) exercises its actual projection barrier on
1088
+ an inert fixture pane with an already-ready or later-ready level and repeated
1089
+ option hooks before waiting. An atomic claim shared by the level check and
1090
+ hook emits exactly one signal; duplicate `wait-for -S` calls would otherwise
1091
+ lose the pending wakeup. The existing barrier deadline is unchanged.
1034
1092
  - `test/e2e/codex-lifecycle.sh` (C01) pins the exact disconnect token at its
1035
1093
  disconnect projection barrier and at the reconnect-gap hook comparison. It
1036
1094
  waited on the literal `disconnected` before, which was the bucket published
@@ -1123,6 +1181,17 @@
1123
1181
  `TestAgentMessageReplyReturnsOnlyToOriginalSourceConversation` pin exact
1124
1182
  current-Pane source authority, exact target eligibility before effects,
1125
1183
  same-envelope retry without resend, and broker-owned reply correlation.
1184
+ - `TestCoordinationContentLabelsSourceClaimsForBothProviders` pins claimed,
1185
+ unverified source Agent/provider notices, including wrong UID/provider
1186
+ fixtures, while preserving exact routing metadata, untrusted payload, and
1187
+ peer permission and reply boundaries in both provider contents.
1188
+ - `TestAgentMessageSendSourceAnchorAndOmittedFallbackPreserveRouteAndPeerAuthority`
1189
+ pins explicit source anchors, omitted-source active Pane fallback, refusal
1190
+ without an anchor, and unchanged exact routes and peer authority.
1191
+ - `TestAgentMessageSourceAnchorCatalogHelpAndGeneratedDocsParity` pins
1192
+ `--source` usage and its anchor (not caller authentication) meaning across
1193
+ agent/message/send catalog, help, and generated CLI reference sections;
1194
+ `TestGeneratedReferenceMatchesTheCommandManifest` rejects stale docs.
1126
1195
  - `TestClaudePrivateProjectionPreservesPublicBoundaryAndAmbiguity`,
1127
1196
  `TestLiveClaudeAdapterMapsTerminalResponseKindsWithoutDelivery`,
1128
1197
  `TestLiveClaudeAdapterRefusesPublicValidEnvelopeThatExceedsPrivateFrame`, and
@@ -1136,6 +1205,33 @@
1136
1205
  terminal-safe output, activation fencing, and deterministic Registry-only
1137
1206
  idle, timeout, and stale waits. Codex `delivered` ends at target self-claim;
1138
1207
  it does not assert model processing or reply.
1208
+ - `TestCodexCoordinationPushClassifiesNativeOutcomesForSenders` and
1209
+ `TestCodexCoordinationPushExpiresBeforeAnyProviderCall` own the native Codex
1210
+ push outcome table: start success and the `turn-in-progress` steer fallback
1211
+ deliver as `provider-turn-push`, every known zero-write refusal code fails as
1212
+ `codex-turn-push-refused`, every ambiguous or unrecognised code fails closed
1213
+ as `codex-turn-push-outcome-unknown`, an unconfigured seam, a failed content
1214
+ render and an unavailable binding fail with their own causes, and a
1215
+ pre-dispatch deadline expires as `deadline-expired` with zero binding
1216
+ resolutions and zero control calls. The refusal code is the discriminator and
1217
+ `stale-turn` keeps its per-operation split.
1218
+ - `TestCodexCoordinationPushFailuresExitNonzeroWithCauseAndStatusParity` pins
1219
+ the sender contract: `agent message send` to a Codex target prints
1220
+ `ref/state/reason/action` and then exits nonzero without a usage dump when the
1221
+ native push did not deliver, and `agent message status` reports the same state
1222
+ and reason. A failed terminal store write keeps the original cause token on
1223
+ the receipt, and a delivered turn whose write failed projects
1224
+ `broker-delivery-persist-failed` with an unknown outcome; those two cases
1225
+ cannot reach status parity, because the store write is what failed.
1226
+ - `TestCodexCoordinationPushKeepsTerminalReceiptAndSkipsExtraSteer` pins no
1227
+ steer after a successful start, no new event or delivery change from a second
1228
+ push over a terminal record, and a delivered reason that names only the
1229
+ provider turn push, never model consumption, acknowledgement, or completion.
1230
+ - The offline Claude dialogue fixtures reply to a Codex source, so they now
1231
+ accept a terminal Codex push failure receipt from the qualification reply --
1232
+ the four-field `ref/state/reason/action` line for a fresh reply ref, read
1233
+ instead of the exit code -- because no Codex app-server exists in those
1234
+ fixtures.
1139
1235
 
1140
1236
  ### Heterogeneous Agent dialogue Phase 4 tests
1141
1237
 
@@ -1155,11 +1251,41 @@
1155
1251
  recursive, late, or unrelated Stop publishes zero replies.
1156
1252
  - `TestClaudeQualificationRequiresBrokerChallengeAndExplicitReply` requires a
1157
1253
  stored original challenge, exact current source and target, a full frozen
1158
- frame, and a broker-committed explicit reply before opening general ingress.
1159
- A text echo or Stop alone cannot qualify the endpoint.
1254
+ frame, and a broker-committed explicit reply before opening the general reply
1255
+ execution gate. Current qualification responses stay pending across ordinary
1256
+ delivery and Stop text, become qualified after the exact reply, and report
1257
+ helper restart after close while the execution gate refuses further actions.
1160
1258
  - `TestClaudeExplicitReplyRejectsForeignStaleAndAlteredCorrelationBeforeCommit`
1161
1259
  covers foreign refs, altered conversations, stale generations/incarnations,
1162
1260
  reversed-route mismatch, and a wrong qualification response with commit zero.
1261
+ - `TestClaudeExplicitReplyKnownZeroManualRetryPublicReceiptsAndReload` and
1262
+ `TestStoreExplicitReplyKnownZeroManualRetrySurvivesReload` pin the documented
1263
+ [manual reply recovery](agent-message-replies.md): a known-zero failure keeps
1264
+ its immutable receipt, same-ref calls write zero, and a fresh corrected ref
1265
+ delivers once with the original conversation, reversed routes, deadline and
1266
+ peer authority. Public stderr and text/JSON status preserve cause and ref.
1267
+ - `TestClaudeExplicitReplyPartialUnknownExpiredAndStaleNeverDuplicate`,
1268
+ `TestStoreExplicitReplyNonzeroUnknownAndExpiredNeverRetry`, and
1269
+ `TestStoreExplicitReplyRetryKeepsDeadlineAndExactRoute` reject delivered,
1270
+ partial/unknown, pending, expired and stale retries, including store reload,
1271
+ false-unknown-flag and unrecognized-reason cases; Codex retry policy stays
1272
+ unchanged. `TestStoreExplicitReplyConcurrentAttemptsAndCapacityPreserveReceipts`
1273
+ permits only one concurrent new attempt and prevents capacity pruning from
1274
+ reopening a live reply relationship.
1275
+ - `TestClaudeExplicitReplyGuardRetryRetainsQualificationAndRouteFences` keeps
1276
+ known-zero recovery behind the existing exact qualification, provider caller,
1277
+ route and bounded-command gates. `TestClaudeEndpointProcessIntegration/explicit-reply-known-zero-manual-retry`
1278
+ runs the public CLI as the synthetic provider's real descendant: invalid
1279
+ content fails with stderr/ref/action, a corrected manual reply reaches the
1280
+ provider once, same/different-ref duplicates write zero, and independent
1281
+ public status processes preserve both attempts. The provider checks for
1282
+ unexpected queued connections before exit.
1283
+ - `TestClaudeExplicitReplyAcknowledgementRequiresFreshDispatchProof` refuses
1284
+ legacy or malformed acknowledgements without granting a push; replay replies
1285
+ use a distinct response kind that older callers also cannot dispatch.
1286
+ `TestClaudeExplicitReplyStoreRefusalsKeepCauseAndUnknownReservation` preserves
1287
+ bounded busy/capacity/missing/malformed store causes, frees only proven
1288
+ pre-write refusals, and keeps an ambiguous persistence attempt reserved.
1163
1289
  - `TestClaudeExplicitMultipleRequestsAndHumanOverlapSelectOnlyNamedOriginal`
1164
1290
  permits an explicit reply to B then A across human activity, rejects a second
1165
1291
  reply to either request, and never chooses correlation by arrival order.
@@ -1216,8 +1342,10 @@
1216
1342
  composite authority before send; semantic barriers replace sleeps. The
1217
1343
  scenario requires exact UID/Pane/generation/message/conversation/reply
1218
1344
  receipts, Agent count two, Codex provider writes zero, and pre-removal
1219
- Registry/tmux/helper/socket/process residual checks. It does not require a
1220
- provider binary, model, network, or version matrix.
1345
+ Registry/tmux/helper/socket/process residual checks. Its strict Claude content
1346
+ fixture preserves `sourceNotice` and verifies claimed/unverified source,
1347
+ caller-authentication limits, and untrusted payload semantics. It does not
1348
+ require a provider binary, model, network, or version matrix.
1221
1349
  - The real-provider path is opt-in through `scripts/agent-dialogue-canary-setup.py`
1222
1350
  and `docs/heterogeneous-dialogue-canary.md`. It uses public reply-only activation,
1223
1351
  product observer/helper-memory evidence and separate qualification/idle claims.
@@ -1811,6 +1939,22 @@ separate decision this measurement exists to inform.
1811
1939
  - `TestClaudePushSourceReplacementAfterDurableHandoffWritesZero` and
1812
1940
  `TestClaudeProviderPushFinalRouteCheckPrecedesSoleWrite` cover source/target
1813
1941
  authority loss across durable work and the final provider pre-write fence.
1942
+ - `TestClaudeProviderFrameSerializedByteBoundaryAndSafeRejection` fixes the
1943
+ complete auth/user frame boundary at 8191/8192/8193 serialized bytes.
1944
+ `TestClaudeProviderPostPreservesConstructionReasonBeforeAnyRouteOrWrite`
1945
+ separates invalid auth/content from size rejection before route or write.
1946
+ `TestClaudeProviderWriteReasonsRequireOneActualWrite` distinguishes actual
1947
+ zero/partial writes and invalid-count/full-with-error unknown outcomes.
1948
+ `TestClaudeProviderRejectionReasonsSurviveHubReceiptStatusAndStoreReload`
1949
+ preserves reasons through helper and public receipts, terminal store reload,
1950
+ sender recovery text, secret exclusion, and duplicate sends with zero retries.
1951
+ `TestClaudeProviderRejectionResponseValidationIsClosedAndLegacyCompatible`
1952
+ admits canonical numeric size details and legacy reasons while rejecting
1953
+ inconsistent ambiguity and secret-bearing suffixes.
1954
+ `TestClaudeEndpointProcessIntegration/provider-frame-rejection-receipt`
1955
+ exercises the built binary's public send/status against the isolated provider:
1956
+ size/action survive, credentials stay absent, and the rejected frame adds no
1957
+ provider connection before the next expected message.
1814
1958
  - `TestClaudeExplicitMultipleRequestsAndHumanOverlapSelectOnlyNamedOriginal`
1815
1959
  requires an explicit choice of the original request; Stop text has no reply
1816
1960
  authority regardless of human activity or pending request count.
@@ -1847,9 +1991,10 @@ separate decision this measurement exists to inform.
1847
1991
  outside the disposable root, reject unsafe candidate permissions before
1848
1992
  credential copying, and retain roots after uncertain cleanup without retry. Product `TestClaudeDialogueStream*` tests own public output parsing;
1849
1993
  the obsolete separate canary collector is removed.
1850
- - `TestClaudeDialogueCanaryAcceptsProductionStoreAndPublicClaimReceipts` validates
1851
- production Store.PutReply/Claim and public CLI JSON with the Python companion,
1852
- including the actual `target-self-claim` reason and version-2 envelope.
1994
+ - `TestClaudeDialogueCanaryAcceptsProductionStoreClaimAndPublicReceipt` validates
1995
+ production Store.PutReply/Claim records and current public send/status receipt
1996
+ JSON with the Python companion, including the actual `target-self-claim`
1997
+ reason and version-2 envelope, without a public claim command or serializer.
1853
1998
 
1854
1999
  - L20 owns a real authenticated Codex broker host and keeps its observed snapshot
1855
2000
  binding alive for the round trip; Registry-only synthetic authority is refused.
@@ -1964,3 +2109,6 @@ separate decision this measurement exists to inform.
1964
2109
 
1965
2110
  - `test/agent_dialogue_rpc_envelope_test.py`: pinned public JSON-RPC forms classify server requests, errors and notifications without admitting them. Exact success shape and expected ID remain mandatory; wrong/foreign IDs, unknown fields, mixed forms and malformed method/error/trace types refuse. All 64 known-field combinations and nested type branches verify the independent fixed `envelopeFacts` projection, simultaneous defect visibility and dynamic-name/value exclusion. Policy/native initialization tests propagate only those facts; audit tests reject malformed projections before writing. Real parent setup/finally tests preserve external facts before once cleanup, retain uncertain roots or failed audit writes, and produce no source effect or success receipt.
1966
2111
  - `test/agent_dialogue_websocket_test.py`: memory-only RFC6455 upgrade/masked writes/message framing, Unicode fragmentation with interleaved control frames, cumulative bounds/EOF/deadline and malformed transport privacy; pinned full initialization with exact owned codexHome precedes framed config/read and thread/read. Canary audit tests connect upgrade refusal/socket close to once parent cleanup and external code retention. Closed substage/rejection-kind regressions distinguish connect/peer, upgrade, initialize/initialized, config/read, EOF/deadline/frame and error/ID/notification envelope rejection; unknown labels remain unknown, raw exception/RPC content is excluded, and audit failure still retains the root.
2112
+
2113
+ - `make test`: Codex recovery diagnostics retain the original request through RPC classification, catalog wrappers, bounded broker lifecycle IPC and observer fallback. `TestRequestFailureDiagnosticPreservesOriginalThroughCatalogWrapping`, `TestFailureDiagnosticIPCProjectionIsClosedAndBounded`, `TestLifecycleRPCDiagnosticPreservesCodeAndRejectsUnrepresentableCode`, `TestObserverFallbackJournalPreservesSafeFailureAndClosedStartup`, `TestObserverDiagnosticRejectedAuthorityWritesZero`, `TestObserverSessionFailurePreservesTerminalReasonAfterAuthorityRejection`, `TestAIIngestTextPreservesBoundedRecoveryDiagnosticsAndLegacyRecords`, and `TestRecoveryDiagnosticGoldenInventoryAndBounds` cover unsupported/catalog/protocol/transport causes, missing/null codes, secret/invalid UTF-8/oversized inputs, existing reason tokens, terminal session-construction fallback after a rejected authority write, bounded public text projection with legacy-row compatibility, and commit-before-journal refusal. `TestManagerEvidenceContradictionAndOwnershipMatrix` and `TestManagerEvidenceCommandFailureAndTimeout` (with `TestManagerDiagnosticDeadlineProcess`) separate independent attach and official read-only manager observations, with unknown/contradictory refusal and zero lifecycle argv. The existing startup parser, parent fallback, exact authority, Phase 0 timing/close and Phase 1 recovery tests remain required.
2114
+ - Opt-in installed zero-turn fixture: `CODEX_DIAGNOSTIC_INPUT=/absolute/input.json go test ./internal/app -run '^TestInstalledCodexRecoveryDiagnosticFixture$' -count=1 -v`, with inherited `TMUX` and `TMUX_PANE` unset. Input fields are `root` (new short `/tmp` directory), `binary`, `binarySHA256`, `sourceHead`, `sourceTree`, and `evidence` (outside root). The binary must come from that clean committed source. Synthetic manager/provider responses are explicitly labeled; the actual executable runs its observer and doctor against a private Registry, exact private tmux socket and real bounded broker IPC. `TestCodexDiagnosticProxyProcess` is the read-only synthetic subprocess helper. The fixture never uses authentication, starts a turn, resumes a live session, or mutates a manager. It checks journal/doctor agreement, the actual public `diagnostics agent-hook` JSON and text consumers (including a labeled synthetic legacy row), old startup parsing, version/ownership refusals, output bounds and exact socket cleanup, then removes its private root. Evidence retains the public JSON records and text lines alongside observer rows and doctor reports. Run the identical fixture against the candidate and the isolated merged-main install; preserve the input, binary/source/tree hashes and evidence JSON.
@@ -242,6 +242,16 @@ Resources and ownership:
242
242
  session observation, and no replacement allocation. The requested Window and
243
243
  all descendants are removed exactly; only explicit Project deletion removes
244
244
  the owning root itself.
245
+ Every in-place anchor writer uses the one eligibility predicate `Validate`
246
+ enforces: same-Window ancestry, `shell` or `agent` role, and an Agent Pane
247
+ only while it is its owner's `status.paneRef`. Delete reselection picks the
248
+ first eligible Pane in Registry order; a termination that releases the
249
+ anchoring Agent's binding moves the anchor in the same mutation; a rebind
250
+ moves an anchor on the previous binding Pane onto the new managed Pane.
251
+ Ineligible retained Agent Pane rows are skipped, never selected or deleted,
252
+ and when no eligible Pane remains the anchor lands on a shell allocated
253
+ through the default-shell path. A non-Running Agent still carries an empty
254
+ `paneRef`: the anchor moves, the binding is never kept for it.
245
255
 
246
256
  Home and root kinds:
247
257
 
package/docs/cli-guide.md CHANGED
@@ -549,6 +549,14 @@ content, prompt, history, and transcript are never parsed. `abnormal`,
549
549
  `killed`, `unknown`, stale/resumed bindings, empty or unavailable inventory,
550
550
  permission failure, and foreign-host/window observations keep their diagnostic
551
551
  rows and produce no automatic delete plan.
552
+ Every teardown decision on this hook path appends one
553
+ `topology.teardown.decision` journal record (`projmux diagnostics log
554
+ --component topology`) with its `delete-window`/`delete-pane-agent`/`retain`/
555
+ `refuse` decision, `topology.teardown.<reason>` code, Window/Pane UID, and
556
+ termination classification. An unpaired `window-unlinked` records `retain`
557
+ with `topology.teardown.awaiting-pane-exit` when it first waits and again when
558
+ its bounded pair wait is exhausted; `kill-window` closes surface this way,
559
+ because tmux fires no `pane-exited` for them.
552
560
 
553
561
  For `pane-exited`, tmux supplies `%N` as `#{hook_pane}`. Its current-context
554
562
  session/window formats may already name a surviving client Window, so the owner
@@ -760,6 +768,14 @@ Registry-only result reports that no tmux Pane was killed, preserves the owning
760
768
  Window/root/socket and all siblings, and repeating the exact apply returns the
761
769
  ordinary no-match result.
762
770
 
771
+ A name, scope, or active-target delete of a target that carries that evidence
772
+ still refuses with the live-mirror refusal, but the refusal then names the
773
+ typed resource's exact form to run instead —
774
+ `projmux delete pane|agent uid:<uid> --socket-path <server> --dry-run`, then the
775
+ same command with `--yes` — and says which evidence qualifies it. A target the
776
+ `uid:` form would refuse too, such as a Pane without `MissingRuntime` or a
777
+ `Running` or `Failed` Agent, gets no such pointer.
778
+
763
779
  `delete window|pane|agent` names the server its live half addresses the same
764
780
  way `reconcile resources` does: `--socket <name>`, `--socket-path <absolute>`,
765
781
  or the inherited absolute `$TMUX`. Outside tmux with neither flag it refuses
@@ -1144,7 +1160,9 @@ autosave-session-state`, `window record`, and
1144
1160
  successful `diagnostics log` views do not produce an event. Errors from those
1145
1161
  automatic hook/poll paths still produce one safe `error` outcome. Successful
1146
1162
  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
1163
+ read-only and do not produce an event. The automatic `internal tmux converge`
1164
+ route still appends one typed `topology.teardown.decision` record per Window or
1165
+ Pane teardown decision it consumes. Journal failures are a best-effort side
1148
1166
  channel and never change command output or exit status. See
1149
1167
  [operational-diagnostics.md](operational-diagnostics.md) for the file,
1150
1168
  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
@@ -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
@@ -2,7 +2,11 @@
2
2
 
3
3
  Session snapshots are explicit desired-state inputs for one Project. They are
4
4
  not tmux replay scripts and they are not Registry backups. Snapshot save keeps
5
- the existing v1 schema and storage behavior.
5
+ the existing v1 schema and storage behavior. In snapshot save's runtime id
6
+ duplicate check, a Window or Pane recorded as `MissingRuntime`/`RuntimeUnbound`
7
+ does not claim its retained runtime id, so a tmux id reused after a server
8
+ restart does not refuse the save; two live Windows or two live Panes sharing an
9
+ id are still refused.
6
10
 
7
11
  ```sh
8
12
  projmux get snapshots [--session <snapshot-session>]
@@ -42,10 +46,11 @@ same-kind collision is rejected as damaged before trust authorization,
42
46
  Registry/tmux/provider mutation, or any snapshot write.
43
47
 
44
48
  The committed Registry is then converged by the ordinary Project materializer.
45
- For a restored offline Agent-anchor Window, `Continue project` visibly plans a
49
+ For a restored offline Agent-anchor Window, snapshot materialization visibly plans a
46
50
  lazy default shell, creates the Window from that shell, and stages the Agent on
47
51
  its retained anchor Pane UID. A successful repeat writes neither Registry nor
48
- topology. Agent recipes use the canonical provider launch/resume path. Stored startup
52
+ topology. Snapshot Agent recipes use the canonical provider launch/resume path,
53
+ including their existing fresh-conversation fallback. Stored startup
49
54
  commands are not directly executed by snapshot restore. A runtime item refusal
50
55
  does not roll the Registry back: desired state and the source snapshot remain
51
56
  available for another `Continue project`, and the refusal is reported as an
@@ -70,6 +75,41 @@ A closed Project has exactly two actions:
70
75
  Esc/cancel returns to Projects; it is not an action row. Picker failure falls
71
76
  back to the non-destructive `Continue project` action.
72
77
 
78
+ Continue resumes an Agent's exact recorded conversation after interrupted,
79
+ killed, abnormal, unknown, or unrecorded termination. Intentional and normal
80
+ termination remain excluded. A recorded receipt must agree on the Agent and
81
+ its retained Pane's current managed activation. Without a receipt, Running
82
+ requires its exact paneRef; Offline or Failed requires one unambiguous retained
83
+ Agent-owned Pane activation. Pending Agents are skipped. Live Agents are not
84
+ launched again.
85
+
86
+ A missing, blank, malformed, or mismatched conversation ref, a disabled provider,
87
+ a missing workspace, or a resume preparation failure skips the Agent with a
88
+ reason. Continue never substitutes a new conversation. Shells and other
89
+ recoverable Agents still converge; an unrecoverable Agent that is itself a
90
+ Window's required anchor keeps the existing Window refusal. Explicit snapshot
91
+ restore and `agent resume` retain their separate authority.
92
+
93
+ After Continue commits, its startup summary shows the resumed and skipped Agent
94
+ totals and `projmux diagnostics log --component topology`. The same counts are
95
+ available in the public materialize-project JSON result's `recovery` field.
96
+ Already-live Agents count in neither total. Full per-Agent explanations remain
97
+ on stderr; the transient summary stays within 220 UTF-8 bytes, including any
98
+ ellipsis, independently of Agent names or how many were skipped.
99
+
100
+ The private operations journal stores one `topology.outcome` and one
101
+ `topology.agent.skipped` row per reason in the same invocation `run_id`. Reason
102
+ counts sum to the committed skipped total. The ten codes use `topology.agent.`
103
+ followed by `termination-excluded`, `phase-ineligible`, `activation-unproven`,
104
+ `termination-invalid`, `session-ref-missing`, `session-ref-invalid`,
105
+ `session-ref-mismatch`, `provider-unavailable`, `workspace-unavailable`, or
106
+ `resume-prepare-failed`. `diagnostics report` includes recent closed recovery
107
+ events in `topology-recovery.json`, including successful partial and 0/0 results.
108
+ Journal rows contain no resource IDs/names, conversation IDs, payloads, or raw
109
+ errors. Dry-run writes no execution event; failed or rolled-back execution
110
+ records an error with zero committed counts. Journal and display failures are
111
+ best effort and never change the topology result.
112
+
73
113
  `Recreate Project` never deletes or overwrites autosave or named snapshot files. It
74
114
  preserves the root, Git/worktrees, trust decision, and all unrelated Registry
75
115
  graphs while changing the Project identity. A rejected commit retains the
@@ -111,6 +111,32 @@ The `Codex app-server` result keeps four readiness axes separate:
111
111
  remain separate supporting fields. A ready endpoint therefore does not hide an
112
112
  unmanaged process or version skew.
113
113
 
114
+ When a current Codex activation records a different endpoint generation from
115
+ the running default endpoint, `Codex endpoint generation comparison` lists each
116
+ affected Agent by exact `uid:` selector. The comparison uses the activation's
117
+ `endpointGenerationID` and the observed running version, independently of the
118
+ installed CLI version. It reports an observed interruption risk during endpoint
119
+ replacement (2026-09-09, n=1), with causality undetermined; interruption is not
120
+ certain. This is a snapshot valid before the next replacement, and performs no
121
+ recovery or process mutation.
122
+
123
+ The census includes bound Codex activations across every Project and Window in
124
+ the managed Registry, even if their Agent phase is stale. Offline conversation
125
+ history without a current activation is excluded. A complete comparison with no
126
+ mismatches emits no comparison block. An unreadable Registry or missing,
127
+ inconsistent, orphaned, or unobservable activation produces an `unavailable` or
128
+ `incomplete` enumeration signal; confirmed mismatches remain visible alongside
129
+ those gaps. A foreign Codex state domain, opaque generation, or a present or
130
+ unreadable generation pool is unobservable from the default daemon probe.
131
+ Private pool generations can also have version-shaped IDs, so Doctor does not
132
+ infer their running endpoint from that spelling or from admission-current.
133
+
134
+ The additive `codex_endpoint_mismatch` JSON field carries the same mismatch set,
135
+ enumeration gaps, and evidence strength without changing the schema version.
136
+ Support report `doctor.json` preserves those counts and closed evidence fields;
137
+ Agent and generation IDs retain the support report's normal deterministic
138
+ hashes. Exact Agent selectors are available in ordinary Doctor text and JSON.
139
+
114
140
  `external-cli-only` states only two observed facts: the ordinary Codex CLI
115
141
  exists, and the managed standalone payload was not observed. It does not mean
116
142
  the ordinary CLI is unsupported, identify how that CLI was installed, or prove
@@ -120,9 +146,12 @@ An explicit native action refuses a ready unmanaged or version-skewed endpoint.
120
146
  The refusal reports `shared-clients-disconnect`: replacing this shared process
121
147
  can interrupt every attached Codex client. For a managed skew, confirm the
122
148
  interruption and run `codex app-server daemon restart`. For an unmanaged
123
- endpoint, close every sharing client, stop the process through the operator
124
- that owns it, then run `codex app-server daemon start`. Projmux never performs
125
- those stop/restart steps or invents an ownership-specific kill command.
149
+ endpoint, close every sharing Codex client, run
150
+ `codex app-server daemon bootstrap`, then rerun diagnostics. The observed `pid`
151
+ backend requires bootstrap again after reboot; this observation does not
152
+ establish boot persistence for other backends. The unmanaged bootstrap
153
+ prescription is absent for managed or unknown ownership. Projmux reports this
154
+ operator-owned guidance without executing the recovery commands.
126
155
 
127
156
  A prompted managed Codex create also requires that endpoint. When it is not
128
157
  ready or not attachable, `projmux create codex -- "<prompt>"` refuses instead of
@@ -159,6 +188,38 @@ method, rerun Doctor. Do not copy binaries, create symlinks in the Codex home,
159
188
  or edit the control socket as a diagnostic workaround. Doctor, Settings, and
160
189
  support-report collection never start the daemon or modify the installation.
161
190
 
191
+ The read-only manager observation is recorded separately as `manager_evidence`:
192
+ `status`, `backend`, `result`, `agreement`, and the manager's reported `version`.
193
+ `running_version` describes the independently initialized endpoint. A version or
194
+ running/stopped contradiction yields `manager_ownership=unknown`,
195
+ `native_action_refusal=evidence-contradictory`, and inspection guidance. A ready
196
+ endpoint with insufficient manager evidence also refuses mutation. Running/PID
197
+ or a managed-looking path alone does not prove ownership. The existing exact
198
+ cold-start rule remains separate; a contradictory running-manager observation
199
+ cannot authorize it.
200
+
201
+ Observer `ai-ingest.log` fallback rows keep the existing `reason` and add a
202
+ `failure` object: allowlisted request `method`, nullable original `rpc_code`, and
203
+ closed `cause`. For example, an unsupported request and a catalog rejection can
204
+ share `reason=unsupported` while retaining different original methods/codes.
205
+ Transport and local protocol failures have `rpc_code=null`. Read-only preflight
206
+ refusals also carry the same `recovery` evidence and operator decision Doctor
207
+ reports. These fields are appended only after the exact authority write succeeds;
208
+ the existing startup handshake and reason consumers are unchanged.
209
+ `projmux diagnostics agent-hook` displays the same safe `failure=` and `recovery=`
210
+ JSON objects in its default text output; `--json` retains the original JSONL
211
+ records. Older rows keep their existing text format.
212
+
213
+ Diagnostic bounds are 32 bytes per method/cause/evidence token or version,
214
+ 20 decimal bytes for an RPC integer, 64 bytes per recovery decision token,
215
+ 160 bytes for `failure` JSON, 256 for `manager_evidence`, and 768 for `recovery`.
216
+ The Codex health JSON/text section and enriched observer row each fit within
217
+ 4096 bytes (the row reserves space for its timestamp). Public journal text adds
218
+ at most 947 bytes for both diagnostic objects, labels and separators; an enriched
219
+ observer line remains within the same 4096-byte row bound. The existing journal file
220
+ retention bound remains 1 MiB. Provider messages, payloads, prompts, auth, paths,
221
+ and raw PIDs are excluded; unknown strings are replaced by closed unknown values.
222
+
162
223
  ## Incomplete npm install
163
224
 
164
225
  If the npm shim exits before Projmux starts with:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -28,9 +28,9 @@
28
28
  "package:npm:pack": "scripts/package-npm.sh --pack"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@projmux/linux-x64": "0.15.0",
32
- "@projmux/linux-arm64": "0.15.0",
33
- "@projmux/darwin-x64": "0.15.0",
34
- "@projmux/darwin-arm64": "0.15.0"
31
+ "@projmux/linux-x64": "0.15.1",
32
+ "@projmux/linux-arm64": "0.15.1",
33
+ "@projmux/darwin-x64": "0.15.1",
34
+ "@projmux/darwin-arm64": "0.15.1"
35
35
  }
36
36
  }