Testability Boundaries

Agentty keeps external systems behind trait boundaries so orchestration logic can be tested deterministically.

Testability and Boundaries🔗

The traits below are mocked with mockall. Most use #[cfg_attr(test, mockall::automock)]; shared workspace crates such as ag-forge also expose test mocks through crate features for downstream tests.

TraitModuleBoundary
SyncMainRunnerapp/core.rsApp-level async sync orchestration trigger used by list-mode sync flows.
ReviewRequestClientcrates/ag-forge/src/client.rsGitHub review-request detection and gh orchestration boundary.
ForgeCommandRunnercrates/ag-forge/src/command.rsProvider CLI command execution boundary used to unit-test the GitHub review-request adapter without a live gh binary.
GitClientinfra/git/client.rsGit/process operations (worktree, merge, rebase, diff, push, pull, and ahead/behind comparisons for both upstream-tracking and session-vs-base-branch status).
FsClientinfra/fs.rsAsync filesystem operations and path probes used by app or runtime orchestration, including non-blocking file reads, existence checks, canonicalization, session worktree cleanup, and prompt-image temp file or directory removal.
TmuxClientinfra/tmux.rsTmux subprocess operations for opening session worktrees and dispatching open commands.
TmuxCommandRunnerinfra/tmux.rsInternal tmux command boundary that keeps multi-command send-keys flows deterministic in unit tests.
AgentChannelinfra/channel.rsProvider-agnostic turn execution (session init, run turn, shutdown).
AgentBackendinfra/agent/backend.rsPer-provider setup and transport command construction.
AgentAvailabilityProbeinfra/agent/availability.rsMachine-scoped backend discovery used to filter settings defaults and /model without shelling out directly from app or runtime orchestration.
AppServerClientinfra/app_server/contract.rsProvider-specific app-server RPC execution and session runtime lifecycle.
EventSourceruntime/event.rsTerminal event polling for deterministic event-loop tests.
Clockapp/session/core.rsShared wall-clock and monotonic time boundary used by session orchestration and runtime helpers such as pasted-image file naming.
Backend (generic)runtime/core.rsRuntime accepts Terminal<B: Backend> via run_with_backend, enabling in-process TUI tests with TestBackend without a real terminal.
TerminalOperationruntime/terminal.rsTerminal raw-mode and alternate-screen transitions for deterministic setup and restore failure-path tests.
Sleeperlib.rsWall-clock sleep boundary used by retry/polling flows such as git rebase assistance.
UpdateRunnerinfra/version.rsnpm install command execution for background auto-updates.
VersionCommandRunnerinfra/version.rsnpm/curl command execution for update checks.
ProjectDiscoveryClientinfra/project_discovery.rsHome-directory repository discovery used by startup catalog refresh without walking the real filesystem from app/.
GitCommandRunnerinfra/git/rebase.rsRebase command invocation boundary for conflict/retry tests.
SyncAssistClientapp/session/workflow/merge.rsSync-rebase assistance execution boundary.
SessionRepositoryinfra/db/session.rsSession row persistence, turn metadata storage, and session list queries without binding app workflows to the full Database surface.
ProjectRepositoryinfra/db/project.rsProject persistence and project-list aggregation behind a narrow mockable boundary.
ReviewRepositoryinfra/db/review.rsSession review-request linkage persistence used by branch publish and refresh flows.
UsageRepositoryinfra/db/usage.rsPer-session model usage aggregation used by turn persistence and usage views.
ActivityRepositoryinfra/db/activity.rsSession-activity history queries and backfill helpers used by startup and session list refresh.
OperationRepositoryinfra/db/operation.rsPersisted session-operation lifecycle tracking used by worker restart recovery and cancellation.
SettingRepositoryinfra/db/setting.rsGlobal and project-scoped setting persistence used by startup and settings orchestration.
AppServerClient retry helpersinfra/app_server/retry.rsShared restart-and-replay orchestration for provider runtimes without duplicating lifecycle policy in each provider.
CodexRuntimeTransportinfra/agent/app_server/codex/transport.rsCodex stdio transport boundary for lifecycle, compaction, and turn-stream tests without scripted shell runtimes.
GeminiRuntimeTransportinfra/agent/app_server/gemini/transport.rsACP stdio transport boundary for Gemini runtime protocol tests.

