Skip to content

Native E2E Harness ​

Wardian uses a native Tauri/WebDriver harness for tests that must exercise real IPC, PTY behavior, provider spawning, or terminal rendering. Browser-only Playwright tests are not sufficient evidence for those areas.

Setup ​

Use Node.js 24 LTS for the native suite. Canonical state evidence checks use the built-in node:sqlite module, which is unavailable in Node.js 20. The nightly workflow verifies SQLite support before building the app.

Run the cross-platform setup command:

bash
npm run setup:e2e:native

The setup script:

  • verifies cargo is available
  • installs tauri-driver when missing
  • checks for a native WebDriver
  • on Windows, can download a matching msedgedriver.exe into tools/e2e-native/
  • on macOS/Linux, prints the WebDriver package or WARDIAN_NATIVE_WEBDRIVER guidance when automatic setup is not reliable

The legacy command is kept as an alias:

bash
npm run setup:e2e:native:windows

Generated driver artifacts belong under tools/e2e-native/ and are ignored by git.

Native artifact selection is fail-closed. Cargo metadata supplies the effective target directory, including CARGO_TARGET_DIR and Cargo config overrides; a stale repository-local target/ binary is not selected. Set WARDIAN_NATIVE_APP=<artifact-path> only when using a deliberate custom app artifact. Relative values are resolved from the repository root, and a missing, empty, or directory override stops before WebDriver/provider startup. The setup script also verifies package-local @tauri-apps/cli and selenium-webdriver resolution before driver setup.

POSIX shell:

bash
WARDIAN_NATIVE_APP=<artifact-path> npm run test:e2e:native:fast -- <native-test-file>

PowerShell:

powershell
$env:WARDIAN_NATIVE_APP = '<artifact-path>'
npm run test:e2e:native:fast -- <native-test-file>

For a prebuilt app and its matching CLI, also set WARDIAN_NATIVE_CLI and WARDIAN_NATIVE_SKIP_BUILD=1. The CLI must have the platform executable name (wardian-cli.exe on Windows, wardian-cli on POSIX) and match the CLI in the app's packaged resources. Invalid pairs stop before Cargo metadata, isolated home preparation, or driver startup. App-backed runs without this override freeze the CLI packaged with the app, then validate the copied pair before launch. A later Cargo debug build cannot replace an app's staged release CLI. All maintained CLI setup callers check paired and skip-build inputs before Cargo entry. They revalidate the already-frozen CLI against the app's packaged bytes; missing or mismatched inputs fail without a compiler fallback. Missing, nonregular, or conflicting packaged CLIs fail before copying the run's binaries. Equivalent direct and nested copies retain the app loader's direct path precedence. CLI-only runs and explicit post-build CLI freezing retain Cargo's effective target selection, including its preference for a debug CLI.

POSIX shell:

bash
WARDIAN_NATIVE_SKIP_BUILD=1 \
WARDIAN_NATIVE_APP='<absolute-app-path>' \
WARDIAN_NATIVE_CLI='<absolute-paired-cli-path>' \
npm run test:e2e:native:fast -- <native-test-file>

PowerShell:

powershell
$env:WARDIAN_NATIVE_SKIP_BUILD = '1'
$env:WARDIAN_NATIVE_APP = '<absolute-app-path>'
$env:WARDIAN_NATIVE_CLI = '<absolute-paired-cli-path>'
npm run test:e2e:native:fast -- <native-test-file>

Pair validation follows the app's maintained resource loader: Windows uses the executable directory; macOS bundles use Contents/Resources; Linux bundles carry usr/lib/Wardian alongside usr/bin. Ordinary Cargo target/<profile> and target/<triple>/<profile> outputs with .cargo-lock use the executable directory. Within the resource root, the installed CLI is selected from bin/ first, then resources/bin/. Isolated POSIX pairs must carry their own resources rather than use a separate system installation. The run freezes the app's runtime and the CLI file, then checks the copied pair. Libraries or resources adjacent to the separate CLI are not imported. Windows pairs also retain the declared agent_prompts/ and _up_/scripts/ folders at their original relative paths. Repeated freezes preserve existing resource files and copy missing leaves; unrelated compiler outputs stay outside the frozen runtime.

e2e-native/tests/artifact-presentation-native.test.mjs proves the artifact control path with real IPC: an isolated mock agent presents an authorized Markdown file through the built CLI, Wardian routes a non-focused Files tab, the renderer opens the canonical file, and the same artifact restores after a native app relaunch.

Running ​

Run the native mock-provider suite:

bash
npm run test:e2e:native

