projmux 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,26 @@
27
27
 
28
28
  ## Maintained Test List
29
29
 
30
+ - `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
+
32
+ - `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
+
34
+ - `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.
35
+
36
+ - `make test`: Detached control-plane SIGQUIT artifact Phase 0 installs one process-local sink only on `internal agent-hook ingest codex-broker-watch` and `internal codex-broker serve`. `TestDetachedCrashArtifactPathUsesClosedRoleAndPIDVocabulary`, `TestResolveDetachedCrashStateDirFollowsStandardStateHomeOnly`, `TestDetachedCrashArtifactIsPrivateBoundedAndAtomicallyPublished`, and `TestDetachedCrashArtifactRefusesSymlinkAndNonRegularTargets` pin the standard state path, closed role-plus-PID filenames, `0700`/`0600` modes, 1 MiB bound, explicit truncation marker, atomic no-clobber publication, and hostile path/target refusal. `TestDetachedRoutesPersistActualSIGQUITWithStderrDiscarded` builds the production binary and sends actual `SIGQUIT` to disposable watcher and broker route PIDs with fd2 set to `/dev/null`, requiring an all-goroutine artifact and exit code 2 while normal startup creates no crash state. `TestDetachedCrashSinkNormalLifetimeCreatesNothing` and `TestDetachedCrashSinkPreservesQuietAndPrivateSurfaceBoundaries` keep state resolution lazy, append logs absent, hook redirection byte-identical, `supervise` stderr attached to its PTY, the sink out of public routes/config/env/schema, and crash artifacts out of support reports. Installed verification uses only an isolated `XDG_STATE_HOME` and exact broker child PID; live watcher, broker, supervise, provider, and generation-host processes are never signal targets.
37
+
38
+ - `make test` / `make smoke-assert-contract`: `test/smoke-assert-contract.sh` checks only `smoke_assert_file_contains` against a real file in fresh shell subprocesses. Present and absent `--` needles and ordinary needles must return the exact expected exit status, empty stdout, and byte-exact stderr (empty on success, only the existing assertion diagnostic on failure). The ordinary absent case also pins fixed-string semantics. Grep option/usage errors and an unconditional-success helper fail this gate; the CI `Unit Tests` job runs it through `make test`.
39
+
40
+ - `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
+
42
+ - `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.
43
+ - `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
+ - `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
+ - `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.
46
+ - `make test` / `make test-integration` / opt-in installed private observation: Codex generation-pool Phase 6 projects notification, sidebar, statusbar, and native reply from `codexgeneration.ProjectConsumers`, whose one endpoint plus five-axis authority digest is copied into content-free notification metadata. `TestDerivedConsumersUseOneEndpointFenceAndStaleActionsAreZero`, `TestGenerationConsumersShareExactEndpointFenceAndStaleActionZero`, `TestGenerationObserverRefreshesDurableDrainingOperationForConsumerProjection`, `TestGenerationNotificationProducerCarriesCanonicalFence`, `TestAgentControlCLIStaleCanonicalConsumerFenceCallsNoControlTransport`, and `TestGenerationAwareListerRegistryReadErrorMakesExistingNoticeStaleAndActionZero` pin exact Agent/Pane/runtime/thread ownership, durable planned-operation refresh, Current/Draining parity, quiet/state-only policy, pre-control fence revalidation, read-error fail-closed behavior, and stale endpoint/fence action zero; `TestNotifyListSidebarEnterOnGroupStaleRoutableHasZeroAction` and `TestStatusbarClickNotifyStaleHeadHasZeroAction` retain only their unique picker/statusbar transport assertions. `TestDoctorCodexGenerationActionabilityTable`, `TestDoctorCodexGenerationPinnedTupleSkewFailsClosed`, `TestDoctorCodexGenerationMissingBundleIsActionableBlocked`, `TestDoctorCodexGenerationBundleDriftIsBlockedAndReadOnly`, `TestDoctorCodexGenerationJSONAndTextAreContentFree`, `TestDoctorCodexGenerationHandoverTimelineUsesReceiptOrder`, `TestDoctorCommandProjectsGenerationPoolInJSONAndText`, `TestDoctorCodexGenerationOrphanObligationClassificationTable`, and `TestDoctorOrphanObligationsCannotBlockOrUnblockThePoolVerdict` cover expected Current+Draining health, invalid admission and pinned tuples, qualified 0.152.0→0.152.1 versions, owner/bundle/state-domain inventory, no-turn close-or-replace, Draining handover-required, ordered receipt actionability, Doctor mutation zero, and byte-exact content-free JSON/text goldens. A journal obligation whose Agent no longer exists in the Registry is reported under its own `orphaned` / `await-obligation-reprojection` / `agent-record-absent` triple and a separate per-generation `orphaned_agents` count: it stays listed, leaves `pinned_agents` and every live obligation's classification untouched, and can neither block nor unblock the pool verdict. AgentObligation carries no ThreadID, so only a fresh obligation reprojection can retire such a row; Doctor never writes one. The existing opt-in `TestInstalledPrivateJournaledGenerationHandover` now owns the complete release qualification without changing the Phase-5 journal: its pre-handover barrier pins one existing N Agent and one new N+1 Agent to distinct exact Pane/thread/endpoint tuples, materializes the new thread's provider-required rollout once before assignment, waits for both generation observers' semantic ready handshakes, starts both public `agent turn start uid:<agent>` message paths before awaiting either command or observer-owned terminal projection, requires exact Agent/Pane/runtime/generation/thread/turn/authority pairing plus post-baseline provider-control completion with cross-wire zero, and proves the Phase-5 journal bytes unchanged before continuing the unchanged no-turn block, explicit close, old-stop→resume→snapshot→CAS→same-Pane relaunch→retire→lease-release receipt and exact cleanup. No secondary provider connection observes either active turn. It also negatively refuses selectorless Agent resume with Registry/tmux/old-endpoint mutation zero. The post-close remediation adds `TestManagedCurrentActivationAfterDefaultUpgradePreservesOldAgentAndForeignLifecycle`, `TestManagedCurrentActivationReopenAfterAdmissionConvergesDrainBeforeCreate`, `TestOrdinaryCodexCreateSpellingsActivateRollingManagedCurrent`, `TestProductionManagedActivationBuildsExactDefaultUpgradeRequest`, `TestManagedCodexRuntimeLocationPortableBoundRefusesBeforeMutation`, `TestManagedCodexActivationHostRefusalsNameExactCorrectiveAction`, `TestProductionManagedActivationUnsafeStateDomainNamesExactActionWithZeroMutation`, `TestLeaseStandaloneReleaseRetainsClosedRequiredBundle`, `TestDoctorManagedActivationShowsCurrentDrainingPinnedTruthReadOnly`, and opt-in `TestInstalledDefaultUpgradeOrdinaryCreatesActivateManagedGeneration`: an installed-CLI/default version skew now requires the exact canonical shared Codex state domain to satisfy the existing owner-private host gate, leases the complete new standalone release, preflights a deterministic collision-resistant private socket against the portable Unix path bound, resumes the exact durable activation through post-admission crashes, and admits both ordinary create spellings only after the managed Current plus external Draining projection converges. The installed witness takes `PROJMUX_CODEX_PHASE6_REMEDIATION_SMOKE_ROOT=/tmp/<isolated-root>` and `PROJMUX_CODEX_PHASE6_REMEDIATION_OLD=/absolute/old/codex`, seeds an old live Agent/Pane/thread before activation, and preserves every Phase-6 managed-Current, old-unmanaged-Draining continuity, foreign-lifecycle-zero, Doctor-read-only, and cleanup assertion unconditionally; its two ordinary payload-free spellings then accept only one uniform exact-tuple Phase-7 result, either the prior durable-ready Pane/broker path or two exact Failed identities pinned to Current with new Pane/broker/false-Running/plain-fallback zero. The old/default endpoint receives zero stop/restart/kill/adopt calls, its live thread/Pane/approval state stays pinned, unsafe shared-state or runtime-path gates return an exact operator action before bundle/journal/runtime mutation, and Doctor reports stored Current/Draining versions, pinned obligations, missing-qualification action, and mutation zero without fabricating a YES receipt. Deletion/mutant ledger: no Phase 3 empty-prompt or Phase 0–5 canonical symbol was deleted or merged; two stale-action tests were renamed in place from permissive focus expectations to transport-owned zero-action expectations, lifecycle/actionability equivalence moved to the ninth pure `internal/core/codexgeneration` model file, and `TestPhase0ModelImportsNoMutationAdapter` is the mutant-killing ownership receipt for that new canonical owner. Phase 0 supersedes only this witness's payload-free functional assertions: the fixture is retained as skipped historical negative evidence, and prompted generation activation remains covered by the unit route.
47
+ - `make test`: A Codex generation with nothing left to hand over can retire without a version-pair receipt. `ActivateManagedCurrent` starts a rolling activation automatically on a version skew and carries no qualification guard, so an ordinary user reaches `draining` without one; deleting the Agents pinned to that generation then removes the only production caller of the rolling handover request (`agent resume` on a live pinned Agent), leaving both `version-pair-not-qualified` and `exact-rolling-handover-not-requested` permanently unclearable. `codexgeneration.EvaluateRetirementVacancy` defines the one case where the receipt has no subject, and `TestEvaluateRetirementVacancyOpensOnlyForACompleteZeroCensus` plus `TestRetirementVacancyReportsOneCauseInAStableOrder` pin its total, order-stable verdict, including that a census which never ran is never read as an empty one. `TestVacantGenerationRetiresWithoutAVersionPairReceipt` is the acceptance fixture for both unmanaged and Projmux-private ownership: the plan reports `request-handover` with a vacant census and zero targets, Apply fires the exact rolling request through the declared requester seam, the old route reaches `retired`, its obligation rows disappear, the pool falls back to one live route, the post-retirement journal still validates, and the shared state domain's thread files are byte-for-byte the same set before and after. `TestOccupiedGenerationStaysFailClosed` tables the fail-closed axes -- one live obligation, a fresh projection that disagrees with an empty journal ledger, an endpoint-bound Agent carrying no ThreadID, a Pane activation outliving its Agent, and an unreadable shared state domain -- and requires both original blockers, an unmutated journal, a still-`draining` route, and effect zero. `TestVacantRetirementLeavesTheDrainingCapAndSlotArithmeticAlone` keeps `multiple-draining` and the two-slot arithmetic unchanged, `TestQualifiedGenerationDoesNotNeedAVacancyCensus` keeps the ordinary qualified path free of the state-domain read, `TestVacantRetirementRefusesWithoutARequesterSeam` refuses rather than improvising the request, `TestVacantRetirementIsIdempotentAcrossEveryFailpoint` holds one retirement effect across all five coordinator failpoints, and `TestEnumerateStateDomainThreadsReadsNamesOnly` pins that the census reads rollout file names only and reports a missing domain as an error. `TestPhase0ModelImportsNoMutationAdapter` counts `vacancy.go` as the tenth pure model file, so the Registry projection and the filesystem census stay outside that package.
48
+ - `make test` / version-qualified negative evidence: Codex generation-pool Phase 7's zero-turn `thread/start → independent resume/read` probes remain lower-layer safety fixtures only. `TestPayloadFreeNativeCreateClosesCreatorBeforeIndependentDurableResumeBarrier`, `TestDurableResumeBarrierUsesFreshIndependentExactResumeReadUntilReady`, `TestDurableResumeBarrierDeadlineIsBoundedAndRetriesOnlyTheExactThread`, `TestDurableResumeBarrierFailureOutcomeTableIsClosed`, and `TestPayloadFreeDurableResumeBarrierKeepsDefaultAndPrivateRoutesExact` still prove that the experimental path uses no dummy turn, never crosses routes, and classifies failure content-free. They are not functional `projmux create codex` success. `TestPhase7PayloadFreeReadinessFailureIsNegativeSafetyEvidenceOnly` and `TestInstalledPayloadFreeCreateOutputClassificationRequiresFunctionalPlainSuccess` enforce that reclassification; `TestInstalledIsolatedGenerationPinnedEmptyPromptCreateSmoke` and `TestInstalledDefaultUpgradeOrdinaryCreatesActivateManagedGeneration` are retained as skipped historical negative fixtures, while `TestInstalledPayloadFreePlainFallbackOutcomeSmoke` owns the functional installed result.
49
+
30
50
  - `make test` / `make test-integration`: Phase 1 explicit-create route correction preserves inherited app-owned socket/PID evidence while excluding unrelated `TMUX_PANE` containment. `TestExactProjectCreateWindowUsesAppRouteDespiteStaleInheritedPane` pins a nondefault logical `-L` alias resolved through the exact inherited `-S` path, ownership/logical markers, and PID; `TestExactProjectCreateWindowIsByteEquivalentAcrossAmbientPanes` requires byte-equivalent results and tmux call ledgers across current and stale ambient Panes; and `TestExactProjectCreateWindowRejectsInheritedAppDriftBeforeWrite` keeps stale PID and foreign marker at zero Registry/runtime writes. Outside tmux retains validated default app `-L`, standalone inherited routes still require exact Pane containment, and the discovery integration fixture keeps its custom app logical socket without requiring `env -u`.
31
51
  - `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 0 finalizes the unpublished Registry v2 Window shape without consuming a new schema number. `TestFinalV2AnchorAndDefaultShellValidationMatrix`, `TestSchemaVersion2WindowShapeNormalizationTable`, and the deterministic random-graph properties cover required same-Window shell or managed-Agent `anchorPaneRef`, optional direct-shell `defaultShellPaneRef`, mixed legacy/final refusal, v1 and intermediate-v2 deterministic normalization, final-writer legacy-field zero, and second-pass idempotence. `TestIntermediateV2NormalizationPublishesExactEvidenceAndSecondPassIsZeroByte`, `TestIntermediateV2NormalizationFailureHonorsEvidencePublicationBoundary`, and `TestPrivateEvidencePublicationIsExclusive` pin the exclusive exact mode-0600 backup, checksum/repair report, pre-publication cleanup, post-publication evidence preservation, pre-replace source-byte preservation, and final-v2 zero-byte pass. Existing create, materialize, resume, focus, and real-tmux outcomes stay at Phase-0 parity through the pure compatibility shell resolver; the E2E stale-anchor fixture now corrupts `anchorPaneRef` and keeps its isolated zero-write refusal.
32
52
  - `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.
@@ -35,14 +55,19 @@
35
55
  - `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.
36
56
  - `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.
37
57
  - `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.
38
- - `make test`: Codex native control plane Phase 1 pins the closed trigger × probe × start × readiness lifecycle table: Doctor, Settings, and support reports repeat with zero daemon mutation; only a native user action whose official local socket is missing or refuses a connection may share one in-process `codex app-server daemon start`; and already-running, executable-missing, nonzero, timeout, socket-unavailable, unsupported, protocol, and other endpoint failures retain exact content-free reasons and the existing fallback. Fake CLI/proxy fixtures cover exact argv, cold and warm initialize, bounded readiness, concurrent callers, per-caller cancellation, late-arrival handoff, zero waiter leaks, and discarded process output. The opt-in installed-Codex smoke strips inherited `TMUX`/`TMUX_PANE`, contains the Codex home and control socket below one validated temporary root, proves idempotent start-to-proxy-initialize readiness, and records the exact cleanup target without adding product shutdown behavior.
39
- - `make test` / `make test-integration` / `make test-e2e`: Codex app-server readiness diagnostics keep endpoint readiness, running executable/version, official daemon-manager ownership, and remote-control capability as independent content-free axes. The managed-current, unmanaged-current, unmanaged-skew, and endpoint-dead matrix proves a ready endpoint never hides ownership or skew; unknown evidence remains typed unknown. Doctor, the Settings no-op row, and support `doctor.json` project the same fields and explicit native-action refusal, shared-client interruption risk, and operator recovery code without retaining managed/socket paths or remote identity. Repeated read-only surfaces emit only `app-server proxy`, `app-server daemon version`, and `remoteControl/status/read`; their process/config/socket mutation argv ledger is zero, while a product-source audit forbids shared-daemon stop/restart/kill/remote-control mutation argv. Managed-current and the exact cold user-action `daemon start` contract remain covered separately.
40
- - `make test`: Codex native control plane Phase 2 binds `thread/start` plus its single optional `turn/start`, or `thread/resume` with zero thread creation, to the exact Agent uid, managed Pane uid, and activation generation. App-server identity is additive schema-2 state on the Pane activation while the durable Agent `sessionRef` remains the conversation pointer; native Pane describe rows expose binding source, generation, thread, and current turn. Fake protocol and materialization tables pin one-prompt create, stored-thread resume, unavailable/unsupported fallback through the unchanged CLI/hook contract, indeterminate post-create refusal without a second lane, and zero-write rejection for another Pane, generation, or thread. Exact late hooks may refine only the current turn; native-bound hooks fail closed before tmux remap or Registry writes, while legacy/unmanaged hooks and bounded late acknowledgement retain their previous behavior.
41
- - `make test`: Codex empty-prompt create fallback keeps fresh native identity limited to immediately attachable input. `TestStartDefaultThreadEmptyAndPromptedRequestCounts` pins zero proxy/open/thread/turn requests plus safe fallback for empty input and exactly one thread plus turn for prompted input. `TestEmptyPromptCodexCreateUsesOnePlainCLILaneAndNoNativeBinding` and `TestEmptyPromptCodexSplitProducersKeepOnePlainCLILane` pin one unchanged plain CLI launch across canonical, provider-shortcut, and split producers; `TestEmptyPromptCodexFallbackFirstInputConvergesSessionRefAndRouting` pins the first real hook input converging the durable Agent `sessionRef` and live Pane routing. `TestCodexFanOutKeepsCurrentPlainCLILaneWithoutNativeCreate` and the prompted/stored/picker/post-mutation tests keep fan-out, exact resume, and fail-closed parity.
42
- - `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 4 makes `thread/list` the Resume Picker catalog source and keeps the exact-id `thread/read(includeTurns=false)` validation seam for Session State. Unit/fake-server goldens pin explicit `cli`/`vscode`/`appServer` source kinds, non-archived and recency filters, opaque multi-page cursor progression, repeated/empty/exhausted cursor rejection, cwd depth, exact thread id, provider name or short-id-only fallback, branch, closed runtime status, deterministic ordering/dedupe, and typed source/confidence/fallback reason. A native failure discards every partial native row before exactly one rollout scan; native-empty never merges rollout history. Picker-to-create tests require native rows to call `Resume` with the exact listed id and rollout rows to stay on the current CLI lane. Session State tests require a bound session id or persisted resume id to win without `thread/read`, a thread-only candidate to pass one exact-id read before storage, a mismatched read to write zero, and the default validator to remain probe-only so autosave never starts the shared daemon. Provider parity and negative audits keep Claude/Antigravity and snapshot recipes unchanged and keep native title inference, prompt preview, transcript turns, and XML unwrap at zero. The installed read-only catalog smoke is opt-in through `PROJMUX_CODEX_CATALOG_SMOKE_ROOT=/tmp/<isolated-root>` with matching contained `CODEX_HOME`; it strips inherited tmux identity, ensure-readies once, lists metadata, and validates the first listed exact id without turns or thread mutation.
58
+ - `make test`: Codex native control plane Phase 1 pins the closed trigger × probe × start × readiness lifecycle table: Doctor, Settings, and support reports repeat with zero daemon mutation; only a native user action whose official local socket is missing or refuses a connection may share one in-process `codex app-server daemon start`; and already-running, executable-missing, nonzero, timeout, socket-unavailable, unsupported, protocol, and other endpoint failures retain exact content-free reasons and the existing fallback. Fake CLI/proxy fixtures cover exact argv, cold and warm initialize, bounded readiness, concurrent callers, per-caller cancellation, late-arrival handoff, zero waiter leaks, and discarded process output. Installed lifecycle evidence is now owned by the hermetic topology qualification below rather than a second daemon-only setup/readiness assertion.
59
+ - `make test`: Bounded lifecycle snapshot transport opens one owned, peer-birth-witnessed direct app-server connection and incrementally projects complete `thread/read` metadata before the broker's generic 1 MiB IPC materialization boundary. `TestEndpointPeerWitnessRejectsSamePathReplacementAndSyntheticPIDReuse`, `TestOwnedLifecycleReadPreservesSharedHistoryAndApprovalAuthority`, and `TestLifecycleSnapshotFivePointTwoMegabytesCrossesClientBrokerIPCBodyFree` pin same-process route proof, shared-vs-owned history equivalence, unchanged approval/event authority, literal 5.2 MB upstream success, and body-free typed IPC. `TestLifecycleTransportAccountsForSimultaneouslyRetainedProjectorState` adds the whole-state retained boundary: 32 nested 64-field objects keep wide decoded keys, fixed duplicate/projection storage, maximum captured flags, and every scalar needed for the body-free summary live together; its independent 64-bit ABI oracle requires the exact 2 MiB boundary on JSONL and WebSocket, rejects +1 and the former key-byte-only boundary, and makes omission of any body/header/capacity/reserve term observable. `TestLifecycleTransportReservesEscapedScalarBeforeValidation` reaches 33 live objects after retaining all nine possible metadata bodies and 64 maximum flag bodies, then forces the largest escaped-string working state before its decoded-size refusal: 6,146 encoded scratch bytes, a 6,152-byte unquote buffer, and a 6,143-byte decoded string. An independent boundary/+1 oracle pins the corrected 108 KiB fixed reserve within the unchanged 2 MiB retained-state cap for both transports, including management state and a conservative later, non-overlapping summary allowance. The other lifecycle JSONL/WebSocket boundary tests pin JSON/wire/chunk/depth/value/field/turn/scalar/identity/summary/frame/control limits, malformed UTF-8 and fragmentation, cancellation and late-result zero. `TestLifecycleSidecarRolesTokensAndOldNewCompatibility`, `TestLifecycleAdmissionRetryAndIdleLedgerReclaimAreBounded`, `TestEveryOwnedFailureLeavesSharedSiblingAuthorityUnchanged`, and the publication-fence tests pin session-scoped sidecars, old/new bind-submit-answer compatibility, endpoint-two/thread-one/queue-zero admission, disconnect/rebind retry fencing, bounded idle reclamation, sibling survival with shared epoch unchanged, and stale result zero across every route/activation axis. The shared broker connection remains the sole notification, approval, bind, submit, and answer authority; the upstream complete response remains large while no turn body crosses local lifecycle IPC. The 2 MiB guarantee is retained projector state, not cumulative allocation, allocator peak, goroutine stack capacity, whole-heap peak, or RSS.
60
+ - `make test` / `make test-integration` / `make test-e2e`: Codex app-server readiness diagnostics keep endpoint readiness, running executable/version, official daemon-manager ownership, and remote-control capability as independent content-free axes. The managed-current, unmanaged-current, unmanaged-skew, and endpoint-dead matrix proves a ready endpoint never hides ownership or skew; unknown evidence remains typed unknown. The official daemon-version observation matrix starts each fixture behind an explicit process-ready signal, runs the unchanged read-only argv only after readiness, and requires bounded clean process termination; the never-ready negative fixture fails with a readiness-specific deadline diagnostic, and the same four observations are required under `go test -race`. Doctor, the Settings no-op row, and support `doctor.json` project the same fields and explicit native-action refusal, shared-client interruption risk, and operator recovery code without retaining managed/socket paths or remote identity. Repeated read-only surfaces emit only `app-server proxy`, `app-server daemon version`, and `remoteControl/status/read`; their process/config/socket mutation argv ledger is zero without fixing private per-surface call counts, while a product-source audit forbids shared-daemon stop/restart/kill/remote-control mutation argv. Managed-current and the exact cold user-action `daemon start` contract remain covered separately.
61
+ - `make test`: Installed Codex app-server qualification Phase 0 centralizes clean-root validation, tmux stripping, contained socket/PID ownership, PR #857 readiness observations, terminal classification, exact cleanup, and the semantic command ledger in `internal/testutil/codexinstalled`. `TestInstalledHermeticTopologyQualification` emits validated version-tuple/topology/stage/class/reason JSON for bounded direct ready/close and managed payload provision/start/reuse/retire; payload absence is typed unsupported and blank/unknown/skip terminal classes are rejected. `TestInstalledIsolatedConversationCatalogSmoke` owns only the real `thread/list` canary, while `TestInstalledIsolatedPreTurnBootstrapSmoke` owns `thread/start`, second attach, and exact `thread/read(includeTurns=false)` without endpoint-not-ready skip. The deletion receipt is: `TestInstalledIsolatedDaemonLifecycleSmoke` → the hermetic topology test → direct/managed lifecycle → payload-missing classification; repeated catalog and pre-turn root/readiness/socket blocks → the shared fixture → their distinct protocol canaries → terminal startup/attach failures; app-local `installCodexArgvLedger` plus its raw argv allowlist → `codexinstalled.Ledger` → the unchanged model/broker assertions → semantic ambient/lifecycle mutation zero. `TestFailureCleanupRemovesOnlyExactOwnedArtifacts` and `TestManagedIdentityMismatchNeverAuthorizesForeignPID` prove foreign artifacts and mismatched PID/socket/backend evidence are never cleanup targets.
62
+ - `make test`: Installed Codex app-server qualification Phase 1 adds a daily/manual, non-required `0.152.0` real-binary matrix without adding another protocol implementation. The hosted setup copies the official Linux x64 optional dependency's native standalone subtree into an isolated release root, verifies its exact package manifest and executable, and puts only that release's `bin` first on `PATH`. Its three matrix entries merge into the surviving Phase 0 lifecycle, `thread/list`, and pre-turn second-attach canonical tests; their unique boundary is invocation plus typed reduction, with no scheduled protocol body to delete or maintain. The runner reduces all component observations to exactly one terminal record per primitive, requires the declared version in every tuple slot, and turns install/version verification failure, missing/invalid output, cleanup refusal, or missing matrix artifacts into terminal non-pass evidence rather than a skip. The closed artifact contains only schema version, CLI/managed/app-server tuple, topology, primitive, canonical terminal stage, class, and enumerated content-free reason; reflection and strict decoding audit the schema structurally instead of scanning serialized bytes. Each child and the separate `Installed Codex Qualification` aggregate retain only typed JSON for 14 days, stay absent from `E2E Tests` and `Test`, and fail closed on unsupported/fail/infra-error while required fake C01 remains unchanged.
63
+ - `make test`: Codex native control plane Phase 2 binds `thread/start` plus its single optional `turn/start`, or `thread/resume` with zero thread creation, to the exact Agent uid, managed Pane uid, and activation generation. App-server identity is additive schema-2 state on the Pane activation while the durable Agent `sessionRef` remains the conversation pointer; native Pane describe rows expose binding source, generation, thread, and current turn. Fake protocol and materialization tables pin one-prompt create, stored-thread resume, indeterminate post-create refusal without a second lane, and zero-write rejection for another Pane, generation, or thread. Generation-pool Phase 3 above supersedes the old unavailable/unsupported automatic plain fallback; exact late hooks may refine only the current turn, native-bound hooks fail closed before tmux remap or Registry writes, and legacy/unmanaged hooks plus bounded late acknowledgement retain their previous behavior.
64
+ - `make test`: Codex payload-free create now chooses a managed plain-interactive lane before any native controller or transaction-side provider mutation. `TestEmptyPromptCodexCreateUsesOnePlainCLILaneAndNoNativeBinding` and `TestEmptyPromptCodexSplitProducersKeepOnePlainCLILane` retain their symbols and now pin canonical/shortcut/default-intent functional success with zero Current/Resolve/create/resume calls; `TestPayloadFreeCodexFallbackCarriesContentFreeDeclaredAuthority` pins the `provider-hook` / `native-fallback` / `payload-free-fallback` runtime tuple. `TestCodexFanOutKeepsCurrentPlainCLILaneWithoutNativeCreate` remains the explicit `--interactive-only` guard, and `TestDeclaredPlainCodexLaneNamesOnlyTheByDesignLanes` closes payload-free fallback, interactive-only, and rollout-catalog resume as the three declared plain lanes. Prompted create, native catalog resume, stored Agent resume, and generation-pinned routes keep their existing native tests and no-second-lane rules.
65
+ - `make test` / opt-in private installed conformance: Codex payload-free Phase 1 qualifies only an exact RoleTUI/RoleAppServer digest, app-server/protocol, bound socket route, state domain, and platform/arch tuple. `TestExactPayloadFreeCapabilitySeparatesReadVisibleFromStoredResumable` owns the independent zero-turn start/read/stored-resume reduction, while `TestRemoteNewCapabilityRequiresExactFirstRealInputThreadAndTurn` rejects TUI liveness and unrelated first-turn identity. `TestCapabilityCacheInvalidatesEveryExactTupleAxis` and `TestCapabilityCacheCorruptFutureAndTrailingRecordsResolveUnknown` pin immutable fixed points, endpoint/socket/state drift misses, and invalid-cache unknown/plain fallback. `TestCodexPayloadFreeDoctorAndCreatePlannerShareExactRecordProjection` requires byte-semantic Doctor/create parity, and `TestCodexPayloadFreeUnknownCapabilityCannotBypassPhaseZeroFallback` keeps Phase-2 routing closed. `TestInstalledExactPayloadFreeCapabilityMatrix` takes only explicitly available 0.152.0/0.152.1/0.153.0 binaries, copied private state, private sockets and tmux, separates creator-live/closed read/resume evidence, and starts the exact remote-new TUI without sending any prompt, input, or turn. The installed remote-new row therefore remains `unknown`; only the separate unit/conformance seam can reduce an externally supplied matching first-real-input thread plus exact one-turn observation to support. Unset versions are `unavailable`, never synthesized; cleanup makes the private route non-current, outbound input/turn and ambient/default lifecycle mutation counts are zero, and the old liveness-only `codex-installed-capabilities.json` is historical `infra-error/evidence-incomplete`, not capability authority.
66
+ - `make test-integration` / `make test-e2e`: Provider-stub fixtures that deliberately test supervisor wait status, resource materialization, rename, startup/topology replay, exit reconciliation, or ControlSession placement rather than provider protocol request the explicit `--interactive-only` lane for their Codex rows; their shell stubs neither provision app-server authority nor become a native-create fallback. L06/L10/L11 move only their provider-neutral resume/replay witnesses to Claude instead of manufacturing an endpoint for a legacy Codex ref, while L11's clean no-resume Codex row remains an explicit endpoint-free lane. L19's Codex shim reports app-server unavailable before selecting its existing rollout-catalog/plain-resume row, which still gains no native authority. C01 remains the native exception: it supplies one isolated canonical `CODEX_HOME` state domain and fake app-server current generation, and its create rows retain native authority.
67
+ - `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 4 makes `thread/list` the Resume Picker catalog source and keeps the exact-id `thread/read(includeTurns=false)` validation seam for Session State. Unit/fake-server goldens pin explicit `cli`/`vscode`/`appServer` source kinds, non-archived and recency filters, opaque multi-page cursor progression, repeated/empty/exhausted cursor rejection, cwd depth, exact thread id, provider name or short-id-only fallback, branch, closed runtime status, deterministic ordering/dedupe, and typed source/confidence/fallback reason. A native failure discards every partial native row before exactly one rollout scan; native-empty never merges rollout history. Picker-to-create tests require native rows to call `Resume` with the exact listed id and rollout rows to stay on the current CLI lane. Session State tests require a bound session id or persisted resume id to win without `thread/read`, a thread-only candidate to pass one exact-id read before storage, a mismatched read to write zero, and the default validator to remain probe-only so autosave never starts the shared daemon. Provider parity and negative audits keep Claude/Antigravity and snapshot recipes unchanged and keep native title inference, prompt preview, transcript turns, and XML unwrap at zero. The installed catalog smoke is opt-in through `PROJMUX_CODEX_CATALOG_SMOKE_ROOT=/tmp/<isolated-root>` and uses the canonical disposable direct endpoint fixture; it owns only the `thread/list` compatibility canary, while exact `thread/read(includeTurns=false)` installed evidence stays with the pre-turn second-attach smoke.
43
68
  - `make test` / `make test-integration` / `make test-e2e`: Resume Picker stable-summary Phase 0 settles Codex, Claude, and Antigravity through provider-local bounded summary adapters before the picker opens, then performs one global dedupe/newest-first sort/cap and renders that immutable list from the first input frame through close. `ResumeSummary` contains only provider, exact resume id, timestamps, compact label, branch, depth-gated relative cwd, and source reference; turns, runtime state, confidence/reason explanation, pagination detail, and preview/transcript bytes cannot enter its row/search projection. Codex reads at most one native summary page under a 300 ms budget while its bounded rollout fallback walks and parses concurrently with context cancellation; native success (including native-empty) keeps authority, while native failure/timeout selects the already-settled fallback or an available empty/partial result and drops every late native result for the current invocation. At the unchanged 450 ms population cutoff, a bounded cancellation handoff collects a matching fallback partial even when its buffered send follows `ctx.Done`; that handoff remains inside the `<500ms` first-frame contract and does not extend discovery. Six provider completion permutations, exact 0/50/500 ms plus blocked-native fixtures, a large-rollout cutoff race, and an empty Codex store pin one settled first frame under 500 ms, exact native/fallback/Claude/Antigravity id and source handoff, and invariant row value/order hashes. The corrective provider projection now maps each provider to pre-global-cap discovered count (including zero), search failure, or Settings-disabled, with localized EN/KO footer text and no native/rollout/fallback transport vocabulary; disabled providers perform no discovery read and enter no selectable row. Row/status tables require every item value to be `new` or an exact parseable resume; native picker tests pin provider state outside query/cursor/Enter cardinality and the exact lower ordering `provider line → shown count · content-free MoreNotLoaded` on one localized status line, including independent EN/KO clipping at 80 columns. The recovered footer row extends only the AI Resume down `SelectionDetail` dock by exactly one row, leaving the popup, provider row, and pre-compaction MoreNotLoaded list limit fixed; generic pickers and `MoreNotLoaded=false` retain their prior budgets. Turn enrichment and page continuation do not write the current list. Focus routes through typed `SelectionDetail` updates whose `Items`, Header/Footer, and `MoreNotLoaded` set flags stay zero; the renderer reserves and pads the independent down detail dock from the first frame so empty/help, loading, ready, unavailable, clipped, and scrolled content cannot move the list or footer. The visible provider-neutral row grammar is `relative age → provider badge → branch → optional relative cwd → conversation title` under one width policy. Its label authority is exact sessionRef-bound Agent `metadata.displayName` → provider-owned non-ID title → exact Agent topic → exact stable name → localized `Untitled/제목 없음 · …last4`; full provider/conversation identity stays in Value, SearchKey, routing, and selected detail, while native/rollout source and fallback reason stay only in selected detail. `TestNativeAIResumeTrustFramesLocaleAndSizeGolden` pins actual 80x24/120x40 EN/KO frame rows and widths; the AI Resume-only detail limiter requires three real nonblank preview lines before overflow, uses the recovered row for one more real content line when continuation is present, and cannot count post-preview metadata as content. Geometry goldens and a 20-row round-trip cursor sweep pin identical origins, limits, dividers, footer positions, query bytes, and item values; app race/privacy tests pin latest-focus-wins cancellation, one read per exact `(provider, id, updatedAt)` key, preview failure isolation, and zero preview-byte persistence.
44
69
  - Visibility clarification: native/rollout source is not rendered in a row or footer; it remains in the frozen hidden SearchKey for the existing routing-search contract and is visibly inspectable only in selected detail. Fallback reason is absent from both rows and SearchKey and is visible only in selected detail.
45
- - Label-source clarification: Codex rollout and Claude transcript labels are prompt-derived by the existing summary scanners, so they are not provider-owned title authority and remain absent from both primary labels and SearchKey. No extra transcript read is introduced; exact Agent metadata or the localized untitled suffix supplies the label instead.
70
+ - Label-source clarification: Codex rollout and Claude transcript labels are prompt-derived by the existing summary scanners, so they are not provider-owned title authority and remain absent from both primary labels and SearchKey. No extra transcript read is introduced. The Project-wide naming closure Phase 1 entry below supersedes the historical Agent metadata fallback chain: exact Agent topic/provider `Context` or the localized untitled suffix supplies the label, while the stable Agent name remains search-only.
46
71
  - `make test` / `make test-integration` / `make test-install-smoke`: App socket marker migration Phase 0 keeps ordinary mutation fail-closed on app-owned pre-0.13 partial markers with a typed exact `config apply --socket <name>` recovery and zero writes; pins config apply's `-L` to absolute `-S` path/PID, pre-source app ownership and missing-or-exact logical policy, typed route-marker write plan, post-write reobservation, and refusal of foreign/forged/mismatch/alias/PID-drift inputs; projects missing/mismatch/unreadable Doctor findings identically in text/JSON without mutation; and recovers an installed live legacy session while preserving its exact server generation.
47
72
  - `make test` / `make test-install-smoke` / `make test-e2e-update`: App socket marker migration Phase 1 makes npm, Go, GitHub Release, and source `make install` converge the exact live route before binary publication and verify it with the published binary afterward. Stage-table tests require an exact eventual `--bin` target, stop publication after pre-apply failure, make source/publication/post-verify failure non-success with the Phase 0 exact recovery, and keep `--no-apply` pre-apply-free with zero live access plus an explicit apply-required state. The isolated install fixture pauses the atomic move and runs legacy/built/installed shell and attach consumers before/during/after while preserving the exact v0.12.2-modeled socket path, PID, and sessions without exposing a raw missing-marker error.
48
73
  - `make fmt`: repository formatting for Go, shell snippets, and generated docs where applicable.
@@ -53,10 +78,10 @@
53
78
  - `make test` / `make test-integration`: Settings IA Phase 4 row-0 HUD visibility coverage pins default/saved/invalid effective-source projection; independent Notifications HUD and Agent Usage HUD persistence; byte-identical default app/standalone output; exact four-combination range/alignment/full-width behavior; all-off one-line (`status on`) convergence with the structural Window row, quiet autosave, and no stale row residue; Settings-generated config source reload; unchanged notify queue and usage snapshot state; and production exact-socket live tmux apply across all four combinations under the integration smoke's guarded socket cleanup.
54
79
  - `make test` / `make test-integration`: Settings IA Phase 5 row-1 segment controls cover Project, Working directory, Git, Resources, Clock, and Settings launcher independently: five visibility files reuse the Phase 4 missing/invalid→on default, saved/effective/source, atomic 0600 persistence, generated-config no-write, and live exact `source-file` apply contracts; Resources has no duplicate visibility file and uses `live-resources` alone to remove its range/job and guard the sampler/cache mutation path; cwd/Git detail Views own separate Visible and Icon controls so icon off preserves their text; app/standalone exact mixed output removes hidden ranges and owned spacing with retired Kube ranges/jobs absent; and Settings launcher off retains the CLI/default-keybinding Settings entry.
55
80
  - `make test` / `make test-integration` / `make test-e2e`: Settings IA Phase 6 retires the hidden Project recipe and Effective merge handlers/catalog/i18n plus every Kube-specific product projection. Named regressions pin the Project tree to Automation and Snapshots, reject public and internal Kube status routes, prove app/standalone generated configs contain no Kube range/job/binding, remove Kube from popup/sidebar switch previews and hook/session environments, preserve project hooks/trust plus `[startup]` and arbitrary generic `[env]` keys, and make a legacy `[kube]` update fail with the exact documented diagnostic while preserving original bytes and mtime. The integration smoke checks the same generated/live tmux absence on an isolated exact socket.
56
- - `make test`: CLI information architecture v2 Phase 0 Cobra command catalog and help boundary: the `internal/cli` manifest holds every public route plus the 2 hidden internal helpers with exactly one canonical/shortcut/compatibility/internal disposition each (34 public, 9/7/13/5 tally, the ninth canonical being the `get` node the selector Phase added) and zero orphan routes in either direction against the canonical route manifest, which pins `rebind project`, `prune project`, `agent usage`, and the Pane-read `cwd` field projection alongside the closed shared `-o uid|name|ref|metadata|json|pane-id|none` catalog that deliberately excludes `cwd`; a golden fixture freezes the byte-identical top-level listing now rendered from the manifest and a command-tree golden freezes the Cobra surface (36 route nodes, hidden flags, `DisableFlagParsing` on every bridge, no injected `help`/`completion`/`__complete` command); a raw-argv table proves each bridge forwards `--`, positional arguments, unknown flags, and payload tails untouched, that handler-returned `flag.ErrHelp` and exit coders survive the Cobra boundary, and that unknown commands keep `unknown command: <token>` on stderr with exit 1 rather than a usage error; and the help no-side-effect negative tests prove every root, nested, and hidden internal help invocation exits 0 with stdout-only manifest help across every `flag`-equivalent spelling — both dash prefixes of `help`/`h`, bare and with any `=value`, matched on the flag name and pinned against the real `flag` package, with a guard that no leaf parser defines a competing `help`/`h` flag, reaches no handler, writes nothing into an isolated home, records zero operational-error journal rows through the same policy the process entrypoint uses and zero phantom state-changing success rows at any route depth (the diagnostics direct-help classifier walks positional sub-route words but stops at the first flag, so `upgrade --ref --help` and a literal `pin add help` path stay conservatively mutation-capable), skips the legacy hook filesystem migration, and — with a bounded timeout regression guard — no longer blocks on the `popup-wait-key` tty read, while a help flag after `--` stays payload and a bare nested `help` word still reaches its handler.
81
+ - `make test`: CLI information architecture v2 Phase 0 Cobra command catalog and help boundary: the `internal/cli` manifest holds every public route plus the 2 hidden internal helpers with exactly one canonical/shortcut/compatibility/internal disposition each (34 public, 9/7/13/5 tally, the ninth canonical being the `get` node the selector Phase added) and zero orphan routes in either direction against the canonical route manifest, which pins `rebind project`, `prune project`, `agent usage`, and the Pane-read `cwd` field projection alongside the closed shared `-o uid|name|ref|metadata|json|pane-id|none` catalog that deliberately excludes `cwd`; every graph route also carries exactly one validated identity/address/topology/desired-state/runtime/focus/cardinality allowed-effect record plus a closed nullable domain-effect discriminant, runtime help and generated `docs/cli.md` project the same tuple without handler/runtime access, the schema-v3 handler outcomes, full route-effect manifest, and mismatch ledger are golden-pinned, and non-route fixtures reserve same-root reparent and `agent-delivery` semantics without adding public routes; a golden fixture freezes the byte-identical top-level listing now rendered from the manifest and a command-tree golden freezes the Cobra surface (36 route nodes, hidden flags, `DisableFlagParsing` on every bridge, no injected `help`/`completion`/`__complete` command); a raw-argv table proves each bridge forwards `--`, positional arguments, unknown flags, and payload tails untouched, that handler-returned `flag.ErrHelp` and exit coders survive the Cobra boundary, and that unknown commands keep `unknown command: <token>` on stderr with exit 1 rather than a usage error; and the help no-side-effect negative tests prove every root, nested, and hidden internal help invocation exits 0 with stdout-only manifest help across every `flag`-equivalent spelling — both dash prefixes of `help`/`h`, bare and with any `=value`, matched on the flag name and pinned against the real `flag` package, with a guard that no leaf parser defines a competing `help`/`h` flag, reaches no handler, writes nothing into an isolated home, records zero operational-error journal rows through the same policy the process entrypoint uses and zero phantom state-changing success rows at any route depth (the diagnostics direct-help classifier walks positional sub-route words but stops at the first flag, so `upgrade --ref --help` and a literal `pin add help` path stay conservatively mutation-capable), skips the legacy hook filesystem migration, and — with a bounded timeout regression guard — no longer blocks on the `popup-wait-key` tty read, while a help flag after `--` stays payload and a bare nested `help` word still reaches its handler.
57
82
  - `make test`: CLI information architecture v2 Phase 1 resource metadata foundation: `internal/core/metadata` unit and table tests cover the closed `Project`/`Window`/`Pane`/`Agent` kind set under `apiVersion: projmux.io/v1alpha1`, the one-time name-base seed priorities for every kind (Agent topics and raw pane titles are excluded everywhere), name-key validation, the persisted `nameReservations` suffix allocator proving the lowest free suffix is scan-order independent and that an explicit collision fails with `ErrNameConflict` and zero reservations written, offline Project bootstrap topology with a resolvable `spec.defaultShellPaneRef`, uid immutability across runtime creation, teardown, and rebind, zero heuristic uid merges (same-basename roots stay distinct; only an exact saved root reuses its uid), rebind and root-missing/root-returning `MissingRoot` condition lifecycle with preserved first-observed timestamps and name reservations, reverse-order operation rollback restoring a byte-identical registry, the closed Agent phase set with its exit classification and resumable Offline/Failed transitions, `schemaVersion` classification that fails closed on any newer envelope and on an unversioned document (production registers no migration step, since v1 is the first shipped envelope), a `testdata/registry-v0-source.json` → `testdata/registry-v1-migrated.golden` migration pair driven by a test-injected older step with reservation backfill that never renumbers existing names, full registry structural validation, and Session State round-tripping through additive `omitempty` `metadata` blocks at the unchanged snapshot `version: 1` including deterministic positional reconciliation of pre-metadata snapshots. `internal/integrations/metadata` covers the locked `<state>/projmux/metadata/registry.json` store: a newer `schemaVersion`, malformed JSON, and a content-bearing document with no `schemaVersion` are all refused byte-for-byte with no write, no temp file, no backup, and no quarantine on `Load`/`Update`/`Migrate`, while an absent, empty, or whitespace-only file still yields a fresh empty registry; migration atomicity is proven against a test-registered older step by injected failures after the backup, after the staged temp write, and just before the atomic replace, each leaving the original bytes and no leaked temp; a successful migration keeps a verifiable pre-migration backup and is idempotent; a failing operation writes nothing; a bootstrap registry golden freezes the camelCase file format and 0600 mode; concurrent registrations serialize through the lock with unique allocated names; and an end-to-end test round-trips an offline snapshot through both the registry store and the session-state store, reconciling every Window and Pane back by uid. The tmux mirror tests pin the exact session/window/pane option writes, the new window-scoped options, `automatic-rename off` on managed windows, `rename pane` touching only `@projmux_pane_label` and never `pane_title`, uid ↔ target resolution in both directions, and write-free legacy observation. `internal/app` and `cmd/projmux` prove the metadata usage-error seam: explicit name and rebind-root collisions reach exit code 2 through the existing `UsageError` path with zero mutations, while a newer registry schema stays a runtime error at exit code 1.
58
83
  - `make test` / `make test-integration`: Registry-first control plane Phase 0 durable recovery envelope: `internal/integrations/metadata` proves the initialized boundary in both directions -- before the first commit every read answers the empty registry and creates nothing under the state dir, the first commit publishes `registry.json` plus the `registry.initialized` marker at 0600 below a 0700 directory, and after that a missing, empty, or whitespace-only registry is `ErrRegistryStateLost` on `Load`, `LoadReadOnly`, `LoadSnapshot`, `Update`, and `UpdateConvergent` instead of a fresh identity domain; a registry written before the marker existed still reads and gains the marker on its next write; each same-version semantic write copies the prior verified bytes into bounded `recovery/` copies whose chronological order survives retention, skips the copy for structurally invalid prior bytes, and a migration keeps its versioned `.bak` without spending a recovery slot; a convergent no-op leaves registry bytes, mtime, inode, marker, and copies untouched; an injected failure at the staged fsync, the staged write, staged validation, the recovery copy, the marker, the pre-replace directory sync, or the atomic replace leaves the prior registry byte-identical with the copy rolled back, no staged temp file, no lock, and a retry that still converges, while a failed first commit leaves neither a registry nor a marker so the next read is still first use; missing-after-initialization, malformed, too-new, and unreadable stay four distinguishable causes that create no registry and no uid; and the integration smoke repeats init -> updates -> no-op -> loss -> diagnostic -> marker-removal remedy against the installed binary under an isolated `XDG_STATE_HOME` below the smoke root, asserting the bounded five retained copies hold the bytes their writes replaced.
59
- - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 1 registry recovery boundary: `projmux reconcile registry` plans with zero writes and restores only an explicitly named verified source. `internal/integrations/metadata` proves the plan classifies the current registry and every bounded candidate -- `valid`, `first-use`, `missing`, `empty`, `malformed`, `schema-too-new`, `invalid`, `unreadable` -- with a `sha256:` digest of the exact bytes and the resource/reservation counts, newest first over both the write-copy and replaced-copy families, while inspecting a first-use state dir creates nothing under it and inspecting a lost one leaves every file's mode, size, and mtime unchanged; malformed, empty, too-new, dangling-`ownerRef`, and duplicate-uid sources are refused as `ErrRecoverySourceRejected` with the live registry and the whole state dir byte-identical, including when the only candidate is a too-new envelope; files this store did not write are never offered as candidates; a valid restore publishes the verified bytes verbatim so uids, owner relations, and name reservations round-trip through `LoadReadOnly`, a known older envelope restores unrewritten and still migrates in memory only, a restore into a marker-less state dir establishes the boundary so a later loss reads as state loss, and a repeat restore is a byte no-op that takes no preserved copy and leaves the lock removed; the bytes a restore replaces are preserved -- malformed originals included -- at 0600 as their own bounded family that is offered back as a candidate and never consumes the write-copy history; a stale `--expect-source-checksum` or `--expect-current-checksum`, a source rewritten under the lock, and a registry replaced under the lock all refuse as `ErrRecoveryRaced` with nothing published, no preserved copy, and no staged temp file, an ambiguous or unknown selector is never ranked or guessed, and an injected staged fsync or directory sync failure leaves the registry and marker byte-identical. `internal/app` proves the command boundary: a dry-run is byte-identical output over two runs with the whole state dir unchanged and zero tmux calls, a sourceless run only ever plans and prints the exact guarded next command, every refusal still emits the report and exits non-zero with the current registry untouched, usage refusals happen before the store is opened, and the human projection carries the same state, candidates, reasons, and next command as the JSON. The mirror diagnostic is evidence only: it appears exactly when recovery is needed and no verified copy exists, resolves containment from stable tmux ids, counts uid-less panes as unattributed rather than recoverable, counts provider-carrying panes as proof of Agents whose uids are not mirrored, always states the six unrecoverable scopes, makes only read-only calls on the one exact socket, and reports a missing transport or a dead server as a reason without probing another server or generating a registry. The integration smoke repeats state loss -> preview -> guarded restore -> resource reads -> repeat no-op -> corrupt/too-new/ambiguous/raced refusal against the installed binary under an isolated `XDG_STATE_HOME` below the smoke root.
84
+ - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 1 registry recovery boundary: `projmux reconcile registry` plans with zero writes and restores only an explicitly named verified source. `internal/integrations/metadata` proves the plan classifies the current registry and every bounded candidate -- `valid`, `first-use`, `missing`, `empty`, `malformed`, `schema-too-new`, `invalid`, `unreadable` -- with a `sha256:` digest of the exact bytes and the resource/reservation counts, newest first over both the write-copy and replaced-copy families, while inspecting a first-use state dir creates nothing under it and inspecting a lost one leaves every file's mode, size, and mtime unchanged; malformed, empty, too-new, dangling-`ownerRef`, and duplicate-uid sources are refused as `ErrRecoverySourceRejected` with the live registry and the whole state dir byte-identical, including when the only candidate is a too-new envelope; files this store did not write are never offered as candidates; a valid current-v4 restore publishes the verified bytes verbatim so uids, owner relations, and name reservations round-trip through `LoadReadOnly`, while a v3 source keeps an exact versioned backup and checksum-bearing migration report before deterministic canonical-v4 publication, and a repeat restore compares the canonical publish checksum as a byte no-op that takes no preserved copy and leaves the lock removed; a restore into a marker-less state dir establishes the boundary so a later loss reads as state loss; the bytes a restore replaces are preserved -- malformed originals included -- at 0600 as their own bounded family that is offered back as a candidate and never consumes the write-copy history; a stale `--expect-source-checksum` or `--expect-current-checksum`, a source rewritten under the lock, and a registry replaced under the lock all refuse as `ErrRecoveryRaced` with nothing published, no preserved copy, and no staged temp file, an ambiguous or unknown selector is never ranked or guessed, and an injected staged fsync or directory sync failure leaves the registry and marker byte-identical. `internal/app` proves the command boundary: a dry-run is byte-identical output over two runs with the whole state dir unchanged and zero tmux calls, a sourceless run only ever plans and prints the exact guarded next command, every refusal still emits the report and exits non-zero with the current registry untouched, usage refusals happen before the store is opened, and the human projection carries the same state, candidates, reasons, and next command as the JSON. The mirror diagnostic is evidence only: it appears exactly when recovery is needed and no verified copy exists, resolves containment from stable tmux ids, counts uid-less panes as unattributed rather than recoverable, counts provider-carrying panes as proof of Agents whose uids are not mirrored, always states the six unrecoverable scopes, makes only read-only calls on the one exact socket, and reports a missing transport or a dead server as a reason without probing another server or generating a registry. The integration smoke repeats state loss -> preview -> guarded restore -> resource reads -> repeat no-op -> corrupt/too-new/ambiguous/raced refusal against the installed binary under an isolated `XDG_STATE_HOME` below the smoke root.
60
85
  - `make test` / `make test-integration` / `make test-e2e`: Runtime topology exit cascade Phase 1 exact Offline Pane/Agent canonical deletion: unit/runtime tables require an explicit exact `uid:` plus durable `MissingRuntime=True/RuntimeUnbound` Pane evidence or an unbound `Offline` Agent whose retained Panes all carry that evidence, and require a positive exact socket identity plus non-empty inventory proving zero mirrors; implicit/name/scope/`--all`, no-server, empty/error/permission inventory, live-without-evidence, duplicate, and foreign ownership cases refuse with zero writes. Route tests pin byte-consistent dry-run/apply Registry-only plans and the exact `would be killed`/`was killed` diagnostics, locked zero-to-live, live-to-zero, generation, owner, duplicate, and foreign revalidation, repeat no-match idempotence, zero raw tmux kills, and preservation of the owning Window/root, sibling Panes, and a resumed live Agent bound to a newer Pane generation. The isolated exact-socket e2e repeats Pane and Agent dry-run/apply/repeat on app-owned and standalone hosts, keeps sibling Registry/tmux hashes and a foreign socket unchanged, and verifies that no canonical `kill-pane` targets the already absent runtime while missing-server authority is refused.
61
86
  - `make test`: Registry-first control plane Phase 2 resolved resource graph: `internal/core/resourcegraph` is the one join between the Registry's desired topology and one exact tmux server, and it ships with its contract frozen rather than with a consumer. The attribution matrix table covers managed sessions/windows/Agent-owned panes, an app-owned control session, the same control marker refused on a standalone host, an unknown role value, an ephemeral scratch session, ephemeral outranking a control marker on one session, unmarked objects resolving to unattributed inside a managed enclosure or on an app-owned server and to foreign otherwise, unknown Project/Window/Pane uids resolving to recoverable, duplicate claims, a window whose session mirrors another Project, a pane whose window mirrors another Window, Pane and Agent uids mirrored onto the wrong kind of option, and absent containment evidence still binding on an exact uid. Status derivation is pinned for all four kinds including `missing-root` outranking a live session, an Agent deriving its managed Pane's status while its phase stays the Registry's, and a paneless Agent reading offline rather than unknown. The owner-chain invariant crosses every level of runtime containment and then asserts that no surviving binding disagrees with the Registry chain, so a cross-Project match is a test failure rather than a review question. Partial failure is a per-scope table: a failed sessions, windows, or panes query downgrades only its own rows to `unknown` with a stated reason while every row count is preserved. Determinism is proven by sixteen shuffled inventories marshalling byte-identically plus a fixed ascending tmux-id order, `Resolve` is proven not to mutate either argument, and a `go/parser` import audit is what keeps the package unable to write anything -- no filesystem, process, or tmux dependency can be added to it without that test failing. The transport table covers explicit `--socket`, an absolute `--socket-path` including cleaning and the relative-path refusal, the mutually exclusive flags, explicit flags outranking an inherited `$TMUX`, an inherited absolute socket path, a discarded relative inherited value, and no transport as a legal state; `@projmux_app` is trusted only at its exact value. `internal/integrations/metadata` covers the observation adapter: a fixed four-call budget that does not grow with a 200-session server and does not repeat across five `Observe` calls, every call pinned to the exact socket with an explicit mutating-verb denylist, the four transport modes (app-owned `-L`, standalone `-L`, explicit `-S`, and no transport with zero tmux calls), two sockets never reading each other, one failed query degrading exactly one scope while the full budget still runs, an absent server costing one call and reading as nothing-live rather than unknown, identical managed rows resolved from one tmux output under both host modes, and the routing layer refusing an absent transport, a non-tmux executable, and a missing runner instead of falling back to the default server. An opt-in real-tmux smoke (`PROJMUX_RESOURCEGRAPH_TMUX_SMOKE=1`, skipped by default so a unit run never spawns a server) builds its own isolated `TMUX_TMPDIR` and two unique sockets, strips the inherited `TMUX`/`TMUX_PANE` from every invocation, and proves the format strings and the containment join against real tmux: the managed Project/Window/Pane bind to the exact `$N`/`@N`/`%N` the server reported, Home resolves to control, a scratch session to ephemeral, a plain session to unattributed, a sibling socket carrying the same uids is never read and is unchanged afterwards, and cleanup kills only the exact `#{socket_path}` it has confirmed lives inside its own temporary root.
