projmux 0.15.1 → 0.15.3
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.
- package/docs/agent-workflow.md +505 -23
- package/docs/architecture.md +17 -8
- package/docs/claude-coordination-endpoints.md +36 -0
- package/docs/cli-guide.md +79 -14
- package/docs/cli.md +40 -17
- package/docs/configuration.md +65 -0
- package/docs/heterogeneous-dialogue-canary.md +28 -0
- package/docs/keybindings.md +125 -1
- package/docs/legacy-cli-retirement.md +1 -1
- package/docs/settings-ia.md +31 -0
- package/docs/statusbar.md +10 -4
- package/docs/theme-palette.md +14 -0
- package/docs/usage-tracking.md +33 -6
- package/package.json +5 -5
package/docs/agent-workflow.md
CHANGED
|
@@ -58,16 +58,17 @@
|
|
|
58
58
|
- `make test` / installed opt-in smoke: Codex generation-pool Phase 3 routes prompted and payload-free native create through one exact current `stateDomainID + endpointGenerationID`, then keeps resume, message/reply/approval, lifecycle observation, and TUI argv pinned to the durable Agent ref even after admission-current changes. `TestCodexNativeCurrentChangePinsExistingAgentAndAdmitsNewCreateOnlyToNew`, `TestCodexNativeResumeRouteUsesDurableEndpointAndRefusesLegacyOrDraining`, `TestOfflineCodexResumeRechecksDrainingInsideTransactionBeforeProviderWrite`, and the Agent-control action table pin old/new cross-wire zero, locked-transaction lifecycle revalidation, typed missing/Draining refusal, and provider/Registry first-write zero. `TestGenerationRoutedBrokerEpochPublishesItsExactRuntimeAuthority`, `TestCodexNativeEndpointRouteTransportShapeIsClosed`, `TestDefaultCodexStateDomainIdentityUsesExactCanonicalCodexHome`, and the broker transport tests require real generation-keyed broker runtime authority, default/private transport XOR, and canonical content-free state-domain identity; a changed `CODEX_HOME` never reattaches a stored Agent. `TestNativeResumePickerRowsPreserveExactGenerationThreadAndSource`, `TestNativeResumePickerDuplicateVisibilityUsesKnownOwnerOrRefusesAmbiguous`, and `TestCodexResumePickerPinnedValueShapeIsClosed` freeze source/domain/generation/state in each native row value, visibly mark handover/unavailable rows, refuse duplicate visibility without a durable owner, and keep rollout rows unable to carry partial native authority. `TestInstalledIsolatedGenerationPinnedEmptyPromptCreateSmoke` is opt-in through `PROJMUX_CODEX_PHASE3_SMOKE_ROOT=/tmp/<isolated-root>` and runs the installed binary against a fixture-owned unmanaged direct endpoint plus unique Registry/tmux roots; it first bootstraps the fresh generated app config through the installed public `config apply` path with no live isolated server, then accepts only the exact tuple's closed Phase-7 result: durable-ready keeps the historical payload-free Agent↔Pane↔thread↔endpoint, zero-turn, pinned-TUI, broker, obligation, ambient-mutation-zero, and cleanup assertions, while typed not-ready preserves one exact Failed Agent/thread/endpoint with Pane/TUI/plain fallback zero and an unresolved exact-UID retry with second Agent/thread/lane zero. Installed Codex does not subscribe the broker until the first real input materializes the rollout, so the successful pre-turn row keeps native authority unavailable instead of adding dummy content; the provider-recorder integration owns first-real-input same-thread/one-turn proof without requiring ambient auth. Each observation records its exact CLI/app-server/platform/route tuple and is not a version-family guarantee. Phase 5 old-endpoint stop, successor same-thread resume, endpoint-ref CAS, Pane relaunch, retirement, lease release, and foreign adoption remain out of scope.
|
|
59
59
|
- `make test` / `make test-integration` / private installed observation: Codex generation-pool Phase 4 owns explicit crash-resumable `plan|apply|resume|abort`, at-most-once admission-current, and old-generation drain. `TestRollingUpgradeStateMachineKeepsPhase5EffectsAtZeroAndReceiptsAtMostOnce`, `TestRollingUpgradeStateSpaceProperty`, and `FuzzRollingUpgradeValidStatesNeverContainPhase5Effects` pin monotonic receipts and all seven Phase-5 effects at zero, including `leaseRelease`. `TestCoordinatorExactPhase0TuplePreflightRefusesBeforeEveryMutation` re-opens both complete leases read-only and pins exact bundle/version/RoleTUI plus same-domain, direct-socket, and non-overlapping private/state/lease-root topology before any physical start. `TestCoordinatorResumeAfterLaunchBeforeProofReusesOneCandidateAndLeavesNoDuplicate`, `TestCoordinatorAbortFencePreventsStalePlannedResumeFromLaunching`, `TestCoordinatorAbortAndAdmissionRaceNeverStopsCurrent`, `TestCoordinatorEveryFailpointCrashRestartConvergesAtMostOnce`, and `TestRollingJournalAtomicWriteCrashHooksRestartWithoutDuplicateEffects` cover coordinator death after supervisor launch, all ten declared coordinator failpoints, both journal atomic-write crash hooks, exactly-once candidate-start/admission/drain/handover convergence after restart, the durable abort fence, and deterministic Abort-vs-admission schedules without fixed sleeps. `TestCoordinatorCandidateReadyAbortCleansExactCandidateAndKeepsSevenPhase5EffectsZero`, `TestCoordinatorLaunchBeforeProofAbortCleansRecoveredCandidateWithoutPhase5Effects`, `TestRemoveCandidateSocketRefusesUnknownOrReboundIdentityWithoutUnlink`, `TestCleanupDurableCandidateAfterSupervisorExitRemovesOnlyOwnedSocketAndReceipts`, and `TestCleanupDurableCandidateRecoversOnlyExactUnlockedOrphanGuard` pin exact candidate cleanup, retained bundle lease, unlocked no-socket orphan-guard recovery, and locked/foreign/rebound mutation zero. `TestExactRoleTUIArtifactRefusesWrongAbsolutePathAndPostVerifyDrift`, `TestCoordinatorWrongAbsoluteTUIPathNeverCommitsAdmission`, and `TestCoordinatorTUIDriftAfterReadyProofFailsInsideAdmissionBarrier` require the exact single RoleTUI lease artifact at publication and commit. `TestCoordinatorTwoSlotThirdUpgradeRefusesWithEveryMutationSurfaceAtZero`, `TestCoordinatorRetainedRetiredRoutesDoNotConsumeThePreparingSlot`, and `TestCoordinatorAbortedOperationRefCannotBeReplayed` close capacity/replay consistency. `TestAgentResumeDrainingGenerationFailsClosedWhenHandoverRequesterIsUnconfigured` tables both Draining and HandoverPending with an unconfigured requester, while `TestAgentResumeDrainingGenerationReusesOneGenerationWideOperationRef`, `TestCoordinatorRepeatedDrainingResumeReusesOneGenerationHandoverOperation`, and `TestDrainingOldCodexAgentKeepsExactTurnControlTUIAndPaneBinding` cover exact operation-ref reuse and direct old-generation turn/control without endpoint/TUI/Pane mutation. `TestPrivateRollingAdmissionReceiptRendersAllSevenPhase5EffectsAtZero` and opt-in `TestInstalledPrivateRollingAdmissionReceipt` cover content-free receipts, private process groups, ambient/default read-only observation, exact old Draining thread read plus successful no-turn-compatible listing, payload-free new Current create/read, assertion-safe exact target teardown, and ambient-preserving cleanup. Deletion ledger: zero canonical tests deleted or renamed and zero Phase 0–3 boundary suites merged; the canonical graph baseline only adds the four explicit upgrade leaves. Phase 5 effects remain zero in the unchanged Phase-4 receipt and are implemented only by the distinct linked handover journal.
|
|
60
60
|
- `make test` / `make test-integration` / opt-in installed private observation: Codex generation-pool Phase 5 owns `agent app-server handover plan|apply|resume|abort` and the generation-wide `stop → resume → snapshot-all → endpoint CAS → same-Pane relaunch → terminal retirement → lease release` journal. `TestGenerationHandoverOrdersStopResumeSnapshotCASRelaunchTerminalLease`, `TestGenerationHandoverCannotCASUntilEverySnapshot`, and `FuzzGenerationHandoverNeverAcceptsUnsafeReceiptOrder` pin monotonic per-effect intents, the global snapshot barrier, and terminal lease ordering. `TestCoordinatorGenerationWideOrderingAndEveryFailpointResumeConvergesWithoutDuplicateEffects`, `TestCoordinatorRecoversExactSameGenerationBeforeHandoverAndCrashRetryIsIdempotent`, and `TestCoordinatorUsesQualifiedFallbackWhenExactSameGenerationRestartIsUnavailable` cover every coordinator effect gap, journal-authoritative repeated Apply, same-generation-first cold recovery, and exact-qualified fallback without a fixed sleep. `TestExternalOwnersRemainAwaitingOwnerStopWithLifecycleArgvZero`, `TestIncompleteManagedOldRouteBlocksBeforeEveryEffect`, `TestSuccessorPreloadedTargetStopsBeforeOldStop`, `TestPlanIgnoresSameGenerationIDFromAnotherStateDomain`, `TestAppliedNoTurnChoiceIsForwardOnlyAndCannotClaimAuthorityRestored`, and the abort-fence tests pin the ownership matrix, exact state-domain tuple, reversible-only abort, explicit no-turn choice, pre-stop loaded-thread refusal, and foreign lifecycle argv zero. Production-effect tests revalidate the pinned Agent/Pane/runtime/generation/thread tuple before successor absence, old stop, resume, snapshot, and same-Pane relaunch; `TestHandoverResumeReceiptSurvivesSuccessorRestartWithoutSecondResumeWire` pins the content-free operation-qualified at-most-once resume receipt. `TestInstalledPrivateJournaledGenerationHandover` is opt-in through `PROJMUX_CODEX_PHASE5_SMOKE_ROOT=/tmp/<isolated-root>` and uses exact private 0.152.0→0.152.1 binaries plus isolated XDG/tmux roots for the public handover and no-turn negative. Enforcement ledger: prompt/turn/approval resend, provider-content replay/inspection, duplicate same-thread ownership, automatic no-turn migration, foreign kill/adopt, live-old same-thread resume, Phase-6 consumers/Doctor, and unrelated cleanup remain zero. Deletion ledger: zero canonical tests deleted or renamed; Phase 0 qualification, Phase 1 lifecycle fencing, Phase 2 host/pool, Phase 3 generation-pinned routing (including the empty-prompt migration receipt), and the Phase-4 seven-effect-zero suite retain their unique symbols and boundaries; the command graph adds only four handover leaves.
|
|
61
|
-
- `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; `
|
|
61
|
+
- `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; `TestNotifyListSidebarEnterOnGroupStaleRoutableFocusesAndAcks` and `TestStatusbarClickNotifyStaleHeadFocusesAndAcks` pin that a routable stale row whose pane still exists now focuses and acks through the same path as a live one, while retaining their unique picker/statusbar transport assertions; `TestNotifyListSidebarEnterOnStaleRoutableRowFocusesAndAcks` covers the single-row Enter path and proves the consumed row leaves the queue, and `TestStatusbarClickNotifyStaleHeadWithoutInventoryFallsBackToFocusOutcome` pins the unavailable-inventory fallback where focus exit code 2 clears the row and any other failure keeps it queued for retry. `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.
|
|
62
62
|
- `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.
|
|
63
63
|
- `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.
|
|
64
64
|
|
|
65
|
-
- `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`.
|
|
65
|
+
- `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 (deterministic through the injected `createCommand` clock that stamps the create-operation marker, pinned by `TestCreateTransactMarkerCarriesInjectedClock`); 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`.
|
|
66
66
|
- `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.
|
|
67
67
|
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 2 closes the role/live consumer table. `TestAnchorAwareCreatePaneAndAgentUseExactLiveShellAnchorDetached`, `TestAnchorAwareCreatePaneAndAgentUseExactLiveAgentAnchorDetached`, `TestAnchorAwareCreatePaneAdoptsNewShellAsEmptyDefaultWithoutReplacingShellAnchor`, the paired resume tests, and the explicit-Pane/popup precedence tests pin detached exact-Window outcomes and create-time default-shell adoption. `TestAnchorAwareCreateRefusesDeadAndCrossWindowAnchorsWithZeroWrites` and `TestRegistryTopologyMaterializationRefusesDeadAnchorWithoutAlternateLivePaneInference` prohibit alternate-live-Pane inference. `TestRegistryTopologyMaterializationAgentAnchorLazyDefaultShellAndRepeatNoop` plus the installed isolated real-tmux Agent-only Window fixture pin the plan-visible lazy shell, preserved Agent anchor/Pane uid, exact default owner/ref graph, smoke-root socket containment, and repeat no-op. `TestFocusKindOnAnOfflineTargetExitsTwoAndMaterializesNothing`, `TestFocusKindMovesTheClientToAnAlreadyLiveTarget`, and `TestControlOwnedRegistryKeepsMaterializeGreen` cover strict read/navigation-only focus and the Project/ControlSession root boundary.
|
|
68
68
|
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 3 keeps canonical Pane/Agent delete and clean exit independent from explicit Window deletion. Registry-order sibling choice, optional-empty default shell, Offline Agent retention, and same Window UID/name remain pinned for non-last Pane deletion. Project runtime stop Phase 1 supersedes the old last-Pane replacement boundary: an exact clean last-Pane receipt waits for its matching `window-unlinked`, then deletes only the Window subtree with zero replacement allocation. Explicit `delete window --yes` uses the same root-preserving shape: a non-last primary reanchors to its existing sibling and a last Window leaves the Project uid/root/reservation with empty `primaryWindowRef`; only explicit `delete project` unregisters the root. Unknown, killed, unpaired, foreign, stale, missing-server, and empty observations retain zero Window-delete authority.
|
|
69
69
|
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 4 closes projection and startup over final-v2 desired state. `TestSnapshotProjectionFinalV2RefsAndOpenFreshGolden`, `FuzzSnapshotProjectionIsScopedValidAndIdempotent`, and `FuzzOpenFreshKeepsOnlyCanonicalProjectShell` pin metadata-bearing and legacy snapshot projection, Agent-anchor/default-shell policy, target-subtree replacement, source-snapshot preservation, and fixed points. `TestProjectFreshStartPruneScope` preserves an existing canonical Project/Window/shell UID chain and exact transaction-level Registry bytes across repeated Open fresh, while `TestProjectFreshStartAgentAnchorCreatesExactMinimumShellAndRepeatsNoop` covers its one permitted shell allocation; `TestRestoredOfflineAgentAnchorContinueConvergesLazyShellAgentAndRepeatNoop` covers offline restore through lazy default-shell/Agent materialization. `TestAuthorshipPromotionPreservesSiblingProjectSocketAndOtherHostDesiredState` pins exact-socket positive convergence while sibling-socket and other-host-only desired refs/status plus Registry order remain byte-identical. The isolated smoke covers snapshot/Open fresh closed repeats with the exact UID chain, lifecycle Window survival, create/resume/materialize/focus, failure/repeat paths, and exact-socket sibling isolation.
|
|
70
70
|
- `make test`: Install handover Phase 1 keeps every Window anchor writer inside the validator's anchor predicate. `TestWindowAnchorEligibilityPredicateBranches` pins the membership, role, and managed-Agent-Pane clauses of `windowAnchorEligibility`; `TestDeletePaneReanchorSkipsReleasedAgentPaneForEligibleShell`, `TestTransitionAgentReleasingAnchorPaneMovesAnchorToEligiblePane`, `TestAnchorWritersCloseOnNewDefaultShellWhenNoPaneIsEligible`, and `TestAttachAgentPaneMovesAnchorFromPreviousBindingToNewManagedPane` cover delete reselection, termination release with an empty `paneRef`, the default-shell close with preserved ineligible rows, and rebind including the snapshot-projection Offline binding; `TestAnchorWritersPreserveValidationOverRandomWindowConfigurations` (seed 20260913, 512 iterations) requires `Validate` after every writer; `TestTopologyAgentContinueAdmitsTerminatedAnchorAgentWithEmptyPaneRef` keeps the terminated anchor Agent out of the `still records a current paneRef` refusal.
|
|
71
|
+
- `make test`: Code diet Phase 1 resolves the Window anchor eligibility predicate in one owner. `Registry.WindowAnchor` no longer re-derives the rule inline and calls `windowAnchorEligibility` instead, which now refuses a shell Pane the Window does not own directly — the answer the Pane role switch in `Validate` already enforces, so no accepted Registry changes meaning. `TestWindowAnchorEligibilityPredicateBranches` gains the divergent `shell owned by a same-Window Agent` case and asserts `windowAnchorEligibility`, `windowAnchorRefEligible`, and `Registry.WindowAnchor` return the same answer for every clause; `TestWindowAnchorShellOwnedByAgentIsNotARepresentableRegistry` records that `Validate` refuses that ownerRef before the Window anchor clause, so the input is unreachable rather than latent-but-producible; `TestWindowAnchorPredicateHasOneOwner` is the retired-source negative audit — it fails when `Registry.WindowAnchor` re-derives any role, ownership, or managed-Pane clause, or when `windowAnchorEligibility` stops being declared exactly once.
|
|
71
72
|
- `make test`: `TestStampProjectSnapshotSkipsUnboundPanesInRuntimeIDDuplicateCheck` pins that snapshot save's Pane runtime id duplicate check skips only a `MissingRuntime`=True/`RuntimeUnbound` Pane: its retained tmux id reused by a live Pane stamps the live Pane's UID, leaves the unbound Pane's `Status.Activation.RuntimeID` intact and absent from the snapshot, while two live Panes, a False condition, or another reason sharing the id still refuse with `share runtime id`.
|
|
72
73
|
- `make test`: `TestStampProjectSnapshotSkipsUnboundWindowsInRuntimeIDDuplicateCheck` pins that snapshot save's Window runtime id duplicate check skips only a `MissingRuntime`=True/`RuntimeUnbound` Window: its retained tmux id reused by a live Window stamps the live Window's UID, leaves the unbound Window's `Status.RuntimeID` intact and absent from the snapshot, while two live Windows, a False condition, or another reason sharing the id still refuse with `Registry Windows ... share runtime id`.
|
|
73
74
|
- `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.
|
|
@@ -113,7 +114,7 @@
|
|
|
113
114
|
- `make test` / `make test-integration` / `make test-e2e`: explicit Registry desired-topology materialization adds the exact-one `--materialize-project <name|uid:uid>` opt-in to `reconcile resources` without changing the default plan or strict reads. A separate pure Project graph plan and ledger executor cover deterministic zero-write human/JSON preview, offline `-L` full and live `-L`/`-S` partial creation in Registry insertion order, fresh session/Window primary binding, primary reconstruction from an exact-bound shell anchor, additional Window/shell Pane creation, exact uid/name/owner/CWD mirrors, stable retries that preserve selector and socket, second-run no-op with no ensure/lease/write, raw-kill recreation versus canonical-delete exclusion, and two-socket isolation. Negative tables reject duplicate selectors, a present-but-blank selector that must not degrade into the broad default reconcile (zero Registry read, transaction, write, tmux call, and output), invalid root/CWD, zero Windows, non-shell or indirect primary refs, every pre-existing uid-less Pane, unknown/duplicate/wrong-owner claims, name collisions, races, and injected failures before mutation or with ownership-checked reverse rollback and explicit residual drift. Pre-mutation owner-set proofs reject a Window relinked to a foreign session, a Pane join-paned out of its planned Window before planning, and the same move injected at the plan-to-execute boundary. A server-wide uid preflight runs before the selected Project session is created, so an offline Project whose desired Window or Pane uid is already claimed by a foreign session is refused with zero sessions created, zero pre/post-create hook runs, and zero runtime mutation; the inventory is refreshed after the session exists to cover its new tuple and any race since. a `new-window` or `split-window` that tmux mutated before reporting a synchronous hook failure has its exact attributed handle claimed and ledgered so rollback removes it, leaving byte-identical runtime and Registry. Call logs prove stored commands, Offline/Failed Agents, Agent-owned Panes, snapshots, notifications, ephemeral sessions, and historic geometry are never replayed. Initial Pane cwd is separate from the unchanged Project hook/root cwd. The isolated real-tmux smoke uses inherited-environment removal plus a unique `TMUX_TMPDIR` and two exact `-L` sockets, and observes zero-write preview, live partial Window/Pane repair back to the original uids, a zero-write repeat no-op, an empty start command on every recreated Pane, canonical delete that is not replayed, offline full topology rebuild under the original Project/Window/Pane uids, no Agent left Running, a byte-identical second socket, and cleanup of only the sockets whose queried `#{socket_path}` is under the smoke root. Offline arbitrary `-S` session creation is an explicit safety refusal because the public name-only `PROJMUX_SOCKET` cannot encode an exact socket path; live partial `-S` remains supported, and the refusal can be lifted only with a future versioned hook socket-path contract.
|
|
114
115
|
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 15 projects one explicit snapshot into one exact closed Project Registry subtree. `FuzzSnapshotProjectionIsScopedValidAndIdempotent` and metadata/legacy/collision tables pin pure planning, target Project identity, unrelated Project and ControlSession preservation, owner/kind collision refusal, and a zero-diff repeat; commit tests require pre-commit zero writes, byte-identical source snapshots, ordinary materialization only, committed desired state plus an item notice after runtime refusal, and exact `-L projmux switch-client -c` as the final handoff. Closed-Project startup exposes exactly `Continue project` and `Open fresh`, with Esc navigation and picker-failure Continue fallback. `FuzzOpenFreshKeepsOnlyCanonicalProjectShell` is the production Fresh planner: the canonical Project Window and one Window-owned shell Pane UID/recipe survive when available, an exact minimum shell is created when absent, and every other target descendant, reservation, and conversation pointer is removed. Invalid anchors refuse with zero writes, repeat planning is a Registry zero-diff, and snapshot bytes are unchanged. Negative audits keep direct snapshot replay, global Registry replacement, snapshot deletion, retired startup labels, and hidden runtime fallback out of the startup path.
|
|
115
116
|
- `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.
|
|
116
|
-
- `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
|
|
117
|
+
- `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` and, inside a proven runtime route, the tmux tab `window_name` through the canonical `rename-window -t @N -- <name>`, without `displayName`, 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. A Window rename with no route is Registry-only, exits zero and prints one stderr line saying the tab still shows the old name; `TestWindowRenameConvergesTheTmuxTabOnTheRegistryName`, `TestWindowRenameConvergesAStaleTabWithoutRewritingTheStableName`, `TestWindowRenameOutsideAnyRuntimeRouteIsRegistryOnlyAndSaysSo` and `TestWindowDisplayRenameFailureIsNonzeroAfterTheDurableRegistryCommit` pin the converged pair, the tab-only repair of a stale tab, the no-route notice with zero tmux calls, and the nonzero committed-mirror failure. `test/integration/cli-window-rename-display.sh` runs `TestCLIWindowRenameConvergesTheTabThroughRealTmux` against an isolated real tmux server in `make test-integration`, because CI's unit job has no tmux. 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 that the Window tab follows the committed name while the session name, the raw Pane title and a second socket remain unchanged, exercises offline later reconcile and injected option-write failure/retry, and cleans only the contained sockets.
|
|
117
118
|
- `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.
|
|
118
119
|
- `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.
|
|
119
120
|
- `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.
|
|
@@ -143,7 +144,7 @@
|
|
|
143
144
|
- `make test`: Agent liveness transition Phase 0 a dead managed Pane releases its Agent: when the tmux pane behind an Agent's managed Pane is gone the Agent moves to `Offline` and drops `status.paneRef`, so `agent resume` reaches its normal path without an explicit `projmux delete pane` first. The subject is an inventory diff rather than an event handler and the tests state it as one: `after-kill-pane` fires with an empty `#{hook_pane}` so no hook can name the pane that died, and `#{pane_dead_status}`/`#{pane_dead_signal}` are empty at hook time for a clean exit, a non-zero exit, and a SIGKILL alike -- they populate only under `remain-on-exit on`, which suppresses `pane-exited` entirely and changes pane lifecycle -- so a hook cannot classify a death from tmux at all -- which is why the classification now comes from the durable receipt the supervisor or the control action wrote instead, and only a death with no receipt at all is treated as unproven; the Phase 9 entry below supersedes this paragraph's `AgentExitNormal`-for-everything rule and its `deadAgentPaneReason` spelling. `internal/integrations/metadata` gains `Mirror.LivePaneUIDs`, the mirrored-uid half of the diff, sharing its one `list-panes -a -F '#{@projmux_pane_uid}\037#{pane_id}'` query with `PaneTargetForUID` so the two can never disagree about which Panes still have a transport binding. `internal/app` covers the sweep: the core case asserts `Offline`, a cleared `paneRef`, a deleted managed Pane, the recorded liveness reason, and a `status.sessionRef` preserved pointer-identical, because the conversation pointer is exactly what makes a released Agent resumable; a five-row phase table cross-checks the selection against `CanTransitionAgent` itself rather than against a hardcoded list, so a phase outside the closed table is skipped with its Agent, `paneRef`, and managed Pane all untouched instead of forced to `Offline`, and the table is never widened; and an Agent holding no `paneRef` is never handed a fresh reason or transition timestamp. Cost is measured in transactions rather than in an unchanged registry, because both tmux pane-exit hooks fire on every pane exit in every session: a sweep that finds every mirrored uid live queries tmux exactly once and opens **zero** write transactions, an unreadable inventory releases nothing, reports no error, and opens zero transactions -- fail closed, since an unreadable inventory is indistinguishable from an empty one and treating it as empty would offline every managed Agent on a machine whose tmux server simply is not up -- and a real death opens exactly one. Both triggers are proven end to end: the reconciler path releases a dangling `paneRef` through a real mutation route while leaving a mirrored Agent `Running` on its own Pane, the ordering is read off the recorded tmux argv so the inventory query must follow the last `@projmux_pane_uid` write of the same pass (a freshly imported Pane must never look dead, which is also why there is no one-shot migration -- the first pass resolves every pre-existing dangling ref and the second finds nothing left to do), and the hidden `internal tmux release-dead-agent-panes` route dispatches, releases, opens one transaction, prints nothing, and refuses arguments because it is a sweep and not a per-pane handler -- it is now the widest-legal-event entrypoint into the shared reconciliation below rather than a second implementation. The generated `pane-exited` and `after-kill-pane` hooks are pinned as one shared backgrounded body that runs `rebalance-panes` first under its own `|| true` and then the sweep under its own, so pane layout never depends on whether the registry sweep succeeded and the sweep still runs when there was no layout to rebalance; the theme config byte-identity goldens move with that shared addition. The pre-existing explicit `delete pane` -> `Offline` transition is re-pinned unchanged and still recorded as `deleted`, so the two paths stay distinguishable in `describe agent`. A pane-exit sweep that waits behind a newer create now refreshes the exact mirrored Pane UID inside the Registry transaction before releasing anything, preserving the newer Agent as `Running` with its Agent-owned Pane; a failed locked refresh aborts with zero writes while a true death still commits one release.
|
|
144
145
|
- `make test`: Registry runtime observation Phase 0 observed status for Window and Pane: `Window`/`Pane` status is no longer inherited from the owning Project's stored `status.session.live` bool -- a bool written `true` at legacy import, refreshed only by the reconciler, and never seen by the read verbs at all, so a registry object bound to nothing live reported `live` forever. `internal/core/selector` gains `ObservedStatus(missingRoot, bound)` as the single derivation rule every kind goes through, pinned by a four-row truth table plus a resolver table that reads one unchanged registry against three different machines (the alpha project running, one pane of a live window closed, and nothing running at all) and requires the reported status to follow the observation every time; the negative guard flips `status.session.live` both ways against one fixed observation and demands byte-identical Window and Pane statuses, which is the falsifiable form of "zero remaining stored-value paths", and it is repeated at route level over real `get windows`/`get panes` output. `MissingRoot` precedence is re-pinned on all three kinds at once -- Project, plus a Window and a Pane the observation says are demonstrably live -- so the preservation contract still outranks a stale runtime. `internal/integrations/metadata` gains `Mirror.LiveWindowUIDs`, the Window half of the registry-versus-machine diff, sharing its one `list-windows -a` query with `WindowTargetForUID` exactly as `LivePaneUIDs` shares `list-panes -a` with `PaneTargetForUID`; both inventories are proven to drop unclaimed tmux objects, to issue exactly one read command each, to write nothing, and to propagate a query failure rather than flatten it into an empty inventory. `internal/core/metadata` gains `RuntimeObservation`, a `MissingRuntime` condition with reason `RuntimeUnbound` on `Window.status` and `Pane.status`, and `Mutator.ObserveRuntimeBindings`, covered for set/clear symmetry, a preserved `firstObservedAt` across repeat observations, and the preservation contract itself: no resource deleted, no uid or name changed, no name reservation released; `Window.status` is declared `omitzero` and a serialization test proves a condition-free Window still round-trips byte-identically to a pre-observation registry, which is what keeps the addition inside `schemaVersion: 1`. `internal/app` covers the observation seam and the reconciler: the snapshot is taken once per process invocation and memoized (five resolutions, one `list-panes -a` and one `list-windows -a`), a failed query fails to *offline* per inventory half and never back to a stored value, and a nil lookup is the empty observation. The reconciler integration test fires **no hook at all** -- panes and windows simply stop existing on the fake server -- and the next pass records `MissingRuntime` on exactly the vanished uids, keeps every resource queryable, preserves `firstObservedAt` on a repeat pass, and clears the condition when the runtime returns on the same uids; an unreadable inventory records nothing, matching the dead-pane sweep's fail-closed tolerance. The acceptance table drives the real `intmetadata.Mirror` over a fake tmux server through the real `get` route: the reported `live` count is asserted as an inequality against the actual tmux pane count while the server is torn down one pane at a time, closing one pane offlines exactly that Pane on the next query with no hook and no write while its siblings stay live and it stays selectable, and a server that is up but mirrors no `@projmux` uid at all -- the tmux-restart shape, and the exact machine state that produced the defect -- reports zero live Windows and zero live Panes while still listing every one of them.
|
|
145
146
|
- `make test`: Registry runtime observation Phase 1 binding reapply and adoption: the `@projmux_window_uid`/`@projmux_pane_uid` options were written once, at legacy-session import time, and never again -- a tmux server restart or an option reset left every live window and pane carrying no uid at all, which is what made `projmux delete pane` with no selector fail with *the active tmux pane carries no `@projmux_pane_uid`* in almost every pane on the measured machine, and the import guard `!result.ProjectReused || len(reg.WindowsOf(projectUID)) == 0` made that permanent by *avoiding* duplicate Windows instead of repairing the drift. `internal/core/metadata` gains `BindingMatcher`, the single structural matching rule: Project scope first, then ordinal alignment (the session's tmux windows in `window_index` order against that Project's registry Windows in creation order, panes the same way inside a matched Window), returning exactly one of rebind/adopt/unmatched/refused. Two table tests, one per kind, pin every decision the rule may make against a two-Project fixture that deliberately carries the Window name `one` and the Pane name `zsh` in both Projects -- so a name-based match would cross and be caught -- and every refusal has its own row: an unresolved session scope, a uid the registry has never heard of (never adopted, reported as its own `AdoptionForeign` outcome so the anchored import path may mint rather than reuse -- projmux itself produces unknown uids, because a reconcile rolled back by a pre-create hook refusal has already written them onto non-transactional tmux options), a uid another Project owns, a Pane of a sibling Window, a uid two live objects both carry, a candidate already bound to a different live tmux object (skipped, never stolen), and a candidate already claimed earlier in the same pass; `paneUIDsInWindowOrder` is pinned to registry insertion order against a Window that mixes shell and managed Panes, because borrowing `snapshotPanesOf`'s shell-then-managed grouping would shear the alignment. `ImportLegacySession` takes a pass-scoped matcher and always walks `legacy.Windows`; `ImportedWindow`/`ImportedPane` gain an `Origin` of created/adopted/rebound, and adopted objects are reported to the adapter for mirroring but never recorded in the transaction ledger, proven by an empty `Created` on a pure-adoption re-import and by a single-entry ledger when an adopted Window still imports one extra Pane it has no candidate for. Import-path negatives cover zero duplicate Windows across two passes with every uid wiped, zero cross-project adoption for two same-shaped sessions, and and a foreign uid that mints a fresh Window instead of re-identifying the unbound one sitting right next to it, while a uid another Project owns produces nothing at all. `internal/integrations/metadata` extends `ObserveLegacySessionTargets` to read the uid each live object already carries (5-field window rows, 9-field pane rows), pinned for a bound window and a blank one in the same session. `internal/app` wires two binding steps against one pre-pass live-uid observation taken through the Phase 0 read seam: the anchored import path, and a new `reapplyUnresolvedBindings` for sessions with no `@projmux_project_path` -- the shape every session on the measured machine had -- which resolves through the Project<->session-name edge `refreshSessionProjections` maintains and creates nothing. The reconciler suite runs the measured fixture directly: one live blank window, a Project reachable only through `status.session.name`, two unbound registry Windows, and a stored `status.session.live` of `false` that nothing is allowed to consult; a **single** pass writes `win-first`/`pan-first` onto the live objects together with `automatic-rename off` and the name mirror, creates and re-identifies nothing, and leaves `MissingRuntime` on exactly the Window and Pane that really are unbound. A second pass leaves both the registry and the whole fake tmux server byte-identical. The refusals are covered at route level too: a session no Project claims and a session two Projects both claim each write nothing, two Projects with two sessions each reach only their own objects, a foreign uid is left exactly as found with none of its panes bound, a Window already bound to another live tmux window is not stolen (the blank window takes the next candidate instead), and a binding write that fails is skipped rather than failing the create that triggered the reconcile.
|
|
146
|
-
- `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
|
|
147
|
+
- `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 the imported displayName is read from tmux `window_name`, 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; it does converge the live tab on the new stable name, which is the rename display contract on line 117 and not an import rule; Pane `pane`/`pane-1` import naming and provider-base Agent naming/linkage/status remain covered by their existing Phase 3/4 tests.
|
|
147
148
|
- `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.
|
|
148
149
|
- `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.
|
|
149
150
|
- `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.
|
|
@@ -159,7 +160,9 @@
|
|
|
159
160
|
- `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.
|
|
160
161
|
- `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.
|
|
161
162
|
- `make test`: interaction-first Keybindings details keep action state, current single keys/sequences, and add/manage/remove/reset/test actions ahead of passive explanation. `TestSettingsKeybindingActionDetailMatrix` verifies semantic/handler/`internal/cli` manifest parity directly against the internal models while separately proving every rendered selectable row is handled; `TestSettingsKeybindingActionDetailPrioritizesStateBindingsAndActions` freezes the default/custom/unbound row order and mutation reachability; `TestSettingsKeybindingKeyDetailRowsAreAllHandled` keeps replace/remove/test routes and requires an unavailable test row to carry both reason and next step; and `TestSettingsKeybindingRetiredContainerCopyIsGone` renders Action/Key/Sequence surfaces in en-US and ko-KR and rejects visible Target/Result/Placement/Anchor/Handler, canonical storage/delivery, cancellation teaching, and Advanced/Details/Delivery/About-style containers. Delivery outcome, one-reader, unavailable/cancel/no-write, capture/typed normalization, feedback-stage recovery, protected-action, reserved-key, sequence storage/runtime, and distinct create/resume parity tests remain in the maintained suite.
|
|
162
|
-
- `make test
|
|
163
|
+
- `make test`: Settings search matches the rendered locale label without moving a typed option name off its row. `withSettingsRenderedLabelSearchText` runs first in `settingsCommand.runPicker` and joins the ANSI-stripped rendered label onto every keyed row's SearchKey as `before<TAB>key<TAB>after`, column by column (glyph, name, description): a column goes after the key unless the joined text would then match an option name (a row's rendered name column, its lowercase, or the last one or two SearchKey words such as `split_cwd_from pane`) that a later row already selects first over the pre-join texts (builder SearchKey alone, or name column + SearchKey; picker text for rows without a key), otherwise before the key under the same check, otherwise word by word, otherwise it is left out; matching goes through `picker.FilterItems` itself. The builder key stays verbatim between the tab delimiters (so every key match still matches), keys that already contain the label or were already joined stay untouched (idempotent), and rows without a SearchKey stay untouched so SearchKey-free Views keep the picker's scored mode; because it runs before `withSettingsFeedback`, the transient Feedback row keeps its old key and never absorbs mutation summary text. `TestSettingsSearchTextCoversLocalizedLabels` drives the real `Run` through a scripted native picker that enters every catalogued navigation row (a dynamic template only from its catalog parent) and a scoped list of View openers, never a mutation or `quit:open`, and captures the exact options every searchable Settings View hands the native picker in en-US and ko-KR: every ko-KR row whose rendered label contains Hangul must survive `picker.FilterItems` for its Korean word and phrase (the Feedback row is the one commented exemption), every en-US row must survive its rendered name as typed and in lowercase, the catalog View/Choice nodes must be reached except a commented exemption list with a frame-count floor, the whole temporary HOME must stay byte-identical with only read-only tmux observations refused, and the first surviving row for every query `test/e2e/linux-smoke.sh` types, including the keymap and Feedback-row states that sequence produces (such as the second `weekly` after `Claude Weekly complete`), must equal the pre-change first row. `TestSettingsSearchTextKeepsOptionNamesOnTheirRow` walks the same en-US and ko-KR trees and types every row's rendered name (en-US also in lowercase) into every keyed View: a row that is the first survivor over the pre-join texts (builder SearchKey alone, and separately name column + SearchKey) must stay the first survivor over the joined rows, and every row the builder key found must still be found. `TestSettingsSearchTextSplitCWDOptionNamesSelectTheirRow` pins `split_cwd_from pane` and `split_cwd_from project` to their own New splits start in option in en-US and ko-KR, with the default config and with global `[ai] split_cwd_from = "pane"`. `TestSettingsSearchTextJoinsRenderedLabel` pins the helper: verbatim key, tab, stripped label, empty key unchanged, idempotent, no escape bytes, contained label unchanged, input slice not mutated; `TestSettingsSearchTextJoinGuardsLaterOptionNames` pins the before-key, word-by-word and shared-description placements with their first rows. `internal/ui/picker`, `internal/ui/pickercompat`, and `internal/i18n` are unchanged.
|
|
164
|
+
- `make test`: Settings root search results land on their owning row. `settingsRootResultLandings` builds the result rows and, in the same walk, the destination each one encodes: the chain of real picker Values from the root frame down to the View that owns the row, plus the row to focus once it is open. `settingsCommand` carries that as `pendingNavigation`/`pendingFocus`, and both halves live in `runPicker`, the single funnel every Settings View already renders through, so none of the twelve section loops learns a focus argument. A pending chain step is answered without rendering — the loop that owns the frame descends on its own — and only after every `with*` decorator has run (the Feedback row inserts at index 0, or 1 behind a Back row) is the target resolved to a `start:pos` binding. `TestSettingsRootResultLandsOnItsOwningRow` drives the real `cmd.Run` once per result row of both scopes — 318 rows in the default fixture, walked out of the catalog rather than a frozen ID list, so a new node extends the test by itself — and asserts four things per row: the picker sees exactly one frame after the root, so the descent really was synthesized; that frame is the frame a hand walk of the same chain ends on, through the real loops; the row the frame's `InitialIndex` names is the target, resolved by an assertion-side matcher rather than the production one; and a target the fixture does not render leaves the View unfocused on its first row instead of erroring. Zero controls run across all of it: the command seams refuse every command and every non-read-only tmux call and record the attempt, the quit runner counts its calls, and the temporary HOME is byte-identical before and after. `TestSettingsRootResultConfirmAndActionRowsAreFocusedNotRun` pins `Quit Projmux`, `Reset theme` and `Clear saved root` by name, and `TestSettingsRootResultAbandonsALandingWhoseChainRowIsGone` corrupts one chain step to prove the guard drops the whole landing and renders that frame normally rather than pressing Enter on whatever is there.
|
|
165
|
+
- `make test-e2e`: Settings root search lands on the owning row, against the real attached tmux client. The smoke types `Git Icon` at the Settings root, where it survives as exactly one result row — `Appearance › Status Bar › Git › Icon` — and selecting it must open `Settings > Appearance > Status Bar > Git > ` in a single press, three Views down, with no decoration file written; the descent is synthesized at `runPicker` and the intermediate Views are never rendered. The cursor is then proven to be on the target row rather than on the View's first row by pressing a bare Enter with no query: it opens `Settings > Appearance > Status Bar > Git > Icon > `, which a cursor resting on `Back` could not do, and opening the chooser still writes no decoration file. Four Back presses unwind the whole descent one View per press back to the root. The older screen-by-screen walk stays alongside it, since in-View search still filters that View's own rows: `Status Bar` at the root, then `status bar components` in Appearance, then `Provider Integrations` and `tmux event source bell producer` for the Notifications walk, then `Appearance` / `agent usage hud providers windows` / `claude` / `weekly` for the Agent Usage HUD leaf save, then `Keybindings` and `Open / close Project Sidebar` for the recorder flow. Every query the script types is also pinned by `TestSettingsSearchTextCoversLocalizedLabels`, which measures the first surviving row for each one, so a search-text change that re-aims Enter fails in `make test` before it can silently walk the smoke somewhere else. Every wait is anchored to a byte offset in the recorder log so a frame from an earlier screen cannot satisfy it.
|
|
163
166
|
- `make test`: lifecycle hook context coverage verifies that the production client supplies `projmux` socket metadata, standard persistent and ephemeral `post-create` receive the exact non-empty pane id returned by session creation, `pre-create` receives no pane id before creation, and empty socket/pane values are omitted from the hook environment.
|
|
164
167
|
- `make test`: atomic project and private-global config writes preserve existing regular-file metadata, resolve valid relative and multi-hop symlinks to the final target without replacing link inodes, retain `0644`/`0600` defaults for missing destinations, recover dangling symlinks as regular files, and preserve original content with no temp artifacts across resolve/stat/create/write/close/chown/chmod/rename failures. Source-level global and Settings project writer tests exercise the production entrypoints with isolated home/repository paths.
|
|
165
168
|
- `make test`: fast unit coverage for app-layer AI split native agent launch/selective popup-toggle/settings/status/notification parity including AI pane option metadata, watcher metadata bootstrap for existing panes, capture-backed reply detection, missing-pane watcher shutdown, armed focus-only reply badge clearing, busy attention preservation on focus clear, manual AI topic preservation while watcher status still updates, agent-labeled desktop notification message context with pane-title-first body text, global/project-local lifecycle hook dispatch for post-create/pre-create/post-attach/send-noti with project hook and `.projmux/config.toml` trust-store hashing plus env/settings kill-switch gating, declarative startup command and hook run coverage, `send-noti` stdin JSON delivery plus `PROJMUX_NOTIFY_*` env payload, notify queue write success/failure/depth-guard dispatch rules, notify/statusbar/sidebar origin-client focus routing, project config generic env session environment application without Kube-specific projection, project hook/trust writes with startup/env compatibility, startup command send-keys orchestration plus startup pane replay markers, pre-create abort behavior, and shared projmux notification icon paths, and scoped even row/column resizing after shell and agent splits, status-bar git segment parity including muted git branch block styling, compact dirty/staged/ahead/behind state colors, and bounded git child execution, statusbar pwd display-only native-framed path popup with shared popup-wait-key/no-extra-payload-line command coverage, no clipboard or tmux buffer copy, and popup-wait-key cursor/raw-mode restore coverage, statusbar usage native HUD popup alignment/height-budget/threshold palette/muted sync-staleness/fallback coverage without raw CLI popup or popup-toggle stacking, statusbar settings click popup fallback and settings chip right-edge rendering without default trailing space, popup-toggle stale marker recovery, isolated `projmux shell` tmux app launch/config generation including home fallback plus project-context default session targeting from `PROJMUX_CWD` or nearest project marker, app-owned project-name statusbar layout, distinct project badge color, and quiet debounced session-state autosave command/app-config trigger, shell-entry welcome release prompt and inline update handling, shell update skip-by-latest-tag behavior plus best-effort stale-cache refresh, pane/window keybindings, keymap.toml tmux override rendering/stale unbinds including retired direct/UserKey cleanup, Settings Keybindings root/list/detail capture flows including parse-error rows, unsafe raw capture and timeout guards, disable/reset writes, app config regeneration, live tmux source-file reload, no-live-tmux save behavior, and stage-by-stage apply recovery copy, window rename bindings, pane rename helper/binding, pane-exit rebalance command/hooks, hook-pane and after-select-pane based attention focus hooks, attention badge toggle/clear/list/window rendering, attach/current/kill/pin/preview/prune/sessions/session-popup/settings commands, switch, tag, tmux helper commands, update status/check/apply cache and installer detection including GitHub Release binary asset selection/extraction/replacement, doctor versioned read-only report/section/verbosity contracts, AI notify integration diagnostics, and Session State resume metadata diagnostics, Settings AI notify integration diagnostics read-only status/conflict/CLI guidance, and Settings About update status/check action wiring, untitled standalone popup-toggle marker close/config install, direct popup minimum sizing, AI picker minimum width and height, sidebar minimum width and compact badge spacing, preview select writes, popup render output after cycling, switch picker pin action behavior without inline settings rows, nested settings hub sections for AI defaults, project picker filesystem scan/pin actions, Project Root settings source/shadowing/set/current/clear flows, app/keybinding info including Ctrl-M rename forwarding, and About version/source rendering, switch picker focused-session kill, switch picker launcher-key abort bindings, switch explicit project-root, unconfigured-root, and weak managed-root heuristic parity, switch popup hiding new-session candidates while sidebar keeps create-capable rows, switch row project-name display with `~` pinned to the top and live-session-first sorting, pretty-path, preview context without retired Kube metadata, switch settings subcommand flows including add-current-pin, interactive add-pin picker, and settings label/preview polish including Phase 2 Settings/hookmaker/project-startup/trust/quit destructive row color regression strings, native preview wiring, baseline picker surface parity including prompt/footer/header fallback without app-name filler and search-key scoped card matching, sidebar compact action-only key footer, sidebar preview-window/start-position behavior without focus-time session switching, sidebar row/window ANSI styling with pane-aggregated attention badge state and AI topic labels, Alt+2/Alt+3 legacy popup row, preview-window, pane metadata, and pane-snapshot parity, switch read0 card rows with active/inactive title styling, right-side status badges, combined directory/git metadata with muted inactive branch styling and bounded slow-repository branch enrichment, statusbar-matched block window tabs with window attention badges, read0 expect-key action parsing, restored pin/tag card badges, and restrained selected-row marker styling, switch preview metadata without duplicated directory/git rows, preview metadata rendering, popup pane display names for AI agents, AI topics, and shell commands, switch preview cycle bindings, sessions picker preview/cycle/open/kill wiring including attached-session fallback behavior, sessions picker launcher-key abort bindings, popup/switch preview summary formatting, popup sessions tmux entry helpers, switch/popup/session rendering, session identity, session-state Claude/Codex/Antigravity resume and declarative startup replay, session snapshot capture/autosave recipe classification including save/autosave pre-capture resume metadata refresh from live AI session ids including Antigravity conversation ids, Claude transcript paths, and Codex rollout log cwd matching/ambiguity skips, candidate discovery, config path derivation, popup preview read-models, and pure state rules including preview, tag, and lifecycle stores.
|
|
@@ -169,6 +172,7 @@
|
|
|
169
172
|
- `make test` also covers resume-title cleaning at the shared Claude/Codex/Antigravity choke point: known Claude command, local-command, task-notification, and system-reminder scaffolding is discarded so discovery falls through to the first real prompt, while inline `<`, leading HTML, and custom-element prompts remain intact.
|
|
170
173
|
- `make test` also covers the hardcoded usage HUD manual refresh key, full enabled-adapter collection through the existing throttle/backoff policy, cooldown no-op, cached popup rerender, and unchanged click/`u` cache-only behavior.
|
|
171
174
|
- `make test` also covers usage collection-failure visibility: Claude/Codex/Antigravity adapter tables for one, several, and all rows failing field validation plus an empty array, asserting the surviving snapshot contents, the exact bounded warning string (count + row-index/field reasons, capped at five with `(+N more)`), and negative assertions that a skipped row is never fabricated as `0%`, a zero reset, or a synthesized identity; the two shape failures that still abort a whole Claude collect (non-array `limits`, the 64-row cap) versus a partial collect that refreshes the healthy rows; privacy guards that no raw upstream value, reset timestamp, opaque bucket ID, or decoder message reaches a warning; `formatStatusUsage` staleness goldens for fresh/stale/very-stale with the fresh golden byte-identical to the pre-change output and the age indicator driven by `staleAfter`/`veryStaleAfter` alone; and the `usage.collect.outcome` operations-journal family — closed provider/failure schema with shape rejection, one row per failing provider, zero rows on success, once-per-run suppression of identical tuples, best-effort appends that never change the command result, and `projmux diagnostics log --component usage --tail N` rendering.
|
|
175
|
+
- `make test` also covers why a whole Claude usage collection failed, as recorded in the operations journal: `TestCollectFailureClassesKeepMessagesByteIdentical` drives every failing return of the Claude adapter's `Collect` (unresolved path; missing, unreadable, and unparseable credentials; empty access token; 401 without a refresh token; rejected, unreachable, and access-token-less refresh; 401, 429, and 500 after a successful refresh; 429; 403 and 500; request build, transport, and body-read failures; unparseable body and non-array `limits`) and requires exactly one `errors.Is` failure class, an `Error()` byte-identical to the message before classes existed, and a still-reachable wrapped cause; `TestCollectBackoffShortCircuitAndPartialResultCarryNoFailureClass` keeps the backoff short-circuit and row-skip warnings unclassified; `TestClaudeWholeCollectFailureClassesLandInJournal` runs the real adapter against temp credentials and httptest endpoints through the Manager and the usage command and requires `credentials-unavailable` (path unresolved, file missing, unreadable, unparseable), `credentials-token-empty`, `auth-rejected` (including a 401 for the refreshed token), `rate-limited`, `http-status`, `network-error`, or `response-invalid` as one sourceless `error/error/runtime` row, the refresh path rewriting only the temp credentials, and a whole-file journal scan with zero fixture tokens, credentials paths, upstream body markers, or status numbers and only the fixed usage fields per record; `TestUnclassifiedAndNonClaudeWholeFailuresStayCollectFailed` and `TestClaudeFailureClassMappingIsClosedAndDistinct` keep a Claude error that only matches a classified message by text, and Codex/Antigravity failures, on `collect-failed`, and give every class its own token; `TestUsageRecorderClaudeWholeCollectFailureClasses` pins each token's severity, tuple match, sanitizer acceptance, store round trip, and rejection of sourced, informational, or message-carrying variants. The `internal/app/usagecmd` `TestMain` points `HOME` and `XDG_STATE_HOME` at a private directory before any test runs and fails the package run, naming each record's event/provider/failure, when a test without an injected `journalFn` reaches the default operations journal; `TestUsageStatusSwallowsAdapterErrorByDefault` and `TestUsageStatusEchoesAdapterErrorWithDebugEnv` inject their journal and assert its row.
|
|
172
176
|
- `make test` also covers usage staleness at the statusbar width: a width-parameterized table over the budgets the generated tmux config can hand the renderer (100/120/140/160, the share a 120/140/160-191/240-column client leaves after the notify reservation) x fresh/stale(>10m)/very-stale(>1h) pins that a stale provider carries a marker at every one of those widths, that width pressure can no longer evict the `~`/`~~` staleness signal together with the purely cosmetic level-0 `(3m)` indicator, and that the three states stay mutually distinguishable at each width; one usage-budget table golden freezes the exact bytes (color escapes included) at 40/60/80/120/160 for all three levels and for both the three-provider fixture and the shape an actual install renders, its 120-cell rows are byte-identical to the `status-usage-w120-*.golden` files it replaced, and the two narrow budgets get their own test that pins the marker as present at 60 and the `~~`/`~` collapse under hard truncation at 40 as a ladder limitation rather than a budget one; and a fresh-state parity golden generated by the pre-change tree (98ec3b1) across widths 0/40/50/60/80/100/120/160/200 proves a healthy install is repainted at no width, backed by a direct assertion that the stale-only tiers collapse byte-exactly onto their neighbours when nothing is stale. The plain `~`/`~~` marker the colorless text tiers now emit is asserted colorless and confined to providers that opted into the age signal.
|
|
173
177
|
- `make test` also covers the statusbar row-0 width budgets: the generated tmux config derives both `--max-width` arguments on `status-format[0]` from `#{client_width}` instead of the former hardcoded 80/120, splitting the row as notify=min(client_width/2, clamp(client_width-140, 20, 80)) and usage=client_width-notify, reserving notify's measured 20-cell floor rather than its 80-cell design cap so a 191-column client gets 140 usage cells instead of the 111 PR #624 left it, so a miniature tmux-format interpreter — modelled on measured tmux 3.6 behaviour, including that a bare `#{<:a,b}` is a string comparison and only `#{e|<:a,b}` is numeric — evaluates the emitted formats against the widths measured on a real server, and pins that the two budgets sum to exactly the row at every width from 2 to 400, that neither segment is ever budgeted to zero (which `--max-width` reads as unbounded), that notify keeps exactly its historical 80 on every row at least 220 wide and is never squeezed below its 20-cell reservation on a row that can host it, that the usage budget is monotone in the client width and reaches the old 120 constant already on a 140-column terminal, that it never comes out below min(client_width-20, 120) at any width (the corrective regression guard PR #624 failed), and that it clears the measured 124 cells Claude's weekly bar needs at the reported 191-column width and first does so at 144, that every nested conditional uses the fail-safe numeric `e|<` form and the innermost false branch is the literal 80 so an unevaluable comparison degrades to the historical reservation, and that neither budget is a literal cell count; a product-shape usage fixture pins that the old 120-cell budget dropped Claude's weekly bar and that the second-bar tier returns at 124 cells with the staleness marker intact; and the generated-config digest goldens for the fallback theme and every preset move together with the one intended line.
|
|
174
178
|
- `make test` also covers the usage segment's element-priority degradation: `usageShedOrder` in `internal/app/usagecmd/usage.go` is the single definition of the drop order (cosmetic age text, then stale age text with the `~`/`~~` marker kept, then a provider's secondary window bar, then bars segment-wide, then long labels), per-provider rules run tail-first over the canonical provider order, and `docs/statusbar.md`'s `Usage element drop order` section is read by the test rather than duplicated so the two cannot drift; a width-sweep table pins the exact element set every fixture renders at 40/60/80/100/120/160/200 for three shapes — the installed shape, plus two shapes where more than one provider is eligible for the per-provider rules, which is the only way to tell element-priority degradation apart from the whole-segment `first tier that fits` ladder it replaced (at 120 cells the dual-window shape now renders 118 cells keeping Claude's second window and shedding only Codex's, where whole-segment selection drew 92); the ordering guarantees are asserted as width relations derived from the renderer rather than at hand-picked widths, so an age indicator is always shed at a strictly wider row than any official window bar and the `~`/`~~` marker outlives every official window bar; every reachable shed state is walked directly to prove no step removes a provider, its official window (5h, otherwise weekly), or its staleness marker, and that each step strictly narrows the segment; and a wide-width parity golden generated by the pre-change tree (`main` 6dfed76) plus the unchanged `status-usage-budgets.golden`, `status-usage-fresh-widths.golden` and the three staleness goldens prove a row wide enough to shed nothing is byte-identical to what whole-segment selection produced.
|
|
@@ -188,6 +192,8 @@
|
|
|
188
192
|
- `make test` also covers keybinding surface tier catalog rules, action-centered `keymap.toml` `keys = [...]` multi-alias parsing/writing, quoted internal `Surface:Action` tables, legacy popup action ID aliases, canonical popup toggle names, AI split popup-vs-direct action labels, generated tmux config multi-alias rendering, global/direct conflict detection, surface-scoped native picker command conflict detection, Settings Keybindings list/search/detail surface-aware picker-local labels, compact action-list state copy, flat single-key/sequence action detail rows, key detail remove/test/replace rows, one visible Add binding flow, and one typed binding surface; the unified native recorder's 0–4 stroke reducer, length-driven single-versus-sequence save, empty Enter/Backspace no-op, last-stroke pop, Esc zero-mutation cancellation, four-stroke cap, continuous native byte-stream comma draft rendering with canonical space-form results, conflict retry, and one-reader/no-event-leak lifecycle; capture/typed comma-or-legacy-space normalization and byte-identical save/apply output, atomic single↔sequence replacement, comma list/detail/diagnostic display with space route/disk/runtime parity, and negative guards against the retired Add sequence, Record next stroke, selectable stroke-remove, and Sequence Editor authoring routes; the reserved Enter/Escape/Tab/Backspace/Delete/navigation base-key classifier across tmux aliases and modifiers, identical capture/typed/final-save rejection before schema/file/config/live-tmux writes, later-printable and first-stroke/raw/conflict parity, shipped PlainChord/PlainChords/PrefixChord/Sequences protected-action inventory, reasoned whole-editor read-only rendering, forged dispatch/direct-helper no-write, retained delivery tests/navigation, and existing schema-v2/default/runtime parse-render-bind parity; collapsed Troubleshooting entry copy, Settings delivery diagnostics that split logical key/raw bytes/tmux received key while distinguishing missing, ambiguous, adapter-needed, and delivered states, safe direct key pool and risky/reserved diagnostic-only copy, Projmux-owned Ghostty/Windows Terminal advanced delivery copy, raw escape/CSI-u/xterm modified/UserKey/UserSequence rejection from primary keymap storage, Labs compatibility redirect to the Keybindings root, existing macOS capture/add-key flows, unsafe raw capture, reset behavior, stale guide docs guards, and welcome/runtime footer copy that avoids hardcoded launch-key guides.
|
|
189
193
|
- `make test` also covers known retired live no-prefix cleanup for the old `C-t` pane-label binding: cleanup renders before current binds, explicit current `C-t` reassignment wins, repeated apply is byte-idempotent, and unrelated bindings remain unchanged. `TestTmuxApplyRetiresStalePaneLabelChordAndMigratesKeymapOnce` additionally pins that the first apply migrates the keymap to `schema_version = 2` and the second writes no bytes and leaves exactly one backup.
|
|
190
194
|
- `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.
|
|
195
|
+
- `make test` / `make test-integration` / `make test-e2e`: Orphaned Registry Window Phase 2 managed close keys route the app config's `prefix x` and `prefix &` through canonical delete. `TestManagedDeleteKeyBindingsRenderMirrorGuardedCanonicalRouteWithStockFallback` pins the app-scoped `pane.delete`/`window.delete` catalog actions and their exact `if-shell -F "#{@projmux_pane_uid}"`/`"#{@projmux_window_uid}"` bindings: the mirrored branch is only `internal tmux delete-confirm`, the else branch is tmux 3.6's stock `confirm-before` body byte for byte, and the standalone snippet keeps both keys stock. `TestManagedDeleteKeymapChangeAndDisableReachGeneratedConfig` moves and disables them through `keymap.toml` `prefix`, adds a root key through `keys`, rejects two managed actions on one prefix key, and proves a vacated stock key is unbound and then re-bound to tmux's stock body before any managed bind. `TestManagedDeleteCatalogSurfaceRowsMapBothDirections` ties `catalog.pane.delete`/`catalog.window.delete` to `mutationKillPane`/`mutationKillWindow` in both directions. `TestTmuxDeleteConfirmIssuesOneLocalizedPromptToTheExactClient` issues exactly one en-US/ko-KR `confirm-before -b -t <client>` whose command is the Pane menu Kill route or `internal tmux window-delete` with the press-time anchor, performs zero deletes before the answer, and resolves to one guarded route and one runtime ledger row; `TestTmuxManagedDeleteIntentsRefuseMalformedArgvWithZeroTmuxCalls`, `TestTmuxWindowDeleteIntentReportsCanonicalResultToTheExactClient`, and `TestTmuxWindowDeleteIntentRefusalIsShownWithoutRawFallback` cover the canonical Window intent. `TestDefaultCatalogManagedDeleteConfirmParity` holds en/ko parity for the Registry deletion and the Agent-does-not-return-on-Continue impact. `TestGeneratedWindowLifecycleActionsReachTypedHandlersWithoutRawManagedVerbs`, `TestGeneratedCatalogMutationAndNavigationArtifactsHaveOneSurfaceRow`, and `TestFullRenderedTmuxConfigsHaveClosedGeneratedMutationSurfaces` now admit `kill-pane`/`kill-window` only as those exact stock bodies, counted per rendered config including a close-key override config. The integration smoke reads both applied `list-keys -T prefix` bindings. The e2e pane-menu leg presses `C-b x`/`C-b &` with `send-keys -K` on the attached client for a managed Pane, an unmanaged Pane, a managed Window, a stale-mirror Window refusal, and the Project's last Window. Raw-kill `killed` retention, Continue, and teardown tests are unchanged.
|
|
196
|
+
- `make test` / `make test-integration` / `make test-e2e`: Orphaned Registry Window Phase 3 routes tmux's stock menu Kill entries for managed resources through canonical delete. The app config unbinds and replaces tmux's stock `prefix <`, `MouseDown3Status`, `M-MouseDown3Status` Window menus and `prefix >`, `M-MouseDown3Pane` Pane menus; the standalone snippet keeps all five stock. `TestManagedStockMenusRenderInAppConfigOnlyAfterUnbindingTmuxStock` pins that scope, unbind-before-bind, the unchanged `MouseDown3Pane` in both configs, and that a keymap-assigned `prefix <` still wins. `TestManagedStockMenusKeepTmux36ItemsExceptManagedRoutes` parses tmux 3.6's stock menus spelled as literals and requires every generated item to equal them except Kill (`if-shell -F "#{@projmux_window_uid}"` to `internal tmux window-delete` with no `delete-confirm`, or `"#{@projmux_pane_uid}"` to the Pane menu Kill route, each with tmux's stock `kill-window`/`kill-pane` as the else branch), Rename (`internal tmux window-rename`), New At End (`internal tmux window-create`), Horizontal/Vertical Split (`internal tmux pane-menu split-right|split-down`), and the removed Respawn and New After. `TestManagedMenuKillCarriesTmuxKillOnlyAsTheMirrorAbsentBranch` holds all six Kill items, including `MouseDown3Pane`, whose unmanaged Kill now runs tmux's kill instead of refusing. `TestManagedWindowMenuRunsTheCatalogCreateAndRenameCommands` ties New At End and Rename to the catalog renderer and proves the Rename prompt template is `##`-escaped: measured on tmux 3.6, a menu item is format-expanded when the menu opens, and an expanded `%N` Pane handle in a `command-prompt` template is a response placeholder. `TestGeneratedPaneMenuArtifactsHaveOneClosedSurfaceRow` maps every item of all six menus to exactly one `window-menu.*`/`pane-menu.*` surface row and back, admits `swap-window`/`swap-pane`/`resize-pane` only as the exact stock presentation items and `kill-window`/`kill-pane` only as the Kill else branch; `TestFullRenderedTmuxConfigsHaveClosedGeneratedMutationSurfaces` counts those per rendered config. `TestGeneratedPaneMenuGolden`, `TestGeneratedPaneMenuOmitsRawTmuxMutationVerbsAndRespawn`, `TestTmuxPrintConfigBindsPaneContextMenu`, and the `TestBrightPhase2GeneratedConfigByteIdentity` digests change only for the Kill guard and the new menus. The run-shell ledger adds a `window-menu` surface. The integration smoke reads the applied `list-keys` of the five menus and `MouseDown3Pane`. The e2e pane-menu leg drives the real menus on the attached client with `send-keys -K` and SGR right-clicks: `prefix <` New At End, `MouseDown3Status` Rename and Kill on a non-current managed Window, `M-MouseDown3Status` Kill on an unmanaged Window with the Registry unchanged, a stale-mirror refusal with no raw kill, `prefix >` Horizontal Split and `M-MouseDown3Pane` Kill on a non-active managed Pane, `MouseDown3Pane` Kill on an unmanaged Pane, and a second Project's last Window leaving a zero-Window Project.
|
|
191
197
|
- `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.
|
|
192
198
|
- `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.
|
|
193
199
|
- `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.
|
|
@@ -210,7 +216,7 @@
|
|
|
210
216
|
- `make test` also covers AI hook runtime action precedence over catalog defaults, runtime quiet for known Codex notify events, runtime notify for known Claude quiet events, generic in-app-only notify rows for known Codex hook events without specialized handlers, suppression of desktop notification and `send-noti` dispatch on that generic path, separation of runtime hook action from catalog install events, Settings > Notifications hook quiet policy display/write behavior without external install/remove execution, hook desktop notification payload parity with the in-app queue text across the shared OS notification payload, normal/transient OS urgency and expiration for critical AI queue rows, and dormant title/capture fallback gating once a pane is hook-active.
|
|
211
217
|
- `make test` also covers Settings > Session State as global settings-only UI with default-off auto-save and no window/pane tree, Settings > Project > Session State project-derived identity plus project auto-save override/effective source rows and distinct save labels, closed-project save disabled reasons, named-snapshot portable path conversion, project/global auto-save precedence, Projects > Sessions > State read-only snapshot overview, and the optional closed-Project startup screen with exactly `Continue project` / `Open fresh`, Esc navigation, existing-session bypass, startup-before-trust ordering, detached client-scoped continuation, trust approve, and trust deny/cancel zero-session behavior.
|
|
212
218
|
- `make test` also covers `projmux shell` usage without startup selector flags, project-context default target lookup from `PROJMUX_CWD` or nearest project marker, explicit `--session` separation from project-derived defaults, and direct empty attach behavior without shell startup picker/replay.
|
|
213
|
-
- `make test` also covers pane identity separation and projection: canonical `rename-pane-label` prompts with `pane label:` initialized only from the current label, Enter
|
|
219
|
+
- `make test` also covers pane identity separation and projection: canonical `rename-pane-label` prompts with `pane label:` initialized only from the current label, Enter runs the Registry rename route `internal tmux pane-rename`, empty Enter changes nothing, and native Esc cancels; unchanged Ctrl-Shift-M default and Settings expose only `rename-pane-label`, while stale `[bindings.rename-pane-topic]` keymaps fail with an exact `[bindings.rename-pane-label]` replacement. AI topic set/clear keep existing topic/manual ownership, no raw-title action is exposed, and pane border/window tab/popup/sidebar/recent-window resolve user label → agent AI topic → known shell → raw title.
|
|
214
220
|
- `make test` also covers session snapshot pane identity capture, old/new decode, and replay field-matrix contracts: `Pane.Label`, raw title, recipe topic/manual ownership, startup fields, and agent/resume metadata stay separate; old snapshots leave label and ownership absent without equality inference; tmux creation-returned pane ids are the replay targets; and agent direct-start never copies a saved topic into the raw title.
|
|
215
221
|
- `make test` also covers Antigravity v1.1.12 current-storage resume discovery from sanitized synthetic `last_conversations.json` and workspace-bearing `conversation_metadata.json` fixtures: exact/depth workspace filtering, URI/path list variants, required metadata summaries, safe-title/short-UUID and blank-turn degradation, normalized UUID plus exact regular `<uuid>.db` existence/mtime validation, missing DB/sidecar/traversal/file-symlink rejection without DB reads, malformed/missing/unknown/workspace-less clean degradation, the separate live hook/session-state lane plus disk-picker last-conversation > metadata > legacy source dedupe without cross-provider behavior changes, high live/medium cache/low history preview and doctor confidence, and picker selection wiring to `agy --conversation <uuid>`.
|
|
216
222
|
- `make test-e2e` also runs a disposable exact tmux server save → destroy → production restore flow for shell/startup/agent panes, changes `pane-base-index` before restore to reject index-derived targets, verifies raw tmux metadata plus visible label resolution, and replays an old snapshot with cleared label/manual ownership.
|
|
@@ -276,16 +282,17 @@
|
|
|
276
282
|
- Shell focused unit coverage includes canonical typed Project/ControlSession bootstrap followed by the non-creating foreground route `tmux -L projmux -f <generated config> attach-session -t =<session> [-c <cwd>]`; disappearance after preflight fails closed instead of letting `new-session -A` provision outside the plan. Generated tmux config callbacks, native key-broker platform policy, runtime overrides, and nested-socket guards remain covered.
|
|
277
283
|
- `make test` also covers npm retire/staging path canonicalization for the resolved projmux executable so generated tmux config and hooks never embed a `node_modules/.projmux-<hash>` directory, plus error-suppressed attention and rebalance-panes hooks.
|
|
278
284
|
- `make test` also covers the public config spelling: `projmux config edit [--get|--set <mode>]`, `config render standalone|app`, and `config apply` forward raw argv to their manual leaf handlers; flags, unknown tokens, streams, handler errors, and side effects stay on those handlers. `make install` and every update path emit `projmux config apply`, while generated plumbing uses only the hidden `internal` namespace.
|
|
279
|
-
- `make test` also covers Legacy CLI retirement Phase 2/3: rejected compatibility argv below the surviving mixed roots returns exact replacement guidance with stdout/handler/pre-dispatch mutation zero; `attach project`, `focus project|window|pane`, `pin project`, and `prune project|snapshot` remain exact raw-argv forwards. The fully removed human roots (`current`, `kill`, `notify`, `sessions`, `session-state`, `tag`, `upgrade`, `usage`), removed pre-namespace internal tokens, and the final `ai` root have no catalog or top-level handler and use root unknown-command exit 1 for every argv. The exact `tmux apply` updater handoff still normalizes to `config apply` before routing, while every other `tmux` argv remains unknown. Canonical `internal agent-hook ingest` forwards all provider event shapes to the unchanged handler, while stale unmanaged legacy-hook fixtures remain byte-preserved and require the documented manual canonical-prefix remediation. The tracked-repository literal audit permits the retired producer spelling only in test fixtures and explicit retirement/upgrade history, ignores untracked local/build artifacts without opening them, and a normal `go build ./cmd/projmux` regression requires the production binary itself to contain zero contiguous retired producer bytes. Maintained-doc scans, catalog/canonical/handler two-way audits, root/command-tree/reference goldens, the exact removed route set, preserved Shortcut/kind aliases, and generated producer scans close the topology and drift contracts. The release-boundary ledger additionally pins the post-retirement `-p`/`-w`/`-A` resource scopes, Registry topology materialization, Registry-only Offline Window deletion, and their generated CLI reference evidence so compatibility cleanup cannot silently roll back later canonical behavior.
|
|
285
|
+
- `make test` also covers Legacy CLI retirement Phase 2/3: rejected compatibility argv below the surviving mixed roots returns exact replacement guidance with stdout/handler/pre-dispatch mutation zero; `attach project`, `focus project|window|pane`, `pin project`, and `prune agent|project|snapshot` remain exact raw-argv forwards. The fully removed human roots (`current`, `kill`, `notify`, `sessions`, `session-state`, `tag`, `upgrade`, `usage`), removed pre-namespace internal tokens, and the final `ai` root have no catalog or top-level handler and use root unknown-command exit 1 for every argv. The exact `tmux apply` updater handoff still normalizes to `config apply` before routing, while every other `tmux` argv remains unknown. Canonical `internal agent-hook ingest` forwards all provider event shapes to the unchanged handler, while stale unmanaged legacy-hook fixtures remain byte-preserved and require the documented manual canonical-prefix remediation. The tracked-repository literal audit permits the retired producer spelling only in test fixtures and explicit retirement/upgrade history, ignores untracked local/build artifacts without opening them, and a normal `go build ./cmd/projmux` regression requires the production binary itself to contain zero contiguous retired producer bytes. Maintained-doc scans, catalog/canonical/handler two-way audits, root/command-tree/reference goldens, the exact removed route set, preserved Shortcut/kind aliases, and generated producer scans close the topology and drift contracts. The release-boundary ledger additionally pins the post-retirement `-p`/`-w`/`-A` resource scopes, Registry topology materialization, Registry-only Offline Window deletion, and their generated CLI reference evidence so compatibility cleanup cannot silently roll back later canonical behavior.
|
|
286
|
+
- `make test`: `projmux prune agent --older-than <duration> [--no-session-ref] [--no-pane] [--exclude <agent-ref>]... [--yes]` is the explicit, bounded stale-Agent prune beside `prune project`. `TestPruneAgentUsageErrorsChangeNothing` requires `--older-than` (non-negative, parseable) plus at least one evidence flag, and refuses an unresolvable, empty, or ambiguous `--exclude` through the shared Agent selector with zero stdout, zero transactions, and zero tmux observation. `TestPruneAgentNeverSelectsRunningLiveExcludedOrYoungAgents` iterates every flag combination, listing and `--yes` alike, so Running Agents, Agents with a Pane observed live, `--exclude` targets, and Agents younger than `status.lastTransitionAt + --older-than` are never candidates, with controls proving each rule alone excluded its Agent. `TestPruneAgentDryRunLeavesRegistryBytesUntouched` runs against a real temp-dir `registry.json` and requires an identical sha256 and state-directory tree (no transaction, temp file, or lock file) plus the name/uid/provider/phase/reason/lastTransitionAt/sessionRef/panes/continue columns and the dry-run line; `TestPruneAgentCandidateListingIsBounded` caps rows at `selector.MaxCandidates`. `TestPruneAgentContinueColumnComesFromTopologyEligibility` pins the Continue column to `decideTopologyAgentContinueEligibility`. `TestPruneAgentYesRefusesWhenLockedReselectionDiffers` changes the stored Registry, the clock, or the live observation between the listing and the lock and requires `nothing was deleted` with zero writes; `TestPruneAgentYesMatchesMutatorDeleteAgentOnAClone` requires the committed Registry to equal `Mutator.DeleteAgent` replayed on a clone, Window anchor repair included, with `termination-receipts.jsonl` byte-identical. `TestPruneAgentLivenessRequiresAnObservedAppOwnedHost` admits "no live Pane" only from an actual read of a `HostModeAppOwned` server with an available Pane scope: driving the real runtime reader and `InventoryObserver` over a scripted tmux runner (plus a Registry-only fallback snapshot and a missing observer), no transport outside tmux, a standalone server without the `@projmux_app` marker or with a non-app marker value, no server running on the socket, and a Pane list failure all list Pane-holding matches as `unknown` with the reason and refuse `--yes` with an identical `registry.json` sha256 and empty stdout, while a uid mirrored on the observed server still excludes its Agent and the app-owned control with an empty Pane scope reads offline and deletes; the host check lives only in `prune_agent.go`, and the shared inventory and `resourcegraph` scope semantics are unchanged. `TestPruneAgentWithoutRemainingPanesNeedsNoTmux` keeps Pane-free matches from observing tmux at all.
|
|
280
287
|
- `make test` also covers current-directory keybinding shell safety: the generated `run-shell` command passes `#{q:pane_current_path}` to `switch open`, and adversarial cwd fixtures containing spaces, single and double quotes, command substitutions, backticks, and shell metacharacters remain one literal argv without executing injected commands.
|
|
281
288
|
- `make test` / `make test-integration` also cover post-retirement generated UI routing: Agent/Pane default-target bindings use hidden `internal agent-pane launch-default`, launch/resume popups re-enter through `internal agent-pane picker`, and direct Codex/Claude/shell bindings use the hidden `launch-provider` / `launch-shell` canonical-intent bridges; the notification sidebar uses `get notifications`, and the existing-session popup uses `runtime sessions`. Regression assertions reject retired `ai`, `notify`, and `sessions` roots in these generated commands, raw-argv forwarding tests preserve the canonical intent and picker handlers behind the hidden bridge, and the built-binary smoke drives a saved shell default through that bridge with `%7` as its exact origin: the missing Pane mirror returns the origin-loss usage refusal with exit 2 and zero stdout, logs only the read-only Pane probe, and performs zero raw tmux mutations.
|
|
282
289
|
- `make test` also covers the canonical manifest honesty audit: every canonical spelling resolves to a full executable path in the command tree, no canonical summary carries forward-looking vocabulary or any adjudicated-and-retired string, the remaining target-state summary (`delete pane`) is pinned verbatim with a named owning track and must stay absent from every command-tree node, and the separate shortcut gaps are enumerated as a two-way diff so a future change cannot drop a mapping unadjudicated; config and workflow routes are also asserted to have no cardinality-matrix row because they resolve no resource set.
|
|
283
290
|
- `make test` also covers singular and plural kind spellings on the four resource verbs: `Route.Aliases` in `internal/cli/catalog.go` is the single declaration, `cli.CanonicalChildToken` normalizes an alias to its canonical token before `get`/`describe`/`delete`/`rename` dispatch, and the unknown-kind refusals are rendered from `cli.ChildSpellings` so an accepted spelling can never be missing from the list they print. `TestKindSpellingAliasesAreByteIdenticalToTheCanonicalSpelling` runs all 17 canonical/alias pairs -- including the two forwarded parity kinds per verb, whose forwarded argv is compared as well -- and requires identical stdout, identical stderr, and an identical error, with the canonical run required to succeed so an all-failing pair cannot satisfy byte-equality vacuously; `TestEveryManifestKindHasAParityCase` derives the matrix from the manifest so a kind added later cannot escape coverage, and `TestKindSpellingAliasesNeverReachAnotherRoute` proves each singular `get` alias answers its own inventory and is distinguishable from every sibling kind. On the manifest side `TestCanonicalKindSpellingsSurviveTheAliasContract` pins the canonical kind token of every resource-verb child so an alias can never replace a shipped spelling, `TestNoChildAliasShadowsACanonicalSpelling` walks the whole tree for alias/name collisions and duplicate aliases, and `TestResourceVerbsAcceptBothFormsOfEveryKind` asserts zero missing singular/plural combinations with `get pane`/`get panes` as the one enumerated divergent pair. That exception is the product decision the track recorded and is held by `TestGetPaneKeepsItsOwnRouteUnderBothSpellings` and `TestGetPaneSpellingIsUnchanged`: `get pane` stays the exact-one Pane read that owns `--current -o cwd`, `get panes` stays the 0..N inventory, and neither aliases onto the other. `rename` no longer special-cases the `agent` token by hand -- the manifest has no Agent child, so `agent` and `agents` both reach the ordinary refusal.
|
|
284
|
-
- `make test-integration` / `make test-e2e` / `make test-install-smoke`: suite containers still run with `--network none`, so `scripts/test-docker-run.sh` prefetches the checked-in module graph once per `go.sum` into a host-side Go module cache outside the repository and mounts it into the isolated run; `test/lib/smoke.sh` honors that inherited `GOMODCACHE` instead of forcing an empty run-local one.
|
|
291
|
+
- `make test-integration` / `make test-e2e` / `make test-install-smoke`: suite containers still run with `--network none`, so `scripts/test-docker-run.sh` prefetches the checked-in module graph once per `go.sum` into a host-side Go module cache outside the repository and mounts it into the isolated run; `test/lib/smoke.sh` honors that inherited `GOMODCACHE` instead of forcing an empty run-local one. The runner also mounts its host-side Go build cache (`PROJMUX_TEST_GOCACHE`, default `~/.cache/projmux/test-gocache`) read-write at `/gocache` and points `GOCACHE` there, and `test/lib/smoke.sh` honors the inherited `GOCACHE`, so later suites in one invocation reuse earlier compile actions (and local worktrees share that host cache); the build cache is the only added read-write mount, and `--network none`, the read-only workspace, and HOME/XDG isolation are unchanged. `SuiteContainerBuildCacheContractTest` (`test_suite_run_mounts_the_runner_build_cache_at_gocache`, `test_suite_run_defaults_the_build_cache_to_the_prefetch_location`, `test_suite_run_keeps_network_workspace_and_home_isolation`, `test_smoke_env_honors_an_inherited_build_cache`, `test_no_suite_script_overwrites_an_inherited_build_cache`) pins this in `make ci-contract`, which `make test` runs.
|
|
285
292
|
- `make test` / `make test-e2e`: resource-first create and materialization removes the `--project` dispatch discriminator, so `create window|pane|agent|<provider>` share one parser and one product model. A two-way argv table parses every flag group -- `--name`, `--label`, `-o`, the `--` payload, `--window`/`-w`, `--pane`, `--selector`, `--create-window`, `--placement`, `--cwd`, `--add-dir` -- with and without an explicit `--project` on all six spellings and requires the two parses to be identical except for the scope occurrence, which is what the user reproduction `create codex -w hi --create-window` failed on; a structural test additionally requires `create` to carry no raw-argv split seam at all, so the removed model cannot return by one edit. Scope resolution has two branches: an explicit `--project` wins inside and outside tmux and never observes the active target, while an omitted one derives the Project from the mirrored `@projmux_window_uid` and its registry owner chain, and derives the Window **and** anchor Pane only when no `--project`, `--window`, `--pane`, or `--selector` was given at all -- which is what keeps the generated `create codex|claude|pane --placement right|down` bindings a split of the Window the operator is in rather than a fan-out over the Project. Refusals are measured, not asserted: outside tmux, a Home/control pane with no mirrored Window uid, an unattributed pane, a foreign uid the Registry does not hold, and a Window whose owning Project is gone each exit 2 naming `--project` with zero stdout, zero registry transactions, a byte-identical registry snapshot, and zero tmux calls, on all four create spellings; an argv-only refusal table additionally requires every invalid-input row to be decided with zero active-target observations, so a typo is never reported as a missing scope. Behavior tests run against the in-memory tmux server: the user reproduction creates Window/Agent/managed Pane in one transaction, materializes them in the derived Project's own session, mirrors the allocated uids onto the exact tmux objects, and prints the managed Pane's live handle for `-o pane-id`; an explicit `--project beta` from inside an alpha pane lands in beta with the active-target seam untouched; an explicit `--window` suppresses the implicit anchor; and a failing `split-window` under an implicit scope rolls back to a byte-identical registry with no leftover tmux window. The generated keybinding producer now renders every `run-shell` projmux binding as `TMUX_PANE=#{pane_id} <bin> ...`, because tmux exports `$TMUX` to a `run-shell` child but never `$TMUX_PANE`; a catalog-wide test pins that rendering for every run-projmux action and runs each `create ...` binding body through the route to prove the argv the generated config ships is the argv the implicit-scope route accepts. No create path issues `switch-client`, `select-window`, `select-pane`, or `attach-session`.
|
|
286
293
|
- `make test-integration`: Docker-backed Linux integration smoke with real `tmux`, `git`, and `stty`; covers Doctor schema 2 missing/corrupt/permission degradation, runtime socket/config current/stale findings against a guarded run-unique `-L` socket, source byte/mode/mtime/inventory no-repair evidence, explicit support-report no-request/preview/private archive and Doctor-schema inspection, missing/corrupt/permission-denied source manifest reasons with no source-mode repair, preview-writer failure before output creation, collision byte/mode preservation and temp cleanup, tmux config render/install/apply including stale live `C-t` pane-label retirement, current `M-p` preservation, current `C-t` reassignment, repeated apply/keymap byte preservation, and unrelated live/current binding preservation, default-off/on live resource config plus two-sample procfs status rendering, notify queue CRUD against isolated HOME/XDG paths, private operational-log persistence/mode repair/readback/hot-path exclusion/best-effort write failure, and the built-CLI saved-default split bridge reaching canonical intent scope: with `%7` carrying no Pane mirror it exits 2 with the exact origin-loss reason, writes nothing to stdout, issues only the read-only exact-Pane probe, and performs zero raw tmux mutations, where the retired bridge would have logged one `split-window`. The generated config is also audited for the CLI internal-namespace relocation: it must contain the `internal status`/`internal statusbar`/`internal tmux` spellings and no `'<bin>' <route>` invocation of any of the 7 removed pre-namespace plumbing tokens; invoking any removed token directly is separately pinned to the root unknown-command contract. Root containers run the permission case through `runuser`+`nobody`; environments without that adapter take an explicit asserted skip instead of treating root chmod access as denial.
|
|
287
294
|
- `make test-install-smoke`: Docker-backed source install smoke; covers `make install`, atomic binary replacement, `config apply` against a live `projmux` socket, post-install notify queue initialization, the installed `~/.config/projmux/tmux.conf` carrying the relocated `internal status`/`internal statusbar` spellings and no pre-namespace `status`/`statusbar`/`tmux` invocation of the installed binary, and installed-binary temporary HOME/XDG support-report preview → explicit private archive → manifest/Doctor schema inspection with exact smoke-root cleanup.
|
|
288
|
-
- `make test-e2e`: Docker-backed real-tmux workflow smoke for session/pane setup, a canonical `create pane -o pane-id` whose returned `%N` handle is verified against the isolated live server, an attached-client `rename-pane-label` command-prompt flow covering current-label initial value,
|
|
295
|
+
- `make test-e2e`: Docker-backed real-tmux workflow smoke for session/pane setup, a canonical `create pane -o pane-id` whose returned `%N` handle is verified against the isolated live server, an attached-client `rename-pane-label` command-prompt flow through `internal tmux pane-rename` on a recorder Pane with no Registry identity, covering the captured current-label initial value refused with its usable name, a quote-breaking `x'y` response refused with `x-y`, a valid name refused for the unmanaged Pane, native Esc cancel, an empty response that changes nothing, and label plus AI topic/manual/raw-title non-mutation, app config sourcing, reply-state notify reconciliation, focus notify fallback, canonical `internal status notify` rendering with contextual project/state/agent badges, and an isolated attached-client `display-popup` Settings recorder flow against a temporary HOME/keymap: one Add binding recorder saves a single C-r after one confirm, Esc cancels another draft with zero mutation, the same still-open reader records visible `C-o,o`, pops/re-adds its last stroke with Backspace, writes canonical `C-o o`, applies the live root-table trie, renders comma-form detail, and removes the sequence without stale tables; stale retired picker env/file values remain inert, unchanged, and silent. The reused FIFO/attached-client Notify Clear All flow has command markers and queue JSON proving cancel no-write, INFO/WARN/CRIT confirm clear, and pane focus restoration. Host-only terminal, WSL, macOS, and GUI notification behavior remains outside Docker; see [docs/testing.md](testing.md).
|
|
289
296
|
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 7 splits Project discovery from pin authority. Unit coverage pins the typed pin envelope (`projmux-pins v2`, stored kind, corrupt/newer-version refusal, order-preserving dedup), the legacy resolution table (exactly one Registry root becomes that Project uid, zero matches stay a candidate, two or more refuse the whole migration and name the repair), the `candidates.MatchKeyFor` Linux/Windows compatibility table asserted from either host, a managed pin surviving rebind/rename/MissingRoot/no-root as the same sidebar tier, resolve-or-candidate typing of every `<dir>` argument with `uid:<uid>` as the explicit escape, write-free repeats and reads, the `create project --root` exact-path and idempotence contract, the sidebar bootstrap running after the trust gate and only for the path being opened, and a two-platform Settings golden showing Additional discovery roots, Pinned Projects, and Candidate Pins as three collections. Integration adds the real-file legs: a generic mutation route reconciling against a live server registers zero Projects for the adjacent scan-root children, an unregistered `--project <name>` refuses with the exact `--root` to pass, `pin project migrate --dry-run` writes nothing, an ambiguous legacy pin leaves both the pin file and the Registry byte-identical until a rebind repairs it, and four concurrent migrations converge on one valid typed envelope. E2E drives the user-visible flow on an exact socket with a real attached client: `switch open` on one candidate registers that one path, its siblings stay unregistered, reopening is Registry-write-free, and a rebind keeps the managed pin on the same uid with the candidate pin untouched.
|
|
290
297
|
- `make test`: the Claude provider workspace/payload argv boundary is verified against the provider's own option grammar rather than substring containment, because a payload absorbed by a variadic option is still *contained* in the argv -- which is how the shipped `claude --add-dir <root> <payload>` regression passed Phase 6 review and then started sessions with no task. `internal/app/agent_launch_argv_test.go` replays each provider's documented arity over the produced argv (one-value option consumes exactly one operand; variadic option consumes operands until an option-looking token or the terminator; the terminator makes the rest operands) across Claude and Codex x zero/one/multiple additional roots x empty/one-token/many-token payloads, and requires the recovered roots, working root and prompt to equal what the launch was given, with zero undeclared options. A negative control replays the pre-fix concatenation and requires the model to *lose* the prompt, so the grammar replay cannot pass vacuously. Exact-argv tables pin `--add-dir <a> <b> -- <payload>` for Claude at every root count, `-C <cwd> --add-dir <a> --add-dir <b> <payload>` for Codex, a bare argv for an empty payload on both, and the refusal for a stored additional root on a provider projmux gives none to. `create agent` and `agent resume` are then run through the real seams and compared on the exact exec tail, which also pins that resume adds no terminator because the provider's `--resume` option is what stops the variadic root option; the former substring assertion in `agent_workspace_test.go` is now an exact-argv assertion. The one Codex payload shape that stays unsupported -- a prompt beginning with `-`, unchanged from the shipped build -- is pinned as a deliberate limit rather than left to be rediscovered. Measured against installed Claude Code 2.1.234 and Codex: `claude --add-dir DIR hello` reports "Input must be provided either through stdin or as a prompt argument" (the prompt became a directory) while `claude --add-dir A B -- hello` reaches execution with the prompt intact, and `claude --add-dir A B --resume <id>` parses both roots and the conversation id. The real-provider installed activation smoke -- installed `create agent --provider claude` with `--add-dir` plus a short payload on an isolated exact socket producing stdout `%N`, provider-hook activation acknowledgement and a transcript, with sibling and default sockets unread and exact cleanup -- runs post-merge and is recorded in the track's Archive evidence rather than in CI, because CI has no authenticated provider CLI.
|
|
291
298
|
|
|
@@ -311,7 +318,7 @@
|
|
|
311
318
|
|
|
312
319
|
- `make test`: Declarative contract stabilization Phase 2 adds the Registry materialization invariant audit to `projmux doctor` as the `registry` section, present in the default report and in `--section registry`, in both text and `--json`. The audit is the consumer predicate rather than a second opinion about it: every Registry Project is planned through the shipped `planRegistryTopology` with no observed sessions, and `refusalScope` supplies the fatal/skipped split, so `TestDoctorRegistryInvariantAuditReusesTheMaterializePredicate` fails if the reported counts ever diverge from the planner's own refusals over the same Registry. After schema v2, `TestDoctorRegistryInvariantAuditReportsTheFieldFailureStructure` proves deleting a Window's last shell installs a canonical replacement and therefore emits no skipped-Window finding; the remaining gone-root Project is still reported as `registry.materialize.fatal.project`. `TestDoctorRegistryInvariantAuditStatesTheZeroCase` requires a clean Registry to report `registry.materialize.clean` and the number of Projects planned without `--verbose`, because a silent clean audit is indistinguishable from a section that never ran, and `TestDoctorRegistryInvariantAuditGoldens` pins the zero and N section output in text and JSON. `TestDoctorRegistryInvariantAuditWritesNothing` runs the production zero-write snapshot read under an isolated `HOME`/`XDG_STATE_HOME`/`XDG_CONFIG_HOME` on a machine that never created a Project and requires an unchanged filesystem tree and no state directory; `TestDoctorRegistryInvariantAuditNeverReachesATmuxServer` pins that the runner handed to the planner refuses every call, and `TestDoctorRegistryInvariantAuditDegradesWhenTheRegistryCannotBeRead` keeps an unreadable Registry a `registry.materialize.unavailable` finding rather than a failed diagnostic. `TestSupportReportCarriesRegistryInvariantCountsWithoutReasonsOrPaths` requires the archive's `doctor.json` to carry the same codes, kinds, and counts and neither the planner's reason wording nor the fixture's absolute paths, and `TestDoctorRegistryInvariantCodesStayInsideThePublishedInventory` keeps every emitted code and remediation inside `doctorFindingCodeInventory`/`doctorFindingRemediationInventory` so nothing reaches a support archive as an opaque hash. The audit performs no repair, schema migration, or display-layer diagnosis, and adds no second diagnostic command.
|
|
313
320
|
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 3 raises the Registry envelope to schema v2 and ships the atomic v1 → v2 invariant repair. `TestRegistryV2GenerationMigrationGoldens` labels the 0.10/0.11/0.12 source generations and pins raw SHA-256 bytes for every source/output pair while comparing generated output directly to the raw golden bytes and asserting repair and information-loss counts, identity/name/owner/name-reservation preservation, Agent conversation-pointer preservation, graph validity, and a no-op second pass. `TestV1MigrationCreatesCanonicalShellChainForAZeroWindowProject` covers additive opaque Window/Pane creation, and `TestValidV2CanonicalAnchorRoundTripsWithZeroDiff` fixes a valid anchor at zero diff. `TestEveryFirstMigratorLeavesDurableV012RepairAndLossEvidenceOnce` drives normal `Load`, `Update`, and explicit `Migrate` as the first writer and requires one private report adjacent to the exact versioned backup, with that backup's absolute path/SHA-256 and exact repair/loss detail; returned paths match the durable pair where the API returns evidence, and the next migration pass writes zero Registry, backup, or report bytes. Failure-injection rows require a report published before Registry staging to be removed on every failed ordering. `TestMigrateRejectsAnInvalidCurrentRegistryWithZeroWrite` prevents a current-version no-op from bypassing validation, and future/unversioned envelopes remain byte-identical refusals with no evidence report. `TestV012MigrationLeavesNoMaterializeRefusals` runs the shipped materialize predicate over the repaired 0.12 sample and requires zero fatal and zero skipped items plus the exact Project → Window → direct shell Pane chain. Current-schema validation globally enforces that chain, and last-shell deletion installs a replacement bare shell. Phase 15 consumes this canonical anchor as the only identity retained by `Open fresh`; schema-v2 validation remains the pre-commit refusal boundary for an invalid anchor.
|
|
314
|
-
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 5 moves the generated `MouseDown3Pane` Horizontal/Vertical Split and Kill entries onto managed resource intents. The generated-config byte golden fixes the exact `internal tmux pane-menu --client #{client_tty} <action> #{pane_id}` actions, and a negative audit requires zero `split-window`, `
|
|
321
|
+
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 5 moves the generated `MouseDown3Pane` Horizontal/Vertical Split and Kill entries onto managed resource intents. The generated-config byte golden fixes the exact `internal tmux pane-menu --client #{client_tty} <action> #{pane_id}` actions, and a negative audit requires zero `split-window`, `respawn-pane`, and `Respawn` literals in that menu (since Orphaned Registry Window Phase 3 its one `kill-pane` is tmux's stock item as the mirror-absent branch of Kill). Unit tables prove both split directions emit one `agentPaneIntent` with the exact clicked `%N` in `anchorPaneID`, so `createFromIntent` and the popup-origin `anchoredActiveTargetLookup` remain the only scope and mutation definitions; Kill resolves through the same anchor and calls canonical `delete pane`, preserves its full output, and displays its summary on the originating client. Injected create and delete refusals are displayed on that client and trigger no fallback mutation. Respawn is removed from the generated menu and its internal action is rejected; no managed respawn/replace intent is introduced. The isolated real-tmux leg strips inherited `TMUX`/`TMUX_PANE`, uses a run-unique `TMUX_TMPDIR` and socket, audits the installed `MouseDown3Pane` binding and selects Split/Kill actions through a real `display-menu` on an attached client, verifies split uid/Registry visibility and canonical Kill removal/result, and validates then records the exact socket cleanup target under its smoke root.
|
|
315
322
|
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 6 makes termination evidence survive the exit boundary without taking the Registry lock. The supervisor writes one bounded JSON row, framed by leading and trailing delimiters in a single `O_APPEND` call to `termination-receipts.jsonl`; the leading delimiter terminates any prior process's partial tail. Controller convergence reads the append snapshot outside the Registry transaction, absorbs generation-guarded receipts before absence projection, tolerates malformed/partial rows without disabling later convergence, and relies on duplicate/stale receipts as no-ops rather than truncating or garbage-collecting the journal. `TestSupervisorReceiptPrewriteDoesNotWaitForRegistryLock` proves the prewrite never enters a deliberately blocked Registry update, `TestMalformedTerminationJournalRowCannotDisableConvergence` pins recovery from a truly unterminated tail, and `TestInvalidTerminationJournalRowCannotDisableLaterValidReceipt` proves a well-formed but contract-invalid row cannot poison later valid evidence. The generated hooks use three distinct reasons: `pane-exited` carries exact `#{hook_pane}` and reobserves only the matching stored activation handle; `pane-killed` carries no invented pane and retains whole-host reobservation; `window-unlinked` carries exact `#{hook_window}` as explicit event evidence while retaining whole-host fresh reobservation. Phase 6 had no persisted runtime/liveness mapping; Phase 3 now persists only the exact last-positive Window owner handles required for causal teardown. `TestLifecycleHookRoutesCarryExactPaneAndWindowEvidence` and `TestWindowUnlinkedRecordsKillEvidenceWithoutPersistedRuntimeState` pin the Phase 6 boundaries and kill-window evidence recording. `TestPaneKilledCoalescedBehindExactPaneExitWidensEveryLaterPass` pins that a narrow pane-exited lease holder widens after any coalesced event and stays whole-host through its final no-op verification, so a kill or different-pane event cannot be acknowledged under the first Pane's selector. `TestPaneExitedAbsorbsLateSupervisorReceiptAfterUnknownProjection` covers the fast-exit ordering where runtime-created first records unknown: exact pane-exited still treats later journal absorption as work, refines the released same-generation Agent's evidence to normal or killed, and repeats with zero transactions; `TestLateSupervisorRefinementNeverFollowsAResumedBinding` keeps a new non-empty Pane binding strictly outside that exception. The closed classification vocabulary adds `killed`: only a supervisor's SIGHUP wait status may claim it, it means external kill rather than intent, and a same-generation `control-action/intentional` receipt remains sticky when the resulting HUP arrives. `TestHookWaitStatusReceiptDecisionTableIsClosed` covers pane exit, pane kill, standalone window kill, last-pane clean exit, paired control receipt, and evidence-free absence; `TestTerminationPairingRejectsUnentitledClaims` proves supervisor cannot claim intent, control action cannot claim observed outcomes, and reconcile can claim only unknown. `FuzzTerminationEventSequencePreservesCanonicalDeleteAndHookRows` fixes the authority boundary over arbitrary event order: canonical delete removes the Pane row and every repeat is byte-identical, while hook-driven observation changes evidence/status, clears the Agent binding, preserves Pane/Agent rows, and a repeated reconciliation opens zero transactions.
|
|
316
323
|
|
|
317
324
|
- `make test`: Phase 6 kill/reap race enforcement keeps external-kill evidence from settling as unknown merely because tmux's 50ms hook beat the supervisor append. `TestPaneKilledWaitsPastHookDelayForSupervisorReceipt` delays the SIGHUP receipt past 50ms and requires killed evidence; `TestPaneKilledWaitRefinesRacingUnknownWithDelayedSupervisorReceipt` starts from a racing reconcile/unknown and requires the same killed refinement. `TestPaneKilledReceiptWaitIsBoundedBeforeUnknownProjection` proves a genuinely receipt-free kill waits only for the fixed bound and then remains honest unknown. Phase 2 extends the same lock-free bounded wait to the exact `pane-exited` activation only: `TestExactPaneExitedReceiptWaitIsBoundedBeforeUnknownProjection` proves a receipt-free timeout still retains the resource with honest unknown evidence, while integration/e2e prove a racing same-generation normal supervisor row enables deletion. The wait reads only the exact-host inventory, Registry snapshot, and lock-free journal before convergence; it takes no Registry write lock, invents no intent, keeps exact pane exit narrowed to its `%N`, and leaves kill/window paths whole-host. At the process boundary, `TestRunSupervisedChildRetainsRelayedHUPEvidenceWhenChildExits129` proves a provider that catches a successfully relayed HUP and exits 129 still records external-kill evidence, while `TestRunSupervisedChildDoesNotInferHUPFromExit129` proves the same numeric exit without an observed relay remains abnormal.
|
|
@@ -362,6 +369,7 @@
|
|
|
362
369
|
remain the parity gates; heartbeat alone supplies no connection health proof.
|
|
363
370
|
|
|
364
371
|
- `make test`: Codex native control plane Phase 9 makes the compact Usage identity native-first: label goldens map healthy authoritative app-server rows to exact `Codex`, fresh rollout rows and unknown non-stale provenance to `Codex [fallback]`, and retained last-known-good rows to `Codex [stale]`; en-US/ko-KR narrow statusbar fixtures pin spacing and width. Same-snapshot HUD/table/JSON tests and operations-journal tests preserve exact percent/reset values plus typed source/fallback/stale reasons, while Claude/Antigravity labels, projection order, and Usage semantics remain unchanged.
|
|
372
|
+
- `make test`: the Codex fallback usage label is a color instead of a bracket tag, which supersedes the `Codex [fallback]` spelling in the entry above. `TestCodexCompactIdentityLabelGolden` pins the bare `Codex` identity for the rollout, blank, and unknown non-stale lanes together with the HUD paint role per lane, byte-identical `Codex` / `Codex [stale]` output, and unchanged table/JSON provenance; `TestCodexFallbackHUDUsesProvenanceRoleNotThresholdColors` proves the label uses `usage.provenance_fg` and that the role is never warning, critical, or the AI label color while Claude/Antigravity keep theirs; `TestCodexIdentityNarrowStatusbarLocaleFixture` pins the colorless text tiers (`Codex^ 5h:17%`, `X^ 5h:17%`, native and stale unchanged) with zero tmux escapes in en-US and ko-KR; and the journal test keeps the closed source/reason rows while asserting the new fallback-provenance signal. On the theme side `TestPresetTokensSatisfyDesignRubric` gains the `provenance` rule for all eight presets (nearest colourN distinct from the five state colors and `colour121`, hue 15-45°, resolved role luma readable on that preset's status background), `TestProvenanceRoleLumaGateMatchesAccentAI` pins the fallback literal `colour208`, the darkened `#9d5300` under an explicit light `status_background`, and verbatim preset/explicit values, and `TestProvenanceTokenIsAPublicResolverToken`, `TestProjectThemeConfigRoundTripsProvenance`, and `TestThemeStateGroupCarriesProvenanceLast` cover the resolver wiring, `[theme] provenance` config round-trip with the shared invalid-value warning, and the Settings `[state]` row.
|
|
365
373
|
|
|
366
374
|
- `make test` / `make test-e2e`: Alt-1 Runtime diagnostics visibility Phase 0 makes the Projects sidebar's Runtime link contextual without touching the diagnostics contract behind it. `internal/config` owns the saved `runtime-diagnostics-visibility` choice (`when-needed` default, `always`), and its unit coverage pins the read-time default with no file written, the round trip, an unrecognized saved value that resolves to the default without rewriting the operator's bytes, the closed normalization table, and a rejected save. `internal/app` owns the pure reducer and the settings adapter: the visibility table covers preference x class x observability with no gap -- empty, control-only, ephemeral-only and control+ephemeral hosts withhold the row; each of `Unattributed`, `Foreign`, `Recoverable`, `Conflict` and a mixed tally restore it; no transport and any unavailable scope restore it; and `Always` keeps it in every one of those states. A second table proves a visible row keeps its exact shipped label, tally and sentinel. The rendered-surface tests drive the production observation adapter over a fake host that mirrors precisely what the Registry desires -- the fixture asserts a zero tally and a complete observation before asserting the withheld row -- then over a uid-conflict host and a no-transport host that restore it, with `Always` placing it last before Settings on the healthy host. A taxonomy guard records that an un-mirrored control or scratch session restores the row through its `Unattributed` descendants rather than through its own class, so a class-taxonomy change surfaces there instead of silently changing Alt-1. The settings tests pin the three source annotations including the invalid fail-safe, the save/reload round trip applying on the next invocation, the rejected action value, the ko-KR/en-US parity of the row, both choices, the state row and its source, and the control living inside the exact `Project Sidebar` view with no Advanced or Troubleshooting container. The negative parity audit compares `get runtime sessions|windows|panes` output, the exact server and the Registry across both modes, and compares the whole Registry navigation view -- its single complete Runtime row, its complete class tally, every row's actions and reason -- plus the Sessions surface's own Runtime link, across both modes. The e2e smoke adds a real 80x24 sidebar over a host whose every observed session, window and pane is managed: the default withholds the row (proven by the list's own filter answering `no matches`), `Always` restores it with its empty-state label and its nested Runtime entry and Esc return, and the direct `get runtime` output is byte-identical across both modes while the Registry, the sibling socket and the attached client are unchanged.
|
|
367
375
|
|
|
@@ -517,6 +525,8 @@
|
|
|
517
525
|
- `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.
|
|
518
526
|
|
|
519
527
|
- `make test` / `make test-integration`: Window teardown decision journal Phase 0 records each consumed lifecycle teardown decision at the app layer (never inside the pure core planner) as one closed `topology.teardown.decision` event. `TestTeardownReasonJournalMappingCoversEveryCoreReason` and `TestTeardownVocabularyMatchesCoreDecisionTable` read `internal/core/metadata` source for every `TeardownReason`, `TeardownAction`, and `TerminationClassification` constant and fail on any unmapped or unallowlisted value; the first also round-trips every code through the real store and every conflict reason through its unchanged error bytes. `TestTeardownDecisionEventClosedShapes` and `TestTeardownRecorderAppendsWithoutOutcomeOwnership` pin the closed shape, strict `win-`/`pane-` UIDs, rejection of the new fields on every other family, and best-effort appends that never own the top-level outcome. `TestTeardownJournalCleanLastPanePairRecordsOneDeleteWindow`, `TestTeardownJournalKilledLastPaneRecordsRetainAndKeepsRegistry`, `TestTeardownJournalCleanupRetryRecordsRetainOnce`, and `TestTeardownJournalAuthorityConflictRecordsOneRefusal` run each fixture beside an unjournaled twin and require identical results, errors, and Registry bytes with exactly one decision per evaluation; `TestTeardownJournalWindowUnlinkRecordsFirstWaitAndExhaustion` requires one `retain`/`awaiting-pane-exit` on the hook's first wait, zero during carried causal retries, and one at exhaustion; `TestTeardownJournalKillWindowShapeFirstWaitCarriesWindowPaneAndKilled` drives the real controller body for a `kill-window`-shaped Registry and requires that first record to carry the exact Window UID, Pane UID, and `killed` with unchanged Registry bytes, while `TestTeardownJournalAmbiguousUnlinkSubjectOmitsUIDs` and `TestLifecycleWindowUnlinkAwaitingSubjectOmitsAmbiguousPane` omit any ambiguous UID. The integration termination smoke asserts one `delete-window`/`window-teardown` record for the exact closed Window UID, the first-wait plus exactly one exhaustion record for a replayed unpaired `window-unlinked`, and one `awaiting-pane-exit` record with the exact Window UID for a real `tmux kill-window` whose Window row is retained. Its read-only status/viewer and automatic-path journal line-count windows, which follow hook-driven session stop/unregister/prune closes, exclude only the asynchronously appended `topology.teardown.decision` lines, so any record the measured command itself appends still fails.
|
|
528
|
+
- `make test`: Orphaned Registry Window Phase 1 records the live `$N/@N` of every Window a Project start/Continue topology replay materializes, in the replay commit itself, through the same `ObserveWindowRuntimeBinding` writer a canonical Window create uses. `TestTopologyReplayCommitsLiveBindingForEveryMaterializedWindowOnly` requires the new-session-adopted first Window and the new-window ones to carry their live pair with MissingRuntime cleared at the single replay write, while a Window outside the replay keeps its runtime ids byte-for-byte; `TestTopologyReplayUnderLiveSessionLeavesPresetLiveWindowBindingsUntouched` re-creates a raw-killed first Window under the existing session and leaves every preset-live Window row, including a wrong sentinel binding, byte-identical. `TestReplayedWindowCleanLastPaneExitAndLiveUnlinkDeleteWindowAndJournalOnce` pairs the replayed sole Pane's clean `pane-exited` with the live `window-unlinked`, deletes the Window and Pane, keeps the Project, journals one `delete-window`/`window-teardown`, and shows the stale pre-fix row shape does not pair; `TestReplayedWindowKillWindowRetainsWindowAndJournalsAwaitingPaneExitKilled` keeps a raw-killed replayed Window and journals `retain`/`awaiting-pane-exit` with `killed` and the exact Window UID; `TestReplayedWindowNonLastAgentPaneCleanExitRemovesOnlyPaneAndKeepsAgentOffline` removes only the Agent Pane and keeps the Agent Offline with its conversation; `TestPastOfflineWindowWithUnpairedTeardownEvidenceSurvivesReplayAndOtherPairs` keeps a past offline Window with MissingRuntime and unpaired evidence byte-identical across the replay and another Window's causal pair. Pairing conditions, teardown decisions, and Windows the replay does not materialize are unchanged.
|
|
529
|
+
- `make test` / `make test-e2e`: Human-created Windows take focus Phase 0 moves the pressing client to a Window created through the hidden `internal tmux window-create` intent (`window.create` key, Window menu New At End) after the canonical create commits. `createWindowFromIntent` now also returns the committed `$N/@N`; the route checks that exactly the `--client` client is attached, then reuses the focus core's `switch-client -c <client> -t $N` and `select-window -t $N:@N` without `pickFocusClient`. `TestWindowCreateIntentMovesOnlyThePressingClientToTheCreatedWindow` (Project and ControlSession) requires exactly those two commands after the Registry commit, the pressing client on the new Window, a client on a different Session unchanged, and the unchanged `Created Window` line. `TestWindowCreateIntentWithAbsentPressingClientMovesNoClientAndKeepsTheCreate` requires zero `switch-client`/`select-window`, even with a bystander on the origin Session, and a kept Registry and runtime Window. `TestWindowCreateIntentMoveFailureKeepsTheCreateAndTellsThePressingClientWhy` injects `switch-client` and `select-window` failures and requires a kept Window, no other move, an unchanged different-Session client, and a `Created Window, but projmux could not move this client to it: <reason>` line. `TestWindowCreateIntentCreateFailureIssuesNoClientMoveAndKeepsItsMessage` requires zero moves and zero client inventory reads plus the unchanged `projmux Create Window failed: ...` line. `TestWindowIntentsReportOneBoundedLineToTheExactClient` pins the move argv before the create line. The e2e interactive leg and Window menu 6a read the pressing client's Window from `list-clients` and require the created Window, then refocus the origin Window so 6b/6c keep a non-current target. Public `create window|pane|agent` stays detached: every `assertNoClientMovement` call is unchanged.
|
|
520
530
|
|
|
521
531
|
## When To Update This List
|
|
522
532
|
|
|
@@ -622,19 +632,33 @@
|
|
|
622
632
|
surface, so assigning an observed-flake count would invent evidence. A future
|
|
623
633
|
unit quarantine first needs attempt-preserving per-test evidence and a separate
|
|
624
634
|
contract.
|
|
625
|
-
- `test/e2e/admission-contract.sh` (`make test
|
|
635
|
+
- `test/e2e/admission-contract.sh` (`make test` via `e2e-admission-contract`,
|
|
636
|
+
so CI `Unit Tests` executes it, plus `make test-e2e-contract`) pins the local
|
|
626
637
|
full-suite capacity boundary used by default `make test-e2e`: one invocation
|
|
627
638
|
owns the active state while another waits, and the wait diagnostic plus state
|
|
628
639
|
file expose `reason=local-full-suite-capacity`, capacity one, owner pid,
|
|
629
640
|
start time, cwd, and exact state path. Both callers retain their own terminal
|
|
630
|
-
status after admission.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
641
|
+
status after admission. The canonical local root is
|
|
642
|
+
`/tmp/projmux-e2e-admission-<euid>` (`id -u`), chosen without reading
|
|
643
|
+
`XDG_RUNTIME_DIR`, `XDG_STATE_HOME`, `HOME`, or `TMPDIR`;
|
|
644
|
+
`PROJMUX_E2E_ADMISSION_STATE_DIR` exists only as the isolation-test override,
|
|
645
|
+
and `PROJMUX_E2E_ADMISSION_RESOLVE_ONLY=1` prints the resolved root and exits
|
|
646
|
+
without acquiring it. The contract asserts that two callers with all four of
|
|
647
|
+
those variables different resolve the same pinned root without acquiring the
|
|
648
|
+
live root. `PROJMUX_E2E_ADMISSION_BYPASS=1` is the explicit stress path and is
|
|
649
|
+
required to overlap a live admitted owner. Single-scenario replay and CI
|
|
650
|
+
shard/suite selectors remain outside this local full-suite boundary;
|
|
651
|
+
`test/e2e/shard-contract.sh` keeps the four Linux plus two suite jobs on
|
|
652
|
+
independent runners with `fail-fast: false` and the stable required name
|
|
653
|
+
`E2E Tests`, while also proving both selector families enter with a local slot
|
|
654
|
+
already occupied. A dead owner is diagnosed as `stale-owner` and reclaimed
|
|
637
655
|
only while the active hard link still identifies its exact owner record.
|
|
656
|
+
Non-guarantees: bypass, the CI selectors, and `E2E_SCENARIO` replay stay
|
|
657
|
+
outside the boundary; a different EUID, a different host, or an execution
|
|
658
|
+
environment that does not share `/tmp` (containers, namespaces) gets a
|
|
659
|
+
different root; and dead owner records left in the former roots
|
|
660
|
+
(`/run/user/<uid>/...`, `$HOME/.local/state/...`) are neither reclaimed nor
|
|
661
|
+
migrated.
|
|
638
662
|
- `test/e2e/evidence-contract.sh` (`make test-e2e-contract`) and
|
|
639
663
|
`test/e2e/reliability-contract.sh` (`make test-e2e-reliability`) keep the persisted
|
|
640
664
|
`projmux.e2e-attempt/v1` evidence and success result hash stable while adding
|
|
@@ -1142,6 +1166,15 @@
|
|
|
1142
1166
|
exact SessionStart registration, process/Pane/generation authority, private
|
|
1143
1167
|
lease and coord-socket inode cleanup, helper-death invalidation, and
|
|
1144
1168
|
same-path replacement preservation.
|
|
1169
|
+
- `TestClaudeEndpointIdleRegistryGateEvaluatesOnlyOnChangeFailureOrFloor`,
|
|
1170
|
+
`TestClaudeEndpointRegistryStatIdentityTracksReplacementAndMissingFile`,
|
|
1171
|
+
`TestClaudeEndpointIdleTickReloadsChangedRegistryBeforeFloor`,
|
|
1172
|
+
`TestClaudeEndpointIdleRegistryFloorBoundsExitUnderStatCollision`, and
|
|
1173
|
+
`TestClaudeEndpointDeliveryFenceStaysFreshWhileIdleRegistryGateHolds` keep
|
|
1174
|
+
the idle tick's Registry gate (reload only on a stat identity change, a stat
|
|
1175
|
+
failure, or the 1.8s floor) separate from the delivery-time fence: lease
|
|
1176
|
+
readiness, coordination requests, and provider push prewrite still run the
|
|
1177
|
+
full current check.
|
|
1145
1178
|
- `TestClaudeEndpointProcessIntegration` uses a synthetic provider process and
|
|
1146
1179
|
the real supervisor/helper entrypoints. It accepts exactly the auth line and
|
|
1147
1180
|
one frozen user frame, completes exact Stop-marker qualification, pushes a
|
|
@@ -1227,6 +1260,19 @@
|
|
|
1227
1260
|
steer after a successful start, no new event or delivery change from a second
|
|
1228
1261
|
push over a terminal record, and a delivered reason that names only the
|
|
1229
1262
|
provider turn push, never model consumption, acknowledgement, or completion.
|
|
1263
|
+
- `TestAgentMessageSendExitCodeFollowsReceiptStateForEveryDeliveryState`,
|
|
1264
|
+
`TestAgentMessageSendAndExplicitReplyShareTheUndeliveredJudgment`,
|
|
1265
|
+
`TestAgentMessageSendReplayOfTerminalUndeliveredReceiptExitsNonzero`,
|
|
1266
|
+
`TestAgentMessageSendCodexTargetTerminalFailureExitsNonzeroUnderTheSameJudgment`,
|
|
1267
|
+
and `TestClassifyCodexTurnPushNeverReturnsUndeliveredWithoutCause` pin the
|
|
1268
|
+
send exit contract: `agent message send` exits 0 only when its receipt is
|
|
1269
|
+
`delivered` or not yet terminal (`accepted`, `held`, or an observed handoff),
|
|
1270
|
+
and a `failed`, `refused`, `expired`, or `stale` receipt exits nonzero without
|
|
1271
|
+
a usage dump after the receipt line is printed. One judgment decides the plain
|
|
1272
|
+
send, the Claude-source explicit reply, and the Codex-source reply; a same-ref
|
|
1273
|
+
replay of a stored undelivered receipt exits nonzero without a second push; an
|
|
1274
|
+
expired Claude deadline stays `refused`/`claude-private-frame-unsupported`;
|
|
1275
|
+
and every undelivered native Codex push outcome carries a cause.
|
|
1230
1276
|
- The offline Claude dialogue fixtures reply to a Codex source, so they now
|
|
1231
1277
|
accept a terminal Codex push failure receipt from the qualification reply --
|
|
1232
1278
|
the four-field `ref/state/reason/action` line for a fresh reply ref, read
|
|
@@ -1275,8 +1321,8 @@
|
|
|
1275
1321
|
- `TestClaudeExplicitReplyGuardRetryRetainsQualificationAndRouteFences` keeps
|
|
1276
1322
|
known-zero recovery behind the existing exact qualification, provider caller,
|
|
1277
1323
|
route and bounded-command gates. `TestClaudeEndpointProcessIntegration/explicit-reply-known-zero-manual-retry`
|
|
1278
|
-
runs the public CLI as the synthetic provider's real descendant:
|
|
1279
|
-
|
|
1324
|
+
runs the public CLI as the synthetic provider's real descendant: an oversized
|
|
1325
|
+
frame fails with stderr/ref/action, a corrected manual reply reaches the
|
|
1280
1326
|
provider once, same/different-ref duplicates write zero, and independent
|
|
1281
1327
|
public status processes preserve both attempts. The provider checks for
|
|
1282
1328
|
unexpected queued connections before exit.
|
|
@@ -1344,8 +1390,11 @@
|
|
|
1344
1390
|
receipts, Agent count two, Codex provider writes zero, and pre-removal
|
|
1345
1391
|
Registry/tmux/helper/socket/process residual checks. Its strict Claude content
|
|
1346
1392
|
fixture preserves `sourceNotice` and verifies claimed/unverified source,
|
|
1347
|
-
caller-authentication limits, and untrusted payload semantics.
|
|
1348
|
-
|
|
1393
|
+
caller-authentication limits, and untrusted payload semantics. The Claude
|
|
1394
|
+
fixture also replays every `drop` item of
|
|
1395
|
+
`internal/app/testdata/claude-dialogue-observed-frames.json` after init, and
|
|
1396
|
+
the scenario requires the emitted names to equal the corpus drop set. It does
|
|
1397
|
+
not require a provider binary, model, network, or version matrix.
|
|
1349
1398
|
- The real-provider path is opt-in through `scripts/agent-dialogue-canary-setup.py`
|
|
1350
1399
|
and `docs/heterogeneous-dialogue-canary.md`. It uses public reply-only activation,
|
|
1351
1400
|
product observer/helper-memory evidence and separate qualification/idle claims.
|
|
@@ -1465,6 +1514,208 @@ decision itself are separate concerns.
|
|
|
1465
1514
|
equivalent belongs to the recurrence gate, so green here is not a claim about
|
|
1466
1515
|
any other file.
|
|
1467
1516
|
|
|
1517
|
+
### Claude quiet hook marking tests
|
|
1518
|
+
|
|
1519
|
+
`ingestClaudeHook` marks the attributed Pane before it dispatches, so a quiet
|
|
1520
|
+
Claude event is one hook marking, not two.
|
|
1521
|
+
|
|
1522
|
+
- `TestClaudeQuietHookMarksThePaneOnce` owns the cost of one quiet event
|
|
1523
|
+
(PreToolUse, PostToolUse, PostToolBatch, an unknown event) on an owned Pane
|
|
1524
|
+
and PreToolUse on an unbound one: one set-option per marker option, the exact
|
|
1525
|
+
tmux command count, and the exact Registry read count.
|
|
1526
|
+
- `TestClaudeHookOutcomeIsUnchangedByMarkingOnce` pins golden outcomes for
|
|
1527
|
+
SessionStart, state and runtime-quiet UserPromptSubmit, PreToolUse,
|
|
1528
|
+
PostToolBatch, Stop, and an unknown event: final Pane option state, the Agent
|
|
1529
|
+
session ref and activation, and the ingest records.
|
|
1530
|
+
|
|
1531
|
+
### Antigravity quiet hook marking tests
|
|
1532
|
+
|
|
1533
|
+
`ingestAntigravityHook` marks the attributed Pane before it dispatches, so a
|
|
1534
|
+
quiet Antigravity event is one hook marking, not two.
|
|
1535
|
+
|
|
1536
|
+
- `TestAntigravityQuietHookMarksThePaneOnce` owns the cost of one quiet event
|
|
1537
|
+
(PostToolUse with and without an error, PostInvocation, an unknown event, the
|
|
1538
|
+
idle, default and late-busy Statusline branches, and runtime-quiet Stop,
|
|
1539
|
+
PreInvocation and approval Statusline) on an owned Pane and PostToolUse on an
|
|
1540
|
+
unbound one: one set-option per marker option, the exact tmux command count,
|
|
1541
|
+
and the exact Registry read count.
|
|
1542
|
+
- `TestAntigravityHookOutcomeIsUnchangedByMarkingOnce` pins golden outcomes for
|
|
1543
|
+
notify, state and runtime-quiet Stop and approval Statusline, busy, late, idle
|
|
1544
|
+
and default Statusline, state and runtime-quiet PreInvocation, PostInvocation,
|
|
1545
|
+
PostToolUse with and without an error, and an unknown event: final Pane option
|
|
1546
|
+
state, the Agent session ref and activation, and the ingest records.
|
|
1547
|
+
|
|
1548
|
+
### First Window registry name tests
|
|
1549
|
+
|
|
1550
|
+
`new-session` creates a Project session's first tmux Window under the Registry
|
|
1551
|
+
name of the Window the caller adopts, so on create and on Continue
|
|
1552
|
+
`#{window_name}`, `#{@projmux_window_name}`, and the Registry
|
|
1553
|
+
`metadata.name` agree once `mirrorWindow` turns automatic-rename off.
|
|
1554
|
+
|
|
1555
|
+
- `TestMaterializerCreateSessionNamesTheFirstWindow` owns the
|
|
1556
|
+
`ensureSessionAt` argv: exactly one `-n <name>` for a non-blank name, no `-n`
|
|
1557
|
+
for a blank or whitespace name, and a fresh-server `-f <config>` declaration
|
|
1558
|
+
with `-n` that still validates and assembles with `-f` before `new-session`.
|
|
1559
|
+
- `TestRegistryTopologyContinueNamesFirstWindowFromRegistry` owns the Continue
|
|
1560
|
+
path: the one `new-session` carries the first planned Window's Registry name,
|
|
1561
|
+
and the adopted first Window's uid and stable-name mirror match that row.
|
|
1562
|
+
- `TestCreateSessionNamesTheFirstWindowItAdopts` owns the create path: the
|
|
1563
|
+
`new-session` name is the Window `adoptInitialWindow` adopts, for a stored
|
|
1564
|
+
first Window and for the automatic idx0 Window of a default registration
|
|
1565
|
+
whose Registry name is its own uid, with `--name` landing on the second
|
|
1566
|
+
Window.
|
|
1567
|
+
- `TestCanonicalProjectStartupNamesTheFirstWindowItAdopts` owns canonical
|
|
1568
|
+
Project startup (`materializeProjectSessionCanonical`): `new-session` carries
|
|
1569
|
+
the Registry name of the Window `adoptInitialWindow` adopts, both for a stored
|
|
1570
|
+
first Window and for the automatic Window a zero-Window Continue allocates,
|
|
1571
|
+
and a Project that reaches startup with no Window to adopt gets no `-n`.
|
|
1572
|
+
- `test/integration/linux-smoke.sh` `assert_first_window_registry_name` owns
|
|
1573
|
+
the real-tmux check: after the Session State Project's first `switch open`
|
|
1574
|
+
and after its `switch sidebar-open --mode continue` recreation, the first
|
|
1575
|
+
Window's `#{window_name}` and `#{@projmux_window_name}` both equal the
|
|
1576
|
+
Registry name of its `@projmux_window_uid`.
|
|
1577
|
+
|
|
1578
|
+
### Resumed Agent Pane name handoff tests
|
|
1579
|
+
|
|
1580
|
+
Continue topology replay and `projmux agent resume` give a new Agent Pane the
|
|
1581
|
+
non-automatic `metadata.name` of that Agent's non-live old Pane row through one
|
|
1582
|
+
rule, `selectAgentPaneNameHandoff`. Only Agent-owned `agent` role rows proven
|
|
1583
|
+
live nowhere are candidates, a row named by its own UID is automatic and never
|
|
1584
|
+
carried, several candidates are resolved only by the last termination receipt
|
|
1585
|
+
Pane, and a name another resource reserves is not carried. A name that cannot be
|
|
1586
|
+
carried leaves the automatic name and one
|
|
1587
|
+
`projmux: agent/<label> new Pane keeps an automatic name: <reason>` line; it
|
|
1588
|
+
never refuses Continue or resume.
|
|
1589
|
+
|
|
1590
|
+
- `TestSelectAgentPaneNameHandoffTable` owns the rule: explicit,
|
|
1591
|
+
`<agent>-pane` and `-L` names are carried; automatic, live, other-Agent and
|
|
1592
|
+
shell rows are not candidates; the receipt Pane wins among several
|
|
1593
|
+
candidates and no receipt match carries nothing; a name reserved by another
|
|
1594
|
+
resource is not carried.
|
|
1595
|
+
- `TestAttachAgentPaneWithNameFallsBackToAutomaticName` owns the mutation
|
|
1596
|
+
side: a name the Registry refuses at attach yields an automatic name and one
|
|
1597
|
+
reason instead of an error.
|
|
1598
|
+
- `TestRegistryTopologyContinueCarriesOldAgentPaneName` and
|
|
1599
|
+
`TestRegistryTopologyContinueResolvesSeveralOldPaneNamesByTerminationReceipt`
|
|
1600
|
+
own Continue: the new Pane UID differs, the Registry name and the fake tmux
|
|
1601
|
+
`@projmux_pane_label` equal the old name, only the new Pane holds it, an
|
|
1602
|
+
automatic old UID is no name anywhere, and an unresolvable set of names
|
|
1603
|
+
converges with exactly one notice.
|
|
1604
|
+
- `TestAgentResumeCarriesOldAgentPaneName`,
|
|
1605
|
+
`TestAgentResumeResolvesSeveralOldPaneNamesByTerminationReceipt` and
|
|
1606
|
+
`TestAgentResumeKeepsAutomaticNameWhenOldPaneIsNotProvenNonLive` own
|
|
1607
|
+
`agent resume`: the selected old row is released under the replay owner
|
|
1608
|
+
inventory and its name lands on the new Pane, unselected and automatic rows
|
|
1609
|
+
stay as evidence, and a row still claimed live elsewhere is kept with an
|
|
1610
|
+
automatic new name and one stderr line.
|
|
1611
|
+
- `TestContinueReplayCarriesOldAgentPaneNamesThroughRealTmux` and
|
|
1612
|
+
`TestAgentResumeCarriesOldAgentPaneNamesThroughRealTmux` own the real-tmux
|
|
1613
|
+
check: on an isolated server with a `tail -f /dev/null` provider, old names
|
|
1614
|
+
`reviewer`, `default-pane` and `-L` land on new Pane UIDs in the Registry and
|
|
1615
|
+
in `#{@projmux_pane_label}`, and the automatic control is named by its new
|
|
1616
|
+
UID. Both run in `make test-integration` through
|
|
1617
|
+
`test/integration/resumed-agent-pane-names.sh`, which requires tmux and both
|
|
1618
|
+
PASS lines.
|
|
1619
|
+
|
|
1620
|
+
### Flag-shaped Window name tests
|
|
1621
|
+
|
|
1622
|
+
`runtimeMutationArgv` reads each operand as a tmux flag or value slot the way
|
|
1623
|
+
tmux getopt reads the assembled command (the argument-flag table is checked
|
|
1624
|
+
against `tmux list-commands` on tmux 3.4 and 3.6), so a Registry Window name
|
|
1625
|
+
spelled like a tmux flag (`-L`, `-t`, `-Lx`) is accepted where tmux reads a
|
|
1626
|
+
value and still refused where tmux would read a flag. `--` ends options only
|
|
1627
|
+
for rename-window: every producer spells the Window display rename
|
|
1628
|
+
`rename-window -t @N -- <name>`, and for every other verb `--` stays a flag
|
|
1629
|
+
slot.
|
|
1630
|
+
|
|
1631
|
+
- `TestRuntimeMutationArgvAcceptsFlagShapedNamesInTmuxValueSlots` owns the
|
|
1632
|
+
producer shapes that carry a Window name: create-window `-n`, create-session
|
|
1633
|
+
`-n` with and without the fresh-server `-f <config>`, and the write-identity,
|
|
1634
|
+
write-stable-name and write-option stable-name value. Each name reaches argv
|
|
1635
|
+
unchanged and `-t`, `-s` and `-f` still bind to the real declaration.
|
|
1636
|
+
- `TestRuntimeMutationArgvKeepsRefusingFlagSlotRouteAndAttachedOperands` owns
|
|
1637
|
+
the exact refusals: flag-slot `-L`/`-S`, attached `-Lx -Sx -tx -sx`
|
|
1638
|
+
(including a rename-window name with no `--` before it), real duplicate `-t`,
|
|
1639
|
+
`-s` and `-f`, `;` and `\;` in flag and value slots, a value token that never
|
|
1640
|
+
makes the next token a value, and a verb with no tmux argument row (Codex
|
|
1641
|
+
relaunch) that keeps every operand a flag slot.
|
|
1642
|
+
- `TestRuntimeMutationArgvAcceptsFlagShapedWindowNamesAfterRenameWindowEndOfOptions`
|
|
1643
|
+
owns the canonical Window display rename: `-L -S -s -t -f -n -Lx -Sx -sfoo
|
|
1644
|
+
-tfoo ok-name` and `--` itself each reach argv as
|
|
1645
|
+
`rename-window -t @2 -- <name>`, the action prints, and a drifted `-t` still
|
|
1646
|
+
refuses.
|
|
1647
|
+
- `TestRuntimeMutationArgvRefusesRenameWindowWithoutSingleEndOfOptionsName`
|
|
1648
|
+
owns the rename-window shape rule: no `--` (the old `-t @2 <name>` shape and
|
|
1649
|
+
flag-shaped `-s`, `-f`, `-n`), two `--`, `--` not directly before the name,
|
|
1650
|
+
`--` with no name, and two operands after `--` refuse with `must end options
|
|
1651
|
+
with -- before exactly one Window name`; flag-slot `-L`/`-S` and attached
|
|
1652
|
+
operands before `--` keep their existing messages; kill-window, create-window
|
|
1653
|
+
and write-stable-name keep `--` a flag slot.
|
|
1654
|
+
- `TestControllerWindowNameRenameEndsOptionsAndRollsBackFlagShapedName` owns
|
|
1655
|
+
the controller `window_name` effect: the declared, executed and owned-reverse
|
|
1656
|
+
argv all spell `rename-window -t @N -- <name>` and print, a declaration or
|
|
1657
|
+
printed action without `--` is refused, and a failed sibling write rolls a
|
|
1658
|
+
flag-shaped old name back onto the fake tmux Window.
|
|
1659
|
+
- `TestMaterializerCreatesFlagShapedWindowNamesThroughRealTmux` owns the
|
|
1660
|
+
real-tmux check: `newWindow`, the Window UID claim and `mirrorWindow` on an
|
|
1661
|
+
isolated server create Windows named `-L`, `-t` and `-Lx`, and each
|
|
1662
|
+
`#{window_name}` and `#{@projmux_window_name}` equals the input.
|
|
1663
|
+
- `TestGeneratedWindowRenameProjectsFlagShapedNamesThroughRealTmux` owns the
|
|
1664
|
+
real-tmux check for the key and Window menu rename: `renameWindowFromIntent`,
|
|
1665
|
+
wired like `newCreateCommand` and `newRenameCommand` over an isolated server,
|
|
1666
|
+
renames one Window to `-L -t -s -Lx -S -Sx -sfoo -tfoo -f -n` in turn, and
|
|
1667
|
+
after each rename `#{window_name}`, `#{@projmux_window_name}` and the
|
|
1668
|
+
Registry name equal the input. Both real-tmux tests run in
|
|
1669
|
+
`make test-integration` through `test/integration/flag-shaped-window-names.sh`,
|
|
1670
|
+
which requires tmux and both PASS lines.
|
|
1671
|
+
|
|
1672
|
+
### Generated rename Registry tests
|
|
1673
|
+
|
|
1674
|
+
The Window rename key, the Window menu Rename item, and the Pane rename key
|
|
1675
|
+
commit through `renameCommand.commitRename`, the owner `projmux rename
|
|
1676
|
+
window|pane` uses, so the Registry name, its tmux mirror, and for a Window the
|
|
1677
|
+
tmux `window_name` change together and survive Continue.
|
|
1678
|
+
|
|
1679
|
+
- `TestGeneratedWindowRenameCommitsRegistryNameMirrorAndDisplay` owns the
|
|
1680
|
+
Window route for a Project and a ControlSession root on the session's first
|
|
1681
|
+
Window: Registry `metadata.name`, `@projmux_window_name`, and `window_name`
|
|
1682
|
+
all hold the new name, the stable-name mirror write precedes the display
|
|
1683
|
+
`rename-window`, and the client sees one success line.
|
|
1684
|
+
- `TestGeneratedPaneRenameCommitsRegistryNameAndLabel` owns the Pane route for
|
|
1685
|
+
Project and ControlSession shell Panes and an Agent-owned Pane whose name its
|
|
1686
|
+
launcher chose: Registry `metadata.name` and `@projmux_pane_label` change
|
|
1687
|
+
together and the Agent ownership and name stay.
|
|
1688
|
+
- `TestGeneratedRenameRefusalsWriteNothing` owns the input and conflict rules
|
|
1689
|
+
for both routes: a space, leading or trailing whitespace, `'`, `"`, `$(...)`,
|
|
1690
|
+
`;`, `%` and `#{...}` are refused with the reason and the
|
|
1691
|
+
`SanitizeNameBase` usable name, `..` is refused with no suggestion, an empty
|
|
1692
|
+
or whitespace-only response reports that nothing changed, a same-scope name
|
|
1693
|
+
conflict and an anchor with no Registry identity are refused, and every row
|
|
1694
|
+
writes zero Registry state, zero tmux state, and exactly one client line.
|
|
1695
|
+
- `TestGeneratedRenameNameInputRules` owns `generatedRenameName` on its own.
|
|
1696
|
+
- `TestGeneratedRenameRouteRequiresTheIntactHereDocumentBody` owns the stdin
|
|
1697
|
+
contract: the `name=` sentinel and trailing newline are required, a blank
|
|
1698
|
+
body never reaches the intent, the positional `-- <name>` producer still
|
|
1699
|
+
works, and malformed argv is refused before any intent.
|
|
1700
|
+
- `TestTmuxClientMessagesAreDeliveredLiterally` owns the exact
|
|
1701
|
+
`display-message` argv of both client message writers: `%` and `#` are
|
|
1702
|
+
doubled so tmux's strftime and format passes show the text as written.
|
|
1703
|
+
- `TestGeneratedRenameBindingsCarryTheResponseOnlyInAQuotedHereDocument` owns
|
|
1704
|
+
the rendered bindings: the response placeholder `%%%` is the only `%` and
|
|
1705
|
+
sits in a quoted here-document body.
|
|
1706
|
+
- `TestGeneratedRenameBindingDeliversHostileResponsesVerbatimThroughRealTmux`
|
|
1707
|
+
sources both rendered bindings into an isolated real tmux, applies tmux's
|
|
1708
|
+
own prompt template replacement, and requires `x'y`, `a"b`, `$(...)`,
|
|
1709
|
+
backquotes, `;`, spaces, `%1 %% %%%`, the here-document delimiter, and blank
|
|
1710
|
+
responses to reach the recorder byte for byte with no shell side effect; it
|
|
1711
|
+
also pins the documented residual that run-shell expands `#{session_name}`.
|
|
1712
|
+
- `test/integration/linux-smoke.sh` owns the real-tmux regression: on the
|
|
1713
|
+
Session State Project the generated `window-rename` and `pane-rename` routes
|
|
1714
|
+
run with an explicit client and anchor, a quote-breaking response is
|
|
1715
|
+
refused, and after `switch sidebar-open --mode continue` the Registry names,
|
|
1716
|
+
`#{window_name}`, `#{@projmux_window_name}`, and `#{@projmux_pane_label}`
|
|
1717
|
+
hold the new names.
|
|
1718
|
+
|
|
1468
1719
|
### Settled Codex authority admission tests
|
|
1469
1720
|
|
|
1470
1721
|
`aiCodexLifecycleSink.SetAuthority` publishes one native authority transition as
|
|
@@ -1929,6 +2180,181 @@ Nothing in this feature terminates, starts, signals, or restarts a process.
|
|
|
1929
2180
|
The census is two file reads per process; replacing the residual processes is a
|
|
1930
2181
|
separate decision this measurement exists to inform.
|
|
1931
2182
|
|
|
2183
|
+
### Registry read result fence tests
|
|
2184
|
+
|
|
2185
|
+
- `make test`: `TestRegistryReadResultFence`,
|
|
2186
|
+
`TestRegistryReadEntryPointResultFence`, and
|
|
2187
|
+
`TestClassifyRegistryBytesResultFence` pin, as literals observed on main,
|
|
2188
|
+
what the Registry envelope read returns for one shared input matrix: the
|
|
2189
|
+
current schema, every registered migration version (1, 2, 3), and newer,
|
|
2190
|
+
negative, zero, absent, `null`, string, float, and overflowing
|
|
2191
|
+
`schemaVersion` values; truncated documents with a known and an unknown
|
|
2192
|
+
version; trailing bytes; the version as the last key; duplicate and
|
|
2193
|
+
case-variant version keys in both orders; a version only inside a child
|
|
2194
|
+
object; top-level array, string, number, and `null`; a UTF-8 BOM; body type
|
|
2195
|
+
errors behind a current, a migration, and an unknown version; invalid graphs;
|
|
2196
|
+
empty and whitespace-only files with and without the initialized marker; and
|
|
2197
|
+
a generated document over 1 MiB with a current and an unknown version.
|
|
2198
|
+
`readWithoutRepairWithReport` pins the whole 5-tuple (Registry digest,
|
|
2199
|
+
on-disk version, existed, MigrationReport digest, error), the error's
|
|
2200
|
+
`errors.Is` sentinel set and `errors.As` types, and its message with temp
|
|
2201
|
+
paths normalized. `LoadDegradedReadOnly`, `LoadReadOnly`, and
|
|
2202
|
+
`LoadWithMigrationResult` pin the same error facts, the Registry digest, and
|
|
2203
|
+
the migration result fields; `classifyRegistryBytes` pins State,
|
|
2204
|
+
SchemaVersion, Detail, and Contents. The matrix rows and pinned values must
|
|
2205
|
+
not be edited to follow a product change: a change that moves one is a
|
|
2206
|
+
behavior change, not a fence update.
|
|
2207
|
+
- `make test`: `TestRegistryReadMatchesLegacyTwoPassReference` and
|
|
2208
|
+
`FuzzRegistryReadMatchesLegacyTwoPassReference` compare the product read and
|
|
2209
|
+
classifier against frozen test-only copies of the two-pass order (envelope
|
|
2210
|
+
decode, schema classification, then body decode), `legacyReadWithoutRepairWithReport`
|
|
2211
|
+
and `legacyClassifyRegistryBytes`, under an identically reset deterministic
|
|
2212
|
+
environment: equal Registry and report values, version, existed, error text,
|
|
2213
|
+
and error facts, and an equal `RegistryFileInfo`. The fuzz target seeds every
|
|
2214
|
+
matrix row with its marker flag, runs its seeds under `make test`, and covers
|
|
2215
|
+
both the read path and the classifier in one `-fuzz` run. The legacy copies
|
|
2216
|
+
are the reference and are not kept in step with the product.
|
|
2217
|
+
|
|
2218
|
+
### Registry single decode tests
|
|
2219
|
+
|
|
2220
|
+
- `make test`: `TestScanTopLevelSchemaVersion` pins the allocation-free
|
|
2221
|
+
top-level `schemaVersion` scanner that lets a current or migratable Registry
|
|
2222
|
+
document be decoded by encoding/json once instead of twice. Rows it must read
|
|
2223
|
+
exactly: the key first, in the middle, last, or absent; `schemaVersion` only
|
|
2224
|
+
inside child objects, arrays, and string values; escaped quotes and
|
|
2225
|
+
backslashes and brackets inside skipped strings; case-variant keys;
|
|
2226
|
+
duplicate keys with the last one winning; `null` alone and after an integer;
|
|
2227
|
+
zero, negative, `-0`, and the int range limits. Every such row is also held
|
|
2228
|
+
to `json.Valid` and to the real envelope decode. Rows it must leave to
|
|
2229
|
+
encoding/json: non-object top levels, a UTF-8 BOM, float and exponent
|
|
2230
|
+
values, string, bool, object, and array values, integer overflow, escaped
|
|
2231
|
+
and non-ASCII keys (including one that Unicode-folds to `schemaVersion`),
|
|
2232
|
+
and malformed separators or trailing bytes.
|
|
2233
|
+
- `make test`: `FuzzScanTopLevelSchemaVersionMatchesEnvelopeDecode` holds the
|
|
2234
|
+
scanner to its contract: for any valid JSON input on which it answers, the
|
|
2235
|
+
envelope decode succeeds with the same version. It seeds every Registry read
|
|
2236
|
+
fence matrix row and every scanner table row.
|
|
2237
|
+
- `make test`: `TestDecodeRegistryDocumentStages` pins where
|
|
2238
|
+
`decodeRegistryDocument` stops: newer, negative, zero, absent, and `null`
|
|
2239
|
+
versions with a body type error behind them are refused as schema errors
|
|
2240
|
+
rather than malformed; a known version followed by a syntax error falls back
|
|
2241
|
+
to the envelope decode and reports malformed JSON with version 0; a body
|
|
2242
|
+
type error behind a current or migration version is a body decode failure
|
|
2243
|
+
carrying that version; and valid documents, including one whose version key
|
|
2244
|
+
is escaped, decode.
|
|
2245
|
+
- The Registry read result fence tests above are the equivalence gate for this
|
|
2246
|
+
decoder: the scanner only selects the single-decode path, and every refusal
|
|
2247
|
+
and error is still produced by the envelope-then-body sequence.
|
|
2248
|
+
|
|
2249
|
+
### Route guard identity reuse tests
|
|
2250
|
+
|
|
2251
|
+
Within one create, Continue, or resume transaction, the route guards reuse a
|
|
2252
|
+
tmux server identity that the same transaction already proved against tmux.
|
|
2253
|
+
The identity is the exact `-S`/`-L` route, `#{socket_path}`, authority class,
|
|
2254
|
+
`#{pid}`, `@projmux_app` and `@projmux_socket_name`. The first proof always
|
|
2255
|
+
reads tmux. Every guarded write the materializer executes runs through
|
|
2256
|
+
`materializer.guardedWriteSteps`, which drops the reused proofs after each
|
|
2257
|
+
Apply and Undo, so the write's post-effect observation proves the route against
|
|
2258
|
+
tmux again. Any differing key component, a route rebind or re-resolution, a
|
|
2259
|
+
probe failure, or the end of the transaction also forces a full proof. The
|
|
2260
|
+
split-layout batch keeps its pre-write revalidation, and a transaction that
|
|
2261
|
+
reused any proof proves the route once more before the Registry commits.
|
|
2262
|
+
Nothing is persisted or shared across processes or transactions.
|
|
2263
|
+
|
|
2264
|
+
- `make test`: `TestRuntimeRouteIdentityCacheReusesOnlyTheExactProvenTuple`
|
|
2265
|
+
owns the key. Only the exact tuple is reused. A differing PID, physical
|
|
2266
|
+
socket, socket flag, `-L` name, authority class (including standalone versus
|
|
2267
|
+
standalone-explicit), socket-name marker, app marker, or guard scope misses.
|
|
2268
|
+
A nil cache, a blank operation id, a cold cache, and incomplete identities
|
|
2269
|
+
never reuse.
|
|
2270
|
+
- `make test`: `TestRuntimeRouteIdentityCacheInvalidationTriggers` owns
|
|
2271
|
+
invalidation. A server swap and an idempotent transaction-end close drop every
|
|
2272
|
+
proof. On a real guarded write, a server PID change, a socket path change,
|
|
2273
|
+
reconnection (`route-bind`), route re-resolution, a probe error, and
|
|
2274
|
+
transaction end each make the next write's pre-write guards read tmux again,
|
|
2275
|
+
while the control case reuses the previous write's post-effect proof.
|
|
2276
|
+
- `make test`: `TestMaterializerRouteGuardReprovesIdentityAfterEveryGuardedWrite`
|
|
2277
|
+
counts tmux calls. The first proof and the segment after every guarded write
|
|
2278
|
+
each read `#{socket_path}` and `#{pid}` from tmux, identity reads per write
|
|
2279
|
+
are fewer than without a cache, and the ordered write argv is identical.
|
|
2280
|
+
- `make test`: `TestMaterializerPlansRunOnlyThroughTheGuardedWriteSeam` parses
|
|
2281
|
+
the package and fails when a `materializer` method passes steps to
|
|
2282
|
+
`executeRuntimeMutationPlan` without wrapping them in `guardedWriteSteps`, or
|
|
2283
|
+
builds runtime mutation steps without running any through that seam. Every
|
|
2284
|
+
materializer plan site still calls `executeRuntimeMutationPlan` directly, so
|
|
2285
|
+
`TestPlanOnlyMutationNegativeAuditHasZeroBypass` is unchanged.
|
|
2286
|
+
- `make test`: `TestRouteIdentityCacheMatchesUncachedCreateUnderDriftAfterEachGuardedWrite`
|
|
2287
|
+
enumerates every guarded write of a canonical `create pane` and a `create
|
|
2288
|
+
agent` transaction, restarts the server generation with objects surviving
|
|
2289
|
+
immediately after each one, and requires the error string, ordered write argv,
|
|
2290
|
+
Registry commit, and final Pane set to be byte-identical with and without the
|
|
2291
|
+
cache.
|
|
2292
|
+
- `make test`: `TestRouteIdentityCommitReproofRefusesDriftBeforeCommitWithoutAWrite`
|
|
2293
|
+
moves the server generation at the final reconcile pass, after the last
|
|
2294
|
+
post-effect proof and with no write before commit. The commit re-proof
|
|
2295
|
+
refuses, the runtime ledger rollback re-proves and refuses its kills, and the
|
|
2296
|
+
Registry is not committed; without reuse the same window commits.
|
|
2297
|
+
- `make test`: `TestCachedRouteGuardRefusalsKeepTheirErrorFamily` requires
|
|
2298
|
+
socket path drift, server generation drift, a cleared app marker, and a
|
|
2299
|
+
drifted logical marker to refuse with byte-identical `runtime mutation plan:`
|
|
2300
|
+
wording with and without a cache, with zero writes and nothing cached.
|
|
2301
|
+
- `make test`: `TestStaleRouteIdentityCacheIsCaughtByTheNextFullProof` moves the
|
|
2302
|
+
server generation after a post-effect proof, shows that a guard with no write
|
|
2303
|
+
since that proof reuses it, and requires a pre-write revalidation to refuse
|
|
2304
|
+
with the uncached wording, drop every scope's proof, and make the next guarded
|
|
2305
|
+
write refuse exactly as it does without a cache.
|
|
2306
|
+
- `make test`: `TestSplitLayoutRevalidationNeverReusesTransactionIdentity`
|
|
2307
|
+
keeps the split-layout batch's full four-read proof immediately before its
|
|
2308
|
+
first resize. `TestCanonicalCreatePaneLayoutServerGenerationDriftWritesZeroResizes`
|
|
2309
|
+
and `TestCanonicalCreatePaneMultiPaneLayoutUsesBoundedBatchInsideRegistryUpdate`
|
|
2310
|
+
are unchanged.
|
|
2311
|
+
- `make test`: `TestCreateTransactionScopesRouteIdentityReuseToOneOperation`
|
|
2312
|
+
drives consecutive `create pane` transactions. Each must prove the generation
|
|
2313
|
+
and both markers before its first write and close its identity scope.
|
|
2314
|
+
- `make test`: `TestStaleRouteIdentityDriftDuringCreateRollsBackThroughRuntimeLedger`
|
|
2315
|
+
restarts the server right after the split write inside `create pane`, both
|
|
2316
|
+
losing runtime objects and keeping them. Both fail with the same residual-plan
|
|
2317
|
+
message as without a cache, leave the Registry uncommitted, and reach the
|
|
2318
|
+
uncached Pane end state through the runtime ledger rollback with the identity
|
|
2319
|
+
scope closed.
|
|
2320
|
+
- `make test`: `TestContinueReplayProvesItsFirstRouteIdentityAgainstTmux` drives
|
|
2321
|
+
one `start project` Continue replay of a closed Project. The replay reads
|
|
2322
|
+
`#{socket_path}`, `#{pid}` and `@projmux_app` from tmux before its first
|
|
2323
|
+
runtime write, its first reuse follows a recorded proof that really ran, the
|
|
2324
|
+
exact tuple it proved is reusable while every differing component misses, and
|
|
2325
|
+
a replay with the scope closed reuses nothing and reads identity far more.
|
|
2326
|
+
- `make test`:
|
|
2327
|
+
`TestRouteIdentityCacheMatchesUncachedContinueReplayUnderDriftAfterEachGuardedWrite`
|
|
2328
|
+
enumerates every guarded write of a Continue topology replay, restarts the
|
|
2329
|
+
server generation with objects surviving immediately after each one, and
|
|
2330
|
+
requires the error string, ordered write argv, Registry commit, and final Pane
|
|
2331
|
+
set to be byte-identical with and without the cache.
|
|
2332
|
+
- `make test`: `TestContinueReplayClosesTheIdentityScopeBeforeItsRollback`
|
|
2333
|
+
drifts the generation after the replay's split write. The transaction closes
|
|
2334
|
+
its identity scope before the runtime ledger unwinds, never reopens it, and
|
|
2335
|
+
the unwind re-reads `#{socket_path}` and `#{pid}` from tmux.
|
|
2336
|
+
- `make test`:
|
|
2337
|
+
`TestContinueReplayCommitReproofRefusesDriftBeforeCommitWithoutAWrite` moves
|
|
2338
|
+
the server generation in the write-free window the commit re-proof opens. The
|
|
2339
|
+
replay refuses with `topology materialization: runtime mutation plan: route
|
|
2340
|
+
identity refused commit after reused proofs: planned runtime server generation
|
|
2341
|
+
drifted`, commits nothing, and unwinds through the runtime ledger.
|
|
2342
|
+
- `make test-integration`: `test/integration/route-guard-identity-cache.sh`
|
|
2343
|
+
drives the built binary against an isolated real tmux server (dropped
|
|
2344
|
+
`TMUX`/`TMUX_PANE`/`__PROJMUX_RUNTIME_ANCHOR_PANE`, owned `HOME`/XDG, short
|
|
2345
|
+
`TMUX_TMPDIR`, run-unique `-L`, cleanup of the exact queried socket path
|
|
2346
|
+
only). It prints a PASS line when `create pane` and `create window` keep
|
|
2347
|
+
their result shape, when a PATH tmux wrapper's argv log shows the
|
|
2348
|
+
`#{socket_path}`, `#{pid}` and `@projmux_app` reads before the transaction's
|
|
2349
|
+
first write, and when both creates refuse a server without `@projmux_app`
|
|
2350
|
+
with the existing `runtime mutation route: exact invocation server is not
|
|
2351
|
+
app-owned; ...` wording and leave the Pane and Window sets unchanged. It
|
|
2352
|
+
prints two more PASS lines for the Continue half: `start project` rebuilds a
|
|
2353
|
+
stopped Project's stored Window and Pane uid sets on the same exact server
|
|
2354
|
+
with the `#{socket_path}`, `#{pid}` and `@projmux_app` reads ahead of the
|
|
2355
|
+
replay's first write, and the same replay refuses a server without
|
|
2356
|
+
`@projmux_app`, writes no stdout, and materializes no session.
|
|
2357
|
+
|
|
1932
2358
|
## Review Checklist
|
|
1933
2359
|
- The branch stays within its stated scope.
|
|
1934
2360
|
- The change preserves boundaries between portable `projmux` behavior and local machine policy.
|
|
@@ -1942,7 +2368,8 @@ separate decision this measurement exists to inform.
|
|
|
1942
2368
|
- `TestClaudeProviderFrameSerializedByteBoundaryAndSafeRejection` fixes the
|
|
1943
2369
|
complete auth/user frame boundary at 8191/8192/8193 serialized bytes.
|
|
1944
2370
|
`TestClaudeProviderPostPreservesConstructionReasonBeforeAnyRouteOrWrite`
|
|
1945
|
-
separates invalid auth/content from size rejection before route or
|
|
2371
|
+
separates invalid auth/content shape from size rejection before route or
|
|
2372
|
+
write; content above the 4096-byte reply payload limit is judged by the frame.
|
|
1946
2373
|
`TestClaudeProviderWriteReasonsRequireOneActualWrite` distinguishes actual
|
|
1947
2374
|
zero/partial writes and invalid-count/full-with-error unknown outcomes.
|
|
1948
2375
|
`TestClaudeProviderRejectionReasonsSurviveHubReceiptStatusAndStoreReload`
|
|
@@ -1955,6 +2382,35 @@ separate decision this measurement exists to inform.
|
|
|
1955
2382
|
exercises the built binary's public send/status against the isolated provider:
|
|
1956
2383
|
size/action survive, credentials stay absent, and the rejected frame adds no
|
|
1957
2384
|
provider connection before the next expected message.
|
|
2385
|
+
- `TestClaudePushAcceptedPayloadLimitBodyIsDeliveredInOneFrameWithinBudget`
|
|
2386
|
+
pushes a real envelope with a 4096-byte ASCII and a 4095-byte Korean body
|
|
2387
|
+
through the hub: delivered, one full frame, frame within 8192 bytes.
|
|
2388
|
+
`TestClaudePushSymbolHeavyPayloadLimitBodyReportsSizedFrameRefusalThroughReceipt`
|
|
2389
|
+
requires the independently serialized `frameBytes`, zero provider writes, a
|
|
2390
|
+
known outcome, and survival through public receipt and store reload.
|
|
2391
|
+
`TestClaudePushSizeExcessEndsSizedNeverInvalidContentOrUnsupported` keeps
|
|
2392
|
+
every push size excess off `provider-frame-invalid-content` and
|
|
2393
|
+
`provider-frame-unsupported`; empty, invalid UTF-8, and NUL content stay
|
|
2394
|
+
invalid-content. `TestClaudeReplyToolArgvBodyKeepsPayloadByteLimit` keeps the
|
|
2395
|
+
reply tool argv body at 4096 bytes.
|
|
2396
|
+
- `TestAgentMessageSendRefusesClaudeBodyWhoseRenderedFrameExceedsBudgetBeforeAcceptance`
|
|
2397
|
+
refuses an over-budget Claude body on plain send, Claude-source reply and
|
|
2398
|
+
Codex-source reply with the independently computed `frameBytes`, a nonzero
|
|
2399
|
+
exit, and no receipt, reply receipt, adapter or explicit-reply call.
|
|
2400
|
+
`TestAgentMessageSendWithinFrameBudgetBodyKeepsAcceptThenPushOrder` accepts
|
|
2401
|
+
the exact 8192-byte frame with `route,route,store-put,adapter` and refuses
|
|
2402
|
+
one byte more. `TestAgentMessageSendCodexTargetHasNoClaudeFramePrecheck`
|
|
2403
|
+
leaves Codex targets unchecked.
|
|
2404
|
+
`TestAgentMessageSendFramePrecheckUsesLongerExecutableAndAssumedTokenLength`
|
|
2405
|
+
renders the longer of the sender executable and the fixed phrase with a
|
|
2406
|
+
48-byte token and never reads the messaging token.
|
|
2407
|
+
`TestAgentMessageSendFramePrecheckCoversEveryPrivateEnvelopeSizeRefusal`
|
|
2408
|
+
sweeps escape-heavy, Korean and ASCII bodies with production-length refs so
|
|
2409
|
+
no private envelope size refusal escapes the pre-check.
|
|
2410
|
+
`TestAgentMessageFailureActionNamesRenderedContentBytesForOldHelperInvalidContent`
|
|
2411
|
+
names the rendered content bytes, the old 4096-byte content limit and
|
|
2412
|
+
re-activation for an older helper's invalid-content failure; the persisted
|
|
2413
|
+
reason and every other action stay unchanged.
|
|
1958
2414
|
- `TestClaudeExplicitMultipleRequestsAndHumanOverlapSelectOnlyNamedOriginal`
|
|
1959
2415
|
requires an explicit choice of the original request; Stop text has no reply
|
|
1960
2416
|
authority regardless of human activity or pending request count.
|
|
@@ -2059,6 +2515,26 @@ separate decision this measurement exists to inform.
|
|
|
2059
2515
|
`TestClaudeDialogueStreamRejectsUnknownEffectsAndBoundsState` cover transient
|
|
2060
2516
|
public JSON validation, action/result pairing, zero pre-inbound tool effects,
|
|
2061
2517
|
unknown metadata refusal and finite hook/tool state.
|
|
2518
|
+
- `TestClaudeDialogueStreamReplaysObservedFrameCorpus` replays
|
|
2519
|
+
`internal/app/testdata/claude-dialogue-observed-frames.json`, the top-level
|
|
2520
|
+
key shapes preserved from a Claude Code 2.1.263 run, through a fresh stream in
|
|
2521
|
+
each item's declared state. Items carry provenance labels and the measured
|
|
2522
|
+
verdict. A reject must stop once its recorded rule keys are removed, so any
|
|
2523
|
+
validator change that moves a recorded verdict fails with the item name and rule.
|
|
2524
|
+
- L20 feeds the same corpus's `drop` items through `claudeDialogueStream.inspect`
|
|
2525
|
+
end to end: the Claude fixture reads the corpus from
|
|
2526
|
+
`PROJMUX_FAKE_CLAUDE_OBSERVED_FRAMES`, emits each frame after init with only the
|
|
2527
|
+
`session_id` value replaced, and records the emitted names, which the scenario
|
|
2528
|
+
compares with the corpus drop set. In `test/e2e/claudefixture`,
|
|
2529
|
+
`TestObservedSideFramesAreExactlyCorpusDropItems`,
|
|
2530
|
+
`TestObservedSideFramesFailClosed`,
|
|
2531
|
+
`TestObservedSideFramesLoadOnlyAnAbsoluteRegularCorpus` and
|
|
2532
|
+
`TestFixtureSourceSpellsNoCorpusSideFrameVocabulary` require the selection to be
|
|
2533
|
+
exactly the drop items; refuse a missing or malformed corpus, zero drop items, a
|
|
2534
|
+
non-`initialized` state or a frame without a string `session_id`; and fail when
|
|
2535
|
+
the fixture source spells a key or type value that only the drop frames carry.
|
|
2536
|
+
Rejected real shapes, value drift, unknown frames and fields, and
|
|
2537
|
+
`command_lifecycle` or `thinking_tokens` frames stay outside L20.
|
|
2062
2538
|
- `TestClaudeDialoguePipeEOFClosesInputButWaitsCurrentTurn`,
|
|
2063
2539
|
`TestClaudeDialoguePipeDiagnosticLossFailsClosedWithoutBusyLoop`,
|
|
2064
2540
|
`TestClaudeDialogueCleanupWaitsExactWriterAndHandlesZombieWithoutSignals`, and
|
|
@@ -2073,6 +2549,9 @@ separate decision this measurement exists to inform.
|
|
|
2073
2549
|
claimed by the original Codex route. This is deterministic integration
|
|
2074
2550
|
evidence, not actual model execution. Wrapped pane output is joined for the
|
|
2075
2551
|
readiness marker; cancellation closes the fixture's own first-frame listener.
|
|
2552
|
+
Between init and the startup result the fixture emits every `drop` item of the
|
|
2553
|
+
observed-frame corpus, so the observer reaches ready only after the validator
|
|
2554
|
+
drops each one.
|
|
2076
2555
|
- `TestClaudeEndpointProcessIntegration` intentionally remains the private
|
|
2077
2556
|
registration/bootstrap/process fixture. Its owned registration child opts in
|
|
2078
2557
|
after activation; it does not depend on a supervisor-inherited guard flag,
|
|
@@ -2112,3 +2591,6 @@ separate decision this measurement exists to inform.
|
|
|
2112
2591
|
|
|
2113
2592
|
- `make test`: Codex recovery diagnostics retain the original request through RPC classification, catalog wrappers, bounded broker lifecycle IPC and observer fallback. `TestRequestFailureDiagnosticPreservesOriginalThroughCatalogWrapping`, `TestFailureDiagnosticIPCProjectionIsClosedAndBounded`, `TestLifecycleRPCDiagnosticPreservesCodeAndRejectsUnrepresentableCode`, `TestObserverFallbackJournalPreservesSafeFailureAndClosedStartup`, `TestObserverDiagnosticRejectedAuthorityWritesZero`, `TestObserverSessionFailurePreservesTerminalReasonAfterAuthorityRejection`, `TestAIIngestTextPreservesBoundedRecoveryDiagnosticsAndLegacyRecords`, and `TestRecoveryDiagnosticGoldenInventoryAndBounds` cover unsupported/catalog/protocol/transport causes, missing/null codes, secret/invalid UTF-8/oversized inputs, existing reason tokens, terminal session-construction fallback after a rejected authority write, bounded public text projection with legacy-row compatibility, and commit-before-journal refusal. `TestManagerEvidenceContradictionAndOwnershipMatrix` and `TestManagerEvidenceCommandFailureAndTimeout` (with `TestManagerDiagnosticDeadlineProcess`) separate independent attach and official read-only manager observations, with unknown/contradictory refusal and zero lifecycle argv. The existing startup parser, parent fallback, exact authority, Phase 0 timing/close and Phase 1 recovery tests remain required.
|
|
2114
2593
|
- Opt-in installed zero-turn fixture: `CODEX_DIAGNOSTIC_INPUT=/absolute/input.json go test ./internal/app -run '^TestInstalledCodexRecoveryDiagnosticFixture$' -count=1 -v`, with inherited `TMUX` and `TMUX_PANE` unset. Input fields are `root` (new short `/tmp` directory), `binary`, `binarySHA256`, `sourceHead`, `sourceTree`, and `evidence` (outside root). The binary must come from that clean committed source. Synthetic manager/provider responses are explicitly labeled; the actual executable runs its observer and doctor against a private Registry, exact private tmux socket and real bounded broker IPC. `TestCodexDiagnosticProxyProcess` is the read-only synthetic subprocess helper. The fixture never uses authentication, starts a turn, resumes a live session, or mutates a manager. It checks journal/doctor agreement, the actual public `diagnostics agent-hook` JSON and text consumers (including a labeled synthetic legacy row), old startup parsing, version/ownership refusals, output bounds and exact socket cleanup, then removes its private root. Evidence retains the public JSON records and text lines alongside observer rows and doctor reports. Run the identical fixture against the candidate and the isolated merged-main install; preserve the input, binary/source/tree hashes and evidence JSON.
|
|
2594
|
+
|
|
2595
|
+
- `make test` / `make test-integration`: split start directory. `--cwd-from project|pane` and `[ai] split_cwd_from` select where a new shell or Agent split starts, through two named resolvers. `cliSplitCWDSource` owns `create pane`, `create agent` and the provider shortcuts and reads the flag alone; `resolveUISplitCWDSource` owns the split-UI intents and the Pane menu and reads flag > owner-root project config > global config > `project`. `TestCLISplitCWDSourceFollowsOnlyTheFlag` pins the CLI half, and `TestCLISplitCreateOpensNoSplitStartConfig` counts reads at the config seam: with `pane` in both tiers a CLI create with no flag opens zero split start config files and starts in the Project root, while the same config still moves a UI intent. `TestUISplitCWDSourceResolvesFlagThenProjectThenGlobalThenDefault` pins the UI precedence and the tier label, with an unknown value skipping its own tier and another tree's config never deciding. `TestSplitPaneCWDStaysInsideTheOwnerProjectRoot` pins the root rule on canonicalized paths: a descendant is kept, the root itself keeps its stored spelling, and `$HOME`, another registered Project tree, a missing directory, a failed read and symlinks pointing outside all fall back to the root with one notice naming the reason. `TestSplitCWDDefaultAndProjectSourceAreByteIdentical` requires the CLI default, an explicit `--cwd-from project`, a configured `project` and a configured `pane` to produce identical recorded tmux argv, Registry launch surface, stdout and stderr with zero live directory reads, and `TestSplitCWDDefaultIntentAndPaneMenuAreByteIdentical` holds the same for the UI default and a configured `project`. `TestCreateSplitStartsInTheActivePaneDirectory` covers the shell Pane and the Claude and Codex workspaces under `--cwd-from pane`, and the UI intent under a `pane` config; `TestCreateSplitFallsBackToTheProjectRootWithOneNotice` and `TestSplitCWDFallbackReachesTheClientOnce` pin one stderr line for a CLI create, one client message carrying the Pane menu result, and one `display-message` from the split-UI funnel. `TestSplitCWDFlagRefusalsCreateNothing` refuses `--cwd-from` with `--cwd` and unknown values with zero tmux calls and zero Registry writes while `--cwd` alone reads no config, `TestSplitCWDActivePaneIsTheAnchorNotTheFocus` proves the directory comes from the anchor or clicked Pane rather than the focused one, and `TestSplitCWDLeavesExcludedRoutesUnchanged` plus `TestSplitCWDNeverWritesTheScopeIdentity` keep resume intents, ControlSession roots and `create window` unchanged and keep the resolved directory out of session naming and owner selection. `test/integration/split-cwd-from-pane.sh` drives the built binary against a real isolated tmux server for the end-to-end boundary -- a Pane in a Project subdirectory, one outside the root, the unchanged default, a `pane` config in both tiers that a CLI create ignores, and `--cwd-from pane` still honored under that config -- and prints a PASS line per case, because CI's unit job has no tmux.
|
|
2596
|
+
- `make test`: Settings `AI > New splits start in`. `TestSettingsSplitCWDFromRowTracksTheUIResolver` recomputes the expected value from `resolveUISplitCWDSource` and fails when the row summary (`<value> - <tier>`) or the chooser toggle diverges from it, including a project-tier override of the global value. `TestSettingsSplitCWDFromWritesOnlyTheGlobalKeyAndMovesTheNextUISplit` requires the choice to add exactly one `split_cwd_from` line to an otherwise byte-identical global config and the next UI split to start in the active Pane directory. `TestSettingsSplitCWDFromCopyIsExactInBothLocales` pins the en-US and ko-KR row label, both value labels and the description, and requires both descriptions to carry the CLI boundary. The nav and static-node goldens gain the one row.
|