On Windows, the harness launches the installed Tauri CLI through Node for builds. This preserves the inline build configuration when a test is launched directly with Node, without npm's environment variables. The test runner also passes test paths directly to Node, including paths containing spaces.

For rapid iteration after you already have a current native build, reuse the existing binary instead of rebuilding on every run:

bash
npm run test:e2e:native:fast

Use the fast command only after rebuilding the native binary for Rust or bundled-asset changes:

bash
npm run tauri -- build --debug --no-bundle

You can also target a specific file:

bash
npm run test:e2e:native:fast -- e2e-native/tests/opencode-native.test.mjs

HTTP fixture cleanup ​

Browser fixtures use closeHttpFixture from e2e-native/lib/httpFixture.mjs to stop their HTTP listener and close only that server's accepted HTTP connections. The close starts before the connections are drained. A live browser can keep a socket open without sending a request, so awaiting server.close() alone can block the later after hook that stops the browser. Register cleanup after the fixture starts; call it after the fixture's assertions have completed.

The CI-tier connection and after-hook regression runs without Tauri or a provider. Run it directly with Node:

sh
node --test e2e-native/tests/http-fixture-cleanup.test.mjs

PowerShell:

powershell
node --test e2e-native/tests/http-fixture-cleanup.test.mjs

Runner input contracts before App launch ​

When changing a native-run wrapper, test the runtime producer's serialized output through the exact frozen consumer and shell that the run will invoke. Use harmless owned fixtures before occupying the shared native slot. Replace only the final App/provider launch; keep the complete preceding input path unchanged, including argument construction, serialization, initialization, every actual file read and all admission checks.

A SourceOnly admission mode must call the same runtime producer and validate its complete output. Rebuilding input from an already accepted parent inventory misses runtime-only entries. Sharing a consumer, passing a producer/consumer composition test or checking an initial input prefix proves only that subset. For example, a copied package can pass composition while omitting the transport plan, protocol manifest or strict-root initialization that a later pre-App branch reads. Exercise those reads and validations in the same whole-path positive, without bypasses or reconstructed intermediate inputs.

Retain that complete unchanged pre-App positive and focused negatives for the changed boundary in the project's normal tests and CI. Cover:

  • Zero, one and multiple arguments or entries. Empty strings, spaces, quotes and trailing backslashes must survive the actual serialized argument consumer.
  • Identity timestamps as literal strings, preserving every fractional digit. Compare process births from the same API. A lower-precision projection cannot establish an exact birth match or physical closure.
  • Pins for every literal path the consumer actually reads, including required aliases, metadata, transport plans and protocol manifests. Enforce the required file attributes, including read-only metadata, as well as hashes.
  • Strict ancestor and root validation against the real package layout. An equivalent filename or a file outside the admitted root does not qualify the literal read. Exercise missing reads, wrong ancestors/roots and changed attributes as focused negatives, with refusal before App launch.
  • Outer control metadata outside the inner run's reserved output namespace. Test collisions as negatives; preserve the namespace guard.

The maintained Windows supervisor regression is a bounded argv contract test:

sh
node --test scripts/native-e2e-windows-supervisor.test.mjs

PowerShell (Windows):

powershell
node --test scripts/native-e2e-windows-supervisor.test.mjs

It uses the actual createWindowsSupervisorPlan producer and native-e2e-windows-supervisor.ps1 consumer under Windows PowerShell 5.1 (powershell.exe). An owned compiled recorder accepts genuinely zero child arguments; the suite checks 0/1/N round trips, literal seven-fraction ISO identity strings, child exit codes, malformed input, launch failure and Job cleanup. The existing Windows CI job runs it after Node setup; non-Windows runs skip it explicitly. Preserve the canonical shell, argument quoting and Job ownership. PowerShell 7-only JSON switches such as -DateKind do not apply to this consumer.

These tests establish the supervisor boundary only. They do not qualify a private QA package's complete runtime producer or pre-App path, and the zero-argument fix does not repair missing inputs, initialization or namespace conflicts elsewhere. Label composition and prefix checks as partial evidence. Even a complete pre-App contract pass does not establish normal product behavior. This contract adds no permission or acknowledgement step for unchanged, already-qualified work.

Concurrent runs ​

Two native runs can execute at the same time. Every run claims its own resources automatically, so nothing has to be chosen by hand:

  • Ports. The driver and its child native driver each get a port reserved from the OS at startup, passed through as --port and --native-port. There is no fixed port any more, so a second run cannot collide with the first.
  • Home. Each run gets wardian-e2e-native-<runId> under the OS temp directory. The runner pins that value once and hands it to every child, so the runner and harness never disagree about which home is in play.
  • Cleanup. A run ends only the process tree it started. It does not search for processes by command line, so an unrelated process is never terminated because its command line happens to mention the home path.