62
87
  - `make test`: CLI information architecture v2 Phase 2 selector and read-only resolution: `internal/core/selector` unit and table tests cover the pure selector grammar and the read-only resolution engine over a structurally validated registry fixture that carries duplicate `displayName`s, an offline Project, a `MissingRoot` Project, and Window/Pane names repeated across owner scopes. An aggregate negative table proves the excluded forms resolve **0 occurrences** — an implicit comma split of two real names, a duplicate `displayName`, a real `spec.root`/`spec.cwd` path, a tmux `%N`/`@N`/`$N` handle, and a bare uid without the `uid:` prefix — while `displayName` is still reported as ambiguity context and the `uid:` form does resolve. A recorded per-stage trace pins the fixed name/uid-union -> AND label-filter -> uid-dedupe order with a 3/2/1 shape no other ordering can produce; repeated singular `--window`/`--pane` occurrences union in argv order and collapse by uid; the Pane universe is owner-scoped across Projects, Windows, and Agent-managed Panes; Window/Pane status is inherited from the owning Project as live/offline/`missing-root` with `MissingRoot` outranking a stale live session; and the resolver never mutates the registry it is handed. The `<verb, kind>` cardinality matrix pins the contract cells (`get windows` 0..N, `rename window` exact-one, repeated `create --window` 1..N) and a violation returns a usage error whose ambiguity listing is bounded to 5 candidate rows carrying name/`displayName`/owner context plus an omitted count. `internal/cli` proves `cwd` is accepted by exactly one canonical route: `ResolveOutputToken` grants it only on `get pane` and rejects it on every other read kind and on every named mutation route, the shared `-o` catalog still excludes it, and the new `get` node owns only the `pane` kind while the `current` compatibility route is left untouched. `internal/app` covers the route: `get pane --current -o cwd` emits a single trailing-newline path scalar from one tmux `display-message` query; `-o cwd` without `--current` and `--current` with any other projection or with selectors are usage errors with 0 bytes on stdout and 0 tmux calls; ambiguity, no-match, an emptied label filter, and an unresolvable `--project` scope are exact-one violations with 0 bytes on stdout; the output table pins default/uid/name/ref/metadata/json/none for a resolved read; the read loads the registry through a store path that creates no directory and no lock file when no registry exists; and `get` is excluded from the pre-dispatch legacy-hook filesystem migration. `internal/integrations/metadata` pins the zero-side-effect `LoadReadOnly` read, and `cmd/projmux` proves a bounded cardinality failure reaches exit code 2 with the listing on stderr and 0 bytes on stdout.
@@ -72,7 +97,7 @@
72
97
  - `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 10 closes managed runtime mutation behind printable typed plans. Named coverage keeps the verb inventory and product-surface table closed, proves deterministic bytes and successful execute → exact effect ledger → empty replan, rejects unknown observation with zero Registry deletion/runtime kill, audits every non-test app and tmux/mux/metadata/replay source (including typed helpers, argv callbacks, and variable argv), and bijects generated catalog, Pane-menu, popup, hook, and native provider/resume picker producers to planned or exact semantic-exemption rows. The executor validates printable Socket/PhysicalSocket/server-generation authority before pre-effect reobservation can declare a repeat empty, then validates every pending semantic guard before the first write. Focused controller tests cover Project/ControlSession Window create/rename, Project/sidebar and Session-picker stop, first-use Project and attach Home bootstrap, typed automatic layout operands, batch Pane/Window delete, app-owned path/pid/app/logical guards, exact inherited standalone `TMUX=path,pid,index` plus producer-anchored `$N`/`@N`/`%N` authority, and explicit controller `--socket-path` authority with PID/blank markers plus action UID/session/window guards and no inferred Pane. Partial-marker/forged/recycled-server refusal, fresh bootstrap generation binding, full-tuple owned reverse rollback, and self-target commit/flush ordering are pinned. Generated popup/menu actions carry their verified Pane anchor into the child without synthesizing `TMUX_PANE`; Settings persists config but refuses live source/retirement on standalone authority. The isolated tmux suites remove inherited `TMUX`/`TMUX_PANE` where independence is required, use a unique socket root, assert physical `-S` writes, and clean only exact observed sockets.
73
98
  - `make test` / `make test-integration` / `make test-e2e`: rename/rebind transport convergence keeps Registry uid/name/root authority and writes only the corresponding exact live field after the Registry transaction: Project name → `@projmux_project_name` without session rename, Window name → `@projmux_window_name` without `displayName`/`window_name`, Pane name → `@projmux_pane_label` without `pane_title`, and Project root → `@projmux_project_path` without filesystem or uid change. Public `rename agent` adds the exact-one Agent cardinality/aliases and changes only Window-scoped `metadata.name` plus its reservation, preserving topic annotations, provider, lifecycle linkage, and managed Pane metadata with zero tmux writes. Unit tables pin field-specific call logs, offline and inventory-unavailable later convergence, duplicate UID refusal, and post-commit live-write failures that exit nonzero with the public reconcile retry while retaining durable Registry drift. Immediate projection exists only for an inherited absolute `$TMUX` socket, routes every lookup/write through exact `-S`, targets stable `$N`/`@N`/`%N` handles, and performs zero tmux calls outside tmux or for a malformed relative inherited socket. Public reconcile treats a known unique Project UID as authoritative over the stale old path after rebind, plans/guards only the path-anchor write, repeats no-op, and keeps unknown/duplicate claims fail-closed. The isolated real-tmux smoke uses inherited-environment removal plus a validated exact socket path, asserts session/window/pane runtime names and a second socket remain unchanged, exercises offline later reconcile and injected option-write failure/retry, and cleans only the contained sockets.
74
99
  - `make test` / `make test-e2e`: canonical resource create lifecycle reentrancy coverage pins the private, session-scoped create lease passed atomically through `new-session -e`, exact-socket/session lifecycle inspection, live-only deferral, stale-lease cleanup, and a second in-transaction reconciliation after explicit mirrors. Standalone `after-new-window`/`after-split-window` hooks remain synchronous. Window/Pane materialization preserves combined tmux output on non-zero exit, accepts only one exact newly observed `@N`/`%N` handle for ownership-checked reverse rollback, emits zero stdout on failure, keeps the registry byte-identical, reports ambiguous or ownership-mismatched residual drift, and never removes an unrelated object. The isolated tmux smoke exercises `--create-window`, exact returned pane IDs, registry/runtime uid agreement, and forced synchronous-hook failure cleanup; the existing Agent-create smoke covers canonical creation in a live managed Window with its stub provider.
75
- - `make test` / `make test-e2e`: create-window foreign-session attribution hardens the same lifecycle boundary with an atomic `Created` plus `$N`/`@N`/`%N` new-session result and exact owner verification. Before the first reconcile import, lease, mirror, or Registry write, create now requires the selected live session to carry the registered Project's exact uid and canonical root with no server-wide duplicate uid/root claimant; the check repeats against the reconciler's fresh inventory and immediately before an inner create, so outer-miss/inner-hit, first-use discovery, blank/foreign same-name, and duplicate-other-session races fail closed. New Windows accept only the exact composite `@N`/`%N` pair that is the sole global before/after addition under the stable `$N`; wrong, pre-existing, wrong-session, missing, or ambiguous handles remain unclaimed with exact residual diagnostics and byte-identical Registry/pre-existing options. Claim injection tests pin uid claim → rollback ledger → full mirror ordering for Session/Window/Pane, ownership-checked rollback, and chained name/uid selector refusal. The isolated real-tmux smoke preserves blank and foreign same-name sessions byte for byte on the exact contained socket, then runs eight concurrent same-name `--create-window` racers and requires one Window, nine unique Pane uid mirrors, exact returned `%N` handles, and unchanged pre-existing bindings.
100
+ - `make test` / `make test-e2e`: create-window foreign-session attribution hardens the same lifecycle boundary with an atomic `Created` plus `$N`/`@N`/`%N` new-session result and exact owner verification. Before the first reconcile import, lease, mirror, or Registry write, create now requires the selected live session to carry the registered Project's exact uid and canonical root with no server-wide duplicate uid/root claimant; the check repeats against the reconciler's fresh inventory and immediately before an inner create, so outer-miss/inner-hit, first-use discovery, blank/foreign same-name, and duplicate-other-session races fail closed. New Windows accept only the exact composite `@N`/`%N` pair that is the sole global before/after addition under the stable `$N`; wrong, pre-existing, wrong-session, missing, or ambiguous handles remain unclaimed with exact residual diagnostics and byte-identical Registry/pre-existing options. Claim injection tests pin uid claim → rollback ledger → full mirror ordering for Session/Window/Pane, ownership-checked rollback, and chained name/uid selector refusal. Required L06 on the isolated real-tmux socket now holds the persistent `registry.json.flock`, starts one real CLI waiter, and releases through held/released/settled FIFO barriers with no sleep; it requires the waiter to succeed with exact Registry owner/runtime bindings, tmux UID/owner mirrors, unchanged pre-existing bindings, the persistent flock present, and zero transient residue. The former eight-way full-create burst is an explicit non-gating stress observation invoked exactly as `PROJMUX_E2E_REGISTRY_STRESS=1 make test-e2e E2E_SCENARIO=L06`; it gates all eight real CLI processes into actual overlap and emits a typed PID/status/outcome/start/finish record for every racer without making all-success throughput or host capacity a required guarantee.
76
101
  - `make test` / `make test-e2e`: Window resource deletion converges the Registry desired-topology cascade and an exact live tmux binding when one exists. Non-implicit targets (explicit references or `--all`) inventory only `-L projmux`: zero exact `@projmux_window_uid` mirrors, including a typed absent-server result, is a valid Registry-only offline cascade, while one exact mirror is killed under the owning Project's Registry-authoritative session. Duplicate, foreign, stale-session, generic inventory-failure, and preflight-to-execution race states remain fail-closed; selector-omitted active deletion still requires its exact live mirror and refuses an implicit foreign-caller socket. The plan and confirmation include the exact `@N` kill plus implicit Project-session end when present, while an offline plan explicitly reports that no tmux Window will be killed; `--dry-run` renders the complete resource/live-or-offline cascade with zero Registry transactions and zero tmux writes. Execution revalidates both resource and live signatures under the Registry lock, preserves the Project, sibling Windows, unrelated sockets, and Registry bytes on pre-mutation failure, and reports exact removed `@N`/session plus retained Registry UIDs if the store commit fails. Repeat deletion of an already-removed offline UID is a no-match with zero writes. A self-target commits and flushes the Registry result before queueing a shell-quoted exact-socket kill. The isolated real-tmux smoke covers live sibling/last-Window/self-target parity plus an offline Window with shell and Agent descendants, byte-identical offline dry-run, exact Registry-only cascade, repeat no-match, and foreign-socket preservation. Before every Registry byte-identity baseline, the smoke requires ten unchanged samples with no exact-socket hook shell or controller writer while preserving retry-exhausted startup evidence; a deterministic delayed pre-exec shell proves the observer cannot return during the generated hook's sleep, and the same boundary covers the final kill-server/no-server transition.
77
102
  - `make test` / `make test-e2e`: Pane and Agent live-binding deletion extends the same fail-closed boundary to `delete pane|agent`. One exact `-L projmux` Pane inventory joins each Registry Pane uid through its owning Agent/Window/Project graph to the exact `@projmux_pane_uid`, `@projmux_window_uid`, optional session Project uid, and Registry-authoritative session projection; missing, duplicate, foreign-Window/Project, stale-session, and foreign-caller-socket rows cause zero writes. Agent deletion derives its runtime targets only from its Registry-owned managed Panes—never process content—and removes the Agent, managed Pane resources, and exact `%N` objects while preserving sibling Panes, Windows, and owner graph; deleting the current managed Pane alone preserves the Agent as `Offline` with a cleared `paneRef`. Dry-run, confirmation, and result output list every exact Pane plus implicit last-Pane Window and last-Window session cascades. Execution revalidates both resource and live signatures under the Registry lock, kills external targets before commit, and reports already-killed exact Pane/window/session/Pane-uid tuples with retained Registry UIDs on partial tmux or store failure. A caller-containing plan writes the complete deleted-Pane tombstone set under the Registry lock before commit; a partial tombstone or store failure restores every reachable marker while the Registry identities still exist, and any incomplete rollback keeps those Registry owners authoritative. After commit and flush, exact-socket background kills are queued only from revalidated tombstones, so any unqueued survivor remains explicitly tombstoned and retryable. Both anchored import and repair matching refuse that marker, preventing a failed delayed kill from re-importing a deleted Pane as an orphan. The isolated smoke strips inherited `TMUX`/`TMUX_PANE`, validates both socket paths below its dedicated root, covers sibling, last-Pane, managed-Pane-to-Offline, Agent cascade, anti-reimport, and second-socket preservation, and cleans up only the validated exact sockets.
78
103
  - `make test-e2e`: Implicit active target selector Phase 3 runs installed-shape apply and lifecycle convergence against real tmux servers under a dedicated `TMUX_TMPDIR`, after stripping inherited `TMUX`/`TMUX_PANE`. It verifies the real `#{socket_path}` stays below the smoke root; deleted managed Window/Pane uid options return to their original registry identities after exact-socket apply; a newly created Window and a split Pane carry bindings before the creating tmux command returns and before an implicit read; repeat apply/lifecycle leaves registry bytes and bindings unchanged; a second socket remains untouched; and cleanup kills only the validated exact sockets.
@@ -103,8 +128,14 @@
103
128
  - `make test`: Registry runtime observation Phase 2 stable Window identity and runtime display projection: the `LegacyWindowNameSeed` table supplies tmux `window_name`, both automatic-rename modes, Pane label, provider, known shell, topic, and raw title and requires every row to return only `FallbackWindowNameBase`; an isolated two-Window import allocates `window`/`window-1`, records the observed `window_name`s as duplicate-allowed `metadata.displayName`, and proves `describe window` renders stable `Name` and runtime `DisplayName` as separate rows. A re-import after an explicit `rename window` keeps every existing uid, `metadata.name`, ownerRef, and name reservation while updating only displayName, and the unanchored binding-reapply fixture repeats that negative guard at the reconciler seam. `internal/integrations/metadata` pins the split transport: `@projmux_window_name` continues to mirror stable metadata.name while tmux `window_name` is driven by displayName, with an empty legacy displayName falling back safely to metadata.name. The explicit resource rename test proves `projmux rename window ... --name` does not change displayName; Pane `pane`/`pane-1` import naming and provider-base Agent naming/linkage/status remain covered by their existing Phase 3/4 tests.
104
129
  - `make test`: Registry runtime observation Phase 3 orphan live pane import: Phase 1 taught the binding-repair path to *adopt*, and adoption needs an existing registry Pane to adopt into -- so a live pane created outside the Registry had no counterpart, stayed permanently unbound, and kept `projmux delete pane` with no selector refusing with *carries no `@projmux_pane_uid`* in the operator's own active pane; on the measured socket exactly one live pane out of seven carried a binding and the active one was not it. `internal/core/metadata` gains `Mutator.ImportOrphanPane`, which mints one shell-role Pane owned by an already-paired Window through the same `addPaneTx`/`storePaneStatus` primitives the legacy import path uses rather than a second write convention, and its unit tests pin the two fixed decisions as the things that would otherwise go wrong: the name is `FallbackPaneNameBase` uniquified by the registry allocator (`pane`, then `pane-1`) and is asserted to be neither the `claude` command nor the `@projmux_pane_label` that `LegacyPaneNameSeed` would have produced, because `metadata.name` is contractually never derived from a runtime attribute while the runtime reading still lands in `status.displayTitle`; and a pane whose observed provider, topic and command all say `claude` mints **zero** Agents, because deciding registry topology from pane content is the inference direction the track refuses. The cwd table covers the observed value and the fallback to the owning Project root, and an unknown Window is `ErrNotFound` with a `reflect.DeepEqual`-identical registry, which is what lets the caller treat the error as "skip this pane". `internal/app` opens the one gap `reapplySessionBindings` had -- `if !paneMatch.Matched() { continue }` -- by threading the `Mutator` and operation id into the repair path and resolving each pane through `paneBindingFor`. The end-to-end reconciler test registers the orphan, mirrors its uid *and* its name onto the live tmux pane through the existing whole `MirrorPane` write, leaves the paired sibling on `pan-first`, keeps `MissingRuntime` off the pane this pass bound, and then runs a **second** pass that leaves the registry and the whole fake tmux server byte-identical -- the regression that would appear without the `binder.Claim` on what was just minted. The registration rule is a table with a row per outcome: a blank pane with no candidate left is registered, a uid the registry has never heard of is registered (never adopted -- projmux itself produces unknown uids after a rolled-back reconcile writes them onto non-transactional tmux options), while a uid a sibling Window owns, two live panes carrying one Pane's uid, a pane under a Window carrying a foreign uid, a pane under a live window with no registry Window left (the Window layer still creates nothing), a session no Project claims, and a session two Projects both claim all register **zero**; every registration is additionally asserted to land under a Window owned by `prj-alpha`. The cross-project negative runs two Projects, two sessions and an orphan in each against Window and Pane names that collide across them, and requires each registration to sit under its own Project's Window -- the boundary is structural, since the owning Window was paired earlier in the same walk. The diff audit is the preservation contract: a uid/name/owner/spec snapshot of every pre-existing object must come out identical, no name reservation may be released, and the object count may only grow. Tolerance is pinned by injecting a `NewUID` that fails for `KindPane`: reconcile returns nil, nothing is mirrored, and the adoption work that did succeed is kept.