Typed Error Enums at Infra Boundaries🔗

Each infra boundary exposes a typed error enum instead of opaque String errors, so the app layer can discriminate failure causes without parsing formatted messages.

Error TypeModuleVariantsWraps
DbErrorinfra/db.rsMigration, Query, Iosqlx::Error
GitErrorinfra/git/error.rsWorktreeCreate, WorktreeRemove, BranchDelete, Command, etc.std::io::Error, process exit details
AppServerTransportErrorinfra/app_server_transport.rsIo, ProcessTerminated, Timeoutstd::io::Error
AppServerErrorinfra/app_server/error.rsTransport, Provider, SessionNotFound, ShutdownAppServerTransportError via #[from]
AgentErrorinfra/channel/contract.rsAppServer, Backend, IoAppServerError via #[from]

The conversion chain AppServerTransportError → AppServerError::Transport → AgentError::AppServer allows ?-propagation through the transport, provider, and channel layers without collapsing causal context into formatted strings.

Typed Error Propagation at the App Layer🔗

The app layer propagates infra errors through two orchestration-level enums instead of flattening them to String:

Error TypeModuleWraps via #[from]
SessionErrorapp/session/error.rsDbError, GitError, AppServerError, plus Workflow(String) for contextual app-level failures
AppErrorapp/error.rsSessionError, DbError, GitError, plus Workflow(String) for contextual app-level failures

App-layer functions that cross infra boundaries return AppError or SessionError so callers can discriminate failure causes by variant. SessionError::with_context adds an operation-specific prefix to Workflow messages (for example "Commit assistance failed: …") while passing typed infrastructure variants through unchanged. At event and display boundaries (for example AppEvent variants and ReviewCacheEntry), errors are converted to String via Display because those types require Clone and Eq, which the infra error types cannot satisfy due to non-cloneable inner types such as std::io::Error.

When adding higher-level flows involving multiple external commands, prefer injectable trait boundaries and mockall-based tests over flaky end-to-end shell-heavy tests. Add a narrower internal command-runner boundary when a public orchestration trait still needs deterministic coverage of subprocess sequencing or retry behavior.

Apply the same rule to filesystem discovery and path probes in app/ and runtime/: route directory walking, exists checks, canonicalize, and file copy or persistence helpers through an infra boundary instead of calling std::fs or Path helpers directly from orchestration code.

Use the same pattern for time access in app/ and runtime/: if orchestration logic needs Instant::now() or SystemTime::now(), route that call through the shared Clock boundary instead of calling the clock API directly in production logic.

Session review-request publication and refresh follow this rule directly: SessionManager combines GitClient with ReviewRequestClient so tests can cover branch publish, duplicate detection, stored-link reuse, and archived session refresh without live forge auth or network state.

TUI E2E Testing Framework (testty)🔗

The testty workspace crate provides a dual-oracle model for TUI end-to-end testing. The PTY path (portable-pty + vt100) is the semantic oracle for text, style, and location assertions; the VHS path is the visual oracle and review artifact generator.

ModulePurpose
sessionPTY executor: spawns binaries in a pseudo-terminal, writes input, captures ANSI output.
frameTerminal frame parser: converts ANSI bytes into a cell grid with text, color, and style access.
regionRectangular region definitions with named anchors (top row, footer, quadrants, percentages).
locatorText locators with style/color filtering for identifying TUI controls.
assertionStructured matcher APIs: assert_text_in_region, assert_span_is_highlighted, assert_match_count.
recipeAgent-friendly helpers: expect_selected_tab, expect_keybinding_hint, expect_dialog_title.
scenario / stepScenario DSL: compose user journeys from steps, compile to PTY or VHS.
vhsVHS tape compiler: generates .tape files from scenarios for visual screenshot capture.
calibrationCell-to-pixel geometry mapping for screenshot overlays.
overlayBounding box and indicator rendering onto screenshot PNGs.
snapshotPaired baseline workflow: visual PNG + semantic frame sidecar with environment-driven update mode.
artifactArtifact directory management for test captures and failure diagnostics.