Both npm run test:e2e:native and the runner script activate this. Nothing needs a free port picked in advance.

Using an explicit home ​

Set WARDIAN_E2E_NATIVE_HOME to keep a run's state for inspection:

bash
WARDIAN_E2E_NATIVE_HOME=/tmp/wardian-e2e-native-inspect npm run test:e2e:native

PowerShell:

powershell
$env:WARDIAN_E2E_NATIVE_HOME = "$env:TEMP\wardian-e2e-native-inspect"
npm run test:e2e:native

The path must be under the OS temp directory and begin with wardian-e2e-native, or sit under .tmp/e2e-native in the repository. The harness resets the home it is given, and that guard is what stops a reset from reaching an unrelated directory. The harness checks this guard before acquiring the home lock. Rejecting an unsafe path leaves its directories, contents, and existing lock unchanged.

A run writes .native-e2e-lock/owner.json into its home and removes it on exit. The harness snapshots the lock object returned by acquireHomeLock(...).lock. When a supervised child finishes a proven shutdown, it releases the lock only if the current claim is its own. A live claim with the same run id and the same pid as that snapshot is the upstream runner's claim; the child records home_lock_released: false and home_lock_release_deferred: true, then the runner releases the claim after the child exits. A normal child-owned release records home_lock_release_deferred: false. A foreign, malformed, or changed claim remains locked and fails cleanup confirmation.

A second run pointed at the same explicit home is refused before anything is deleted or terminated. Give each concurrent run its own home, or leave the variable unset.

If a previous run crashed, its lock is left behind. The next run reports the stale lock and proceeds. It does not terminate processes that run may have orphaned, because they cannot be told apart from unrelated processes without the kind of command-line matching that caused cross-run kills.

Endpoint ownership ​

A run refuses to use a driver endpoint it cannot prove it owns. After the port answers, the harness resolves the pid listening on it and requires that pid to be the driver it started or one of that driver's children. A live listener left by something else fails the run instead of being adopted, and a driver that exits before binding is reported rather than treated as ready.

Some capture and chat helper scripts still assume the old fixed port. They read the port from the harness session instead: harness.driverPort and harness.nativeDriverPort.

For manual validation, run the same native harness in visible watch mode:

bash
npm run test:e2e:native:watch -- e2e-native/tests/cli-shared-state-native.test.mjs

Watch mode reuses the current native binary, prints named test steps, pauses briefly between watch steps, and keeps the WebView open until you press Enter. Set WARDIAN_E2E_STEP_DELAY_MS to change the pause length, or set WARDIAN_E2E_WATCH_KEEP_OPEN=0 to close the window automatically.

If the WebView shows a localhost:1420 connection failure, the fast/watch runner is using a binary that expects the Vite dev server. Either start npm run vite or rebuild the native debug app first:

bash
npm run tauri -- build --debug --no-bundle

Use this layer when validating:

  • workbench JSON persistence, backup rotation, restart recovery, or safe mode
  • terminal scrollback or renderer behavior
  • Tauri invoke commands
  • PTY-backed input/output
  • desktop/remote presentation ownership, geometry, snapshots, or event order
  • provider spawn, resume, or shutdown behavior
  • app and CLI shared-state behavior through isolated WARDIAN_HOME
  • automation behavior that depends on native runtime state

The CLI shared-state smoke can be run directly:

bash
npm run test:e2e:native:fast -- e2e-native/tests/cli-shared-state-native.test.mjs

It starts the native app with an isolated WARDIAN_HOME, creates agents through both Tauri IPC and live CLI control, then runs the local wardian-cli binary against the same home. The smoke asserts live app state is readable, explicit agent spawn --provider --class works, agent wait --until observes the requested status, and lifecycle commands affect the running app. The CLI still falls back to state.db when the desktop app is not running.

The control fixture captures Mock Init events before each spawn, clone and resume. It requires the launch's agent and provider identities, a fresh Init, and release of persisted conversation exclusion before seeding status or exercising lifecycle commands. A seeded visible status does not prove startup completion. Skip-build and explicit paired runs consume the harness's frozen CLI without another Cargo build; the control case records the consumed app and CLI hashes and sizes.

Workbench and Terminal Presentation Package ​

After building current native assets, run the focused package:

bash
npm run tauri -- build --debug --no-bundle
npm run test:e2e:native:workbench

It proves four native boundaries with an isolated Wardian home:

  • exact workbench primary/backup bytes, restart, corrupt-file fallback, and future-schema preservation;
  • desktop terminal owner/mirror activation races, timeouts, stale lease rejection, ordered output, and stable canonical geometry;
  • closing all workbench presentations leaves the agent runtime live, while safe mode preserves the durable split tree byte-for-byte;
  • authenticated desktop-to-remote-to-desktop transfer and socket liveness.