105
130
  - `make test`: Registry runtime observation Phase 4 agent runtime linkage and observed status: three defects that only look separate. Phase 3 registered every orphan live pane as a Window-owned *shell* Pane whatever it was running, nothing ever wrote `status.paneRef` for a pane it had not itself launched, and `agentMatch` inherited Agent status from the owning Window (`status = windowMatch.Status`) -- so on the measured socket `get panes` printed an empty AGENT column for every row while `get agents` showed two finished conversations as `live` and hid the three that were running, and `claude-1` reported `Phase: Offline` next to `Status: live`. `internal/core/selector` derives Agent status from the Agent's own runtime object -- the managed Pane its `status.paneRef` names -- through the same single `ObservedStatus(missingRoot, bound)` rule, with the owning Window contributing only its Project's `MissingRoot` flag and specifically not its liveness; a five-Agent fixture under one live Window (running with a live pane, running with a dead pane, released, pending, failed) pins the table, an invariance test flips the owner Window's observation with every other input held fixed and requires byte-identical Agent answers (the falsifiable form of "zero inheritance paths", with the Window itself asserted to have moved so the invariance is not vacuous), an exhaustive sweep over all 8 live-pane subsets proves no reachable observation reports `live` for a non-`Running` phase, an empty observation against a stored `status.session.live` of `true` reports every Agent offline, a dangling `paneRef` is offline rather than live, and `MissingRoot` still outranks a live managed pane on every Agent at once. `internal/core/metadata` gains `Mutator.LinkAgentPane`, whose evidence rule is authorship rather than content: `pane_current_command` is never read and `@projmux_ai_agent` -- the option the AI routes write when projmux itself launches an agent -- is the sole marker, which is the same option `bindLegacyPaneTx` already trusted on its create path, so a pane the operator typed `claude` into is refused exactly as Phase 1's rule requires. It resolves an already-Agent-owned Pane to its Agent, otherwise attaches to an Agent of the *same Window* whose `status.sessionRef` records the identical provider conversation as the pane's `@projmux_ai_session_id`/`@projmux_ai_thread_id` (an exact identifier equality with `provider` compared too, so a Codex thread id is never equated with a Claude session id), and otherwise mints one; ambiguity mints rather than guesses because two Agents recording one conversation is legal state, and a candidate already claimed this pass or holding another `paneRef` is not a candidate at all. Eleven unit tests pin every branch plus the preservation contract: the linked Pane keeps its uid and its name while its reservation follows it into the Agent scope with no leftover Window-scoped entry, the Pane count cannot move, an unknown Window or Pane is `ErrNotFound` with a `reflect.DeepEqual`-identical registry, an unrecognized provider spelling still yields an Agent on the `agent` name base, two live panes reporting one conversation cannot claim one Agent, and a second pass rebinds without rewriting anything. `internal/integrations/metadata` reads the two conversation ids on the existing `list-panes` query (11-field pane rows) so linkage costs no extra tmux call. `internal/app` calls the linkage on the Pane `paneBindingFor` just settled on, discarding the error rather than escalating it *or* skipping the binding write that already succeeded; the reconciler suite runs two live agent panes and one plain shell through one pass and requires a populated AGENT column and `Role: agent` on both agent panes, an untouched shell pane, an Agent count equal to the live agent pane count and a `live` Agent count that matches it, a byte-identical registry *and* fake tmux server on the second pass, a four-row authorship table (projmux-launched, hand-typed `claude`, a shell, an unrecognized provider), tolerance under a `NewUID` that fails for `KindAgent` (reconcile returns nil, zero Agents, and the pane keeps the binding and the shell role it already had), an Agent that goes offline on the next read when its pane closes with no hook and no reconcile while staying queryable, and the one visible consequence stated rather than left implicit -- a promoted Pane now joins the managed-Pane lifecycle, so the dead-agent-pane sweep removes that Pane row while the Agent itself survives with the same uid, name, and `sessionRef`, still resumable.
106
- - `make test`: Columnar default for list reads Phase 0 plus display-first resource projection: the default human plural reads (`get projects|windows|panes|agents`) are space-aligned tables whose first two columns are always `DISPLAY NAME`, `NAME`, followed by the existing per-kind columns in their prior relative order (`STATUS`, owner chain, optional AGENT/SESSION, `AGE`). Four whole-table goldens pin Project aliases, the already-projected tmux Window display/tab beside its distinct stable name, and current Pane/Agent fallback; the fallback uses metadata.name only when `strings.TrimSpace(metadata.displayName)` is empty and preserves a non-empty stored displayName byte-for-byte. Adversarial Pane `status.displayTitle` and Agent topic values, raw-title wording, tmux handles, duplicate display aliases, and selector negatives prove that secondary display data is never promoted and that only metadata.name or explicit `uid:` remains selectable. Each golden also asserts zero box/rule characters, tabs, and trailing whitespace. Widths stay terminal-display-cell based through `projmuxpicker.VisibleLen`; Hangul, long values, empty interior/trailing cells, and the direct rune-count falsification remain covered. The table remains gated on `OutputModeDefault` plus plural `list`: singular `get`, `describe`, and rename/create summaries remain byte-frozen, as do scalar `-o uid|name|ref|none` and whole List-envelope `-o metadata|json` projections. Zero rows still succeed with zero stdout/stderr bytes, and the injected-clock AGE boundary/determinism suite remains unchanged.
107
- - `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 machine-consumer contract is frozen the whole way: `-o uid|name|ref|none` byte for byte, and `-o metadata|json` as whole documents rather than by substring -- they are rendered from the same `ObjectMeta` the AGE cell reads, so a new key anywhere in either 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.
131
+ - `make test` / opt-in `PROJMUX_RESOURCEGRAPH_TMUX_SMOKE=1`: Project-wide naming closure Phase 1 replaces stored presentation reads with one typed invocation-scoped `registryview.Context{Value, Source, Observed}` projector. `TestContextProjectorNoTransportPriorityMatrix`, `TestContextProjectorLiveOverlayRequiresExactUIDBinding`, and `TestContextSourcesCannotChangeAddressSelectionReservationOrOwnerRef` pin Project root-basename, ControlSession session, Window anchor topic/provider/command/fallback, Agent topic/provider, and Pane owner-topic/live-title/command priority while proving context changes cannot alter durable names, selected UIDs, reservations, ownerRefs, or Registry bytes. `TestGetPaneAmbiguityUsesOneExactRuntimeContextSnapshotWithoutChangingSelection` gives cardinality diagnostics the same exact live projector before enforcement without a second observation. Human `get` tables use `CONTEXT`, `SOURCE`, `OBSERVED`, and separate stable `NAME`; `describe` uses `Context`, `ContextSource`, and `ContextObserved`. The EN/KO golden and empty-context tables keep terminal-cell alignment and never copy stable name into an absent context. JSON/metadata tests retain the v3 durable bytes/schema, including compatibility fields, and reject ephemeral context. `TestStoredPresentationProductConsumerInventoryIsZero` plus its closed compatibility-site inventory permit only v3 writers, migration, mirror, and divergence convergence to read `metadata.displayName`/`status.displayTitle`; sidebar, recent Window, resume labels, Registry view, tables, descriptions, and ambiguity diagnostics all consume the projector. The isolated real-tmux smoke strips inherited identity, confines two exact sockets below a temporary root, and requires only exact UID-bound primary `window_name`/raw `pane_title` to overlay with `Observed=true` while the sibling remains unchanged.
132
+ - `make test` / `make test-e2e`: Columnar view Phase 0 exposes that same invocation-scoped context without making it durable: only Registry-backed plural `get projects|windows|panes|agents -o json` wraps each stored resource with a top-level `context{value,source,observed}`, and all three keys remain present for empty context. This supersedes the prior historical entry's plural-JSON rejection while leaving its Registry-schema and `-o metadata` guarantees intact. `TestGetListJSONCarriesContextFromTheResolutionSnapshot` pins the exact whole List document for all four kinds and compares the stored resource prefix byte-for-byte before the additive sibling; `TestGetListJSONLiveContextRequiresExactUIDBinding` admits `observed=true` only for an exact UID-bound Window/Pane and keeps Project/Agent/unmatched runtime false while requiring one injected snapshot read and zero Registry transactions/writes; `TestGetListJSONContextDoesNotChangeMetadataOrRegistry`, `TestResourceContextJSONIsConfinedToPluralGet`, and `TestGetListJSONEmptyAndReadFailureAreAtomic` freeze metadata/scalar/default/singular/create-fan-out bytes, Registry bytes, the exact empty `items: []` envelope, and zero partial JSON on Registry or stale-UID read failure. L06 `create-materialize` adds the real-tmux witness on its guarded isolated socket: the same Window/Pane are unobserved outside tmux, exact inherited-socket reads project the UID-bound live `window_name`/raw `pane_title`, every context has exactly three keys, and Registry bytes remain unchanged.
133
+ - `make test` / `make test-integration` / copied-v3 installed smoke: Project-wide naming closure Phase 2 advances the Registry to schema v4, changes descendant reservations to `(Project|ControlSession root UID, kind, name)`, and gives every automatic Project/ControlSession/Window/Pane/Agent its exact full UID name. `TestRootWideReservationSameDifferentRootAndKindMatrix`, `TestEveryAutomaticallyCreatedResourceKindUsesItsExactFullUID`, `TestAutomaticUIDNameExhaustionIsBoundedAndZeroWrite`, and `TestAutomaticNumericSuffixProducerInventoryIsEmpty` pin the namespace matrix, all five create kinds, bounded 100-remint refusal, and zero semantic/numeric automatic name producers. `TestV3ToV4CanonicalizationUsesDuplicateAllMembersAndDestinationFixedPoint` plus its byte golden covers every duplicate-group member, multi-step destination closure, already-canonical mappings, and preservation of unique names outside the set. Store tests require an exact-byte backup, checksum-bearing content-free field-removal report (including a present-empty key with SHA-256 of empty content), staged validation/fsync failpoints, downgrade refusal, and byte/count-identical repeat migration. Legacy import and v3 snapshot projection use exact UID names and the same root-wide canonicalization; a current-v4 collision is refused before Registry, tmux, provider, trust, or snapshot writes. Explicit multi-target `--name` returns `explicit-name-cardinality` before UID or external writes, rename collisions are total zero-write, and same-root Pane-to-Agent reparent preserves the exact reservation bytes. `metadata.displayName` and persisted `status.displayTitle` are decode-only v3 migration inputs; invocation-scoped `registryview.Context` remains non-identifying.
134
+ 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
+ - `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
+ - `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.
137
+ - `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
+ - `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.
108
139
  - `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.
109
140
  - `make test` also covers Settings directional hierarchy parity: every catalogued navigation/actionable/passive/Back kind has a Right/Left outcome; Right opens only forward-navigation rows and never executes mutation, passive, or Back rows; child and grandchild Left return exactly one View while root Left stays in place; Esc and the Settings popup-toggle close action remain available at root and every visited depth; en-US/ko-KR frames advertise the arrows; and typed-query, color-grid, scope-chip/chord, and key-recorder input retain their existing native semantics. A native root → child → grandchild → back → root interaction and a config-tree mutation-negative audit pin the shared policy without changing global picker arrows.
110
141
  - `make test`: Settings IA re-cut Phase 1 Automation and Notification ownership: the two containers are proven to be different product concepts by their labels alone. Automation names only Projmux-owned user scripts and the policy that gates them (`Projmux session lifecycle`, `After notification queued`, `Project automation policy`) and never a Provider or an integration; Notifications names only delivery and projection and never a lifecycle script or a `[hooks.*]` key. The Provider inventory is pinned to the Claude/Codex/Antigravity enum: the tmux bell producer is filtered out of Provider Integrations by producer identity and rendered flat at its own `tmux event source` View, whose wiring row says in words that the bell is not an Agent Provider. `Agent event behavior` renders the same Provider enum, so a Provider can never be wireable in one destination and missing from the other, and a provider x event matrix walks every event to its four-way `default`/`notify`/`state`/`quiet` choice and asserts each choice is addressed to the provider and event that rendered it. The two Provider destinations are proven disjoint: install/remove/conflict/config-path/`Check integration` rows exist only in the wiring detail and the `ai-hook-set:` choices only in the behavior view, with neither surface rendering the other's action prefix. `Check integration` (and `Check` for the bell) is an observable read-only action rather than a placeholder: it re-reads the shipped doctor diagnostic, reports status/config path/conflict as Settings feedback, shells out to nothing, leaves the config tree byte-identical, and errors on an unknown id. The `[hooks.send-noti]` + `PROJMUX_NOTIFY_HOOK` parity test sets both at once and proves they are a supported combination rather than an either/or: each keeps its own resolved value, the desktop row disclaims the fan-out and the Automation row disclaims the sender, neither row renders the other's value, and the three session lifecycle events stay empty. An automation event x scope matrix walks `pre-create`/`post-create`/`post-attach`/`send-noti` in both scopes, keeps the technical `[hooks.<event>]` spelling in the state row, requires the project scope to name its missing-context reason instead of falling through to a global value, and pins `send-noti` as a sibling of the lifecycle list rather than a fourth member of it. The attention guard keeps live Pane badges out of every Notifications surface while the badge style keeps its Appearance home, and the retired `Hook quiet policy` vocabulary is gone from the shared UI text catalog and from the rendered rows in both locales while staying on the negative-guard list.
@@ -140,8 +171,12 @@
140
171
  - `make test` / `make test-integration` / `make test-e2e`: keymap multi-stroke Phase 0 pins v2 `sequences` parse/render/merge, marker-only v1 migration with pre-v2 backup and rollback, repeat no-op bytes, deterministic two-to-four-stroke trie/table generation, shared prefixes across standalone/app actions, duplicate/strict-prefix/single-first-stroke conflict rejection, rejection of the undeliverable `C-m`/`C-i`/`C-[` spellings a terminal reports as `Enter`/`Tab`/`Escape`, unsafe and picker-local/command/output/default-sequence negatives, apply-ordered retire of the recorded roots/tables strictly before `source-file` (the generated config records that state but never removes it, because a `run-shell` loop is not ordered against a sourced file), native modified-stroke transport allowlisting without independent dispatch, and zero-write config/live failure ordering. The isolated real-tmux paths prove single dispatch for shared-prefix `mouse.toggle`/`window.create`, Escape and unknown continuation cancellation to `root` with zero raw pane input, terminal and native `send-keys -K -c` logical-stream parity, repeated apply idempotence, sequence removal cleanup, and unchanged single-chord behavior.
141
172
  - `make test` / `make test-integration` / `make test-e2e`: keymap multi-stroke Phase 1 Settings authoring pins one shared 1–4 stroke Add/replace recorder, picker-local sequence exclusion, en-US/ko-KR locale and navigation golden reachability, shared reserved-key policy with first-plain rejection and later-plain acceptance, typed/captured canonical v2 keymap and generated-config byte parity, length-driven single/sequence add and atomic cross-kind replace plus strict-prefix conflict recovery, saved/prepared/running-session reporting including no-live-tmux recovery, native-macOS-on/off versus Linux/WSL logical authoring parity with transport differences confined to Delivery diagnostics, and zero keymap/generated-config/live-server mutation from navigation, cancellation, Backspace editing, or delivery tests.
142
173
  - `make test`: Settings IA re-cut Phase 3 versioned keymap action-ID migration. The action catalog carries a `CanonicalID` beside the runtime `ID`, and `keymapActionManifest` derives the exhaustive old→canonical table from it, so `TestKeymapManifestCoversEveryCatalogActionExactlyOnce` fails on any action without a v1 spelling or any manifest row that resolves to no action, `TestKeymapCanonicalIDsAreUniqueAndDistinctFromLegacyIDs` rejects a canonical id that duplicates another action's canonical or legacy id, `TestKeymapRetiredIDsAreDisjointFromTheManifest` keeps retired ids out of the rename table and requires a remediation for each, `TestKeymapCanonicalIDsUseTheDottedSchema` pins the dotted lower-case shape and that the writer quotes it, and `TestKeymapDispositionIsExactlyOnePerTable` proves every table gets exactly one of canonical/coalesce/preserve-unknown/retired-remediation. Parser coverage adds the root `schema_version` marker with duplicate, forward-version and unknown-root-key refusals, and rejects an unquoted dotted table because that is a nested TOML table rather than a dotted key (`TestKeymapV1FileParsesAndRoundTrips`, `TestKeymapRejectsUnquotedDottedTableAndFutureSchema`). `TestKeymapMigrationPreservesEffectiveBindings` migrates a fixture carrying a custom multi-alias, `keys = []`, a transport-dependent additive alias, a legacy `plain` and a legacy `prefix` and requires the merged chord table to be identical before and after. The apply half is covered by `TestKeymapMigrationCoalescesIdenticalDualTables`, `TestKeymapMigrationRefusesConflictingDualTablesWithZeroWrites` (no keymap write, no backup, no tmux call), `TestKeymapMigrationPreservesUnknownTables`, `TestKeymapMigrationRepeatRunIsAByteWriteFreeNoOp` (unchanged bytes *and* unchanged mtime), `TestKeymapMigrationOnAbsentFileIsANoOp`, `TestKeymapMigrationBackupIsDigestNamedExclusiveAndReused`, `TestKeymapMigrationRefusesToClobberAForeignBackup`, `TestKeymapMigrationFailsClosedWhenTheDirectoryIsReadOnly`, `TestKeymapMigrationPreservesFileModeAndSymlinkTarget` (writes through the link, keeps 0600), `TestKeymapRollbackRestoresV0` and `TestKeymapRollbackRefusesACorruptBackup`. Convergence and the write boundary are pinned by `TestSettingsKeySaveConvergesAV0KeymapToV2`, `TestSettingsKeySaveAbortsWhenMigrationConflicts` (Schema/Saved/Prepared/Running-session stages), `TestTmuxApplyRefusesToApplyWhenTheKeymapMigrationFails`, `TestTmuxApplyNoReloadMigratesWithoutTouchingTheServer`, `TestConfigRenderReportsMigrationOnStderrAndWritesNoKeymap` (the preflight never reaches the generated artifact on stdout) and `TestReadOnlyRoutesNeverRewriteTheKeymap`, which drives the real dispatcher against an isolated HOME/XDG for `help`, `version`, `get`, `describe`, `agent usage`, `doctor --help`, both `config render` artifacts and `update apply --dry-run`. `TestUpdateDryRunPreviewsTheMigrationStageWithoutPromisingADiff` requires the preview to name the stage and leak no canonical id, and `TestPostUpdateApplyArgsAlwaysReachTheNewBinary` pins that `--no-apply` becomes `config apply --no-reload` so an installer suppresses the reload without skipping the migration. `TestKeymapAndRegistrySchemaMarkersAreIndependent` and `TestKeymapMigrationFailureLeavesTheRegistryUntouched` keep the keymap `schema_version` domain separate from the resource registry `apiVersion`/`schemaVersion` envelope, with separate backups and no shared transaction.
174
+ - `make test`: Go updater exact-target atomic replacement. `update apply` on the `go` backend no longer runs `go install` and then converges with the path it resolved beforehand: it stages a scratch `GOBIN` beside the active executable, publishes into it, checks the artifact is a regular executable file, replaces the exact active executable through the same `atomicBinaryReplacer` the github-release backend uses, and runs the post-publication `config apply` against that replaced target. `TestUpdateApplyGoReplacesTheExactActiveExecutable` requires the target file to hold the published bytes for both `apply` and `--no-apply` and names the replaced path in the output; `TestUpdateApplyGoPublishesIntoAScratchGobinBesideTheTarget` pins that the publication carries exactly one environment entry — an absolute `GOBIN` that is a sibling of the target, so the user's real `GOBIN` is never written and the rename stays within one filesystem; `TestUpdateApplyGoRefusesArtifactsThatAreNotRegularExecutables` covers a missing artifact, a directory, a symlink and a non-executable file, and requires the target untouched, the scratch removed, and no post-publication convergence; `TestUpdateApplyGoCleansScratchAndLeavesAUsableBinaryOnEveryFailure` walks a missing toolchain, a failing `go install`, a failing replacement and a failing convergence, requiring the recovery command in every message and either the old or the new binary at the target, never a partial write; `TestUpdateApplyGoDryRunReplacesNothing` pins the preview lines and zero execution; and `TestRunUpdateExternalWithEnvLayersOverTheCallerEnvironment` holds the memoization boundary by requiring `PROJMUX_PROJDIR` to reach the child alongside the `GOBIN` override. `TestUpdateApplyRunsGoUpgradeInPublicationOrder` and `TestUpdateApplyRunsGoUpgradeNoApply` keep the published stage order unchanged.
143
175
  - `make test`: update-availability parity Phase 0 post-apply version verification. `update apply` now reads the version of the executable PATH resolves for `projmux` (falling back to the running executable when PATH has none) before publication and again after every stage, and reports an explicit failure instead of success when that version did not rise. `TestUpdateApplyFailsWhenTheInstalledVersionDidNotChange` covers a same-version npm reinstall, the same for `go`, `--no-apply`, an install already at the expected version, and a version that went backwards, and requires the message to name the current version, the expected version, and the install channel. `TestUpdateApplyWithoutACachedCheckStillNamesTheMissingExpectedVersion` keeps the expected slot filled with an explicit unknown rather than dropped, `TestUpdateApplySucceedsWhenTheInstalledVersionRose` keeps a real upgrade passing and names the verified executable, `TestUpdateApplyRefusesToReportSuccessWhenTheVersionCannotBeRead` turns a failing or unparseable probe into an unverified failure with no `>> verified:` line, `TestUpdateApplyVerifiesThePathResolvedExecutable` pins that the comparison reads the PATH-resolved binary and never an installer report, `TestUpdateApplyFallsBackToTheRunningExecutableWhenPathHasNoProjmux` keeps an off-PATH install verifiable, `TestUpdateApplyStageOrderIsUnchangedByVersionVerification` proves the published pre-converge/publication/`config apply` sequence is byte-identical for npm, go, and both `--no-apply` forms, `TestUpdateApplyDryRunPreviewsVerificationWithoutProbing` keeps `--dry-run` a pure preview across all three installers, and `TestParseProjmuxVersionOutput` pins the `projmux <version>` line reader.
144
176
  - `make test`: update-availability parity Phase 1 channel-scoped availability source. The judgment authority is now chosen from the detected install channel: npm asks `registry.npmjs.org/projmux` for `dist-tags.latest`, while go, github-release, and source builds keep the GitHub release API, and both requests go through the same update HTTP client so the shell gate's timeout and redirect ceiling are unchanged. The cache gains an additive `source` field; an answer recorded for a different source is discarded rather than aged out, and a cache written before the field existed still reads as a GitHub release answer so the unchanged channels neither refetch nor lose their offer. `TestUpdateCheckPicksTheAvailabilitySourceFromTheInstallChannel` pins the per-channel routing, the recorded source in the cache file, and the single `v`-prefixed version format both authorities produce. `TestShellGateWaitsForTheNPMRegistryToPublish` covers the reported symptom in both directions with the GitHub authority made unreachable: no `u` while only GitHub has the version, `u` once npm publishes it. `TestUpdateJudgmentIsUnchangedForNonNPMChannels` holds the change-freeze boundary for go, github-release, and source, including a pre-existing source-less cache that must not trigger a refetch. `TestUpdateCacheRecordsItsAvailabilitySourceAndDropsAForeignOne` proves the recorded source, the discard on a channel switch (status and `cachedLatestVersion` both fall back to unknown), the refetch that replaces it, and the unchanged cache file name. `TestShellGateStaysSilentWhenTheAvailabilitySourceFails` covers an unreachable registry, an error status, a response with no `dist-tags`, and a non-JSON body, requiring the failure to be reported and the status report to name the state and the source. `TestSettingsAboutRefreshRowNamesTheAvailabilitySource` pins that the Settings > About refresh row names the authority the check actually contacts.
177
+ - `make test`: Prerelease rc channel Phase 1 release channel axis. Update judgment now carries a release channel orthogonal to the install path, defaulting to stable in the strong sense -- unset, empty, and unrecognised values all resolve to stable, so no misconfiguration opts an install into prereleases (`TestReleaseChannelDefaultsToStableForEveryUnrecognisedValue`, `TestReleaseChannelEnvOptsIn` for the `PROJMUX_RELEASE_CHANNEL` opt-in and the resolver seam a stored setting will drive). `TestDefaultReleaseChannelNeverSeesAPrerelease` is the negative fixture for both install paths at once: npm is handed a `dist-tags.rc` pointer it must ignore, and github-release fails the test if it so much as requests the endpoint that lists prereleases, with no `u` offered in either case. `TestRCChannelAnswersWithTheNewerOfStableAndRC` pins the opted-in authority for npm (`dist-tags.rc` beside `dist-tags.latest`) and github-release (the release list, drafts skipped, ordered by semver precedence rather than creation) and requires the newer of the two lines, so an rc install rejoins stable the moment that line ships; `TestRCChannelReadsBothNPMDistTagsInOneRequest` holds the shell gate's request budget by proving the rc pointer comes out of the abbreviated packument the stable judgment already fetched. `TestPrereleaseVersionsCompareByPrecedence` covers the defect itself -- `updateVersionPattern` used to discard the prerelease suffix, so rc.1 read rc.2 and its own line's stable as `current` -- across rc-to-rc, rc-to-stable, numeric identifier ordering, and the stable-outranks-its-own-rc direction. `TestUpdateCacheIsKeyedByInstallPathAndReleaseChannel` makes the cache key the pair: a fresh answer from the other channel is discarded rather than aged out in both directions, a default install writes no `release_channel` field so its on-disk shape is unchanged, and a cache written before the axis existed still reads as stable without a refetch. `TestUpdateStatusJSONRevealsTheReleaseChannel` pins the additive `release_channel` field and that a default install's `latest_version` can never carry a prerelease suffix, and `TestUpdateStatusTextNamesOnlyANonDefaultChannel` keeps the default channel's text report free of a channel row. The whole of the pre-existing `update_test.go` passes unmodified.
178
+ - `make test`: Prerelease rc channel Phase 2 About > Updates opt-in toggle. The release channel axis Phase 1 built now has a user-facing switch: a `Release channel` toggle under `global.about.updates` writes `[update] release_channel` into the global config, and `newUpdateCommand` binds `releaseChannelSource` to that stored value so the next judgment runs on the chosen channel with no restart. `TestAboutUpdatesTogglesTheReleaseChannelOptIn` drives the real picker loop for the whole round trip -- the row opens on stable, one Enter puts the judgment on rc, a second returns it to stable, and the config carries the explicit opt-out -- because a toggle that only repaints itself would close neither half of C-3. `TestReleaseChannelOptInIsVisibleToTheNextJudgment` reads the stored value back through `updateCommand.releaseChannel()` rather than through the Settings process that wrote it. `TestReleaseChannelDefaultsToOffOnAnInstallThatNeverConfiguredIt` is the empty-fixture default: an untouched home resolves to stable, and rendering the row is proven not to be a write, since a key created merely by looking at the screen would silently retire a `PROJMUX_RELEASE_CHANNEL` opt-in. That is also why the resolution order is `stored setting > environment > stable` and not `stored setting exists at all`: `TestReleaseChannelSettingOutranksTheEnvironmentOnlyOnceStored` pins that the environment keeps answering until a setting exists and loses the axis the moment one does, including when the stored choice is stable -- which is what makes "turn it off and you are back on the stable line" true. `TestStoredReleaseChannelIsFailClosedForUnknownValues` keeps the stored side as strict as the axis, and `TestProjectUpdateConfigReleaseChannelRoundTrip` / `TestProjectUpdateConfigOmitsAnUnsetReleaseChannel` hold the on-disk shape, including that an unset channel renders no `[update]` section so "never configured" stays distinguishable from "configured to the default". `TestReleaseChannelRowStaysInAboutAndNotInLabs` is the positive companion to the Labs negative guard in `settings_test.go`, which passes unmodified; the whole of `update_test.go` also passes unmodified. `TestReleaseChannelToggleDoesNotReportItselfAsAnUpdate` keeps the row's feedback out of the apply vocabulary: the toggle shares the `update:` action prefix with check and apply, so the generic prefix label announced "Update complete" one row below "Update now" -- wording a user is entitled to read as "the binary was just replaced". The toggle now names itself in the mutation inventory while its two siblings keep theirs.
179
+ - `make test`: Prerelease rc channel Phase 3 channel-aware `update apply`. Apply now picks its install target from the same authority the judgment read and verifies the result by semver precedence, closing the gap where an opted-in install was told an rc was available and then handed the stable line anyway. `TestRCChannelAppliesTheDistTagItsJudgmentRead` pins the npm target across the three registry states -- the rc pointer ahead of stable resolves `projmux@rc`, the line's own stable release supersedes its rc and resolves `projmux@latest`, and a registry with no rc pointer stays on the stable line -- so an rc install follows the judgment back off the prerelease instead of being stranded on it. `TestRCChannelGitHubReleaseApplyDownloadsThePrerelease` pins the github-release half against the release list with `releases/latest` made a trap, because that endpoint by definition never carries a prerelease, and requires the rc asset to be the binary actually published. `TestUpdateApplyVerifiesPrereleaseUpgradesByPrecedence` covers the reported symptom directly: `0.15.0-rc.1` to `0.15.0` succeeded and was still reported as "installed version did not change" because the check compared only the numeric core; the fixture also holds rc-to-rc, numeric identifier ordering, and the unchanged stall and backwards verdicts. The change-freeze boundary is `TestDefaultChannelNpmApplyIsUnchangedAndAsksNoRegistry`, which requires the default channel's published sequence to be byte-identical and its target to resolve without a single HTTP request, and `TestDefaultChannelGitHubReleaseApplyNeverAsksTheReleaseList`, which fails if a default install so much as requests the prerelease-carrying list. `TestUpdateApplyDryRunNamesTheAuthorityItsChannelWillRead` keeps `--dry-run` from promising `releases/latest` or `projmux@latest` to an install that will read neither. The whole of the pre-existing `update_test.go` passes unmodified.
145
180
  - Darwin native-key focused coverage includes portable physical chord parsing for Alt/Option, Control, Control-Option, Shift, letters/digits/navigation/function keys, `darwin && cgo` synthetic event-to-canonical-chord mapping for Option, Control, shifted letters, function keys, and arrows with unmatched/plain event pass-through, shared `keymap.toml` custom-action loading (including `M-a`), focused tmux client discovery, canonical `send-keys -K -c` dispatch through the existing root key table, native Settings Add key capture winning over simultaneous terminal text/error results after ignoring the activation Enter while the non-Darwin path preserves immediate terminal capture, default-on shell startup, `PROJMUX_NATIVE_KEYS=0`/`false` and Settings `[ui].native_keys = false` broker opt-out gates, broker direct no-op behavior, one-time pre-permission consent guidance, broker-only process replacement after an Accessibility wait so fresh macOS permission state is observed without restarting tmux, preservation of the direct no-server shutdown boundary while permission is pending, and an explicit Linux/Windows/no-CGO shell startup no-op boundary.
146
181
  - `make test` covers the default picker key ownership across catalog, generated tmux config, terminal adapters, setup probes, and native popup close hints: `Alt-4` opens AI Resume as a guaranteed launch default, while `Alt-7` opens the AI split picker as its editable default.
147
182
  - `make test` also covers canonical `projmux setup terminal` terminal detection, preview no-write, apply/backup, Ghostty candidate conflicts, Windows Terminal merge, config overrides, and symlink refusal/allow; removed top-level command dispatch/help negatives, canonical rejection and help absence of the removed legacy-only `--dry-run` flag, and setup remediation hints are asserted separately.
@@ -185,7 +220,7 @@
185
220
  - `make test` also covers Operational diagnostics Phase 3 Session State adoption: the closed outcome/operation/code/source/six-count schema; direct and popup manual save/delete, Settings latest/named save and current/project delete, deduplicated prune delete, and manual snapshot projection ownership; exact snapshot and item aggregates; error count omission; Settings feedback error capture; append-failure best-effort ownership; autosave session/config/source/interval/debounce/store/save/set-option failure ownership including quiet error-one and project on/off precedence over a broken global config, plus repeated autosave success/no-op zero-volume behavior; same-run coexistence with the runtime lifecycle pair; and seeded path/command/session metadata negative fixtures. `make test-integration` exercises manual save, autosave success/error, save → desired-state mutation → exact closed-Project restore projection → ordinary materialization, and aggregate delete against the guarded run-unique tmux socket and isolated XDG root, including exact snapshot cleanup and raw metadata negative checks.
186
221
  - `make test` also covers Operational diagnostics Phase 4 notify/focus adoption: the closed transition/disposition/provider/category/route schema and cross-family rejection; explicit notify enqueue ownership with queued/deduplicated/failed outcomes; secondary attention and nested prune-reconcile enqueue ownership isolation with no-change zero volume; external-hook, Linux, WSL Toast/fallback, disabled, unavailable, desktop-dedupe, and visible-pane delivery outcomes; one shared process recorder with identical-tuple hot-path coalescing; append-failure best-effort semantics; focus direct/queue/toast request classification with unresolved, notify-only, fallback, and focused outcomes; same-run coexistence with the existing `session.switch` lifecycle pair without a generic duplicate; unchanged `PROJMUX_FOCUS_DEBUG` output; and seeded summary/body/tag/group/title/path/UUID plus support-report negative fixtures.
187
222
  - `make test` also covers Operational diagnostics Phase 5 AI watcher/ingest adoption: the closed provider/event-kind/result/failure schema, provider-specific kind catalogs, result/failure tuple compatibility, and cross-family rejection; watcher start plus pane-gone/hook-active terminal ownership, actual launch-failure classification, per-run tuple coalescing, and no per-poll records; Codex/Claude/Antigravity malformed, read, oversized, unmatched, unsupported-event, and route-error projection plus tmux-bell valid, blank, and pane-not-found coverage; zero common AI events for normal state/notify/known-quiet/deduped ingress while Phase 4 notify retains its transition; legacy `ai-ingest.log` producer, `diagnostics agent-hook`, bounded retention, and support count-consumer parity; append-failure best-effort ownership with no generic duplicate; and seeded prompt/tool/path/UUID/conversation/session/title negative fixtures for operations and support output.
188
- - `make test` also covers Operational diagnostics Phase 6 resource adoption and legacy review: exact closed resource source/result/failure tuples and cross-family rejection; production app graph wiring through Resource Inspector lifecycle → tmux inventory/project discovery/procfs attribution; unavailable, partial, stale, collection error, and actual context-deadline scan-budget fixtures; normal sample/refresh zero volume, persistent-transition coalescing, silent recovery plus anomaly re-entry, and append-failure best-effort UI/CLI preservation; separate host-only status sampler zero ownership; operations text/JSON and error-only support projection with partial/stale omission; seeded CPU/MEM/PID/process/path/title/command/UUID/session/error text negative fixtures; common journal retention/rotation regression; and the file/surface legacy diagnostic inventory without breaking removal.
223
+ - `make test` also covers Operational diagnostics Phase 6 resource adoption and legacy review: exact closed resource source/result/failure tuples and cross-family rejection; production app graph wiring through Resource Inspector lifecycle → tmux inventory/project discovery/procfs attribution; unavailable, partial, stale, collection error, and actual context-deadline scan-budget fixtures; normal sample/refresh zero volume, persistent-transition coalescing, silent recovery plus anomaly re-entry, and append-failure best-effort UI/CLI preservation; separate host-only status sampler zero ownership; operations text/JSON and error-only support projection with partial/stale omission; seeded CPU/MEM/PID/process/path/title/command/UUID/session/error text negative fixtures whose oracle inspects non-time semantic fields rather than whole serialized JSON, including a deterministic `.100` timestamp false-positive guard and real path/PID field fail-closed fixtures; common journal retention/rotation regression; and the file/surface legacy diagnostic inventory without breaking removal.
189
224
  - `make test` also covers the Phase 1 explicit redacted support report: complete preview-before-write ordering, deterministic private archive entries and manifest schema/redaction metadata, Doctor schema-version-1 structural reuse with closed static diagnostic allowlists, strict no-repair bounded operations reads, config-presence-only and AI-ingest count-only projection, exhaustive seeded free-text/path/routing/content negative redaction, missing/corrupt/unreadable source reasons, collision/partial-temp safety, and report success/error exclusion from legacy-hook migration and operational outcomes.
190
225
  - `make test` also covers welcome revisit policy: legacy `skip_version` readability without shell suppression, `s` Skip until next vs Enter continue, Settings > About > Welcome native viewer, best-effort shell update cache refresh, and attach-popup no-duplicate/no-op behavior.
191
226
  - `make test` also covers `projmux quit` action-picker rows, cancel/close no-op behavior, explicit quit of only app-owned tmux runtimes marked by `@projmux_app=1`, missing/default runtime no-ops, dispatcher wiring, and `Settings > About > Quit projmux` routing through the same picker before any shutdown side effect.