The related browser package remains useful for navigation and layout only:

bash
npm run test:e2e:workbench

Do not use its mocked transport to claim native file durability, PTY resize, lease enforcement, remote authentication, or provider behavior.

Real Providers ​

Real-provider checks are opt-in. Keep them isolated and use them for every provider-runtime claim. The mock provider can prove Wardian-owned behavior such as routing, queueing, rendering, state sharing, and deterministic PTY plumbing. It cannot prove that a real provider CLI accepts input, exposes a ready prompt, clears a compose field, resumes a session, or responds through its real transcript path.

Never make a mock-backed test spoof a real provider identity to validate Codex, Claude, Gemini, OpenCode, Antigravity, or Pi behavior. If a test would need that, write an opt-in real-provider native E2E test or leave a skipped test with // @real-provider-only.

bash
WARDIAN_E2E_REAL_OPENCODE=1 WARDIAN_E2E_REAL_WORKSPACE=<absolute-workspace-path> npm run test:e2e:native

For the opt-in Antigravity smoke:

bash
WARDIAN_E2E_REAL_ANTIGRAVITY=1 WARDIAN_E2E_REAL_WORKSPACE=<absolute-workspace-path> npm run test:e2e:native:fast -- e2e-native/tests/antigravity-native.test.mjs

On PowerShell, use the same placeholder with a Windows absolute path:

powershell
$env:WARDIAN_E2E_REAL_OPENCODE='1'
$env:WARDIAN_E2E_REAL_WORKSPACE='<absolute-workspace-path>'
npm run test:e2e:native

PowerShell Antigravity smoke:

powershell
$env:WARDIAN_E2E_REAL_ANTIGRAVITY='1'
$env:WARDIAN_E2E_REAL_WORKSPACE='<absolute-workspace-path>'
npm run test:e2e:native:fast -- e2e-native/tests/antigravity-native.test.mjs

The harness uses an isolated WARDIAN_HOME by default, so native E2E runs should not modify production <wardian-home> state.

Claude Inbox Stop-hook acceptance ​

This one-turn probe verifies that an interactive Claude Stop event produces one durable canonical Inbox item, and that a workspace-defined Stop hook runs alongside Wardian's injected hook. It does not modify the user's Claude settings or copy authentication data. Select an explicitly preflighted model; haiku is the lowest-priced model in Anthropic's current public API lineup.

PowerShell:

powershell
$env:WARDIAN_E2E_REAL_CLAUDE_INBOX = '1'
$env:WARDIAN_E2E_CLAUDE_INBOX_MODEL = 'haiku'
$env:WARDIAN_NATIVE_SKIP_BUILD = '1'
$env:WARDIAN_NATIVE_APP = '<absolute-isolated-artifact-path>'
node scripts/run-native-e2e-fast.mjs e2e-native/tests/claude-inbox-stop-real-native.test.mjs

The test refreshes the catalog before provider spawn, submits exactly one short prompt, correlates the provider-authored transcript with the hook's prompt_id, and checks the persisted response and event timestamp source. It writes private evidence under the isolated test home. Run provider probes serially.

Real Provider PTY Rendering Lab ​

Use the rendering lab when investigating issue #110 class failures: line wrapping, row bleed, stale terminal geometry after resize, resize/minimize/maximize lag, or whole-app slowdown with real provider PTYs. This suite is real-provider-only and defaults to Codex and Claude when WARDIAN_E2E_RENDERING_PROVIDERS is unset.

POSIX shell:

bash
export WARDIAN_E2E_REAL_RENDERING=1
export WARDIAN_E2E_RENDERING_PROVIDERS=codex,claude
export WARDIAN_E2E_REAL_WORKSPACE=<absolute-workspace-path>
export WARDIAN_E2E_RENDERING_EXPECT_RESPONSE_TEXT=WARDIAN_SCROLL_050
VITE_WARDIAN_TERMINAL_DEBUG=1 npm run tauri -- build --debug --no-bundle
npm run test:e2e:native:fast -- e2e-native/tests/real-provider-rendering-native.test.mjs

PowerShell:

powershell
$env:WARDIAN_E2E_REAL_RENDERING = '1'
$env:WARDIAN_E2E_RENDERING_PROVIDERS = 'codex,claude'
$env:WARDIAN_E2E_REAL_WORKSPACE = '<absolute-workspace-path>'
$env:WARDIAN_E2E_RENDERING_EXPECT_RESPONSE_TEXT = 'WARDIAN_SCROLL_050'
$env:VITE_WARDIAN_TERMINAL_DEBUG = '1'
npm run tauri -- build --debug --no-bundle
npm run test:e2e:native:fast -- e2e-native/tests/real-provider-rendering-native.test.mjs

For screenshot evidence that may be published, set WARDIAN_E2E_RENDERING_MASK_PRIVATE_ROWS=1. The harness hides terminal rows containing the workspace directory or managed-home path only while capturing PNG screenshots, then restores the live terminal. The JSON and raw-output artifacts remain private and may still contain paths. Set WARDIAN_E2E_RENDERING_SUBMIT_INPUT=0 to inspect a typed draft without sending a provider turn.

The provider list also accepts pi. Include it in WARDIAN_E2E_RENDERING_PROVIDERS for a Pi rendering run, and set WARDIAN_E2E_RENDERING_PI_MODEL when the test should use a specific configured Pi model.

Provider function conformance ​

The provider function matrix records real acceptance separately from fixture tests, setup blockers, and intentional omissions. Keep paid provider runs serialized under their execution owner. The shared harness allocates per-session WebDriver ports, verifies listener ownership, and freezes the app and CLI into the run-owned home. Supply an already packaged artifact and an explicit model from the current provider catalog. The suite does not build the application or choose a default model.

bash
WARDIAN_E2E_REAL_CHAT_CONFORMANCE=1 \
WARDIAN_E2E_CHAT_PROVIDERS=claude \
WARDIAN_E2E_CHAT_CLAUDE_MODEL='<verified-low-cost-model>' \
WARDIAN_NATIVE_APP='<absolute-packaged-app-path>' \
node scripts/run-native-e2e.mjs e2e-native/tests/provider-chat-conformance-real-native.test.mjs

PowerShell:

powershell
$env:WARDIAN_E2E_REAL_CHAT_CONFORMANCE = '1'
$env:WARDIAN_E2E_CHAT_PROVIDERS = 'claude'
$env:WARDIAN_E2E_CHAT_CLAUDE_MODEL = '<verified-low-cost-model>'
$env:WARDIAN_NATIVE_APP = '<absolute-packaged-app-path>'
node scripts/run-native-e2e.mjs e2e-native/tests/provider-chat-conformance-real-native.test.mjs

Provider selection accepts claude, codex, opencode, antigravity, and pi. Set the corresponding WARDIAN_E2E_CHAT_<PROVIDER>_MODEL for every selected provider. WARDIAN_E2E_CHAT_EXTENDED_INPUTS=1 adds multiline, trailing-newline, and long-paste assertions. WARDIAN_E2E_CHAT_CATALOG_ONLY=1 performs discovery without starting provider turns. WARDIAN_E2E_CHAT_CODEX_STARTUP_DIAGNOSTIC=1 allows 180 seconds instead of 90 before any prompt; it does not change delivery timeouts or resolve a model choice automatically.

On Windows, the Codex scratch-read case explicitly uses cmd.exe. Native evidence showed that the installed WindowsApps PowerShell could not launch inside Codex's sandbox. This constraint is recorded with the result; the test does not change sandbox or approval policy.

The separate context suite uses WARDIAN_E2E_REAL_CONTEXT_PERMISSIONS=1, one WARDIAN_E2E_CONTEXT_PROVIDERS value, and its explicit WARDIAN_E2E_CONTEXT_<PROVIDER>_MODEL. It exercises managed class instructions, Wardian skill deployment, approval rejection before provider invocation, and inherited headless session boundaries. Independent provider-native records bind each headless answer to its request and session. Normalized automation output or saved agent configuration alone cannot establish that identity.

For canonical task delivery, use the maintained provider-delivery native acceptance procedure. The separate Codex v2 Native acceptance procedure covers the Codex v2 sender/receiver suite; its configuration and claims are separate from this provider-delivery matrix. The documented Claude/Antigravity composer exceptions remain separate results.

The delivery suite limits Claude to Bash and an empty strict MCP configuration, and disables its cross-session messaging tools. This qualifies the canonical CLI reply through the composer exception; it does not qualify Claude MCP delivery or provider-native collaboration tools. These limits apply only to the test agent.

The older provider-native-broker-real-native.test.mjs integration path uses the retired wardian send command. Its retained reports describe historical builds; that path cannot qualify the current canonical messaging implementation. Run real-provider suites through scripts/run-native-e2e.mjs so the supervisor owns the isolated home lock and child processes.