@@ -240,7 +275,7 @@
240
275
  - `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.
241
276
 
242
277
  - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 9 exit reconciliation consumes a generation-safe termination receipt and one exact runtime absence in a single lifecycle transition. `internal/core/metadata` adds `ProjectTermination`, the one transition body, plus the `TerminationSourceReconcile` provenance, the `AgentExitUnknown` exit, and a distinct `status.reason` clause per classification. Its tests pin the whole matrix from the evidence side -- an explicit control action is `intentional`/`Offline`, a supervised exit 0 is `normal`/`Offline`, exit 42 and a signal are `abnormal`/`Failed`, and an absence with no receipt is `unknown`/`Offline` -- with the released Agent asserted on every row for a cleared `paneRef`, a preserved `status.sessionRef`, a mirrored receipt that survives the deleted Pane resource, and a registry that still validates. Idempotence is asserted as two independent properties: the absence-with-no-receipt case *records* an `unknown` receipt so a second pass finds it already stored, and running the projection again leaves the registry byte-identical for an agent Pane with a receipt, an agent Pane without one, and a shell Pane. The guards get their own cases: an event quoting a replaced generation leaves the current binding and the whole registry untouched; an Agent-owned Pane the Agent no longer binds after a resume receives the evidence and nothing else, with the resumed binding still `Running`; a shell Pane's runtime loss keeps the resource, adds `MissingRuntime`, and adds the evidence; a Pane with no activation generation at all -- every Project session's adopted first pane -- converges on `unknown` and stops being re-projected; recorded intent still wins over the signal a deliberate delete causes; and a nine-row pairing table plus a `Validate` leg prove `unknown` is writable only by `source: reconcile` and that the reconciler may write nothing else, so a hand-edited registry cannot bypass it. `internal/app` owns the dirty-event seam: a narrowed event reconciles exactly the Pane it names and leaves another dead Pane's evidence unwritten, a stale-generation event opens **zero** transactions, a repeat pass opens zero and reports why, and both trigger paths -- the reconciler's observation step and the one-shot -- are asserted to classify the same exit 42 into the same phase, reason, and preserved exit code, because they share one transition body. Exact-host containment is proven over the routed two-server fake for both `-L` and `-S`: the sibling mirrors the dead Pane's uid on a live pane of its own and reuses the same `%N` handle space, and the assertion is that the sibling receives zero calls while the event host's absence still decides the outcome. A forbidden-verb audit over the recorded argv proves the pass issues no `split-window`, `respawn-*`, `kill-*`, `send-keys`, `set-option`, or focus-moving call, so an observation can never become an activation; the core-layer half asserts no replacement Pane is materialized for an Offline Agent. Fail-closed is pinned for both the preflight and the in-lock observation, each leaving the registry byte-identical and returning no error. The read projection is asserted with zero writes throughout: `describe agent|pane` renders classification, source, observed instant, exit code or signal, Pane ref and generation as their own rows and renders none at all for a live resource, `get pane|agent` renders a `TERMINATION` column of `<classification>/<source> [exit=|signal=] <age>` that stays blank for a live row (the four whole-table goldens and the display-cell width suite move with the added column), the Registry-first navigation row carries a *copy* of the receipt and renders it in its own column, a navigation refresh is re-pinned as single-socket and mutation-free, and a pending intent receipt on a still-live Pane survives `get` in three output modes plus `describe` with the Agent left `Running`. The integration smoke reconciles real shell and provider exits on isolated exact sockets and reads the phases and receipts back out of an isolated Registry: exit 0, exit 42 and a signal for the three provider stubs, an external `kill-pane`, a `SIGKILL`ed supervisor (the one case that is deterministically `unknown`, from `source: reconcile`), a preserved shell Pane carrying `MissingRuntime` plus its evidence, a write-free repeat after every case, and a restart leg on both an app-owned and a standalone host whose assertions are deliberately narrow -- an unreadable server may not attract *reconciler* evidence even though the same pass honestly reprojects session liveness, `kill-server` SIGHUPs the panes so a surviving supervisor's real `abnormal signal=HUP` receipt is accepted alongside `unknown`, and the restarted server must end with zero mirrored pane uids because an observation is not an activation authority. A sibling server carrying the same mirrored Pane uid on a live pane of its own is byte-identical afterwards and changed no outcome. The e2e smoke proves the user-visible property the integration legs cannot: with the generated config installed on an exact socket, a provider exiting 42 reaches `Failed` and one exiting 0 reaches `Offline` **with no projmux command run to make it happen** -- convergence is polled through `describe`, which reconciles nothing -- and it repeats the read-projection, restart/no-autostart, and sibling-containment legs against a real server.
243
- - `make test` / `make test-e2e`: Registry-first control plane Phase 10 scopes an explicit singular reference to the active Project. A `metadata.name` is unique inside its owner scope and never across the Registry, so `projmux describe window zsh` typed inside a managed Project compared one word against every Project at once; the measured baseline on the installed binary was `--window zsh matched 9 windows, want exactly one`. `describe window|pane|agent <ref>` and `rename window|pane <ref>` now fill `selector.Query.DefaultRoot` with the active Window's Project owner, which is the same `windowScope` choice point the plural `get windows|panes|agents` default uses -- the two differ in which invocations opt in, and Phase 13 later widens only the plural default to both managed root kinds. `internal/app` pins the applied matrix in both directions: each of the five adopting `<verb, kind>` pairs resolves a name that also exists in another Project to the active Project's resource with exactly **one** observation, while `describe|rename project`, `rename agent`, `delete`, `rebind`, and `agent resume` stay whole-registry with **zero** observations, and an explicit `-p beta` resolves beta with **zero** observations. The narrowing is proven not to be a target rule: two same-named Panes inside the one Project stay the bounded `matched 2 panes, want exactly one` ambiguity with no beta candidate in the listing rather than being broken by the active Window, a `--selector` label is scoped the same way, and a `uid:` reference belonging to another Project is `matched no windows` rather than a cross-Project hit. Outside tmux every one of those argv shapes keeps its byte-identical whole-registry result and ambiguity. Inside tmux an underivable namespace is its own refusal -- `resolve project scope: a resource reference was given inside tmux and ...; the active Project namespace is undecidable, ...` -- pinned verbatim for a missing `@projmux_window_uid`, a mirrored Window uid the Registry does not hold, and a Window with no owning Project, each asserted to be a usage error with 0 bytes on stdout, 0 writes, and a byte-identical registry snapshot on the mutation half; it is deliberately not the active-target refusal, whose "no selector was given" opening would be false here. E2E runs the same three shapes on both host modes against real tmux: the standalone inherited-socket client builds Window `ns-one` and Pane `shared` in alpha *and* beta plus a second alpha `shared` under `ns-two`, then proves the reproduction resolves one Window, the intra-Project ambiguity survives, `-w`/`-p` disambiguate, a foreign uid is a no-match, `rename window` moves only the active Project's Window, and unsetting the live `@projmux_window_uid` refuses instead of widening; the app-owned `-L projmux` host repeats the reference/explicit-`--project`/foreign-Window trio read-only against its own fixture. Singular `uid:` namespace semantics, `delete`, and the empty-selector active target remain unchanged.
278
+ - `make test` / `make test-e2e`: Registry-first control plane Phase 10 scopes an explicit singular reference to the active Project. A `metadata.name` is unique inside its owner scope and never across the Registry, so `projmux describe window zsh` typed inside a managed Project compared one word against every Project at once; the measured baseline on the installed binary was `--window zsh matched 9 windows, want exactly one`. `describe window|pane|agent <ref>` and `rename window|pane <ref>` now fill `selector.Query.DefaultRoot` with the active Window's Project owner, which is the same `windowScope` choice point the plural `get windows|panes|agents` default uses -- the two differ in which invocations opt in, and Phase 13 later widens only the plural default to both managed root kinds. `internal/app` pins the applied matrix in both directions: each of the five adopting `<verb, kind>` pairs resolves a name that also exists in another Project to the active Project's resource with exactly **one** observation, while `describe|rename project`, `rename agent`, `delete`, `rebind`, and `agent resume` stay whole-registry with **zero** observations, and an explicit `-p beta` resolves beta with **zero** observations. The narrowing is proven not to be a target rule: schema v4 scopes same-kind name uniqueness to the owning root, so a positional name inside one Project resolves the unique root-wide resource while a `--selector role=shell` label match over two Panes of that Project stays the bounded `matched 2 panes, want exactly one` ambiguity with no beta candidate in the listing rather than being broken by the active Window, and a `uid:` reference belonging to another Project is `matched no windows` rather than a cross-Project hit. Outside tmux every one of those argv shapes keeps its byte-identical whole-registry result and ambiguity. Inside tmux an underivable namespace is its own refusal -- `resolve project scope: a resource reference was given inside tmux and ...; the active Project namespace is undecidable, ...` -- pinned verbatim for a missing `@projmux_window_uid`, a mirrored Window uid the Registry does not hold, and a Window with no owning Project, each asserted to be a usage error with 0 bytes on stdout, 0 writes, and a byte-identical registry snapshot on the mutation half; it is deliberately not the active-target refusal, whose "no selector was given" opening would be false here. E2E L07 `binding-convergence` runs the same three shapes on both host modes against real tmux: the standalone inherited-socket client builds Window `ns-one` and Pane `shared` in alpha *and* beta, a second `role=shell` alpha Pane `sibling` under Window `ns-two`, and an alpha Window also named `shared` -- root-scoped uniqueness refuses a second alpha `pane/shared`, so the bounded ambiguity is reached through the shared label and the cross-kind coexistence witness replaces the duplicate name. It then proves the reproduction resolves one Window, `describe window shared` and `describe pane shared` each resolve their own exact uid inside the one root, the intra-Project ambiguity survives as `resolve pane: --selector role=shell matched 2 panes, want exactly one` listing `owner=project/alpha window/ns-one` and `owner=project/alpha window/ns-two` and never `project/beta` while the identical label outside tmux is `matched 3 panes` including `owner=project/beta window/ns-one`, `-w` narrows that same label match to one Pane per Window and `-p beta` reaches beta's `shared`, a foreign uid is a no-match, `rename window` moves only the active Project's Window, and unsetting the live `@projmux_window_uid` refuses instead of widening; the app-owned `-L projmux` host repeats the reference/explicit-`--project`/foreign-Window trio read-only against its own fixture. Singular `uid:` namespace semantics, `delete`, and the empty-selector active target remain unchanged.
244
279
  - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 11 lifecycle trigger convergence and legacy seam retirement. **One entrypoint.** `internal tmux reconcile-bindings` and `internal tmux release-dead-agent-panes` are retired and replaced by one hidden route, `internal tmux converge --socket-path <path> --session <id> --reason <config-apply|runtime-created|runtime-exited>`, which `config apply` and all four generated lifecycle hooks reach; the reason set is one closed enum the route parser and the hook renderer both read, so a spelling a config can emit is one the binary accepts, and a malformed target, a missing reason, an unknown reason, and a positional argument each refuse with the controller reached zero times. A trigger carries no pane id and no generation on purpose -- `after-kill-pane` fires with an empty `#{hook_pane}` -- and the trigger-inventory test reads both generated configs plus the live `show-hooks -g` output to prove every hook reaches the entrypoint, that the creation hooks stay synchronous while the exit hooks stay backgrounded, and that neither retired route survives anywhere. **One pass.** A pass is one locked reconciliation followed by an exit-half reobservation, in that order: the projection has to run after the same pass's binding writes or it files an unknown termination against every Pane the pass was about to bind, which a stage-order test states directly. `config apply` still converges only after a successful `source-file`, on the exact `-L` target it reloaded, with no hook session because an apply is not caused by a create; `--no-reload` and every preflight failure remain strict no-touch paths. **At most one worker.** The dirty-event log is one file per event under `<state>/projmux/controller/<key>.events/`, so marking needs no lock and no counter can be lost, and the worker lease is an advisory whole-file `flock` the kernel releases on process death -- an abandoned lock file is acquirable immediately, which is what makes crash recovery need no staleness rule. Coalescing is measured rather than described: eight producers arriving during one pass cost two convergence bodies and one worker, every loser exits zero with its event still unacknowledged, the loop repeats until a pass writes nothing and nothing is pending, and the repeat bound stops a producer that marks faster than a pass completes without claiming convergence. The e2e burst additionally proves no `acquire lock: exhausted` appears anywhere, which is the failure mode the pre-Phase fleet of per-event workers produced, and that a `get` read starts no worker and leaves no event or lease behind. Two spellings of one server get two keys and therefore two leases, and a sibling socket is never blocked by another's. **Split UI convergence.** `internal agent-pane launch-default` and the `Alt-7`/resume pickers produce a canonical create intent -- provider, placement, and for a resume the provider-normalized conversation id -- rendered as the exact argv an operator would type, so the projection is asserted as a pure function rather than through whatever the create did next; the resume branch has no public spelling and reaches the shared Agent body with one extra field, which keeps `create` free of a public `--resume`. The saved mode is the only hidden state the UI reads, a mode naming no launch opens its popup, and a saved or selected provider Settings has disabled refuses before the intent exists with zero mutations. The legacy `ai split` route, its `--agent`/`--force-agent`/`--print-pane-id` flags, the layout/context/title-watcher helpers only it used, and `mux.SplitWindow` itself are all deleted, and a source audit over `internal/app` proves the materializer is the only place a `split-window` argv exists -- tmux's own pane-context-menu entries excepted, which are menu items rather than a projmux-issued split.
245
280
  - `make test` / `make test-integration` / `make test-e2e`: Popup-origin split anchor. A `display-popup -E` job inherits `$TMUX` and no `$TMUX_PANE`, so after the split UI moved onto the canonical create intent every terminal action of the two split pickers -- provider row, `shell` row, resume row, and the resume picker's `new` row -- reached `create` with nothing to derive a scope from and refused with the `--project` usage error (exit 2). **The origin pane travels on the intent.** `agentPaneIntent.anchorPaneID` carries the pane the keypress came from, `createCommand.withAnchorPane` makes it that invocation's active target through `anchoredActiveTargetLookup`, and Project/Window/anchor resolve through the same identity mirror a pane-hosted invocation reads. Named regressions fix both halves in one table: an anchored intent with no `$TMUX_PANE` creates exactly one Pane in the origin pane's Window, while an anchor-free intent, a whitespace anchor, an anchor that leaked outside any client, and an anchor carrying no managed identity all keep the exact refusal and exit 2 with zero Registry writes and zero tmux calls. A typed `create pane|window|agent` inside the same popup runtime -- origin pane present in the environment -- keeps its refusal byte-for-byte, the pane-hosted `launch-default` producer states no anchor and probes nothing, and a source audit pins `TMUX_SPLIT_TARGET_PANE` to one reader in the split UI so it cannot become an implicit scope for the read, rename, or delete verbs.
246
281
 
@@ -316,6 +351,47 @@
316
351
  command, path, permission, and request content out of Registry, tmux, notify,
317
352
  diagnostics, support, and Archive sinks.
318
353
 
354
+ - `make test`: Exact Codex turn-start admission reads one fresh, content-free
355
+ lifecycle snapshot through the bound control epoch immediately before the
356
+ provider mutation. `TestExactAgentControlStartFreshLifecycleAdmissionTable`
357
+ pins the cached/fresh matrix, exact `turn-in-progress` and
358
+ `turn-state-unavailable` tokens, read-before-start ordering, cross-thread and
359
+ structurally inconsistent snapshot refusal, private-detail exclusion, and
360
+ zero mutation on every refusal.
361
+ `TestExactAgentControlStartRechecksBindingAfterFreshLifecycleRead` keeps the
362
+ Agent/Pane/runtime/generation/thread binding current across the fresh read,
363
+ while `TestExactAgentControlFreshTerminalReconcileClearsCachedApproval`
364
+ proves that an idle/terminal snapshot repairs stale turn and approval state
365
+ before the same request starts exactly one new turn. Start-specific behavior,
366
+ interrupt, approval, old-epoch, and canonical generation-consumer fences are
367
+ unchanged by the steer admission slice below.
368
+
369
+ - `make test`: Exact Codex steer admission reuses the content-free lifecycle
370
+ read and accepts only the cached turn that a fresh snapshot still proves is
371
+ the same exact active turn. `TestExactAgentControlSteerFreshLifecycleAcceptanceTable`
372
+ pins the active/idle/terminal/different-turn/unavailable matrix plus exact
373
+ lifecycle-read-before-steer order and one-or-zero steer wire count;
374
+ `TestExactAgentControlSteerDifferentTurnReconcilesWithoutRetargetingCurrentRequest`
375
+ proves reconciliation cannot retarget the current request and only an
376
+ explicit retry may address the newly observed turn.
377
+ `TestExactAgentControlSteerPostReadFenceMismatchIsTurnStateUnavailable` and
378
+ `TestExactAgentControlSteerOldOrCrossEpochReadsAndWritesZero` keep the
379
+ Agent/Pane/runtime/generation/thread/control-epoch fences closed before the
380
+ provider write. `TestBodyFreeSteerSuccessIsProviderAcceptanceWithDeliveryUnconfirmed`
381
+ kills a body-free-success-to-delivery-confirmed mutant by requiring the
382
+ structured `acceptance=provider`, `delivery=unconfirmed` receipt, while
383
+ `TestAgentControlCLISteerExactAcceptanceRefusalOutputAndExit` fixes the exact
384
+ CLI receipt, refusal tokens, and exit 0/1 mapping.
385
+ `TestAgentControlCLISteerRejectsMissingOrConfirmedDeliveryReceipt` prevents a
386
+ missing or fabricated delivery acknowledgement from reaching successful CLI
387
+ output. `make test-e2e E2E_SCENARIO=C01` runs the maintained
388
+ `test/e2e/codex-lifecycle.sh` replacement-sibling steer: it boundedly retries
389
+ only `turn-state-unavailable` with stdout and the exact sibling steer ledger
390
+ held at zero, then requires one qualified provider write and the exact
391
+ acceptance/unconfirmed-delivery receipt. Retry time is pacing only, never
392
+ lifecycle authority. Start, interrupt, approval, payload privacy, TUI
393
+ input/resume, and upstream delivery acknowledgement remain unchanged.
394
+
319
395
  - `make test` / `make test-integration`: Codex public turn control live-binding
320
396
  compatibility uses one strict six-field tmux frame. Unit tests accept only
321
397
  literal `\037` and raw unit-separator spellings without generic escape
@@ -376,12 +452,25 @@
376
452
  - `make test`: Codex endpoint broker core Phase 1 adds the dark `internal/integrations/agents/codexbroker` package: one shared app-server connection, many fenced per-thread bindings, and a reconnect supervisor that reaches the endpoint only through an injected opener whose production default is the Phase 0 attach seam. `TestConcurrentBindingsRouteOutOfOrderResponsesAndInterleavedEventsToTheExactThread` runs two bindings over one in-memory endpoint whose control responses come back in the reverse order to the requests and whose notifications interleave: each response reaches exactly the caller that issued it, each notification reaches exactly the thread it names, and the shared connection ends with one connect and zero disconnects. `TestSlowBindingBacklogOverflowRevokesOnlyThatBindingAndKeepsTheSharedConnection` overruns one binding's bounded queue by exactly one event and requires `resync-required` on that binding alone, its stream closed after the events that fit rather than silently gapped, and the fast binding, the shared connection, and the open-attempt count untouched. `TestLongDisconnectReconnectsWhileABindingRemainsAndFencesTheOldEpochOut` injects a 3.75s outage through the fake clock (250ms + 500ms + 1s + 2s of capped jittered backoff across four refused attaches), reconnects while the binding remains, and then proves the retired epoch inert: its event is dropped as stale with zero fan-out and a fence minted on it refuses with `stale-connection-epoch` and zero requests on either connection, while the current fence still writes; unbinding the last binding freezes the open-attempt count and leaves no scheduled wait. `TestReconnectSnapshotAndLiveEventsMergeIntoOneOrderBehindTheBarrier` closes the snapshot/live race matrix - live events entirely before the snapshot, straddling it, and entirely after it - as one continuous snapshot-then-buffered-then-live order, and holds control authority shut for the whole barrier: `ControlAuthority` refuses `control-not-open` and a mutation attempted there writes zero bytes and enters no ledger row. `TestBarrierSnapshotFailureRevokesOnlyTheRefusedThread` splits the barrier's fault surface, so a live endpoint refusing one thread revokes only that binding with `snapshot-unavailable` while a disconnect during the snapshot leaves the binding intact for the next epoch's barrier. `TestMutationAtADisconnectBoundaryEndsIndeterminateWithZeroResends` pins the write ledger: an in-flight mutation cut by a disconnect ends `indeterminate` with one recorded attempt, the replacement connection receives zero requests, the resend counter stays at zero, and an endpoint that answers with an error stays classified `refused` rather than unknown. `TestThreadlessNotificationFansOutToNoBindingAndAttributesToNoAgent` runs the threadless fixtures - no params, a payload with no thread id, a blank thread id, and malformed params - through both the pure `AttributeNotification` seam and the live broker for zero binding fan-out and zero Agent attribution, with a following attributed event proving the stream was reachable throughout. `TestApprovalLeaseIsAnsweredOncePerConnectionAndDiesWithIt` binds the raw JSON-RPC request id, the thread, and both epochs into one lease, spends it once, refuses the duplicate, a forged thread, and an id-less lease, and shows connection replacement revoking it with zero answers on the new wire. `TestBrokerLifecycleRefusalsAndEpochsAreClosed` closes the construction and lifecycle table - unknown endpoint key, missing opener, blank thread id, duplicate bind, bind after close - and requires a rebound thread to take a fresh binding epoch that refuses the retired fence. `TestReconnectBackoffStaysInsideItsCappedJitterBounds` holds every attempt and jitter pair inside the half-base floor and the five-second cap, monotonically. `TestConcurrentBindUnbindReconnectAndSlowConsumerAreRaceAndLeakFree` runs binds, unbinds, connection replacement, delivery, and deliberate slow consumers together under `-race` and requires an idempotent concurrent `Close` with no goroutine left behind. `TestBrokerRetainsNoProviderContent` audits the retained surface as closed field sets, keeps every refusal, outcome, and origin code a bare content-free token, renders a typed refusal as its code alone over a path-bearing cause, and requires a prompt-bearing mutation and a payload-bearing event to leave no byte in the diagnostics or the ledger. `TestBrokerImportsNoRegistryTmuxOrCLIPackage` is the reverse-dependency audit: the package's only in-repo import may be `codexappserver`, so no Registry, tmux, or CLI package can reach back into the endpoint layer.
377
453
 
378
454
  - `make test`: Codex endpoint broker runtime Phase 2 gives the dark `internal/integrations/agents/codexbroker` package an OS-level runtime: a per-state-domain singleton host, an owner-private discovery contract, and an authenticated content-free local IPC protocol, plus the minimal hidden `internal codex-broker serve|probe` entrypoint in `internal/app` that hosts one and reaches one. `TestConcurrentClientsShareOneRuntimeProcessAndOneUpstreamConnection` starts four separate client processes, holds them at one barrier, and releases them together against one empty state domain: the ledger the runtime shares with the parent must show exactly one published runtime, exactly one upstream connection, and one snapshot per client, with every client reporting the same runtime identity, because the socket bind is the singleton mutex and the exclusive startup lock keeps a losing starter from reclaiming the winner's socket. `TestDiscoveryCredentialAndPermissionFailuresBindNothingAndWriteNothing` runs the adversarial matrix - a wrong credential, an absent credential, a foreign endpoint key, a malformed first frame, a record readable beyond its owner, a record replaced by a symlink, a record naming another endpoint, no record at all, and a relative or empty state domain - and requires zero bindings, zero upstream requests, and zero upstream snapshots after all of them; an unshared protocol window gets a runtime of its own because that refusal also starts a drain. `TestStaleRuntimeArtifactsAreReclaimedOnlyByExactOwnerProof` is the ownership fixture: a live runtime's socket is reused rather than replaced with a typed `host-live`, a socket left behind by a killed runtime is removed, a regular file or a symlink at the socket path is refused as `discovery-untrusted` and left byte-identical, and the last row varies the record's pid across live, absent, and foreign values to require an identical decision every time, because a reusable pid may not be ownership authority. `TestRuntimeCrashRevokesTheOldEpochBeforeANewRuntimeRestoresAuthority` kills a real runtime process out from under a bound client: the binding revokes with `host-unavailable` before anything can present its fence, its stream closes, `ControlAuthority` and `Submit` both refuse with zero added upstream writes, and `Ensure` then proves the abandoned socket stale, starts a replacement with a different runtime identity, and restores authority only through a fresh snapshot, where a foreign epoch still refuses and writes nothing. `TestRestoredAuthorityOpensOnlyBehindTheSnapshotBarrier` holds the runtime's snapshot open and requires a bound-but-unopened client to refuse `control-not-open` with zero endpoint calls until the snapshot crosses the socket. `TestLastBindingRemovalIdlesTheRuntimeOutAndLeavesNoArtifact` keeps a held binding alive well past the idle bound, then requires the runtime to stop after the last binding is removed, remove exactly the socket and record it published, and leave no goroutine behind. `TestIncompatibleClientDrainsTheRuntimeWithoutSeveringLiveBindings` is the rolling binary replacement matrix: overlapping version windows negotiate down and reuse the running runtime in both directions, while a disjoint window is refused by name with `drain-required` without replacing the runtime or its socket, the live binding keeps delivering and keeps control authority through the drain, a new bind is refused so the drain stays finite, and only after the last binding drains does a replacement start under exact owner proof with a new runtime identity. `TestRuntimeStateAndDiagnosticsRetainNoProviderContent` drives a prompt-bearing mutation and a payload-bearing event across the socket and then searches every file the runtime persisted in its state domain for them, and pins `HostStats` as a closed set of tokens and counters. `TestRuntimeProtocolNegotiationAndFrameBoundsAreClosed` closes the negotiation table and requires an oversized frame to be refused on write with zero bytes emitted and on read. `TestInternalCodexBrokerServePublishesAndIdlesOutWithoutTouchingAnyDaemon` runs the real entrypoint against an isolated state domain: it publishes an owner-only record, reports readiness, and idles out without reaching any Codex daemon, because the broker opens its upstream connection only once a binding exists. `TestInternalCodexBrokerRouteStaysHiddenPlumbingWithNoPublicSurface` is the scope audit - no public route gains a broker spelling, the runtime stays inside the hidden `internal` namespace and out of the canonical command projection, every malformed invocation is a usage error, and `probe --no-start` refuses with `host-unavailable` while creating nothing outside the broker artifact directory. `TestCodexBrokerStateDomainFollowsTheProjmuxStatePaths` pins the singleton to the installation's state directory rather than to a working directory or a pane.
379
- - `make test`: Codex native lifecycle and control cutover Phase 3 makes the endpoint broker the product's endpoint producer. `TestBrokerBoundAgentsShareOneUpstreamConnectionWithIndependentBindingEpochs` binds a prompted-create Agent and a stored-resume Agent through the real runtime host and requires one upstream connection between them, one shared connection epoch, two different binding epochs, and interleaved events that reach only the thread that named them. `TestBrokerThreadlessAndForeignEventsReachNoExactAgentBinding` runs the ambiguous fixtures - no thread id, a blank one, a foreign thread, and a foreign thread sharing the bound Agent's working directory - and requires the exact binding to receive none of them while a following attributed event proves the stream stayed live. `TestBrokerEpochRotationFencesTheOldEpochOutWithZeroEndpointWrites` replaces the connection under a live binding: the retired epoch's stream closes, its steer and its approval answer are both refused, the replacement connection records zero steer requests and zero answers from it, the binding epoch survives the reconnect unchanged, and the replacement epoch starts a turn normally. `TestBrokerRuntimeLossEndsTheEpochAndRefusesEveryLateMutation` takes the runtime away from a bound client and requires the epoch to end before any caller can present its fence, every late mutation and answer to be refused with zero endpoint writes, and a reopen against an unpublished runtime to refuse rather than start one. `TestBrokerDisconnectEndsTheEpochBeforeAnyReplacementBarrier` covers the promptness half of the disconnect contract: a binding survives an outage so the broker can keep reconnecting, so the binding's out-of-band suspension - not the ordered stream, which has nothing to say until the next barrier - retires the live epoch immediately, refuses its late mutations with zero endpoint writes, and keeps the stale barrier from being re-served while the endpoint is gone. `TestBrokerEpochBacklogOverflowEndsOnlyThatEpochStream` overruns one Agent's bounded stream and requires that Agent alone to be cut to a resync while its sibling on the same shared connection keeps receiving its own events. `TestBrokerApprovalLeaseAnswersTheExactRawRequestExactlyOnce` carries an inbound server request through the broker to the app layer with its byte-faithful raw id and its normalized rendering intact, decodes it with the existing envelope decoder, answers it once, and requires the repeat to be refused with exactly one entry in the endpoint's answer ledger. `TestBrokerEpochReadsTheExactLifecycleSnapshotThroughItsOwnFence` pins the control epoch's state input to the same fence its mutations use. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` and `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` are the single-producer and persistent-reconnect audits: the producer decision is total and never yields both, and a producer that owns its own reconnect keeps recovering well past the legacy six-attempt budget without ever publishing the terminal `reconnect-exhausted` fallback, while that budget stays byte-identical for the producer that still needs it. `TestBrokerCutoverPreservesTheHookFallbackVocabulary` is the compatibility audit for the authority sources, the hook-suppression rule, and the pre-mutation convergence to `provider-hook`. On the endpoint side, `TestNativeCreateAndResumeAttachToTheExactCurrentUnmanagedEndpoint` runs the real create and resume paths against a fake Codex whose every argv is recorded and requires an exact-current endpoint the official manager does not own to accept one `thread/start`, one `turn/start`, and one `thread/resume` with a daemon start/stop/restart/kill/bootstrap/remote-control/login/config argv count of zero, while `TestNativeCreateAndResumeKeepSafeFallbackOnEveryRefusedAttachRow` keeps a skewed and an ownership-unknown endpoint refusing before the connection opens, as a safe fallback carrying its typed reason and with zero thread or turn requests. `TestEndpointAttachAuthorityIsTheOnlyWidenedProductRow` walks the availability x ownership x version table behind the product's own source and availability preconditions and requires exactly one row to differ from the retired native-action gate: the ready, exact-current, unmanaged one. `TestInstalledIsolatedBrokerNativeBindingSmoke` is opt-in through `PROJMUX_CODEX_CUTOVER_SMOKE_ROOT` with a matching contained `CODEX_HOME`, an isolated state domain, and inherited `TMUX`/`TMUX_PANE` stripped, and it is the only check that runs the cutover against a real Codex app-server: a direct `codex app-server --listen unix://` under that contained `CODEX_HOME`, which upstream reports as running with no daemon backend. It requires that endpoint to be unmanaged, exact-current, attach-allowed, and to carry no daemon lifecycle authority; runs the product's own prompted create there so the thread's rollout materializes; binds that exact thread through the product session over a runtime it publishes itself; starts one turn through the resulting broker epoch and requires the epoch's own fenced snapshot to show that exact turn in progress before steering it through the same wire; interrupts it; and then requires the runtime to leave no artifact behind. Every `codex` argv the whole path ran is recorded by a PATH shim the test installs, and the run fails unless all of them are `app-server proxy` or `app-server daemon version`, which is the zero-daemon-lifecycle-mutation half of the acceptance against a real endpoint rather than a fake one.
380
- - `make test`: Codex interactive-only mode and native-required prompted create makes an unproven native authority a typed refusal instead of a silent plain-CLI Agent. `TestPromptedNativeCodexCreateIssuesOneTurnAndNeverRepeatsThePromptInPaneArgv` and `TestStartDefaultThreadEmptyAndPromptedRequestCounts` pin one `thread/start` plus one `turn/start` per payload with the prompt absent from Pane argv and from the Registry. `TestUnavailableNativeCreateRefusesInsteadOfSilentlyCreatingAPlainAgent` and `TestUnavailableNativePickerResumeRefusesInsteadOfRebindingOntoTheRolloutLane` close the pre-mutation refusal matrix for the two Agent-creating routes at zero Registry and zero tmux mutation, `TestIndeterminateNativeCreateRefusesASecondLaneAndWritesZero` keeps the post-mutation row refusing without offering any second lane, and `TestUnavailableNativeResumeKeepsTheStoredConversationOnTheProviderResumeLane` holds the boundary at `agent resume`, which creates no Agent and keeps its safe fallback to one provider resume of the stored conversation. `TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent` pins flag acceptance, manifest and rendered help parity, and byte-equal output across `create agent --provider codex --interactive-only` and `create codex --interactive-only`, plus the exit-2 non-Codex-provider refusal at zero transactions. `TestStartDefaultThreadDeliversAdditionalRootsOrFailsClosed` is the roots capability matrix: the exact cleaned list on a negotiated connection, a fail-closed typed refusal when the endpoint cannot carry it, and nothing on the wire for an empty list. `TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality` refuses a prompted multi-Window native create with zero threads, Panes, Registry writes, and tmux objects while keeping the `--interactive-only` and empty-prompt fan-out cardinality. `TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate` and `TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate` guard the deferred empty-prompt boundary and the unchanged Claude/Antigravity hook activation and fan-out contract, and `TestCodexNativeLaunchOutcomeTableIsClosed` keeps the closed outcome table describing exactly these rows.
455
+ - `make test`: Codex native lifecycle and control cutover Phase 3 makes the endpoint broker the product's endpoint producer. `TestBrokerBoundAgentsShareOneUpstreamConnectionWithIndependentBindingEpochs` binds a prompted-create Agent and a stored-resume Agent through the real runtime host and requires one upstream connection between them, one shared connection epoch, two different binding epochs, and interleaved events that reach only the thread that named them. `TestBrokerThreadlessAndForeignEventsReachNoExactAgentBinding` runs the ambiguous fixtures - no thread id, a blank one, a foreign thread, and a foreign thread sharing the bound Agent's working directory - and requires the exact binding to receive none of them while a following attributed event proves the stream stayed live. `TestBrokerEpochRotationFencesTheOldEpochOutWithZeroEndpointWrites` replaces the connection under a live binding: the retired epoch's stream closes, its steer and its approval answer are both refused, the replacement connection records zero steer requests and zero answers from it, the binding epoch survives the reconnect unchanged, and the replacement epoch starts a turn normally. `TestSessionReplacementSnapshotForwardsPendingSuspensionFirst` and `TestBrokerObserverReplacementSnapshotConsumesPriorSuspensionEdge` pin both sides of the reconnect transport: the runtime forwards the causally prior suspension before a replacement snapshot, and an observer that selects an already-buffered snapshot first consumes that suspension before rotating, so a late edge cannot retire the new sibling epoch. `TestBrokerRuntimeLossEndsTheEpochAndRefusesEveryLateMutation` takes the runtime away from a bound client and requires the epoch to end before any caller can present its fence, every late mutation and answer to be refused with zero endpoint writes, and a reopen against an unpublished runtime to refuse rather than start one. `TestBrokerDisconnectEndsTheEpochBeforeAnyReplacementBarrier` covers the promptness half of the disconnect contract: a binding survives an outage so the broker can keep reconnecting, so the binding's out-of-band suspension - not the ordered stream, which has nothing to say until the next barrier - retires the live epoch immediately, refuses its late mutations with zero endpoint writes, and keeps the stale barrier from being re-served while the endpoint is gone. `TestBrokerEpochBacklogOverflowEndsOnlyThatEpochStream` overruns one Agent's bounded stream and requires that Agent alone to be cut to a resync while its sibling on the same shared connection keeps receiving its own events. `TestBrokerApprovalLeaseAnswersTheExactRawRequestExactlyOnce` carries an inbound server request through the broker to the app layer with its byte-faithful raw id and its normalized rendering intact, decodes it with the existing envelope decoder, answers it once, and requires the repeat to be refused with exactly one entry in the endpoint's answer ledger. `TestBrokerEpochReadsTheExactLifecycleSnapshotThroughItsOwnFence` pins the control epoch's state input to the same fence its mutations use. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` and `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` are the single-producer and persistent-reconnect audits: the producer decision is total and never yields both, and a producer that owns its own reconnect keeps recovering well past the legacy six-attempt budget without ever publishing the terminal `reconnect-exhausted` fallback, while that budget stays byte-identical for the producer that still needs it. `TestBrokerCutoverPreservesTheHookFallbackVocabulary` is the compatibility audit for the authority sources, the hook-suppression rule, and the pre-mutation convergence to `provider-hook`. On the endpoint side, `TestNativeCreateAndResumeAttachToTheExactCurrentUnmanagedEndpoint` runs the real create and resume paths against a fake Codex whose every argv is recorded and requires an exact-current endpoint the official manager does not own to accept one `thread/start`, one `turn/start`, and one `thread/resume` with a daemon start/stop/restart/kill/bootstrap/remote-control/login/config argv count of zero, while `TestNativeCreateAndResumeKeepSafeFallbackOnEveryRefusedAttachRow` keeps a skewed and an ownership-unknown endpoint refusing before the connection opens, as a safe fallback carrying its typed reason and with zero thread or turn requests. `TestEndpointAttachAuthorityIsTheOnlyWidenedProductRow` walks the availability x ownership x version table behind the product's own source and availability preconditions and requires exactly one row to differ from the retired native-action gate: the ready, exact-current, unmanaged one. `TestInstalledIsolatedBrokerNativeBindingSmoke` is opt-in through `PROJMUX_CODEX_CUTOVER_SMOKE_ROOT` with a matching contained `CODEX_HOME`, an isolated state domain, and inherited `TMUX`/`TMUX_PANE` stripped, and it is the only check that runs the cutover against a real Codex app-server: a direct `codex app-server --listen unix://` under that contained `CODEX_HOME`, which upstream reports as running with no daemon backend. It requires that endpoint to be unmanaged, exact-current, attach-allowed, and to carry no daemon lifecycle authority; runs the product's own prompted create there so the thread's rollout materializes; binds that exact thread through the product session over a runtime it publishes itself; starts one turn through the resulting broker epoch and requires the epoch's own fenced snapshot to show that exact turn in progress before steering it through the same wire; interrupts it; and then requires the runtime to leave no artifact behind. Every `codex` argv the whole path ran is recorded by a PATH shim the test installs, and the run fails unless all of them are `app-server proxy` or `app-server daemon version`, which is the zero-daemon-lifecycle-mutation half of the acceptance against a real endpoint rather than a fake one.
456
+ - `make test` / installed opt-in smoke: Codex payload-free safe fallback chooses the existing managed plain-interactive lane before `Current`, `Resolve`, `thread/start`, `turn/start`, or the durable-resume barrier. `TestCodexCreatePayloadCardinalityInteractiveOnlyAndReadinessOutcomeTable` closes payload cardinality × `--interactive-only` × readiness, while `TestPayloadFreeCodexCreateUsesSafePlainFallbackAndInteractiveOnlyEquivalentLane` pins canonical/shortcut stdout and launch-argv equivalence, exactly one Running Agent/Pane, no native binding, and the `payload-free-fallback` declaration. `TestEmptyPromptCodexSplitProducersKeepOnePlainCLILane` covers saved-default, provider-picker, and direct-provider AI intents; `TestPayloadFreeCodexPlainLaunchFailureRollsBackWithoutProviderMutation` covers Registry/Pane/tmux rollback with no second provider lane; and `TestPayloadFreeFallbackDescribeAndDoctorSignalsAreContentFree` pins the activation/source plus CLI/Doctor reduced-control signal. `TestInstalledPayloadFreePlainFallbackOutcomeSmoke` strips inherited `TMUX`/`TMUX_PANE`, uses isolated XDG/Codex/tmux roots and the actual socket path, and requires one live plain Pane, one Running Agent, no session ref, zero provider-thread delta, no ambient lifecycle mutation, and exact cleanup. `TestPhase7PayloadFreeReadinessFailureIsNegativeSafetyEvidenceOnly` and `TestInstalledPayloadFreeCreateOutputClassificationRequiresFunctionalPlainSuccess` retain the old typed safe failure only as negative evidence. Prompted create remains native: `TestPromptedNativeCodexCreateIssuesOneTurnAndNeverRepeatsThePromptInPaneArgv` pins one turn and no duplicate prompt; native picker resume, stored Agent resume, generation routing, Claude, and Antigravity retain their existing tests. `--interactive-only` keeps its spelling and meaning under `TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent`, and `TestCodexNativeLaunchOutcomeTableIsClosed` keeps all functional and negative rows classified explicitly.
381
457
  - `make test`: Codex install capability guidance Phase 0 makes prompted native create refusal the primary actionable surface while Doctor and Settings consume the same typed `internal/app` authority. `TestCodexInstallCapabilityGuidanceMatrixStatesOnlyObservedFacts` closes `managed-ready`, `external-cli-only`, `cli-missing`, and `unknown`: external-only states exactly that the ordinary CLI exists and the managed standalone payload was not observed, while managed-ready and unknown never imply payload absence or reinstall, and no row infers npm/curl/brew provenance. `TestCodexInstallCapabilityGuidanceHasThreeConsumerParity` requires native create, Doctor, and Settings to render the same capability observation and official Codex CLI reference; the create refusal also names `--interactive-only`. `TestCodexInstallCapabilityConsumersCarryNoSurfaceLocalCopyOrURL` keeps all URL and capability copy in the one authority. Doctor and Settings remain read-only, and support JSON, install/update/daemon mutation, shell welcome, dismiss state, i18n keys, interactive-only semantics, and other providers remain unchanged.