These suites retain disposable Wardian homes under .tmp/e2e-native/, with artifact/harness hashes and per-case observations. Raw logs and screenshots stay local. Publish only sanitized evidence. A native provider can also write to its ordinary session store; do not describe Wardian-home isolation as isolation of all provider-owned history.

Freeze a packaged runtime before verification commands that compile Cargo test targets. Those commands can replace the same debug executable with a dev-URL build. Check the artifact hash when associating a run with a source change.

Provider-delivery native acceptance ​

The maintained provider-delivery real-native harness is e2e-native/tests/provider-delivery-real-native.test.mjs. Its real-provider opt-in and provider/case/native-route settings are documented in Provider readiness. Against a frozen packaged app and matching CLI, a bounded selected-provider run is:

bash
WARDIAN_E2E_REAL_DELIVERY=1 \
WARDIAN_E2E_DELIVERY_ALLOW_PARTIAL=1 \
WARDIAN_E2E_DELIVERY_PROVIDERS=<provider-list> \
WARDIAN_E2E_DELIVERY_CASES=<case-list> \
WARDIAN_E2E_DELIVERY_NATIVE_PROVIDERS=<native-provider-list> \
WARDIAN_E2E_REAL_WORKSPACE=<absolute-workspace-path> \
WARDIAN_NATIVE_SKIP_BUILD=1 \
WARDIAN_NATIVE_APP=<absolute-packaged-app-path> \
npm run test:e2e:native:fast -- e2e-native/tests/provider-delivery-real-native.test.mjs

PowerShell:

powershell
$env:WARDIAN_E2E_REAL_DELIVERY = '1'
$env:WARDIAN_E2E_DELIVERY_ALLOW_PARTIAL = '1'
$env:WARDIAN_E2E_DELIVERY_PROVIDERS = '<provider-list>'
$env:WARDIAN_E2E_DELIVERY_CASES = '<case-list>'
$env:WARDIAN_E2E_DELIVERY_NATIVE_PROVIDERS = '<native-provider-list>'
$env:WARDIAN_E2E_REAL_WORKSPACE = '<absolute-workspace-path>'
$env:WARDIAN_NATIVE_SKIP_BUILD = '1'
$env:WARDIAN_NATIVE_APP = '<absolute-packaged-app-path>'
npm run test:e2e:native:fast -- e2e-native/tests/provider-delivery-real-native.test.mjs

The Provider readiness guide documents the full matrix, case names, and Claude/Antigravity composer-exception setting. Acceptance requires an actual provider-authored reply correlated to the Wardian request and bound to the observed provider session, generation, and native evidence. Setup, a task claim, a receipt, a terminal echo, a historical receipt, or mock-provider output alone does not qualify as native acceptance.

Long-habitat acceptance gate ​

Issue #1285 adds an explicit opt-in gate to the maintained provider-delivery suite. Set WARDIAN_E2E_ASSERT_LONG_HABITAT=1 only when the selected run includes both codex and claude. The managed habitat path under test is <wardian-home>/agents/<agent-id>/habitat/workspace; its computed UTF-16 path must exceed the observed Windows limit. WARDIAN_E2E_REAL_WORKSPACE may remain a short external project folder. The gate checks the returned AgentConfig.folder and records the managed path length separately from the actual provider CWD before the first paid provider prompt. Claude uses the managed habitat path through the owned short alias and therefore requires the alias record, alias workspace target, pause/resume reuse, and exact cleanup after owned agent deletion. Codex uses the short external project folder as its actual provider CWD and requires no habitat alias; it verifies long-home publication, captures the nonempty runtime request values separately from the restored persistent baseline leaves, including explicit absence for leaves that were not persisted. It checks launch-journal cleanup and baseline preservation across pause/resume. For Codex, runtime proof requires one owned rollout whose completed turn has a matching turn_context model and effort, plus the user and provider-authored marker on that same turn. A request hash or terminal footer does not qualify model selection.

Before explicit owned-agent deletion, the helper writes a bounded private report containing hashed transcript fields, provider/archive identity counts, relative habitat paths, cwd-length evidence, and separate baseline presence/value hashes and request value hashes. Codex also retains the bounded selected model/effort and hashed thread/turn identities before deletion. Claude's report includes alias ownership identities and exact cleanup; Codex's report records that no alias was expected and retains the short provider CWD alongside the long managed-habitat path evidence. Reports include the CWD mode, measured managed path length, and measured external/provider CWD length. The long managed path is evidence about Wardian habitat publication; it does not claim that the provider process CWD was over the Windows limit. Both paths verify pause/resume identity, the normal provider turn, joined owned-agent deletion, and retention of the external workspace marker. The helper does not claim that normal agent removal preserves the removed agent's local habitat or provider archive. Direct child GetCurrentDirectory observation is not required; the provider CWD mode is reported from the inspected provider spawn wiring and provider-specific filesystem evidence. A Codex report therefore says explicitly that the managed habitat path is long while the actual provider CWD is the short external workspace.