382
- - `make test`: Codex per-Agent native observer retirement makes the endpoint broker the only native lifecycle, control, and approval producer, and projects its content-free telemetry onto the diagnostics surfaces. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` is the source audit that replaced the old producer selection: `internal/app` opens no app-server proxy of its own anywhere, and the native lifecycle observer is built with exactly one connection opener, so there is no second branch a dual-write could take. `TestRetiredCodexAppServerWatchRouteIsRefused` proves the retired producer cannot be revived by argv, hook, or script: the `codex-appserver-watch` ingest route is gone and the surviving route names the broker binding it consumes. `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` and `TestRetiredObserverRecoveryBackoffIsCappedAndNeverExhausts` replace the fixed six-attempt exhaustion budget with the whole reconnect contract: recovery is bounded only by the capped backoff and by the exact binding, the terminal `reconnect-exhausted` fallback is never published again, the reconnect gap writes zero bytes upstream, and the only exits are a replaced binding and a cancelled context. `TestCodexNativeObserversSharingOneEndpointRecoverIndependently` keeps that containment per activation: one activation reconnecting onto its own replacement projects nothing onto its sibling and cannot terminate it. `TestRuntimeTelemetryCountsOneConnectionForConcurrentBindings` is the number the retirement is measured by - three bindings, one upstream connection, one live client session, and a telemetry frame that reaches no endpoint - and `TestRuntimeTelemetryNamesEachBindingScopedFault` keeps queue eviction, a refused reconnect snapshot, and a fenced-out epoch separable instead of collapsed into one revocation count, with a voluntary unbind never counted as a fault. `TestBrokerDiagnosticsReadCreatesNothing` and `TestBrokerDiagnosticsReportTheRestingAbsentRuntime` fix the read-only contract of that projection: asking about the broker publishes no discovery artifact, reclaims nothing, starts no runtime, and reports a machine with no live native Agent as the resting `absent` state under its closed refusal rather than as a fault. `TestBrokerDiagnosticsProjectOneConnectionAndTypedFaults` and `TestCodexBrokerDiagnosticIsContentFree` pin the rendered value: one connection per effective endpoint regardless of binding count, a down connection distinguished from one that never existed, and a closed field set carrying no provider content and no location, with an unclassified failure never leaking its message. `TestDeclaredPlainCodexLaneNamesOnlyTheByDesignLanes`, `TestDeclaredLaneVocabularyIsClosedAgainstUnknownPaneValues`, and `TestDeclaredLaneReadsBackFromTheExactPaneOption` give the two by-design plain-CLI lanes - an empty-prompt default create and an explicit `--interactive-only` - a typed declaration that survives the round trip through the exact pane option, while a forged or unknown value is refused so a declaration cannot hide a real fallback. `TestManagedCodexAuthorityCensusSeparatesDeclaredFromUnexplainedFallback` is the acceptance surface: the declared lanes are counted apart from the unexplained native fallback that the native-authority contract requires to be zero, sibling providers never enter the census, and the classes partition the managed Codex Agents exactly. `TestPlainCodexCreatesWriteTheirDeclarationOntoTheExactPane` closes the write half through the real create route and the real pane binder, so a create route that stopped passing the declaration - or a binder that stopped writing it - would fail rather than silently return every by-design plain Agent to the unexplained count.
458
+ - `make test`: Codex per-Agent native observer retirement makes the endpoint broker the only native lifecycle, control, and approval producer, and projects its content-free telemetry onto the diagnostics surfaces. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` is the source audit that replaced the old producer selection: `internal/app` opens no app-server proxy of its own anywhere, and the native lifecycle observer is built with exactly one connection opener, so there is no second branch a dual-write could take. `TestRetiredCodexAppServerWatchRouteIsRefused` proves the retired producer cannot be revived by argv, hook, or script: the `codex-appserver-watch` ingest route is gone and the surviving route names the broker binding it consumes. `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` and `TestRetiredObserverRecoveryBackoffIsCappedAndNeverExhausts` replace the fixed six-attempt exhaustion budget with the whole reconnect contract: recovery is bounded only by the capped backoff and by the exact binding, the terminal `reconnect-exhausted` fallback is never published again, the reconnect gap writes zero bytes upstream, and the only exits are a replaced binding and a cancelled context. `TestCodexNativeObserversSharingOneEndpointRecoverIndependently` keeps that containment per activation: one activation reconnecting onto its own replacement projects nothing onto its sibling and cannot terminate it. `TestRuntimeTelemetryCountsOneConnectionForConcurrentBindings` is the number the retirement is measured by - three bindings, one upstream connection, one live client session, and a telemetry frame that reaches no endpoint - and `TestRuntimeTelemetryNamesEachBindingScopedFault` keeps queue eviction, a refused reconnect snapshot, and a fenced-out epoch separable instead of collapsed into one revocation count, with a voluntary unbind never counted as a fault. `TestBrokerDiagnosticsReadCreatesNothing` and `TestBrokerDiagnosticsReportTheRestingAbsentRuntime` fix the read-only contract of that projection: asking about the broker publishes no discovery artifact, reclaims nothing, starts no runtime, and reports a machine with no live native Agent as the resting `absent` state under its closed refusal rather than as a fault. `TestBrokerDiagnosticsProjectOneConnectionAndTypedFaults` and `TestCodexBrokerDiagnosticIsContentFree` pin the rendered value: one connection per effective endpoint regardless of binding count, a down connection distinguished from one that never existed, and a closed field set carrying no provider content and no location, with an unclassified failure never leaking its message. `TestDeclaredPlainCodexLaneNamesOnlyTheByDesignLanes`, `TestDeclaredLaneVocabularyIsClosedAgainstUnknownPaneValues`, and `TestDeclaredLaneReadsBackFromTheExactPaneOption` give the three by-design plain-CLI lanes - payload-free fallback, explicit `--interactive-only`, and rollout-catalog resume - typed declarations that survives the round trip through the exact pane option, while a forged or unknown value is refused so a declaration cannot hide a real fallback. `TestManagedCodexAuthorityCensusSeparatesDeclaredFromUnexplainedFallback` is the acceptance surface: the declared lanes are counted apart from the unexplained native fallback that the native-authority contract requires to be zero, sibling providers never enter the census, and the classes partition the managed Codex Agents exactly. `TestPlainCodexCreatesWriteTheirDeclarationOntoTheExactPane` closes the write half through the real create route and the real pane binder, so a create route that stopped passing the declaration - or a binder that stopped writing it - would fail rather than silently return every by-design plain Agent to the unexplained count.
383
459
  - `make test` / `make test-e2e`: Codex native reconnect stability keeps the durable exact-thread binding as reconnect authority after a transient endpoint loss. `TestCodexNativeTwoAgentDisconnectRecoversSameAgentControlAndStableProjection` fences stale start, steer, interrupt, and approval writes out of both the retired and replacement epochs; holds one target at a single invalidating/unavailable projection with zero intermediate provider-hook or hook-badge writes; restores same-Agent control only after the replacement snapshot and control endpoint agree; and proves a healthy sibling's authority, semantic projection, and control remain live. `TestInstalledIsolatedRealTmuxTwoAgentReconnectSmoke` reruns the maintained lifecycle fixture with the installed binary, stripped inherited `TMUX`/`TMUX_PANE`, disposable Registry/XDG roots, a unique real-tmux socket, immutable binary hash proof, and exact contained cleanup. `test/e2e/codex-lifecycle.sh` repeatedly polls the reconnect gap and requires byte-identical authority/status/badge tuples before accepting the replacement semantic snapshot once.
384
- - `make test-e2e-shards`: the Linux e2e suite is scheduled one shard per runner. `scripts/test-e2e-docker.sh` accepts `PROJMUX_E2E_LINUX_SHARD=<shard>` and `PROJMUX_E2E_SUITE=codex-lifecycle|npm-staging` as mutually exclusive single-suite selectors that reuse the existing `E2E_SCENARIO` replay plumbing and derive their exact terminal inventory from the same `test/e2e/linux-shards.tsv` row the replay router reads; setting none leaves the default four-shard parallel run plus both suites unchanged. `test/e2e/shard-contract.sh` pins both workflow topologies to that manifest: `e2e-linux` matrixes exactly the manifest shards and `e2e-suite` the codex/npm pair, both `fail-fast: false` on their own bounded `ubuntu-latest` runner with uniquely named evidence artifacts. In CI they are `needs` and `--required` children of the aggregate `Test` gate, while the thin `E2E Tests` compatibility job keeps reporting the status-check name the branch ruleset requires with `if: always()` and the same `required-gate.py` reduction. In the tag-triggered Release workflow, the same children reduce through an exact `Release E2E Tests` aggregate and `Build Release` depends only on that gate, so every shard must succeed before archive construction without coupling the release workflow to the branch ruleset's required context name.
460
+ - `make test-e2e-shards`: selectorless local `make test-e2e` runs the four Linux fixtures serially in canonical `fixture-1` through `fixture-4` order as the default acceptance profile; `PROJMUX_E2E_LINUX_MODE=parallel` is the explicit stress profile and crosses a four-consumer start/release barrier. Both profiles build exactly one immutable product binary, consume the exact `L01`-`L19`/`C01`/`N01` inventory, and reduce it through the same terminal result hash. `scripts/test-e2e-docker.sh` also accepts `PROJMUX_E2E_LINUX_SHARD=<shard>` and `PROJMUX_E2E_SUITE=codex-lifecycle|npm-staging` as mutually exclusive single-suite selectors that reuse the existing `E2E_SCENARIO` replay plumbing and derive their exact terminal inventory from the same `test/e2e/linux-shards.tsv` row the replay router reads. `test/e2e/shard-contract.sh` uses fake-runner causal barriers rather than elapsed timing to pin default maximum overlap one and explicit parallel overlap four, then pins both workflow topologies to that manifest: `e2e-linux` matrixes exactly the manifest shards and `e2e-suite` the codex/npm pair, both `fail-fast: false` on their own bounded `ubuntu-latest` runner with uniquely named evidence artifacts. In CI they are `needs` and `--required` children of the aggregate `Test` gate, while the thin `E2E Tests` compatibility job keeps reporting the status-check name the branch ruleset requires with `if: always()` and the same `required-gate.py` reduction. In the tag-triggered Release workflow, the same children reduce through an exact `Release E2E Tests` aggregate and `Build Release` depends only on that gate, so every shard must succeed before archive construction without coupling the release workflow to the branch ruleset's required context name.
461
+ - `make test` / installed opt-in smoke: Codex Resume Picker state-DB-only catalog wire compatibility makes every `thread/list` the picker sends an explicit `useStateDbOnly:true` bounded metadata read instead of the omitted request that asks the endpoint for a rollout JSONL scan-and-repair. `TestCatalogListSendsStateDbOnlyWithoutOtherWireDrift` is the byte golden for both the first page and the exact-cwd continuation: the field is present and true, and the cursor, limit, sortKey, sortDirection, sourceKinds, archived, and cwd bytes around it are unchanged. `TestCatalogListRejectionIsTerminalWithoutOmittedOrFalseRetry` closes the compatibility matrix -- method-unsupported and unknown-field refusals of a first page are attributed to `ErrStateDbOnlyRejected` wrapping `ErrUnsupported`, an unrelated refusal and a refused continuation cursor are not, and every arm sends exactly one list request with no false or omitted retry, because support is never inferred from a version allowlist and the retry would be the scan-and-repair read the field exists to avoid. `TestStateDbOnlyRejectionRunsOneRolloutFallbackWithoutRetryOrNativeMerge` carries that typed refusal through provider discovery to exactly one bounded rollout fallback whose rows keep `codex-rollout` source, medium confidence, and the `app-server-unsupported` reason with native authority and native/rollout merge at zero. `TestStateDbOnlyIsAListOnlyRequestField` is the negative wire audit that keeps `thread/read`, `thread/resume`, `thread/start`, `turn/start`, and `thread/loaded/list` params free of the list-only field. `TestInstalledStateDbOnlyCatalogListABSmoke` is opt-in through `PROJMUX_CODEX_STATE_DB_ONLY_SMOKE_ROOT=/tmp/<isolated-root>` with an optional absolute `PROJMUX_CODEX_STATE_DB_ONLY_BINARY` pinning the exact generation; it opens a fixture-owned private listener and records the route, row count, nextCursor presence, and elapsed time of the true, false, and omitted arms, failing only when the real endpoint refuses the explicit field. That smoke owns compatibility, not latency: its isolated CODEX_HOME holds no sessions, so the scan-and-repair timing separation of a large real store stays with the exact installed matrix.
462
+ - `make test`: Codex Resume Picker route budget isolation gives route resolution, the native page, the rollout scan, and the result handoff four independently measured clocks instead of one 450ms provider envelope every stage inherits the remainder of. `TestAIResumeStageBudgetsAreDeclaredNotDerived` is the exact bound audit -- route 2.5s, native 300ms, fallback 1.25s, handoff 35ms, a 2.835s declared Codex provider terminal, the 450ms discovery envelope, and the app's native bound matching `aisessions.DefaultResumeSummaryNativeBudget` -- plus per-span elapsed measured from each span's own start and the typed `aiResumeBudgetTimeoutError` wrapping `context.DeadlineExceeded` without a new failure vocabulary. `TestResumeRouteBudgetIsNotCutByTheProviderEnvelope` drives a virtual clock through the 450ms cutoff and a 1.97s route and keeps both resolving, published, and counted, while `TestResumeRouteBudgetOverrunIsTypedWithoutLateRouteWrite` makes a route that spends its whole 2.5s bound a typed route timeout whose blocked call publishes zero inventory entries, zero row mutations, and zero list updates after the frame settled. `TestResumeFallbackClockRunsWhileRouteResolvesWithoutTouchingSiblings` holds the rollout clock on its own 1.25s bound while the route is still inside 2.5s and leaves both sibling provider results exactly as they settled, and `TestResumeRouteTerminalReasonTableKeepsOneResultWithoutLateMutation` closes empty, invalid, errored, and cancelled inventories at one terminal result each. `TestCatalogRoutesProjectsCurrentAndDrainingWithZeroLifecycleWrites`, `TestUnobservableCatalogRoutesRefuseWithoutMutatingThePool`, and `TestDefaultCatalogRoutesIsOneAttachOnlyReadWithoutLifecycleCalls` keep `CatalogRoutes` a read-only Current+Draining projection: exact route identity, journal bytes and mtime unchanged, and zero start/stop/admit/drain calls.
463
+ - `make test`: Codex Resume Picker native/rollout budget ownership makes the bounded native page read and the bounded rollout scan siblings rather than a chain, so neither is spent out of the other and neither is reduced to whatever a caller context has left. `TestResumeSummaryStageBudgetsAreDeclaredAndOwnedPerStage` pins the declared 300ms native and 1.25s rollout bounds, zero-value resolution, and a per-stage deadline measured from that stage's own start even when the caller deadline is already spent. `TestResumeSummaryRunsOneRolloutScanThatOutlivesTheNativeAttempt` runs exactly one rollout scan per invocation and keeps it alive until the native attempt reaches its own terminal, and `TestResumeSummarySourceSettlementPermutationsKeepOneSourceAndOneSnapshot` settles fresh, nearly-spent, and cancelled callers against native rows, native empty, native error, and native overrun at one source and one immutable snapshot with zero merges. `TestResumeSummarySlowNativeRetainsDepthAwareRolloutRowsAtScale` holds the completed depth-0 43-row and depth-5 158-row results of an 828-session-equivalent store after a native attempt spends its whole bound and fails, and `TestResumeSummaryRolloutCutoffKeepsFullyParsedRowsAcrossTheSendEdge` keeps only fully parsed rows at the rollout stage's own cutoff while a send inside the settlement window is retained and a later one never reaches the published snapshot. `TestResumeSummaryNativeAuthorityAndRolloutRowsKeepGenerationAxesEmpty` leaves native success and native empty with the authority they had and keeps every retained rollout row free of a state-domain, endpoint-generation, or generation-state axis. `TestBoundedNativeSummaryReadOwnsItsBudgetAndClosesOnceOnOverrun` separates a bound overrun from a cancellation and closes a blocked list exactly once, while `TestBoundedRolloutScanNegativeEdgesReturnFullyParsedRowsOnly` degrades unreadable rollouts, symlink directory aliases, and cancellation mid-walk to fewer complete rows with zero duplicates.
464
+ - `make test` / `make test-e2e`: honest Project lifecycle verbs and operation receipts. `projmux start|open|stop project` and the canonical `projmux unregister project` become executable routes; `delete project` stays a behavior-preserving deprecated alias of the last of those, with byte-identical stdout, one stderr deprecation line, and one `compatibilityWarnings` receipt entry. `TestStartProjectMaterializesDetachedAndReportsAlreadyLive`, `TestOpenProjectMovesTheCurrentClientAndRefusesOutsideTmux`, and `TestStopProjectEndsOnlyTheRuntimeAndRefusesAnOfflineTarget` pin the offline/live runtime split, the inside/outside-tmux refusals that precede any materialization, and the offline-stop zero-write refusal; `TestUnregisterProjectAndItsDeprecatedDeleteAliasAreByteIdenticalOnStdout` and `TestUnregisterProjectReceiptCountsTheWholeRemovedSubtree` pin the alias equality and the preserved runtime. Every create, rename, delete, and lifecycle route now emits one `OperationReceipt/v1`: the default result prints it as a trailing line and the new `-o receipt` prints it as JSON, while `-o uid|name|ref|metadata|json|pane-id|none` keep their bytes. `TestReceiptValidateRejectsEffectsTheRouteDoesNotAllow`, `TestProjectLifecycleReceiptsMatchTheDecidedEffectMatrix`, and `TestEveryOperationNamesAnExecutableRouteWithAnEffectRow` require every emitted tuple to be one the route manifest already allows and every lifecycle verb to have a distinct tuple. A same-root `create project` repeat now reports `reused` rather than `created` (`TestTheFirstMutationCreatesTheRegistryFromACompletelyEmptyState`). `switch` declares `create project` and `open project` as its canonical composition instead of `focus project`, and the closed-Project startup row `Open fresh` is renamed `Recreate Project` and gated by a confirmation naming the exact old Project UID and counts (`TestSwitchProjectStartupRecreateConfirmsBeforeReplacingIdentity`, `TestSwitchProjectStartupRecreateDeclineWritesNothing`). The isolated tmux smoke drives start/open/focus/attach/stop/unregister against one Project on a contained socket.
465
+ - `make test`: Codex Resume Picker concurrent rollout scan start enters the bounded rollout scan with the Codex provider instead of after the generation route has resolved, so the declared route + native + handoff provider terminal is the real worst case rather than route_end + a full rollout bound. `TestResumeRolloutScanIsEnteredOnceWithTheProviderWhileTheRouteResolves` is the start-order ledger: on a virtual clock the scan is entered at the provider start instant, exactly once per invocation, and is already running while a 1.97s route is still inside its own 2.5s bound, with the single native read entered only at the route terminal. `TestRouteSlowInvocationPublishesTheRolloutRowsItAlreadyFound` runs the 1.97s route against a 0.95s depth-aware scan and requires the rollout rows to be settled before the route terminal, the Codex footer to be a count rather than `search_failed`, and every retained row to keep source `codex-rollout` with empty state-domain, endpoint-generation, and generation-state axes. `TestCodexProviderTerminalTableStaysWithinTheDeclaredWorstCase` is the boundary table for a route that spends its whole bound and a route that resolves one millisecond inside it: `providerTerminal()` stays route + native + handoff, the scan's own terminal stays inside it, and the Codex result settles as a count in both arms. `TestCodexRouteReadAuthorityIsUnchangedByTheConcurrentScan` re-runs the Phase 2 source decision at the app layer, where native rows and native empty both keep authority over the concurrent scan and only a native read that settles on no catalog selects the already-running rollout result. A per-route native read declares `codexRouteReadRolloutBudget` so it never opens a second scan of its own; the invocation's single scan is the one owned by the fallback span.
466
+ - `make test` / `make test-integration` / `make test-e2e`: explicit child-create target cardinality. `create pane|agent|<provider>` gains `--all-windows` and `--primary-window`, the two spellings that state the target Window set outright: `--all-windows` was the explicit form of the `--project P` whole-Project fan-out of that release and resolved the identical set, and `--primary-window` was the exact-one `spec.primaryWindowRef` selection the release after it made the default. Nothing flipped in that release -- `--project P` alone still fanned out -- it only gained one stderr line and one `compatibilityWarnings` receipt entry naming both escape hatches, with stdout byte-identical; the bullet below is the cutover that spent that notice and removed it. `TestChildCreateTargetCardinalityTable` is the zero/one/multi-Window table across all four spellings, reading the selected set out of `-o receipt`; `TestChildCreateZeroWindowCardinalityRefusesWithZeroWrites` closes the empty-Project row for every spelling including the Agent route. `selectedWindowUIDs` is now sourced from the shared target planner rather than from the created results, so `TestChildCreateSelectedWindowSetIsIdenticalAcrossProviders` can require `create pane`, `create agent`, and the `codex`/`claude`/`antigravity` shortcuts to report one identical set for identical argv. `TestChildCreateCardinalityFlagConflictsAreUsageErrors` pins every mutual exclusion -- each new flag against `--window`, `--pane`, `--selector`, `--create-window`, and against the other -- as an in-parser exit `2`, and `TestChildCreatePreflightRefusalsCostNothing` adds the dangling and cross-Project `spec.primaryWindowRef` refusals and the explicit-`--name` cardinality refusal; every refusal leaves zero Registry writes, zero tmux objects, and zero provider effects. `TestChildCreateCardinalityFlagsMaterializeAndRollBack` keeps the new spellings on the shared runtime path: an offline Project is still materialized detached, a failed `split-window` still rolls the whole fan-out back, and the Settings provider gate still refuses before allocation. The native Codex exact-one fan-out refusal is unchanged and is now decided on that same planner set. `TestRepositoryCallSitesSpellTheirChildCreateCardinality` is the migration gate: every `.sh` call site under `scripts/`, `test/`, and `npm/` that scopes a child create with `--project` must name its cardinality, and the smoke fixtures were migrated to `--all-windows` because that spelling is provably the set they already resolved.
467
+ - `make test` / `make test-integration` / `make test-e2e`: primary-one child-create default. The `--project P` child create whose whole-Project fan-out the previous release deprecated now resolves exactly one Window, the Project's `spec.primaryWindowRef`, and the compatibility notice it earned is gone from stderr and from the receipt's `compatibilityWarnings` -- the default is the behavior that notice named. Both spellings of that selection share one preflight (`primaryWindowSpelling` picks which one the refusal names), so `--primary-window` and the bare `--project` scope cannot drift apart on the target or on the missing/dangling/cross-Project refusal. `TestProjectOnlyOmissionIsThePrimaryWindowDefault` is the cutover golden: the omission is byte-identical to `--primary-window` on stdout, stderr, and the resulting Registry; `--all-windows` still produces one Pane per Window of a two-Window Project while the omission produces exactly one, in the primary Window only, which is the `silent target 확대/축소 0` claim in observable form; and the two spellings that were never part of the compatibility surface keep their meanings -- a wholly omitted scope inside a managed Pane still splits the active Window even when the Project's primary is elsewhere, and `--primary-window` still overrides the active Window. `TestChildCreateTargetCardinalityTable` moves the `--project`-alone rows to the exact-one primary set and now requires every spelling to carry zero warnings on both channels, and `TestChildCreateZeroWindowCardinalityRefusesWithZeroWrites` moves the empty-Project row from a bare no-match to `--project: project/empty has no spec.primaryWindowRef`. `TestChildCreatePreflightRefusalsCostNothing` adds the dangling `spec.primaryWindowRef` refusal reached through the bare `--project` spelling, on the same zero Registry writes, zero tmux objects, zero provider effects assertion. The integration smoke adds the end-to-end half on the two-Window Project the discovery section already builds, invoked from a Pane in the *non-primary* Window so that "the default is the primary Window" is separable from "the create split wherever the caller was": `--project` alone and `--primary-window` each add one Pane to the primary Window and none to the other, `--all-windows` adds one to each, an explicit `--window` adds one to the Window it names, combining the two cardinality flags is exit `2` with no Pane, and the `--project`-only receipt carries the primary Window uid and `"compatibilityWarnings": []`. The Go fan-out fixtures that had been spelling the whole-Project set as `--project P` alone are respelled `--all-windows`, which is provably the set they already resolved, and `TestRepositoryCallSitesSpellTheirChildCreateCardinality` keeps every `.sh` call site explicit -- a fixture whose subject is the implicit spelling opts out by writing `projmux-cardinality-default-fixture` on the line, so an exemption is visible rather than silent. `--all-windows`, `--primary-window`, every mutual exclusion, the `natural-omitted` active-Window contract, the `delete project` alias, and the native Codex exact-one fan-out refusal are all unchanged.
468
+ - `make test`: Codex Resume Picker provider-independent status and elapsed settlement gives every enabled provider its own settlement envelope, started at that provider's own instant with that provider's own declared bound, instead of one shared discovery deadline that stretched to the longest declared provider bound whenever Codex was configured. `TestCodexSettlementLeavesSiblingProviderStatusAndCountUnchanged` runs the whole Codex matrix -- native rows, a route read that declines authority, an unavailable route inventory, route reads that all fail, a genuine failure with nothing found, and a Codex that never returns inside its own envelope -- and requires Claude 143 and Antigravity 4 to keep the same projection, the same footer count, and the same rows in every arm, with a final pair of runs proving that enabling or disabling Codex does not move where Claude terminalizes. `TestCodexFallbackRowsProjectFoundInsteadOfSearchFailed` makes rows the authority for the status: route reads that all fail, an unavailable route inventory, and a partial that settles on its own cancellation all publish their rollout rows as `N found` with source `codex-rollout` and empty generation axes, while `search_failed` is left to a Codex terminal that genuinely found nothing. `TestProviderElapsedIsMeasuredOnEachProviderOwnClock` drives a virtual clock where Claude terminalizes at 120ms and Antigravity at 200ms while a 1.97s Codex route is still inside its own bound, and requires each witness to stay on its own clock, each provider to have been granted its own envelope, and the elapsed values never to reach a row or the footer. `TestGlobalCapAppliesOnceAndProviderFooterCountsStayPreCap` applies the visible cap once to the settled rows of all three providers and keeps the provider footer counts pre-cap, and `TestProviderSettlementKeepsFooterVocabularyLocalesAndGeometry` is the EN/KO golden for the unchanged `count`/`search_failed`/`disabled` vocabulary, provider order, and two-line footer geometry. `TestLateProviderSendAndCloseNeverMutateSettledItemsOrFooter` returns a provider after its own terminal and then closes the controller, with zero changes to the settled Items, the settled footer, and the deferred update.
469
+
470
+ - `make test` / installed opt-in smoke: Codex Resume Picker exact private-generation installed closure replays the source, status, time, and cleanup contracts of this track once against a real binary and a real-scale store, because every earlier phase settled them on virtual clocks and synthetic trees. `TestInstalledStateDbOnlyCatalogListSettlesAtRealScale` is opt-in through `PROJMUX_CODEX_SCALE_CATALOG_SMOKE_ROOT=<isolated-root>`, which like every installed smoke root must be an isolated child of the process `TMPDIR` -- a multi-gigabyte store copy usually needs that pointed at real disk rather than a tmpfs -- with `PROJMUX_CODEX_SCALE_CATALOG_BINARY` naming the exact absolute generation and `PROJMUX_CODEX_SCALE_CATALOG_VERSION` naming the version it must self-report and answer with; the root holds a pre-made read-only `codex-home` copy the fixture adopts, never the live store. It puts a pass-through WebSocket ledger in front of the private listener so the assertion is what the product actually wrote on the wire: exactly one `thread/list` with `useStateDbOnly` explicitly true, settling inside the declared 300ms native window as either an exact result or a typed timeout rather than a field or params refusal, and rewriting zero rollout files. The explicit-false and omitted arms run afterwards as recorded baselines only, and the ledger's final tri-state order proves they were never a product retry. `TestInstalledResumePickerSettlesProviderTruthOnTheExactPrivateGeneration` is opt-in through `PROJMUX_CODEX_SCALE_RESUME_SMOKE_ROOT`, `PROJMUX_CODEX_SCALE_RESUME_BINARY`, `PROJMUX_CODEX_SCALE_RESUME_VERSION`, `PROJMUX_CODEX_SCALE_RESUME_STORE`, and `PROJMUX_CODEX_SCALE_RESUME_CWD`. It takes a same-run census of the store copy first -- rollout file count plus untimed depth-0 and depth-5 rows per provider -- and compares every observation against that census instead of a planning literal, because the store keeps growing. Four arms settle the picker population at `resume_scan_depth=5`: a native read on the exact private listener, a slow native on a socket that accepts and never answers, an absent native socket, and a run with Codex disabled. The native arm must publish `codex-appserver` rows carrying the resolved route's exact generation identity; both failing-native arms must publish exactly the census depth-5 rollout row count as `codex-rollout` with all three generation axes empty; Claude and Antigravity must equal their own census counts and settle on their own clocks in every arm including the Codex-disabled one; no provider witness or frame may exceed the declared provider terminal; and no published frame may change after the terminal has fully elapsed. Both smokes register a live-store guard that manifests the operator's real rollout store and requires it byte-identical at the end, and both require the fixture rollout store to be unchanged by the product path.
471
+ - `make test`: Agent-owned Pane derived explicit name. `create agent` and the three provider shortcuts now supply `<agent-name>-pane` as an **explicit** `BootstrapPane.Name` for the Pane the Agent owns, so the launcher convention that lived only in documentation as a mandatory follow-up `rename pane` is code and a caller that never read that page no longer leaves a managed Pane addressed by a raw UID. `TestCreateAgentNamesItsManagedPaneAfterItsAgent` is the derivation table: an explicit `--name reviewer` produces `reviewer-pane`; no `--name` at all produces `<agent-uid>-pane`, because the Agent's own automatic name is its exact full UID and the point is that the *Pane* is not named after its own UID; a 123-byte Agent name produces the largest legal 128-byte derived name; and a 128-byte Agent name, whose derivation would be 133 bytes and fail `ValidateName`, falls back to ordinary automatic naming rather than refusing a `create agent --name <128 bytes>` that works today -- length is the only fallback, and every row additionally requires the name reservation to follow the Pane. `TestADerivedAgentPaneNameCollisionIsATypedZeroWriteRefusal` proves the other half rather than assuming it: the derived name enters the same explicit-name machinery an operator's `--name` does, so a name already held root-wide for kind Pane is a typed exit-2 refusal with zero Registry commits, zero minted uid published, zero tmux objects, zero provider bindings, and no invented suffix; the whole metadata phase runs before the first tmux or provider call, which is what makes that free. `TestAgentAndPaneRenamesStayIndependentAfterTheDerivation` pins the deliberately-absent invariant: renaming the Pane to the very name the derivation produced is a successful no-op (`reserveExplicitName` guards with `owner != uid`, so an existing launcher's follow-up rename keeps working), an explicit `rename pane` is neither forbidden nor restricted and does not follow the Agent, and `rename agent` still reports `panes=0 agents=1` so its `cardinality=exact-one` effect tuple and the seven-axis command effect manifest are untouched. `internal/core/metadata`'s `TestAnAgentlessPaneKeepsItsExactUIDAutomaticName` is the C-5 `Sequence-free automatic naming` regression guard: the automatic allocator is not touched by this change, so bootstrap topology Panes, `AddPane` with no declared name, and `AttachAgentPane` with no declared name all still carry their exact full minted UID, and an explicitly named sibling never makes the next automatic name react to it.
472
+ - `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
+ - `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.
385
474
 
386
475
  ## When To Update This List
387
476
 
@@ -396,13 +485,17 @@
396
485
 
397
486
  - `make test` / `make test-integration` / `make test-e2e`: Create Agent early-exit authority keeps the provider behind the creator's exact Registry commit without weakening rollback identity. The supervised activation child enters the existing cross-process Registry lock and admits only the committed Agent-owned Pane whose generation, operation, `%N` runtime, and Running `paneRef` all match; the read-only callback writes zero Registry bytes. The creator carries one private cleaned absolute Registry path through the supervisor, so admission reads and termination-receipt writes stay on the creator's state root even when the tmux server inherited another XDG environment; this path is not exported to the provider or public hooks. On POSIX, a private CLOEXEC failure channel distinguishes admission/provider-exec failure (no termination receipt) from the same numeric provider exit (exact supervisor receipt), while HUP during admission kills the supervised gate before provider side effects and remains exact killed evidence. `TestSuperviseWaitsForTheCommittedActivationBeforeStartingTheChild`, `TestSuperviseRefusesTheChildWhenCreateAbortsBeforeActivationCommit`, `TestSuperviseActivationAdmissionIsExactAndZeroWrite`, `TestActivationAuthorityRejectsNonExactRegistryPathsBeforeProviderStart`, `TestAgentAdmissionAndReceiptUseCreatorRegistryAuthorityNotAmbientXDG`, `TestActivationExecFailureHandshakeIsTyped`, `TestSuperviseActivationHandshakeDistinguishesGateFailureFromProviderExit`, and `TestActivationExecCloseOnExecDoesNotWaitForProviderDescendants` pin commit/abort, blank/malformed/foreign identity refusal, split XDG authority, sibling byte preservation, exit 1/42 disambiguation, HUP side-effect zero, and descendant fd containment. The real-tmux integration and L17 e2e run an immediate exit-42 provider with the generated `pane-exited` hook live and require no blank-UID rollback, exact generation/operation evidence, sibling preservation, and fixed-point repeat. `PMX_TEST_EARLY_EXIT_REPEATS=30 test/integration/create-agent-early-exit-repeat.sh` is the only repeat interface: it builds one immutable attempt binary, then gives every sample a fresh HOME/XDG/Registry/root and unique app/sibling sockets before exact-owned cleanup. Shell Pane supervision and the public `PROJMUX_*` hook contract are unchanged.
398
487
 
399
- - `make test` / `make test-integration` / `make test-e2e`: Native Codex exact-selector mutation route authority carries the activation gate's exact raw `%N` through the existing private `__PROJMUX_RUNTIME_ANCHOR_PANE` only; the public `PROJMUX_*` environment, activation generation, sessionRef, thread/turn, and lifecycle meanings stay unchanged. `TestNativeActivationEnvironmentAddsOnlyThePrivateExactRouteAnchor`, `TestExactThreeUIDCreateAgentRouteIgnoresUnrelatedAmbientPane`, `TestNativePrivateActivationAnchorNegativeAuthorityMatrixIsFirstWriteZero`, and `TestStandaloneRouteRequiresBlankClassAndExactInheritedPaneReceipt` pin exact anchor production, UID owner-chain independence from an unrelated client, stale/malformed/wrong-server/foreign first-write refusal, and unchanged standalone receipt strength. `TestOutsideTmuxExactThreeUIDCreateAgentUsesOnlyQuietAppRoute`, `TestOutsideTmuxExactThreeUIDCreateRoutesProductionAIPresentationWrites`, `TestOutsideTmuxProductionAIPresentationFailureIsVisibleAndRollsBack`, `TestAgentResumeRoutesProductionAIPresentationWritesThroughExactRuntime`, and `TestProductionAIPaneBindersUseOnlySuppliedExactRuntimeRoute` require an outside-tmux exact create to use only explicit `-L`/`-S` calls—including exact `select-pane -T ... -t %N` title presentation through the real AI binder—while returning one trimmed `%N` on stdout and no stderr; the provider shell contains no tmux mutation, and real permission and route-authority failures remain non-zero and transactional. The real-tmux early-exit integration asserts provider/private-anchor equality plus owner-title parity, and L06 audits the exact command's tmux argv while preserving two attached clients' sibling Window hash and the sibling socket hash.
488
+ - `make test` / `make test-integration` / `make test-e2e`: Native Codex exact-selector mutation route authority carries the activation gate's exact raw `%N` through the existing private `__PROJMUX_RUNTIME_ANCHOR_PANE` only; the public `PROJMUX_*` environment, activation generation, sessionRef, thread/turn, and lifecycle meanings stay unchanged. `TestNativeActivationEnvironmentAddsOnlyThePrivateExactRouteAnchor`, `TestExactThreeUIDCreateAgentRouteIgnoresUnrelatedAmbientPane`, `TestNativePrivateActivationAnchorNegativeAuthorityMatrixIsFirstWriteZero`, and `TestStandaloneRouteRequiresBlankClassAndExactInheritedPaneReceipt` pin exact anchor production, UID owner-chain independence from an unrelated client, stale/malformed/wrong-server/foreign first-write refusal, and unchanged standalone receipt strength. `TestOutsideTmuxExactThreeUIDCreateAgentUsesOnlyQuietAppRoute`, `TestOutsideTmuxExactThreeUIDCreateRoutesProductionAIPresentationWrites`, `TestOutsideTmuxProductionAIPresentationFailureIsVisibleAndRollsBack`, `TestAgentResumeRoutesProductionAIPresentationWritesThroughExactRuntime`, `TestAgentResumeProductionBinderWriteFailureIsVisibleAndRollsBack`, `TestRegistryTopologyBinderWriteFailureIsVisibleAndRollsBack`, and `TestProductionAIPaneBindersUseOnlySuppliedExactRuntimeRoute` require exact create, native create, resume, and retained topology materialization to use only explicit `-L`/`-S` calls—including exact `select-pane -T ... -t %N` title presentation through the real AI binder—while returning success only after the caller consumes `BindAgentPaneOnRoute`'s result; injected option-write failures remain non-zero and roll back the partial Pane and Registry state. The provider shell contains no tmux mutation. The real-tmux early-exit integration asserts provider/private-anchor equality plus owner-title parity, and L06 audits the exact command's tmux argv while preserving two attached clients' sibling Window hash and the sibling socket hash.
400
489
 
401
490
  - `make test` / `make test-integration` / `make test-e2e`: Project sidebar detached-open authority is explicit from the generated Alt-1 popup through materialization: `switch --anchor %N` retains the popup Pane unless that sidebar flow stops its origin Session, in which case it reobserves the exact target-client Pane once; the detached child serializes required `switch sidebar-open --anchor %N`, and typed Project-open/session/topology requests carry that same field into exact route binding. `TestBuildPopupToggleCarriesSidebarAnchorAsTypedSwitchArgumentOnly`, `TestSwitchExecuteSidebarHookProjectLaunchesContinuationBeforeSelfClose`, `TestSwitchExecuteSidebarAfterOriginStopRebindsContinuationAnchorToClient`, `TestSwitchSidebarOpenRequiresExplicitAnchorBeforeEffects`, `TestSwitchSidebarOpenCarriesOneExplicitAnchorThroughTypedMaterializerRequest`, `TestDetachedExplicitAnchorBindsExactAppRouteWithoutAmbientEnvironment`, `TestDetachedExplicitAnchorAuthorityNegativeMatrixIsFirstWriteZero`, `TestSidebarAnchorDriftAfterPreflightRefusesBeforeFreshRegistryWrite`, and `TestSidebarProjectOpenRouteRejectsActualPaneProjectOwnershipMismatchWithoutWrites` pin the generated command, one-time rebind, parser refusal, environment-free typed handoff, exact socket/PID/$/@/% containment, post-trust pre-write revalidation, and stale/wrong-server/foreign/managed-owner first-write-zero behavior. L11 keeps `TMUX`/`TMUX_PANE` absent from detached continuations, runs under a dedicated `TMUX_TMPDIR` and unique logical sockets, verifies offline Continue/Fresh plus exact client retention and error-sidebar recovery, and cleans only observed socket paths allowlisted beneath its isolated root. The native supervised Codex private carrier above remains unchanged.
402
491
 
403
492
  - `make test` / `make test-integration` / `make test-e2e`: Save and Quit policy Phase 0 freezes one complete exact-route Registry/resource-graph observation and captures every `StatusLive` managed Project latest snapshot in Project UID/session order before the existing guarded shutdown. C-1 enforcement is `TestPlanQuitSnapshotBatchSelectsOnlyLiveManagedProjectsInStableOrder`, `TestQuitSavePreflightUnavailableOrConflictPerformsZeroCaptureAndKill`, `TestQuitSaveBarrierContinuesAfterMiddleFailureAndDoesNotKill`, `TestQuitSaveBarrierRetryRecapturesEveryTargetThenRunsOneGuardedShutdown`, `TestQuitSaveAndQuitIntegrationCapturesRegistryGraphBeforeGuardedShutdown`, and `TestQuitSaveBarrierKeepsNamedRegistryAndSiblingBytesInvariant`; missing-root, incomplete identity, marker/route/socket drift, 0/1/N, atomic validity, retry, named-snapshot, Registry, and sibling invariants remain fail-closed. C-2 enforcement is `TestQuitUnsavedActionsAndFlagsAreSnapshotFreeAndByteEquivalent`, `TestQuitCommandPickerActionsHaveEnglishKoreanMeaningParity`, and `TestQuitCommandCancelAndCloseDoNotShutdown`: explicit unsaved quit and existing `--yes`/`--force` retain zero snapshot I/O and exact guarded-shutdown compatibility while Cancel/Esc write nothing. The installed smoke strips inherited `TMUX`/`TMUX_PANE`, uses one unique `TMUX_TMPDIR` with exact app and sibling sockets, materializes two managed Projects plus Home/control and ephemeral sessions with auto-save off, requires two refreshed latest snapshots before app death, excludes every non-Project session, preserves named/Registry/sibling bytes, and cleans only queried socket paths proven below the smoke root.
404
493
 
405
494
  - `make test` / `make test-e2e`: Sidebar popup runtime-stop anchor authority gives the `sessionizer-sidebar` popup one anchor transport end to end. The generated Alt-1 launcher already passes the origin Pane as a `switch --ui=sidebar --anchor %N` operand and supplies no `__PROJMUX_RUNTIME_ANCHOR_PANE`, so both stop consumers the sidebar reaches now read that operand: `stopManagedProjectSession` resolves its route through `resolveInvocationRuntimeMutationRouteWithAnchor`, and `executeUnmanagedRuntimeStop` takes the anchor as an explicit argument that non-popup callers leave blank. `TestSwitchSidebarKillStopsManagedProjectFromAPopupWithoutTMUXPANE` and `TestSwitchSidebarKillStopsUnmanagedSiblingFromAPopupWithoutTMUXPANE` pin one exact `kill-session -t $N` plus the fallback client handoff in the real popup shape -- a valid inherited `TMUX` receipt with `TMUX_PANE` and the private anchor env both blank -- for the managed Project row and the discovered unmanaged sibling; `TestSwitchSidebarKillWithoutFallbackRefreshesWithoutWriting` keeps a sidebar with no other live session on a plain refresh with zero writes; `TestSwitchSidebarKillWithoutAnchorOperandRefusesWithZeroWrites` and `TestSwitchSidebarKillRefusesAMalformedAnchorOperand` keep a missing or non-`%N` operand a typed route refusal rather than a silent no-op, with tmux writes at zero and the managed route refusing before the fallback handoff; and `TestSidebarPopupAnchorSupplierAndStopConsumerShareOneTransport` fails if the launcher stops emitting the operand or a consumer stops reading it. `TMUX_PANE` is never promoted to anchor authority, and the route authority rules, `session-popup`'s private env transport, and every other popup mode are unchanged. L11's two sidebar Ctrl-X drivers now run with `TMUX_PANE` unset so they exercise the operand instead of a shell export.
495
+ - `make test`: Claude resume discovery opens candidates in descending mtime order until it has the displayed limit of unique matching IDs and has drained the terminal mtime tie group. `TestClaudeDiscoveryScanCostTracksLimitAndTerminalTie` reverses Phase 0's whole-history cost assertion for both interactive and summary lanes: 120 and 480 transcripts open the same limit-plus-terminal-tie count. `TestClaudeDiscoverySkipsOlderNonMatchingTranscripts` reverses its older-nonmatch assertion. The bound is data dependent: `TestClaudeLimitedScanPreservesFullSettledRows` compares the real global settlement against independently declared full-scan rows for a 512-file terminal tie with reversed filename/ResumeID order, 256 newest duplicate IDs, 256 newest cwd nonmatches, and cross-directory depth scans with equal-mtime duplicate IDs carrying different titles. These adversarial cases may require O(M) reads; correctness does not have a fixed file-cap exception. The summary lane retains confirmed unique matches before the global visible cap, including terminal ties, so its provider count describes this invocation rather than the accumulated history. `TestClaudeCanceledScanReturnsOnlyFullyParsedRows` deterministically cancels before scanning and during a real second-file read, retaining only complete earlier rows and marking incomplete discovery with `MoreNotLoaded`.
496
+ - Installed observation: `TestClaudeResumeRealStoreObservation` skips unless `PROJMUX_CLAUDE_RESUME_OBSERVE_CWD` explicitly supplies the real absolute cwd. After merging and installing, run it from the merged source alongside the installed native picker smoke. The opt-in test restores the original XDG config home that ordinary package tests isolate, without changing normal test isolation. It first runs all configured providers with the production native catalog opener and real read-only generation-route inventory at the configured depth/limit, then records Claude-only comparisons at depths 0 and 5. It reports only the exact-cwd file census, provider states, confirmed/visible counts, route count, and each provider's elapsed witness. Criterion ⑥a requires visible Claude rows with `count` in the concurrent invocation, including a nonempty partial; ⑥b requires each Claude-only scan below the unchanged 450ms budget. The observation performs population only, without preview, selection, launch activation, or transcript/config/Registry writes. It does not print conversation contents or session identities.
497
+ - `make test`: `TestClaudeEnvelopeExpiryPublishesParsedPartialRows` replaces Phase 0's artificial late-send harness and pins C-1/⑥c: the injected clock expires the provider envelope, a private nil-by-default timer seam witnesses entry into the unchanged 35ms handoff, and only then a channel releases four fully parsed fixture rows into an invocation limited to twenty. The controller publishes those exact rows and detail references with `count=4` and `MoreNotLoaded=true`. The boundary fixture uses a smaller scan limit to obtain its real prefix; cancellation during parsing is independently covered by `TestClaudeCanceledScanReturnsOnlyFullyParsedRows`. The combined assertion has no real timer or wall-clock dependency. `TestClaudeHandoffExpiryKeepsLateResultOutsideSettlement` preserves the separate negative case: a provider that misses both windows leaves its late result buffered outside the already immutable settlement. C-3 is absolute when the scan completes; envelope expiry yields a descending-mtime prefix, strictly better than discarding every row but not equivalent to the completed scan. Remaining provider budget-allocation asymmetry is outside this phase.
498
+ - `make test`: `TestResumeProviderUnfinishedAndFailureHaveDistinctLocalizedTerminals` pins the Phase 2 empty-scan terminal split in both footer locales: an expired envelope without a provider error is `scan_unfinished`, a genuine error remains `search_failed` (including when the expiry marker is also set), and a completed empty scan remains `count=0`. The virtual-clock handoff test keeps late rows outside that new terminal. `TestSessionScanThreeProviderDiscoveryEquivalence` compares full ordered exported discovery rows for Claude, Codex, and Antigravity against fixtures captured and passed on `fc287c26` before the sparse parser change; Antigravity is a control lane because its current-storage/history adapters do not use the transcript scanner. `TestSessionScanRecordPreservesLegacyFields` separately checks tolerant aliases, unknown nested metadata, escaped/case-sensitive keys, malformed/overflowing records, and unchanged provider title helpers. Candidate readiness, line limits, and the Phase 1 mtime/dedupe/cwd stop rule stay unchanged.
406
499
 
407
500
  ## E2E Required-Gate Evidence
408
501
 
@@ -475,21 +568,35 @@
475
568
  a stable scenario ID, owner, ISO-8601 deadline, and at least three observations;
476
569
  this closed zero-set contract rejects a non-empty row until execution/evidence
477
570
  and non-gating wiring exist. Therefore the quarantine execution Negative is
478
- N/A: its antecedent does not exist. Instead, the contract proves that both E2E
479
- child families still feed the exact `E2E Tests` compatibility context and the
480
- project-wide `Test` gate. Go `Unit Tests` remain an exact required child and
481
- outside automatic classification/quarantine: they have no per-test attempt
482
- artifact or `class` surface, so assigning an observed-flake count would invent
483
- evidence. A future unit quarantine first needs attempt-preserving per-test
484
- evidence and a separate contract.
571
+ N/A: its antecedent does not exist. `test/e2e/shard-contract.sh` is the single
572
+ canonical owner proving that both E2E child families still feed the exact
573
+ `E2E Tests` compatibility context and the project-wide `Test` gate. Go
574
+ `Unit Tests` remain an exact required child and outside automatic
575
+ classification/quarantine: they have no per-test attempt artifact or `class`
576
+ surface, so assigning an observed-flake count would invent evidence. A future
577
+ unit quarantine first needs attempt-preserving per-test evidence and a separate
578
+ contract.
579
+ - `test/e2e/admission-contract.sh` (`make test-e2e-contract`) pins the local
580
+ full-suite capacity boundary used by default `make test-e2e`: one invocation
581
+ owns the active state while another waits, and the wait diagnostic plus state
582
+ file expose `reason=local-full-suite-capacity`, capacity one, owner pid,
583
+ start time, cwd, and exact state path. Both callers retain their own terminal
584
+ status after admission. `PROJMUX_E2E_ADMISSION_BYPASS=1` is the explicit
585
+ stress path and is required to overlap a live admitted owner. Single-scenario
586
+ replay and CI shard/suite selectors remain outside this local full-suite
587
+ boundary; `test/e2e/shard-contract.sh` keeps the four Linux plus two suite
588
+ jobs on independent runners with `fail-fast: false` and the stable required
589
+ name `E2E Tests`, while also proving both selector families enter with a local
590
+ slot already occupied. A dead owner is diagnosed as `stale-owner` and reclaimed
591
+ only while the active hard link still identifies its exact owner record.
485
592
  - `test/e2e/evidence-contract.sh` (`make test-e2e-contract`) and
486
593
  `test/e2e/reliability-contract.sh` (`make test-e2e-reliability`) keep the persisted
487
594
  `projmux.e2e-attempt/v1` evidence and success result hash stable while adding
488
595
  a separate stderr-only `projmux.e2e-terminal/v1` first-failure record. Its
489
596
  closed fields identify scenario, phase, owner, shard, status, repository
490
597
  source/line, sanitized single-scenario command, binary/state hashes, and exact
491
- replay without raw argv. Synthetic L06/L08/L16 fixtures pin eight-racer lock
492
- state, bounded Registry changed paths plus pending owned state, and child/file/
598
+ replay without raw argv. Synthetic L06/L08/L16 fixtures pin cardinality-neutral
599
+ holder/racer event state, bounded Registry changed paths plus pending owned state, and child/file/
493
600
  exact tmux state plus sanitized hashed tails; denylist checks reject token,
494
601
  raw HOME/path, full Registry, and raw command leakage. Normal scenario meaning,
495
602
  one-build/four-shard topology, and `.bin/e2e-evidence` retention are unchanged.
@@ -521,11 +628,12 @@
521
628
  exposes the predicate as a truth table, and `e2e-evidence.py flake-rate <dir>`
522
629
  reports per-scenario flake rate over downloaded attempt evidence plus an
523
630
  `E2E_FLAKE_FLIP` line for every stored class the other attempts contradict.
524
- L06 diagnostics are derived the same way: `attribution` names what the racers
525
- were observed to do instead of always claiming lock exhaustion, `acquire` is
526
- no longer unconditionally `contended`, and `lock_age_ms` is paired with
527
- `lock_observed` so an unread lock file cannot report a scenario timer as lock
528
- age. That renamed payload travels as `projmux.e2e-diagnostic/v2` while the
631
+ L06 diagnostics are derived the same way: `attribution` names the event-time
632
+ racer outcome (`registry-lock-deadline`, `command-failure`, no observed racer,
633
+ or no racer failure) with no `other`/`unknown` fallback. Holder held/released
634
+ timestamps and racer PID/start/finish values are captured while those processes
635
+ run; a leftover compatibility marker cannot invent holder identity or timing.
636
+ The cardinality-neutral payload travels as `projmux.e2e-diagnostic/v3` while the
529
637
  unchanged L08/L16 payloads stay `v1`, and evidence retained from before the
530
638
  rename still validates and still aggregates. `PROJMUX_E2E_ATTEMPT` now defaults to `GITHUB_RUN_ATTEMPT`, so a rerun's
531
639
  evidence is no longer written as attempt 1.
@@ -554,11 +662,19 @@
554
662
  refuse a writer that never reached the lock.
555
663
  `TestAConcurrentWriterIsNotRefusedInsideAnotherCommitsMarkerWindow` builds that
556
664
  window directly and requires the second writer to wait through it. The
557
- `Race Tests` CI job runs the metadata, notify, and recent-window packages
558
- under `-race`.
559
- The real-tmux L06 burst still requires all eight exact-socket creates to
560
- converge on one Window with nine unique mirrored Panes and no lock or staged
561
- residue; it is a regression guard, not the evidence for the lock change.
665
+ `TestHostStartupAndShutdownHaveDeterministicHappensBefore` pins `StartHost`
666
+ return as the startup barrier: immediate and concurrent `Close` stop accept
667
+ before waiting for sessions, stop the broker before publishing the shared
668
+ terminal `Done`, and return every caller through that same channel.
669
+ `TestStaleRuntimeArtifactsAreReclaimedOnlyByExactOwnerProof/late_old_host_close_preserves_replacement_artifacts`
670
+ keeps shutdown cleanup behind `SameFile`, so an old Host cannot remove or
671
+ change a replacement socket or record. The stable `Race Tests` CI aggregate
672
+ now fails closed over separate core, Codex broker, and Codex app-server
673
+ children; the core child preserves metadata, notify, and recent-window
674
+ coverage, and `CIWorkflowContractTest` pins both Codex failure paths red.
675
+ Required real-tmux L06 owns only the deterministic holder/released-before-
676
+ deadline/waiter convergence guarantee. Eight-way throughput and fairness stay
677
+ outside required acceptance in the explicit stress command documented above.
562
678
  - `make test` / `make test-integration`: attention omitted-target convergence
563
679
  - `make test` / `make test-e2e`: Closed-Project startup mode selection is one adjudication shared by both entry points. `sidebarStartupPickerState` is the single read-only preference authority: no file resolves to `on/default`, saved `on` and `off` keep their byte meanings, and runtime plus Settings consume the same result. `TestSidebarStartupDefaultPolicyIsSharedReadOnlyAndLocaleStable` pins the en-US/ko-KR row/detail golden, action IDs, runtime/Settings parity, missing-file zero-create, and saved bytes/mtime preservation. `TestProjectStartupModeSelectionIsOneDecisionAcrossEntryPoints` drives no-file/on/off × registered/unregistered × explicit choice through the in-process open, sidebar emitter, and re-exec continuation, requiring one matching lifecycle entry. Saved `off` still uses `defaultProjectStartupMode`; `TestSidebarOpenPromotesUnregisteredRootToFreshWhenPickerIsOff` and `TestSidebarOpenKeepsRegisteredRootOnContinue` preserve that automatic split. No-file and saved `on` preserve explicit choice, including `TestSidebarOpenContinueOnUnregisteredRootKeepsTheSnapshotRefusal`; live-session bypass, `switch sidebar-open --mode` tokens, picker row order, trust ordering, and Continue/Open fresh engines remain unchanged. L11 step 7 removes the preference, observes both native rows, cancels back to Projects with zero config creation, then selects Continue. Step 9 writes exact `off`, forwards the legacy neutral `continue` against an unregistered root, requires Fresh adjudication and a new Project, and proves saved bytes/mtime unchanged on an isolated socket/HOME/XDG/TMUX_TMPDIR.
564
680
  is enforced by `TestAttentionMutationOmittedTargetMatchesExplicitPaneLedger`,
@@ -575,9 +691,1276 @@
575
691
  have non-zero status, stdout zero, bounded actionable stderr, and unchanged
576
692
  attention state.
577
693
 
694
+ ### Codex app-server generation Phase 0 tests
695
+
696
+ - `TestCodexEndpointAndAuthorityRefsRoundTripExactlyAndIgnorePaneGeneration`,
697
+ `TestLegacyCodexRefsRemainGenerationUnavailableWithoutInference`,
698
+ `TestLegacyHookReobservationPreservesAnExactCodexEndpointRef`, and
699
+ `TestCodexEndpointIdentityRejectsNonCanonicalWhitespace` own the additive
700
+ Registry schema: refs reopen exactly, remain independent of Pane generation,
701
+ reject noncanonical identities, preserve future exact refs across legacy
702
+ hook observations, and never infer a current endpoint for legacy state.
703
+ - `TestBoundedPoolRejectsEveryInvalidV1Topology`,
704
+ `TestBoundedPoolStateSpaceProperty`, and
705
+ `FuzzBoundedPoolNeverAcceptsMoreThanTwoLiveSlots` own the current-one plus
706
+ draining-one state model.
707
+ - `TestCompositeAuthorityRejectsSameNumberCrossGenerationAndLegacyWithZeroWrites`
708
+ and `TestOldStopBarrierKeepsSuccessorResumeAtZeroUntilSafe` own the Phase 0
709
+ write-zero fences for equal numeric epochs, missing generation identity, and
710
+ same-thread successor resume before old-stop.
711
+ - `TestReadOnlyUpgradePlanIsRepeatableContentFreeAndNamesExactBlockers` freezes
712
+ exact JSON/text blockers and all five zero mutation counters;
713
+ `TestReadOnlyUpgradePlanNeverRendersInvalidIdentitiesOrQualificationFields`
714
+ keeps hostile invalid input out of both projections.
715
+ - `TestQualificationDecisionTableClosesPhase2OnEitherSharedStateFailure` and
716
+ `TestQualificationReceiptRetainsOnlyContentFreeAllowlistedFields` own the
717
+ persisted go/no-go and redaction schema.
718
+ - `TestContentAddressedBundleSurvivesSourceRemovalForServerTUIAndHelpers`,
719
+ `TestBundleDriftAndProtocolMismatchRefuseBeforeFinalCommit`, and
720
+ `TestCommittedBundleTamperingIsRefusedBeforeLaunch`,
721
+ `TestCommittedBundleRefusesMatchingSymlinksBeforeLaunch`, and
722
+ `TestBundleRefusesNonCanonicalRootsAndVersions` own immutable complete
723
+ release-bundle retention and refusal, including manifest/artifact links and
724
+ noncanonical mutation roots.
725
+ - Opt-in `TestInstalledIsolatedGenerationPoolQualification` is the
726
+ declared-pair shared-state conformance. The pair under test comes from
727
+ `PROJMUX_CODEX_GENERATION_OLD_VERSION`/`_NEW_VERSION` and each declared
728
+ version must match what its binary reports; the canonical receipt is written
729
+ to `PROJMUX_CODEX_GENERATION_RECEIPT` before the verdict is asserted, so a
730
+ refusal is on disk too. It strips inherited tmux identity, uses one unique
731
+ private state root and two private sockets, records no provider content or
732
+ secrets, requires distinct-thread overlap plus the old-stop same-thread
733
+ barrier, tallies the receipt's coverage counters from the ledgers it already
734
+ writes, and performs exact cleanup. See
735
+ [codex-generation-pool.md](codex-generation-pool.md).
736
+ - `TestQualificationGateRefusesEvidenceCountersNoObservationBacks`,
737
+ `TestQualificationEvidenceIntegrityIsFixedByCounterAndClaim`, and
738
+ `TestQualificationSchemaVersionRefusesTheCoverageFreeReceipt` own the
739
+ evidence-counter integrity the gate checks and `Validate` cannot: a
740
+ self-consistent YES whose coverage counters back nothing is refused as forged,
741
+ and a coverage-free version-1 receipt does not decode.
742
+ - `TestActivateManagedCurrentRefusesEveryUnqualifiedRequestBeforeDraining`,
743
+ `TestActivateManagedCurrentPublishesTheQualificationItRanUnder`,
744
+ `TestActivateManagedCurrentRefusesAnExistingPoolWithNoReceipt`,
745
+ `TestResumeRefusesAnUnqualifiedPairBeforeAnyHandoverEffect`, and
746
+ `TestResumeStillRetiresAVacantGenerationWithoutAReceipt` own the two entry
747
+ gates: each refuses before its own irreversible step with a path-specific
748
+ token, the accepted receipt is published into the journal, and the vacancy
749
+ lane the planner leaves open stays open.
750
+ - `TestQualificationStoreAnswersOnlyForThePairAReceiptNames`,
751
+ `TestQualificationStoreRefusesAFileThatIsNotItsOwnReceipt`,
752
+ `TestQualificationStorePathStaysInsideItsOwnDirectory`,
753
+ `TestUpgradeQualifyCarriesAProducedReceiptToTheEntryPathStore`,
754
+ `TestUpgradeQualifyStoresNothingItWouldNotHonor`, and
755
+ `TestUpgradeQualifyRequiresExactlyOneAbsoluteReceipt` own the route from a
756
+ produced receipt to the store the entry paths read.
757
+ - `TestDeclaredGenerationPairAcceptsAnyDistinctReceiptVersionPair`,
758
+ `TestDeclaredGenerationPairRefusesEmptyEqualAndNonVersionTokens`,
759
+ `TestEmittedReceiptFileRoundTripsToTheIdenticalResult`,
760
+ `TestEmittedReceiptReplacesAnEarlierVerdictAndRefusesRelativePaths`, and
761
+ `TestGenerationQualificationSourcesCarryNoVersionLiteral` own the declared
762
+ pair and the receipt file: any distinct receipt version pair is accepted, the
763
+ emitted file is `0600` and byte-identical to the canonical result, a rerun
764
+ never republishes a stale verdict, and neither harness source may pin a
765
+ version literal again. Only the pair is declared — every evidence boolean and
766
+ counter stays measured.
767
+ - `TestEmittedQualificationReceiptEntersTheUpgradeRequestVerbatim` carries the
768
+ emitted receipt file into the `qualification` field of an `agent app-server
769
+ upgrade --request` document and reloads it through the real request loader.
770
+ The declared pair, every counter, `Validate`, and `GateQualification` survive
771
+ the hop, and a tampered evidence counter stops decoding instead of keeping
772
+ its verdict.
773
+ - `CIWorkflowContractTest.test_generation_pool_lane_is_dispatch_only_and_fail_closed`
774
+ and `test_generation_pool_runner_types_every_terminal_outcome` own the
775
+ dispatch-only lane and `scripts/test-generation-pool-qualification.sh`: the
776
+ lane never schedules, never joins a required aggregate, always uploads its
777
+ typed record, and is green only on a measured `pass`; the runner refuses a
778
+ non-version pair before creating any root and still types an unavailable
779
+ binary as `unsupported`.
780
+
781
+ ### Codex app-server generation Phase 1 tests
782
+
783
+ - `TestGenerationLifecycleProjectionClosedTable` and
784
+ `FuzzGenerationLifecycleProjectionMatchesClosedTable` own the single 6×7
785
+ effective-interaction × generation-state tuple. Planned drain, handover,
786
+ recovery, and blocked projections require an exact operation endpoint;
787
+ `TestPlannedGenerationProjectionRequiresExactDurableOperationRef`,
788
+ `TestMarkerlessCrashAndVersionDriftRemainOrdinaryFailure`,
789
+ `TestMarkerlessCrashAndVersionDriftRemainOrdinaryFailureThroughProductionReconcile`, and
790
+ `TestLifecycleProjectionAuthorityInputsExcludeProcessHeuristics` keep
791
+ process exit, version, executable, path, socket, and PID evidence from
792
+ becoming maintenance authority.
793
+ - `TestRuntimeMutationEquivalenceTableIsClosed`,
794
+ `TestRuntimeMutationClassesMatchDecisionKernel`, and
795
+ `TestRuntimeMutationCompositeFenceAndSiblingRecorder` own the eight
796
+ owner/foreign × current/stale × target/sibling classes. Exactly one class
797
+ has a semantic effect; equal numeric epochs from another endpoint generation
798
+ or restarted broker invoke neither Registry nor tmux writer.
799
+ - `TestGenerationLifecycleSinkCompositeAuthorityHasZeroCrossWrites` exercises
800
+ the application sink with a Registry, fake tmux, and the exact-Pane authority
801
+ fence. Endpoint generation, broker runtime, connection epoch, binding epoch,
802
+ and stored operation mismatches return the managed-observation refusal with
803
+ zero Registry and tmux writes. A syntactically valid provider-supplied
804
+ operation ID cannot replace the durable operation authority.
805
+ - `TestNativeSemanticApplyAndInvalidationShareExactPaneFence` deterministically
806
+ pauses an older provider-control Apply after its Registry commit and before
807
+ its Pane writes, starts disconnect invalidation behind the same exact-Pane
808
+ fence, and requires invalidation to remain the final Registry/Pane tuple.
809
+ It then pins the recovery order separately: a replacement snapshot may Apply
810
+ while authority is `invalidating`, followed by the exact ready authority.
811
+ - `TestGenerationLifecycleProjectionReconcileWritesOnceThenZero`,
812
+ `TestGenerationLifecycleProductionReconcileRejectsForeignOrSiblingAuthorityWithZeroWrites`, and
813
+ `TestGenerationLifecycleProjectionUsesIsolatedRealTmuxAndExactCleanup` own
814
+ production fake and isolated-real-tmux convergence for draining,
815
+ handover-pending, recovering, and blocked tuples. They prove the durable
816
+ lifecycle/activation input survives a later full resource reconcile and
817
+ makes its second pass zero-write. The real fixture strips inherited
818
+ `TMUX`/`TMUX_PANE`, starts on one unique contained physical `-S` path, accepts
819
+ that path only from the successful `new-session` Pane/socket receipt, waits
820
+ on tmux command completion as its semantic barrier, and kills/removes only
821
+ that queried and containment-proven socket; there is no logical-name cleanup
822
+ fallback.
823
+ - `TestPlanOnlyMutationProductSurfaceInventoryIsBidirectionalAndClosed` keeps
824
+ generation presentation under the existing `agent.presentation` typed
825
+ surface and requires the complete composite fence in its guard. Existing
826
+ pure reducer PBT, Phase 0 endpoint/authority tests, broker reconnect tests,
827
+ and C01 remain their canonical unique boundaries. The complete
828
+ old/new-mutant migration receipt is in
829
+ [codex-generation-pool.md](codex-generation-pool.md#mapping-and-authority-test-migration-ledger).
830
+
831
+ ### Provider capability matrix Phase 0 tests
832
+
833
+ - `TestAgentCapabilityCatalogIsClosedCartesianMatrix`,
834
+ `TestAgentCapabilityCatalogPinsCurrentGroupsAndDeferredVocabulary`, and
835
+ `TestAgentCapabilityCatalogPinsCodexNativeAndSharedFamilies` own the single
836
+ action × provider catalog, including all eight app-server generation leaves,
837
+ the four unsupported future coordination cells, the four integration
838
+ targets, and the three usage providers.
839
+ - `TestAgentCapabilitiesProviderProjectionIsStaticAndReadOnly` and
840
+ `TestAgentCapabilitiesExactProjectionSeparatesRuntimeEpochAndAvailability`
841
+ own the provider-only versus exact-Agent projection and prove that Registry
842
+ reads, live transport, and provider writes are not smuggled into static
843
+ support. `TestUnsupportedNativeAgentActionsRefuseBeforeRuntimeOrProviderEffects`
844
+ owns the pre-runtime non-Codex refusal for turn, approval, and review.
845
+ - `TestProviderResumeExecutionPreservesExactAgentAndLaunchesOneExactArgv`
846
+ owns the three-provider resume execution matrix: one exact child argv, stable
847
+ Agent uid/name, and no fresh Agent creation.
848
+ - `TestAgentCapabilityCatalogRoutesMatchExecutableHelpGraph` and
849
+ `TestAgentIntegrationAndGenerationLeafHelpMatchesCapabilityCatalog` own
850
+ catalog ↔ CLI/help parity. The generated CLI reference test owns the docs
851
+ projection; provider-neutral `agent message` and `agent wait` are the only
852
+ coordination routes, and provider-specific Agent namespaces remain absent.
853
+
854
+ ### Resume Picker exact projmux binding Phase 0 tests
855
+
856
+ - `TestAIResumeExactBindingProjectsCoherentAgentAndPaneNames` owns exact
857
+ provider/conversation sessionRef selection: prefer a round-trip Pane owner,
858
+ then lexical Agent UID, and project that same Agent's context and stable
859
+ name with its optional Pane stable name. Duplicate ordering cannot combine
860
+ different Agents' fields. Stable names come only from `metadata.name`.
861
+ - `TestAIResumeBindingDisplayFallsBackWithoutRoundTripPane` pins the three
862
+ binding-first row forms: `[agent → pane] conversation`, `[agent] conversation`,
863
+ and the unchanged conversation-only row. Missing/stale/foreign Pane bindings
864
+ omit only the Pane; missing Agent bindings retain exact Value, count, and
865
+ order. No cwd, topic, live tmux title, newest resource, or pane-order guess
866
+ recovers a deleted binding.
867
+ - `TestAIResumeBindingNamesPreserveFullSearchAndDetailWithinCellBudget` pins
868
+ the existing 90-cell trailing budget, balanced binding brackets, ANSI/CJK
869
+ clipping, 128-byte stable names, and full names in SearchKey and selected
870
+ detail projection. `TestAIResumeBindingLongNamesAreRecoverableByNativeDetailScroll`
871
+ reconstructs complete 128-byte and CJK names from the app-local, 64-cell
872
+ continuation lines and verifies native Shift-Down exposes every chunk at
873
+ 80 columns in EN/KO. The canonical full binding line remains intact; only
874
+ overflowing bindings add continuation fields under Details.
875
+ `TestAIResumeBindingNativeFramesLocaleAndSizeGolden`
876
+ exercises the public native runner on an isolated PTY/Registry fixture at
877
+ 80x24 and 120x40 in EN/KO; short Agent and Pane names are visible together in
878
+ both the row and detail. The PTY harness uses Python 3's standard library.
879
+ - `TestAIResumeBindingSnapshotIsInvocationLocalAndAddsNoReadsOrWrites`
880
+ pins one Registry load per invocation, cached detail reads, unchanged
881
+ row/order/Value/footer after focus, zero extra command/file reads and writes,
882
+ and next-open conversation fallback after Registry deletion. Binding names
883
+ are an invocation snapshot: rename/deletion is observed on the next open,
884
+ historical names are not persisted or reconstructed after cascade. Existing
885
+ title suppression/precedence, provider settlement/status, exact routing,
886
+ and first-frame bound suites remain the authority for those unchanged paths.
887
+ ### Resume Picker provider session title Phase 1 tests
888
+
889
+ - `TestClaudeExplicitTitleProvenanceWithinBoundedScan` pins top-level
890
+ `type:ai-title` / `aiTitle`, first valid explicit title, duplicate stability,
891
+ foreign session rejection, blank/full-ID/short-ID rejection followed by a
892
+ valid title, and the line-100 inclusive bound with no line-101 read. An
893
+ ordinary Claude prompt cannot stop the scan before a later explicit title.
894
+ Explicit titles normalize whitespace without prompt-noise filtering.
895
+ - `TestCodexFirstRealPromptHasDerivedTitleProvenance` pins the first typed
896
+ `user_message` event or user-role response text after scaffold, assistant,
897
+ tool-carrier, untyped, and wrong-provider records. The label is marked
898
+ `derived-user-prompt`; Claude `ai-title` and Codex app-server non-ID titles
899
+ are `explicit-provider-title`. `TestNativeTitleProvenanceRejectsBlankAndIDCandidates`
900
+ keeps blank/full-ID/short-ID native candidates without title authority.
901
+ - `TestAIResumeTitleAuthorityProviderSourceProvenanceMatrix` closes the
902
+ provider × source × provenance × ID/context table. For Claude/Codex the
903
+ conversation label order is explicit title, derived Codex rollout prompt,
904
+ exact-bound Agent topic, exact-bound Agent provider, localized Untitled.
905
+ Canonical titles never lose to the bound Agent's topic/provider; stable
906
+ binding names remain independent. Antigravity keeps its existing known-source
907
+ title and context-first behavior.
908
+ This narrowly supersedes the earlier Resume Picker prohibition on prompt
909
+ inference and context-before-provider-title policy: only bounded Codex
910
+ typed real-user prompts gain derived display authority, matching the adopted
911
+ native Codex resume-title policy. Ordinary Claude prompts, missing/unknown
912
+ provenance, unknown/mismatched sources, and ID-like titles remain rejected;
913
+ no summary generation or additional transcript read is introduced.
914
+ `TestAIResumeMissingProvenanceDoesNotInferConversationTitle` preserves the
915
+ rejection boundary when a caller provides only a transcript/rollout string.
916
+ - `TestResumeSummaryPreservesTitleProvenanceWithoutAdditionalReads` and
917
+ `TestAIResumeTitleDiscoverySummaryRoundTripNeedsNoTranscriptReread` carry the
918
+ content-free discriminator through SessionMeta → ResumeSummary → SessionMeta.
919
+ Real disposable Claude/Codex discovery is projected after its transcript has
920
+ been removed; row/detail title and exact provider/ID Value remain available.
921
+ `TestAIResumeBindingSnapshotIsInvocationLocalAndAddsNoReadsOrWrites` now
922
+ exercises a canonical title with binding while keeping one Registry load,
923
+ cached detail reads, zero extra reads/writes, frozen row/order/Value/footer,
924
+ and next-open binding deletion behavior.
925
+ - `TestAIResumeProviderTitleBindingNativeFramesGolden` exercises the public
926
+ native runner on isolated PTYs at 80x24 and 120x40 in EN/KO for Claude explicit,
927
+ Codex rollout derived, and Codex app-server explicit rows. Each source is
928
+ selected in turn; the golden requires combined binding/title rows, the exact
929
+ provider/title detail heading, and full conversation ID independently of
930
+ preview text. Existing Phase 0 binding, provider settlement/latency, privacy,
931
+ frozen-frame, and exact-routing suites retain their unchanged contracts.
932
+
933
+ ### Codex observer disconnect-reason Phase 0 tests
934
+
935
+ - `TestCodexObserverEventLoopExitsCarryTheirOwnReasonToken` owns the exit-path
936
+ table: every way out of the observer event loop publishes the vocabulary
937
+ token for that path, and the authority writes it pins prove the recovery
938
+ strategy did not move when the tokens became precise. A cancelled observer,
939
+ a closed notification stream, and a real endpoint disconnect no longer share
940
+ a word.
941
+ - `TestCodexObserverExitPathsAreOneToOneWithTokens` owns the converse
942
+ property: the exit tokens are distinct, neither the `unrecorded` nor the
943
+ retired `disconnected` bucket is reachable from an exit, and `hold` is a
944
+ stored decision rather than a comparison against the published reason.
945
+ - `TestCodexObserverReasonVocabularyIsClosed` owns the single vocabulary.
946
+ `codexNativeReason`'s four open-failure tokens are members of it, every
947
+ token round-trips through `safeCodexAuthorityReason`, and anything outside
948
+ it renders as `bounded reason unavailable`.
949
+ - `TestCodexBrokerEpochRecordsWhyItsStreamClosed` owns the close cause at the
950
+ layer that knows it. All five calls that end a broker epoch record a
951
+ different token, which is what separates an upstream connection that went
952
+ away from a rotated barrier, a revoked binding, a backlog overflow, and an
953
+ observer that closed its own connection.
954
+ - `TestCodexObserverJournalRecordsConnectDisconnectReconnectInOrder` owns the
955
+ durable history the pane option cannot hold: connect, disconnect, and
956
+ reconnect records in time order, each carrying the epoch label, and no
957
+ fallback record behind a held disconnect.
958
+ - `TestCodexObserverJournalRecordFieldsAreWhitelisted` is the negative audit.
959
+ It names every column an observer record may carry and refuses an
960
+ out-of-vocabulary reason and an unnamed transition.
961
+ - `TestCodexObserverJournalCoalescesRepeatedTransitions` owns the write-rate
962
+ boundary on the shared `ai-ingest.log` sink: one record per
963
+ (transition, reason) pair per window, with the suppressed count riding the
964
+ next record so the flap rate stays readable.
965
+ - `TestObserverRecoversIntoANewEpochAfterConsumerBacklogOverflow`,
966
+ `TestBacklogOverflowStopIsDistinguishableFromARecoveringInvalidating`, and
967
+ `TestBacklogOverflowRecoverySurvivesATransientBindingReadFailure` own
968
+ recovery after a consumer-side backlog overflow. All three drive the real
969
+ broker runtime, the real binding session, and the real observer loop: the
970
+ observer is held inside one sink write until its epoch's bounded stream
971
+ overruns, which is the actual `end(backlog-overflow)` plus `resync` pair
972
+ rather than an injected close cause. A sibling Agent holds its own binding on
973
+ the same shared connection throughout, because the overflowing binding being
974
+ the broker's last one retires the upstream connection and would make these
975
+ measure an endpoint reconnect instead. The first pins the guarantee - Open is
976
+ called again after the resync, the replacement barrier publishes a new epoch
977
+ label, and the overflowed epoch's records are exactly connected/ready,
978
+ disconnected/backlog-overflow, reconnecting/backlog-overflow. The second owns
979
+ the operator distinction: when the exact binding stops being current the
980
+ scheduler is right to stop, but the Pane keeps the
981
+ `invalidating|<epoch>|backlog-overflow` projection and `SetAuthority` refuses
982
+ every later correction, so `observer.stopped` with result `stuck` is the only
983
+ surface on which a stuck Pane differs from one still reconnecting on the same
984
+ projection. The third owns the defect behind that shape: `BindingCurrent`
985
+ returns false both for a replaced binding and for a Registry or `tmux` read
986
+ that merely failed, and the recovery scheduler took its only non-cancel exit
987
+ on a single such sample. It now makes the same bounded wait the loop head has
988
+ always made, so a transient read failure no longer retires a live activation.
989
+ - `TestObserverRecoversWhenTheOverflowedBindingWasTheBrokersLast` and
990
+ `TestLastBindingOverflowRecordsEveryFailedReplacementOpen` own the same
991
+ overflow under the opposite premise: no sibling binding, so the resync takes
992
+ the runtime to zero bindings. `Broker.wanted()` is `len(b.bindings) > 0` and
993
+ therefore broker-global, so `serve` folds the shared upstream connection and
994
+ no replacement barrier can exist until it is reopened. That premise is not
995
+ exotic - it holds permanently for a user who runs one Codex pane - and the
996
+ fold itself is a race, because a rebind that lands before `serve` processes
997
+ the unbind wake leaves the connection up. The fixture gates every replacement
998
+ Open on the fold so the premise is a fact rather than a hope. The first pins
999
+ that the fold self-heals whenever upstream is reachable. The second owns the
1000
+ defect: while upstream is away every replacement Open blocks to its own open
1001
+ timeout - 15s in production - and the scheduler computed the failure's
1002
+ vocabulary token and discarded it, so a Pane sat on
1003
+ `invalidating|<epoch>|backlog-overflow` with no record of any attempt, which
1004
+ is byte-identical to the stuck Pane `observer.stopped` exists to name except
1005
+ that recovery really was running and so no terminal record arrived either.
1006
+ The failure is now recorded on the retry transition, keyed apart from the
1007
+ epoch-loss token so the journal's coalescing window folds a long outage into
1008
+ one line plus a repeat count, and the disconnect's reason column is untouched.
1009
+ - `TestLastBindingRevokedByOverflowFoldsTheUpstreamConnection`
1010
+ (`internal/integrations/agents/codexbroker`) owns the invariant `wanted()`
1011
+ exists for, against the involuntary revocation paths. `Bind` and
1012
+ `Binding.Close` both signal the supervisor because both change what the
1013
+ connection has left to serve; the involuntary paths - a bounded delivery queue
1014
+ that overflowed, a thread-scoped snapshot refusal - deleted the binding from
1015
+ routing with no wake, so a runtime could reach zero bindings with nothing
1016
+ pending on the wake channel and `serve` would stay in its select holding an
1017
+ upstream connection nothing is bound to until the host idle timer recycled the
1018
+ whole runtime. A one-deep queue makes the overflow exact rather than a volume
1019
+ guess, and the test binds exactly one thread and never replaces it, because a
1020
+ rebind carries its own signal and would hide the gap.
1021
+ - `TestLiveEpochSurvivesATransientBindingReadFailure` and
1022
+ `TestLiveEpochBindingLossLeavesATerminalRecord` own the live epoch's
1023
+ `bindingTicker` exit, which carried the same single-sample defect the recovery
1024
+ scheduler had. One false from `BindingCurrent` ended the observer, and that
1025
+ exit wrote no authority - correctly, because `SetAuthority` refuses once the
1026
+ predicate is false - and no record, so the Pane kept
1027
+ `provider-control-plane|ready` with no producer behind it. That is the inverse
1028
+ of a stuck Pane: nothing looks wrong at all. The path now makes the same
1029
+ bounded wait as the loop head, and a binding that is provably gone leaves an
1030
+ `observer.stopped` record with result `stuck` and reason `binding-replaced`.
1031
+ That token is deliberately not `binding-revoked`: the four close-cause tokens
1032
+ answer "who closed the stream" and come from the broker epoch, and here the
1033
+ stream was never closed at all.
1034
+ - `test/e2e/codex-lifecycle.sh` (C01) pins the exact disconnect token at its
1035
+ disconnect projection barrier and at the reconnect-gap hook comparison. It
1036
+ waited on the literal `disconnected` before, which was the bucket published
1037
+ when the observer recorded no reason at all; killing the exact E1 proxy takes
1038
+ the upstream connection away, so the projection now reads
1039
+ `endpoint-suspended`. The barrier is still an exact string match, so a path
1040
+ that stops capturing its reason lands on a different token and fails it. That
1041
+ barrier also now gates on `@projmux_attention_state`: the projection writes
1042
+ its three pane options as three separate `set-option` calls, so releasing on
1043
+ the first two let the five-field assertion 46 lines later read a torn
1044
+ projection under load. The assertion is unchanged and still owns the verdict.
1045
+ - `TestManagedCodexAuthorityDoctorSeparatesFlappingFrozenAndStopped` owns the
1046
+ operator surface. The census carries a count-only reason distribution and
1047
+ `doctor` renders it, which is what tells flapping, frozen, and stopped
1048
+ apart; `TestManagedCodexAuthorityCensusSeparatesDeclaredFromUnexplainedFallback`
1049
+ keeps that distribution pinned alongside the source counts.
1050
+
1051
+ ### Claude coordination endpoint Phase 1 tests
1052
+
1053
+ - `TestAgentRouteClaudeIdentityTransitions`, `TestClaudeRegistrationAdmissionAndCleanupCAS`,
1054
+ and the `TestAgentRoute` / `TestClaudeRegistration` model tests own the sealed
1055
+ provider authority union, existing Codex composite fence, exact Claude process
1056
+ and registration claims, same-UID rename preservation, and stale registration
1057
+ admission/cleanup replay. The property model retains historical routes and
1058
+ requires stale replay to leave current state unchanged.
1059
+ - `TestClaudeEndpoint` unit tests own private lease probes, kernel peer identity,
1060
+ socket owner/mode/inode checks, nested producer refusal, creator-selected
1061
+ activation context, read-only capability observation, and token/locator
1062
+ residue scans. Private readiness probes never connect to a provider inbox.
1063
+ - `test/integration/claude-endpoint-binding.sh`, included in
1064
+ `make test-integration`, builds the product and runs
1065
+ `TestClaudeEndpointProcessIntegration` through supervisor, activation gate,
1066
+ public SessionStart hook, and detached helper. It covers registration
1067
+ replacement, exact-version qualification without inheritance, two exact
1068
+ auth+frozen-user-frame pushes, provider exit cleanup, and zero token/locator
1069
+ residue in output and live lease files. The synthetic socket runs no model,
1070
+ account, connector, tool, MCP server, or external service.
1071
+ - `TestClaudeEndpointInstalledSourceGate` is opt-in evidence from a disposable
1072
+ installed Claude one-shot. It checks public SessionStart identity, exact
1073
+ readiness, exit invalidation, empty tools/MCP/plugins, and zero tool-use.
1074
+ Raw provider streams remain in memory. This is the Phase 1 registration source
1075
+ gate; it does not satisfy or relax Phase 4's separate exact-version,
1076
+ hook-enabled, long-lived provider qualification. A safe-mode one-shot cannot
1077
+ substitute because safe mode disables the hooks under test. Setup and limits are in
1078
+ [claude-coordination-endpoints.md](claude-coordination-endpoints.md).
1079
+
1080
+ ### Claude coordination endpoint lifecycle tests
1081
+
1082
+ - `TestClaudeEndpoint*`, `TestClaudeCoordinationSocketReplacementInvalidatesWithoutRemovingReplacement`,
1083
+ and `TestCleanupClaudeActivationLeasesPreservesCoordinationReplacement` retain
1084
+ exact SessionStart registration, process/Pane/generation authority, private
1085
+ lease and coord-socket inode cleanup, helper-death invalidation, and
1086
+ same-path replacement preservation.
1087
+ - `TestClaudeEndpointProcessIntegration` uses a synthetic provider process and
1088
+ the real supervisor/helper entrypoints. It accepts exactly the auth line and
1089
+ one frozen user frame, completes exact Stop-marker qualification, pushes a
1090
+ durable broker record, replaces the registration without inheriting
1091
+ qualification, freshly qualifies again, and proves secret/output/file
1092
+ residue zero. It runs no model or network provider.
1093
+ - `TestClaudeCoordinationServerHasNoWaiterIngressOperations` refuses historical
1094
+ `wait`, `waiter-ready`, `begin-handoff`, and `receipt` operations. Integration
1095
+ tests require SessionStart registration plus short synchronous Stop reply and
1096
+ UserPromptSubmit boundary hooks, with zero `asyncRewake` ingress commands.
1097
+
1098
+ ### Provider-neutral message broker Phase 3 tests
1099
+
1100
+ - `TestAgentCapabilityCatalogPinsCurrentGroupsAndDeferredVocabulary`,
1101
+ `TestAgentCapabilitiesClaudeMessageCellsReflectMissingRegistrationLease`, and
1102
+ `TestAgentCapabilityCatalogRoutesMatchExecutableHelpGraph` supersede the Phase
1103
+ 0 placeholder boundary: `agent message send|wait|status` and `agent wait` are
1104
+ callable through one provider-neutral catalog, while Antigravity and Claude
1105
+ inbox claim remain explicit unsupported cells and exact Claude availability
1106
+ follows its current registration lease.
1107
+ - `TestAuthorityMatrixIsExhaustiveAndPeerNeverEscalates`,
1108
+ `TestEnvelopeRetryAndReplyCorrelationRandomizedProperties`, and
1109
+ `TestPublicReducerMatchesReferenceModelForRandomSequences` own the exhaustive
1110
+ authority table, immutable versioned envelope, exact reversed reply route and
1111
+ conversation, idempotent message reference, and terminal-once public reducer.
1112
+ Peer input remains untrusted coordination and cannot become a user turn,
1113
+ approval, configuration, tool, connector, or model-history write.
1114
+ - The `TestStore*` suite owns the private 0700/0600 store, advisory locking,
1115
+ atomic replace, bounded payload/file/record count, terminal retention,
1116
+ unknown-field refusal, concurrent claim, restart, timeout, and post-handoff
1117
+ `failed` plus unknown-outcome contract. Codex claim touches only the exact
1118
+ activation-scoped inbox and leaves unrelated Claude records unchanged.
1119
+ - `TestAgentMessageSendGatesBothStaticCellsAndRoutesBeforeStoreOrAdapter`,
1120
+ `TestAgentMessageUnsupportedProviderStopsBeforeRouteAndStore`,
1121
+ `TestAgentMessageSendSameReferenceRetryKeepsBrokerCorrelationAndDoesNotResend`,
1122
+ `TestAgentMessageConcurrentSameReferenceUsesOneConversation`, and
1123
+ `TestAgentMessageReplyReturnsOnlyToOriginalSourceConversation` pin exact
1124
+ current-Pane source authority, exact target eligibility before effects,
1125
+ same-envelope retry without resend, and broker-owned reply correlation.
1126
+ - `TestClaudePrivateProjectionPreservesPublicBoundaryAndAmbiguity`,
1127
+ `TestLiveClaudeAdapterMapsTerminalResponseKindsWithoutDelivery`,
1128
+ `TestLiveClaudeAdapterRefusesPublicValidEnvelopeThatExceedsPrivateFrame`, and
1129
+ `TestAgentMessageBrokerHasNoCodexOrUserTurnWritePath` own the Phase 2 Claude
1130
+ projection, transport-bound refusal, foreign receipt no-op, and zero Codex
1131
+ app-server, user-turn, raw-terminal, or model-history write path.
1132
+ - `TestAgentMessageWaitClaimsOnlyCurrentExactActivation`,
1133
+ `TestAgentMessageClaimDefaultOutputEscapesTerminalControlsAndKeepsEnvelope`,
1134
+ `TestAgentMessageWaitDetectsGenerationChangeBeforeClaim`, and
1135
+ `TestAgentWaitRegistryIdleReadyTimeoutAndStale` pin full-envelope self-claim,
1136
+ terminal-safe output, activation fencing, and deterministic Registry-only
1137
+ idle, timeout, and stale waits. Codex `delivered` ends at target self-claim;
1138
+ it does not assert model processing or reply.
1139
+
1140
+ ### Heterogeneous Agent dialogue Phase 4 tests
1141
+
1142
+ - `TestClaudeCoordinationPrivateBridgeRequiresExactV5Route`,
1143
+ `TestClaudeCoordinationV1ThroughV4HelpersCannotReceiveV5Traffic`, and
1144
+ `TestHeterogeneousDialogueLifecycleUpgradeFenceMatrix` pin public/private v5
1145
+ route equality, old generation, same-generation old incarnation, stale
1146
+ provider process, foreign socket, helper exit, Codex endpoint replacement,
1147
+ and fresh current-version requalification. Old routes write/claim zero; a new
1148
+ endpoint writes zero before qualification and exactly one after it. The v4
1149
+ fence applies only to the existing coordination UDS protocol: v3 Stop-qualified
1150
+ helpers cannot pass current lease/eligibility checks. The frozen vendor
1151
+ auth-plus-user frame is unchanged; recovery retains the public same-UID path.
1152
+ - `TestHeterogeneousDialogueIdleActiveSafeBoundaryMatrix` and
1153
+ `TestClaudePushDuringOpenHumanTurnNeverCorrelatesThatTurnsStop` preserve
1154
+ immediate idle/active push without a waiter or interruption. Every ordinary,
1155
+ recursive, late, or unrelated Stop publishes zero replies.
1156
+ - `TestClaudeQualificationRequiresBrokerChallengeAndExplicitReply` requires a
1157
+ 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.
1160
+ - `TestClaudeExplicitReplyRejectsForeignStaleAndAlteredCorrelationBeforeCommit`
1161
+ covers foreign refs, altered conversations, stale generations/incarnations,
1162
+ reversed-route mismatch, and a wrong qualification response with commit zero.
1163
+ - `TestClaudeExplicitMultipleRequestsAndHumanOverlapSelectOnlyNamedOriginal`
1164
+ permits an explicit reply to B then A across human activity, rejects a second
1165
+ reply to either request, and never chooses correlation by arrival order.
1166
+ - `TestClaudeQualificationPublishesOriginalBeforeConcurrentExplicitReply`
1167
+ synchronizes an immediate provider response with durable challenge publication;
1168
+ reply timing cannot expose a missing original.
1169
+ - `TestClaudeExplicitQualificationMissingToolProofOrOriginalWritesZero`,
1170
+ `TestClaudeExplicitQualificationPartialAndTimeoutNeverResend`, and
1171
+ `TestClaudeQualificationResponseRequiresExactStableClosedShape` pin exact
1172
+ version/process/lease evidence, the single Bash execution-gate assertion,
1173
+ missing original request, partial write, timeout, and no automatic resend.
1174
+ - `TestStoreMigratesV1EnvelopeToUnqualifiedStaleFence` uses literal v1 JSON
1175
+ without incarnation fields. `TestStoreReplyIsAtomicCorrelatedAndReplayIdempotent`
1176
+ pins reversed routes, original conversation/replyTo, crash-before-rename
1177
+ recovery, restart replay, and exactly one committed reply.
1178
+ - `TestClaudeBrokerStoreLayoutAndImmutableEnvelopeMismatchAreExact` and the mismatched
1179
+ same-reference case require the helper and public CLI to share one canonical
1180
+ message store and reject an immutable-envelope mismatch without mutation.
1181
+ `TestClaudePushNeverHoldsAndQualificationBrokerFencePrecedesProviderWrite`,
1182
+ `TestClaudePushWriteOutcomeAndDurableReceiptAreTerminalOnce`,
1183
+ `TestClaudePushRechecksDeadlineAfterDurableHandoffBeforeProviderWrite`,
1184
+ `TestClaudeProviderPushFinalRouteCheckPrecedesSoleWrite`,
1185
+ `TestLiveClaudeAdapterClosesMalformedSubmitResponsesAsAmbiguousTerminal`, and
1186
+ `TestClaudeCoordinationBrokerPersistenceFencesProviderWriteAndIsTerminalOnce`
1187
+ compose the persistence proof: durable handoff failure leaves provider bytes
1188
+ zero, durable delivered failure after a full write remains ambiguous and
1189
+ reply-ineligible, and a successful durable terminal repeats without another
1190
+ provider write.
1191
+ `TestAgentMessageLifecycleLeavesInteractionAndBadgeAuthorityUntouched`
1192
+ carries both in-progress and approval-required Registry projections through
1193
+ send/handoff/delivered/reply/claim, proves terminal-once, and proves Registry
1194
+ and badge writes remain zero.
1195
+ - `TestAIIntegrateClaude*` pins short compatibility Stop and
1196
+ `UserPromptSubmit` commands, zero SessionStart/Stop ingress waiter and zero
1197
+ `asyncRewake`, v1/v2-to-v3 marker convergence, user-hook preservation,
1198
+ remove, and idempotence.
1199
+ `TestClaudeAutomaticMigrationPreservesEveryCoordinationVersionUntilExplicitIntegration`
1200
+ and `TestObsoleteAndCurrentClaudeCoordinationHooksNeverRunAutomaticMigration`
1201
+ keep config apply/install and already-running historical hook children from
1202
+ rewriting global settings; only explicit integration converges v1/v2 to v3.
1203
+ - `TestAgentCapabilitiesClaudeMessageCellsReflectMissingRegistrationLease`,
1204
+ `TestProviderResumeExecutionPreservesExactAgentAndLaunchesOneExactArgv`, and
1205
+ the pre-revalidation rows of
1206
+ `TestHeterogeneousDialogueLifecycleUpgradeFenceMatrix` compose the rollout
1207
+ recovery proof: a missing lease exposes the exact integrate/normal-exit/resume
1208
+ action and permits delivery zero; Claude resume rematerializes one exact child
1209
+ while preserving Agent UID/name/count; and delivery remains zero until the
1210
+ new Ready lease and route incarnation are revalidated.
1211
+ - Required selectorless E2E scenario `L20` is the only deterministic full
1212
+ heterogeneous round trip. It uses isolated XDG/tmux roots, a payload-free
1213
+ long-lived fake Codex child plus typed `StageCodexEndpoint` /
1214
+ `BindCodexActivation` setup for one pre-existing synthetic route, and official
1215
+ Claude registration plus explicit public reply commands. A public capability barrier proves the exact Codex
1216
+ composite authority before send; semantic barriers replace sleeps. The
1217
+ scenario requires exact UID/Pane/generation/message/conversation/reply
1218
+ 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.
1221
+ - The real-provider path is opt-in through `scripts/agent-dialogue-canary-setup.py`
1222
+ and `docs/heterogeneous-dialogue-canary.md`. It uses public reply-only activation,
1223
+ product observer/helper-memory evidence and separate qualification/idle claims.
1224
+ Actual model action is compared with the pinned guard and successful broker
1225
+ commit; delivered alone is insufficient. Partial setup and run failures share
1226
+ the owned pidfd writer barrier. Candidate and runner digests are pinned before
1227
+ launch, and success is published only after root removal. Active/human/recovery
1228
+ and installed cases remain distinct observations. Provider/version stress is
1229
+ separately opt-in and never joins required selectorless E2E.
1230
+
1231
+ ### Provider hook pane identity tests
1232
+
1233
+ - `TestProviderHookCommandsCarryTheirOwnPaneIdentity` and
1234
+ `TestProviderHookPaneArgumentIsProviderNeutral` own the installed hook command
1235
+ string: every provider hook is handed the Pane it belongs to through the same
1236
+ argument, the argument reads the activation envelope rather than the tmux
1237
+ environment, and an app-server shared by several Panes expands it to an empty
1238
+ value instead of breaking the hook.
1239
+ - `TestCodexIntegrationStillOwnsThePaneBlindHookCommand` and
1240
+ `TestCodexIntegrationConvergesThePaneBlindHookCommandForward` own the
1241
+ convergence of an already installed pane-blind command: it stays recognized as
1242
+ projmux-authored and is rewritten forward instead of being refused as
1243
+ hand-edited wiring.
1244
+ - `TestMatchAIPaneResolvesTheHandedPaneWithoutTmuxEnvironment` and
1245
+ `TestMatchAIPanePrefersTheHandedPaneOverInheritedEvidence` own attribution
1246
+ when the hook was handed an identity: the Registry resolves it with no tmux
1247
+ server read at all, and it wins over inherited `TMUX_PANE`, cwd, and thread
1248
+ evidence.
1249
+ - `TestMatchAIPaneKeepsTheEstablishedFallbackWhenNothingWasHandedOver` owns the
1250
+ unchanged three-step ladder for hooks that were handed nothing.
1251
+ - `TestMatchAIPaneRefusesAHandedPaneThatIsGone` owns the four ways a handed
1252
+ identity can be stale and proves none of them redirects the event onto another
1253
+ live Pane.
1254
+ - `TestAttributionFailureReasonsAreDistinctAndClosed` owns the failure
1255
+ vocabulary: an unreachable Pane inventory, a genuine no-match, an unreachable
1256
+ Registry, an unregistered Pane, a conversation no Pane holds, and a
1257
+ conversation two Panes hold each name themselves, and every token stays a
1258
+ bounded phrase.
1259
+ - `TestHookIngestRoutesAcceptTheExplicitPaneArgument` owns the route contract:
1260
+ `--pane` is optional, an empty value is a valid answer, and a positional
1261
+ payload argument is still refused.
1262
+
1263
+ ### Shared-host hook attribution tests
1264
+
1265
+ A provider host shared by several Panes has no Pane to hand over and no tmux
1266
+ environment to inherit, so both the explicit argument and the three-step ladder
1267
+ above come up empty for it. These tests own the one further step that reads the
1268
+ conversation the Registry already records.
1269
+
1270
+ - `TestMatchAIPaneAttributesASharedHostHookThroughTheRegistry` owns the normal
1271
+ path: with nothing handed over and no readable Pane inventory, the thread or
1272
+ session the payload carries resolves to the live runtime handle of the Pane
1273
+ that holds that conversation.
1274
+ - `TestMatchAIPaneRefusesAConversationNoPaneHolds` and
1275
+ `TestMatchAIPaneRefusesAConversationTwoPanesHold` own the refusals: an
1276
+ unclaimed conversation and an ambiguous one both fail by name rather than
1277
+ landing on a neighbouring Pane that merely shares a working directory.
1278
+ - `TestRegistryAttributionRequiresAnIntactBinding` owns the round trip. A Pane
1279
+ that is no longer materialized, one whose Agent no longer points back at it,
1280
+ and one whose Agent is gone are all torn state, never attribution targets.
1281
+ - `TestRegistryAttributionIsProviderNeutral` owns the shape of the lookup: it
1282
+ reads the resource model, so every provider whose conversation the Registry
1283
+ records resolves through the same code and none is named in it.
1284
+ - `TestMatchAIPaneKeepsTheRegistryStepBehindTheEstablishedLadder` and
1285
+ `TestMatchAIPaneLeavesTheLadderReasonWhenThereIsNoConversation` own the order.
1286
+ A handed identity, inherited `TMUX_PANE`, and the Pane inventory each still
1287
+ answer first, and a payload carrying no conversation keeps the ladder's own
1288
+ failure reason instead of being relabelled.
1289
+ - `TestRegistryAttributionReportsAnUnreachableRegistry` and
1290
+ `TestRegistryAttributionDefinesNoBinding` own the boundary: the step reads the
1291
+ Registry exactly once, writes nothing, defines no binding, and reports an
1292
+ unreachable Registry as its own failure.
1293
+ - `TestCodexHookIngestAttributesASharedHostEventThroughTheRegistry` owns the
1294
+ whole route rather than the matcher alone: a payload arriving with an empty
1295
+ `--pane` produces a record naming its own Pane, and an unclaimed conversation
1296
+ produces a record naming why it has none.
1297
+
1298
+ ### Hook reflection write tests
1299
+
1300
+ Attribution and reflection are two layers. A hook that found its Pane still has
1301
+ to write the event onto it, and every one of those tmux writes used to discard
1302
+ its error — 47 of them across `internal/app/ai.go` and
1303
+ `internal/app/ai_ingest.go`. A hook whose route to tmux was broken therefore
1304
+ produced a `result:"state"` record: the log reported a delivery that never
1305
+ happened, which is a stronger failure than an unattributed hook, because an
1306
+ unattributed hook at least says so.
1307
+
1308
+ These tests own the reporting, not the repair. Retry, recovery, and the route
1309
+ decision itself are separate concerns.
1310
+
1311
+ - `TestHookRecordRefusesToReportADeliveryThePaneWritesMissed` owns the record in
1312
+ the three shapes an operator meets: nothing lands, only markers were ever
1313
+ written, and — the live one — the reflection lands through a working route
1314
+ while the markers written without it do not. All three end as `error` with the
1315
+ bounded reason that names which kind of write failed, and the Pane they were
1316
+ attributed to stays in the record.
1317
+ - `TestHookRecordIsUnchangedWhenThePaneWritesLand` owns the normal path. Same
1318
+ result word, same empty reason, and the same number of attempted writes as the
1319
+ broken route — the writes stay best-effort and the sequence of attempts did
1320
+ not change.
1321
+ - `TestPaneWriteFailureReasonIsAClosedTokenCarryingNoOpaqueValue` owns the
1322
+ vocabulary. There are two tokens and the split is structural — the reflection
1323
+ of the event, whose failure its caller reports, and the markers laid down
1324
+ alongside it, whose callers have no error channel — because those two really
1325
+ do fail apart. tmux answers a rejected write with an exit status and a socket
1326
+ path; the classification keeps both out of either token, and the log file is
1327
+ checked for the fragments directly.
1328
+ - `TestObserverRecordsAreNotColouredByAReflectionWriteFailure` owns the
1329
+ boundary. The observer journal writes into the same file from a process that
1330
+ lives for hours, and its records never inherit a failure this process saw on
1331
+ another path.
1332
+ - `TestHookIngestSourcesNeverDiscardAReflectionWriteError` is the machine check
1333
+ that keeps the fix. It parses `ai.go`, `ai_ingest.go`, and `ai_pane_write.go`
1334
+ and fails on any tmux write whose error is dropped, because that defect is
1335
+ invisible by construction: the record it produces claims the hook succeeded.
1336
+ Its scan list is those three sources and its name says so; the package-wide
1337
+ equivalent belongs to the recurrence gate, so green here is not a claim about
1338
+ any other file.
1339
+
1340
+ ### Settled Codex authority admission tests
1341
+
1342
+ `aiCodexLifecycleSink.SetAuthority` publishes one native authority transition as
1343
+ three separate tmux writes — `@projmux_codex_authority`, then
1344
+ `@projmux_codex_authority_epoch`, then `@projmux_codex_authority_reason` — under
1345
+ a per-Pane kernel fence. That fence serializes writers against each other only.
1346
+ Turn and approval admission reads the same three options with one
1347
+ `display-message`, so a read that lands between two of those writes can see an
1348
+ authority that is already `provider-control-plane` paired with the epoch or
1349
+ reason of the transition being replaced, and refuse the turn for an unavailable
1350
+ epoch while the native connection is alive.
1351
+
1352
+ Admission now takes that same fence for the live read, so the triple it judges
1353
+ is one completed transition:
1354
+
1355
+ - `TestSettledCodexAuthorityAdmissionIgnoresTornAuthorityWrites` owns the
1356
+ mid-write-to-verdict mapping. Each row injects the triple that exists while
1357
+ the writer still holds the fence, records whether judging that triple directly
1358
+ would have refused a live connection, and then asserts the verdict reached
1359
+ from the settled triple. A control plane that is genuinely lost — the provider
1360
+ hook, a pending connection, an invalidation with no epoch — still refuses, and
1361
+ the refusal still names the observer's reason.
1362
+ - `TestCodexAuthorityAdmissionTakesTheExactWriterFence` owns the fence identity.
1363
+ The reader derives the same lock file as the writer, waits while a transition
1364
+ is in flight, holds it exclusively while it samples, and gives the wait up at
1365
+ the control deadline rather than stalling `projmux agent turn` behind a wedged
1366
+ transition.
1367
+ - `TestConcurrentCodexAuthorityTransitionsNeverExposeATornAdmissionSnapshot`
1368
+ owns the race. A transition writer and the real admission path run
1369
+ concurrently, and every snapshot admission read must be one of the complete
1370
+ triples the writer published; a control-plane authority with no epoch is the
1371
+ false refusal this closes.
1372
+
1373
+ The fence covers the live read only, not the judgment that follows it. A
1374
+ transition that lands after admission returns is not prevented: the control
1375
+ epoch still owns provider-side fencing, and `revalidateControlConsumerFence`
1376
+ still re-checks the Registry half immediately before the transport call.
1377
+ Admission also does not reduce how often the native connection drops, and it
1378
+ says nothing about the `@projmux_ai_state` / `@projmux_ai_badge_kind` /
1379
+ `@projmux_attention_state` triple, which has no fence of its own.
1380
+
1381
+ ### Inherited pane identity refusal tests
1382
+
1383
+ A provider host shared by several Panes inherits the activation envelope of
1384
+ whichever Pane happened to launch it, tmux environment included. The `--pane`
1385
+ argument therefore arrives holding a live, perfectly resolvable Pane that
1386
+ belongs to a different provider, and resolving it verbatim attributes the event
1387
+ to that Pane. Having an explicit value and owning it are different things; only
1388
+ the Registry, which already records which provider an Agent runs, can tell them
1389
+ apart.
1390
+
1391
+ - `TestResolveExplicitAIPaneRefusesAnInheritedForeignIdentity` owns the decision
1392
+ itself. Both spellings of the handed identity are refused whenever the
1393
+ Registry positively records a different provider for that Pane, in every
1394
+ direction between the catalogued providers, so the rule is symmetric rather
1395
+ than a guard around one of them.
1396
+ - `TestMatchAIPaneNeverAttributesAnInheritedForeignIdentity` owns the same
1397
+ fixture through the whole matcher: with nothing else able to answer, a refused
1398
+ inherited identity ends as a named failure and never as the foreign Pane.
1399
+ - `TestMatchAIPaneKeepsAnExplicitPaneWithNoRecordedProvider` owns the silence
1400
+ case. A Pane the Registry records no provider for — an unbound shell where
1401
+ somebody ran a provider by hand — is not a contradiction, and neither is an
1402
+ unrecognized spelling on either side; all of them resolve exactly as before.
1403
+ - `TestMatchAIPaneContinuesPastARefusedInheritedIdentity` owns the priority
1404
+ ladder behind the refusal: the Registry conversation record answers first, the
1405
+ established inventory ladder answers behind it, and only a fall-through that
1406
+ also fails ends as a failure.
1407
+ - `TestForeignExplicitFallThroughIgnoresTheInheritedEnvironment` owns the one
1408
+ step the fall-through deliberately skips. `TMUX_PANE` arrives in the same
1409
+ envelope whose Pane identity was just refused, so honouring it would
1410
+ reintroduce the misattribution through a second door; the path that was handed
1411
+ nothing keeps that step exactly as before.
1412
+ - `TestForeignExplicitRefusalLeavesTheRegistryPathUnchanged` is the negative
1413
+ audit. The stage writes nothing and defines no binding, and the Registry
1414
+ conversation index returns the same map whether or not an Agent records a
1415
+ provider, so the shared-host attribution path above is unchanged.
1416
+ - `TestAttributionFailureReasonsAreDistinctAndClosed` additionally owns the two
1417
+ new tokens: the refusal itself, and the composite the matcher reports once the
1418
+ fall-through has also failed. The composite is its own entry rather than the
1419
+ downstream step's reason, so a record still says the hook was handed somebody
1420
+ else's Pane. Every token stays provider-neutral in wording.
1421
+
1422
+ The fall-through has a second door of its own. Its inventory step matches on
1423
+ working directory alone, and where every provider Pane sits in the same
1424
+ repository that step would hand the event straight back to a Pane of the very
1425
+ provider just refused. Phase 8 is what routes a foreign-refused hook into that
1426
+ ladder at all, so the coherence check is applied once more to whatever the
1427
+ fall-through resolves — to the answer, never inside the shared ladder.
1428
+
1429
+ - `TestForeignExplicitFallThroughDoesNotLandOnAnotherProvidersPane` owns that
1430
+ door in both directions: a cwd-sharing Pane of the refused provider is not
1431
+ taken, and the attempt ends with its own reason rather than with that Pane.
1432
+ - `TestForeignExplicitFallThroughStillTakesACoherentLadderAnswer` owns the
1433
+ fail-open direction that keeps the Phase 2 contract intact: a ladder answer
1434
+ running the hook's own provider, one the Registry records no provider for, and
1435
+ one recording an unrecognized spelling are all still taken.
1436
+ - `TestForeignExplicitFallThroughDoesNotTakeAForeignConversationPane` owns the
1437
+ same check on the Registry conversation record, and proves refusing it does
1438
+ not consume the inventory ladder's turn behind it.
1439
+ - `TestNoExplicitPathStillTakesACwdMatchRegardlessOfProvider` is the proof that
1440
+ the shared ladder is untouched: with no explicit value there is no refused
1441
+ envelope and therefore no coherence question, and the cwd and thread steps
1442
+ resolve exactly as before for every provider.
1443
+ ### Codex control-plane regression-detection gate tests
1444
+
1445
+ Three control-plane defects lived through a neighbouring eight-phase track
1446
+ closing green from end to end. Nothing reached those surfaces, and the one test
1447
+ that did — `test/e2e/codex-lifecycle.sh` — asserted the literal `disconnected`
1448
+ for a managed Codex Pane's authority reason. `disconnected` is the bucket that
1449
+ means no reason was captured, so capturing a real one would have turned the job
1450
+ red. Shipping the fixes without this gate would leave the same blind spot under
1451
+ new code.
1452
+
1453
+ The gate has three parts.
1454
+
1455
+ - `TestControlPlaneTestsNeverExpectAnUncapturedReason` sweeps every
1456
+ `internal/**/*_test.go` and `test/e2e/*.sh` for values that mean "nothing was
1457
+ recorded" — `unrecorded`, the pre-instrumentation `disconnected` literal, the
1458
+ `bounded reason unavailable` read fallback, and the `target-unmatched`
1459
+ operations bucket — and fails on any that is an expectation rather than an
1460
+ input. A survivor is admitted only by an `uncaptured-default: <reason>`
1461
+ comment on its line or in the comment block above it; every current survivor
1462
+ is a different vocabulary that happens to spell a token the same way.
1463
+ - `TestControlPlaneContractCellsNameLiveTests` resolves every test named in
1464
+ `codexControlPlaneContractEnforcement` against the declarations in the tree.
1465
+ Squash merges erase the branch that tied a fix to the test holding it, so a
1466
+ contract cell naming a renamed test reads as enforced while enforcing nothing.
1467
+ - `TestControlPlaneContractEnforcementCoversEverySurface` holds the two
1468
+ directions of that map closed: every rendered surface names a contract, and
1469
+ every contract in the map names a surface the diagnosis renders.
1470
+
1471
+ `projmux doctor` gained the operator half under **Codex control-plane
1472
+ surfaces**. Seven verdicts, one per surface, each with the counters it was
1473
+ reached on:
1474
+
1475
+ - `TestCodexBrokerDiagnosticsSurfaceNamesThePublishedButUnreachableRuntime`
1476
+ separates a domain that published nothing — the ordinary resting state — from
1477
+ one whose published endpoint answered nothing, which used to render as
1478
+ `absent` and was read as "no broker is running".
1479
+ - `TestHookAttributionSurfaceSeparatesTotalFailureFromPartial` keeps "no hook
1480
+ reached its Pane" apart from "a few did not". `TestAttributionHealth*` own the
1481
+ bounded tail read of `ai-ingest.log` behind it, including that an unreadable
1482
+ log is reported as unobserved rather than as zero failures.
1483
+ - `TestObserverReasonSurfaceRefusesToCallAnUncapturedReasonHealthy` is the
1484
+ product-side half of the sweep: a reason distribution made entirely of
1485
+ uncaptured tokens is never a healthy row.
1486
+ - `TestConnectionContinuitySurfaceReportsCumulativeReconnects` reports the count
1487
+ as the total it is and derives no rate from a single sample.
1488
+ - `TestTurnAdmissionSurfaceReportsATornAuthoritySnapshotAsBroken`, with
1489
+ `TestSettledAuthorityObservationNamesATornTripleOnlyUnderTheFence` and
1490
+ `TestAuthorityFenceObservation*`, owns the C-5 read. Doctor samples each
1491
+ Pane's authority triple under a **shared, non-creating** hold of the writer's
1492
+ own fence, so a torn pairing is a producer fact rather than a transition
1493
+ caught in flight. A Pane with no fence file is reported `unfenced`, separately
1494
+ from torn: its writer predates the fence, which is a deployment answer.
1495
+
1496
+ - `TestHookDeliverySurfaceTreatsAnOpaqueFailureAsBroken` owns the layer after
1497
+ attribution. A hook that found its Pane has not yet changed it, and the write
1498
+ that follows can fail on its own. A failure naming its cause is a fault an
1499
+ operator acts on; one reporting only `exit status 1` says a process ended and
1500
+ nothing else. `TestOpaqueDeliveryReasonsNeverReachTheDiagnosis` pins both
1501
+ halves of that: the raw string is counted rather than repeated, and a reason
1502
+ carrying a path is opaque for the second reason that it is a leak.
1503
+ - `TestPaneOwnershipSurfaceReportsAForeignAttributionAsBroken` owns the failure
1504
+ that looks like success from every other angle — the event was attributed, the
1505
+ write landed, and the Pane it moved belongs to another provider.
1506
+ `TestOwnershipHealthCallsOnlyAPositivelyForeignPaneForeign` pins the predicate
1507
+ as one-sided and provider-neutral: an attribution is foreign only when the
1508
+ Registry positively records a *different* provider, so a Pane recording none
1509
+ is not a finding, and the judgment is a table rather than a branch.
1510
+
1511
+ Two things the attribution verdict counts, and one it does not.
1512
+
1513
+ The section carries the record span its hook rows were counted over. Every
1514
+ hook-layer number is cumulative across the whole reading window, and a
1515
+ deployment inside that window leaves records from both binaries in the same
1516
+ counts — so a repair that lands mid-window moves nothing until its predecessors
1517
+ age out, and a reader without the span reads the delay as the repair having
1518
+ failed and the eventual drop as time having healed it. Splitting the counts at
1519
+ a deployment boundary would need something this reader does not have: the hook
1520
+ layer is a short-lived process re-resolved from PATH on every firing, so the
1521
+ binary vintage of a long-running process says nothing about which image wrote a
1522
+ given record. That is also why the vintage line above qualifies the broker and
1523
+ observer rows and not these three.
1524
+
1525
+ It is reached **per source** rather than over the total. The defect it detects
1526
+ was one provider's, and a busy neighbour attributing everything would carry the
1527
+ aggregate and render the dead provider as a few stale panes — which is the
1528
+ reading that let it live for eight phases.
1529
+
1530
+ It counts only events the contract owed a Pane. `aiPaneMatchRefusals` names the
1531
+ answers that decline something the contract never promised — a conversation or
1532
+ an explicit Pane that no longer exists — and those are reported beside the
1533
+ failure count, never inside it.
1534
+ `TestAttributionHealthSeparatesAContractualRefusalFromAFailure` pins the split.
1535
+ A retired conversation whose thread keeps firing hooks is the mechanism working;
1536
+ reporting it as breakage made `doctor` call a machine behaving to spec broken,
1537
+ and a gate that cries wolf gets switched off — which is how the defects this
1538
+ section exists to catch survived next door. What stays a failure is the
1539
+ mechanism not answering: an unreadable inventory or Registry, or the ladder
1540
+ running over readable data and finding nothing, which is the shape a re-broken
1541
+ hook identity would take.
1542
+
1543
+ `TestEverySurfaceDeclaresWhatItsNumbersAreOver` and
1544
+ `TestEverySurfaceRendersTheScopeItDeclares` fold four separate corrections into
1545
+ one rule. Each of the four was the same mistake wearing a different face: a
1546
+ refusal the contract never promised counted among the failures it did; a judged
1547
+ count standing without its population; a lane that writes nothing sitting in a
1548
+ write rate's denominator; records from two binaries counted in one window. Every
1549
+ number was arithmetically right and none said what it ranged over, so a reader
1550
+ supplied the wrong range — and all four were found by a person, none by a test.
1551
+ A surface must now declare the phrase its rendering carries to answer "over
1552
+ what", and adding one without that answer fails the completeness check. The
1553
+ rule cannot judge whether a scope is *correct*; it makes stating one
1554
+ unavoidable, which is what none of the four had to do.
1555
+
1556
+ `TestHookReflectionWritesNeverDiscardTheirErrorSilently` covers what none of the
1557
+ log-derived verdicts can. A reflection path that runs a tmux write and throws
1558
+ the error away records `result:"state"` for an event whose Pane never moved, so
1559
+ the log itself reports success and every diagnosis built on it inherits the lie.
1560
+ It is the strongest form of the failure this whole section exists to catch:
1561
+ `disconnected` and `no matching pane` were at least wrong out loud.
1562
+
1563
+ The gate is zero within the hook reflection path, and it carries no baseline.
1564
+ Recording the current count as an allowance was considered and rejected:
1565
+ pinning today's state as the passing condition is the exact shape of the E2E
1566
+ assertion that made a silent control plane the condition for green, and
1567
+ building that trap inside the gate written to stop it is not a tradeoff worth
1568
+ making. An exception is admitted by a stated `best-effort-write: <reason>`
1569
+ comment, never by a count — the difference being that a reason is a judgment a
1570
+ reviewer can dispute, while a number just defers on volume. Three writes carry
1571
+ one today: two cosmetic (a pane title, a one-shot message on the operator's own
1572
+ screen) and one global cleanup marker whose failure simply causes a retry. None
1573
+ of the three can make a surface report a state a Pane does not hold.
1574
+
1575
+ Three things about the scan itself. It covers the whole package rather than a
1576
+ named list of files, so a discarded write appearing somewhere new is caught
1577
+ rather than skipped for being somewhere nobody thought to look. It counts a
1578
+ bare call statement as well as an assignment to blank identifiers — that second
1579
+ form appears nowhere in the tree today and is covered as prevention, so that
1580
+ deleting the assignment rather than checking the error does not quiet the gate.
1581
+ And it reads the syntax tree, not the text: a textual version counted the pattern inside the
1582
+ comment explaining why a checked helper replaced it, so the sentence describing
1583
+ the fix registered as the defect.
1584
+ `TestDiscardedWriteScanNeverCountsProseAboutCode` pins that, because a gate
1585
+ which cannot tell code from prose about code is one people route around.
1586
+
1587
+ The `pane-ownership` foreign count is broken down by direction for a related
1588
+ reason. A hook landing on the Pane that launched its host is the identity leak
1589
+ the contract closes; a provider's events arriving under another provider's
1590
+ source were misrouted before attribution ever ran, which a stale hook config on
1591
+ the machine can produce and no code change here would fix. One number would let
1592
+ either be mistaken for the other.
1593
+
1594
+ The section leads with a deployment-vintage line, and
1595
+ `TestControlPlaneVintage*` owns it. `make install` replaces the executable on
1596
+ disk and never replaces the image of a running process, so a green surface read
1597
+ from a replaced-image process describes code that process never loaded. This
1598
+ track lost two acceptance criteria to that twice. On Linux the diagnosis
1599
+ resolves `/proc/<pid>/exe` for the broker runtime and the lifecycle observers
1600
+ and reports how many run a replaced image; on a platform with no such table it
1601
+ reports the answer as unknown rather than claiming currency.
1602
+
1603
+ That line answers which image the Codex verdicts under it were read from, so it
1604
+ counts only those two roles. It is not the fleet answer, and reading it as one
1605
+ understated the fleet by a factor of five: on 2026-09-05 it named six processes
1606
+ while thirty-two children of the same executable were live, eighteen of them
1607
+ per-pane `internal supervise` supervisors that matched no named route and were
1608
+ dropped at a map lookup. `projmux doctor` therefore carries a second census
1609
+ under `Runtime`, rendered as `Projmux process vintage` and owned by
1610
+ `TestProjmuxProcessVintage*`, `TestProjmuxProcessRole*` and
1611
+ `TestDoctorSeparatesTheCodexDiagnosisVintageFromTheFleetCensus`.
1612
+
1613
+ - It is provider-neutral, which is why it is not on the Codex line. A
1614
+ supervisor supervises a Claude pane exactly as it supervises a Codex one, and
1615
+ `TestCodexControlPlaneVintageIgnoresProviderNeutralProcesses` keeps it off the
1616
+ line that qualifies Codex verdicts.
1617
+ - It has no unmatched case. A child whose route this reader cannot name is
1618
+ counted under `other` rather than dropped, so an unknown route can never make
1619
+ the fleet look smaller than it is.
1620
+ - Route words are argv without argv[0] and without anything behind a bare `--`.
1621
+ `projmux agent turn ... -- <text>` carries arbitrary prose, including the very
1622
+ route words this census matches on, and counting a message about the broker as
1623
+ a broker is the same class of error in the other direction.
1624
+ - Both censuses come from one process-table read, so the two lines can never
1625
+ disagree about the same process, and both answer `unknown` where the platform
1626
+ exposes no table.
1627
+ - Neither line names a process. `TestProcessVintageNamesNoProcess` audits both
1628
+ rendered strings for pids, paths and argv; the section is counts and nothing
1629
+ else, which is what makes a process census printable at all.
1630
+
1631
+ The gate does not change `projmux doctor`'s exit code. A broken surface is a
1632
+ readable verdict, not a failed command, and the CI half of the detection is the
1633
+ regression tests themselves.
1634
+
1635
+ ### Shared AI pane option routing tests
1636
+
1637
+ `TestSharedPaneRoutingCodexIngest` drives the marker (`PreToolUse`) and ordinary
1638
+ state (`UserPromptSubmit`) caller paths through full Codex ingest. Both require
1639
+ writes on the verified app route and honest records; removing the shared argv
1640
+ prefix makes both fail. `TestSharedPaneRoutingSetClearRecord` pins exact inherited
1641
+ `-S` and detached app `-L` argv. `TestSharedPaneRoutingDenialIsBoundedAndWritesNothing`
1642
+ rejects unreachable servers, missing panes, foreign servers and absent resource
1643
+ identity without writes or success records. Existing Codex delivery authority,
1644
+ semantic, refusal-vocabulary and diagnostics gates remain in force.
1645
+
1646
+ `TestSharedPaneRoutingProviderConsumers` covers Codex, Claude, Antigravity and
1647
+ bell hook consumers, including state and resume markers.
1648
+ `TestSharedPaneRoutingLaunchBeforeFirstMarker` checks canonical detached
1649
+ materialization through its supplied exact runner before any managed marker or
1650
+ Pane UID exists. Existing topic/status semantic fixtures retain
1651
+ their option expectations while their mock server supplies explicit containment.
1652
+ `TestManagedProjectionRoutingCallers` enters managed interaction through Claude
1653
+ `UserPromptSubmit` hook ingest and managed topic through the internal
1654
+ `aiCommand.Run` compatibility set/clear path. Its internal status-idle companion
1655
+ covers badge/attention unset; hooks do not emit idle. Caller result, committed
1656
+ Registry state, exact route argv, target values and actual unset are asserted
1657
+ together. `TestManagedProjectionRoutingFailureKeepsCommit` refuses disappeared,
1658
+ denied and foreign routes after commit, with no fallback writes, and retains the
1659
+ existing committed-mirror error and first-write-failure behavior.
1660
+
1661
+ `TestManagedProjectionRoutingRealTmux` repeats these managed caller cases on two
1662
+ isolated real servers with identical pane IDs and opposite-server sentinels.
1663
+ Detached attribution reads use the fixture's exact route; projection writes run
1664
+ unaltered. Baseline detached raw projections and a shared-prefix-removal mutant
1665
+ both fail the target assertions. Inherited raw baseline writes can already work
1666
+ through the tmux environment; the argv fixture additionally pins explicit routing.
1667
+ An optional `PROJMUX_TEST_MANAGED_ROUTE_BINARY=<absolute binary>` executes
1668
+ the reachable hook case and a separate canonical public status/topic parity
1669
+ subtest through that binary. Internal topic and idle cases always
1670
+ compile this source; there is no installed target-topic CLI. Canonical public
1671
+ `agent status/topic` uses its separate already-routed mirror and is parity evidence,
1672
+ not proof of these compatibility projections.
1673
+
1674
+ `TestSharedPaneRoutingRealTmux` uses two real isolated servers with the same pane
1675
+ ID and checks set, clear, marker and full hook state against target readback and
1676
+ opposite-server sentinels for detached and inherited routes. It refuses a missing
1677
+ inherited route without fallback. Every server starts with a unique socket name
1678
+ inside a dedicated tmux root; only a verified private alias exposes the fixed app
1679
+ locator. Cleanup rechecks the observed exact socket before `-S` shutdown. The test
1680
+ runs with `make test` where tmux is available; an explicit installed smoke runs the
1681
+ same hook assertions against `PROJMUX_TEST_PANE_ROUTE_BINARY=<absolute binary>`.
1682
+ This is a test-only selector, not a product environment contract. The fixture
1683
+ seeds and strips the caller's internal activation Pane/generation envelope so a
1684
+ managed test process cannot suppress the isolated hook as a stale activation.
1685
+
1686
+ ### Attributed Codex hook delivery route tests
1687
+
1688
+ Attribution and reflection are different layers. A Codex hook attributed to its
1689
+ Pane still has to write that Pane's options, and the hook the shared app-server
1690
+ launches inherits no tmux environment at all: its `tmux set-option` carried no
1691
+ socket argument, probed the operator's `default` server, found none, and handed
1692
+ back `exit status 1`. `ai-ingest.log` recorded that exit status where a cause
1693
+ belongs.
1694
+
1695
+ `applyCodexHookSemanticDelivery` now resolves one exact server before it writes
1696
+ anything, and refuses by name when it cannot.
1697
+
1698
+ - `TestCodexHookDeliveryWritesItsPaneWithoutAnyTmuxEnvironment` owns the case
1699
+ the failure came from: with no `TMUX` at all the delivery still resolves a
1700
+ route, and the argv it produces is pinned to the app-owned socket rather than
1701
+ left unprefixed.
1702
+ - `TestCodexHookDeliveryPrefersTheInheritedReceiptOverTheAppRoute` owns the
1703
+ precedence. A pane client's own `$TMUX` receipt names its server without a
1704
+ search, is taken as an explicit socket path, and needs no containment probe.
1705
+ - `TestCodexHookDeliveryRefusesARouteItCannotProve` owns the proof. The
1706
+ app-owned route is never trusted on its name: an unreachable runtime, an empty
1707
+ answer, a server that is not app-owned, a runtime holding a different pane,
1708
+ and a pane that is not a projmux Pane each produce their own reason and write
1709
+ nothing.
1710
+ - `TestCodexHookDeliveryFailureReasonsAreConcreteAndClosed` owns the vocabulary.
1711
+ Every token a refusal can render — the three reasons, the two route kinds, the
1712
+ four tmux causes — is non-empty and carries no socket path, no routing flag,
1713
+ and no process exit status.
1714
+ - `TestCodexHookDeliveryLeavesNoTransportDetailInTheIngestLog` owns the record
1715
+ rather than the error value. It injects a transport failure whose tmux output
1716
+ names its socket, drives the real ingest route, and reads the whole log file
1717
+ back: a reason that leaks is caught there even when every assertion above
1718
+ still passes.
1719
+ - `TestCodexHookDeliveryExplainsARejectedWriteByRereadingItsRoute` owns the
1720
+ residual failure. A `set-option` that fails after the route was proven is
1721
+ explained by re-reading the route — a pure read that repeats no write — so the
1722
+ record says whether the pane vanished or the runtime rejected the option.
1723
+
1724
+ tmux explains a refusal on stderr and that text carries the socket path it
1725
+ tried, so `codexHookDeliveryCause` is the only reader of tmux's own words here:
1726
+ it classifies them into a closed set and returns none of them. A reason names
1727
+ which lane answered — inherited or app-owned — and never which file backs it,
1728
+ because this track's change boundary keeps paths out of durable records.
1729
+
1730
+ The route decides where a reflection is written, not which Pane it belongs to;
1731
+ attribution stays with the matcher and the Registry step above. Two other hook
1732
+ writers on the same shared-host path, `applyAIStatusInternalWithActivationPolicy`
1733
+ and `markAIHookPane`, still issue unrouted tmux calls and discard their errors,
1734
+ so their failures are invisible rather than fixed.
1735
+
1736
+ ### Install residue census tests
1737
+
1738
+ `make install` and `npm install` replace the executable and leave every
1739
+ long-lived projmux child on the image it started with. `projmux internal
1740
+ install-residue` measures how much of the fleet that is, appends one record to
1741
+ `install-residue.jsonl`, and prints at most one stderr notice; see
1742
+ [operational-diagnostics.md](operational-diagnostics.md#install-residue-ledger)
1743
+ for the ledger format and the mapping from its fields to the questions a
1744
+ bounded drain of those processes has to answer.
1745
+
1746
+ The deliverable is the measurement, not the message, so the tests are weighted
1747
+ toward the record rather than the wording.
1748
+
1749
+ - `TestProjmuxProcessVintageMeasuresTheResidualAgeDistribution` owns the age
1750
+ half of the census: only replaced-image processes contribute an age, the
1751
+ distribution is stored ascending and whole, and a clock that moved backwards
1752
+ floors at zero rather than reporting a process started after the census that
1753
+ observed it.
1754
+ - `TestProjmuxProcessVintageKeepsAnUnknownStartTimeOutOfTheDistribution` owns
1755
+ both halves of the unknown case: the process still counts as residue, and no
1756
+ age is invented for it.
1757
+ - `TestProjmuxProcessVintageCapsOneRoleAgeDistribution` owns the per-role sample
1758
+ bound and pins that a census which hit it says so, rather than reporting a
1759
+ prefix as a whole distribution.
1760
+ - `TestDoctorProcessVintageProjectionCarriesNoAges` keeps `projmux doctor`'s
1761
+ record exactly what it was: the age fields populate only when a caller
1762
+ supplies a reference instant.
1763
+ - `TestProcessImageListerReadsAPlausibleStartTime` (Linux) covers the one part
1764
+ a synthetic process table cannot. A reader that silently returned the zero
1765
+ time would leave every distribution empty, which is indistinguishable from a
1766
+ platform where start times are unknowable.
1767
+ - `TestInstallResidueReportsPerRoleResidualCountsAndAges` owns the record
1768
+ field-for-field, including the per-role distribution being as long as that
1769
+ role's residual count and ascending.
1770
+ - `TestInstallResidueZeroResidualPrintsNothing` owns the boundary that keeps the
1771
+ notice worth reading: an install that reached the whole fleet prints
1772
+ absolutely nothing, and still records that it did.
1773
+ - `TestInstallResidueUnsupportedPlatformPrintsNothingButRecordsTheGap` owns the
1774
+ macOS path: no terminal output where the census cannot be taken, and a
1775
+ `supported:false` record so the coverage gap is measured rather than absent.
1776
+ - `TestInstallResidueCarriesNoProcessIdentity` is the privacy gate, asserted
1777
+ negatively over the rendered notice and the ledger bytes at once: sentinel
1778
+ pids, executable path, and argv appear in neither.
1779
+ - `TestInstallResidueLedgerAppendsAndDerivesTheInstallGap` owns the frequency
1780
+ half — records accumulate and each carries the gap to the one before it.
1781
+ - `TestInstallResidueLedgerRotatesAtItsRecordBound` owns growth: the oldest rows
1782
+ go, the newest stay, and the file keeps mode `0600` across the rotation.
1783
+ - `TestInstallResidueNoticeOrdersRolesByResidualCount` and
1784
+ `TestInstallResidueNoticeTiesBreakOnTheCensusRoleOrder` own the render
1785
+ contract: biggest residual first, census role order on a tie, zero-residual
1786
+ roles omitted entirely.
1787
+ - `TestInstallResidueAgeTextRendersDurationsAnOperatorReads` owns the duration
1788
+ format, the median of both odd and even distributions, and the unknown case,
1789
+ which must not render as a zero age.
1790
+ - `TestInstallResidueNeverFailsAnInstall` owns the hard edge: an unresolvable
1791
+ state directory, an unwritable ledger, and the route entrypoint itself all end
1792
+ in a silent success.
1793
+ - `TestInstallResidueInstallerComesFromTheExistingEnvVar` pins that installer
1794
+ identity is read from `PROJMUX_INSTALLER` and that no new `PROJMUX_*` variable
1795
+ is introduced, since any such variable is a minor-release input to the hook
1796
+ contract.
1797
+ - `TestInstallResidueRouteIsRegisteredAsInternalPlumbing` pins the spelling the
1798
+ installer invokes and keeps the census out of the legacy hook migration path.
1799
+
1800
+ Nothing in this feature terminates, starts, signals, or restarts a process.
1801
+ The census is two file reads per process; replacing the residual processes is a
1802
+ separate decision this measurement exists to inform.
1803
+
578
1804
  ## Review Checklist
579
1805
  - The branch stays within its stated scope.
580
1806
  - The change preserves boundaries between portable `projmux` behavior and local machine policy.
581
1807
  - The required `make` targets were run in order.
582
1808
  - Test inventory updates are included when behavior changed.
583
1809
  - Known parity gaps are explicit.
1810
+
1811
+ - `TestClaudePushSourceReplacementAfterDurableHandoffWritesZero` and
1812
+ `TestClaudeProviderPushFinalRouteCheckPrecedesSoleWrite` cover source/target
1813
+ authority loss across durable work and the final provider pre-write fence.
1814
+ - `TestClaudeExplicitMultipleRequestsAndHumanOverlapSelectOnlyNamedOriginal`
1815
+ requires an explicit choice of the original request; Stop text has no reply
1816
+ authority regardless of human activity or pending request count.
1817
+ - `TestClaudeReplyToolLiteralCommandAndOpaqueCarrier` and
1818
+ `TestClaudeReplyToolRejectsMalformedAndOtherOfficialTools` reject shell
1819
+ expansion, extra commands, nonallowlisted tools and malformed hook input.
1820
+ A shell-prefix carrier supplies only one canonical memory ticket; its other
1821
+ bytes are never executed or retained.
1822
+ - `TestClaudeReplyToolTicketsChooseExactActionAndConsumeOnce` and
1823
+ `TestClaudeReplyToolForeignStaleExpiredAndReplacedExecutableExecuteZero`
1824
+ bind parallel actions to separate tickets and one exact process birth,
1825
+ executable inode, current route and argv. Replays, expired tickets, foreign
1826
+ callers, replaced executables and missing hooks obtain no execution witness.
1827
+ - `TestClaudeReplyToolRequestedCaptureFailureRefusesAndSecretsAreAbsent` keeps
1828
+ requested guard capture failures closed and messaging credentials out of the
1829
+ fixed public-command environment.
1830
+ - `TestClaudeExplicitReplyUnsupportedTargetCannotCorruptStore` rejects a
1831
+ non-Codex reply target before a durable write. The process integration fixture
1832
+ now owns a real Codex broker binding for its original source.
1833
+
1834
+ - `TestAuthorityCheckUsesExistingExactLeaseWithoutProviderTraffic` proves the
1835
+ dialogue source's exact live Codex broker runtime, connection and binding
1836
+ fence with no new binding or provider request.
1837
+ - `TestNonblockingHookStoreRefusesContentionWithoutLateWrite` and
1838
+ `TestClaudeOfficialHookContentionInvalidatesReplyWithoutWaiting` keep compatibility hooks bounded. Explicit reply commits use broker original
1839
+ request validation independently of hook timing.
1840
+
1841
+ - `test/agent_dialogue_canary_test.py` covers exact public claim/schema mutations,
1842
+ runtime-first owner/generation negatives, prepare-without-provider-launch,
1843
+ environment isolation, and automatic partial-setup delayed-writer/early-error
1844
+ cleanup, plus registered Project session projection/foreign-scope refusals
1845
+ before runtime creation and automatic cleanup of a registered Project when
1846
+ setup fails before tmux creation. Audit tests preserve closed stage/exit and exact writer birth evidence
1847
+ outside the disposable root, reject unsafe candidate permissions before
1848
+ credential copying, and retain roots after uncertain cleanup without retry. Product `TestClaudeDialogueStream*` tests own public output parsing;
1849
+ 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.
1853
+
1854
+ - L20 owns a real authenticated Codex broker host and keeps its observed snapshot
1855
+ binding alive for the round trip; Registry-only synthetic authority is refused.
1856
+ The offline endpoint records and rejects every provider request/answer.
1857
+ First-failure diagnostics accept L20 and keep its replay attribution.
1858
+
1859
+ - `test/agent_dialogue_canary_test.py` also exercises Linux-only failed-canary
1860
+ cleanup with explicit delayed-writer barriers: the actual EXIT trap retains
1861
+ its root until the captured writer exits, preserves the original failure,
1862
+ tracks reparented descendants, and refuses stubborn or replaced births.
1863
+ These regressions use no live provider and skip when Linux pidfd is absent.
1864
+
1865
+ - `TestClaudeQualificationRequiresCurrentPinnedMemoryGuard` rejects asserted
1866
+ evidence with a missing, replaced or writable guard image before any push.
1867
+ Private coord v5 fences earlier unguarded qualification; the frozen provider
1868
+ auth/user frame is unchanged. L20 and the process fixture now execute the
1869
+ guarded public CLI, with real isolated tmux identity and Codex broker binding.
1870
+
1871
+ - `TestLifecycleDiscardedASCIIKeepsStringBoundariesAndValidation` covers the
1872
+ body-free projector's buffered ASCII scan across chunk, quote, escape,
1873
+ control-character and UTF-8 boundaries. The optimized path retains no body
1874
+ and leaves byte/depth/scalar limits and the 750 ms operation limit unchanged.
1875
+ `TestLifecycleDiscardedASCIIChecksCancellationWithinBoundedBufferedRun`
1876
+ requires cancellation within one 4 KiB buffered run and preserves the
1877
+ retained-state refusal before scanning.
1878
+
1879
+ - Offline L20 cleanup (`test/e2e/dialogue-cleanup.py`) reuses the failed-canary
1880
+ pidfd barrier and also captures exact owned cwd paths before canonical delete.
1881
+ It waits for captured pane/supervisor/helper births after reparenting; only
1882
+ role-validated fixture jobs and its exact broker receive pidfd TERM. The outer
1883
+ EXIT trap retains the root on missing exit proof. `OfflineDialogueCleanupTest`
1884
+ synchronizes a cwd-only late receipt writer with actual delete intent, checks
1885
+ original failure exit preservation, and rejects stubborn/foreign/replaced
1886
+ births without broad signals. Historical L20 late-writer attribution remains
1887
+ unknown; process exit proof does not depend on that attribution.
1888
+ - `TestStoreReplyIsAtomicCorrelatedAndReplayIdempotent` pins both store instances
1889
+ to the fixture clock so the replay test remains independent of wall-clock
1890
+ terminal retention while retaining the explicit one-hour replay timestamp.
1891
+
1892
+ - `TestClaudeDialogueCreateOptInRefusesUnsupportedOrUnconfiguredLaunchBeforeWrites`,
1893
+ `TestClaudeDialogueResumeOptInKeepsRunningAndNonClaudeRefusalsReadOnly`, and
1894
+ `TestClaudeDialogueOptInIsAnExplicitSupervisorEnvelopeNotAnInheritedDefault`
1895
+ cover the next-activation `--dialogue-reply-only` preflight and supervisor/gate
1896
+ envelope. No AgentSpec or default tool policy persists this opt-in. The
1897
+ production launcher creates a private generation profile and an owned
1898
+ stdout/stderr observer. Missing launcher adapters still fail before allocation.
1899
+ - `TestAgentMessageAndReplyOnlyPreflightDoNotMigrateGlobalOrProjectHooks` checks
1900
+ that public message authentication and explicit reply-only preflight never
1901
+ migrate unrelated global/project legacy hooks. Existing normal create/resume
1902
+ pre-dispatch behavior remains unchanged.
1903
+
1904
+ - `TestClaudeDialogueProfilePinsFilesAliasAndInvocationMode` and
1905
+ `TestClaudeDialogueOrdinaryEnvironmentKeepsOfficialPrefixWithoutInheritingOptIn`
1906
+ cover immutable profile inputs, pinned candidate alias, FIFO refusal, and
1907
+ ordinary environment compatibility. `TestAgentMessageAndReplyOnlyPreflightDoNotMigrateGlobalOrProjectHooks`
1908
+ also covers the profile's exact lifecycle/register/helper callbacks.
1909
+ - `TestClaudeDialogueCurrentHelperEvidenceAndObserverLossPrecludeEffects` uses
1910
+ the current helper UDS to obtain qualification evidence and complete a valid
1911
+ guarded reply baseline. Replacing only the observer descriptor then refuses
1912
+ otherwise-valid prepare, consume, commit and push requests without effects.
1913
+ - `TestClaudeDialogueStreamDiscardsTextThinkingAndPairsOnlyExactReplyTool` and
1914
+ `TestClaudeDialogueStreamRejectsUnknownEffectsAndBoundsState` cover transient
1915
+ public JSON validation, action/result pairing, zero pre-inbound tool effects,
1916
+ unknown metadata refusal and finite hook/tool state.
1917
+ - `TestClaudeDialoguePipeEOFClosesInputButWaitsCurrentTurn`,
1918
+ `TestClaudeDialoguePipeDiagnosticLossFailsClosedWithoutBusyLoop`,
1919
+ `TestClaudeDialogueCleanupWaitsExactWriterAndHandlesZombieWithoutSignals`, and
1920
+ `TestClaudeDialoguePartialSetupAndUnknownFilesRetainExactRoot` cover Linux
1921
+ terminal EOF, current-turn completion, stderr loss, exact-birth pidfd exit
1922
+ proof and retained evidence on uncertain cleanup. No provider runs in these tests.
1923
+
1924
+ - L20 now launches the synthetic Claude through public `--dialogue-reply-only`,
1925
+ exercises the real profile executor, observer and pinned prefix dispatch, and
1926
+ qualifies from current helper-memory evidence. It creates no hand-authored
1927
+ public-init evidence file. Qualification and ordinary replies are separately
1928
+ claimed by the original Codex route. This is deterministic integration
1929
+ evidence, not actual model execution. Wrapped pane output is joined for the
1930
+ readiness marker; cancellation closes the fixture's own first-frame listener.
1931
+ - `TestClaudeEndpointProcessIntegration` intentionally remains the private
1932
+ registration/bootstrap/process fixture. Its owned registration child opts in
1933
+ after activation; it does not depend on a supervisor-inherited guard flag,
1934
+ which production correctly scrubs. It does not substitute for public launch
1935
+ or actual provider qualification evidence.
1936
+
1937
+ - `TestClaudeDialogueToolEvidenceIsReadOnlyAndIndependentOfArrivalOrder` and
1938
+ `TestClaudeDialogueObservedToolCannotGrantOrForgeCommitEvidence` distinguish
1939
+ observed model tool ID/original selection and paired result from the pinned
1940
+ execution witness and successful broker commit. A failed commit, wrong result
1941
+ ref or forged observation cannot become correlated execution evidence; reading
1942
+ that bounded snapshot performs no delivery or reply action.
1943
+ - `TestAgentCapabilitiesClaudeMessageCellsReflectMissingRegistrationLease` and
1944
+ `TestAgentCapabilitiesClaudeUnqualifiedRecoveryUsesCurrentPublicProfile` keep
1945
+ recovery read-only and actionable: normal exit, same-UID explicit reply-only
1946
+ resume, then current Codex qualification from helper-memory evidence.
1947
+
1948
+ - `test/agent_dialogue_codex_observation_test.py` checks the frozen public Codex
1949
+ 0.153.2 schema, explicit observed source, one thread/turn/item, paired command
1950
+ completion and closed result correlation, unknown/body privacy negatives, and
1951
+ bounded read-only connection EOF/deadline/peer replacement. It supplies no
1952
+ actual provider or model-action evidence.
1953
+ - `test/agent_dialogue_source_action_test.py` checks exact public qualification,
1954
+ original-source inbox claims and one idle send against production-shaped
1955
+ reply fixtures; changed composite routes fail before dispatch and unknown
1956
+ send outcomes are never retried. The source action returns closed facts and
1957
+ performs no fleet/root cleanup. Parent orchestrator wiring remains pending.
1958
+
1959
+ - `test/agent_dialogue_native_source_test.py`: private native launch argv/image/environment, public genuine initial task, bounded initialization, source release before effects and closed tool return before parent cleanup; fake endpoints/launches and owned inert child processes only.
1960
+
1961
+ - `test/agent_dialogue_native_policy_test.py`: pinned public config/read projection, missing/mismatched policy, foreign or unknown origins, private-value exclusion, raw-layer/extra-execution-input refusal, bounds/EOF; native source tests also refuse an otherwise-valid action when policy changes before release.
1962
+
1963
+ - Policy diagnostics: `test/agent_dialogue_native_policy_test.py` checks closed schema/request/value/origin codes and exception redaction; native source tests preserve a valid baseline while independently failing config/socket/request/schema boundaries. `test/agent_dialogue_canary_test.py` exercises real setup finally after a policy code, audit privacy, uncertain-writer retention and audit-write failure cleanup without a success receipt. All use offline fixtures.
1964
+
1965
+ - `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
+ - `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.