Choose a disposable Wardian home whose own path is commonly about 205–231 UTF-16 units, then use a short external project folder. The managed agents/<agent-id>/habitat/workspace suffix supplies the boundary pressure while the project path stays short, isolating the managed habitat path from the actual provider CWD.

Run the offline helper exercise without an app or provider:

bash
node --test e2e-native/tests/provider-long-path-evidence.test.mjs

For the paid acceptance run, add the gate and a private evidence root to the maintained provider-delivery command above:

bash
WARDIAN_E2E_ASSERT_LONG_HABITAT=1 \
WARDIAN_E2E_LONG_HABITAT_EVIDENCE_ROOT=<absolute-private-evidence-root> \
WARDIAN_E2E_DELIVERY_ALLOW_PARTIAL=1 \
WARDIAN_E2E_DELIVERY_PROVIDERS=codex,claude \
npm run test:e2e:native:fast -- e2e-native/tests/provider-delivery-real-native.test.mjs

PowerShell:

powershell
$env:WARDIAN_E2E_ASSERT_LONG_HABITAT = '1'
$env:WARDIAN_E2E_LONG_HABITAT_EVIDENCE_ROOT = '<absolute-private-evidence-root>'
$env:WARDIAN_E2E_DELIVERY_ALLOW_PARTIAL = '1'
$env:WARDIAN_E2E_DELIVERY_PROVIDERS = 'codex,claude'
npm run test:e2e:native:fast -- e2e-native/tests/provider-delivery-real-native.test.mjs

Keep the evidence root private and retain its bounded report before the explicit deletion step removes agent-local state. The offline test uses a disposable Windows junction fixture; it does not establish real-provider or packaged-app acceptance.

Attached runs use an input-only block that prevents composer/task input while keeping the attached runtime and native provider owner alive. Privileged control replies remain available; terminal pause or termination is not the acceptance mechanism. Failed or uncertain submissions remain failed or uncertain and are never replayed. Provider outcomes are evaluated separately, so one result does not establish acceptance for every provider.

Wardian retains the canonical ask_... request identity for correlation. OpenCode receives a distinct deterministic msg_... provider message identity at its adapter boundary; evidence must keep those identities bound rather than substituting one for the other.

Rendering evidence ​

The run writes Wardian-side evidence under e2e/screenshots/real-provider-rendering/<run-id>/. Each provider directory includes JSON and screenshots for initial, settled, narrow, resized, wide, card-level card-maximized and card-restored, window-level minimized, restored-after-minimize, maximized, restored-after-maximize, rapid-resize-final, scrolled-top, cleared-immediate, paused, and resumed.

When WARDIAN_E2E_RENDERING_INPUT_TEXT is unset, the lab submits a compact default prompt that asks the provider to print exactly 50 lines from WARDIAN_SCROLL_001 through WARDIAN_SCROLL_050. This keeps the typed prompt from polluting scrollback with a second copy of the audit rows while still requiring WARDIAN_SCROLL_050 in the provider response.

Each state JSON records the xterm parser rows, DOM rows, card/screen/viewport rectangles, terminal debug columns and rows, renderer cell metrics, native window rectangle, browser viewport metrics, app-shell rectangle, screenshot timestamps, artifact timestamps, and row-stability timing. Resize and disruptive-action states also record before/after native window rectangles, before/after browser viewport metrics, before/after terminal debug geometry, action duration, stable-row duration, and any exposed fit or resize counters.

The full native runner enables VITE_WARDIAN_TERMINAL_DEBUG=1 while it builds the app for this lab. test:e2e:native:fast intentionally reuses a prebuilt native app, so build the debug app with that Vite flag first; otherwise the Wardian-side terminal debug snapshots will be unavailable.

The lab checks the terminal debug API after the Agents overview mounts and before spawning audit agents. It records debug-preflight.json in the evidence directory and fails with the build prerequisite when instrumentation is missing. A Rust debug executable can contain a production Vite frontend; setting the flag only when launching that executable does not change its compiled assets. Passing this preflight proves only that instrumentation is available. Real-provider response, presentation ownership, and before/after resize evidence remain required.

When terminal history exists, the lab also captures <state>-scrollback-top and, for deeper history, <state>-scrollback-mid artifacts. These are intentionally not limited to the visible bottom viewport; use them to diagnose row bleed, wrapped-line corruption, stale geometry, and defects that only appear higher in scrollback after resize, card maximize/restore, clear, pause, or resume flows.

Useful tuning variables:

bash
WARDIAN_E2E_RENDERING_WINDOW_WIDTH=1920
WARDIAN_E2E_RENDERING_WINDOW_HEIGHT=1080
WARDIAN_E2E_RENDERING_RESIZED_WIDTH=980
WARDIAN_E2E_RENDERING_RESIZED_HEIGHT=980
WARDIAN_E2E_RENDERING_WIDE_WIDTH=1920
WARDIAN_E2E_RENDERING_WIDE_HEIGHT=1080
WARDIAN_E2E_RENDERING_RAPID_SEQUENCE=1040x900,1320x1040,1160x980,980x980
WARDIAN_E2E_RENDERING_ROW_HEIGHT=900
WARDIAN_E2E_TERMINAL_FONT_SIZE=10
WARDIAN_E2E_RENDERING_STABLE_ROWS_QUIET_MS=750
WARDIAN_E2E_RENDERING_SETTLE_TIMEOUT_MS=10000
WARDIAN_E2E_RENDERING_POST_INPUT_WAIT_MS=0
WARDIAN_E2E_RENDERING_SUBMIT_INPUT=1
WARDIAN_E2E_RENDERING_SUBMIT_SEQUENCE=\r
WARDIAN_E2E_RENDERING_POST_SUBMIT_WAIT_MS=8000
WARDIAN_E2E_RENDERING_EXPECT_RESPONSE_TEXT=<optional-response-marker>
WARDIAN_E2E_RENDERING_CODEX_MODEL=<optional-codex-model>
WARDIAN_E2E_RENDERING_CLAUDE_MODEL=<optional-claude-model>
WARDIAN_E2E_RENDERING_OPENCODE_MODEL=opencode/deepseek-v4-flash-free
WARDIAN_E2E_RENDERING_PI_MODEL=<optional-pi-model>

The default initial, settled, and wide visual states use a 1920x1080 desktop window so PR evidence resembles a normal fullscreen desktop. Keep the smaller resized, narrow, rapid-resize, geometry sweep, and outside-terminal sizes when the test is deliberately proving wrapping, cramped layout, or resize behavior.

The lab sends the configured input text as PTY keystrokes and, by default, submits it with carriage return (\r). This is intentional: the real-provider run must create actual conversation history before resize, scrollback, clear, pause, and resume evidence is captured. When WARDIAN_E2E_RENDERING_INPUT_TEXT is unset, the default prompt asks the provider to print 50 numbered lines from WARDIAN_SCROLL_001 through WARDIAN_SCROLL_050, and the expected response marker defaults to WARDIAN_SCROLL_050. For custom deterministic history checks, make the input text ask for a short marker and set WARDIAN_E2E_RENDERING_EXPECT_RESPONSE_TEXT to that marker. Set WARDIAN_E2E_RENDERING_SUBMIT_INPUT=0 only when intentionally inspecting prompt-editing behavior without a completed provider turn.

For OpenCode rendering runs, the lab defaults to the free remote OpenCode model opencode/deepseek-v4-flash-free so the provider does not fall back to local model backends such as LM Studio. Override WARDIAN_E2E_RENDERING_OPENCODE_MODEL only when intentionally testing a different OpenCode model.

The lab fails the run for obvious Wardian-side evidence problems before manual screenshot inspection: non-empty screenshot requirements, missing fixed audit text after resize, unchanged columns when a resize state expects a geometry change, screen rectangle mismatch against xterm cell metrics, paused-buffer mismatch, and rendered rows that do not stabilize before the settle timeout. Outside-terminal parity is still captured separately with scripts/capture-outside-provider-rendering.ps1 when side-by-side native Windows Terminal evidence is needed.

Windows WebView2 profile isolation ​

Every Windows session started by the native harness creates a unique .webview2-* directory beneath its locked native test home. The driver child receives WEBVIEW2_USER_DATA_FOLDER pointing there; the parent environment and the desktop app's default profile are unchanged. The same path is passed through tauri:options.webviewOptions.userDataFolder, because EdgeDriver otherwise selects its own temporary profile outside the test home. The home lock must belong to the run and have a live owner before the profile is created. Each restart gets a fresh browser profile while Wardian's persisted state remains in the same home.

This applies to all native targets using startNativeSession, including direct test invocations. Profiles follow the existing test-home retention policy; do not clear the default desktop profile or close another app to run tests. Microsoft documents the child environment override as replacing the API's user-data-folder argument. An effective-profile check must observe the owned browser child's --user-data-dir value; an environment value alone does not prove what the runtime used. See also Microsoft's EdgeDriver WebView options.

Released under the MIT License.